Commit 496db8e0 by zhaochengxiang

模型移植

parent dc066ee1
...@@ -24,11 +24,13 @@ ...@@ -24,11 +24,13 @@
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"echarts": "^5.3.1", "echarts": "^5.3.1",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"immer": "9.0.15",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"insert-css": "^2.0.0", "insert-css": "^2.0.0",
"less": "^4.1.1", "less": "^4.1.1",
"less-loader": "^8.0.0", "less-loader": "^8.0.0",
"local-storage": "^2.0.0", "local-storage": "^2.0.0",
"qiankun": "2.4.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-contexify": "^5.0.0", "react-contexify": "^5.0.0",
"react-dnd": "^14.0.2", "react-dnd": "^14.0.2",
...@@ -44,7 +46,6 @@ ...@@ -44,7 +46,6 @@
"rxjs": "^7.5.5", "rxjs": "^7.5.5",
"showdown": "^1.9.1", "showdown": "^1.9.1",
"smooth-scroll": "^16.1.3", "smooth-scroll": "^16.1.3",
"qiankun": "2.4.0",
"typescript": "^4.6.2", "typescript": "^4.6.2",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
}, },
...@@ -75,6 +76,6 @@ ...@@ -75,6 +76,6 @@
"last 1 safari version" "last 1 safari version"
] ]
}, },
"proxy": "http://139.198.127.28:17280", "proxy": "http://192.168.0.111:8189",
"homepage": "http://myhost/data-govern" "homepage": "http://myhost/data-govern"
} }
...@@ -365,4 +365,10 @@ svg { ...@@ -365,4 +365,10 @@ svg {
.yy-notification-notice-description { .yy-notification-notice-description {
max-height: 70vh; max-height: 70vh;
overflow: auto; overflow: auto;
}
.word-wrap {
overflow: hidden;
word-wrap: break-word;
white-space:normal;
} }
\ No newline at end of file
...@@ -308,4 +308,138 @@ export function* requirementBind(payload) { ...@@ -308,4 +308,138 @@ export function* requirementBind(payload) {
export function* requirementUnbind(payload) { export function* requirementUnbind(payload) {
return yield call(datamodelerService.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() { export function loadDataModelCatalog() {
return GetJSON("/datamodeler/easyDataModelerCURD/loadDataModelCatalog"); return GetJSON("/datamodeler/easyDataModelerCURD/loadDataModelCatalog");
...@@ -263,4 +263,139 @@ export function requirementBind(payload) { ...@@ -263,4 +263,139 @@ export function requirementBind(payload) {
export function requirementUnbind(payload) { export function requirementUnbind(payload) {
return PostJSON("/datarequirement/dataReqTechJobAssoc/unbinding", 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'; ...@@ -3,6 +3,7 @@ import { message, notification, Modal, Space, Button } from 'antd';
import { Redirect } from 'react-router-dom'; import { Redirect } from 'react-router-dom';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import LocalStorage from 'local-storage';
import { dispatchLatest, action } from '../model'; import { dispatchLatest, action } from '../model';
import { set_sess_state } from "../model/reducer"; import { set_sess_state } from "../model/reducer";
...@@ -396,4 +397,120 @@ export function getDataModelerRole(user) { ...@@ -396,4 +397,120 @@ export function getDataModelerRole(user) {
// } // }
return ''; 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-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
.model-import-action-header { .model-import-action-header-readolny {
.yy-form-item:nth-last-col { .yy-form-item-label > label {
margin-bottom: 24px; height: auto;
} }
.yy-form-item-has-error { .yy-form-item-control-input {
margin-bottom: 0px; min-height: 0;
}
.yy-descriptions-row > th, .yy-descriptions-row > td {
padding-bottom: 15px;
} }
} }
\ No newline at end of file
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
...@@ -11,21 +11,16 @@ ...@@ -11,21 +11,16 @@
} }
} }
.template-highlight-row {
.yy-table-cell {
background-color: #e7f7ff !important;
}
}
.attention-row { .primary-row {
.yy-table-cell { .yy-table-cell {
background-color: #9dcef3 !important; background-color: #d3ebff !important;
} }
} }
.primary-row { .gray-row {
.yy-table-cell { .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 { ...@@ -689,14 +689,14 @@ class Model extends React.Component {
</Space> </Space>
<Space> <Space>
{ {/* {
!keyword && <Space> !keyword && <Space>
<Radio.Group onChange={this.handleShowModeChange} value={showMode}> <Radio.Group onChange={this.handleShowModeChange} value={showMode}>
<Radio.Button value="list">列表展示</Radio.Button> <Radio.Button value="list">列表展示</Radio.Button>
<Radio.Button value="graph">图形展示</Radio.Button> <Radio.Button value="graph">图形展示</Radio.Button>
</Radio.Group> </Radio.Group>
</Space> </Space>
} } */}
{ {
(currentView==='dir'||keyword!=='') && <Space> (currentView==='dir'||keyword!=='') && <Space>
<Select <Select
......
...@@ -3,7 +3,7 @@ import { Spin, Tabs, Popover, Divider, Button, Space } from 'antd'; ...@@ -3,7 +3,7 @@ import { Spin, Tabs, Popover, Divider, Button, Space } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import TemplateActionHeader from './TemplateActionHeader'; import TemplateActionHeader from './TemplateActionHeader';
import ImportActionTable from '../../Model/Component/ImportActionTable'; import { ImportActionTable } from '../../Model/Component/ImportActionTable';
import { dispatchLatest, dispatch } from '../../../../model'; 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 { 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 { 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'; ...@@ -3,7 +3,8 @@ import { Tabs } from 'antd';
import WordTemplate from './Component/WordTemplate'; import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD'; 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 PartitionCURD from './Component/PartitionCURD';
import './index.less'; import './index.less';
...@@ -27,9 +28,12 @@ const ModelConfig = () => { ...@@ -27,9 +28,12 @@ const ModelConfig = () => {
<TemplateCURD /> <TemplateCURD />
</TabPane> </TabPane>
<TabPane tab='规范配置' key='3'> <TabPane tab='规范配置' key='3'>
<ConstraintDetail /> <RuleCURD />
</TabPane> </TabPane>
<TabPane tab='分区配置' key='4'> <TabPane tab='规则库管理' key='4'>
<RuleTemplateCURD />
</TabPane>
<TabPane tab='分区配置' key='5'>
<PartitionCURD /> <PartitionCURD />
</TabPane> </TabPane>
</Tabs> </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