Commit 496db8e0 by zhaochengxiang

模型移植

parent dc066ee1
......@@ -24,11 +24,13 @@
"crypto-js": "^4.0.0",
"echarts": "^5.3.1",
"eslint-config-react-app": "^7.0.1",
"immer": "9.0.15",
"immutability-helper": "^3.1.1",
"insert-css": "^2.0.0",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"local-storage": "^2.0.0",
"qiankun": "2.4.0",
"react": "^17.0.1",
"react-contexify": "^5.0.0",
"react-dnd": "^14.0.2",
......@@ -44,7 +46,6 @@
"rxjs": "^7.5.5",
"showdown": "^1.9.1",
"smooth-scroll": "^16.1.3",
"qiankun": "2.4.0",
"typescript": "^4.6.2",
"web-vitals": "^1.0.1"
},
......@@ -75,6 +76,6 @@
"last 1 safari version"
]
},
"proxy": "http://139.198.127.28:17280",
"proxy": "http://192.168.0.111:8189",
"homepage": "http://myhost/data-govern"
}
......@@ -366,3 +366,9 @@ svg {
max-height: 70vh;
overflow: auto;
}
.word-wrap {
overflow: hidden;
word-wrap: break-word;
white-space:normal;
}
\ No newline at end of file
......@@ -309,3 +309,137 @@ export function* requirementBind(payload) {
export function* requirementUnbind(payload) {
return yield call(datamodelerService.requirementUnbind, payload);
}
export function* getRuleTemplateList() {
return yield call(datamodelerService.getRuleTemplateList);
}
export function* addRuleTemplate(payload) {
return yield call(datamodelerService.addRuleTemplate, payload);
}
export function* updateRuleTemplate(payload) {
return yield call(datamodelerService.updateRuleTemplate, payload);
}
export function* deletesRuleTemplate(payload) {
return yield call(datamodelerService.deletesRuleTemplate, payload);
}
export function* deleteRuleTemplate(payload) {
return yield call(datamodelerService.deleteRuleTemplate, payload);
}
export function* getRuleTemplateDetail(payload) {
return yield call(datamodelerService.getRuleTemplateDetail, payload);
}
export function* getRuleTemplateCheckTypes() {
return yield call(datamodelerService.getRuleTemplateCheckTypes);
}
export function* getRuleTemplateAllCheckPropertyTypes() {
return yield call(datamodelerService.getRuleTemplateAllCheckPropertyTypes);
}
export function* getRuleTemplateAllVerifyExpressionTypes() {
return yield call(datamodelerService.getRuleTemplateAllVerifyExpressionTypes);
}
export function* getRuleTemplateAllVertifyExpressions() {
return yield call(datamodelerService.getRuleTemplateAllVertifyExpressions);
}
/* rule catalog */
export function* addRuleCatalog(payload) {
return yield call(datamodelerService.addRuleCatalog, payload)
}
export function* updateRuleCatalog(payload) {
return yield call(datamodelerService.updateRuleCatalog, payload)
}
export function* upRuleCatalog(payload) {
return yield call(datamodelerService.upRuleCatalog, payload)
}
export function* downRuleCatalog(payload) {
return yield call(datamodelerService.downRuleCatalog, payload)
}
export function* deleteRuleCatalog(payload) {
return yield call(datamodelerService.deleteRuleCatalog, payload)
}
export function* getRuleCatalogList() {
return yield call(datamodelerService.getRuleCatalogList)
}
export function* getRuleCatalogEnableList() {
return yield call(datamodelerService.getRuleCatalogEnableList)
}
export function* getRuleCatalogVersionList(payload) {
return yield call(datamodelerService.getRuleCatalogVersionList, payload)
}
export function* compareRuleCatalogVersion(payload) {
return yield call(datamodelerService.compareRuleCatalogVersion, payload)
}
export function* getRuleCatalogStatus() {
return yield call(datamodelerService.getRuleCatalogStatus)
}
/* rule */
export function* addRule(payload) {
return yield call(datamodelerService.addRule, payload)
}
export function* updateRule(payload) {
return yield call(datamodelerService.updateRule, payload)
}
export function* deleteRules(payload) {
return yield call(datamodelerService.deleteRules, payload)
}
export function* getRuleList(payload) {
return yield call(datamodelerService.getRuleList, payload)
}
export function* getRuleAlertTypes() {
return yield call(datamodelerService.getRuleAlertTypes)
}
export function* getRuleStatus() {
return yield call(datamodelerService.getRuleStatus)
}
export function* addComment(payload) {
return yield call(datamodelerService.addComment, payload)
}
export function* deleteComment(payload) {
return yield call(datamodelerService.deleteComment, payload)
}
export function* getComments(payload) {
return yield call(datamodelerService.getComments, payload)
}
export function* uploadCommentFile(payload) {
return yield call(datamodelerService.uploadCommentFile, payload)
}
export function* deleteCommentFile(payload) {
return yield call(datamodelerService.deleteCommentFile, payload)
}
export function* getSearchProperties() {
return yield call(datamodelerService.getSearchProperties)
}
export function* searchModelBySearchProperties(payload) {
return yield call(datamodelerService.searchModelBySearchProperties, payload)
}
\ No newline at end of file
import { PostFile, GetJSON, PostJSON, Post, Get } from "../util/axios"
import { PostFile, GetJSON, PostJSON, Post, Get, Delete } from "../util/axios"
export function loadDataModelCatalog() {
return GetJSON("/datamodeler/easyDataModelerCURD/loadDataModelCatalog");
......@@ -264,3 +264,138 @@ export function requirementBind(payload) {
export function requirementUnbind(payload) {
return PostJSON("/datarequirement/dataReqTechJobAssoc/unbinding", payload);
}
export function getRuleTemplateList() {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/list");
}
/* rule template */
export function addRuleTemplate(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleTemplate/add", payload);
}
export function updateRuleTemplate(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleTemplate/update", payload);
}
export function deletesRuleTemplate(payload) {
return Delete("/datamodeler/easyDataModelerRuleTemplate/dels", payload);
}
export function deleteRuleTemplate(payload) {
return Delete("/datamodeler/easyDataModelerRuleTemplate/del", payload);
}
export function getRuleTemplateDetail(payload) {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getById", payload);
}
export function getRuleTemplateCheckTypes() {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getCheckTypes");
}
export function getRuleTemplateAllCheckPropertyTypes() {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllCheckPropertyTypes");
}
export function getRuleTemplateAllVerifyExpressionTypes() {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressionTypes")
}
export function getRuleTemplateAllVertifyExpressions() {
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressions")
}
/* rule catalog */
export function addRuleCatalog(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleCatalog/add", payload)
}
export function updateRuleCatalog(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleCatalog/update", payload)
}
export function upRuleCatalog(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleCatalog/up", payload)
}
export function downRuleCatalog(payload) {
return PostJSON("/datamodeler/easyDataModelerRuleCatalog/down", payload)
}
export function deleteRuleCatalog(payload) {
return Delete("/datamodeler/easyDataModelerRuleCatalog/del", payload)
}
export function getRuleCatalogList() {
return GetJSON("/datamodeler/easyDataModelerRuleCatalog/list")
}
export function getRuleCatalogEnableList() {
return GetJSON("/datamodeler/easyDataModelerRuleCatalog/enableList")
}
export function getRuleCatalogVersionList(payload) {
return GetJSON("/datamodeler/easyDataModelerRuleCatalog/versionList", payload)
}
export function compareRuleCatalogVersion(payload) {
return GetJSON("/datamodeler/easyDataModelerRuleCatalog/compare", payload)
}
export function getRuleCatalogStatus() {
return GetJSON("/datamodeler/easyDataModelerRuleCatalog/getRuleStatus")
}
/* rule */
export function addRule(payload) {
return PostJSON("/datamodeler/easyDataModelerRule/add", payload)
}
export function updateRule(payload) {
return PostJSON("/datamodeler/easyDataModelerRule/update", payload)
}
export function deleteRules(payload) {
return Delete("/datamodeler/easyDataModelerRule/dels", payload)
}
export function getRuleList(payload) {
return GetJSON("/datamodeler/easyDataModelerRule/getListByCatalogId", payload)
}
export function getRuleAlertTypes() {
return GetJSON("/datamodeler/easyDataModelerRule/getRuleAlertTypes")
}
export function getRuleStatus() {
return GetJSON("/datamodeler/easyDataModelerRule/getRuleStatus")
}
export function addComment(payload) {
return PostJSON("/datamodelercomment/comment/add", payload)
}
export function deleteComment(payload) {
return Delete("/datamodelercomment/comment/del", payload)
}
export function getComments(payload) {
return GetJSON("/datamodelercomment/comment/list", payload)
}
export function uploadCommentFile(payload) {
return PostFile("/datamodelercomment/file/upload", payload)
}
export function deleteCommentFile(payload) {
return Delete("/datamodelercomment/file/del", payload)
}
export function getSearchProperties() {
return GetJSON("/datamodeler/easyDataModelerCURD/getModelSearchProperties")
}
export function searchModelBySearchProperties(payload) {
return PostJSON("/datamodeler/easyDataModelerCURD/searchEasyDataModelerDataModelsByModelSearchProperties", payload);
}
\ No newline at end of file
import React, { useMemo } from 'react';
import { Button, Tooltip } from 'antd';
const FC = (props) => {
const { permissionKey, permissions, defaultPermission, disabled, tip, ...restProps } = props;
const havePermission = useMemo(() => {
let _havePermission = true;
if (defaultPermission===false) {
_havePermission = defaultPermission;
} else if (permissionKey) {
const index = (permissions||[]).findIndex(item => item === permissionKey);
_havePermission = (index !== -1);
}
return _havePermission;
}, [permissionKey, permissions, defaultPermission])
return (
<Tooltip title={havePermission?tip:'暂无权限'}>
<Button disabled={!havePermission||disabled} {...restProps} />
</Tooltip>
)
}
export default FC;
\ No newline at end of file
import React, { useMemo } from 'react';
import { Tooltip, Menu } from 'antd';
const FC = (props) => {
const { permissionKey, permissions, defaultPermission, disabled, tip, children, ...restProps } = props;
const havePermission = useMemo(() => {
let _havePermission = true;
if (defaultPermission===false) {
_havePermission = defaultPermission;
} else if (permissionKey) {
const index = (permissions||[]).findIndex(item => item === permissionKey);
_havePermission = (index !== -1);
}
return _havePermission;
}, [permissionKey, permissions, defaultPermission])
return (
<Menu.Item disabled={!havePermission||disabled} {...restProps}>
<Tooltip title={havePermission?tip:'暂无权限'}>
{children}
</Tooltip>
</Menu.Item>
)
}
export default FC;
\ No newline at end of file
import React, { useMemo } from 'react';
import { Tooltip } from 'antd';
import { Item as RcItem } from "react-contexify";
const FC = (props) => {
const { permissionKey, permissions, defaultPermission, disabled, tip, children, ...restProps } = props;
const havePermission = useMemo(() => {
let _havePermission = true;
if (defaultPermission===false) {
_havePermission = defaultPermission;
} else if (permissionKey) {
const index = (permissions||[]).findIndex(item => item === permissionKey);
_havePermission = (index !== -1);
}
return _havePermission;
}, [permissionKey, permissions, defaultPermission])
return (
<RcItem disabled={!havePermission||disabled} {...restProps}>
<Tooltip title={havePermission?tip:'暂无权限'}>
{children}
</Tooltip>
</RcItem>
)
}
export default FC;
\ No newline at end of file
// @import (reference) "@/less" 只导入变量
@ant-prefix: foobar;
.ssetable {
.react-resizable{
position: relative;
background-clip: padding-box;
user-select: none;
}
.react-resizable-handle {
position: absolute;
width: 10px;
height: 100%;
bottom: 0;
right: -5px;
cursor: col-resize;
// background: red;
z-index: 999;
}
.@{ant-prefix}-table-thead > tr > th {
padding: 8px 8px !important;
white-space: nowrap;
text-overflow: ellipsis;
}
// .@{ant-prefix}-table-tbody > tr > td {
// padding: 12px 8px !important;
// }
.@{ant-prefix}-table-tbody > .@{ant-prefix}-table-measure-row > td {
padding: 0px !important;
}
.@{ant-prefix}-table-tbody > tr .@{ant-prefix}-table-row-selected > td {
background: #fff !important;
}
tr.@{ant-prefix}-table-expanded-row > td {
padding: 0 !important;
background: #fff !important;
}
tr.@{ant-prefix}-table-expanded-row {
.@{ant-prefix}-table {
margin: 0 !important;
}
}
.@{ant-prefix}-table-thead > tr > th {
background-color: #F2F5FC !important;
}
}
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { PaginationProps, Table, TableColumnType, TableProps } from 'antd'
import type { SortOrder, ColumnGroupType, ColumnType } from 'antd/lib/table/interface'
import { Resizable } from 'react-resizable'
import ResizeObserver from 'rc-resize-observer'
import produce from 'immer'
import { nanoid } from 'nanoid'
import { getScrollbarWidth } from '..'
import { defaultPageSizeOptions } from '../hooks/page'
import ToolTip from './Tooltip'
import { Menu, useContextMenu } from 'react-contexify'
import { generateUUID } from ".."
import PermissionRcItem from './PermissionRcItem'
import 'react-contexify/dist/ReactContexify.css'
import './Table.less'
interface Props<RowType> {
width: number
maxHeight?: string | number
pageSize: number
pageNum: number
total: number
pageSizeOptions: string[] | number[]
onPaginate: PaginationProps["onChange"]
rowSelection?: TableProps<RowType>
bodyCell: any
extraColWidth?: number
menuData: string[]
menuPermissions: string[]
onMenuItemClick: Function
onRowClick: Function
shouldRowContextMenu: Function
}
const scrollbarWidth = getScrollbarWidth()
function FC<RowType extends object = any>({ width, maxHeight, pageSize, pageNum, total, pageSizeOptions, onPaginate, columns, bodyCell, extraColWidth = 0, menuData, menuPermissions, onMenuItemClick, onRowClick, shouldRowContextMenu, ...rest }: TableProps<RowType> & Props<RowType>) {
type Columns = typeof columns
const MENU_ID = generateUUID()
const [tableWidth, setTableWidth] = useState(0)
const { show } = useContextMenu({
id: MENU_ID,
})
const paddingCol = useRef<TableColumnType<any>>({
key: 'padding',
width: 0,
render: () => undefined
})
const handleResize = (index: number) => (e: any, { size }: any) => {
setCols((prevCols) => {
const nextColumns = [...(prevCols ?? [])];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
return nextColumns;
});
};
const [cols, setCols] = useState<Columns>()
useEffect(() => {
if (!!columns && tableWidth > 0) {
const contentWidth = getWidth(width ?? tableWidth,extraColWidth)
setDefaultWidth(columns, contentWidth)
paddingCol.current.width = 0
const cols = columns
.map((col, index) => {
const render = getRender(col);
const colWidth = col.width ?? 100;
return {
...col, /* colRef: createRef(), */ width: colWidth, render, ellipsis: true,
onHeaderCell: (column: any) => ({
width: column.width,
// colRef: column.colRef,
onResize: handleResize(index),
}),
};
})
setCols(cols)
}
}, [columns, tableWidth, width, extraColWidth])
// add padding column
const cols1 = useMemo(() => !!cols ? [...cols, paddingCol.current] : undefined, [cols, pageSize, pageNum])
const scroll = useMemo(() => (maxHeight === undefined || total === 0 /* fix 暂无数据显示滚动条? */ ? { x: '100%' } : { y: maxHeight, x: '100%' /* 'max-content' 所有列显示全宽*/ }), [maxHeight, total])
const ref = useRef<HTMLDivElement>(null)
const [rightClickNode, setRightClickNode] = React.useState<any>(undefined)
const handleContextMenu = (event: any, node: any) => {
show(event, {
position: {
x: event.clientX + 30,
y: event.clientY - 10
}
})
}
return (
<ResizeObserver
onResize={(size) => {
setTableWidth(size?.width)
}}
>
<Table
ref={ref}
className="ssetable"
size='middle'
rowKey="id" // could be overrided in rest.
showSorterTooltip={false}
columns={cols1}
pagination={{
pageSizeOptions: pageSizeOptions ?? defaultPageSizeOptions, showSizeChanger: true,
position: ['bottomCenter'], pageSize, current: pageNum, total, onChange: onPaginate,
showTotal: total => `共 ${total} 条`,
}}
components={{
body: {
cell: bodyCell ?? null
},
header: {
cell: ResizableTitle,
},
}}
onRow={(record, index) => {
return {
onClick: event => {
onRowClick?.(event, record)
},
onContextMenu: event => {
setRightClickNode(record)
if (shouldRowContextMenu?.(record)) {
handleContextMenu(event, record)
}
},
}
}}
scroll={scroll}
{...rest}
/>
<Menu id={MENU_ID}>
{
menuData?.map(item => <PermissionRcItem
key={item}
id={item}
disabled={rightClickNode?.[`${item}`]?.disabled}
tip={rightClickNode?.[`${item}`]?.tip}
permissions={rightClickNode?.permissions||menuPermissions}
permissionKey={item}
onClick={() => onMenuItemClick?.(item, rightClickNode)}>
{item}
</PermissionRcItem>)
}
</Menu>
</ResizeObserver>
)
}
// 可变列宽
const ResizableTitle = (props: any) => {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={e => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} />
</Resizable>
);
};
function setDefaultWidth(columns: any[], width: number) {
let rowWidth = 0, count = 0
for (const col of columns) {
if (typeof col.width === 'number') {
rowWidth += col.width
} else {
count++
}
}
if (count > 0) {
const defaultW = (rowWidth > width ? 0 : width - rowWidth) / count
for (const col of columns) {
if (typeof col.width !== 'number') {
col.width = (defaultW < 100) ? 100 : defaultW
}
}
}
}
function getWidth(tableWidth: number, extraColWidth: number): number {
return tableWidth - scrollbarWidth - extraColWidth // scrollbar width, checkbox column
}
function getRender<T>(col: ColumnGroupType<T> | ColumnType<T>) {
return (value: any, record: T, index: number) => {
// 用户自定render
if (col.render) {
const rs = col.render(value, record, index)
if (typeof rs === 'string') {
return (<ToolTip msg={rs} defaultStyle />)
}
return rs
} else {
return (<ToolTip msg={value} defaultStyle />)
}
}
}
export default FC
export function getStringSorter<T>(k: keyof T) {
return (a: T, b: T, sortOrder?: SortOrder) => {
const aVal = a[k], bVal = b[k]
if (typeof aVal === 'string' && typeof bVal === 'string')
return aVal.localeCompare(bVal);
return 0
}
}
export function getNumberSorter<T>(k: keyof T) {
return (a: T, b: T, sortOrder?: SortOrder) => {
const aVal = a[k], bVal = b[k]
if (typeof aVal === 'number' && typeof bVal === 'number')
return aVal - bVal;
return 0
}
}
export function generateId<T = any>(data: T[], id = 'id') {
return produce(data, (draft) => {
draft?.forEach((row: any) => {
row[id] = nanoid()
})
})
}
export function generateIdFromArrayRow<T extends unknown[]>(data?: T[], id = 'id'): unknown[] | undefined {
return data?.map((row, i) => {
const obj: { [key: string]: unknown } = { [id]: nanoid() }
row.forEach((cell, j) => {
obj[j] = cell
})
return obj
})
}
export function filterRows<RowType = unknown>(rows: RowType[] | undefined, filter: (row: RowType) => boolean) {
if (rows) {
const matched = rows.filter(filter)
return matched
}
return rows
}
\ No newline at end of file
import React, { CSSProperties, HTMLAttributes, useEffect, useRef, useState } from "react";
import { Tooltip as Tooltip_, TooltipProps } from "antd";
import type { TooltipPlacement } from "antd/es/tooltip";
const DefaultStyle: CSSProperties = {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
// backgroundColor: "red"
}
export default function ({ msg, defaultStyle, style, placement, children, ...rest }: { msg: string | JSX.Element, defaultStyle?: boolean, placement?: TooltipPlacement } & HTMLAttributes<any>) {
const wrapper = useRef<HTMLDivElement>(null);
const text = useRef<HTMLSpanElement>(null);
const [toolTip, setToolTip] = useState(false);
useEffect(() => {
const wrapperRect = wrapper.current!.getBoundingClientRect(),
textRect = text.current!.getBoundingClientRect();
if (wrapperRect.width < textRect.width) {
setToolTip(true);
}
}, [msg]);
const _text = (
<div
ref={wrapper}
style={defaultStyle ? { ...DefaultStyle, ...style } : style}
{...rest}
>
<span ref={text}>{children ?? msg}</span>
{/* {msg} */}
</div>
);
if (toolTip) {
return <Tooltip_ title={msg} placement={placement} /* color="#fff" overlayInnerStyle={{ color: '#000' }} */ >{_text}</Tooltip_>;
} else {
return _text;
}
}
export const Tooltip = ({ children, color, ...props }: TooltipProps) => {
return <Tooltip_ /* color="#fff" overlayInnerStyle={{ color: '#000' }} */ {...props} >{children}</Tooltip_>
}
\ No newline at end of file
import React from "react"
import { Tree } from "antd"
import { Menu, Item, useContextMenu } from 'react-contexify'
import { generateUUID } from ".."
import 'react-contexify/dist/ReactContexify.css';
const FC = ({ shouldRowContextMenu, menuData, onMenuItemClick, ...restProps }) => {
const MENU_ID = generateUUID()
const { show } = useContextMenu({
id: MENU_ID,
})
const [rightClickNode, setRightClickNode] = React.useState(undefined)
const handleContextMenu = (event, node) => {
show(event, {
position: {
x: event.clientX + 30,
y: event.clientY - 10
}
})
}
return (
<div>
<Tree
onRightClick={({event, node}) => {
setRightClickNode(node)
if (shouldRowContextMenu?.(node)) {
handleContextMenu(event, node)
}
}}
{...restProps}
/>
<Menu id={MENU_ID}>
{
menuData?.map(item => <Item key={item.id} id={item.id} onClick={() => onMenuItemClick?.(item.id, rightClickNode)}>{item.title}</Item>)
}
</Menu>
</div>
)
}
export default FC
\ No newline at end of file
import { useState } from "react"
export interface Page {
pageSize: number
pageNum: number
}
export const defaultPageSize = 20
export const defaultPageSizeOptions = [10, 20, 50, 100]
export type PageSizeOptions = typeof defaultPageSizeOptions[number]
export const defaultPage = {
pageSize: defaultPageSize,
pageNum: 1
}
export function usePage(pageSize?: number): [Page, React.Dispatch<React.SetStateAction<Page>>, (pageNum: number) => void] {
const [page, setPage] = useState<Page>(() => ({
pageSize: pageSize ?? defaultPageSize, pageNum: 1,
}))
const gotoPage = (pageNum: number) => {
setPage(prev => ({ ...prev, pageNum }))
}
return [page, setPage, gotoPage]
}
export type PageType = ReturnType<typeof usePage>[0]
export function paginate<T>(list: T[] | undefined, page: Page) {
if (list) {
const { pageSize, pageNum } = page
const start = (pageNum - 1) * pageSize
const end = start + pageSize
return list.slice(start, end)
}
return list
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ import { message, notification, Modal, Space, Button } from 'antd';
import { Redirect } from 'react-router-dom';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Subject } from 'rxjs';
import LocalStorage from 'local-storage';
import { dispatchLatest, action } from '../model';
import { set_sess_state } from "../model/reducer";
......@@ -397,3 +398,119 @@ export function getDataModelerRole(user) {
return '';
}
export function getScrollbarWidth() {
// Creating invisible container
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll'; // forcing scrollbar to appear
outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
document.body.appendChild(outer);
// Creating inner element and placing it in the container
const inner = document.createElement('div');
outer.appendChild(inner);
// Calculating difference between container's full width and the child width
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);
// Removing temporary elements from the DOM
outer.parentNode?.removeChild(outer);
return scrollbarWidth;
}
function getOffsetTop(element, container) {
if (!element.getClientRects().length) {
return 0;
}
const rect = element.getBoundingClientRect();
if (rect.width || rect.height) {
if (container === window) {
container = element.ownerDocument?.documentElement;
return rect.top - container?.clientTop;
}
return rect.top - container?.getBoundingClientRect().top;
}
return rect.top;
}
export function getInternalCurrentAnchor(_linkIds, _offsetTop = 0, _bounds = 5, container) {
const linkSections: Section[] = [];
_linkIds.forEach((id) => {
const target = container?.querySelector(`.${id}`);
if (target) {
const top = getOffsetTop(target, container);
if (top < _offsetTop + _bounds) {
linkSections.push({ id, top });
}
}
});
if (linkSections.length) {
const maxSection = linkSections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
return maxSection.id;
}
return '';
};
export const showInfoNotifaction = function(title, tip, duration=0) {
notification.config({ prefixCls: "yy-notification" });
notification.info({
message: title||'提示',
description: (typeof tip === 'string' ? (<span dangerouslySetInnerHTML={{ __html: tip||''}} />) : tip),
duration: duration,
});
}
export function checkMenuAdmit(menuinfo) {
const menu = LocalStorage.get('menu');
const menuadmit = LocalStorage.get('menuadmit');
if (!menu || !menuadmit) {
return false;
}
let queryurl = `/view/${menuinfo}`
let menumessage = menu.mapurl[queryurl]
if (menumessage) {
let type = menumessage.id.split("-")
let key = ""
if (type[0] !== "item") {
key = "sub-" + type[0]
}
if (key) {
try {
let parent = menu.parentIdMap[key]||''
let totalbit = menuadmit[parent.name];
if(!parent.hidden){
if((menumessage.bit&totalbit)===menumessage.bit){
return true
}else{
showInfoNotifaction('提示', `暂无访问路由【${menumessage.name}】的权限`);
return false
}
}else{
showInfoNotifaction('提示', `暂无访问路由【${menumessage.name}】的权限`);
return false;
}
} catch (error) {
showInfoNotifaction('提示', `暂无访问路由$【${menumessage.name}】的权限`);
return false;
}
} else {
showInfoNotifaction('提示', `暂无访问路由【${menumessage.name}】的权限`);
return false
}
}
return false;
}
\ No newline at end of file
import React from 'react';
export const EditModelContext = React.createContext({
attrIsEditingFunction: null,
indexIsEditingFunction: null,
});
\ No newline at end of file
import React, { useState, useEffect, useRef } from 'react';
import { Spin } from 'antd';
import React, { useState, useEffect, useRef, useImperativeHandle } from 'react';
import { Spin, Tabs, Anchor, Affix, Button } from 'antd';
import LocalStorage from 'local-storage';
import ImportActionHeader from './ImportActionHeader';
import ImportActionTable from './ImportActionTable';
import { ImportActionTable } from './ImportActionTable';
import ImportActionIndex from './ImportActionIndex';
import { getQueryParam } from '../../../../util';
import ImportActionManage from './ImportActionManage';
import ImportActionRelation from './ImportActionRelation';
import ImportActionComment from './ImportActionComment';
import { getInternalCurrentAnchor, getQueryParam } from '../../../../util';
import { Action } from '../../../../util/constant';
import { dispatch } from '../../../../model';
const ImportAction = (props) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut } = props;
import './ImportAction.less'
const ImportAction = React.forwardRef((props, ref) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut, catalogId } = props;
const [ constraints, setConstraints ] = useState([]);
const [ constraint, setConstraint ] = useState({});
......@@ -20,20 +24,30 @@ const ImportAction = (props) => {
const [ modelerData, setModelerData ] = useState(null);
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ supportedPartitionTypes, setSupportedPartitionTypes ] = useState([]);
const [ supportedIndextypes, setSupportedIndextypes ] = useState([]);
const [ validateReports, setValidateReports ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [container, setContainer] = useState();
const [activeValue, setActiveValue] = useState();
const mountRef = useRef(true);
const modelerDataRef = useRef(null);
const animating = useRef(false);
useEffect(() =>{
useImperativeHandle(ref, () => ({
isLoading: () => {
return loading;
}
}), [loading])
useEffect(() =>{
if ((action||'')==='') return;
if (!mountRef.current && action === 'edit' && !permitCheckOut) {
if ((!mountRef.current && action === 'edit' && !permitCheckOut) || action === 'edit-inherited') {
return;
}
//流程打开模型详情的情况下,id由-1变成-2,会再次触发获取模型详情.这里直接return掉
if (!mountRef.current && action === 'flow') {
return;
......@@ -43,7 +57,7 @@ const ImportAction = (props) => {
//初始化form状态
if (action==='add'||action==='edit'||action==='flow') {
form.resetFields();
form?.resetFields();
}
if (action === 'detail'|| action ==='flow' || action === 'detail-version') {
......@@ -56,46 +70,106 @@ const ImportAction = (props) => {
}
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraintsAndTemplates',
type: 'datamodel.getAllConstraints',
callback: data => {
setConstraints(data.constraints||[]);
setTemplates(data.templates||[]);
setConstraints(data);
if (action === 'add') {
setConstraint((data.constraints||[]).length>0?data.constraints[0]:{});
setTemplate({});
// setConstraint(data?.length>0?data[0]:{});
// setTemplate({});
if ((hints||[]).length>0) {
getDraft((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,hints);
getDraft(data?.length>0?data[0]:{}, {} ,hints);
} else if ((ddl||'').length>0) {
getDraftUsingDDL((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,ddl);
getDraftUsingDDL(data?.length>0?data[0]:{}, {} ,ddl);
} else if ((modelerId||'')!=='') {
getCurrentDataModel();
} else if (roughModelerData) {
setLoading(false);
getExtraData(roughModelerData);
} else {
getDraft((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,[]);
getDraft(data?.length>0?data[0]:{}, {} ,[]);
}
} else if(action === 'edit' || action === 'detail' || action ==='flow' || action === 'detail-version') {
getCurrentDataModel();
} else if (action === 'edit-inherite-modal') {
setLoading(false);
getExtraData(roughModelerData);
}
},
error: () => {
setLoading(false);
}
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [action, hints, modelerId, ddl ]);
useEffect(() => {
if (constraint?.name) {
getTemplates();
} else {
setTemplates([]);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [constraint])
React.useEffect(() => {
if (container) {
handleScroll();
container?.addEventListener('scroll', handleScroll);
return () => {
container?.removeEventListener('scroll', handleScroll);
};
}
}, [container]);
const handleScroll = React.useCallback(() => {
if (animating.current) {
animating.current = false;
return;
}
const currentActiveLink = getInternalCurrentAnchor(
[
'model-import-action-basic',
'model-import-action-technical',
'model-import-action-table',
'model-import-action-index',
'model-import-action-manage',
// 'model-import-action-relation',
'model-import-action-comment',
],
20,
5,
container
);
setActiveValue(currentActiveLink)
}, [container]);
const getTemplates = () => {
dispatch({
type: 'datamodel.getAllTemplates',
payload: {
constraintName: constraint.name
},
callback: data => {
setTemplates(data);
}
})
}
const getDraft = (_constraint, _template, _hints) => {
dispatch({
type: 'datamodel.getDraft',
payload: {
params: {
dataCatalogId: catalogId,
},
data: {
hints: _hints,
modelerModelingConstraint: _constraint,
easyDataModelerModelingTemplate: _template
easyDataModelerModelingTemplate: _template,
}
},
callback: data => {
......@@ -144,12 +218,19 @@ const ImportAction = (props) => {
onChange && onChange(newModelerData);
validateDataModel(newModelerData);
},
error: () => {
form.setFieldsValue({
easyDataModelerModelingTemplate: modelerDataRef.current.easyDataModelerModelingTemplate,
tableType: modelerDataRef.current.tableType
});
setTemplate(modelerDataRef.current.easyDataModelerModelingTemplate);
}
})
}
const getCurrentDataModel = () => {
if ((modelerId||'') === '') {
setLoading(false);
return;
......@@ -176,7 +257,8 @@ const ImportAction = (props) => {
} else if (action==='edit' && permitCheckOut) {
type = 'datamodel.checkOutDataModel';
} else if (action==='edit') {
const _action = getQueryParam(Action, props.location.search);
const _action = getQueryParam(Action, props.location?.search);
if (_action === 'flow') {
params.ignoreNamespace = true;
......@@ -211,6 +293,7 @@ const ImportAction = (props) => {
onChange && onChange(newModelerData||{});
getSupportedDatatypes();
getSupportedPartitionTypes();
getSupportedIndextypes();
if (newModelerData) {
form.setFieldsValue({
......@@ -238,14 +321,9 @@ const ImportAction = (props) => {
const onConstraintChange = (value) => {
let currentConstraint = null;
(constraints||[]).forEach((_constraint, index) => {
if (_constraint.name === value) {
currentConstraint = _constraint;
}
});
if (!currentConstraint) return;
const index = (constraints??[]).findIndex(item => item.id === value)
if (index !== -1) {
currentConstraint = constraints[index]
form.setFieldsValue({
easyDataModelerModelingConstraint: currentConstraint
});
......@@ -258,26 +336,20 @@ const ImportAction = (props) => {
setConstraint(currentConstraint);
getConsult(newModelerData);
}
}
const onTemplateChange = (value) => {
const onTemplateChange = (value, isCustom = false) => {
let currentTemplate = null;
if (!isCustom) {
(templates||[]).forEach((_template, index) => {
if (_template.name === value) {
if (_template.cnName === value) {
currentTemplate = _template;
}
});
}
form.setFieldsValue({
easyDataModelerModelingTemplate: currentTemplate||{}
});
const newModelerData = {...modelerData, easyDataModelerModelingTemplate: currentTemplate };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
const newModelerData = {...modelerData, easyDataModelerModelingTemplate: currentTemplate, tableType: currentTemplate ? currentTemplate.cnName : value };
setTemplate(currentTemplate);
getConsult(newModelerData);
......@@ -304,6 +376,15 @@ const ImportAction = (props) => {
});
}
const getSupportedIndextypes = () => {
dispatch({
type: 'datamodel.getSupportedIndextypes',
callback: data => {
setSupportedIndextypes(data||[]);
}
});
}
const onHeaderChange = (changedValues, allValues) => {
let newModelerData = {...modelerData, ...allValues};
......@@ -326,6 +407,41 @@ const ImportAction = (props) => {
})
}
//类主键自动创建索引
let autoEasyDataModelerIndexOrders = [];
(newModelerData.easyDataModelerSemiPrimaryKey||[]).forEach(item => {
autoEasyDataModelerIndexOrders.push('ASC');
});
let autoEasyDataModelerIndex = {
"name": `i_pk_${newModelerData.name}`,
"indextype": {
"name": "btree",
"displayName": "B-tree",
"supportedDBTypes": [
"Greenplum",
"MySQL"
],
"default": true
},
"indexedAttributeOrders": [...autoEasyDataModelerIndexOrders],
"indexedEasyDataModelAttributes": [...newModelerData.easyDataModelerSemiPrimaryKey],
"unique": false
};
let newEasyDataModelerIndices = [];
(newModelerData.easyDataModelerIndices||[]).forEach((easyDataModelerIndex, index) => {
if (easyDataModelerIndex.name!==`i_pk_${modelerDataRef.current?.name}` && easyDataModelerIndex.name!==`i_pk_${newModelerData.name}`) {
newEasyDataModelerIndices.push(easyDataModelerIndex);
}
});
newEasyDataModelerIndices.push(autoEasyDataModelerIndex);
newEasyDataModelerIndices = newEasyDataModelerIndices.filter(item => (item.indexedEasyDataModelAttributes||[]).length > 0);
newModelerData = {...newModelerData, easyDataModelerIndices: newEasyDataModelerIndices};
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
......@@ -425,10 +541,19 @@ const ImportAction = (props) => {
}
//类主键
let newSemiPrimary = [...(newModelerData.easyDataModelerSemiPrimaryKey||[])];
let newSemiPrimary = [];
(newModelerData.easyDataModelerSemiPrimaryKey||[]).forEach((item, index) => {
const _index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(_item => item.iid === _item.iid);
if (_index !== -1) {
newSemiPrimary.push({...newModelerData.easyDataModelerDataModelAttributes[_index]});
}
})
//索引
let newEasyDataModelerIndices = [...(newModelerData.easyDataModelerIndices||[])];
(newModelerData.easyDataModelerIndices||[]).forEach((easyDataModelerIndex, index) => {
const newIndexedEasyDataModelAttributes = [], newIndexedAttributeOrders = [];
......@@ -453,9 +578,32 @@ const ImportAction = (props) => {
return (
<Spin spinning={loading}>
{
(action==='detail' && ((modelerData||{}).optionList||[]).findIndex(item => item.enabled && item.name==='查看') === -1) ? <div style={{ padding: '10px 20px', height: 60 }}>
{loading?'':'暂无权限'}
</div> : <div className='import-action'>
<Tabs activeKey={activeValue} centered onChange={(val) => {
setActiveValue(val);
var targetElement = container?.querySelector(`.${val}`); // 找到目标元素
if (targetElement) {
animating.current = true;
targetElement.scrollIntoView();
}
}}>
<Tabs.TabPane tab='基本信息' key="model-import-action-basic" />
<Tabs.TabPane tab='技术信息' key="model-import-action-technical" />
<Tabs.TabPane tab='数据表结构' key="model-import-action-table" />
<Tabs.TabPane tab='数据表索引' key="model-import-action-index" />
<Tabs.TabPane tab='管理信息' key="model-import-action-manage" />
{/* <Tabs.TabPane tab='关联对象' key="model-import-action-relation" /> */}
{
modelerData?.id && <Tabs.TabPane tab='模型评论' key="model-import-action-comment" />
}
</Tabs>
<div ref={setContainer} style={{ height: action==='edit-inherite-modal'?'60vh':'calc(100vh - 44px - 64px - 66px)', overflow: 'auto', padding: '20px 20px 0' }}>
<ImportActionHeader
form={form}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
modelerData={modelerData||{}}
constraints={constraints}
templates={templates}
......@@ -473,7 +621,7 @@ const ImportAction = (props) => {
validateReports={validateReports}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
action={action}
originAction={getQueryParam(Action, props?.location?.search)}
terms={terms}
......@@ -482,13 +630,30 @@ const ImportAction = (props) => {
modelerData={modelerData||{}}
constraint={constraint}
template={template}
types={supportedIndextypes}
validateReports={validateReports}
onChange={onIndexChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
terms={terms}
/>
<ImportActionManage
form={form}
modelerData={modelerData||{}}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
/>
{/* <ImportActionRelation
modelerData={modelerData} action={action}
/> */}
{
modelerData?.id && <ImportActionComment
modelerData={modelerData}
/>
}
</div>
</div>
}
</Spin>
);
};
});
export default ImportAction;
\ No newline at end of file
.import-action {
.yy-tabs-nav {
margin: 0;
}
}
\ No newline at end of file
import React from "react"
import { Button, Space, Input, Divider, Upload, Row, Col, Tooltip, List, Typography, Modal } from "antd"
import { DownOutlined, UpOutlined, PlusOutlined } from '@ant-design/icons'
import { showMessage } from "../../../../util"
import { dispatch } from '../../../../model'
import './ImportActionComment.less'
const FC = (props) => {
const { modelerData } = props
const [isCollapse, setCollapse] = React.useState(true)
const [uploading, setUploading] = React.useState(false)
const [fileList, setFileList] = React.useState()
const [comment, setComment] = React.useState()
const [comments, setComments] = React.useState()
const [modal, contextHolder] = Modal.useModal()
React.useEffect(() => {
if (modelerData?.id) {
getComments()
}
}, [modelerData])
const getComments = () => {
dispatch({
type: 'datamodel.getComments',
payload: {
modelId: modelerData?.id
},
callback: data => {
setComments(data)
}
})
}
const onAddCommentClick = () => {
if (uploading) {
showMessage('warn', '文件上传中,请稍后')
return
}
dispatch({
type: 'datamodel.addComment',
payload: {
data: {
fileList,
modelId: modelerData?.id,
comment
}
},
callback: data => {
showMessage('success', '发表评论成功')
setFileList([])
setComment()
getComments()
}
})
}
const onDeleteClick = (item) => {
modal.confirm({
title: '提示',
content: '确定删除该评论嘛?',
onOk: () => {
dispatch({
type: 'datamodel.deleteComment',
payload: {
id: item?.id
},
callback: data => {
showMessage('success', '删除成功')
getComments()
},
})
}
})
}
const uploadProps = {
beforeUpload: file => {
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
showMessage('error', '上传文件必须小于5M')
return false
}
setUploading(true)
dispatch({
type: 'datamodel.uploadCommentFile',
payload: {
fileList: [file]
},
callback: data => {
setUploading(false)
if (data) {
setFileList(prevFileList => {
return [...prevFileList??[], data]
})
}
},
error: () => {
setUploading(false)
}
})
return false
},
fileList: []
}
return (
<div className='model-import-action-comment'>
<div className='mb-3'>
<Space>
<h3 style={{ marginBottom: 0 }}>评论{` (${(comments??[]).length})`}</h3>
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space>
</div>
{
!isCollapse && <React.Fragment>
<div style={{ border: '1px solid #d9d9d9', borderRadius: 4 }}>
<Input.TextArea value={comment} bordered={false} rows={3} placeholder='请输入您的评论' onChange={(e) => { setComment(e.target.value) }} />
<Divider style={{ margin: 0 }}/>
<div className='flex' style={{ padding: '8px 11px', justifyContent: 'space-between' }}>
<Space align='start'>
<Upload {...uploadProps }>
<Button size='small' icon={<PlusOutlined />} />
</Upload>
<AttachesItem value={fileList} onChange={(val) => { setFileList(val) }} />
</Space>
<Tooltip title={comment?'':'请先输入您的评论'}>
<Button disabled={!comment} size='small' type='primary' onClick={onAddCommentClick}>发表评论</Button>
</Tooltip>
</div>
</div>
<div className='my-3'>
<List
itemLayout="horizontal"
dataSource={comments??[]}
pagination={
(comments??[]).length<=20 ? false : {
pageSize: 20,
size: 'small',
}}
renderItem={(item) => (
<List.Item
actions={item.currentUser?[<a key="list-delete" onClick={() => {
onDeleteClick(item)
}}>删除</a>]:null}
>
<List.Item.Meta
avatar={
<div style={{ width: 60 }}>
<Tooltip title={item.userName}>
<Typography.Text ellipsis={true}>{item.userName}</Typography.Text>
</Tooltip>
</div>
}
title={
<div>
{item.comment}
<AttachesItem value={item.fileList} readOnly />
</div>
}
description={new Date(item.createdTS).toLocaleString()}
/>
</List.Item>
)}
/>
</div>
</React.Fragment>
}
{contextHolder}
</div>
)
}
export default FC
const AttachesItem = ({ value, onChange, readOnly }) => {
return (
<React.Fragment>
{
value?.map((item, index) => {
return (
<div key={index} style={{ marginTop: (!readOnly&&index!==0)?5:0 }}>
<Space>
<a onClick={() => {
window.open(`/api/datamodelercomment/file/download?id=${item.id}`)
}}>{item.fileName}
</a>
{
!readOnly && <Button
size='small'
type='danger'
onClick={() => {
dispatch({
type: 'datamodel.deleteCommentFile',
payload: {
id: item?.id
},
callback: () => {
const newValue = [...value]
newValue.splice(index, 1)
onChange?.(newValue)
}
})
}}
>删除</Button>
}
</Space>
</div>
)
})
}
</React.Fragment>
)
}
\ No newline at end of file
.model-import-action-comment {
.yy-list-pagination {
text-align: center;
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Input, Row, Col, Descriptions, Select, AutoComplete, Button, Divider } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import React, { useState, useEffect, useMemo, useRef } from 'react';
import { Form, Input, Row, Col, Select, AutoComplete, Button, Divider, Tooltip, Checkbox, Space } from 'antd';
import { DownOutlined, UpOutlined, ExclamationCircleFilled, WarningFilled } from '@ant-design/icons';
import { Subject } from 'rxjs';
import classnames from 'classnames';
import { highlightSearchContentByTerms, generateUUID } from '../../../../util';
import { highlightSearchContentByTerms, generateUUID, IsArr } from '../../../../util';
import { dispatch, dispatchLatest } from '../../../../model';
import Rule from '../../ModelConfig/Component/rule-readonly';
import DebounceInput from './DebounceInput';
import './ImportActionHeader.less';
......@@ -42,232 +44,565 @@ const updateOptions = [
'作业提交',
'脚本加载',
];
const dataTypeRemark = '描述ETL框架中目标表的数据类型';
const bindingLoadRemark = '描述ETL框架绑定加载列表,如chain、daily、current等';
const ConstraintSelect = ({ value = {}, constraints = [], onChange, ...restProps }) => {
const ImportActionHeader = (props) => {
const { editable, form, modelerData, constraints, templates, onConstraintChange, onTemplateChange, validateReports, onChange, terms, supportedPartitionTypes } = props;
return (
<Select
onChange={onChange}
value={value?.name || ''}
placeholder='请选择规范'
{...restProps}
>
{
(constraints||[]) && constraints.map((constraint, index) => {
return (
<Option key={index} value={constraint.name||''} >{constraint.cnName||''}</Option>
);
const [ options, setOptions ] = useState([]);
const [ autoTranslate, setAutoTranslate ] = useState(false);
const [ dataTypeList, setDataTypeList ] = useState(null);
const [ bindingLoadRangeList, setBindingLoadRangeList ] = useState(null);
const [isCollapse, setCollapse] = useState(true)
const [ruleParams, setRuleParams] = useState({
visible: false
})
useEffect(() => {
getDataTypeList();
}, [])
useEffect(() => {
if (modelerData?.dataType) {
getBindingLoadRangeList(modelerData?.dataType);
} else {
setBindingLoadRangeList([]);
}
</Select>
)
}
}, [modelerData?.dataType])
// const TemplateSelect = ({ value = {}, templates = [], onChange, ...restProps }) => {
// return (
// <Select
// onChange={onChange}
// value={value?.name || ''}
// placeholder='请选择生成表类型'
// allowClear
// {...restProps}
// >
// {
// (templates||[]) && templates.map((template, index) => {
// return (
// <Option key={index} value={template.name||''} >{template.cnName||''}</Option>
// );
// })
// }
// </Select>
// )
// }
useEffect(() => {
const AttributesSelect = ({ value = [], modelerData, onChange, mode = 'multiple', ...restProps }) => {
setAutoTranslate((modelerData.name||'')==='');
if (modelerData) {
form?.setFieldsValue(modelerData);
}
const onAttributeChange = (value) => {
let currentAttributes = [];
if (mode === 'multiple') {
currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
} else {
(value||[]).forEach(item => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
const filterAttributes = (newAttributes||[]).filter(attribute => item ===attribute.iid);
//分布
const distributionDescription = useMemo(() => {
let newDistributionDescription = ''
if (!editable && modelerData) {
if (modelerData?.easyDataModelerDistributionKey) {
(modelerData?.easyDataModelerDistributionKey||[]).forEach((item, index) => {
if (index > 0) {
newDistributionDescription += ',';
}
if (filterAttributes.length !== 0) {
currentAttributes = [...currentAttributes, ...filterAttributes];
} else {
currentAttributes = [...currentAttributes, { name: item, iid: generateUUID() }];
newDistributionDescription += item.name||'';
});
}
}
return newDistributionDescription;
}, [editable, modelerData])
//主键
const primaryDescription = useMemo(() => {
let newPrimaryDescription = ''
if (modelerData?.easyDataModelerPrimaryKey) {
(modelerData?.easyDataModelerPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
newPrimaryDescription += ',';
}
newPrimaryDescription += item.name||'';
})
}
return newPrimaryDescription;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [editable, modelerData])
triggerChange(currentAttributes);
//分区
const partitionsDescription = useMemo(() => {
let newPartitionsDescription = ''
if (modelerData?.partition?.keys) {
(modelerData?.partition?.keys||[]).forEach((item, index) => {
if (index > 0) {
newPartitionsDescription += ',';
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
newPartitionsDescription += item.name||'';
})
}
if (modelerData?.partition?.partitionType?.cnName) {
newPartitionsDescription += '/' + modelerData?.partition?.partitionType?.cnName||'';
}
return newPartitionsDescription;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [editable, modelerData])
//value有可能为空
value = value ? value: [];
//类主键
const semiPrimaryDescription = useMemo(() => {
let newSemiPrimaryDescription = ''
if (modelerData?.easyDataModelerSemiPrimaryKey) {
(modelerData?.easyDataModelerSemiPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
newSemiPrimaryDescription += ',';
}
let attributeIds = [];
let newAttributes = [...(modelerData?.easyDataModelerDataModelAttributes||[])];
newSemiPrimaryDescription += item.name||'';
})
}
return newSemiPrimaryDescription;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [editable, modelerData])
value.forEach(attribute => {
attributeIds.push(attribute.iid);
const marginBottom = useMemo(() => {
return editable ? 15 : 10
}, [editable])
if (mode === 'tags') {
const filterAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(_attribute => attribute.iid ===_attribute.iid);
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
if (filterAttributes.length === 0) {
newAttributes = [...newAttributes, attribute];
}
const getDataTypeList = () => {
dispatch({
type: 'datamodel.dataTypeList',
callback: data => {
setDataTypeList(data);
}
})
}
return (
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode={mode}
allowClear={true}
>
{
(newAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={attribute.iid?attribute.iid:(attribute.name||'')}>{attribute.name||''}</Option>
);
const getBindingLoadRangeList = (dataTypeName) => {
dispatch({
type: 'datamodel.bindingLoadRangeList',
payload: {
dataTypeName
},
callback: data => {
setBindingLoadRangeList(data);
}
})
}
</Select>
);
}
const PartitionSelect = ({ value = {}, modelerData, partitionTypes = [], onChange, ...restProps }) => {
const onPartitionTypeChange = (value) => {
const onSearch = (searchText) => {
let currentPartitionType = {};
(partitionTypes||[]).forEach((partitionType, index) => {
if (value === partitionType.name) {
currentPartitionType = partitionType;
const _searchText = searchText.replace(/ /g,'');
if (_searchText !== '') {
dispatchLatest({
type: 'datamodel.autocomplete',
payload: {
params: {
word: _searchText,
isEasyDataModelerDataModelAttribute: false,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
})
triggerChange({ partitionType: currentPartitionType });
setOptions(_options);
}
})
} else {
dispatchLatest({
type: 'datamodel.recommandEnglishWords',
payload: {
params: {
chineseWord: modelerData.cnName,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
})
const onAttributeChange = (value) => {
const currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
triggerChange({ keys: currentAttributes });
setOptions(_options);
}
})
}
}
const triggerChange = (changedValue) => {
onChange?.({
...value,
...changedValue,
});
};
const onValuesChange = (changedValues, allValues) => {
if (changedValues.hasOwnProperty('tableType')) return;
//value有可能为空
value = value ? value: {};
onChange && onChange(changedValues, allValues);
let attributeIds = [];
(value?.keys||[]).forEach(attribute => {
attributeIds.push(attribute.iid);
//有手动编辑过英文名称并且有内容的情况下, 不能通过编辑中文名称自动翻译
if (changedValues.hasOwnProperty('name')) {
setAutoTranslate(changedValues.name==='');
} else if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslate) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
params: {
phaseInChinese: changedValues.cnName,
}
},
callback: data => {
if ((data?.translated||'') !== '') {
form?.setFieldsValue({ name: data?.translated||'' });
onChange && onChange({...changedValues, ...{name: data?.translated||''}}, {...allValues, ...{name: data?.translated||''}});
}
}
})
}
} else if (changedValues.hasOwnProperty('dataType')) {
if (changedValues.dataType) {
onChange?.({...changedValues, bindingLoadRange: ''}, {...allValues, bindingLoadRange: ''});
} else {
onChange?.({...changedValues, bindingLoadRange: ''}, {...allValues, bindingLoadRange: ''});
}
}
}
return (
<div className={classnames('model-import-action-header', editable?'':'model-import-action-header-readolny')}>
<div className='model-import-action-basic mb-3'>
<h3 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h3>
</div>
<Form form={form} {...formItemLayout}
onValuesChange={onValuesChange}
>
<Row gutter={10}>
<Col span={8}>
<Select
onChange={onPartitionTypeChange}
value={value?.partitionType?.name || ''}
placeholder='请选择分区类型'
allowClear={true}
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label={<ItemTitle name='cnName' cnName='中文名称' validateReports={validateReports} />}
name="cnName"
rules={[{ required: true, message: '请输入中文名称!' }]}
style={{ marginBottom }}
>
{
(partitionTypes||[]).map((partitionType, index) => {
return (
<Option key={partitionType.name||''}>{partitionType.cnName||''}</Option>
);
})
editable ? <InputDebounce /> : <span className='word-wrap'>
{highlightSearchContentByTerms(modelerData?.cnName, terms)}
</span>
}
</Select>
</Form.Item>
</Col>
<Col span={16}>
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode='multiple'
allowClear={true}
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label={<ItemTitle name='name' cnName='英文名称' validateReports={validateReports} />}
name="name"
rules={[{ required: true, message: '请输入英文名称!' }]}
style={{ marginBottom }}
>
{
(modelerData?.easyDataModelerDataModelAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={attribute.iid||''}>{attribute.name||''}</Option>
);
})
editable ? <AutoComplete options={options} onSearch={onSearch} /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.name, terms)}</span>
}
</Select>
</Form.Item>
</Col>
</Row>
);
}
const LoadSelect = ({ value = '', onChange, ...restProps }) => {
const onLoadChange = (value) => {
triggerChange(value.join('/'));
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label={<ItemTitle name='remark' cnName='数据内容' validateReports={validateReports} />}
name="remark"
rules={[{ required: true, message: '请输入数据内容!' }]}
style={{ marginBottom }}
>
{
editable ? <TextArea rows={1} placeholder='描述数据表包含的业务数据,包括数据内容业务描述、表的适用范围、数据粒度、数据统计口径、数据来源等。对于原始数据表,可以说明加载的源接口信息。' /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.remark, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="技术主键"
name="easyDataModelerPrimaryKey"
style={{ marginBottom }}
>
{
editable ? <AttributesSelect modelerData={modelerData} mode='tags' /> : <span className='word-wrap'>{highlightSearchContentByTerms(primaryDescription, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据平台"
name="dataResidence"
style={{ marginBottom }}
>
{
editable ? <Input placeholder='描述数据表落地的数据平台及数据库' /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataResidence, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="规范"
name="easyDataModelerModelingConstraint"
rules={[{ required: true, message: '请选择规范!' }]}
style={{ marginBottom }}
>
{
editable ? <ConstraintSelect
constraints={constraints}
onChange={onConstraintChange}
onDetail={() => {
setRuleParams({ visible: true })
}}
/> : <div className='flex' style={{ alignItems: 'flex-start' }}>
<div className='word-wrap mr-2'>
{modelerData?.easyDataModelerModelingConstraint?.cnName}
</div>
<div style={{ flex: 1, minWidth: 30 }}>
<a onClick={() => {
setRuleParams({ visible: true })
}}>查看</a>
</div>
</div>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据表类型"
name="tableType"
rules={[{ required: true, message: '请选择数据表类型!' }]}
style={{ marginBottom }}
>
{
editable ? <TemplateSelect
modelerData={modelerData}
templates={templates}
onChange={onTemplateChange}
/> : <span className='word-wrap'>{modelerData?.tableType}</span>
}
</Form.Item>
</Col>
</Row>
</Form>
<div className='model-import-action-technical mb-3' style={{
display: 'flex',
alignItems: 'center',
}}
>
<h3 className='mr-3' style={{ marginBottom: 0 }}>技术信息</h3>
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</div>
{
!isCollapse && <Form
form={form}
{...formItemLayout}
onValuesChange={onValuesChange}
>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分布键"
name="easyDataModelerDistributionKey"
style={{ marginBottom }}
>
{
editable ? <AttributesSelect modelerData={modelerData} /> : <span className='word-wrap'>{highlightSearchContentByTerms(distributionDescription, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分区键"
name="partition"
style={{ marginBottom }}
>
{
editable ? <PartitionSelect modelerData={modelerData} partitionTypes={supportedPartitionTypes} /> : <span className='word-wrap'>{highlightSearchContentByTerms(partitionsDescription, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="类主键"
name="easyDataModelerSemiPrimaryKey"
style={{ marginBottom }}
>
{
editable ? <AttributesSelect modelerData={modelerData} mode='tags' /> : <span className='word-wrap'>{highlightSearchContentByTerms(semiPrimaryDescription, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据情况"
name="dataCircumstances"
style={{ marginBottom }}
>
{
editable ? <Input placeholder='描述数据表中数据的更新频率、每日增量情况、数据量级和历史数据' /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataCircumstances, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="更新时间"
name="dataUpdatingTiming"
style={{ marginBottom }}
>
{
editable ? <UpdateSelect placeholder='描述数据表的更新时间点'/> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataUpdatingTiming, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据类型"
name="dataType"
tooltip={dataTypeRemark}
style={{ marginBottom }}
>
{
editable ? <Select allowClear placeholder='请选择数据类型'>
{
dataTypeList?.map((item, index) => <Option key={index} value={item}>
{item}
</Option>)
}
</Select> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataType, terms)}</span>
}
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="绑定加载范围"
name="bindingLoadRange"
tooltip={bindingLoadRemark}
style={{ marginBottom }}
>
{
editable ? <Select allowClear placeholder='请选择绑定加载范围'>
{
bindingLoadRangeList?.map((item, index) => <Option key={index} value={item}>
{item}
</Option>)
}
</Select> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.bindingLoadRang, terms)}</span>
}
</Form.Item>
</Col>
</Row>
</Form>
}
<Rule
{...ruleParams}
onCancel={() => {
setRuleParams({ visible: false })
}}
/>
</div>
)
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
//value有可能为空
value = value ? value: '';
let loadNames = [];
export default ImportActionHeader;
if (value !== '') {
value.split('/').forEach(item => {
loadNames.push(item);
const ConstraintSelect = ({ value = {}, constraints = [], onChange, onDetail, ...restProps }) => {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Select
onChange={(val) => { onChange?.(val) }}
value={value?.id}
placeholder='请选择规范'
style={{ flex: 1 }}
{...restProps}
>
{
(constraints||[]) && constraints.map((constraint, index) => {
return (
<Option key={index} value={constraint.id} >{constraint.cnName||''}</Option>
);
})
}
</Select>
<a className='ml-3' onClick={() => { onDetail?.() }}>查看</a>
</div>
)
}
const TemplateSelect = ({ value = '', modelerData = undefined, templates = [], onChange, ...restProps }) => {
const mountRef = useRef(true);
const [isCustom, setIsCustom] = useState(false);
useEffect(() => {
if (mountRef.current && modelerData && Object.keys(modelerData).length > 0) {
if (modelerData?.tableType && !modelerData?.easyDataModelerModelingTemplate?.name) {
setIsCustom(true);
} else {
setIsCustom(false);
}
mountRef.current = false;
}
}, [modelerData])
return (
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onLoadChange && onLoadChange(value) }}
value={loadNames}
placeholder='请选择选择或者手动输入加载方式'
allowClear={true}
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{
isCustom ? <InputDebounce
placeholder='请输入数据表类型'
allowClear
value={modelerData?.tableType}
onChange={(val) => {
onChange?.(val, true);
}}
style={{ flex: 1 }}
/> : <Select
onChange={(val) => {
onChange?.(val);
}}
value={value || undefined}
placeholder='请选择数据表类型'
allowClear
style={{ flex: 1 }}
{...restProps}
>
{
loadOptions.map((item, index) => {
(templates||[]) && templates.map((template, index) => {
return (
<Option key={index} value={item}>{item}</Option>
<Option key={index} value={template.cnName||''} >
<Tooltip title={template.remark}>
{template.cnName}
</Tooltip>
</Option>
);
})
}
</Select>
);
}
<Checkbox className='ml-3' checked={isCustom} onChange={(e) => {
setIsCustom(e.target.checked);
onChange?.('');
}}>自定义</Checkbox>
</span>
)
}
const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
const AttributesSelect = ({ value = [], modelerData, onChange, mode = 'multiple', ...restProps }) => {
const onUpdateChange = (value) => {
const onAttributeChange = (value) => {
let currentAttributes = [];
if (mode === 'multiple') {
currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
} else {
(value||[]).forEach(item => {
triggerChange(value.join('/'));
const filterAttributes = (newAttributes||[]).filter(attribute => item ===attribute.iid);
if (filterAttributes.length !== 0) {
currentAttributes = [...currentAttributes, ...filterAttributes];
} else {
currentAttributes = [...currentAttributes, { name: item, iid: generateUUID() }];
}
})
}
triggerChange(currentAttributes);
}
const triggerChange = (changedValue) => {
......@@ -275,28 +610,35 @@ const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
};
//value有可能为空
value = value ? value: '';
value = value ? value: [];
let updateNames = [];
if (value !== '') {
value.split('/').forEach(item => {
updateNames.push(item);
})
let attributeIds = [];
let newAttributes = [...(modelerData?.easyDataModelerDataModelAttributes||[])];
value.forEach(attribute => {
attributeIds.push(attribute.iid);
if (mode === 'tags') {
const filterAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(_attribute => attribute.iid ===_attribute.iid);
if (filterAttributes.length === 0) {
newAttributes = [...newAttributes, attribute];
}
}
})
return (
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onUpdateChange && onUpdateChange(value) }}
value={updateNames}
placeholder='请选择选择或者手动输入更新时间'
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode={mode}
allowClear={true}
>
{
updateOptions.map((item, index) => {
(newAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={item}>{item}</Option>
<Option key={index} value={attribute.iid?attribute.iid:(attribute.name||'')}>{attribute.name||''}</Option>
);
})
}
......@@ -304,429 +646,206 @@ const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
);
}
const ImportActionHeader = (props) => {
const { editable, form, modelerData, constraints, templates, onConstraintChange, onTemplateChange, validateReports, onChange, terms, supportedPartitionTypes } = props;
const [ causes, setCauses ] = useState([]);
const [ options, setOptions ] = useState([]);
const [ autoTranslate, setAutoTranslate ] = useState(false);
const [ onlyShowRequireChange, setOnlyShowRequireChange ] = useState(true);
const [ maintenanceRecords, setMaintenanceRecords ] = useState(null);
useEffect(() => {
const PartitionSelect = ({ value = {}, modelerData, partitionTypes = [], onChange, ...restProps }) => {
const causes = [];
(validateReports||[]).forEach(report => {
const onPartitionTypeChange = (value) => {
if (report.type === 'DataModel') {
(report.reportItems||[]).forEach((item) => {
causes.push(item.cause||'');
})
let currentPartitionType = {};
(partitionTypes||[]).forEach((partitionType, index) => {
if (value === partitionType.name) {
currentPartitionType = partitionType;
}
});
})
if (editable) {
form?.setFields([{ name: 'name', errors: causes }]);
} else {
setCauses(causes);
triggerChange({ partitionType: currentPartitionType });
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports])
useEffect(() => {
const onAttributeChange = (value) => {
const currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
setAutoTranslate((modelerData.name||'')==='');
if (modelerData) {
form?.setFieldsValue(modelerData);
if ((modelerData.id||'')!=='' && maintenanceRecords===null) {
getMaintenanceRecords();
}
triggerChange({ keys: currentAttributes });
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
const triggerChange = (changedValue) => {
onChange?.({
...value,
...changedValue,
});
};
const getMaintenanceRecords = () => {
dispatch({
type: 'datamodel.getMaintenanceRecords',
payload: {
params: {
id: modelerData.id
}
},
callback: data => {
setMaintenanceRecords(data);
}
})
}
const onSearch = (searchText) => {
//value有可能为空
value = value ? value: {};
const _searchText = searchText.replace(/ /g,'');
if (_searchText !== '') {
dispatchLatest({
type: 'datamodel.autocomplete',
payload: {
params: {
word: _searchText,
isEasyDataModelerDataModelAttribute: false,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
let attributeIds = [];
(value?.keys||[]).forEach(attribute => {
attributeIds.push(attribute.iid);
})
setOptions(_options);
}
})
} else {
dispatchLatest({
type: 'datamodel.recommandEnglishWords',
payload: {
params: {
chineseWord: modelerData.cnName,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
return (
<Row gutter={10}>
<Col span={10}>
<Select
onChange={onPartitionTypeChange}
value={value?.partitionType?.name}
placeholder='请选择分区类型'
allowClear={true}
>
{
(partitionTypes||[]).map((partitionType, index) => {
return (
<Option key={partitionType.name||''}>{partitionType.cnName||''}</Option>
);
})
setOptions(_options);
}
</Select>
</Col>
<Col span={14}>
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode='multiple'
allowClear={true}
>
{
(modelerData?.easyDataModelerDataModelAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={attribute.iid||''}>{attribute.name||''}</Option>
);
})
}
}
const onValuesChange = (changedValues, allValues) => {
</Select>
</Col>
</Row>
);
}
onChange && onChange(changedValues, allValues);
const LoadSelect = ({ value = '', onChange, ...restProps }) => {
//有手动编辑过英文名称并且有内容的情况下, 不能通过编辑中文名称自动翻译
if (changedValues.hasOwnProperty('name')) {
setAutoTranslate(changedValues.name==='');
} else if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslate) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
params: {
phaseInChinese: changedValues.cnName,
}
},
callback: data => {
if ((data?.translated||'') !== '') {
form?.setFieldsValue({ name: data?.translated||'' });
onChange && onChange({...changedValues, ...{name: data?.translated||''}}, {...allValues, ...{name: data?.translated||''}});
}
}
})
}
}
}
const onLoadChange = (value) => {
const onOnlyShowRequireChange = () => {
setOnlyShowRequireChange(!onlyShowRequireChange);
triggerChange(value.join('/'));
}
let distributionDescription = '', primaryDescription = '', partitionsDescription = '', semiPrimaryDescription = '', maintenanceDescription = '';
if (!editable && modelerData) {
//分布
if (modelerData?.easyDataModelerDistributionKey) {
(modelerData?.easyDataModelerDistributionKey||[]).forEach((item, index) => {
if (index > 0) {
distributionDescription += ',';
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
distributionDescription += item.name||'';
});
}
//value有可能为空
value = value ? value: '';
//主键
if (modelerData?.easyDataModelerPrimaryKey) {
(modelerData?.easyDataModelerPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
primaryDescription += ',';
}
let loadNames = [];
primaryDescription += item.name||'';
if (value !== '') {
value.split('/').forEach(item => {
loadNames.push(item);
})
}
//分区
if (modelerData?.partition?.keys) {
(modelerData?.partition?.keys||[]).forEach((item, index) => {
if (index > 0) {
partitionsDescription += ',';
}
partitionsDescription += item.name||'';
return (
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onLoadChange && onLoadChange(value) }}
value={loadNames}
placeholder='请选择或者手动输入加载方式'
allowClear={true}
>
{
loadOptions.map((item, index) => {
return (
<Option key={index} value={item}>{item}</Option>
);
})
}
</Select>
);
}
if (modelerData?.partition?.partitionType?.cnName) {
partitionsDescription += '/' + modelerData?.partition?.partitionType?.cnName||'';
}
const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
//类主键
if (modelerData?.easyDataModelerSemiPrimaryKey) {
(modelerData?.easyDataModelerSemiPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
semiPrimaryDescription += ',';
}
const onUpdateChange = (value) => {
semiPrimaryDescription += item.name||'';
})
}
triggerChange(value.join('/'));
}
if ((maintenanceRecords||[]).length>0) {
maintenanceRecords.forEach((record, index) => {
if (index !== 0) {
maintenanceDescription += '/';
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
maintenanceDescription += record;
})
//value有可能为空
value = value ? value: '';
form?.setFieldsValue({ maintenanceRecords: maintenanceDescription });
let updateNames = [];
if (value !== '') {
value.split('/').forEach(item => {
updateNames.push(item);
})
}
return (
<div className='model-import-action-header'>
<div
className='mb-3'
style={{
display: 'flex',
alignItems: 'center',
}}
>
<h2 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h2>
{
onlyShowRequireChange ? <Button type='text' style={{ padding: 0, color: '#0069AC' }} onClick={onOnlyShowRequireChange}>展开<DownOutlined /></Button> : <Button type='text' style={{ padding: 0, color: '#0069AC' }} onClick={onOnlyShowRequireChange}>收起<UpOutlined /></Button>
}
</div>
{
editable ? (
<Form
form={form}
{...formItemLayout}
onValuesChange={onValuesChange}
>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="中文名称"
name="cnName"
rules={[{ required: true, message: '请输入中文名称!' }]}
>
<InputDebounce />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="英文名称"
name="name"
rules={[{ required: true, message: '请输入英文名称!' }]}
>
<AutoComplete options={options} onSearch={onSearch} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="规范"
name="easyDataModelerModelingConstraint"
rules={[{ required: true, message: '请选择规范!' }]}
>
<ConstraintSelect
constraints={constraints}
onChange={onConstraintChange}
/>
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据内容"
name="remark"
rules={[{ required: true, message: '请输入数据内容!' }]}
style={{ marginBottom: 15 }}
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onUpdateChange && onUpdateChange(value) }}
value={updateNames}
allowClear={true}
{...restProps}
>
<TextArea row={4} />
</Form.Item>
</Col>
</Row>
{
!onlyShowRequireChange && <Divider style={{ margin: '0 0 15px' }} />
updateOptions.map((item, index) => {
return (
<Option key={index} value={item}>{item}</Option>
);
})
}
{
!onlyShowRequireChange && <Row gutter={10}>
{/* <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="生成表类型"
name="easyDataModelerModelingTemplate"
rules={[{ required: false, message: '请选择生成表类型!' }]}
>
<TemplateSelect
templates={templates}
onChange={onTemplateChange}
/>
</Form.Item>
</Col> */}
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据表类型"
name="tableType"
>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据平台"
name="dataResidence"
>
<Input disabled={true} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据情况"
name="dataCircumstances"
>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分布键"
name="easyDataModelerDistributionKey"
>
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分区键"
name="partition"
>
<PartitionSelect modelerData={modelerData} partitionTypes={supportedPartitionTypes} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="主键"
name="easyDataModelerPrimaryKey"
>
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="类主键"
name="easyDataModelerSemiPrimaryKey"
>
<AttributesSelect modelerData={modelerData} mode='tags' />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="加载方式"
name="dataLoadingStrategy"
>
<LoadSelect />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="更新时间"
name="dataUpdatingTiming"
>
<UpdateSelect />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="维护历史"
name="maintenanceRecords"
>
<TextArea rows={3} disabled={true} />
</Form.Item>
</Col>
</Row>
</Select>
);
}
const ItemTitle = ({ name, cnName, validateReports }) => {
return (
<Space size={2}>
{cnName}
<ValidateTip validateReports={validateReports} type='DataModel' propertyName={name} />
</Space>
)
}
export const ValidateTip = ({ validateReports, type, propertyName, iid }) => {
const reports = useMemo(() => {
const index = (validateReports??[]).findIndex(item => (
(item.type === type) && (!iid || item.iid === iid)
))
if (index !== -1) {
return (validateReports[index].reportItems??[]).filter(item => item.checkRule?.ruleTemplate?.checkProperty?.originalPropertyEnName === propertyName)
}
</Form>
) : (
return []
}, [validateReports, type, propertyName])
return (
<React.Fragment>
<Descriptions column={3}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>中文名称</div>} >{highlightSearchContentByTerms(modelerData.cnName||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>英文名称</div>}>
{
(reports??[]).length === 0 ? null : <Tooltip title={
<div>
<div>{highlightSearchContentByTerms(modelerData.name||'', terms)}</div>
{
(causes||[]).map((cause, index) => {
return (
<div key={index} style={{ color: '#ff4d4f' }}>
{cause||''}
</div>
)
})
(reports??[]).map((item, index) => (
<Row key={index}>
<Space>
{ (item.checkRule?.alertTypeId === 'enforced') ? <WarningFilled style={{ color: '#E94848' }} /> : <ExclamationCircleFilled style={{ color: '#F7AB00' }} /> }
<span>{item.checkRule?.alertContent}</span>
</Space>
</Row>
))
}
</div>
}
</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>规范</div>} >{modelerData.easyDataModelerModelingConstraint?(modelerData.easyDataModelerModelingConstraint.cnName||''):''}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据内容</div>}>{highlightSearchContentByTerms(modelerData.remark||'', terms)}</Descriptions.Item>
</Descriptions>
}>
{
!onlyShowRequireChange && <Divider style={{ margin: '0 0 15px' }} />
(reports??[]).findIndex(item => item.checkRule?.alertTypeId === 'enforced') !== -1 ? <WarningFilled style={{ color: '#E94848' }} /> : <ExclamationCircleFilled style={{ color: '#F7AB00' }} />
}
{
!onlyShowRequireChange && <Descriptions column={3}>
{/* <Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>生成表类型</div>} >{modelerData.easyDataModelerModelingTemplate?(modelerData.easyDataModelerModelingTemplate.cnName||''):''}</Descriptions.Item> */}
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据表类型</div>} >{highlightSearchContentByTerms(modelerData.tableType||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据平台</div>} >{highlightSearchContentByTerms(modelerData.dataResidence||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据情况</div>} >{highlightSearchContentByTerms(modelerData.dataCircumstances||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>分布键</div>} >{highlightSearchContentByTerms(distributionDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>分区键</div>} >{highlightSearchContentByTerms(partitionsDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>主键</div>} >{highlightSearchContentByTerms(primaryDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>类主键</div>} >{highlightSearchContentByTerms(semiPrimaryDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>加载方式</div>} >{highlightSearchContentByTerms(modelerData.dataLoadingStrategy||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>更新时间</div>} >{highlightSearchContentByTerms(modelerData.dataUpdatingTiming||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>维护历史</div>} >
<div>
{
(maintenanceRecords||[]).map((record, index) => {
return <div key={index}>{record||''}</div>;
})
}
</div>
</Descriptions.Item>
</Descriptions>
</Tooltip>
}
</React.Fragment>
)
}
</div>
)
}
\ No newline at end of file
export default ImportActionHeader;
\ No newline at end of file
.model-import-action-header {
.yy-form-item:nth-last-col {
margin-bottom: 24px;
.model-import-action-header-readolny {
.yy-form-item-label > label {
height: auto;
}
.yy-form-item-has-error {
margin-bottom: 0px;
}
.yy-descriptions-row > th, .yy-descriptions-row > td {
padding-bottom: 15px;
.yy-form-item-control-input {
min-height: 0;
}
}
\ No newline at end of file
import React, { useState, useCallback, useRef, useEffect } from 'react';
import React, { useState, useCallback, useRef, useEffect, useContext, useMemo } from 'react';
import { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table, Space } from 'antd';
import { DeleteOutlined, CloseOutlined, CheckOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { DeleteOutlined, CloseOutlined, CheckOutlined, PlusOutlined, QuestionCircleOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
......@@ -10,6 +10,8 @@ import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify"
import DebounceInput from './DebounceInput';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { showMessage, highlightSearchContentByTerms, inputWidth } from '../../../../util';
import { EditModelContext } from './ContextManage';
import { ValidateTip } from './ImportActionHeader';
const { Option } = Select;
......@@ -18,6 +20,44 @@ const MENU_ID = 'model-index-menu';
const InputDebounce = DebounceInput(300)(Input);
const TypesItem = ({ value, types, onChange }) => {
useEffect(() => {
if ((value?.name||'')==='' && (types||[]).length > 0) {
const filterTypes = (types||[]).filter(type => type.default===true);
if ((filterTypes||[]).length > 0) {
onChange && onChange(filterTypes[0]);
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
const onTypeChange = (val) => {
const filterTypes = (types||[]).filter(type => type.name===val);
if ((filterTypes||[]).length > 0) {
onChange && onChange(filterTypes[0]);
}
}
return (
<span onClick={e => e.stopPropagation()}>
<Select
onChange={(val) => { onTypeChange && onTypeChange(val) }}
value={value?.name}
placeholder='请选择排序方式'
>
{
(types||[]).map((type, index) => {
return (
<Option key={index} value={type.name}>{type.displayName}</Option>
);
})
}
</Select>
</span>
);
}
const AttributesInputItem = ({ indexedAttribute = null, indexedAttributeOrder = null, attributes, onAttributeChange, onOrderChange, onDelete , className }) => {
return (
......@@ -164,13 +204,14 @@ const EditableCell = ({
record,
index,
attributes,
types,
children,
...restProps
}) => {
let editingComponent = null;
if (editing) {
if (dataIndex !== 'attributesWithOrders') {
if (dataIndex!=='attributesWithOrders' && dataIndex!=='indextype') {
const inputNode = inputType === 'check' ? <Checkbox /> : <Input />
......@@ -191,6 +232,26 @@ const EditableCell = ({
{ inputNode }
</Form.Item>
);
} else if (dataIndex === 'indextype') {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: true,
message: `请输入${colTitle}!`,
},
]}
>
<TypesItem types={types} />
</Form.Item>
);
} else {
editingComponent = (
<Form.Item
......@@ -278,7 +339,7 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) =>
};
const ImportActionIndex = (props) => {
const { modelerData, onChange, editable, constraint, template, validateReports, terms } = props;
const { modelerData, onChange, editable, constraint, template, validateReports, terms, types } = props;
const [ attributes, setAttributes ] = useState([]);
const [ data, setData ] = useState([]);
......@@ -291,11 +352,15 @@ const ImportActionIndex = (props) => {
const [ filterData, setFilterData ] = useState([]);
const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null);
const [isCollapse, setCollapse] = React.useState(true)
const { indexIsEditingFunction } = useContext(EditModelContext);
const dataRef = useRef();
dataRef.current = data;
const tableRef = useRef(null);
const termsRef = useRef(null);
const { show } = useContextMenu({
id: MENU_ID,
......@@ -305,12 +370,18 @@ const ImportActionIndex = (props) => {
save();
}, tableRef);
useEffect(() => {
indexIsEditingFunction && indexIsEditingFunction(editingKey!==null);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ editingKey ])
//规则改变的时候 数据表为可编辑状态
useEffect(() => {
setEditingKey(null);
}, [constraint, template, modelerData])
useEffect(() => {
termsRef.current = terms;
setAttributes(modelerData.easyDataModelerDataModelAttributes||[]);
setData(modelerData.easyDataModelerIndices||[]);
......@@ -329,7 +400,7 @@ const ImportActionIndex = (props) => {
setFilterData(__filterData);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
}, [modelerData, terms])
useEffect(() => {
if (needFilter) {
......@@ -349,6 +420,14 @@ const ImportActionIndex = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [keywordCondition])
const menuData = useMemo(() => {
return [
{ title: '在上方插入行', key: 'up' },
{ title: '在下方插入行', key: 'down' },
{ title: '删除', key: 'delete' },
]
}, [])
const isEditing = (record) => record.name === editingKey;
const onAddClick = (event) => {
......@@ -368,7 +447,7 @@ const ImportActionIndex = (props) => {
})
}
const insertToFront = (record) => {
const onInsertToFrontClick = (record) => {
save().then(result => {
if (result) {
......@@ -405,7 +484,7 @@ const ImportActionIndex = (props) => {
})
}
const insertToBack = (record) => {
const onInsertToBackClick = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
......@@ -431,12 +510,14 @@ const ImportActionIndex = (props) => {
}
const editLogic = (record) => {
form.resetFields();
form.setFieldsValue({
name: '',
attributesWithOrders: {
indexedEasyDataModelAttributes: [{}],
indexedAttributeOrders: [''],
},
indextype: {},
unique: false,
...record,
});
......@@ -468,7 +549,7 @@ const ImportActionIndex = (props) => {
onChange && onChange(newData);
}
const remove = (record) => {
const onRemoveClick = (record) => {
if (record.name === '') {
const newData = [...dataRef.current];
......@@ -542,6 +623,7 @@ const ImportActionIndex = (props) => {
newData.splice(insertIndex, 0 , {
name: row.name,
unique: row.unique,
indextype: row.indextype,
indexedEasyDataModelAttributes: _indexedEasyDataModelAttributes,
indexedAttributeOrders: _indexedAttributeOrders,
});
......@@ -550,6 +632,7 @@ const ImportActionIndex = (props) => {
newData.splice(index, 1, {...{
name: row.name,
unique: row.unique,
indextype: row.indextype,
indexedEasyDataModelAttributes: _indexedEasyDataModelAttributes,
indexedAttributeOrders: _indexedAttributeOrders,
}});
......@@ -593,8 +676,25 @@ const ImportActionIndex = (props) => {
ellipsis: true,
render: (text, record, index) => {
return (
<React.Fragment>
<ValidateTip type='Index' propertyName='name' validateReports={validateReports} iid={record.name} />
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, terms)}</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
{
title: '索引类型',
width: 200,
dataIndex: 'indextype',
editable: true,
ellipsis: true,
render: (_, record, __) => {
return (
<Tooltip title={record.indextype?.displayName||''}>
<span >{highlightSearchContentByTerms(record.indextype?.displayName||'', termsRef.current)}</span>
</Tooltip>
)
}
......@@ -635,7 +735,7 @@ const ImportActionIndex = (props) => {
return (
<Row key={index}>
<span>字段: </span>
{ highlightSearchContentByTerms(item.name||'', terms) }
{ highlightSearchContentByTerms(item.name||'', termsRef.current) }
<span>{` 排序: ${order||''}`}</span>
</Row>
)
......@@ -647,119 +747,9 @@ const ImportActionIndex = (props) => {
},
];
const editableColumn = [
...columns,
{
title: '操作',
dataIndex: 'action',
width: 180,
render: (_, record) => {
if (!editable) return null;
return (
<React.Fragment>
<Button
className='mr-3'
size='small'
type='text'
icon={<PlusOutlined />}
onClick={(event) => {
event.stopPropagation();
insertToFront(record);
}}
/>
<Button
className='mr-3'
size='small'
type='text'
icon={<DeleteOutlined style={{ color: 'red' }} />}
onClick={(event) => {
event.stopPropagation();
remove(record);
}}
/>
</React.Fragment>
)
},
},
]
const validateColumn = {
title: '规范',
dataIndex: 'validate',
width: 180,
ellipsis: true,
render: (text, record, index) => {
let shortCauses = [];
let causes = [];
(validateReports||[]).forEach((report) => {
if (report.type==='Index' && report.iid === record.name) {
(report.reportItems||[]).forEach((item) => {
shortCauses.push(item.shortCause||'');
causes.push(item.cause||'');
})
}
});
return (
<div className='d-flex' key={index} style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<div>
{
shortCauses && shortCauses.map((shortCause, index) => {
return (
<Tooltip key={index} title={shortCause}>
<div className='textOverflow' style={{ width: 130, color: '#f5222d' }} title={shortCause} key={index}>
<span>{shortCause||''}</span>
</div>
</Tooltip>
);
})
}
</div>
{
(causes||[]).length>0 && <Popover
content={
<div style={{ maxWidth: 200, maxHeight: 200, wordWrap: 'break-word', overflow: 'auto' }}>
{
causes && causes.map((cause, index) => {
return (
<Typography.Paragraph key={index} >
{cause||''}
</Typography.Paragraph>
)
})
}
</div>
}
title="规则详情"
>
<a href="#">详情</a>
</Popover>
}
</div>
)
}
};
const includeValidateColumn = [
...columns,
validateColumn
]
const includeValidateEditableColumn = [
...editableColumn,
validateColumn
]
const mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='Index').length>0;
if (editable) {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
return _columns.map((col) => {
return columns.map((col) => {
if (!col.editable) {
return col;
}
......@@ -773,12 +763,13 @@ const ImportActionIndex = (props) => {
colTitle: col.title,
editing: isEditing(record),
attributes,
types,
}),
};
});
}
return hasValidateReports ? includeValidateColumn : columns;
return columns;
}
const moveRow = useCallback(
......@@ -794,7 +785,7 @@ const ImportActionIndex = (props) => {
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[dataRef.current],
[dataRef.current, onChange],
);
const onSearchInputChange = (value) => {
......@@ -810,22 +801,31 @@ const ImportActionIndex = (props) => {
const key = event.currentTarget.id;
if (key === 'up') {
insertToFront(currentItem);
onInsertToFrontClick(currentItem);
} else if (key === 'down') {
insertToBack(currentItem);
onInsertToBackClick(currentItem);
} else if (key === 'delete') {
onRemoveClick(currentItem);
}
}
return (
<div className='model-import-action-index'>
<div className='model-import-action-index' id='model-import-action-index'>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<Space>
<h2 style={{ marginBottom: 0 }}>数据表索引</h2>
<h3 style={{ marginBottom: 0 }}>数据表索引</h3>
{
editable && <Popover content='点击行进行编辑,表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space>
<Space>
{
......@@ -844,7 +844,8 @@ const ImportActionIndex = (props) => {
</div>
</Space>
</div>
<div className='mb-3' id="containerId" ref={tableRef}>
{
!isCollapse && <div className='mb-3' id="containerId" ref={tableRef}>
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
......@@ -895,17 +896,18 @@ const ImportActionIndex = (props) => {
/>
</Form>
</DndProvider>
</div>
}
<RcMenu id={MENU_ID} >
<RcItem id="up" onClick={handleItemClick}>
在上方插入行
</RcItem>
<RcItem id="down" onClick={handleItemClick}>
在下方插入行
{
(menuData??[]).map(item => (
<RcItem key={item.key} id={item.key} onClick={handleItemClick}>
{item.title}
</RcItem>
))
}
</RcMenu>
</div>
</div>
);
};
......
import React from 'react'
import { Button, Form, Descriptions, Input, Row, Col } from 'antd'
import { DownOutlined, UpOutlined } from '@ant-design/icons'
import { Subject } from 'rxjs';
import { dispatch } from '../../../../model'
export const ImportActionHeaderSubject = new Subject();
const FC = (props) => {
const { editable, form, modelerData } = props
const [isCollapse, setCollapse] = React.useState(true)
const [maintenanceRecords, setMaintenanceRecords] = React.useState()
React.useEffect(() => {
if (modelerData?.id) {
getMaintenanceRecords()
}
const $$header = ImportActionHeaderSubject.subscribe((act) => {
if (act?.type === 'refreshMaintenanceRecords') {
getMaintenanceRecords();
}
})
return () => {
$$header.unsubscribe()
}
}, [modelerData])
const maintenanceDescription = React.useMemo(() => {
if ((maintenanceRecords??[]).length>0) {
let newDescription = ''
for (const [index, record] of maintenanceRecords.entries()) {
if (index !== 0) {
newDescription += '/'
}
newDescription += record
}
return newDescription
}
return ''
}, [maintenanceRecords])
const getMaintenanceRecords = () => {
dispatch({
type: 'datamodel.getMaintenanceRecords',
payload: {
params: {
id: modelerData?.id
}
},
callback: data => {
setMaintenanceRecords(data);
}
})
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
return (
<div>
<div className='model-import-action-manage mb-3' style={{
display: 'flex',
alignItems: 'center',
}}
>
<h3 className='mr-3' style={{ marginBottom: 0 }}>管理信息</h3>
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</div>
{
!isCollapse && <React.Fragment>
{
editable ? (
<Form form={form} {...formItemLayout}>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="维护历史"
>
<Input.TextArea rows={3} disabled={true} value={maintenanceDescription} />
</Form.Item>
</Col>
</Row>
</Form>
) : (
<Descriptions column={3}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }} >维护历史</div>} >
<div style={{ maxHeight: 70, overflow: 'auto' }}>
{
(maintenanceRecords||[]).map((record, index) => {
return <div key={index}>{record||''}</div>;
})
}
</div>
</Descriptions.Item>
</Descriptions>
)
}
</React.Fragment>
}
</div>
)
}
export default FC
\ No newline at end of file
import React from "react"
import { Button, Descriptions, Space, Popover } from "antd"
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { Action, ModelerId, PermitCheckOut, Editable, StateId, Holder, ReadOnly } from '../../../../util/constant'
export const inheritanceHistoricalType = 'historical'
export const inheritanceZipperType = 'zipper'
const FC = (props) => {
const { modelerData, action } = props
const [isCollapse, setCollapse] = React.useState(true)
const [relationModelerDatas, setRelationModelerDatas] = React.useState([])
React.useEffect(() => {
if (modelerData?.inheritedFromEasyDataModelerDataModel) {
const newRelationModelerDatas = [];
newRelationModelerDatas.push(modelerData?.inheritedFromEasyDataModelerDataModel);
if (modelerData?.otherEasyDataModelerDataModelsInheritedFromSameOrigin) {
Object.keys(modelerData.otherEasyDataModelerDataModelsInheritedFromSameOrigin).forEach(key => {
newRelationModelerDatas.push(modelerData.otherEasyDataModelerDataModelsInheritedFromSameOrigin[key]);
})
}
setRelationModelerDatas(newRelationModelerDatas);
} else {
const newRelationModelerDatas = [];
if (modelerData?.inheritedEasyDataModelerDataModels?.historical) {
newRelationModelerDatas.push(modelerData?.inheritedEasyDataModelerDataModels?.historical);
}
if (modelerData?.inheritedEasyDataModelerDataModels?.zipper) {
newRelationModelerDatas.push(modelerData?.inheritedEasyDataModelerDataModels?.zipper);
}
setRelationModelerDatas(newRelationModelerDatas);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
return (
<div>
<div className='model-import-action-relation mb-3'>
<Space>
<h3 style={{ marginBottom: 0 }}>关联对象</h3>
{
action==='add' && <Popover content='保存当前模型后方可选择历史存储形式'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space>
</div>
{
!isCollapse && <Descriptions column={3}>
<Descriptions.Item
label={
<div style={{ textAlign: 'right', width: 100 }}>
历史存储形式
</div>}
>
<span>
{
relationModelerDatas?.length===0 ? '暂无信息' :
relationModelerDatas?.map((item, index) => (
<a className='mr-3' key={index} onClick={() => {
window.open(`/data-govern/data-model-action?${Action}=detail&${ModelerId}=${item.id}&${PermitCheckOut}=${item.permitCheckOut||false}&${Editable}=${item.editable||false}&${StateId}=${item.state?.id||''}&${Holder}=${item.holder||''}&${ReadOnly}=false`);
}}>
{item.cnName}
</a>
))
}
</span>
</Descriptions.Item>
</Descriptions>
}
</div>
)
}
export default FC
\ No newline at end of file
import React, { useState, useCallback, useRef, useEffect } from 'react';
import React, { useState, useCallback, useRef, useEffect, useContext, useMemo } from 'react';
import { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table, Pagination, Space } from 'antd';
import { CheckOutlined, PlusOutlined, QuestionCircleOutlined, DeleteOutlined, HeartOutlined, HeartFilled } from '@ant-design/icons';
import { CheckOutlined, PlusOutlined, QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
......@@ -14,18 +14,22 @@ import { dispatch, dispatchLatest } from '../../../../model';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { AppContext } from '../../../../App';
import DebounceInput from './DebounceInput';
import SuggestTable from './SuggestTable';
import Suggest from './suggest';
import { AttentionSvg, UnAttentionSvg } from './ModelSvg';
import { EditModelContext } from './ContextManage';
import { ValidateTip } from './ImportActionHeader';
import './ImportActionTable.less';
import 'react-contexify/dist/ReactContexify.css';
const { Option } = Select;
const type = 'DragableTableBodyRow';
const type = `DragableTableBodyRow${generateUUID()}`;
const perSuggestCount = 5;
const supportMaxAttributeCountPerPage = 100;
const MENU_ID = 'model-attribute-menu';
const InputDebounce = DebounceInput(300)(Input);
export const InputDebounce = DebounceInput(300)(Input);
const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...restProps } = props;
......@@ -57,7 +61,7 @@ const ResizeableHeaderCell = props => {
);
};
const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
export const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
const onNameChange = (value) => {
......@@ -101,17 +105,13 @@ const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
value = value ? value: {};
return (
<>
<Row align='middle'>
<Col span={9}>
<span>名称:</span>
</Col>
<Col span={15}>
<span onClick={e => e.stopPropagation()}>
<div className='flex' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<div onClick={e => e.stopPropagation()} style={{ flex: 1 }}>
<Select
onChange={onNameChange}
value={value.name || ''}
placeholder='请选择类型名称'
style={{ width: '100%' }}
>
{
(datatypes||[]) && datatypes.map((_datatype, index) => {
......@@ -121,36 +121,29 @@ const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
})
}
</Select>
</span>
</Col>
</Row>
</div>
{
(value.parameterCnNames||[]).map((parameterCnName, index) => {
//使用InputNumber:当value改变时 InputNumber显示值没改变 但实际值有改变 是ant design的bug 这里使用只能输入数字的Input
return (
<Row key={index} className='mt-2' align='middle'>
<Col span={9}>
<span>{`${parameterCnName||''}:`}</span>
</Col>
<Col span={15}>
<div key={index} className='ml-2' style={{ flex: 1 }}>
<Input
onChange={(e) => {
onParameterValuesChange(e, index);
}}
value={value.parameterValues[index] || ''}
style={{ width: '100%' }}
placeholder='请输入一个整数'
placeholder={parameterCnName}
/>
</Col>
</Row>
</div>
);
})
}
</>
</div>
)
}
const EditableCell = ({
export const EditableCell = ({
editing,
dataIndex,
colTitle,
......@@ -159,15 +152,21 @@ const EditableCell = ({
index,
datatypes,
require,
onPressEnter,
children,
...restProps
}) => {
let editingComponent = null;
if (editing) {
if (dataIndex !== 'datatype') {
let inputNode = <InputDebounce onPressEnter={() => { onPressEnter?.() }} />;
const inputNode = inputType === 'check' ? <Checkbox /> : <InputDebounce />
if (inputType === 'check') {
inputNode = <Checkbox />;
} else if (inputType === 'textarea') {
inputNode = <Input.TextArea autoSize={{ minRows: 1, maxRows: 6 }} />;
} else if (inputType === 'datatype') {
inputNode = <DatatypeInput datatypes={datatypes} />;
}
editingComponent = (
<Form.Item
......@@ -186,28 +185,7 @@ const EditableCell = ({
{ inputNode }
</Form.Item>
);
} else {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: (require===null)?false:require,
message: `请输入${colTitle}!`,
},
]}
>
<DatatypeInput datatypes={datatypes} />
</Form.Item>
)
}
}
return (
<td {...restProps}>
......@@ -220,7 +198,7 @@ const EditableCell = ({
);
};
const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
export const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop(
......@@ -243,7 +221,7 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) =>
}
},
}),
[index],
[index, moveRow],
);
const [, drag] = useDrag(
() => ({
......@@ -259,7 +237,7 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) =>
isDragging: monitor.isDragging(),
}),
}),
[index],
[index, moveRow],
);
drop(drag(ref));
......@@ -273,32 +251,37 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) =>
);
};
const ImportActionTable = (props) => {
export const ImportActionTable = (props) => {
const { modelerData, onChange, editable, supportedDatatypes, constraint, template, validateReports, type = 'model', terms, action, originAction } = props;
const [ data, setData ] = useState([]);
const [ form ] = Form.useForm();
const [ editingKey, setEditingKey ] = useState('');
const [ loadingSuggest, setLoadingSuggest ] = useState(false);
const [ suggests, setSuggests ] = useState([]);
const [ suggestHaveMore, setSuggestHaveMore ] = useState(false);
const [ suggestOffset, setSuggestOffset ] = useState(1);
const [ currentChangedValues, setCurrentChangedValues] = useState({});
const [ englishSuggests, setEnglishSuggests ] = useState([]);
const [ keywordCondition, setKeywordCondition ] = useState({ keyword: '', needFilter: true });
const { keyword, needFilter } = keywordCondition;
const [ tableWidth, setTableWidth ] = useState(0);
const [ autoTranslate, setAutoTranslate ] = useState(false);
const [ filterPageCondition, setFilterPageCondition ] = useState({ pageNum: 1, pageSize: supportMaxAttributeCountPerPage, filterData: [] });
const [ filterPageData, setFilterPageData ] = useState([]);
const { pageNum, pageSize, filterData } = filterPageCondition;
const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null);
const [suggestParams, setSuggestParams] = useState({
visible: false,
name: undefined,
cnName: undefined,
triggerType: undefined,
})
const { attrIsEditingFunction } = useContext(EditModelContext);
const moveRowRef = useRef({ data, pageNum, pageSize });
const tableRef = useRef(null);
const termsRef = useRef(null);
const autoTranslateRef = useRef(false);
const app = useContext(AppContext)
const { show } = useContextMenu({
id: MENU_ID,
......@@ -309,7 +292,7 @@ const ImportActionTable = (props) => {
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
width: 44,
fixed: 'left',
render: (text, record, index) => {
return (index+1).toString();
......@@ -323,13 +306,14 @@ const ImportActionTable = (props) => {
ellipsis: true,
require: true,
fixed: 'left',
render: (text, _, __) => {
render: (text, record, __) => {
return (
<React.Fragment>
<ValidateTip type='DataModelAttribute' propertyName='cnName' validateReports={validateReports} iid={record.iid} />
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
......@@ -342,9 +326,12 @@ const ImportActionTable = (props) => {
require: true,
render: (text, record, index) => {
return (
<React.Fragment>
<ValidateTip type='DataModelAttribute' propertyName='name' validateReports={validateReports} iid={record.iid} />
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, terms)}</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
......@@ -374,9 +361,9 @@ const ImportActionTable = (props) => {
},
{
title: 'Not Null',
width: 80,
width: 70,
dataIndex: 'notNull',
editable: (type==='model'?true:false),
editable: true,
render: (notNull, record, index) => {
if (!notNull) {
return '-';
......@@ -391,7 +378,7 @@ const ImportActionTable = (props) => {
},
{
title: '主键',
width: 50,
width: 44,
dataIndex: 'partOfPrimaryKeyLogically',
editable: (type==='model'?true:false),
render: (partOfPrimaryKeyLogically, record, index) => {
......@@ -408,7 +395,7 @@ const ImportActionTable = (props) => {
},
{
title: '外键',
width: 50,
width: 44,
dataIndex: 'foreignKey',
editable: (type==='model'?true:false),
render: (foreignKey, record, index) => {
......@@ -423,23 +410,23 @@ const ImportActionTable = (props) => {
return '';
}
},
// {
// title: '重点关注',
// width: 75,
// dataIndex: 'needAttention',
// editable: (type==='model'?true:false),
// render: (needAttention, record, index) => {
// if (!needAttention) {
// return '-';
// } else if (needAttention === true) {
// return (
// <CheckOutlined />
// )
// }
// return '';
// }
// },
{
title: '重点关注',
width: 80,
dataIndex: 'needAttention',
editable: false,
render: (needAttention, record, index) => {
if (!needAttention) {
return '-';
} else if (needAttention === true) {
return (
<CheckOutlined />
)
}
return '';
}
},
{
title: '业务含义',
dataIndex: 'remark',
......@@ -447,13 +434,14 @@ const ImportActionTable = (props) => {
ellipsis: true,
require: true,
width: 200,
render: (text, _, __) => {
render: (text, record, __) => {
return (
<React.Fragment>
<ValidateTip type='DataModelAttribute' propertyName='remark' validateReports={validateReports} iid={record.iid} />
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
......@@ -466,9 +454,9 @@ const ImportActionTable = (props) => {
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
<Typography.Text ellipsis={true}>
{highlightSearchContentByTerms(text, termsRef.current)}
</Typography.Text>
</Tooltip>
)
}
......@@ -479,13 +467,14 @@ const ImportActionTable = (props) => {
editable: true,
ellipsis: true,
width: 200,
render: (text, _, __) => {
render: (text, record, __) => {
return (
<React.Fragment>
<ValidateTip type='DataModelAttribute' propertyName='definition' validateReports={validateReports} iid={record.iid} />
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
......@@ -495,13 +484,14 @@ const ImportActionTable = (props) => {
editable: true,
ellipsis: true,
width: 100,
render: (text, _, __) => {
render: (text, record, __) => {
return (
<React.Fragment>
<ValidateTip type='DataModelAttribute' propertyName='valueRange' validateReports={validateReports} iid={record.iid} />
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
)
}
},
......@@ -513,23 +503,28 @@ const ImportActionTable = (props) => {
save();
}, tableRef);
useEffect(() => {
attrIsEditingFunction && attrIsEditingFunction(editingKey!=='');
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ editingKey ])
//规则改变的时候 数据表为可编辑状态
useEffect(() => {
setEditingKey('');
}, [constraint, template, modelerData])
useEffect(() => {
termsRef.current = terms;
setData(modelerData?.easyDataModelerDataModelAttributes||[]);
setData(modelerData.easyDataModelerDataModelAttributes||[]);
moveRowRef.current.data = (modelerData?.easyDataModelerDataModelAttributes||[]);
moveRowRef.current.data = (modelerData.easyDataModelerDataModelAttributes||[]);
let _filterData = (modelerData.easyDataModelerDataModelAttributes||[]).filter(item => (item?.name||'').indexOf(keyword)!==-1 || (item.cnName).indexOf(keyword)!==-1);
let _filterData = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(item => (item?.name||'').indexOf(keyword)!==-1 || (item.cnName).indexOf(keyword)!==-1);
setFilterPageCondition({...filterPageCondition, filterData: _filterData});
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
}, [modelerData, terms])
useEffect(() => {
if (needFilter) {
......@@ -565,6 +560,41 @@ const ImportActionTable = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports, editable, editingKey])
const menuData = useMemo(() => {
let newMenuData = []
if (action === 'flow') {
if (currentItem?.isPossibleNewRecommendedDefinition?.possible) {
newMenuData.push({
title: '加入标准',
key: 'addToStandard',
})
}
if (currentItem?.isPossibleNewTerm?.possible) {
newMenuData.push({
title: '加入词汇',
key: 'addToWord',
})
}
}
if (editable) {
newMenuData = [...newMenuData, ...[
{ title: '在上方插入行', key: 'up' },
{ title: '在下方插入行', key: 'down' },
{ title: '删除', key: 'delete' },
]]
if (originAction !== 'flow') {
newMenuData.push({
title: currentItem?.needAttention ? '取消送审关注': '送审关注',
key: 'attention'
})
}
}
return newMenuData;
}, [currentItem, action, originAction, editable])
const isEditing = (record) => record?.iid === editingKey;
const onAddClick = (event) => {
......@@ -598,7 +628,7 @@ const ImportActionTable = (props) => {
})
}
const insertToFront = (record) => {
const onInsertToFrontClick = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
......@@ -629,7 +659,7 @@ const ImportActionTable = (props) => {
})
}
const insertToBack = (record) => {
const onInsertToBackClick = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
......@@ -659,7 +689,7 @@ const ImportActionTable = (props) => {
form.setFieldsValue(record);
setEditingKey(record?.iid);
setAutoTranslate((record?.name||'')==='');
autoTranslateRef.current = ((record?.name||'')==='');
if ((record?.cnName||'')!=='') {
onValuesChange({ cnName: record?.cnName }, record, 1, record?.iid);
......@@ -691,7 +721,7 @@ const ImportActionTable = (props) => {
onChange && onChange(newData);
}
const remove = (record) => {
const onRemoveClick = (record) => {
if (record.iid !== editingKey) {
save().then(result => {
if (result) {
......@@ -703,6 +733,50 @@ const ImportActionTable = (props) => {
}
}
const onAttentionClick = (record) => {
if (record.needAttention === null) {
record.needAttention = true;
} else {
record.needAttention = !record.needAttention;
}
const newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
if (index !== -1) {
newData.splice(index, 1, {...record});
setData(newData);
moveRowRef.current.data = newData;
onChange && onChange(newData, false);
if (!record.needAttention) {
preSaveDataModel(record);
}
}
}
const onAddToStandardClick = (record) => {
app.setGlobalState?.({
message: 'data-govern-show-standard-create',
data: {
column: {...record, ...{ modelName: modelerData?.name, modelCnName: modelerData?.cnName }},
type: record?.isPossibleNewRecommendedDefinition?.type
}
});
}
const onAddToWordClick = (record) => {
app.setGlobalState?.({
message: 'data-govern-show-standard-create',
data: {
column: record,
type: record?.isPossibleNewTerm?.type
}
})
}
const preSaveDataModel = (attribute) => {
dispatch({
type: 'datamodel.preSaveDataModel',
......@@ -782,7 +856,6 @@ const ImportActionTable = (props) => {
}
setEditingKey('');
setSuggests([]);
setEnglishSuggests([]);
}
......@@ -803,72 +876,8 @@ const ImportActionTable = (props) => {
ownPrimaryKey = (modelerData.easyDataModelerPrimaryKey.filter(item => item.name===allValues.name).length>0);
}
if (changedValues.hasOwnProperty('cnName') || changedValues.hasOwnProperty('name')) {
if (offset === 1) {
setSuggests([]);
}
const newData = [...data];
const index = newData.findIndex((item) => iid === item.iid);
if (index === -1) {
newData.splice(0, 0, { iid, ...allValues});
} else if (index !== -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...allValues });
}
setSuggestHaveMore(false);
setCurrentChangedValues(changedValues);
function getSuggest() {
setLoadingSuggest(true);
dispatchLatest({
type: 'datamodel.suggest',
payload: {
params: {
name: allValues.name||'',
cnName: allValues.cnName||'',
topN: (offset+perSuggestCount-1),
offset
}
},
callback: data => {
setLoadingSuggest(false);
if (changedValues.hasOwnProperty('cnName')) {
const moreSuggests = (data||[]).length>0?(data[0].suggestions||[]):[];
const newSuggests = (offset===1 ? moreSuggests : [...suggests, ...moreSuggests]);
if (moreSuggests.length === perSuggestCount) {
setSuggestHaveMore(true);
}
setSuggestOffset(newSuggests.length+1);
setSuggests(newSuggests);
} else if (changedValues.hasOwnProperty('name')) {
const moreSuggests = (data||[]).length>1?(data[1].suggestions||[]):[];
const newSuggests = (offset===1 ? moreSuggests : [...suggests, ...moreSuggests]);
if (moreSuggests.length === perSuggestCount) {
setSuggestHaveMore(true);
}
setSuggestOffset(newSuggests.length+1);
setSuggests(newSuggests);
}
},
error: () => {
setLoadingSuggest(false);
}
})
}
if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslate) {
if (autoTranslateRef.current) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
......@@ -880,17 +889,11 @@ const ImportActionTable = (props) => {
if ((data?.translated||'') !== '') {
form.setFieldsValue({ name: data?.translated||'' });
}
getSuggest();
}
})
} else {
getSuggest();
}
} else if (changedValues.hasOwnProperty('name')) {
setAutoTranslate(changedValues.name==='');
getSuggest();
}
autoTranslateRef.current(changedValues.name==='');
} else if(changedValues.hasOwnProperty('notNull') ) {
if (!changedValues.notNull && ownPrimaryKey) {
showMessage('info', '主键不允许为空');
......@@ -906,237 +909,76 @@ const ImportActionTable = (props) => {
};
const onSuggestChange = (record) => {
form.resetFields();
const { notNull, partOfPrimaryKeyLogically, foreignKey, ...restRecord } = record;
form.setFieldsValue({
...record
...restRecord
});
setSuggests([]);
};
const editableColumn = [
...cols,
{
title: '操作',
dataIndex: 'action',
width: (action==='flow')?220: ((originAction==='flow')?90:130),
fixed: 'right',
render: (_, record) => {
return (
<AppContext.Consumer>
{
value => <React.Fragment>
{
(action==='flow') && <React.Fragment>
{
record?.isPossibleNewRecommendedDefinition?.possible && <Typography.Link className='mr-3' onClick={(event) => {
event.stopPropagation();
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: record,
type: record?.isPossibleNewRecommendedDefinition?.type
}
});
}}>
加入标准
</Typography.Link>
}
{
record?.isPossibleNewTerm?.possible && <Typography.Link className='mr-3' onClick={(event) => {
event.stopPropagation();
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: record,
type: record?.isPossibleNewTerm?.type
}
})
}}>
加入词汇
</Typography.Link>
}
</React.Fragment>
}
{
editable && <React.Fragment>
{
<React.Fragment>
{
(originAction!=='flow') && <Tooltip title={record.needAttention ? '取消送审关注': '送审关注'}>
<Button
className='mr-3'
size='small'
type='text'
icon={record.needAttention ? <HeartFilled style={{ color: 'red' }} /> : <HeartOutlined />}
onClick={(event) => {
event.stopPropagation();
if (record.needAttention === null) {
record.needAttention = true;
} else {
record.needAttention = !record.needAttention;
}
const newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
if (index !== -1) {
newData.splice(index, 1, record);
setData(newData);
moveRowRef.current.data = newData;
onChange && onChange(newData, false);
if (!record.needAttention) {
preSaveDataModel(record);
}
}
}}
/>
</Tooltip>
}
<Button
className='mr-3'
size='small'
type='text'
icon={<PlusOutlined className='default' />}
onClick={(event) => {
event.stopPropagation();
insertToFront(record);
}}
/>
<Button
className='mr-3'
size='small'
type='text'
icon={<DeleteOutlined style={{ color: 'red' }} />}
onClick={(event) => {
event.stopPropagation();
remove(record);
}}
/>
</React.Fragment>
}
</React.Fragment>
}
</React.Fragment>
}
</AppContext.Consumer>
)
},
},
]
const validateColumn = {
title: '规范',
dataIndex: 'validate',
//小屏幕下隐藏规范, 给编辑多点空间
width: (tableWidth<1300 && editable)?0:200,
fixed: 'right',
render: (text, record, index) => {
let shortCauses = [];
let causes = [];
(validateReports||[]).forEach((report) => {
if (report.type==='DataModelAttribute' && report.iid === record.iid) {
(report.reportItems||[]).forEach((item) => {
shortCauses.push(item.shortCause||'');
causes.push(item.cause||'');
})
}
});
return (
<div className='d-flex' key={index} style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<div>
{
shortCauses && shortCauses.map((shortCause, index) => {
return (
<Tooltip title={shortCause}>
<div className='textOverflow' style={{ width: 130, color: '#f5222d' }} key={index}>
<span>{shortCause||''}</span>
</div>
</Tooltip>
);
const onColumnPressEnter = (title) => {
if (title === '中文名称') {
setSuggestParams({
visible: true,
name: form?.getFieldValue('name'),
cnName: form?.getFieldValue('cnName'),
triggerType: 'cnName',
})
}
</div>
{
(causes||[]).length>0 && <Popover
content={
<div style={{ maxWidth: 200, maxHeight: 200, wordWrap: 'break-word', overflow: 'auto' }}>
{
causes && causes.map((cause, index) => {
return (
<Typography.Paragraph key={index} >
{cause||''}
</Typography.Paragraph>
)
} else if (title === '英文名称') {
setSuggestParams({
visible: true,
name: form?.getFieldValue('name'),
cnName: form?.getFieldValue('cnName'),
triggerType: 'name',
})
}
</div>
}
title="规则详情"
>
<a href="#">详情</a>
</Popover>
}
</div>
)
}
};
const includeValidateColumn = [
...cols,
validateColumn
]
const includeValidateEditableColumn = [
...editableColumn,
validateColumn
]
const mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='DataModelAttribute').length>0;
if (editable) {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
_columns = _columns.map((col) => {
const _columns = cols.map((col) => {
if (!col.editable) {
return col;
}
let inputType = 'text';
if (
col.dataIndex==='notNull'||
col.dataIndex==='partOfDistributionKey' ||
col.dataIndex==='partOfPrimaryKeyLogically' ||
col.dataIndex==='needAttention' ||
col.dataIndex==='foreignKey'
) {
inputType = 'check';
} else if (
col.dataIndex === 'remark' ||
col.dataIndex === 'definition' ||
col.dataIndex === 'valueRange'
) {
inputType = 'textarea';
} else if (col.dataIndex === 'datatype') {
inputType = 'datatype';
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
inputType: (col.dataIndex==='notNull' || col.dataIndex==='partOfDistributionKey' || col.dataIndex==='partOfPrimaryKeyLogically' || col.dataIndex==='needAttention' || col.dataIndex==='foreignKey') ? 'check' : 'text',
inputType,
colTitle: col.title,
editing: isEditing(record),
datatypes: supportedDatatypes,
require: col.require
require: col.require,
onPressEnter: () => {
onColumnPressEnter(col.title)
}
}),
};
});
setColumns(_columns);
} else {
let _columns = hasValidateReports ? includeValidateColumn: cols;
if (action === 'flow') {
_columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
}
// _columns = _columns.filter((col) => col.dataIndex!=='needAttention');
setColumns(_columns);
setColumns(cols);
}
}
......@@ -1165,7 +1007,7 @@ const ImportActionTable = (props) => {
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[moveRowRef.current],
[moveRowRef.current, onChange],
);
const onSearchInputChange = (value) => {
......@@ -1174,20 +1016,6 @@ const ImportActionTable = (props) => {
setKeywordCondition({ keyword: value||'', needFilter: true });
}
const loadMoreSuggests = (event) => {
event.stopPropagation();
onValuesChange(currentChangedValues, form.getFieldsValue(), suggestOffset);
}
const closeSuggests = (event) => {
event.stopPropagation();
setSuggestHaveMore(false);
setSuggests([]);
setSuggestOffset(1);
}
const displayMenu = (e) => {
show(e);
}
......@@ -1196,9 +1024,17 @@ const ImportActionTable = (props) => {
const key = event.currentTarget.id;
if (key === 'up') {
insertToFront(currentItem);
onInsertToFrontClick(currentItem);
} else if (key === 'down') {
insertToBack(currentItem);
onInsertToBackClick(currentItem);
} else if (key === 'delete') {
onRemoveClick(currentItem);
} else if (key === 'attention') {
onAttentionClick(currentItem);
} else if (key === 'addToStandard') {
onAddToStandardClick(currentItem);
} else if (key === 'addToWord') {
onAddToWordClick(currentItem);
}
}
......@@ -1227,10 +1063,10 @@ const ImportActionTable = (props) => {
}
return (
<div className='model-import-action-table'>
<div className='model-import-action-table' id='model-import-action-table'>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<h2 style={{ marginBottom: 0 }}>数据表结构</h2>
<h3 style={{ marginBottom: 0 }}>数据表结构</h3>
{
editable && <Popover content={<span>
新增: 点击操作列的加号按钮在当前字段前新增一个字段<br />
......@@ -1240,7 +1076,8 @@ const ImportActionTable = (props) => {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;支持拖拽方式调整模型字段顺序<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;点击表格外部任意位置退出字段编辑状态<br />
删除: 点击操作列垃圾桶图标删除该字段<br />
保存: 页面右下角保存按钮
保存: 页面右下角保存按钮<br />
中/英文输入框回车键触发推荐浮窗
</span>}>
<QuestionCircleOutlined className='pointer' />
</Popover>
......@@ -1283,40 +1120,52 @@ const ImportActionTable = (props) => {
},
}}
rowClassName={(record, index) => {
if (type==='model' && record?.modelingTemplateTag && record?.modelingTemplateTag!=={} && !isEditing(record)) {
return 'editable-row template-highlight-row';
}
if (type==='model' && !isEditing(record)) {
if (record?.needAttention) {
return 'editable-row attention-row';
} else if (record?.partOfPrimaryKeyLogically) {
if (record?.partOfPrimaryKeyLogically) {
return 'editable-row primary-row';
}
if (record?.provided || record?.inherited || record?.modelingTemplateTag?.id) {
return 'editable-row gray-row';
}
}
return 'editable-row';
}}
onRow={(record, index) => {
let rowParams = {
index,
id: `field-${record.iid}`,
}
let shouldRowContextMenu = false
if (action==='flow') {
if (record?.isPossibleNewRecommendedDefinition?.possible || record?.isPossibleNewTerm?.possible) {
shouldRowContextMenu = true
}
}
if (editable) {
shouldRowContextMenu = true
}
if (shouldRowContextMenu) {
rowParams = {...rowParams, onContextMenu: event => {
setCurrentItem(record);
displayMenu(event);
}
};
}
if (editable) {
if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation();
if (record.inherited || record.provided) {
showMessage('warn', '请到对应的当前表修改');
} else {
edit(record);
}
}
}
if (keyword.length===0) {
rowParams = {...rowParams, moveRow};
......@@ -1337,34 +1186,6 @@ const ImportActionTable = (props) => {
//解决屏幕尺寸窄时,字段不好横向拖动的问题
y: tableWidth>1500?'100%':630,
}}
expandable={{
columnWidth: 0,
expandedRowRender: record => (
<React.Fragment>
{
editingKey!=='' && <React.Fragment>
{
suggests && suggests.length>0 && (
<SuggestTable suggests={suggests} onSelect={onSuggestChange} />
)
}
</React.Fragment>
}
<div className='flex pt-3' style={{ justifyContent: 'center' }}>
<Tooltip title={!suggestHaveMore?'没有更多推荐字段': ''}>
<Button onClick={loadMoreSuggests} disabled={!suggestHaveMore} loading={loadingSuggest}>加载更多</Button>
</Tooltip>
<Button className='ml-3' onClick={closeSuggests}>收起推荐</Button>
</div>
</React.Fragment>
),
expandIcon: ({ expanded, onExpand, record }) => {
return null;
},
rowExpandable: record => (editingKey!==''&&((suggests||[]).length>0 || (englishSuggests||[]).length>0)),
expandedRowKeys: [editingKey]
}}
/>
</Form>
</DndProvider>
......@@ -1389,18 +1210,32 @@ const ImportActionTable = (props) => {
/>
}
<div onClick={(e) => { e.stopPropagation() }}>
<Suggest
{...suggestParams}
onCancel={() => {
setSuggestParams({
visible: false,
name: undefined,
cnName: undefined,
triggerType: undefined,
})
}}
onOk={onSuggestChange}
/>
</div>
<RcMenu id={MENU_ID} >
<RcItem id="up" onClick={handleItemClick}>
在上方插入行
</RcItem>
<RcItem id="down" onClick={handleItemClick}>
在下方插入行
{
(menuData??[]).map(item => (
<RcItem key={item.key} id={item.key} onClick={handleItemClick}>
{item.title}
</RcItem>
))
}
</RcMenu>
</div>
</div>
);
};
export default ImportActionTable;
\ No newline at end of file
......@@ -11,21 +11,16 @@
}
}
.template-highlight-row {
.yy-table-cell {
background-color: #e7f7ff !important;
}
}
.attention-row {
.primary-row {
.yy-table-cell {
background-color: #9dcef3 !important;
background-color: #d3ebff !important;
}
}
.primary-row {
.gray-row {
.yy-table-cell {
background-color: #d3ebff !important;
background-color: #f7f7f7 !important;
}
}
}
\ No newline at end of file
export const UnAttentionSvg = (props) => (
<svg
className="icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
{...props}
>
<defs>
<style>
{
'@font-face{font-family:feedback-iconfont;src:url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944) format("woff2"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944) format("woff"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944) format("truetype")}'
}
</style>
</defs>
<path
d="M873.813 989.867c-3.413 0-8.533-1.707-11.946-3.414L512 805.547 162.133 986.453c-8.533 3.414-17.066 3.414-25.6-1.706-6.826-5.12-11.946-13.654-11.946-22.187V126.293c0-51.2 40.96-92.16 92.16-92.16h588.8c51.2 0 92.16 40.96 92.16 92.16v837.974c0 8.533-5.12 17.066-11.947 22.186-3.413 1.707-6.827 3.414-11.947 3.414zM512 750.933c3.413 0 8.533 1.707 11.947 3.414L848.213 921.6V126.293c0-22.186-18.773-40.96-40.96-40.96H216.747c-22.187 0-40.96 18.774-40.96 40.96V921.6l324.266-167.253c3.414-1.707 8.534-3.414 11.947-3.414z"
fill="#333"
/>
<path
d="M605.867 590.507c-6.827 0-13.654-1.707-20.48-5.12L512 546.133l-75.093 39.254c-13.654 8.533-32.427 6.826-44.374-3.414-13.653-10.24-20.48-25.6-17.066-40.96l13.653-81.92-61.44-59.733c-11.947-11.947-15.36-27.307-10.24-42.667s18.773-25.6 34.133-29.013l83.627-11.947 37.547-75.093c6.826-15.36 22.186-23.893 37.546-23.893s30.72 8.533 37.547 23.893l37.547 75.093 83.626 11.947c15.36 1.707 29.014 13.653 34.134 29.013s1.706 32.427-10.24 42.667l-61.44 59.733 13.653 81.92c3.413 15.36-3.413 32.427-17.067 40.96-5.12 5.12-13.653 8.534-22.186 8.534zM512 493.227c6.827 0 13.653 1.706 20.48 5.12l63.147 34.133-11.947-68.267c-1.707-13.653 1.707-27.306 11.947-37.546l51.2-51.2-69.974-10.24c-13.653-1.707-25.6-10.24-32.426-23.894L512 276.48l-30.72 64.853c-6.827 11.947-18.773 20.48-32.427 23.894l-71.68 10.24 51.2 51.2c10.24 10.24 15.36 23.893 11.947 37.546l-11.947 68.267 63.147-34.133c6.827-3.414 13.653-5.12 20.48-5.12z"
fill="#333"
/>
</svg>
)
export const AttentionSvg = (props) => (
<svg
className="icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
{...props}
>
<defs>
<style>
{
'@font-face{font-family:feedback-iconfont;src:url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944) format("woff2"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944) format("woff"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944) format("truetype")}'
}
</style>
</defs>
<path
d="M826.027 34.133H197.973c-37.546 0-68.266 30.72-68.266 68.267v887.467L512 791.893l382.293 197.974V102.4c0-37.547-30.72-68.267-68.266-68.267zm-148.48 337.92L612.693 435.2c-3.413 3.413-5.12 10.24-5.12 15.36l15.36 87.04c1.707 13.653-11.946 23.893-23.893 17.067L520.533 512c-5.12-3.413-10.24-3.413-15.36 0l-78.506 42.667c-11.947 6.826-27.307-3.414-23.894-17.067l15.36-87.04c1.707-5.12 0-10.24-5.12-15.36l-64.853-63.147c-10.24-10.24-5.12-27.306 8.533-29.013l88.747-13.653c5.12 0 10.24-3.414 11.947-8.534l39.253-80.213c6.827-11.947 23.893-11.947 30.72 0l39.253 80.213c1.707 5.12 6.827 8.534 11.947 8.534l88.747 13.653c13.653 1.707 18.773 18.773 10.24 29.013z"
fill='#196AD2'
/>
</svg>
)
import React from 'react'
import { Modal, Button, Spin, Tooltip, Typography, Space, Input } from "antd"
import { dispatch } from '../../../../model'
import Table from '../../../../util/Component/Table'
import { checkMenuAdmit, inputWidth, isSzseEnv, showMessage } from '../../../../util'
import { useDebounceEffect } from 'ahooks'
const topN = 20
const FC = (props) => {
const { visible, name, cnName, onCancel, onOk, triggerType = 'cnName' } = props
const [loading, setLoading] = React.useState(false)
const [suggests, setSuggests] = React.useState()
const [selectedRows, setSelectedRows] = React.useState()
const [havaMore, setMore] = React.useState(false)
const [offset, setOffset] = React.useState(1)
const [animating, setAnimating] = React.useState(false)
const [args, setArgs] = React.useState({
name: undefined,
cnName: undefined,
triggerType: undefined,
})
React.useEffect(() => {
if (visible) {
setAnimating(true)
setArgs({
name,
cnName,
triggerType
})
setTimeout(() => {
setAnimating(false)
}, 300)
}
}, [visible])
useDebounceEffect(()=>{
if (args.name || args.cnName) {
getSuggests()
}
}, [args], { wait:300 })
const onSourceClick = (id, name) => {
const timestamp = new Date().getTime();
const tempArray = (id??'').split('=');
if (tempArray.length>=3) {
dispatch({
type: 'datamodel.getParent',
payload: {
id
},
callback: data => {
window.open(`/center-home/metadetail?mid=${encodeURIComponent(data?._id)}&action=metadetail&type=detail&manager=false&activekey=1&name=${encodeURIComponent(name||'')}`);
}
})
} else {
if (checkMenuAdmit('datastandard')) {
window.open(`/center-home/menu/datastandard?id=${id}&timestamp=${timestamp}`);
}
}
}
const cols = React.useMemo(() => {
return [
{
title: '中文名称',
dataIndex: 'cnName',
width: 200,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '英文名称',
dataIndex: 'name',
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '类型',
dataIndex: 'datatype',
width: 160,
render: (_, record, __) => {
if (record?.datatype) {
if ((record?.datatype?.name==='Char'||record?.datatype?.name==='Varchar') && record?.datatype?.parameterValues?.length>0) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)})`;
} else if ((record?.datatype?.name==='Decimal'||record?.datatype?.name==='Numeric') && record?.datatype?.parameterValues?.length>1) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)},${(record?.datatype?.parameterValues[1]?record.datatype.parameterValues[1]:0)})`;
}
return record.datatype.name||'';
}
return '';
}
},
{
title: '业务含义',
dataIndex: 'remark',
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<Typography.Text ellipsis={true}>{text||''}</Typography.Text>
</Tooltip>
)
}
},
{
title: '匹配度',
dataIndex: 'score',
width: 100,
render: (_, record, index) => {
return (
<React.Fragment>
<span style={{ color: '#f50' }}>{`${record.recommendedStats?.score}%`}</span>
{ index===0 && <span style={{ color: '#f50' }}> 推荐</span> }
</React.Fragment>
);
}
},
{
title: '使用次数',
dataIndex: 'referencesCount',
width: 80,
render: (_, record) => {
return (
<span>{record.recommendedStats?.referencesCount}</span>
);
}
},
{
title: '来源',
dataIndex: 'source',
render: (_, record) => {
return (
<SourceComponent data={record.recommendedStats?.sourceInfos||[]} name={record.name||''} onClick={onSourceClick} />
);
}
},
]
}, [onSourceClick])
const getSuggests = () => {
setLoading(true)
dispatch({
type: 'datamodel.suggest',
payload: {
params: {
name: args.name,
cnName: args.cnName,
offset,
topN,
}
},
callback: data => {
setLoading(false)
if (args.triggerType === 'cnName') {
setSuggests(prevSuggests => {
const newSuggests = (offset===1) ? data?.[0]?.suggestions : [...prevSuggests??[], ...data?.[0]?.suggestions??[]]
setOffset((data?.[0]?.suggestions??[]).length + offset)
setMore((data?.[0]?.suggestions??[]).length === topN)
return newSuggests
})
} else if (args.triggerType === 'name') {
setSuggests(prevSuggests => {
const newSuggests = (offset===1) ? data?.[1]?.suggestions : [...prevSuggests??[], ...data?.[1]?.suggestions??[]]
setOffset((data?.[1]?.suggestions??[]).length + offset)
setMore((data?.[1]?.suggestions??[]).length === topN)
return newSuggests
})
}
},
error: () => {
setLoading(false)
}
})
}
const close = () => {
setLoading(false)
setAnimating(false)
setSuggests()
setOffset(1)
setMore(false)
onCancel?.()
}
const save = () => {
if ((selectedRows??[]).length === 0) {
showMessage('warn', '请先选择推荐项')
return
}
onOk?.(selectedRows?.[0])
close()
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={footer}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='匹配推荐'
centered destroyOnClose
onCancel={() => { close() }}
>
<div className='mb-3'>
<Space>
<span>中文名称:</span>
<Input size="middle"
placeholder="请输入中文名称"
value={args.cnName}
bordered={true} allowClear
onChange={(e) => {
setOffset(1)
setArgs({...args, cnName: e.target.value, triggerType: 'cnName'})
}}
style={{ width: inputWidth }}
/>
<span>英文名称:</span>
<Input size="middle"
placeholder="请输入英文名称"
value={args.name}
bordered={true} allowClear
onChange={(e) => {
setOffset(1)
setArgs({...args, name: e.target.value, triggerType: 'name'})
}}
style={{ width: inputWidth }}
/>
</Space>
</div>
{
!animating && <Spin spinning={loading}>
<Table
extraColWidth={32}
size='small'
rowKey='iid'
dataSource={suggests||[]}
columns={cols}
pagination={false}
rowClassName={(record, index) => {
return 'pointer';
}}
onRowClick={(event, record) => {
setSelectedRows([record])
}}
rowSelection={{
type: 'radio',
selectedRowKeys: (selectedRows??[]).map(item => item.iid),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
<div className='flex pt-3' style={{ justifyContent: 'center' }}>
<Tooltip title={!havaMore?'没有更多推荐字段':''}>
<Button onClick={getSuggests} disabled={!havaMore} >加载更多</Button>
</Tooltip>
</div>
</Spin>
}
</Modal>
)
}
export default FC
const SourceComponent = (props) => {
const { data, onClick, name } = props;
const moreSourceComponent = <div style={{ maxWidth: 400 }}>
{
(data||[]).map((source, index) => {
return (
<div
className='pointer'
key={index}
style={{
textDecoration: 'underline',
}}
onClick={(e) => {
e.stopPropagation();
onClick && onClick(source.sourceId, name);
}}
>
{source.sourcePath||''}
</div>
);
})
}
</div>;
return (
<Tooltip
title={moreSourceComponent}
overlayClassName='tooltip-common'
>
<a
href='#'
onClick={(e) => {
e.stopPropagation();
onClick && onClick(data[0].sourceId, name);
}}
>
{
(data||[]).length>0 && <span>{data[0].sourcePath||''}</span>
}
</a>
</Tooltip>
);
}
\ No newline at end of file
......@@ -689,14 +689,14 @@ class Model extends React.Component {
</Space>
<Space>
{
{/* {
!keyword && <Space>
<Radio.Group onChange={this.handleShowModeChange} value={showMode}>
<Radio.Button value="list">列表展示</Radio.Button>
<Radio.Button value="graph">图形展示</Radio.Button>
</Radio.Group>
</Space>
}
} */}
{
(currentView==='dir'||keyword!=='') && <Space>
<Select
......
......@@ -3,7 +3,7 @@ import { Spin, Tabs, Popover, Divider, Button, Space } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import TemplateActionHeader from './TemplateActionHeader';
import ImportActionTable from '../../Model/Component/ImportActionTable';
import { ImportActionTable } from '../../Model/Component/ImportActionTable';
import { dispatchLatest, dispatch } from '../../../../model';
......
import React from 'react'
import { Button, Modal, Spin, Typography, Tooltip, Input } from 'antd'
import { dispatch } from '../../../../model'
import Table from '../../../../util/Component/Table'
import { showMessage } from '../../../../util'
import Update from './update-rule-template'
const FC = (props) => {
const { visible, node, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = () => {
const selectedRows = basicRef.current?.selectedRows
if ((selectedRows??[]).length === 0) {
showMessage('warn', '请先选择规则模板')
return
}
setWaiting(true)
dispatch({
type: 'datamodel.addRule',
payload: {
params: {
catalogId: node?.id,
templateIds: (selectedRows??[]).map(item => item.id).toString()
}
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={footer}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='新增检查规则'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({}, ref) {
const [keyword, setKeyword] = React.useState()
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [selectedRows, setSelectedRows] = React.useState()
const [updateParams, setUpdateParams] = React.useState({
visible: false,
type: undefined,
item: undefined
})
React.useImperativeHandle(ref, () => ({
selectedRows
}), [selectedRows])
React.useEffect(() => {
getTemplates()
}, [])
const cols = React.useMemo(() => {
return ([
{
title: '序号',
dataIndex: 'index',
width: 60,
render: (_, __, index) => `${index+1}`
},
{
title: '规则编号',
dataIndex: 'number',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则中文名称',
dataIndex: 'name',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={() => {
setUpdateParams({
visible: true,
type: 'detail',
item: record
})
}}>
{ text }
</a>
</Typography.Text>
</Tooltip>
)
},
{
title: '规则描述',
dataIndex: 'remark',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '检查类型',
dataIndex: 'checkTypeName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
])
}, [])
const _data = React.useMemo(() => {
const _keyword = (keyword??'').trim()
return (data??[]).filter(item => !_keyword
|| (item.name??'').indexOf(_keyword) !== -1
|| (item.remark??'').indexOf(_keyword) !== -1
)
}, [data, keyword])
const getTemplates = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleTemplateList',
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<div>
<div className='d-flex mb-3' style={{ justifyContent: 'end', alignItems: 'center' }}>
<Input size="middle"
placeholder="规则名称/描述搜索"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
setKeyword(e.target.value)
}}
style={{ width: 270 }}
/>
</div>
<Table
extraColWidth={32}
loading={loading}
columns={cols??[]}
dataSource={_data}
pagination={false}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
<Update
{...updateParams}
onCancel={(refresh) => {
setUpdateParams({
visible: false,
type: undefined,
item: undefined
})
refresh && getTemplates()
}}
/>
</div>
)
})
\ No newline at end of file
import React from 'react'
import { Row, Col, Select, Space, Tooltip, Spin, Typography } from 'antd'
import { dispatch } from '../../../../model'
import Table from '../../../../util/Component/Table'
import '../../Model/Component/VersionCompare.less'
const FC = ({ item }) => {
const [versions, setVersions] = React.useState()
const [basicVersion, setBasicVersion] = React.useState()
const [incVersion, setIncVersion] = React.useState()
const [loading, setLoading] = React.useState(false)
const [compareData, setCompareData] = React.useState()
const [loadingCompare, setLoadingCompare] = React.useState(false)
React.useEffect(() => {
if (item?.id) {
getVersions()
}
}, [item])
const [basicVersions, incVersions] = React.useMemo(() => {
let newBasicVersions = [], newIncVersions = []
for (const [index,item] of (versions??[]).entries()) {
let name = `${item.name??''}_${new Date(item.createdTs).toLocaleString()}`
if (index === 0) {
name = name+'(当前版本)'
}
newBasicVersions.push({ id: item.id, name });
}
const index = newBasicVersions.findIndex(item => item.id === basicVersion)
if (index !== -1) {
newIncVersions = newBasicVersions.slice(0, index)
}
return [newBasicVersions, newIncVersions]
}, [versions, basicVersion])
const getVersions = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleCatalogVersionList',
payload: {
catalogId: item?.id,
},
callback: data => {
setLoading(false)
setVersions(data)
if ((data??[]).length >= 2) {
setBasicVersion(data[1].id)
setIncVersion(data[0].id)
getCompare()
}
},
error: () => {
setLoading(false);
}
})
}
const onBasicChange = (value) => {
setBasicVersion(value)
setIncVersion()
setCompareData(null)
}
const onIncChange = (value) => {
setIncVersion(value)
getCompare()
}
const getCompare = () => {
setBasicVersion(prevBasicVersion => {
setIncVersion(prevIncVersion => {
setLoadingCompare(true)
dispatch({
type: 'datamodel.compareRuleCatalogVersion',
payload: {
leftVersionId: prevBasicVersion,
rightVersionId: prevIncVersion
},
callback: data => {
setLoadingCompare(false)
setCompareData(data)
},
error: () => {
setLoadingCompare(false)
}
})
return prevIncVersion
})
return prevBasicVersion
})
}
return (
<div className='model-version-compare'>
<Row gutter={15}>
<Col span={12}>
<Space size={8}>
<span>基线版本:</span>
<Select
loading={loading}
value={basicVersion}
allowClear
onChange={onBasicChange}
style={{ width: 300 }}
>
{
basicVersions?.map((item, index) => <Select.Option
disabled={index===0}
key={index}
value={item.id}>
<Tooltip title={(index===0)?'最近版本只能在增量版本中被选中':''}>
{item.name}
</Tooltip>
</Select.Option>
)
}
</Select>
</Space>
</Col>
<Col span={12}>
<Space size={8}>
<span>增量版本:</span>
<Select
loading={loading}
value={incVersion}
allowClear
onChange={onIncChange}
style={{ width: 300 }}
>
{
incVersions?.map((item, index) => <Select.Option
key={index}
value={item.id}>
{item.name}
</Select.Option>
)
}
</Select>
</Space>
</Col>
</Row>
<div className='py-3'>
<Spin spinning={loadingCompare} >
{
compareData && <div className='flex'>
<div style={{ flex: 1, borderRight: '1px solid #EFEFEF', paddingRight: 7.5 }}>
<Basic
title='基本信息'
header={compareData?.headers?.ruleHeader}
data={compareData?.left?.ruleValue}
/>
<Attribute
title='规则信息'
header={compareData?.headers?.attributeHeader}
data={compareData?.left?.attributeValue}
/>
</div>
<div style={{ flex: 1, paddingLeft: 7.5}}>
<Basic
title='基本信息'
header={compareData?.headers?.ruleHeader}
data={compareData?.right?.ruleValue}
/>
<Attribute
title='规则信息'
header={compareData?.headers?.attributeHeader}
data={compareData?.right?.attributeValue}
/>
</div>
</div>
}
</Spin>
</div>
</div>
)
}
export default FC
export const Basic = ({ header, data, title }) => {
return (
<Typography>
<div className='mb-3'>
<Typography>
<Typography.Title level={5}>{title}</Typography.Title>
</Typography>
</div>
{
header?.map((item, index) => {
let columnValue = undefined
if ((data??[]).length>index) {
columnValue = data[index]
}
let stateClassName = ''
if (columnValue?.state==='ADD' || columnValue?.state==='UPDATE') {
stateClassName = 'add'
} else if (columnValue?.state === 'DELETE') {
stateClassName = 'delete'
}
return (
<Typography.Paragraph>
<Tooltip key={index} title={columnValue.value||''}>
<Typography.Text ellipsis={true}>
{item??''}:&nbsp;<Typography.Text className={stateClassName}>{columnValue.value||''}</Typography.Text>
</Typography.Text>
</Tooltip>
</Typography.Paragraph>
);
})
}
</Typography>
)
}
export const Attribute = ({ header, data, title }) => {
const [columns, tableData] = React.useMemo(() => {
const newTableData = [], newColumns = [{
title: '序号',
dataIndex: 'index',
width: 100,
render: (_, __, index) => `${index+1}`
}]
for (const [index, item] of (header||[]).entries()) {
newColumns.push({
title: item??'',
dataIndex: `column${index}`,
render: (item, record) => {
let stateClassName = '';
if (item?.state==='ADD' || item?.state==='UPDATE') {
stateClassName = 'add';
} else if (item?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Typography.Paragraph>
<Tooltip title={item?.value}>
<Typography.Text className={stateClassName} ellipsis={true}>{item?.value||''}</Typography.Text>
</Tooltip>
</Typography.Paragraph>
);
},
ellipsis: true,
option: true,
})
}
for (const item of data??[]) {
let newAttrItem = {}
for (const [index, _item] of item.entries()) {
newAttrItem[`column${index}`] = _item
}
newTableData.push(newAttrItem)
}
return [newColumns, newTableData]
}, [header, data])
return (
<div>
<div className='my-3'>
<Typography>
<Typography.Title level={5}>{title}</Typography.Title>
</Typography>
</div>
<Table
columns={columns||[]}
dataSource={tableData}
pagination={false}
/>
</div>
)
}
\ No newline at end of file
import React from 'react';
import { Timeline, Spin } from 'antd';
import { dispatch } from '../../../../model'
import Update from './update-rule-catalog'
const FC = ({ item }) => {
const [versions, setVersions] = React.useState()
const [loading, setLoading] = React.useState(false)
const [updateParams, setUpdateParams] = React.useState({
visible: false,
type: undefined,
item: undefined,
});
React.useEffect(() => {
if (item?.id) {
getVersions()
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [item])
const getVersions = () => {
setLoading(true);
dispatch({
type: 'datamodel.getRuleCatalogVersionList',
payload: {
catalogId: item?.id,
},
callback: data => {
setLoading(false)
setVersions(data)
},
error: () => {
setLoading(false);
}
})
}
return (
<React.Fragment>
<Spin spinning={loading}>
<Timeline style={{ padding: 24 }}>
{
(versions??[]).map((version, index) => {
return <Timeline.Item key={index} >
<div>
<a
onClick={() => {
setUpdateParams({
visible: true,
type: 'detail',
item: version
})
}}
>
{version.name}
</a>
<div className='mt-2'>
{`${version.ownerName} ${new Date(version.createdTs).toLocaleString()}`}
</div>
</div>
</Timeline.Item>
})
}
</Timeline>
</Spin>
<Update
{...updateParams}
onCancel={() => {
setUpdateParams({
visible: false,
type: undefined,
item: undefined,
})
}}
/>
</React.Fragment>
);
}
export default FC
\ No newline at end of file
import React from 'react'
import { Drawer, Tabs } from 'antd'
import History from './rule-catalog-history'
import Compare from './rule-catalog-compare'
const FC = ({ visible, item, onCancel }) => {
const close = () => {
onCancel?.()
}
return (
<Drawer
title=''
placement="right"
closable={true}
width={'90%'}
onClose={close}
visible={visible}
destroyOnClose
>
<Tabs defaultActiveKey="1" type="card" size='small'>
<Tabs.TabPane tab="版本历史" key="1">
<History item={item} />
</Tabs.TabPane>
<Tabs.TabPane tab="版本对比" key="2">
<Compare item={item} />
</Tabs.TabPane>
</Tabs>
</Drawer>
);
}
export default FC;
\ No newline at end of file
import React from "react"
import { Typography, Tooltip, Divider, Space, Input, Select, Modal, Row, Col } from "antd"
import { dispatch } from '../../../../model'
import Table from '../../../../util/Component/Table'
import AddRule from './add-rule'
import UpdateRule from './update-rule'
import UpdateRuleTemplate from './update-rule-template'
import PermissionButton from '../../../../util/Component/PermissionButton'
import { showMessage } from "../../../../util"
import { render } from "@testing-library/react"
const nodeItems = [
{ key: 'name', title: '名称' },
{ key: 'statusName', title: '状态' },
{ key: 'ownerName', title: '维护人' },
{ key: 'maintenanceContent', title: '维护说明' },
{ key: 'remark', title: '描述' },
{ key: 'modifiedTs', title: '维护时间', render: (text) => text?new Date(text).toLocaleString():''},
{ key: 'modifiedTs', title: '版本', render: (text) => text?`V_${new Date(text).toLocaleString()}`:''},
]
const FC = (props) => {
const { node, readonly } = props
const [args, setArgs] = React.useState(() => ({
statusId: undefined,
alertTypeId: undefined,
keyword: undefined,
}))
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [selectedRows, setSelectedRows] = React.useState()
const [loadingStatus, setLoadingStatus] = React.useState(false)
const [status, setStatus] = React.useState()
const [loadingAlertTypes, setLoadingAlertTypes] = React.useState(false)
const [alertTypes, setAlertTypes] = React.useState()
const [addRuleParams, setAddRuleParams] = React.useState({
visible: false,
node: undefined,
})
const [updateRuleParams, setUpdateRuleParams] = React.useState({
visible: false,
item: undefined,
})
const [updateRuleTemplateParams, setUpdateRuleTemplateParams] = React.useState({
visible: false,
type: undefined,
item: undefined
})
const [modal, contextHolder] = Modal.useModal()
React.useEffect(() => {
getStatus()
getAlertTypes()
}, [])
React.useEffect(() => {
if (node?.id) {
getRules()
}
}, [node])
const cols = React.useMemo(() => {
return ([
{
title: '序号',
dataIndex: 'index',
width: 60,
render: (_, __, index) => `${index+1}`
},
{
title: '规则名称',
dataIndex: 'ruleTemplateName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则描述',
dataIndex: 'ruleTemplateRemark',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则状态',
dataIndex: 'statusName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则类型',
dataIndex: 'alertTypeName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则提示',
dataIndex: 'alertContent',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '操作',
key: 'action',
width: readonly ? 80 : 200,
render: (text, record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<PermissionButton
type='link'
size='small'
onClick={() => {
setUpdateRuleTemplateParams({
visible: true,
type: 'detail',
item: {
id: record?.ruleTemplateId
},
})
}}
style={{ padding: 0 }}
defaultPermission={true}
>
规则详情
</PermissionButton>
{
!readonly && <React.Fragment>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<PermissionButton
type='link'
size='small'
onClick={() => {
setUpdateRuleParams({
visible: true,
item: record,
})
}}
style={{ padding: 0 }}
// permissionKey='编辑'
// permissions={permissions}
defaultPermission={true}
>
编辑
</PermissionButton>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<PermissionButton
type='link'
size='small'
onClick={() => { onDeteteClick(record); }}
style={{ padding: 0 }}
// permissionKey='删除'
// permissions={permissions}
defaultPermission={true}
>
删除
</PermissionButton>
</React.Fragment>
}
</div>
)
}
}
])
}, [node])
const tableData = React.useMemo(() => {
return data?.filter(item =>
(
!args.keyword
|| (item.ruleTemplateName??'').indexOf(args.keyword) !== -1
|| (item.ruleTemplateRemark??'').indexOf(args.keyword) !== -1
)
&& (
!args.statusId
|| (item.statusId??'').indexOf(args.statusId) !== -1
) && (
!args.alertTypeId
|| (item.alertTypeId??'').indexOf(args.alertTypeId) !== -1
)
)
}, [data, args])
const getRules = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleList',
payload: {
catalogId: node?.id,
},
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
const getStatus = () => {
setLoadingStatus(true)
dispatch({
type: 'datamodel.getRuleStatus',
callback: data => {
setLoadingStatus(false)
setStatus(data)
},
error: () => {
setLoadingStatus(false)
}
})
}
const getAlertTypes = () => {
setLoadingAlertTypes(true)
dispatch({
type: 'datamodel.getRuleAlertTypes',
callback: data => {
setLoadingAlertTypes(false)
setAlertTypes(data)
},
error: () => {
setLoadingAlertTypes(false)
}
})
}
const onAddClick = () => {
if (!node) {
showMessage('warn', '请先选择目录')
return
}
setAddRuleParams({
visible: true,
node
})
}
const onExportClick = () => {
if (!node) {
showMessage('warn', '请先选择目录')
return
}
modal.confirm({
title: '提示',
content: '确定导出该规范吗?',
onOk: () => {
window.open(`/api/datamodeler/easyDataModelerRule/exportRules?catalogId=${node?.id}`)
}
})
}
const onBatchDeteteClick = () => {
modal.confirm({
title: '提示',
content: '确定将选中的规则从规范中移除吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteRules',
payload: {
ids: (selectedRows??[]).map(item => item.id).toString()
},
callback: data => {
showMessage('success', '删除成功')
setSelectedRows()
getRules()
}
})
}
})
}
const onDeteteClick = (record) => {
modal.confirm({
title: '提示',
content: '确定将选中的规则从规范中移除吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteRules',
payload: {
ids: record?.id
},
callback: data => {
showMessage('success', '删除成功')
setSelectedRows()
getRules()
}
})
}
})
}
return (
<div className='pl-4'>
<div className='py-3' style={{ borderBottom: '1px solid #EFEFEF' }}>
<Row gutter={[8, 8]}>
{
(nodeItems??[]).map(item => {
let val = node?.[item.key]
if (item.render) {
val = item.render(val)
}
return (
<Col key={item.title} span={6}>
<Tooltip title={val}>
<Typography.Text ellipsis={true}>{`${item.title}: ${val??''}`}</Typography.Text>
</Tooltip>
</Col>
)
})
}
</Row>
</div>
<div className='d-flex py-3' style={{ justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #EFEFEF' }}>
<Space>
{
!readonly && <PermissionButton
onClick={onAddClick}
// permissionKey='新增'
defaultPermission={true}
>
新建
</PermissionButton>
}
{
!readonly && <PermissionButton
onClick={onBatchDeteteClick}
// permissionKey='删除'
defaultPermission={true}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择规则':''}
>
删除
</PermissionButton>
}
<PermissionButton
onClick={onExportClick}
defaultPermission={true}
>
导出
</PermissionButton>
</Space>
<Space>
<Select value={args.statusId} allowClear
loading={loadingStatus}
placeholder='请选择规则状态'
onChange={(val) => {
setArgs({ ...args, statusId: val })
}}
style={{ width: 150 }}
>
{ (status??[]).map(item => (
<Select.Option key={item.id} key={item.id}>{item.name}</Select.Option>
)) }
</Select>
<Select value={args.alertTypeId} allowClear
loading={loadingAlertTypes}
placeholder='请选择规则类型'
onChange={(val) => {
setArgs({ ...args, alertTypeId: val })
}}
style={{ width: 150 }}
>
{ (alertTypes??[]).map(item => (
<Select.Option key={item.id} key={item.id}>{item.name}</Select.Option>
)) }
</Select>
<Input size="middle"
placeholder="规则名称/描述搜索"
value={args.keyword}
bordered={true} allowClear
onChange={(e) => {
setArgs({ ...args, keyword: e.target.value })
}}
style={{ width: 270 }}
/>
</Space>
</div>
<div className='pt-3'>
<Table
extraColWidth={32}
loading={loading}
columns={cols??[]}
dataSource={tableData??[]}
pagination={false}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
</div>
<AddRule
{...addRuleParams}
onCancel={(refresh) => {
setAddRuleParams({
visible: false,
node: undefined,
})
refresh && getRules()
}}
/>
<UpdateRule
{...updateRuleParams}
onCancel={(refresh) => {
setUpdateRuleParams({
visible: false,
item: undefined,
})
refresh && getRules()
}}
/>
<UpdateRuleTemplate
{...updateRuleTemplateParams}
onCancel={(refresh) => {
setUpdateRuleTemplateParams({
visible: false,
type: undefined,
item: undefined,
})
refresh && getRules()
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from "react"
import { Modal } from "antd"
import RuleCURD from './rule'
const FC = (props) => {
const { visible, onCancel } = props
const close = () => {
onCancel?.()
}
return (
<Modal
visible={visible}
footer={null}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='规范详情'
centered destroyOnClose
onCancel={() => { close() }}
>
<RuleCURD readonly={true} />
</Modal>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Input, Space, Modal, Divider, Tooltip, Typography } from 'antd'
import { dispatch } from '../../../../model'
import PermissionButton from '../../../../util/Component/PermissionButton'
import Table from '../../../../util/Component/Table'
import { showMessage } from '../../../../util'
import Update from './update-rule-template'
const FC = (props) => {
const [keyword, setKeyword] = React.useState()
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [selectedRows, setSelectedRows] = React.useState()
const [updateParams, setUpdateParams] = React.useState({
visible: false,
type: undefined,
item: undefined
})
const [modal, contextHolder] = Modal.useModal()
React.useEffect(() => {
getTemplates()
}, [])
const cols = React.useMemo(() => {
return ([
{
title: '序号',
dataIndex: 'index',
width: 60,
render: (_, __, index) => `${index+1}`
},
{
title: '规则编号',
dataIndex: 'number',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则中文名称',
dataIndex: 'name',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={() => {
setUpdateParams({
visible: true,
type: 'detail',
item: record
})
}}>
{ text }
</a>
</Typography.Text>
</Tooltip>
)
},
{
title: '规则描述',
dataIndex: 'remark',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '检查类型',
dataIndex: 'checkTypeName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '引用次数',
dataIndex: 'referenceCount',
},
{
title: '操作',
key: 'action',
width: 120,
render: (text, record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<PermissionButton
type='link'
size='small'
onClick={() => {
setUpdateParams({
visible: true,
type: 'edit',
item: record,
})
}}
style={{ padding: 0 }}
// permissionKey='编辑'
// permissions={permissions}
defaultPermission={true}
>
编辑
</PermissionButton>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<PermissionButton
type='link'
size='small'
onClick={() => { onDeteteClick(record); }}
style={{ padding: 0 }}
// permissionKey='删除'
// permissions={permissions}
defaultPermission={true}
>
删除
</PermissionButton>
</div>
)
}
}
])
}, [])
const _data = React.useMemo(() => {
const _keyword = (keyword??'').trim()
return (data??[]).filter(item => !_keyword
|| (item.name??'').indexOf(_keyword) !== -1
|| (item.remark??'').indexOf(_keyword) !== -1
)
}, [data, keyword])
const getTemplates = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleTemplateList',
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
const onAddClick = () => {
setUpdateParams({
visible: true,
type: 'add',
item: undefined
})
}
const onBatchDeteteClick = () => {
modal.confirm({
title: '提示',
content: '删除规则,引用的规范将同步删除该规则,确定删除吗?',
onOk: () => {
dispatch({
type: 'datamodel.deletesRuleTemplate',
payload: {
templateIds: (selectedRows??[]).map(item => item.id).toString()
},
callback: data => {
showMessage('success', '删除成功')
setSelectedRows()
getTemplates()
}
})
}
})
}
const onDeteteClick = (record) => {
modal.confirm({
title: '提示',
content: '删除规则,引用的规范将同步删除该规则,确定删除吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteRuleTemplate',
payload: {
templateId: record?.id
},
callback: data => {
showMessage('success', '删除成功')
getTemplates()
}
})
}
})
}
return (
<div>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<PermissionButton
onClick={onAddClick}
// permissionKey='新增'
defaultPermission={true}
>
新建
</PermissionButton>
<PermissionButton
onClick={onBatchDeteteClick}
// permissionKey='删除'
defaultPermission={true}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择规则':''}
>
删除
</PermissionButton>
</Space>
<Input size="middle"
placeholder="规则名称/描述搜索"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
}}
style={{ width: 270 }}
/>
</div>
<Table
extraColWidth={32}
loading={loading}
columns={cols??[]}
dataSource={_data}
pagination={false}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
<Update
{...updateParams}
onCancel={(refresh) => {
setUpdateParams({
visible: false,
type: undefined,
item: undefined
})
refresh && getTemplates()
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import produce from 'immer'
import { Modal, Tooltip, Button, Spin, Space } from 'antd'
import { PlusOutlined, ReloadOutlined } from '@ant-design/icons'
import { dispatch } from '../../../../model'
import Tree from '../../../../util/Component/Tree'
import PermissionButton from '../../../../util/Component/PermissionButton'
import Update from './update-rule-catalog'
import Version from './rule-catalog-version'
import { showMessage } from '../../../../util'
const FC = (props) => {
const { onClick, readonly } = props
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [selectedNode, setSelectedNode] = React.useState()
const [updateParams, setUpdateParams] = React.useState({
visible: false,
type: undefined,
item: undefined,
})
const [versionParams, setVersionParams] = React.useState({
visible: false,
item: undefined
})
const [modal, contextHolder] = Modal.useModal()
React.useEffect(() => {
getTreeData()
}, [])
const treeData = React.useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.id
g.title = g.name
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const getTreeData = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleCatalogList',
callback: data => {
setLoading(false);
setData(data)
if ((data??[]).length > 0 && !selectedNode) {
onTreeSelect([data[0].id], { selectedNodes: [data[0]]})
}
},
error: () => {
setLoading(false);
}
})
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setSelectedNode(selectedNodes[0])
onClick?.(selectedNodes[0])
}
const onAddClick = () => {
setUpdateParams({
visible: true,
type: 'add',
item: undefined
})
}
const onRefreshClick = () => {
getTreeData()
}
const onMenuItemClick = (key, node) => {
if (key === 'delete') {
modal.confirm({
title: '提示!',
content: '您确定删除该目录吗?',
onOk: () => {
setLoading(true);
dispatch({
type: 'datamodel.deleteRuleCatalog',
payload: {
id: node?.id
},
callback: data => {
showMessage('success', '删除目录成功')
if (selectedNode?.id === node?.id) {
setSelectedNode()
}
getTreeData()
},
error: () => {
setLoading(false)
}
})
}
})
} else if (key === 'edit') {
setUpdateParams({
visible: true,
type: 'edit',
item: node
})
} else if (key === 'up') {
modal.confirm({
title: '提示!',
content: '您确定上移该目录吗?',
onOk: () => {
dispatch({
type: 'datamodel.upRuleCatalog',
payload: {
params: {
id: node?.id
}
},
callback: data => {
getTreeData()
}
})
}
})
} else if (key === 'down') {
modal.confirm({
title: '提示!',
content: '您确定下移该目录吗?',
onOk: () => {
dispatch({
type: 'datamodel.downRuleCatalog',
payload: {
params: {
id: node?.id
}
},
callback: data => {
getTreeData()
}
})
}
})
} else if (key === 'version') {
setVersionParams({
visible: true,
item: node,
})
}
}
return (
<div>
{
!readonly && <div className='px-3' style={{
display: 'flex',
width: '100%',
height: 40,
alignItems: 'center',
borderBottom: '1px solid #EFEFEF',
}}>
<Space>
<PermissionButton
defaultPermission={true}
tip="新增目录"
type="text"
icon={<PlusOutlined />}
onClick={onAddClick}
/>
<Tooltip title="刷新目录">
<Button type="text" icon={<ReloadOutlined />} onClick={onRefreshClick} />
</Tooltip>
</Space>
</div>
}
<div className='p-3'>
<Spin spinning={loading}>
<Tree
className='tree'
showLine
showIcon={false}
treeData={treeData}
onSelect={onTreeSelect}
selectedKeys={(selectedNode?.id)?[selectedNode?.id]:undefined}
shouldRowContextMenu={() => !readonly}
menuData={[
{ id: 'edit', title: '编辑目录' },
{ id: 'up', title: '上移目录' },
{ id: 'down', title: '下移目录' },
{ id: 'delete', title: '删除目录' },
{ id: 'version', title: '历史版本' },
]}
onMenuItemClick={onMenuItemClick}
/>
</Spin>
</div>
<Update
{...updateParams}
onCancel={(refresh) => {
setUpdateParams({
visible: false,
type: undefined,
item: undefined
})
refresh && getTreeData()
}}
/>
<Version
{...versionParams}
onCancel={() => {
setVersionParams({
visible: false,
item: undefined,
})
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import classNames from 'classnames'
import { ResizableBox } from 'react-resizable'
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'
import Tree from './rule-tree'
import List from './rule-list'
import Separate from '../../AssetManage/Component/Separate'
import '../../AssetManage/index.less'
const FC = (props) => {
const { readonly = false } = props
const [node, setNode] = React.useState()
const onTreeClick = (value) => {
setNode(value)
}
return (
<div className='asset-manage'>
<div className='left' style={{ width: 230}}
>
<Tree onClick={onTreeClick} {...props} />
</div>
<div className='middle'>
<List node={node} {...props} />
</div>
</div>
)
}
export default FC
\ No newline at end of file
import React from "react"
import { Button, Spin, Modal, Form, Input, Select } from "antd"
import { dispatch } from '../../../../model'
const FC = (props) => {
const { visible, type, item, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const title = React.useMemo(() => {
if (type === 'add') return '新增规范'
if (type === 'edit') return '修改规范'
if (type === 'detail') return '规范详情'
return ''
}, [type])
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
if (type === 'add') {
dispatch({
type: 'datamodel.addRuleCatalog',
payload: {
data: rows
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
} else {
dispatch({
type: 'datamodel.updateRuleCatalog',
payload: {
data: {...item, ...rows}
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
}
} catch (e) {
}
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={type!=='detail'?footer:null}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title={title}
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} type={type} item={item} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ type, item }, ref) {
const [loadingStatus, setLoadingStatus] = React.useState(false)
const [status, setStatus] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form?.validateFields()
},
}), [form])
React.useEffect(() => {
getStatus()
}, [])
React.useEffect(() => {
if (item) {
form?.setFieldsValue(item)
}
}, [item])
const marginBottom = React.useMemo(() => {
return type === 'detail' ? 5 : 15
}, [type])
const getStatus = () => {
setLoadingStatus(true)
dispatch({
type: 'datamodel.getRuleCatalogStatus',
callback: (data) => {
setLoadingStatus(false)
setStatus(data)
},
error: () => {
setLoadingStatus(false)
}
})
}
const onValuesChange = (changedValues, allValues) => {
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item name='name' label='规范名称'
style={{ marginBottom }}
rules={[{ required: true, message: '请输入规范名称!' }]}
>
{
type === 'detail' ? <span>{item?.name}</span> : <Input placeholder='请输入规范名称' allowClear />
}
</Form.Item>
<Form.Item name='remark' label='描述'
style={{ marginBottom }}
>
{
type === 'detail' ? <span>{item?.name}</span> : <Input placeholder='请输入描述' allowClear />
}
</Form.Item>
<Form.Item name='statusId' label='状态'
style={{ marginBottom }}
>
{
type === 'detail' ? <span>{item?.statusName}</span> : <Select loading={loadingStatus} allowClear placeholder='请选择状态'>
{ (status??[]).map(item => ( <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option> )) }
</Select>
}
</Form.Item>
<Form.Item name='maintenanceContent' label='维护说明'
style={{ marginBottom }}
>
{
type === 'detail' ? <span>{item?.name}</span> : <Input placeholder='请输入维护说明' allowClear />
}
</Form.Item>
</Form>
)
})
\ No newline at end of file
import React from "react"
import { Modal, Button, Spin, Form, Input, Select, Space, InputNumber, Row, Col } from "antd"
import { dispatch } from '../../../../model'
import { IsArr } from "../../../../util"
const FC = (props) => {
const { visible, type, item, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [data, setData] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (item?.id) {
getDetail()
}
}, [item])
const title = React.useMemo(() => {
if (type === 'add') return '新增检查规则'
if (type === 'edit') return '修改检查规则'
if (type === 'detail') return '检查规则详情'
return ''
}, [type])
const getDetail = () => {
setLoading(true)
dispatch({
type: 'datamodel.getRuleTemplateDetail',
payload: {
ruleTemplateId: item?.id
},
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setData()
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
if (type === 'add') {
dispatch({
type: 'datamodel.addRuleTemplate',
payload: {
data: rows
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
} else {
dispatch({
type: 'datamodel.updateRuleTemplate',
payload: {
data: {...item, preCheckProperty: null, ...rows}
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
}
} catch (e) {
}
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={type!=='detail'?footer:null}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title={title}
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting||loading}>
<Basic ref={basicRef} type={type} item={data} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ type, item }, ref) {
const [loadingCheckTypes, setLoadingCheckTypes] = React.useState(false)
const [checkTypes, setCheckTypes] = React.useState()
const [checkTypeValue, setCheckTypeValue] = React.useState()
const [preCatalog, setPreCatalog] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form?.validateFields()
},
}), [form])
React.useEffect(() => {
getCheckTypes()
}, [])
React.useEffect(() => {
if (item) {
form?.setFieldsValue(item)
setCheckTypeValue(item.checkType)
setPreCatalog(item.preCheckProperty?.propertyCatalog)
}
}, [item])
const [checkPropertyDescription, preCheckPropertyDescription] = React.useMemo(() => {
if (item) {
let newCheckPropertyDescription = `${item?.checkProperty?.propertyCnName??''} ${item?.checkProperty?.expressionTypeCnName??''} ${item?.checkProperty?.verifyExpression?.cnName??''}`
if (IsArr(item?.checkProperty?.verifyExpression?.value)) {
newCheckPropertyDescription = `${newCheckPropertyDescription} ${item?.checkProperty?.verifyExpression?.value.join(';')}`
} else {
newCheckPropertyDescription = `${newCheckPropertyDescription} ${item?.checkProperty?.verifyExpression?.value}`
}
let newPreCheckPropertyDescription = `${item?.preCheckProperty?.propertyCnName??''} ${item?.preCheckProperty?.expressionTypeCnName??''} ${item?.preCheckProperty?.verifyExpression?.cnName??''}`
if (IsArr(item?.preCheckProperty?.verifyExpression?.value)) {
newPreCheckPropertyDescription = `${newPreCheckPropertyDescription} ${item?.preCheckProperty?.verifyExpression?.value.join(';')}`
} else {
newPreCheckPropertyDescription = `${newPreCheckPropertyDescription} ${item?.preCheckProperty?.verifyExpression?.value}`
}
return [newCheckPropertyDescription, newPreCheckPropertyDescription]
}
return ['', '']
}, [item])
const marginBottom = React.useMemo(() => {
return type === 'detail' ? 5 : 15
}, [type])
const getCheckTypes = () => {
setLoadingCheckTypes(true)
dispatch({
type: 'datamodel.getRuleTemplateCheckTypes',
callback: (data) => {
setLoadingCheckTypes(false)
setCheckTypes(data)
},
error: () => {
setLoadingCheckTypes(false)
}
})
}
const onValuesChange = (changedValues, allValues) => {
if (changedValues.hasOwnProperty('checkType')) {
setCheckTypeValue(changedValues.checkType)
} else if (changedValues.hasOwnProperty('preCheckProperty')) {
setPreCatalog(changedValues?.preCheckProperty?.propertyCatalog)
}
}
const validatorCheckProperty = (_, value) => {
if (!value?.propertyEnName) {
return Promise.reject(new Error('请选择检查对象!'));
}
if (!value?.expressionTypeEnName) {
return Promise.reject(new Error('请选择检查属性!'));
}
if (!value?.verifyExpression?.enName) {
return Promise.reject(new Error('请选择表达式!'));
}
if ((value?.verifyExpression?.valueType==='string' || value?.verifyExpression?.valueType === 'int') && !value?.verifyExpression?.value) {
return Promise.reject(new Error('请输入表达式的值!'));
}
if (value?.verifyExpression?.valueType === 'List<String>' && (value?.verifyExpression?.value??[]).length === 0) {
return Promise.reject(new Error('请输入表达式的值!'));
}
return Promise.resolve();
};
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
{
type !== 'add' && <Form.Item label='规则编号' style={{ marginBottom }}>
<span>{item?.number}</span>
</Form.Item>
}
<Form.Item name='name' label='规则中文名称'
style={{ marginBottom }}
rules={[{ required: true, message: '请输入规则中文名称!' }]}
>
{
type === 'detail' ? <span>{item?.name}</span> : <Input placeholder='请输入规则中文名称' allowClear />
}
</Form.Item>
<Form.Item name='remark' label='规则描述'
style={{ marginBottom }}
>
{
type === 'detail' ? <span>{item?.remark}</span> : <Input placeholder='请输入规则描述' allowClear />
}
</Form.Item>
<Form.Item name='alertContent' label='规则提示'
style={{ marginBottom }}
rules={[{ required: true, message: '请输入规则提示!' }]}
>
{
type === 'detail' ? <span>{item?.alertContent}</span> : <Input placeholder='请输入规则提示' allowClear />
}
</Form.Item>
<Form.Item name='checkType' label='检查类型'
style={{ marginBottom }}
rules={[{ required: true, message: '请选择检查类型!' }]}
>
{
type === 'detail' ? <span>{item?.checkTypeName}</span> : <Select allowClear loading={loadingCheckTypes}
placeholder='请选择检查类型'
>
{ (checkTypes??[]).map((item, index) => (
<Select.Option key={item.type} value={item.type}>{item.name}</Select.Option>
)) }
</Select>
}
</Form.Item>
{
checkTypeValue === 'single' && <Form.Item name='checkProperty' label='检查规则'
style={{ marginBottom }}
rules={[
{
required: true,
validator: validatorCheckProperty,
},
]}
>
{
type === 'detail' ? <span>{checkPropertyDescription}</span> : <CheckItem />
}
</Form.Item>
}
{
checkTypeValue === 'preCheck' && <Form.Item label='检查规则'>
<Form.Item name='preCheckProperty' label='满足条件'
style={{ marginBottom }}
rules={[
{
required: true,
validator: validatorCheckProperty,
},
]}
>
{
type === 'detail' ? <span>{preCheckPropertyDescription}</span> : <CheckItem />
}
</Form.Item>
<Form.Item name='checkProperty' label='检查内容'
style={{ marginBottom }}
rules={[
{
required: true,
validator: validatorCheckProperty,
},
]}
>
{
type === 'detail' ? <span>{checkPropertyDescription}</span> : <CheckItem preCatalog={preCatalog} />
}
</Form.Item>
</Form.Item>
}
</Form>
)
})
const CheckItem = ({ value, onChange, preCatalog }) => {
const [loadingCheckPropertyTypes, setLoadingCheckPrpertyTypes] = React.useState(false)
const [checkPropertyTypes, setCheckPropertyTypes] = React.useState()
const [loadingExpressionTypes, setLoadingExpressionTypes] = React.useState(false)
const [expressionTypes, setExpressionTypes] = React.useState()
const [loadingExpressionMapping, setLoadingExpressionMapping] = React.useState(false)
const [expressionMapping, setExpressionMapping] = React.useState()
const [currentCheckPropertyType, setCurrentCheckPropertyType] = React.useState()
const [currentExpressionType, setCurrentExpressionType] = React.useState()
const [currentExpression, setCurrentExpression] = React.useState()
const [currentExpressionValue, setCurrentExpressionValue] = React.useState()
const mountRef = React.useRef(true)
React.useEffect(() => {
getCheckPropertyTypes()
getExpressionTypes()
getExpressions()
}, [])
React.useEffect(() => {
if (mountRef.current && value) {
setCurrentCheckPropertyType({
propertyCatalog: value?.propertyCatalog,
propertyCnName: value?.propertyCnName,
propertyEnName: value?.propertyEnName,
})
setCurrentExpressionType({
expressionTypeCnName: value?.expressionTypeCnName,
expressionTypeEnName: value?.expressionTypeEnName,
})
setCurrentExpression(value.verifyExpression)
if (IsArr(value.verifyExpression?.value)) {
setCurrentExpressionValue(value.verifyExpression?.value.join(';'))
} else {
setCurrentExpressionValue(value.verifyExpression?.value)
}
}
mountRef.current = false
}, [])
React.useEffect(() => {
onChange?.({...currentCheckPropertyType||{}, ...currentExpressionType||{}, verifyExpression: currentExpression})
}, [currentCheckPropertyType, currentExpressionType, currentExpression])
const visibleExpressions = React.useMemo(() => {
if (currentExpressionType?.expressionTypeEnName) {
return expressionMapping?.[currentExpressionType?.expressionTypeEnName]
}
return []
}, [expressionMapping, currentExpressionType])
const _checkPropertyTypes = React.useMemo(() => {
if (preCatalog) {
return (checkPropertyTypes??[]).filter(item => item.propertyCatalog === preCatalog)
}
return checkPropertyTypes
}, [checkPropertyTypes, preCatalog])
React.useEffect(() => {
if (preCatalog) {
if (currentCheckPropertyType?.propertyCatalog && currentCheckPropertyType?.propertyCatalog !== preCatalog) {
setCurrentCheckPropertyType()
}
}
}, [preCatalog, currentCheckPropertyType])
const getCheckPropertyTypes = () => {
setLoadingCheckPrpertyTypes(true)
dispatch({
type: 'datamodel.getRuleTemplateAllCheckPropertyTypes',
callback: (data) => {
setLoadingCheckPrpertyTypes(false)
setCheckPropertyTypes(data)
},
error: () => {
setLoadingCheckPrpertyTypes(false)
}
})
}
const getExpressionTypes = () => {
setLoadingExpressionTypes(true)
dispatch({
type: 'datamodel.getRuleTemplateAllVerifyExpressionTypes',
callback: (data) => {
setLoadingExpressionTypes(false)
setExpressionTypes(data)
},
error: () => {
setLoadingExpressionTypes(false)
}
})
}
const getExpressions = () => {
setLoadingExpressionMapping(true)
dispatch({
type: 'datamodel.getRuleTemplateAllVertifyExpressions',
callback: (data) => {
setLoadingExpressionMapping(false)
setExpressionMapping(data)
},
error: () => {
setLoadingExpressionMapping(false)
}
})
}
return (
<Row gutter={10}>
<Col span={6}>
<Select allowClear loading={loadingCheckPropertyTypes}
value={currentCheckPropertyType?.propertyEnName}
onChange={(val) => {
if (val) {
const index = (checkPropertyTypes??[]).findIndex(item => item.propertyEnName === val)
if (index !== -1) {
setCurrentCheckPropertyType(checkPropertyTypes[index])
}
} else {
setCurrentCheckPropertyType()
}
}}
placeholder='请选择检查对象'
>
{ (_checkPropertyTypes??[]).map((item, index) => (
<Select.Option key={item.propertyEnName} value={item.propertyEnName}>{item.propertyCnName}</Select.Option>
)) }
</Select>
</Col>
<Col span={6}>
<Select allowClear loading={loadingExpressionTypes}
value={currentExpressionType?.expressionTypeEnName}
onChange={(val) => {
if (val) {
const index = (expressionTypes??[]).findIndex(item => item.expressionTypeEnName === val)
if (index !== -1) {
setCurrentExpressionType(expressionTypes[index])
}
} else {
setCurrentExpressionType()
}
setCurrentExpression()
setCurrentExpressionValue()
}}
placeholder='请选择检查属性'
>
{ (expressionTypes??[]).map((item, index) => (
<Select.Option key={item.expressionTypeEnName} value={item.expressionTypeEnName}>{item.expressionTypeCnName}</Select.Option>
)) }
</Select>
</Col>
<Col span={6}>
<Select allowClear loading={loadingExpressionMapping}
value={currentExpression?.enName}
onChange={(val) => {
if (val) {
const index = (visibleExpressions??[]).findIndex(item => item.enName === val)
if (index !== -1) {
setCurrentExpression(visibleExpressions[index])
}
} else {
setCurrentExpression()
}
setCurrentExpressionValue()
}}
placeholder='请选择表达式'
>
{ (visibleExpressions??[]).map((item, index) => (
<Select.Option key={item.enName} value={item.enName}>{item.cnName}</Select.Option>
)) }
</Select>
</Col>
<Col span={6}>
{ (currentExpression?.valueType === 'string') && <Input value={currentExpressionValue}
onChange={(e) => {
setCurrentExpressionValue(e.target.value)
setCurrentExpression({
...currentExpression, value: e.target.value
})
}}/> }
{ (currentExpression?.valueType === 'int') && <InputNumber min={1} value={currentExpressionValue}
onChange={(val) => {
setCurrentExpressionValue(val)
setCurrentExpression({
...currentExpression, value: val
})
}}/> }
{ (currentExpression?.valueType === 'List<String>') && <Input placeholder='枚举值用;隔开 如高;中;低' value={currentExpressionValue} onChange={(e) => {
setCurrentExpressionValue(e.target.value)
setCurrentExpression({
...currentExpression, value: e.target.value?e.target.value.split(';'):[]
})
}} /> }
</Col>
</Row>
)
}
\ No newline at end of file
import React from 'react'
import { Button, Modal, Spin, Form, Input, Select } from 'antd'
import { dispatch } from '../../../../model'
const FC = (props) => {
const { visible, item, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
dispatch({
type: 'datamodel.updateRule',
payload: {
params: {id: item?.id, ...rows},
},
callback: data => {
close(true)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={footer}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='编辑检查规则'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} item={item} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ type, item }, ref) {
const [loadingStatus, setLoadingStatus] = React.useState(false)
const [status, setStatus] = React.useState()
const [loadingAlertTypes, setLoadingAlertTypes] = React.useState(false)
const [alertTypes, setAlertTypes] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form?.validateFields()
},
}), [form])
React.useEffect(() => {
getStatus()
getAlertTypes()
}, [])
React.useEffect(() => {
if (item) {
form?.setFieldsValue(item)
}
}, [item])
const getStatus = () => {
setLoadingStatus(true)
dispatch({
type: 'datamodel.getRuleStatus',
callback: data => {
setLoadingStatus(false)
setStatus(data)
},
error: () => {
setLoadingStatus(false)
}
})
}
const getAlertTypes = () => {
setLoadingAlertTypes(true)
dispatch({
type: 'datamodel.getRuleAlertTypes',
callback: data => {
setLoadingAlertTypes(false)
setAlertTypes(data)
},
error: () => {
setLoadingAlertTypes(false)
}
})
}
const onValuesChange = (changedValues, allValues) => {
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item label='规则名称'
>
<span>{item?.ruleTemplateName}</span>
</Form.Item>
<Form.Item label='规则描述'
>
<span>{item?.ruleTemplateRemark}</span>
</Form.Item>
<Form.Item name='statusId' label='规则状态'
rules={[{ required: true, message: '请选择规则状态!' }]}
>
<Select loading={loadingStatus} allowClear placeholder='请选择状态'>
{ (status??[]).map(item => ( <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option> )) }
</Select>
</Form.Item>
<Form.Item name='alertTypeId' label='规则类型'
rules={[{ required: true, message: '请选择规则类型!' }]}
>
<Select loading={loadingAlertTypes} allowClear placeholder='请选择类型'>
{ (alertTypes??[]).map(item => ( <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option> )) }
</Select>
</Form.Item>
<Form.Item name='alertContent' label='规则提示'
rules={[{ required: true, message: '请输入规则提示!' }]}
>
<Input allowClear placeholder='请输入规则提示' />
</Form.Item>
</Form>
)
})
\ No newline at end of file
......@@ -3,7 +3,8 @@ import { Tabs } from 'antd';
import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD';
import ConstraintDetail from './Component/ConstraintDetail';
import RuleCURD from './Component/rule';
import RuleTemplateCURD from './Component/rule-template';
import PartitionCURD from './Component/PartitionCURD';
import './index.less';
......@@ -27,9 +28,12 @@ const ModelConfig = () => {
<TemplateCURD />
</TabPane>
<TabPane tab='规范配置' key='3'>
<ConstraintDetail />
<RuleCURD />
</TabPane>
<TabPane tab='分区配置' key='4'>
<TabPane tab='规则库管理' key='4'>
<RuleTemplateCURD />
</TabPane>
<TabPane tab='分区配置' key='5'>
<PartitionCURD />
</TabPane>
</Tabs>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment