Commit d07d320e by zhaochengxiang

Merge branch 'modeler-v2' into 'master'

Modeler v2

See merge request !13
parents 75c5a171 9b61897e
...@@ -74,6 +74,12 @@ code { ...@@ -74,6 +74,12 @@ code {
color: white; color: white;
} }
.word-wrap {
overflow: hidden;
word-wrap: break-word;
white-space:normal;
}
div[id^='__qiankun_microapp_wrapper_'] { div[id^='__qiankun_microapp_wrapper_'] {
height: 100%; height: 100%;
} }
......
...@@ -206,6 +206,10 @@ export function* getCheckoutDataModel(payload) { ...@@ -206,6 +206,10 @@ export function* getCheckoutDataModel(payload) {
return yield call(datamodelerService.getCheckoutDataModel, payload); return yield call(datamodelerService.getCheckoutDataModel, payload);
} }
export function* dataModelRollback(payload) {
return yield call(datamodelerService.dataModelRollback, payload);
}
export function* getDataModelLocation(payload) { export function* getDataModelLocation(payload) {
return yield call(datamodelerService.getDataModelLocation, payload); return yield call(datamodelerService.getDataModelLocation, payload);
} }
...@@ -218,6 +222,10 @@ export function* exportDDLString(payload) { ...@@ -218,6 +222,10 @@ export function* exportDDLString(payload) {
return yield call(datamodelerService.exportDDLString, payload); return yield call(datamodelerService.exportDDLString, payload);
} }
export function* ddlChangeString(payload) {
return yield call(datamodelerService.ddlChangeString, payload);
}
export function* exportERWinString(payload) { export function* exportERWinString(payload) {
return yield call(datamodelerService.exportERWinString, payload); return yield call(datamodelerService.exportERWinString, payload);
} }
...@@ -376,4 +384,98 @@ export function* getRuleTemplateAllVerifyExpressionTypes() { ...@@ -376,4 +384,98 @@ export function* getRuleTemplateAllVerifyExpressionTypes() {
export function* getRuleTemplateAllVertifyExpressions() { export function* getRuleTemplateAllVertifyExpressions() {
return yield call(datamodelerService.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
...@@ -197,6 +197,10 @@ export function getCheckoutDataModel(payload) { ...@@ -197,6 +197,10 @@ export function getCheckoutDataModel(payload) {
return GetJSON("/datamodeler/easyDataModelerCURD/getCheckoutDataModel", payload); return GetJSON("/datamodeler/easyDataModelerCURD/getCheckoutDataModel", payload);
} }
export function dataModelRollback(payload) {
return PostJSON("/datamodeler/easyDataModelerCURD/reset", payload);
}
export function ddlGenerators() { export function ddlGenerators() {
return GetJSON("/datamodeler/easyDataModelerExport/ddlGenerators"); return GetJSON("/datamodeler/easyDataModelerExport/ddlGenerators");
} }
...@@ -205,6 +209,10 @@ export function exportDDLString(payload) { ...@@ -205,6 +209,10 @@ export function exportDDLString(payload) {
return GetJSON("/datamodeler/easyDataModelerExport/ddlString", payload); return GetJSON("/datamodeler/easyDataModelerExport/ddlString", payload);
} }
export function ddlChangeString(payload) {
return Get("/datamodeler/easyDataModelerExport/ddlChangeString", payload);
}
export function exportERWinString(payload) { export function exportERWinString(payload) {
return GetJSON("/datamodeler/easyDataModelerExport/erWinPluginString", payload); return GetJSON("/datamodeler/easyDataModelerExport/erWinPluginString", payload);
} }
...@@ -290,41 +298,136 @@ export function getPrivilegeAdmin() { ...@@ -290,41 +298,136 @@ export function getPrivilegeAdmin() {
} }
export function getRuleTemplateList() { export function getRuleTemplateList() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/list"); return GetJSON("/datamodeler/easyDataModelerRuleTemplate/list");
} }
/* rule template */
export function addRuleTemplate(payload) { export function addRuleTemplate(payload) {
return PostJSON("/shandatamodeler/easyDataModelerRuleTemplate/add", payload); return PostJSON("/datamodeler/easyDataModelerRuleTemplate/add", payload);
} }
export function updateRuleTemplate(payload) { export function updateRuleTemplate(payload) {
return PostJSON("/shandatamodeler/easyDataModelerRuleTemplate/update", payload); return PostJSON("/datamodeler/easyDataModelerRuleTemplate/update", payload);
} }
export function deletesRuleTemplate(payload) { export function deletesRuleTemplate(payload) {
return Delete("/shandatamodeler/easyDataModelerRuleTemplate/dels", payload); return Delete("/datamodeler/easyDataModelerRuleTemplate/dels", payload);
} }
export function deleteRuleTemplate(payload) { export function deleteRuleTemplate(payload) {
return Delete("/shandatamodeler/easyDataModelerRuleTemplate/del", payload); return Delete("/datamodeler/easyDataModelerRuleTemplate/del", payload);
} }
export function getRuleTemplateDetail(payload) { export function getRuleTemplateDetail(payload) {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getById", payload); return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getById", payload);
} }
export function getRuleTemplateCheckTypes() { export function getRuleTemplateCheckTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getCheckTypes"); return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getCheckTypes");
} }
export function getRuleTemplateAllCheckPropertyTypes() { export function getRuleTemplateAllCheckPropertyTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getAllCheckPropertyTypes"); return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllCheckPropertyTypes");
} }
export function getRuleTemplateAllVerifyExpressionTypes() { export function getRuleTemplateAllVerifyExpressionTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressionTypes") return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressionTypes")
} }
export function getRuleTemplateAllVertifyExpressions() { export function getRuleTemplateAllVertifyExpressions() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressions") 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
...@@ -131,7 +131,6 @@ function FC<RowType extends object = any>({ width, maxHeight, pageSize, pageNum, ...@@ -131,7 +131,6 @@ function FC<RowType extends object = any>({ width, maxHeight, pageSize, pageNum,
}} }}
onRow={(record, index) => { onRow={(record, index) => {
return { return {
id: record?.id,
onClick: event => { onClick: event => {
onRowClick?.(event, record) onRowClick?.(event, record)
}, },
......
...@@ -14,7 +14,7 @@ import { DataModelerRoleAdmin, DataModelerRoleUser, DataModelerRoleReader, Asset ...@@ -14,7 +14,7 @@ import { DataModelerRoleAdmin, DataModelerRoleUser, DataModelerRoleReader, Asset
//元曜公网环境 isSzseEnv false //元曜公网环境 isSzseEnv false
export const isSzseEnv = false; export const isSzseEnv = false;
export const inputWidth = isSzseEnv?360:240; export const inputWidth = isSzseEnv?360:200;
export const ContextPath = '/data-govern'; export const ContextPath = '/data-govern';
...@@ -508,4 +508,41 @@ export function getValidString(strs) { ...@@ -508,4 +508,41 @@ export function getValidString(strs) {
return str return str
} }
} }
} }
\ No newline at end of file
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 '';
};
\ No newline at end of file
...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer' ...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs' import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons" import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage' import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page' import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table' import Table from '../../../util/Component/Table'
...@@ -154,7 +155,14 @@ const FC = (props) => { ...@@ -154,7 +155,14 @@ const FC = (props) => {
id={record.id} id={record.id}
did={record.dirId} did={record.dirId}
type='dataAsset' type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]} tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/> />
</div> </div>
} }
......
...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer' ...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs' import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons" import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage' import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page' import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table' import Table from '../../../util/Component/Table'
...@@ -276,7 +277,14 @@ const FC = (props) => { ...@@ -276,7 +277,14 @@ const FC = (props) => {
id={record.id} id={record.id}
did={record.dirId} did={record.dirId}
type='dataAsset' type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]} tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/> />
} }
] ]
......
...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer' ...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs' import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons" import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage' import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page' import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table' import Table from '../../../util/Component/Table'
...@@ -121,7 +122,14 @@ const FC = () => { ...@@ -121,7 +122,14 @@ const FC = () => {
id={record.id} id={record.id}
did={record.dirId} did={record.dirId}
type='dataAsset' type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]} tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/> />
</div> </div>
} }
......
...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer' ...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs' import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons" import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage' import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page' import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table' import Table from '../../../util/Component/Table'
...@@ -154,7 +155,14 @@ const FC = (props) => { ...@@ -154,7 +155,14 @@ const FC = (props) => {
id={record.id} id={record.id}
did={record.dirId} did={record.dirId}
type='dataAsset' type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]} tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/> />
</div> </div>
} }
......
...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer' ...@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs' import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons" import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage' import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page' import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table' import Table from '../../../util/Component/Table'
...@@ -366,7 +367,14 @@ const FC = (props) => { ...@@ -366,7 +367,14 @@ const FC = (props) => {
id={record.id} id={record.id}
did={record.dirId} did={record.dirId}
type='dataAsset' type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]} tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/> />
} }
] ]
......
...@@ -3,7 +3,7 @@ import { Modal, Space, Button, Form, Tabs, Checkbox } from "antd"; ...@@ -3,7 +3,7 @@ import { Modal, Space, Button, Form, Tabs, Checkbox } from "antd";
import LocalStorage from 'local-storage'; import LocalStorage from 'local-storage';
import ImportAction from "./ImportAction"; import ImportAction from "./ImportAction";
import { inheritanceHistoricalType, inheritanceZipperType } from "./ImportActionInherited"; import { inheritanceHistoricalType, inheritanceZipperType } from "./ImportActionRelation";
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
const FC = (props) => { const FC = (props) => {
...@@ -150,9 +150,7 @@ const FC = (props) => { ...@@ -150,9 +150,7 @@ const FC = (props) => {
} }
key={inheritanceHistoricalType} key={inheritanceHistoricalType}
> >
<div style={{ height: '75vh', overflowX: 'hidden' }}>
{ historicalModelerData && <ImportAction form={historicalForm} action='edit-inherite-modal' roughModelerData={historicalModelerData} onChange={onHistoricalChange} /> } { historicalModelerData && <ImportAction form={historicalForm} action='edit-inherite-modal' roughModelerData={historicalModelerData} onChange={onHistoricalChange} /> }
</div>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane <Tabs.TabPane
tab={ tab={
...@@ -168,9 +166,7 @@ const FC = (props) => { ...@@ -168,9 +166,7 @@ const FC = (props) => {
} }
key={inheritanceZipperType} key={inheritanceZipperType}
> >
<div style={{ height: '75vh', overflowX: 'hidden' }}>
{ zipperModelerData && <ImportAction form={zipperForm} action='edit-inherite-modal' roughModelerData={zipperModelerData} onChange={onZipperChange} /> } { zipperModelerData && <ImportAction form={zipperForm} action='edit-inherite-modal' roughModelerData={zipperModelerData} onChange={onZipperChange} /> }
</div>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
} }
......
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Form, Button, Space, Tooltip } from 'antd'; import { Form, Button, Space, Tooltip, Modal } from 'antd';
import LocalStorage from 'local-storage'; import LocalStorage from 'local-storage';
import { useMount, useUnmount } from 'ahooks'; import { useMount, useUnmount } from 'ahooks';
import ImportAction from './ImportAction'; import ImportAction from './ImportAction';
import CatalogModal from './CatalogModal'; import CatalogModal from './CatalogModal';
import { dispatchLatest } from '../../../../model'; import { dispatchLatest, dispatch } from '../../../../model';
import { getQueryParam, showMessage, showNotifaction } from '../../../../util'; import { getQueryParam, showMessage, showNotifaction } from '../../../../util';
import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, VersionId, Holder, DDL, ReadOnly } from '../../../../util/constant'; import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, VersionId, Holder, DDL, ReadOnly } from '../../../../util/constant';
import HistoryAndVersionDrawer from './HistoryAndVersionDrawer'; import HistoryAndVersionDrawer from './HistoryAndVersionDrawer';
import { EditModelContext } from './ContextManage'; import { EditModelContext } from './ContextManage';
import EditInherited from './EditInherited'; import EditInherited from './EditInherited';
import { ImportActionHeaderSubject } from './ImportActionHeader'; import { ImportActionHeaderSubject } from './ImportActionManage';
import PermissionButton from '../../../../util/Component/PermissionButton'; import PermissionButton from '../../../../util/Component/PermissionButton';
import './EditModel.less'; import './EditModel.less';
...@@ -35,6 +35,7 @@ const EditModel = (props) => { ...@@ -35,6 +35,7 @@ const EditModel = (props) => {
const { action, catalogId, modelerId, hints, roughModelerData, permitCheckOut, editable, stateId, versionId, holder, ddl, readOnly } = actionData; const { action, catalogId, modelerId, hints, roughModelerData, permitCheckOut, editable, stateId, versionId, holder, ddl, readOnly } = actionData;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [modal, contextHolder] = Modal.useModal()
const importActionRef = useRef(undefined); const importActionRef = useRef(undefined);
useEffect(() => { useEffect(() => {
...@@ -261,6 +262,32 @@ const EditModel = (props) => { ...@@ -261,6 +262,32 @@ const EditModel = (props) => {
setHistoryAndVersionDrawerVisible(false); setHistoryAndVersionDrawerVisible(false);
} }
const onRollback = () => {
modal.confirm({
title: '提示',
content: '确定将该版本创建为新的草稿吗?',
onOk: () => {
setConfirmLoading(true);
dispatch({
type: 'datamodel.dataModelRollback',
payload: {
params: {
easyDataModelerDataModelId: modelerId,
modelVersionId: versionId,
}
},
callback: data => {
setConfirmLoading(false);
showMessage('success', '版本回退成功')
},
error: () => {
setConfirmLoading(false);
}
})
}
})
}
const attrIsEditingFunction = (value) => { const attrIsEditingFunction = (value) => {
attrIsEditingRef.current = value; attrIsEditingRef.current = value;
} }
...@@ -430,6 +457,14 @@ const EditModel = (props) => { ...@@ -430,6 +457,14 @@ const EditModel = (props) => {
} }
</Space> </Space>
); );
} else if (action === 'detail-version') {
actionsBtn = (
<Space>
<Button type='primary' danger onClick={onRollback} >
回退到该版本
</Button>
</Space>
)
} }
return ( return (
...@@ -469,6 +504,7 @@ const EditModel = (props) => { ...@@ -469,6 +504,7 @@ const EditModel = (props) => {
} }
}} }}
/> />
{contextHolder}
</div> </div>
</EditModelContext.Provider> </EditModelContext.Provider>
); );
......
...@@ -6,24 +6,21 @@ ...@@ -6,24 +6,21 @@
padding: 0 15px; padding: 0 15px;
background-color: #464d6e; background-color: #464d6e;
align-items: center; align-items: center;
position: fixed; // position: fixed;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid #EFEFEF; border-bottom: 1px solid #EFEFEF;
z-index: 100; // z-index: 100;
} }
.edit-container { .edit-container {
top: 44px;
width: 100%;
height: calc(100vh - 44px - 64px);
overflow: auto;
background: #EDF0F5; background: #EDF0F5;
padding: 10px 20px; height: calc(100vh - 44px - 64px);
position: absolute; overflow: hidden;
} }
.edit-container-card { .edit-container-card {
padding: 20px; margin: 10px 20px;
height: calc(100vh - 44px - 64px - 20px);
background: #fff; background: #fff;
} }
......
...@@ -6,6 +6,7 @@ const exportModes = [ ...@@ -6,6 +6,7 @@ const exportModes = [
{ name: '导出Erwin', key: 'erwin' }, { name: '导出Erwin', key: 'erwin' },
{ name: '导出Excel', key: 'excel' }, { name: '导出Excel', key: 'excel' },
{ name: '导出Word', key: 'word' }, { name: '导出Word', key: 'word' },
{ name: '导出模型信息', key: 'basicExcel' },
] ]
const ExportOtherModal = (props) => { const ExportOtherModal = (props) => {
...@@ -62,7 +63,7 @@ const ExportOtherModal = (props) => { ...@@ -62,7 +63,7 @@ const ExportOtherModal = (props) => {
forceRender forceRender
visible={visible} visible={visible}
title='模型导出' title='模型导出'
width={540} width={700}
onCancel={cancel} onCancel={cancel}
footer={footer} footer={footer}
> >
......
import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, useRef, useImperativeHandle } from 'react';
import { Spin } from 'antd'; import { Spin, Tabs, Anchor, Affix, Button } from 'antd';
import LocalStorage from 'local-storage'; import LocalStorage from 'local-storage';
import { Subject } from 'rxjs';
import ImportActionHeader from './ImportActionHeader'; import ImportActionHeader from './ImportActionHeader';
import ImportActionInherited from './ImportActionInherited';
import { ImportActionTable } from './ImportActionTable'; import { ImportActionTable } from './ImportActionTable';
import ImportActionIndex from './ImportActionIndex'; 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 { Action } from '../../../../util/constant';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import './ImportAction.less'
export const importActionSubject = new Subject()
const ImportAction = React.forwardRef((props, ref) => { const ImportAction = React.forwardRef((props, ref) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut, catalogId } = props; const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut, catalogId } = props;
...@@ -23,9 +30,12 @@ const ImportAction = React.forwardRef((props, ref) => { ...@@ -23,9 +30,12 @@ const ImportAction = React.forwardRef((props, ref) => {
const [ supportedIndextypes, setSupportedIndextypes ] = useState([]); const [ supportedIndextypes, setSupportedIndextypes ] = useState([]);
const [ validateReports, setValidateReports ] = useState([]); const [ validateReports, setValidateReports ] = useState([]);
const [ loading, setLoading ] = useState(false); const [ loading, setLoading ] = useState(false);
const [container, setContainer] = useState();
const [activeValue, setActiveValue] = useState();
const mountRef = useRef(true); const mountRef = useRef(true);
const modelerDataRef = useRef(null); const modelerDataRef = useRef(null);
const animating = useRef(false);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
isLoading: () => { isLoading: () => {
...@@ -106,6 +116,40 @@ const ImportAction = React.forwardRef((props, ref) => { ...@@ -106,6 +116,40 @@ const ImportAction = React.forwardRef((props, ref) => {
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [constraint]) }, [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 = () => { const getTemplates = () => {
dispatch({ dispatch({
type: 'datamodel.getAllTemplates', type: 'datamodel.getAllTemplates',
...@@ -280,25 +324,21 @@ const ImportAction = React.forwardRef((props, ref) => { ...@@ -280,25 +324,21 @@ const ImportAction = React.forwardRef((props, ref) => {
const onConstraintChange = (value) => { const onConstraintChange = (value) => {
let currentConstraint = null; let currentConstraint = null;
(constraints||[]).forEach((_constraint, index) => { const index = (constraints??[]).findIndex(item => item.id === value)
if (_constraint.name === value) { if (index !== -1) {
currentConstraint = _constraint; currentConstraint = constraints[index]
} form.setFieldsValue({
}); easyDataModelerModelingConstraint: currentConstraint
});
if (!currentConstraint) return; const newModelerData = {...modelerData, easyDataModelerModelingConstraint: currentConstraint };
setModelerData(newModelerData);
form.setFieldsValue({ modelerDataRef.current = newModelerData;
easyDataModelerModelingConstraint: currentConstraint
}); onChange && onChange(newModelerData);
const newModelerData = {...modelerData, easyDataModelerModelingConstraint: currentConstraint };
setModelerData(newModelerData); setConstraint(currentConstraint);
modelerDataRef.current = newModelerData; getConsult(newModelerData);
}
onChange && onChange(newModelerData);
setConstraint(currentConstraint);
getConsult(newModelerData);
} }
const onTemplateChange = (value, isCustom = false) => { const onTemplateChange = (value, isCustom = false) => {
...@@ -542,46 +582,85 @@ const ImportAction = React.forwardRef((props, ref) => { ...@@ -542,46 +582,85 @@ const ImportAction = React.forwardRef((props, ref) => {
return ( return (
<Spin spinning={loading}> <Spin spinning={loading}>
{ {
(action==='detail' && ((modelerData||{}).optionList||[]).findIndex(item => item.enabled && item.name==='查看') === -1) ? <span> (action==='detail' && ((modelerData||{}).optionList||[]).findIndex(item => item.enabled && item.name==='查看') === -1) ? <div style={{ padding: '10px 20px', height: 60 }}>
{loading?'':'暂无权限'} {loading?'':'暂无权限'}
</span> : <React.Fragment> </div> : <div className='import-action'>
<ImportActionHeader <Tabs activeKey={activeValue} centered onChange={(val) => {
form={form} setActiveValue(val);
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'} var targetElement = container?.querySelector(`.${val}`); // 找到目标元素
modelerData={modelerData||{}} if (targetElement) {
constraints={constraints} importActionSubject.next({ type: 'expand', key: val, action })
templates={templates} setTimeout(() => {
validateReports={validateReports} animating.current = true;
onTemplateChange={onTemplateChange} targetElement.scrollIntoView();
onConstraintChange={onConstraintChange} }, 100)
onChange={onHeaderChange} }
terms={terms} }}>
supportedPartitionTypes={supportedPartitionTypes} <Tabs.TabPane tab='基本信息' key="model-import-action-basic" />
/> <Tabs.TabPane tab='技术信息' key="model-import-action-technical" />
<ImportActionInherited modelerData={modelerData} action={action} /> <Tabs.TabPane tab='数据表结构' key="model-import-action-table" />
<ImportActionTable <Tabs.TabPane tab='数据表索引' key="model-import-action-index" />
modelerData={modelerData||{}} <Tabs.TabPane tab='管理信息' key="model-import-action-manage" />
constraint={constraint} <Tabs.TabPane tab='关联对象' key="model-import-action-relation" />
template={template} {
validateReports={validateReports} modelerData?.id && <Tabs.TabPane tab='模型评论' key="model-import-action-comment" />
supportedDatatypes={supportedDatatypes} }
onChange={onTableChange} </Tabs>
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'} <div ref={setContainer} style={{ height: action==='edit-inherite-modal'?'60vh':'calc(100vh - 44px - 64px - 66px)', overflow: 'auto', padding: '20px 20px 0' }}>
action={action} <ImportActionHeader
originAction={getQueryParam(Action, props?.location?.search)} form={form}
terms={terms} editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
/> modelerData={modelerData||{}}
<ImportActionIndex constraints={constraints}
modelerData={modelerData||{}} templates={templates}
constraint={constraint} validateReports={validateReports}
template={template} onTemplateChange={onTemplateChange}
types={supportedIndextypes} onConstraintChange={onConstraintChange}
validateReports={validateReports} onChange={onHeaderChange}
onChange={onIndexChange} terms={terms}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'} supportedPartitionTypes={supportedPartitionTypes}
terms={terms} action={action}
/> />
</React.Fragment> <ImportActionTable
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
action={action}
originAction={getQueryParam(Action, props?.location?.search)}
terms={terms}
/>
<ImportActionIndex
modelerData={modelerData||{}}
constraint={constraint}
template={template}
types={supportedIndextypes}
validateReports={validateReports}
onChange={onIndexChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
terms={terms}
action={action}
/>
<ImportActionManage
form={form}
modelerData={modelerData||{}}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
action={action}
/>
<ImportActionRelation
modelerData={modelerData} action={action}
/>
{
modelerData?.id && <ImportActionComment
modelerData={modelerData}
action={action}
/>
}
</div>
</div>
} }
</Spin> </Spin>
); );
......
.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'
import { importActionSubject } from "./ImportAction"
const FC = (props) => {
const { modelerData, action } = 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(() => {
const $importActionSubject = importActionSubject.subscribe((props) => {
if (props.type === 'expand' && props.key === 'model-import-action-comment' && props.action === action) {
setCollapse(false)
}
})
return () => {
$importActionSubject.unsubscribe()
}
}, [action])
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, useMemo, useRef } from 'react'; import React, { useState, useEffect, useMemo, useRef } from 'react';
import { Form, Input, Row, Col, Descriptions, Select, AutoComplete, Button, Divider, Tooltip, Checkbox } from 'antd'; import { Form, Input, Row, Col, Select, AutoComplete, Button, Divider, Tooltip, Checkbox, Space } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons'; import { DownOutlined, UpOutlined, ExclamationCircleFilled, WarningFilled } from '@ant-design/icons';
import { Subject } from 'rxjs'; 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 { dispatch, dispatchLatest } from '../../../../model';
import Rule from '../../ModelConfig/Component/rule-readonly';
import DebounceInput from './DebounceInput'; import DebounceInput from './DebounceInput';
import DataQuality, { DataQualityFeignTagList } from '../../../QianKun/data-quality'
import './ImportActionHeader.less'; import './ImportActionHeader.less';
import { importActionSubject } from './ImportAction';
const { TextArea } = Input; const { TextArea } = Input;
const { Option } = Select; const { Option } = Select;
...@@ -46,30 +49,30 @@ const updateOptions = [ ...@@ -46,30 +49,30 @@ const updateOptions = [
const dataTypeRemark = '描述ETL框架中目标表的数据类型'; const dataTypeRemark = '描述ETL框架中目标表的数据类型';
const bindingLoadRemark = '描述ETL框架绑定加载列表,如chain、daily、current等'; const bindingLoadRemark = '描述ETL框架绑定加载列表,如chain、daily、current等';
export const ImportActionHeaderSubject = new Subject();
const ImportActionHeader = (props) => { const ImportActionHeader = (props) => {
const { editable, form, modelerData, constraints, templates, onConstraintChange, onTemplateChange, validateReports, onChange, terms, supportedPartitionTypes } = props; const { editable, form, modelerData, constraints, templates, onConstraintChange, onTemplateChange, validateReports, onChange, terms, supportedPartitionTypes, action } = props;
const [ causes, setCauses ] = useState([]);
const [ options, setOptions ] = useState([]); const [ options, setOptions ] = useState([]);
const [ autoTranslate, setAutoTranslate ] = useState(false); const [ autoTranslate, setAutoTranslate ] = useState(false);
const [ onlyShowRequireChange, setOnlyShowRequireChange ] = useState(true);
const [ maintenanceRecords, setMaintenanceRecords ] = useState(null);
const [ dataTypeList, setDataTypeList ] = useState(null); const [ dataTypeList, setDataTypeList ] = useState(null);
const [ bindingLoadRangeList, setBindingLoadRangeList ] = useState(null); const [ bindingLoadRangeList, setBindingLoadRangeList ] = useState(null);
const [isCollapse, setCollapse] = useState(true)
const [ruleParams, setRuleParams] = useState({
visible: false
})
useEffect(() => { useEffect(() => {
const $$header = ImportActionHeaderSubject.subscribe((act) => { const $importActionSubject = importActionSubject.subscribe((props) => {
if (act?.type === 'refreshMaintenanceRecords') { if (props.type === 'expand' && props.key === 'model-import-action-technical' && props.action === action) {
getMaintenanceRecords(); setCollapse(false)
} }
}) })
return () => { return () => {
$$header.unsubscribe() $importActionSubject.unsubscribe()
} }
}, [modelerData]) }, [action])
useEffect(() => { useEffect(() => {
getDataTypeList(); getDataTypeList();
...@@ -85,54 +88,14 @@ const ImportActionHeader = (props) => { ...@@ -85,54 +88,14 @@ const ImportActionHeader = (props) => {
useEffect(() => { useEffect(() => {
const causes = [];
(validateReports||[]).forEach(report => {
if (report.type === 'DataModel') {
(report.reportItems||[]).forEach((item) => {
causes.push(item.cause||'');
})
}
});
if (editable) {
form?.setFields([{ name: 'name', errors: causes }]);
} else {
setCauses(causes);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports])
useEffect(() => {
setAutoTranslate((modelerData.name||'')===''); setAutoTranslate((modelerData.name||'')==='');
if (modelerData) { if (modelerData) {
form?.setFieldsValue(modelerData); form?.setFieldsValue(modelerData);
if ((modelerData.id||'')!=='' && maintenanceRecords===null) {
getMaintenanceRecords();
}
} }
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData]) }, [modelerData])
useEffect(() => {
if ((maintenanceRecords||[]).length>0) {
let maintenanceDescription = '';
maintenanceRecords.forEach((record, index) => {
if (index !== 0) {
maintenanceDescription += '/';
}
maintenanceDescription += record;
})
form?.setFieldsValue({ maintenanceRecords: maintenanceDescription });
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [maintenanceRecords, editable])
//分布 //分布
const distributionDescription = useMemo(() => { const distributionDescription = useMemo(() => {
let newDistributionDescription = '' let newDistributionDescription = ''
...@@ -201,6 +164,10 @@ const ImportActionHeader = (props) => { ...@@ -201,6 +164,10 @@ const ImportActionHeader = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [editable, modelerData]) }, [editable, modelerData])
const marginBottom = useMemo(() => {
return editable ? 15 : 10
}, [editable])
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
xs: { span: 24 }, xs: { span: 24 },
...@@ -212,20 +179,6 @@ const ImportActionHeader = (props) => { ...@@ -212,20 +179,6 @@ const ImportActionHeader = (props) => {
}, },
}; };
const getMaintenanceRecords = () => {
dispatch({
type: 'datamodel.getMaintenanceRecords',
payload: {
params: {
id: modelerData.id
}
},
callback: data => {
setMaintenanceRecords(data);
}
})
}
const getDataTypeList = () => { const getDataTypeList = () => {
dispatch({ dispatch({
type: 'datamodel.dataTypeList', type: 'datamodel.dataTypeList',
...@@ -322,27 +275,152 @@ const ImportActionHeader = (props) => { ...@@ -322,27 +275,152 @@ const ImportActionHeader = (props) => {
} }
} }
const onOnlyShowRequireChange = () => {
setOnlyShowRequireChange(!onlyShowRequireChange);
}
return ( return (
<div className='model-import-action-header'> <div className={classnames('model-import-action-header', editable?'':'model-import-action-header-readolny')}>
<div <div className='model-import-action-basic mb-3'>
className='mb-3' <h3 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h3>
style={{ </div>
display: 'flex', <Form form={form} {...formItemLayout}
alignItems: 'center', onValuesChange={onValuesChange}
}} >
> <Row gutter={10}>
<h2 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h2> <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 }}
>
{
editable ? <InputDebounce /> : <span className='word-wrap'>
{highlightSearchContentByTerms(modelerData?.cnName, terms)}
</span>
}
</Form.Item>
</Col>
<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 }}
>
{
editable ? <AutoComplete options={options} onSearch={onSearch} /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.name, terms)}</span>
}
</Form.Item>
</Col>
<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>
{
modelerData?.id && modelerData?.state?.id === '4' && <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="标签"
style={{ marginBottom }}
>
<DataQuality
type={DataQualityFeignTagList}
data={{
type: 'model',
id: modelerData?.id,
}}
/>
</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>
{ {
(onlyShowRequireChange ? <Button type='primary' size='small' onClick={onOnlyShowRequireChange}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={onOnlyShowRequireChange}>收起<UpOutlined /></Button>) !isCollapse && <Form
}
</div>
{
editable ? (
<Form
form={form} form={form}
{...formItemLayout} {...formItemLayout}
onValuesChange={onValuesChange} onValuesChange={onValuesChange}
...@@ -350,268 +428,130 @@ const ImportActionHeader = (props) => { ...@@ -350,268 +428,130 @@ const ImportActionHeader = (props) => {
<Row gutter={10}> <Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}> <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item <Form.Item
label="中文名称" label="分布键"
name="cnName" name="easyDataModelerDistributionKey"
rules={[{ required: true, message: '请输入中文名称!' }]} style={{ marginBottom }}
> >
<InputDebounce /> {
editable ? <AttributesSelect modelerData={modelerData} /> : <span className='word-wrap'>{highlightSearchContentByTerms(distributionDescription, terms)}</span>
}
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={24} lg={12} xl={8}> <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item <Form.Item
label="英文名称" label="分区键"
name="name" name="partition"
rules={[{ required: true, message: '请输入英文名称!' }]} style={{ marginBottom }}
> >
<AutoComplete options={options} onSearch={onSearch} /> {
editable ? <PartitionSelect modelerData={modelerData} partitionTypes={supportedPartitionTypes} /> : <span className='word-wrap'>{highlightSearchContentByTerms(partitionsDescription, terms)}</span>
}
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={24} lg={12} xl={8}> <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item <Form.Item
label="规范" label="类主键"
name="easyDataModelerModelingConstraint" name="easyDataModelerSemiPrimaryKey"
rules={[{ required: true, message: '请选择规范!' }]} style={{ marginBottom }}
> >
<ConstraintSelect {
constraints={constraints} editable ? <AttributesSelect modelerData={modelerData} mode='tags' /> : <span className='word-wrap'>{highlightSearchContentByTerms(semiPrimaryDescription, terms)}</span>
onChange={onConstraintChange} }
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={24} lg={12} xl={8}> <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item <Form.Item
label="数据内容" label="数据情况"
name="remark" name="dataCircumstances"
rules={[{ required: true, message: '请输入数据内容!' }]} style={{ marginBottom }}
style={{ marginBottom: 15 }}
> >
<TextArea rows={4} placeholder='描述数据表包含的业务数据,包括数据内容业务描述、表的适用范围、数据粒度、数据统计口径、数据来源等。对于原始数据表,可以说明加载的源接口信息。' /> {
editable ? <Input placeholder='描述数据表中数据的更新频率、每日增量情况、数据量级和历史数据' /> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataCircumstances, terms)}</span>
}
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={24} lg={12} xl={8}> <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item <Form.Item
label="数据表类型" label="更新时间"
name="tableType" name="dataUpdatingTiming"
rules={[{ required: true, message: '请选择数据表类型!' }]} style={{ marginBottom }}
> >
<TemplateSelect {
modelerData={modelerData} editable ? <UpdateSelect placeholder='描述数据表的更新时间点'/> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataUpdatingTiming, terms)}</span>
templates={templates} }
onChange={onTemplateChange}
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> <Col xs={24} sm={24} lg={12} xl={8}>
{ <Form.Item
!onlyShowRequireChange && <React.Fragment> label="数据类型"
<Divider style={{ margin: '0 0 15px' }} /> name="dataType"
<Row gutter={10}> tooltip={dataTypeRemark}
<Col xs={24} sm={24} lg={12} xl={8}> style={{ marginBottom }}
<Row> >
<Col span={24}>
<Form.Item
label="技术主键"
name="easyDataModelerPrimaryKey"
>
<AttributesSelect modelerData={modelerData} mode='tags' />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="分布键"
name="easyDataModelerDistributionKey"
>
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="分区键"
name="partition"
>
<PartitionSelect modelerData={modelerData} partitionTypes={supportedPartitionTypes} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="类主键"
name="easyDataModelerSemiPrimaryKey"
>
<AttributesSelect modelerData={modelerData} mode='tags' />
</Form.Item>
</Col>
</Row>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Row>
<Col span={24}>
<Form.Item
label="数据平台"
name="dataResidence"
>
<Input placeholder='描述数据表落地的数据平台及数据库' />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="数据类型"
name="dataType"
tooltip={dataTypeRemark}
>
<Select allowClear placeholder='请选择数据类型'>
{
dataTypeList?.map((item, index) => <Option key={index} value={item}>
{item}
</Option>)
}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="绑定加载范围"
name="bindingLoadRange"
tooltip={bindingLoadRemark}
>
<Select allowClear placeholder='请选择绑定加载范围'>
{
bindingLoadRangeList?.map((item, index) => <Option key={index} value={item}>
{item}
</Option>)
}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
label="加载方式"
name="dataLoadingStrategy"
>
<LoadSelect />
</Form.Item>
</Col>
</Row>
</Col>
<Col xs={24} sm={24} lg={24} xl={8}>
<Row>
<Col xs={24} sm={24} lg={12} xl={24}>
<Form.Item
label="数据情况"
name="dataCircumstances"
>
<Input placeholder='描述数据表中数据的更新频率、每日增量情况、数据量级和历史数据' />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={24}>
<Form.Item
label="更新时间"
name="dataUpdatingTiming"
>
<UpdateSelect placeholder='描述数据表的更新时间点'/>
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={24}>
<Form.Item
label="维护历史"
name="maintenanceRecords"
>
<TextArea rows={3} disabled={true} />
</Form.Item>
</Col>
</Row>
</Col>
</Row>
</React.Fragment>
}
</Form>
) : (
<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>}>
{
<div>
<div>{highlightSearchContentByTerms(modelerData.name||'', terms)}</div>
{ {
(causes||[]).map((cause, index) => { editable ? <Select allowClear placeholder='请选择数据类型'>
return ( {
<div key={index} style={{ color: '#ff4d4f' }}> dataTypeList?.map((item, index) => <Option key={index} value={item}>
{cause||''} {item}
</div> </Option>)
) }
}) </Select> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.dataType, terms)}</span>
} }
</div> </Form.Item>
} </Col>
</Descriptions.Item> <Col xs={24} sm={24} lg={12} xl={8}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>规范</div>} >{modelerData.easyDataModelerModelingConstraint?(modelerData.easyDataModelerModelingConstraint.cnName||''):''}</Descriptions.Item> <Form.Item
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据内容</div>}>{highlightSearchContentByTerms(modelerData.remark||'', terms)}</Descriptions.Item> label="绑定加载范围"
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据表类型</div>} >{modelerData.tableType}</Descriptions.Item> name="bindingLoadRange"
</Descriptions> tooltip={bindingLoadRemark}
{ style={{ marginBottom }}
!onlyShowRequireChange && <Divider style={{ margin: '0 0 15px' }} /> >
} {
{ editable ? <Select allowClear placeholder='请选择绑定加载范围'>
!onlyShowRequireChange && <React.Fragment> {
<Descriptions column={3}> bindingLoadRangeList?.map((item, index) => <Option key={index} value={item}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>技术主键</div>} >{highlightSearchContentByTerms(primaryDescription||'', terms)}</Descriptions.Item> {item}
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据平台</div>} >{highlightSearchContentByTerms(modelerData.dataResidence||'', terms)}</Descriptions.Item> </Option>)
<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> </Select> : <span className='word-wrap'>{highlightSearchContentByTerms(modelerData?.bindingLoadRang, terms)}</span>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据类型</div>} >{highlightSearchContentByTerms(modelerData.dataType||'', terms)}</Descriptions.Item> }
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>更新时间</div>} >{highlightSearchContentByTerms(modelerData.dataUpdatingTiming||'', terms)}</Descriptions.Item> </Form.Item>
</Descriptions> </Col>
<Row> </Row>
<Col span={16}> </Form>
<Descriptions column={2}> }
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>分区键</div>} >{highlightSearchContentByTerms(partitionsDescription||'', terms)}</Descriptions.Item> <Rule
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>绑定加载范围</div>} >{highlightSearchContentByTerms(modelerData.bindingLoadRange||'', terms)}</Descriptions.Item> {...ruleParams}
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>类主键</div>} >{highlightSearchContentByTerms(semiPrimaryDescription||'', terms)}</Descriptions.Item> onCancel={() => {
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>加载方式</div>} >{highlightSearchContentByTerms(modelerData.dataLoadingStrategy||'', terms)}</Descriptions.Item> setRuleParams({ visible: false })
</Descriptions> }}
</Col> />
<Col span={8}>
<Descriptions column={1}>
<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>
</Col>
</Row>
</React.Fragment>
}
</React.Fragment>
)
}
</div> </div>
) )
} }
export default ImportActionHeader; export default ImportActionHeader;
const ConstraintSelect = ({ value = {}, constraints = [], onChange, ...restProps }) => { const ConstraintSelect = ({ value = {}, constraints = [], onChange, onDetail, ...restProps }) => {
return ( return (
<Select <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
onChange={onChange} <Select
value={value?.name || ''} onChange={(val) => { onChange?.(val) }}
placeholder='请选择规范' value={value?.id}
{...restProps} placeholder='请选择规范'
> style={{ flex: 1 }}
{ {...restProps}
(constraints||[]) && constraints.map((constraint, index) => { >
return ( {
<Option key={index} value={constraint.name||''} >{constraint.cnName||''}</Option> (constraints||[]) && constraints.map((constraint, index) => {
); return (
}) <Option key={index} value={constraint.id} >{constraint.cnName||''}</Option>
} );
</Select> })
}
</Select>
<a className='ml-3' onClick={() => { onDetail?.() }}>查看</a>
</div>
) )
} }
...@@ -891,4 +831,51 @@ const UpdateSelect = ({ value = '', onChange, ...restProps }) => { ...@@ -891,4 +831,51 @@ const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
} }
</Select> </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)
}
return []
}, [validateReports, type, propertyName])
return (
<React.Fragment>
{
(reports??[]).length === 0 ? null : <Tooltip title={
<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>
}>
{
(reports??[]).findIndex(item => item.checkRule?.alertTypeId === 'enforced') !== -1 ? <WarningFilled style={{ color: '#E94848' }} /> : <ExclamationCircleFilled style={{ color: '#F7AB00' }} />
}
</Tooltip>
}
</React.Fragment>
)
} }
\ 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, { useState, useCallback, useRef, useEffect, useContext } 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 { 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 { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper'; import update from 'immutability-helper';
...@@ -11,6 +11,8 @@ import DebounceInput from './DebounceInput'; ...@@ -11,6 +11,8 @@ import DebounceInput from './DebounceInput';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help'; import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { showMessage, highlightSearchContentByTerms, inputWidth } from '../../../../util'; import { showMessage, highlightSearchContentByTerms, inputWidth } from '../../../../util';
import { EditModelContext } from './ContextManage'; import { EditModelContext } from './ContextManage';
import { ValidateTip } from './ImportActionHeader';
import { importActionSubject } from './ImportAction';
const { Option } = Select; const { Option } = Select;
...@@ -338,7 +340,7 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => ...@@ -338,7 +340,7 @@ const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) =>
}; };
const ImportActionIndex = (props) => { const ImportActionIndex = (props) => {
const { modelerData, onChange, editable, constraint, template, validateReports, terms, types } = props; const { modelerData, onChange, editable, constraint, template, validateReports, terms, types, action } = props;
const [ attributes, setAttributes ] = useState([]); const [ attributes, setAttributes ] = useState([]);
const [ data, setData ] = useState([]); const [ data, setData ] = useState([]);
...@@ -351,6 +353,7 @@ const ImportActionIndex = (props) => { ...@@ -351,6 +353,7 @@ const ImportActionIndex = (props) => {
const [ filterData, setFilterData ] = useState([]); const [ filterData, setFilterData ] = useState([]);
const [ insertIndex, setInsertIndex ] = useState(0); const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null); const [ currentItem, setCurrentItem ] = useState(null);
const [isCollapse, setCollapse] = React.useState(true)
const { indexIsEditingFunction } = useContext(EditModelContext); const { indexIsEditingFunction } = useContext(EditModelContext);
...@@ -364,6 +367,18 @@ const ImportActionIndex = (props) => { ...@@ -364,6 +367,18 @@ const ImportActionIndex = (props) => {
id: MENU_ID, id: MENU_ID,
}); });
useEffect(() => {
const $importActionSubject = importActionSubject.subscribe((props) => {
if (props.type === 'expand' && props.key === 'model-import-action-index' && props.action === action) {
setCollapse(false)
}
})
return () => {
$importActionSubject.unsubscribe()
}
}, [action])
useClickAway(() => { useClickAway(() => {
save(); save();
}, tableRef); }, tableRef);
...@@ -417,6 +432,14 @@ const ImportActionIndex = (props) => { ...@@ -417,6 +432,14 @@ const ImportActionIndex = (props) => {
} }
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [keywordCondition]) }, [keywordCondition])
const menuData = useMemo(() => {
return [
{ title: '在上方插入行', key: 'up' },
{ title: '在下方插入行', key: 'down' },
{ title: '删除', key: 'delete' },
]
}, [])
const isEditing = (record) => record.name === editingKey; const isEditing = (record) => record.name === editingKey;
...@@ -437,7 +460,7 @@ const ImportActionIndex = (props) => { ...@@ -437,7 +460,7 @@ const ImportActionIndex = (props) => {
}) })
} }
const insertToFront = (record) => { const onInsertToFrontClick = (record) => {
save().then(result => { save().then(result => {
if (result) { if (result) {
...@@ -474,7 +497,7 @@ const ImportActionIndex = (props) => { ...@@ -474,7 +497,7 @@ const ImportActionIndex = (props) => {
}) })
} }
const insertToBack = (record) => { const onInsertToBackClick = (record) => {
save().then(result => { save().then(result => {
if (result) { if (result) {
setKeywordCondition({ keyword: '', needFilter: false }); setKeywordCondition({ keyword: '', needFilter: false });
...@@ -539,7 +562,7 @@ const ImportActionIndex = (props) => { ...@@ -539,7 +562,7 @@ const ImportActionIndex = (props) => {
onChange && onChange(newData); onChange && onChange(newData);
} }
const remove = (record) => { const onRemoveClick = (record) => {
if (record.name === '') { if (record.name === '') {
const newData = [...dataRef.current]; const newData = [...dataRef.current];
...@@ -666,9 +689,12 @@ const ImportActionIndex = (props) => { ...@@ -666,9 +689,12 @@ const ImportActionIndex = (props) => {
ellipsis: true, ellipsis: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, termsRef.current)}</span> <ValidateTip type='Index' propertyName='name' validateReports={validateReports} iid={record.name} />
</Tooltip> <Tooltip title={text||''}>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -734,119 +760,9 @@ const ImportActionIndex = (props) => { ...@@ -734,119 +760,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 mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='Index').length>0;
if (editable) { if (editable) {
return columns.map((col) => {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
return _columns.map((col) => {
if (!col.editable) { if (!col.editable) {
return col; return col;
} }
...@@ -866,7 +782,7 @@ const ImportActionIndex = (props) => { ...@@ -866,7 +782,7 @@ const ImportActionIndex = (props) => {
}); });
} }
return hasValidateReports ? includeValidateColumn : columns; return columns;
} }
const moveRow = useCallback( const moveRow = useCallback(
...@@ -898,22 +814,31 @@ const ImportActionIndex = (props) => { ...@@ -898,22 +814,31 @@ const ImportActionIndex = (props) => {
const key = event.currentTarget.id; const key = event.currentTarget.id;
if (key === 'up') { if (key === 'up') {
insertToFront(currentItem); onInsertToFrontClick(currentItem);
} else if (key === 'down') { } else if (key === 'down') {
insertToBack(currentItem); onInsertToBackClick(currentItem);
} else if (key === 'delete') {
onRemoveClick(currentItem);
} }
} }
return ( 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' }}> <div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<Space> <Space>
<h2 style={{ marginBottom: 0 }}>数据表索引</h2> <h3 style={{ marginBottom: 0 }}>数据表索引</h3>
{ {
editable && <Popover content='点击行进行编辑,表格可以通过拖拽来排序'> editable && <Popover content='点击行进行编辑,表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='pointer' /> <QuestionCircleOutlined className='pointer' />
</Popover> </Popover>
} }
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space> </Space>
<Space> <Space>
{ {
...@@ -932,67 +857,69 @@ const ImportActionIndex = (props) => { ...@@ -932,67 +857,69 @@ const ImportActionIndex = (props) => {
</div> </div>
</Space> </Space>
</div> </div>
<div className='mb-3' id="containerId" ref={tableRef}> {
<DndProvider backend={HTML5Backend} > !isCollapse && <div className='mb-3' id="containerId" ref={tableRef}>
<Form form={form} component={false} onValuesChange={onValuesChange}> <DndProvider backend={HTML5Backend} >
<Table <Form form={form} component={false} onValuesChange={onValuesChange}>
components={{ <Table
body: { components={{
cell: EditableCell, body: {
//编辑或者搜索状态下不允许拖动 cell: EditableCell,
row: (editable&&editingKey===null&&keyword==='')?DragableBodyRow:null, //编辑或者搜索状态下不允许拖动
}, row: (editable&&editingKey===null&&keyword==='')?DragableBodyRow:null,
}} },
onRow={(record, index) => { }}
let rowParams = { onRow={(record, index) => {
index, let rowParams = {
}; index,
if (editable) {
rowParams = {...rowParams, onContextMenu: event => {
setCurrentItem(record);
displayMenu(event);
}
}; };
if (!isEditing(record)) { if (editable) {
rowParams = {...rowParams, onClick: (event) => { rowParams = {...rowParams, onContextMenu: event => {
event.stopPropagation(); setCurrentItem(record);
edit(record); displayMenu(event);
}
};
if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation();
edit(record);
}
} }
}
if (keyword.length===0) { if (keyword.length===0) {
rowParams = {...rowParams, moveRow}; rowParams = {...rowParams, moveRow};
}
} }
} }
}
return rowParams;
}}
dataSource={filterData||[]}
columns={mergedColumns()}
size='small'
rowKey='name'
rowClassName="editable-row"
pagination={false}
sticky
scroll={{
x: 1200
}}
/>
</Form>
</DndProvider>
<RcMenu id={MENU_ID} > return rowParams;
<RcItem id="up" onClick={handleItemClick}> }}
在上方插入行 dataSource={filterData||[]}
</RcItem> columns={mergedColumns()}
<RcItem id="down" onClick={handleItemClick}> size='small'
在下方插入行 rowKey='name'
rowClassName="editable-row"
pagination={false}
sticky
scroll={{
x: 1200
}}
/>
</Form>
</DndProvider>
</div>
}
<RcMenu id={MENU_ID} >
{
(menuData??[]).map(item => (
<RcItem key={item.key} id={item.key} onClick={handleItemClick}>
{item.title}
</RcItem> </RcItem>
</RcMenu> ))
</div> }
</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'
import { importActionSubject } from './ImportAction';
export const ImportActionHeaderSubject = new Subject();
const FC = (props) => {
const { editable, form, modelerData, action } = props
const [isCollapse, setCollapse] = React.useState(true)
const [maintenanceRecords, setMaintenanceRecords] = React.useState()
React.useEffect(() => {
const $importActionSubject = importActionSubject.subscribe((props) => {
if (props.type === 'expand' && props.key === 'model-import-action-manage' && props.action === action) {
setCollapse(false)
}
})
return () => {
$importActionSubject.unsubscribe()
}
}, [action])
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, { useState, useEffect } from "react"; import React from "react"
import { Popover } from 'antd'; import { Button, Descriptions, Space, Popover } from "antd"
import { QuestionCircleOutlined } from '@ant-design/icons'; import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { Action, ModelerId, PermitCheckOut, Editable, StateId, Holder, ReadOnly } from '../../../../util/constant'; import { Action, ModelerId, PermitCheckOut, Editable, StateId, Holder, ReadOnly } from '../../../../util/constant'
import { importActionSubject } from "./ImportAction"
export const inheritanceHistoricalType = 'historical'; export const inheritanceHistoricalType = 'historical'
export const inheritanceZipperType = 'zipper'; export const inheritanceZipperType = 'zipper'
const FC = (props) => { const FC = (props) => {
const { modelerData, action } = props; const { modelerData, action } = props
const [relationModelerDatas, setRelationModelerDatas] = useState([]); const [isCollapse, setCollapse] = React.useState(true)
const [relationModelerDatas, setRelationModelerDatas] = React.useState([])
useEffect(() => { React.useEffect(() => {
const $importActionSubject = importActionSubject.subscribe((props) => {
if (props.type === 'expand' && props.key === 'model-import-action-relation' && props.action === action) {
setCollapse(false)
}
})
return () => {
$importActionSubject.unsubscribe()
}
}, [action])
React.useEffect(() => {
if (modelerData?.inheritedFromEasyDataModelerDataModel) { if (modelerData?.inheritedFromEasyDataModelerDataModel) {
const newRelationModelerDatas = []; const newRelationModelerDatas = [];
...@@ -39,24 +53,49 @@ const FC = (props) => { ...@@ -39,24 +53,49 @@ const FC = (props) => {
}, [modelerData]) }, [modelerData])
return ( return (
<div className='model-import-action-inherited'> <div>
<div className='mb-3'> <div className='model-import-action-relation mb-3'>
<div className='flex' style={{ alignItems: 'center' }}> <Space>
<h2 className={action==='add'?'mr-1':'mr-3'} style={{ marginBottom: 0 }}>历史存储形式</h2> <h3 style={{ marginBottom: 0 }}>关联对象</h3>
{ {
action==='add' && <Popover className='mr-3' content='保存当前模型后方可选择历史存储形式'> action==='add' && <Popover content='保存当前模型后方可选择历史存储形式'>
<QuestionCircleOutlined className='pointer' /> <QuestionCircleOutlined className='pointer' />
</Popover> </Popover>
} }
{ {
relationModelerDatas?.length===0 ? <span>暂无信息</span> : relationModelerDatas?.map((item, index) => <a className='mr-3' key={index} onClick={() => { isCollapse ? <Button type='primary' size='small' 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`); setCollapse(!isCollapse)
}}>{item.cnName}</a>) }}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
} }
</div> </Space>
</div> </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> </div>
) )
} }
export default FC; export default FC
\ No newline at end of file \ No newline at end of file
import React, { useState, useCallback, useRef, useEffect, useContext } 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 { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table, Pagination, Space } from 'antd';
import { CheckOutlined, PlusOutlined, QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons'; import { CheckOutlined, PlusOutlined, QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { DndProvider, useDrag, useDrop } from 'react-dnd';
...@@ -14,9 +14,10 @@ import { dispatch, dispatchLatest } from '../../../../model'; ...@@ -14,9 +14,10 @@ import { dispatch, dispatchLatest } from '../../../../model';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help'; import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { AppContext } from '../../../../App'; import { AppContext } from '../../../../App';
import DebounceInput from './DebounceInput'; import DebounceInput from './DebounceInput';
import SuggestTable from './SuggestTable'; import Suggest from './suggest';
import { AttentionSvg, UnAttentionSvg } from './ModelSvg'; import { AttentionSvg, UnAttentionSvg } from './ModelSvg';
import { EditModelContext } from './ContextManage'; import { EditModelContext } from './ContextManage';
import { ValidateTip } from './ImportActionHeader';
import './ImportActionTable.less'; import './ImportActionTable.less';
import 'react-contexify/dist/ReactContexify.css'; import 'react-contexify/dist/ReactContexify.css';
...@@ -104,52 +105,41 @@ export const DatatypeInput = ({ value = {}, datatypes, onChange }) => { ...@@ -104,52 +105,41 @@ export const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
value = value ? value: {}; value = value ? value: {};
return ( return (
<> <div className='flex' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Row align='middle'> <div onClick={e => e.stopPropagation()} style={{ flex: 1 }}>
<Col span={9}> <Select
<span>名称:</span> onChange={onNameChange}
</Col> value={value.name || ''}
<Col span={15}> placeholder='请选择类型名称'
<span onClick={e => e.stopPropagation()}> style={{ width: '100%' }}
<Select >
onChange={onNameChange} {
value={value.name || ''} (datatypes||[]) && datatypes.map((_datatype, index) => {
placeholder='请选择类型名称' return (
> <Option key={_datatype.name||''}>{_datatype.name||''}</Option>
{ );
(datatypes||[]) && datatypes.map((_datatype, index) => { })
return ( }
<Option key={_datatype.name||''}>{_datatype.name||''}</Option> </Select>
); </div>
})
}
</Select>
</span>
</Col>
</Row>
{ {
(value.parameterCnNames||[]).map((parameterCnName, index) => { (value.parameterCnNames||[]).map((parameterCnName, index) => {
//使用InputNumber:当value改变时 InputNumber显示值没改变 但实际值有改变 是ant design的bug 这里使用只能输入数字的Input //使用InputNumber:当value改变时 InputNumber显示值没改变 但实际值有改变 是ant design的bug 这里使用只能输入数字的Input
return ( return (
<Row key={index} className='mt-2' align='middle'> <div key={index} className='ml-2' style={{ flex: 1 }}>
<Col span={9}> <Input
<span>{`${parameterCnName||''}:`}</span> onChange={(e) => {
</Col> onParameterValuesChange(e, index);
<Col span={15}> }}
<Input value={value.parameterValues[index] || ''}
onChange={(e) => { style={{ width: '100%' }}
onParameterValuesChange(e, index); placeholder={parameterCnName}
}} />
value={value.parameterValues[index] || ''} </div>
style={{ width: '100%' }}
placeholder='请输入一个整数'
/>
</Col>
</Row>
); );
}) })
} }
</> </div>
) )
} }
...@@ -162,12 +152,13 @@ export const EditableCell = ({ ...@@ -162,12 +152,13 @@ export const EditableCell = ({
index, index,
datatypes, datatypes,
require, require,
onPressEnter,
children, children,
...restProps ...restProps
}) => { }) => {
let editingComponent = null; let editingComponent = null;
if (editing) { if (editing) {
let inputNode = <InputDebounce />; let inputNode = <InputDebounce onPressEnter={() => { onPressEnter?.() }} />;
if (inputType === 'check') { if (inputType === 'check') {
inputNode = <Checkbox />; inputNode = <Checkbox />;
...@@ -266,11 +257,6 @@ export const ImportActionTable = (props) => { ...@@ -266,11 +257,6 @@ export const ImportActionTable = (props) => {
const [ form ] = Form.useForm(); const [ form ] = Form.useForm();
const [ editingKey, setEditingKey ] = useState(''); 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 [ englishSuggests, setEnglishSuggests ] = useState([]);
const [ keywordCondition, setKeywordCondition ] = useState({ keyword: '', needFilter: true }); const [ keywordCondition, setKeywordCondition ] = useState({ keyword: '', needFilter: true });
const { keyword, needFilter } = keywordCondition; const { keyword, needFilter } = keywordCondition;
...@@ -281,6 +267,12 @@ export const ImportActionTable = (props) => { ...@@ -281,6 +267,12 @@ export const ImportActionTable = (props) => {
const { pageNum, pageSize, filterData } = filterPageCondition; const { pageNum, pageSize, filterData } = filterPageCondition;
const [ insertIndex, setInsertIndex ] = useState(0); const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null); const [ currentItem, setCurrentItem ] = useState(null);
const [suggestParams, setSuggestParams] = useState({
visible: false,
name: undefined,
cnName: undefined,
triggerType: undefined,
})
const { attrIsEditingFunction } = useContext(EditModelContext); const { attrIsEditingFunction } = useContext(EditModelContext);
...@@ -289,6 +281,8 @@ export const ImportActionTable = (props) => { ...@@ -289,6 +281,8 @@ export const ImportActionTable = (props) => {
const termsRef = useRef(null); const termsRef = useRef(null);
const autoTranslateRef = useRef(false); const autoTranslateRef = useRef(false);
const app = useContext(AppContext)
const { show } = useContextMenu({ const { show } = useContextMenu({
id: MENU_ID, id: MENU_ID,
}); });
...@@ -298,7 +292,7 @@ export const ImportActionTable = (props) => { ...@@ -298,7 +292,7 @@ export const ImportActionTable = (props) => {
title: '序号', title: '序号',
dataIndex: 'key', dataIndex: 'key',
editable: false, editable: false,
width: 60, width: 44,
fixed: 'left', fixed: 'left',
render: (text, record, index) => { render: (text, record, index) => {
return (index+1).toString(); return (index+1).toString();
...@@ -312,13 +306,14 @@ export const ImportActionTable = (props) => { ...@@ -312,13 +306,14 @@ export const ImportActionTable = (props) => {
ellipsis: true, ellipsis: true,
require: true, require: true,
fixed: 'left', fixed: 'left',
render: (text, _, __) => { render: (text, record, __) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<span> <ValidateTip type='DataModelAttribute' propertyName='cnName' validateReports={validateReports} iid={record.iid} />
{highlightSearchContentByTerms(text, termsRef.current)} <Tooltip title={text||''}>
</span> <span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip> </Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -331,9 +326,12 @@ export const ImportActionTable = (props) => { ...@@ -331,9 +326,12 @@ export const ImportActionTable = (props) => {
require: true, require: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, termsRef.current)}</span> <ValidateTip type='DataModelAttribute' propertyName='name' validateReports={validateReports} iid={record.iid} />
</Tooltip> <Tooltip title={text||''}>
<span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -363,7 +361,7 @@ export const ImportActionTable = (props) => { ...@@ -363,7 +361,7 @@ export const ImportActionTable = (props) => {
}, },
{ {
title: 'Not Null', title: 'Not Null',
width: 80, width: 70,
dataIndex: 'notNull', dataIndex: 'notNull',
editable: true, editable: true,
render: (notNull, record, index) => { render: (notNull, record, index) => {
...@@ -380,7 +378,7 @@ export const ImportActionTable = (props) => { ...@@ -380,7 +378,7 @@ export const ImportActionTable = (props) => {
}, },
{ {
title: '主键', title: '主键',
width: 50, width: 44,
dataIndex: 'partOfPrimaryKeyLogically', dataIndex: 'partOfPrimaryKeyLogically',
editable: (type==='model'?true:false), editable: (type==='model'?true:false),
render: (partOfPrimaryKeyLogically, record, index) => { render: (partOfPrimaryKeyLogically, record, index) => {
...@@ -397,7 +395,7 @@ export const ImportActionTable = (props) => { ...@@ -397,7 +395,7 @@ export const ImportActionTable = (props) => {
}, },
{ {
title: '外键', title: '外键',
width: 50, width: 44,
dataIndex: 'foreignKey', dataIndex: 'foreignKey',
editable: (type==='model'?true:false), editable: (type==='model'?true:false),
render: (foreignKey, record, index) => { render: (foreignKey, record, index) => {
...@@ -412,23 +410,23 @@ export const ImportActionTable = (props) => { ...@@ -412,23 +410,23 @@ export const ImportActionTable = (props) => {
return ''; return '';
} }
}, },
// { {
// title: '重点关注', title: '重点关注',
// width: 75, width: 80,
// dataIndex: 'needAttention', dataIndex: 'needAttention',
// editable: (type==='model'?true:false), editable: false,
// render: (needAttention, record, index) => { render: (needAttention, record, index) => {
// if (!needAttention) { if (!needAttention) {
// return '-'; return '-';
// } else if (needAttention === true) { } else if (needAttention === true) {
// return ( return (
// <CheckOutlined /> <CheckOutlined />
// ) )
// } }
// return ''; return '';
// } }
// }, },
{ {
title: '业务含义', title: '业务含义',
dataIndex: 'remark', dataIndex: 'remark',
...@@ -436,13 +434,14 @@ export const ImportActionTable = (props) => { ...@@ -436,13 +434,14 @@ export const ImportActionTable = (props) => {
ellipsis: true, ellipsis: true,
require: true, require: true,
width: 200, width: 200,
render: (text, _, __) => { render: (text, record, __) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<Typography.Text ellipsis={true}> <ValidateTip type='DataModelAttribute' propertyName='remark' validateReports={validateReports} iid={record.iid} />
{highlightSearchContentByTerms(text, termsRef.current)} <Tooltip title={text||''}>
</Typography.Text> <span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip> </Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -468,13 +467,14 @@ export const ImportActionTable = (props) => { ...@@ -468,13 +467,14 @@ export const ImportActionTable = (props) => {
editable: true, editable: true,
ellipsis: true, ellipsis: true,
width: 200, width: 200,
render: (text, _, __) => { render: (text, record, __) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<Typography.Text ellipsis={true}> <ValidateTip type='DataModelAttribute' propertyName='definition' validateReports={validateReports} iid={record.iid} />
{highlightSearchContentByTerms(text, termsRef.current)} <Tooltip title={text||''}>
</Typography.Text> <span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip> </Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -484,13 +484,14 @@ export const ImportActionTable = (props) => { ...@@ -484,13 +484,14 @@ export const ImportActionTable = (props) => {
editable: true, editable: true,
ellipsis: true, ellipsis: true,
width: 100, width: 100,
render: (text, _, __) => { render: (text, record, __) => {
return ( return (
<Tooltip title={text||''}> <React.Fragment>
<Typography.Text ellipsis={true}> <ValidateTip type='DataModelAttribute' propertyName='valueRange' validateReports={validateReports} iid={record.iid} />
{highlightSearchContentByTerms(text, termsRef.current)} <Tooltip title={text||''}>
</Typography.Text> <span>{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip> </Tooltip>
</React.Fragment>
) )
} }
}, },
...@@ -559,6 +560,41 @@ export const ImportActionTable = (props) => { ...@@ -559,6 +560,41 @@ export const ImportActionTable = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports, editable, editingKey]) }, [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 isEditing = (record) => record?.iid === editingKey;
const onAddClick = (event) => { const onAddClick = (event) => {
...@@ -592,7 +628,7 @@ export const ImportActionTable = (props) => { ...@@ -592,7 +628,7 @@ export const ImportActionTable = (props) => {
}) })
} }
const insertToFront = (record) => { const onInsertToFrontClick = (record) => {
save().then(result => { save().then(result => {
if (result) { if (result) {
setKeywordCondition({ keyword: '', needFilter: false }); setKeywordCondition({ keyword: '', needFilter: false });
...@@ -623,7 +659,7 @@ export const ImportActionTable = (props) => { ...@@ -623,7 +659,7 @@ export const ImportActionTable = (props) => {
}) })
} }
const insertToBack = (record) => { const onInsertToBackClick = (record) => {
save().then(result => { save().then(result => {
if (result) { if (result) {
setKeywordCondition({ keyword: '', needFilter: false }); setKeywordCondition({ keyword: '', needFilter: false });
...@@ -685,7 +721,7 @@ export const ImportActionTable = (props) => { ...@@ -685,7 +721,7 @@ export const ImportActionTable = (props) => {
onChange && onChange(newData); onChange && onChange(newData);
} }
const remove = (record) => { const onRemoveClick = (record) => {
if (record.iid !== editingKey) { if (record.iid !== editingKey) {
save().then(result => { save().then(result => {
if (result) { if (result) {
...@@ -697,6 +733,50 @@ export const ImportActionTable = (props) => { ...@@ -697,6 +733,50 @@ export 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) => { const preSaveDataModel = (attribute) => {
dispatch({ dispatch({
type: 'datamodel.preSaveDataModel', type: 'datamodel.preSaveDataModel',
...@@ -776,7 +856,6 @@ export const ImportActionTable = (props) => { ...@@ -776,7 +856,6 @@ export const ImportActionTable = (props) => {
} }
setEditingKey(''); setEditingKey('');
setSuggests([]);
setEnglishSuggests([]); setEnglishSuggests([]);
} }
...@@ -797,94 +876,24 @@ export const ImportActionTable = (props) => { ...@@ -797,94 +876,24 @@ export const ImportActionTable = (props) => {
ownPrimaryKey = (modelerData.easyDataModelerPrimaryKey.filter(item => item.name===allValues.name).length>0); ownPrimaryKey = (modelerData.easyDataModelerPrimaryKey.filter(item => item.name===allValues.name).length>0);
} }
if (changedValues.hasOwnProperty('cnName') || changedValues.hasOwnProperty('name')) { if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslateRef.current) {
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({ dispatchLatest({
type: 'datamodel.suggest', type: 'datamodel.translatePhase',
payload: { payload: {
params: { params: {
name: allValues.name||'', phaseInChinese: changedValues.cnName,
cnName: allValues.cnName||'',
topN: (offset+perSuggestCount-1),
offset
} }
}, },
callback: data => { callback: data => {
setLoadingSuggest(false); if ((data?.translated||'') !== '') {
form.setFieldsValue({ name: data?.translated||'' });
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);
} }
}) })
} }
} else if (changedValues.hasOwnProperty('name')) {
if (changedValues.hasOwnProperty('cnName')) { autoTranslateRef.current(changedValues.name==='');
if (autoTranslateRef.current) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
params: {
phaseInChinese: changedValues.cnName,
}
},
callback: data => {
if ((data?.translated||'') !== '') {
form.setFieldsValue({ name: data?.translated||'' });
}
getSuggest();
}
})
} else {
getSuggest();
}
} else if (changedValues.hasOwnProperty('name')) {
autoTranslateRef.current(changedValues.name==='');
getSuggest();
}
} else if(changedValues.hasOwnProperty('notNull') ) { } else if(changedValues.hasOwnProperty('notNull') ) {
if (!changedValues.notNull && ownPrimaryKey) { if (!changedValues.notNull && ownPrimaryKey) {
showMessage('info', '主键不允许为空'); showMessage('info', '主键不允许为空');
...@@ -904,203 +913,29 @@ export const ImportActionTable = (props) => { ...@@ -904,203 +913,29 @@ export const ImportActionTable = (props) => {
form.setFieldsValue({ form.setFieldsValue({
...restRecord ...restRecord
}); });
setSuggests([]);
}; };
const editableColumn = [ const onColumnPressEnter = (title) => {
...cols, if (title === '中文名称') {
{ setSuggestParams({
title: '操作', visible: true,
dataIndex: 'action', name: form?.getFieldValue('name'),
width: (action==='flow')?220: ((originAction==='flow')?90:130), cnName: form?.getFieldValue('cnName'),
fixed: 'right', triggerType: 'cnName',
render: (_, record) => { })
return ( } else if (title === '英文名称') {
<AppContext.Consumer> setSuggestParams({
{ visible: true,
value => <React.Fragment> name: form?.getFieldValue('name'),
{ cnName: form?.getFieldValue('cnName'),
(action==='flow') && <React.Fragment> triggerType: 'name',
{ })
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, ...{ modelName: modelerData?.name, modelCnName: modelerData?.cnName }},
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 ? <AttentionSvg style={{ width: 15, height: 15 }} /> : <UnAttentionSvg style={{ width: 15, height: 15 }} />}
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>
);
})
}
</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 = [
...cols,
validateColumn
]
const includeValidateEditableColumn = [
...editableColumn,
validateColumn
]
const mergedColumns = () => { const mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='DataModelAttribute').length>0;
if (editable) { if (editable) {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn; const _columns = cols.map((col) => {
_columns = _columns.map((col) => {
if (!col.editable) { if (!col.editable) {
return col; return col;
} }
...@@ -1133,22 +968,17 @@ export const ImportActionTable = (props) => { ...@@ -1133,22 +968,17 @@ export const ImportActionTable = (props) => {
colTitle: col.title, colTitle: col.title,
editing: isEditing(record), editing: isEditing(record),
datatypes: supportedDatatypes, datatypes: supportedDatatypes,
require: col.require require: col.require,
onPressEnter: () => {
onColumnPressEnter(col.title)
}
}), }),
}; };
}); });
setColumns(_columns); setColumns(_columns);
} else { } else {
let _columns = hasValidateReports ? includeValidateColumn: cols; setColumns(cols);
if (action === 'flow') {
_columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
}
// _columns = _columns.filter((col) => col.dataIndex!=='needAttention');
setColumns(_columns);
} }
} }
...@@ -1186,20 +1016,6 @@ export const ImportActionTable = (props) => { ...@@ -1186,20 +1016,6 @@ export const ImportActionTable = (props) => {
setKeywordCondition({ keyword: value||'', needFilter: true }); 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) => { const displayMenu = (e) => {
show(e); show(e);
} }
...@@ -1208,9 +1024,17 @@ export const ImportActionTable = (props) => { ...@@ -1208,9 +1024,17 @@ export const ImportActionTable = (props) => {
const key = event.currentTarget.id; const key = event.currentTarget.id;
if (key === 'up') { if (key === 'up') {
insertToFront(currentItem); onInsertToFrontClick(currentItem);
} else if (key === 'down') { } 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);
} }
} }
...@@ -1239,10 +1063,10 @@ export const ImportActionTable = (props) => { ...@@ -1239,10 +1063,10 @@ export const ImportActionTable = (props) => {
} }
return ( 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' }}> <div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Space> <Space>
<h2 style={{ marginBottom: 0 }}>数据表结构</h2> <h3 style={{ marginBottom: 0 }}>数据表结构</h3>
{ {
editable && <Popover content={<span> editable && <Popover content={<span>
新增: 点击操作列的加号按钮在当前字段前新增一个字段<br /> 新增: 点击操作列的加号按钮在当前字段前新增一个字段<br />
...@@ -1252,7 +1076,8 @@ export const ImportActionTable = (props) => { ...@@ -1252,7 +1076,8 @@ export const ImportActionTable = (props) => {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;支持拖拽方式调整模型字段顺序<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;支持拖拽方式调整模型字段顺序<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;点击表格外部任意位置退出字段编辑状态<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;点击表格外部任意位置退出字段编辑状态<br />
删除: 点击操作列垃圾桶图标删除该字段<br /> 删除: 点击操作列垃圾桶图标删除该字段<br />
保存: 页面右下角保存按钮 保存: 页面右下角保存按钮<br />
中/英文输入框回车键触发推荐浮窗
</span>}> </span>}>
<QuestionCircleOutlined className='pointer' /> <QuestionCircleOutlined className='pointer' />
</Popover> </Popover>
...@@ -1307,19 +1132,30 @@ export const ImportActionTable = (props) => { ...@@ -1307,19 +1132,30 @@ export const ImportActionTable = (props) => {
return 'editable-row'; return 'editable-row';
}} }}
onRow={(record, index) => { onRow={(record, index) => {
let rowParams = { let rowParams = {
index, index,
id: `field-${record.iid}`, id: `field-${record.iid}`,
} }
let shouldRowContextMenu = false
if (action==='flow') {
if (record?.isPossibleNewRecommendedDefinition?.possible || record?.isPossibleNewTerm?.possible) {
shouldRowContextMenu = true
}
}
if (editable) { if (editable) {
shouldRowContextMenu = true
}
if (shouldRowContextMenu) {
rowParams = {...rowParams, onContextMenu: event => { rowParams = {...rowParams, onContextMenu: event => {
setCurrentItem(record); setCurrentItem(record);
displayMenu(event); displayMenu(event);
} }
}; };
}
if (editable) {
if (!isEditing(record)) { if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => { rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation(); event.stopPropagation();
...@@ -1350,34 +1186,6 @@ export const ImportActionTable = (props) => { ...@@ -1350,34 +1186,6 @@ export const ImportActionTable = (props) => {
//解决屏幕尺寸窄时,字段不好横向拖动的问题 //解决屏幕尺寸窄时,字段不好横向拖动的问题
y: tableWidth>1500?'100%':630, 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> </Form>
</DndProvider> </DndProvider>
...@@ -1402,13 +1210,29 @@ export const ImportActionTable = (props) => { ...@@ -1402,13 +1210,29 @@ export 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} > <RcMenu id={MENU_ID} >
<RcItem id="up" onClick={handleItemClick}> {
在上方插入行 (menuData??[]).map(item => (
</RcItem> <RcItem key={item.key} id={item.key} onClick={handleItemClick}>
<RcItem id="down" onClick={handleItemClick}> {item.title}
在下方插入行 </RcItem>
</RcItem> ))
}
</RcMenu> </RcMenu>
</div> </div>
......
...@@ -13,6 +13,7 @@ import TagCell from './tag-help'; ...@@ -13,6 +13,7 @@ import TagCell from './tag-help';
import './ModelTable.less'; import './ModelTable.less';
import 'react-contexify/dist/ReactContexify.css'; import 'react-contexify/dist/ReactContexify.css';
import produce from "immer";
const { Paragraph, Text } = Typography; const { Paragraph, Text } = Typography;
...@@ -134,7 +135,7 @@ const ModelNameColumn = (props) => { ...@@ -134,7 +135,7 @@ const ModelNameColumn = (props) => {
} }
const ModelTable = (props) => { const ModelTable = (props) => {
const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, view, modelState, user, selectModelerIds, visibleColNames } = props; const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, view, modelState, user, selectModelerIds, visibleColNames, tagSelectOptions, batchAddTagChange } = props;
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]); const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]); const [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]);
...@@ -144,6 +145,7 @@ const ModelTable = (props) => { ...@@ -144,6 +145,7 @@ const ModelTable = (props) => {
const expandedDataMapRef = useRef(new Map()); const expandedDataMapRef = useRef(new Map());
const shouldScrollRef = useRef(false); const shouldScrollRef = useRef(false);
const mountRef = React.useRef(false);
const anchorId = getQueryParam(AnchorId, props?.location?.search); const anchorId = getQueryParam(AnchorId, props?.location?.search);
const anchorTimestamp = getQueryParam(AnchorTimestamp, props?.location?.search); const anchorTimestamp = getQueryParam(AnchorTimestamp, props?.location?.search);
...@@ -266,7 +268,14 @@ const ModelTable = (props) => { ...@@ -266,7 +268,14 @@ const ModelTable = (props) => {
props.row?.state?.id === '4' ? <TagCell props.row?.state?.id === '4' ? <TagCell
id={props.row?.id} id={props.row?.id}
type='model' type='model'
tags={resoureTagMap?.[`${props.row?.id}`]} tags={resoureTagMap?.[props.row?.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[props.row?.id] = val
})
})
}}
/> : null /> : null
) )
} }
...@@ -318,6 +327,14 @@ const ModelTable = (props) => { ...@@ -318,6 +327,14 @@ const ModelTable = (props) => {
}, [data]) }, [data])
useEffect(() => { useEffect(() => {
if (!mountRef.current) {
mountRef.current = true;
} else {
getResourceTag();
}
}, [batchAddTagChange])
useEffect(() => {
window?.addEventListener("storage", modelEventChange); window?.addEventListener("storage", modelEventChange);
return () => { return () => {
window?.removeEventListener("storage", modelEventChange); window?.removeEventListener("storage", modelEventChange);
...@@ -360,6 +377,25 @@ const ModelTable = (props) => { ...@@ -360,6 +377,25 @@ const ModelTable = (props) => {
} }
}) })
const tableData = useMemo(() => {
if ((tagSelectOptions??[]).length === 0) {
return data
} else if (resoureTagMap) {
const ids = []
for (const key in resoureTagMap) {
for (const item of resoureTagMap[key]) {
const index = (tagSelectOptions??[]).filter(_item => item.tagId === _item.id)
if (index !== -1) {
ids.push(key)
break
}
}
}
return (data??[]).filter(item => ids.indexOf(item.id) !== -1)
}
}, [data, tagSelectOptions, resoureTagMap])
const getResourceTag = () => { const getResourceTag = () => {
const ids = (data??[]).map(item => item.id); const ids = (data??[]).map(item => item.id);
if (ids.length > 0) { if (ids.length > 0) {
...@@ -585,7 +621,7 @@ const ModelTable = (props) => { ...@@ -585,7 +621,7 @@ const ModelTable = (props) => {
<Paragraph style={{ overflow: 'hidden' }}> <Paragraph style={{ overflow: 'hidden' }}>
<Text className='title-color' ellipsis={true}> <Text className='title-color' ellipsis={true}>
总数: 总数:
<Text className='text-color'>{(data||[]).length}</Text> <Text className='text-color'>{(tableData||[]).length}</Text>
</Text> </Text>
</Paragraph> </Paragraph>
<Paragraph style={{ overflow: 'hidden', marginLeft: 20 }}> <Paragraph style={{ overflow: 'hidden', marginLeft: 20 }}>
...@@ -603,7 +639,7 @@ const ModelTable = (props) => { ...@@ -603,7 +639,7 @@ const ModelTable = (props) => {
// rows={Array.from({ length: 10000 }).map((_, i) => ({ // rows={Array.from({ length: 10000 }).map((_, i) => ({
// name: `test${i}`, // name: `test${i}`,
// }))} // }))}
rows={data||[]} rows={tableData||[]}
rowHeight={51} rowHeight={51}
rowClassName={(row) => { rowClassName={(row) => {
return (row.id === anchorId)?'anchor':'' return (row.id === anchorId)?'anchor':''
......
...@@ -34,7 +34,7 @@ const ModelTree = (props) => { ...@@ -34,7 +34,7 @@ const ModelTree = (props) => {
id: MENU_ID, id: MENU_ID,
}); });
const { onSelect, onViewChange, refrence='', importStockModel, keyword } = props; const { onSelect, onViewChange, refrence='', importStockModel, keyword, searchProperties } = props;
const { user } = useContext(AppContext); const { user } = useContext(AppContext);
const [ loading, setLoading ] = useState(false); const [ loading, setLoading ] = useState(false);
...@@ -89,7 +89,6 @@ const ModelTree = (props) => { ...@@ -89,7 +89,6 @@ const ModelTree = (props) => {
}, [timestamp]) }, [timestamp])
useEffect(() => { useEffect(() => {
if (keyword!=='') { if (keyword!=='') {
if (item && !prevItem) { if (item && !prevItem) {
setPrevItem(item); setPrevItem(item);
...@@ -107,6 +106,23 @@ const ModelTree = (props) => { ...@@ -107,6 +106,23 @@ const ModelTree = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [ keyword ]) }, [ keyword ])
useEffect(() => {
if ((searchProperties??[]).length > 0) {
if (item && !prevItem) {
setPrevItem(item);
}
setItem(null);
} else {
if (prevItem && !item) {
setItem(prevItem);
onSelect && onSelect(prevItem?.key||'');
setPrevItem(null);
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchProperties])
const haveStockImportPermission = useMemo(() => { const haveStockImportPermission = useMemo(() => {
return (item?.optionList||[]).findIndex(option => option.name==='存量模型导入'&&option.enabled) !== -1 return (item?.optionList||[]).findIndex(option => option.name==='存量模型导入'&&option.enabled) !== -1
}, [item]) }, [item])
......
import React, { useState } from 'react';
import { Table, Tooltip, Typography } from 'antd';
import { Resizable } from 'react-resizable';
import ResizeObserver from 'rc-resize-observer';
import { checkMenuAdmit, isSzseEnv } from '../../../../util';
import { dispatch } from '../../../../model';
import './SuggestTable.less';
const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...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
onClick={onClick}
{...restProps}
/>
</Resizable>
);
};
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>
);
}
const SuggestTable = (props) => {
const { suggests, onSelect } = props;
const [ tableWidth, setTableWidth ] = useState(0);
const cols = [
{
title: '中文名称',
dataIndex: 'cnName',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '英文名称',
dataIndex: 'name',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '业务含义',
dataIndex: 'remark',
width: isSzseEnv?360:160,
ellipsis: true,
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,
ellipsis: true,
render: (_, record) => {
return (
<span>{record.recommendedStats?.referencesCount}</span>
);
}
},
{
title: '来源',
dataIndex: 'source',
ellipsis: true,
render: (_, record) => {
return (
<SourceComponent data={record.recommendedStats?.sourceInfos||[]} name={record.name||''} onClick={sourceOnClick} />
);
}
},
];
const [ columns, setColumns ] = useState(cols);
const sourceOnClick = (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 onTableSelect = (record, selected, selectedRows, nativeEvent) => {
onSelect && onSelect(record);
}
const handleResize = index => (e, { size }) => {
const nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
setColumns(nextColumns);
};
const mergedColumns = () => {
return (
columns.map((column, index) => {
return {
...column,
onHeaderCell: column => ({
width: column.width,
onResize: handleResize(index),
}),
};
})
);
}
const rowSelection = {
type: 'radio',
onSelect: onTableSelect,
};
return (
<div className='suggest-table'>
<ResizeObserver
onResize={({ width }) => {
if (tableWidth !== width) {
setTableWidth(width);
let newColumns = [...cols];
newColumns.forEach((column, index) => {
if (!column.width) {
const rowWidth = (cols.reduce((preVal, col) => (col.width?col.width:0) + preVal, 0)) + 50;
if (width > rowWidth) {
column.width = (width-rowWidth)>200?(width-rowWidth):200;
} else {
column.width = 200;
}
}
});
setColumns(newColumns);
}
}}
>
<Table
rowSelection={rowSelection}
dataSource={suggests||[]}
pagination={false}
loading={false}
rowKey='iid'
rowClassName={(record, index) => {
return 'pointer';
}}
components={{
header: {
cell: ResizeableHeaderCell,
}
}}
columns={mergedColumns()}
onRow={(record, index) => {
return {
onClick: (e) => {
onSelect && onSelect(record);
}
}
}}
/>
</ResizeObserver>
</div>
);
}
export default SuggestTable;
\ No newline at end of file
.suggest-table {
.yy-table {
margin: 0 !important;
max-height: 300px !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { Form, Select, Spin, Tooltip, Checkbox, Typography } from 'antd'; import { Form, Select, Spin, Tooltip, Checkbox, Typography, Space, Button } from 'antd';
import { dispatch, dispatchLatest } from '../../../../model'; import { dispatch, dispatchLatest } from '../../../../model';
import { formatVersionDate } from '../../../../util'; import { formatVersionDate, showMessage } from '../../../../util';
import VersionCompareHeader from './VersionCompareHeader'; import VersionCompareHeader from './VersionCompareHeader';
import VersionCompareTable from './VersionCompareTable'; import VersionCompareTable from './VersionCompareTable';
import VersionCompareIndex from './VersionCompareIndex'; import VersionCompareIndex from './VersionCompareIndex';
import FilterColumnAction from './FilterColumnAction'; import FilterColumnAction from './FilterColumnAction';
import VersionDdlAlter from './version-ddl-alter';
import './VersionCompare.less'; import './VersionCompare.less';
...@@ -28,6 +29,13 @@ const VersionCompare = (props) => { ...@@ -28,6 +29,13 @@ const VersionCompare = (props) => {
const [ onlyShowChange, setOnlyShowChange ] = useState(true); const [ onlyShowChange, setOnlyShowChange ] = useState(true);
const [ attrFilterColumns, setAttrFilterColumns ] = useState([]); const [ attrFilterColumns, setAttrFilterColumns ] = useState([]);
const [ attrSelectedTitles, setAttrSelectedTitles ] = useState(defaultColumnTitles); const [ attrSelectedTitles, setAttrSelectedTitles ] = useState(defaultColumnTitles);
const [ddlAlterParams, setAlterParams] = useState({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
const attrColumnsRef = useRef([]); const attrColumnsRef = useRef([]);
...@@ -183,6 +191,16 @@ const VersionCompare = (props) => { ...@@ -183,6 +191,16 @@ const VersionCompare = (props) => {
setAttrFilterColumns(newFilterColumns); setAttrFilterColumns(newFilterColumns);
} }
const onAlterClick = () => {
setAlterParams({
visible: true,
id,
versions: basicVersions,
defaultBasicVersion: basicVersion,
defaultIncVersion: incVersion,
})
}
const onOnlyShowChange = (e) => { const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked); setOnlyShowChange(e.target.checked);
if (basicVersion!=='' && incVersion!=='') { if (basicVersion!=='' && incVersion!=='') {
...@@ -230,7 +248,10 @@ const VersionCompare = (props) => { ...@@ -230,7 +248,10 @@ const VersionCompare = (props) => {
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>仅显示差异</Checkbox> <Space>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>仅显示差异</Checkbox>
<Button onClick={onAlterClick}>生成ALTER</Button>
</Space>
</Form.Item> </Form.Item>
</Form> </Form>
...@@ -252,6 +273,18 @@ const VersionCompare = (props) => { ...@@ -252,6 +273,18 @@ const VersionCompare = (props) => {
} }
</Spin> </Spin>
</div> </div>
<VersionDdlAlter
{...ddlAlterParams}
onCancel={() => {
setAlterParams({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
}}
/>
</div> </div>
); );
} }
......
import React from 'react'
import { Modal, Button, Select, Space, Input, Tooltip } from 'antd'
import produce from 'immer'
import { dispatch } from '../../../../model'
import Table from '../../../../util/Component/Table'
import { showMessage } from '../../../../util'
const FC = (props) => {
const { visible, onCancel } = props
const basicRef = React.useRef()
const close = (searchProperties = null) => {
onCancel?.(searchProperties)
}
const save = () => {
const searchProperties = basicRef.current.searchProperties
close(searchProperties??[])
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
width={800}
visible={visible}
footer={footer}
bodyStyle={{ padding: '15px', overflowX: 'auto', minHeight: '60vh', maxHeight: '80vh' }}
centered
maskClosable={false}
closable={false}
onCancel={() => { close() }}
>
<Basic ref={basicRef} />
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({}, ref) {
const [loading, setLoading] = React.useState(false)
const [properties, setProperties] = React.useState()
const [currentProperty, setCurrentProperty] = React.useState()
const [currentPropertyMatchType, setCurrentPropertyMatchType] = React.useState()
const [currentPropertyMatchValue, setCurrentPropertyMatchValue] = React.useState()
const [tableData, setTableData] = React.useState()
React.useImperativeHandle(ref, () => ({
searchProperties: tableData,
}), [tableData])
React.useEffect(() => {
getProperties()
}, [])
const onDeleteClick = (index) => {
setTableData(prevTableData => {
const newTableData = [...prevTableData||[]]
newTableData.splice(index, 1);
return newTableData
})
}
const cols = React.useMemo(() => {
return ([
{
title: '筛选属性',
dataIndex: 'cnName',
},
{
title: '筛选方式',
dataIndex: 'matchTypeName',
},
{
title: '值',
dataIndex: 'valueName',
},
{
title: '操作',
dataIndex: 'action',
width: 80,
render: (_, __ ,index) => <a onClick={() => {onDeleteClick(index)}}>删除</a>
}
])
}, [onDeleteClick])
const getProperties = () => {
setLoading(true)
dispatch({
type: 'datamodel.getSearchProperties',
callback: data => {
setLoading(false)
setProperties(data)
if ((data??[]).length > 0) {
setCurrentProperty(data[0])
if ((data[0].supportedMatchTypes??[]).length > 0) {
setCurrentPropertyMatchType(data[0].supportedMatchTypes[0])
}
}
},
error: () => {
setLoading(false)
}
})
}
const onPropertyChange = (value) => {
const index = (properties??[]).findIndex(item => item.name === value)
if (index !== -1) {
setCurrentProperty(properties[index])
if ((properties[index].supportedMatchTypes??[]).length > 0) {
setCurrentPropertyMatchType(properties[index].supportedMatchTypes[0])
setCurrentPropertyMatchValue()
}
}
}
const onPropertyMatchTypeChange = (value) => {
const index = (currentProperty?.supportedMatchTypes??[]).findIndex(item => item.type === value)
if (index !== -1) {
setCurrentPropertyMatchType(currentProperty?.supportedMatchTypes[index])
setCurrentPropertyMatchValue()
}
}
const onPropertyMatchValueChange = (value) => {
setCurrentPropertyMatchValue(value)
}
const onAddClick = () => {
const newProperty = produce(currentProperty, (draft) => {
draft.matchType = currentPropertyMatchType?.type
draft.matchTypeName = currentPropertyMatchType?.name
draft.value = currentPropertyMatchValue
draft.valueName = currentPropertyMatchValue
if (draft.valueType === 'List<String>') {
draft.valueName = (draft.selectItems??[]).filter(item => (currentPropertyMatchValue??[]).indexOf(item.id) !== -1).map(item => item.name).toString()
}
})
setTableData(prevTableData => {
const newTableData = [...prevTableData||[]]
const index = newTableData.findIndex(item => item.name === newProperty.name)
if (index === -1) {
newTableData.push(newProperty)
} else {
newTableData.splice(index, 1, newProperty);
}
return newTableData
})
}
const onClearClick = () => {
setTableData()
}
return (
<div>
<div className='flex' style={{ justifyContent: 'space-between' }}>
<Space>
<Select loading={loading}
value={currentProperty?.name}
onChange={onPropertyChange}
placeholder='请选择筛选属性'
style={{ width: 120 }}
>
{
(properties??[]).map((item, index) => (
<Select.Option key={index} value={item.name}>{item.cnName}</Select.Option>
))
}
</Select>
<Select
value={currentPropertyMatchType?.type}
onChange={onPropertyMatchTypeChange}
placeholder='请选择筛选方式'
style={{ width: 120 }}
>
{
(currentProperty?.supportedMatchTypes??[]).map((item, index) => (
<Select.Option key={index} value={item.type}>{item.name}</Select.Option>
))
}
</Select>
{
(currentProperty?.valueType === 'List<String>') && <Select mode='multiple' value={currentPropertyMatchValue}
onChange={(value) => {
onPropertyMatchValueChange(value)
}}
placeholder='请选择搜索值'
style={{ width: 200 }}
maxTagCount='responsive'
>
{
(currentProperty?.selectItems??[]).map((item, index) => (
<Select.Option key={index} value={item.id}>{item.name}</Select.Option>
))
}
</Select>
}
{
(currentProperty?.valueType === 'String') && <Input
value={currentPropertyMatchValue}
placeholder='搜索值'
onChange={(e) => {
onPropertyMatchValueChange(e.target.value)
}}
style={{ width: 200 }}
/>
}
<Tooltip title={currentPropertyMatchValue?'':'请先输入搜索值'}>
<Button onClick={onAddClick} disabled={!currentPropertyMatchValue}>添加</Button>
</Tooltip>
</Space>
<Space>
<a onClick={onClearClick}>清空筛选</a>
</Space>
</div>
<div className='mt-3'>
<Table columns={cols} dataSource={tableData??[]} pagination={false} />
</div>
</div>
)
})
\ No newline at end of file
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
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Tooltip, Tag, Typography, Space, Button, Modal, Spin } from 'antd' import { Tooltip, Tag, Typography, Space, Button, Modal, Spin, Select, } from 'antd'
import { import {
EllipsisOutlined, EllipsisOutlined,
PlusOutlined, PlusOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import produce from 'immer'
import DataQuality, { DataQualityFeighTagSelect } from '../../../QianKun/data-quality' import DataQuality, { DataQualityFeighTagSelect } from '../../../QianKun/data-quality'
import { showMessage } from '../../../../util' import { showMessage } from '../../../../util'
import { dispatch } from '../../../../model' import { dispatch } from '../../../../model'
import { AppContext } from '../../../../App' import { AppContext } from '../../../../App'
const FC = ({ type, id, did, tags }) => { const FC = ({ type, id, did, tags, onChange }) => {
const [data, setData] = React.useState([]) const [data, setData] = React.useState([])
const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({ const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({
visible: false, visible: false,
...@@ -46,6 +48,7 @@ const FC = ({ type, id, did, tags }) => { ...@@ -46,6 +48,7 @@ const FC = ({ type, id, did, tags }) => {
}, },
callback: data => { callback: data => {
setData(data?.data?.[`${id}`]); setData(data?.data?.[`${id}`]);
onChange?.(data?.data?.[`${id}`]);
} }
}); });
} }
...@@ -169,7 +172,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) { ...@@ -169,7 +172,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) {
}}> }}>
{ {
_value?.map((item, index) => <TagRender _value?.map((item, index) => <TagRender
key={index} key={item.id}
data={item} data={item}
closable={!readonly&&item?.deleteAble} closable={!readonly&&item?.deleteAble}
onClose={(e) => { onClose={(e) => {
...@@ -183,7 +186,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) { ...@@ -183,7 +186,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) {
<Space wrap={true}> <Space wrap={true}>
{ {
value?.map((item, index) => <TagRender value?.map((item, index) => <TagRender
key={index} key={item.id}
data={item} data={item}
closable={!readonly&&item?.deleteAble} closable={!readonly&&item?.deleteAble}
onClose={(e) => { onClose={(e) => {
...@@ -214,7 +217,72 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) { ...@@ -214,7 +217,72 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) {
) )
} }
export function TagSelectPopup({ visible, type, items, onCancel, onChange }) { export function TagSelect({ options, onChange }) {
const [popupParams, setPopupParams] = React.useState({
visible: false,
value: undefined
})
return (
<React.Fragment>
<Select
mode='multiple'
placeholder='请选择标签'
tagRender={(props) => {
const { value } = props
const index = (options??[]).findIndex(item => item.value === value)
if (index !== -1) {
return <TagRender key={value} data={options[index]} {...props} />
}
return <React.Fragment></React.Fragment>
}}
onDropdownVisibleChange={(open) => {
if (open) {
setPopupParams({
visible: true,
value: options
})
}
}}
open={false}
style={{ width: 200 }}
value={(options??[]).map(item => item.value)}
options={options}
allowClear
onChange={(newValue) => {
const newOptions = (options??[]).filter(item => newValue.indexOf(item.value) !== -1)
onChange?.(newOptions)
}}
maxTagCount='responsive'
/>
<TagSelectPopup
value={options}
onCancel={(value) => {
setPopupParams({
visible: false,
value: undefined
})
}}
onChange={(value) => {
const newOptions = produce(value??[], (draft) => {
draft?.forEach(item => {
item.value = item.id
item.label = item.name
})
})
onChange?.(newOptions)
}}
{...popupParams}
/>
</React.Fragment>
)
}
export function TagSelectPopup({ visible, type, value, items, onCancel, onChange, }) {
const [waiting, setWaiting] = React.useState(false) const [waiting, setWaiting] = React.useState(false)
const [selectedRows, setSelectedRows] = React.useState([]) const [selectedRows, setSelectedRows] = React.useState([])
const [showDataQuality, setShowDataQuality] = React.useState(false) const [showDataQuality, setShowDataQuality] = React.useState(false)
...@@ -241,6 +309,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) { ...@@ -241,6 +309,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
showMessage('warn', '请先选择标签') showMessage('warn', '请先选择标签')
return return
} }
setWaiting(true) setWaiting(true)
dispatch({ dispatch({
type: 'tag.addTags', type: 'tag.addTags',
...@@ -251,7 +320,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) { ...@@ -251,7 +320,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
type, type,
catalogId: (items??[]).length>0?items[0].resourceCatalogId:'', catalogId: (items??[]).length>0?items[0].resourceCatalogId:'',
creator: app?.user?.userName, creator: app?.user?.userName,
overrideAll: true, overrideAll: (items??[]).length === 1 ? true : false,
} }
}, },
callback: () => { callback: () => {
...@@ -296,7 +365,8 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) { ...@@ -296,7 +365,8 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
type={DataQualityFeighTagSelect} type={DataQualityFeighTagSelect}
data={{ data={{
type, type,
items items,
value,
}} }}
onChange={(val) => { onChange={(val) => {
setSelectedRows(val) setSelectedRows(val)
......
import React from "react"
import { Modal, Spin, Button, Form, Select, Tooltip, Input } from "antd"
import { dispatch } from '../../../../model'
const FC = (props) => {
const { visible, id, versions, defaultBasicVersion, defaultIncVersion, onCancel } = props
const basicRef = React.useRef()
const close = () => {
onCancel?.()
}
return (
<Modal
visible={visible}
footer={null}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='生成ALTER'
centered destroyOnClose
onCancel={() => { close() }}
>
<Basic ref={basicRef} id={id} versions={versions} defaultBasicVersion={defaultBasicVersion} defaultIncVersion={defaultIncVersion} />
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ id, versions, defaultBasicVersion, defaultIncVersion }, ref) {
const [basicVersion, setBasicVersion] = React.useState(defaultBasicVersion)
const [incVersion, setIncVersion] = React.useState(defaultIncVersion)
const [loadingDDLGenerators, setLoadingGenerators] = React.useState(false)
const [ddlGenerators, setGenerators] = React.useState()
const [currentGeneratorValue, setGeneratorValue] = React.useState()
const [loading, setLoading] = React.useState(false)
const [ddl, setDDL] = React.useState()
React.useEffect(() => {
getDDLGenerators()
}, [])
React.useEffect(() => {
if (basicVersion && incVersion && currentGeneratorValue) {
getDDL()
} else {
setDDL()
}
}, [basicVersion, incVersion, currentGeneratorValue])
const incVersions = React.useMemo(() => {
const index = (versions??[]).findIndex(item => item.id === basicVersion)
if (index !== -1) {
return (versions??[]).slice(0, index)
}
return []
}, [versions, basicVersion])
const getDDLGenerators = () => {
setLoadingGenerators(true)
dispatch({
type: 'datamodel.ddlGenerators',
callback: data => {
setLoadingGenerators(false)
setGenerators(data)
if ((data??[]).length > 0) {
setGeneratorValue(data[0].name)
}
},
error: () => {
setLoadingGenerators(false)
}
})
}
const getDDL = () => {
setLoading(true)
dispatch({
type: 'datamodel.ddlChangeString',
payload: {
easyDataModelerDataModelId: id,
leftVersionId: basicVersion,
rightVersionId: incVersion,
ddlGeneratorName: currentGeneratorValue,
},
callback: data => {
setLoading(false)
setDDL(data)
},
error: () => {
setLoading(false)
}
})
}
const onBasicChange = (value) => {
setBasicVersion(value)
setIncVersion()
}
const onIncChange = (value) => {
setIncVersion(value)
}
const onGeneratorChange = (value) => {
setGeneratorValue(value)
}
return (
<div>
<Form layout='inline'>
<Form.Item label='基线版本'>
<Select value={basicVersion} style={{ width: 300 }} onChange={onBasicChange} >
{
(versions??[]).map((version, index) => (
<Select.Option key={index} value={version.id} disabled={index === 0}>
<Tooltip title={index===0?'最近版本只能在增量版本中被选中':''}>
{version.name}
</Tooltip>
</Select.Option>
))
}
</Select>
</Form.Item>
<Form.Item label='增量版本'>
<Select value={incVersion} style={{ width: 300 }} disabled={basicVersion===''} onChange={onIncChange}>
{
(incVersions||[]).map((version, index) => {
return (
<Select.Option key={index} value={version.id}>{version.name}</Select.Option>
);
})
}
</Select>
</Form.Item>
<Form.Item label='数据库类型'>
<Select loading={loadingDDLGenerators} value={currentGeneratorValue} style={{ width: 150 }} onChange={onGeneratorChange}>
{
(ddlGenerators??[]).map((item, index) => (
<Select.Option key={index} value={item.name}>{item.cnName}</Select.Option>
))
}
</Select>
</Form.Item>
</Form>
<div className='mt-5'>
<Spin spinning={loading}>
<Input.TextArea value={ddl} autoSize={{minRows: 4,maxRows: 20}} />
</Spin>
</div>
</div>
)
})
\ No newline at end of file
...@@ -21,6 +21,8 @@ import { AppContext } from '../../../App'; ...@@ -21,6 +21,8 @@ import { AppContext } from '../../../App';
import DebounceInput from './Component/DebounceInput'; import DebounceInput from './Component/DebounceInput';
import ColSettingModal from './Component/ColSettingModal'; import ColSettingModal from './Component/ColSettingModal';
import PermissionButton from '../../../util/Component/PermissionButton'; import PermissionButton from '../../../util/Component/PermissionButton';
import SelectSearchProperties from './Component/select-search-properties';
import { TagSelect, TagSelectPopup } from './Component/tag-help';
import './index.less'; import './index.less';
...@@ -63,8 +65,17 @@ class Model extends React.Component { ...@@ -63,8 +65,17 @@ class Model extends React.Component {
canExport: false, canExport: false,
canStartFlow: false, canStartFlow: false,
canChangeCatalog: false, canChangeCatalog: false,
canBatchAddTag: false,
canDelete: false, canDelete: false,
permissions: [], permissions: [],
selectSearchPropertiesVisible: false,
searchProperties: [],
tagSelectOptions: [],
tagSelectPopupParams: {
visible: false,
items: undefined
},
batchAddTagChange: false,
} }
} }
...@@ -79,7 +90,7 @@ class Model extends React.Component { ...@@ -79,7 +90,7 @@ class Model extends React.Component {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { selectModelerIds, tableData, catalogId, permissions } = this.state; const { selectModelerIds, tableData, catalogId, permissions } = this.state;
if (selectModelerIds !== prevState.selectModelerIds || tableData !== prevState.tableData) { if (selectModelerIds !== prevState.selectModelerIds || tableData !== prevState.tableData) {
let canExport = true, canStartFlow = true, canChangeCatalog = true, canDelete = true; let canExport = true, canStartFlow = true, canChangeCatalog = true, canDelete = true, canBatchAddTag = true;
selectModelerIds?.forEach(id => { selectModelerIds?.forEach(id => {
const index = (tableData||[]).findIndex(item => item.id?.split('-')[0] === id?.split('-')[0]); const index = (tableData||[]).findIndex(item => item.id?.split('-')[0] === id?.split('-')[0]);
if (index !== -1) { if (index !== -1) {
...@@ -95,6 +106,10 @@ class Model extends React.Component { ...@@ -95,6 +106,10 @@ class Model extends React.Component {
} }
}); });
} }
if ((id??'').indexOf('-4') === -1) {
canBatchAddTag = false;
}
}); });
this.setState({ this.setState({
...@@ -102,8 +117,11 @@ class Model extends React.Component { ...@@ -102,8 +117,11 @@ class Model extends React.Component {
canStartFlow, canStartFlow,
canChangeCatalog, canChangeCatalog,
canDelete, canDelete,
canBatchAddTag
}); });
} else if (catalogId !== prevState.catalogId || permissions !== prevState.permissions) { }
if (catalogId !== prevState.catalogId || permissions !== prevState.permissions) {
let canAdd = true; let canAdd = true;
const index = (permissions||[]).findIndex(item => item.privilegedObjectId === catalogId); const index = (permissions||[]).findIndex(item => item.privilegedObjectId === catalogId);
if (index !== -1) { if (index !== -1) {
...@@ -194,10 +212,42 @@ class Model extends React.Component { ...@@ -194,10 +212,42 @@ class Model extends React.Component {
} }
onTableChange = () => { onTableChange = () => {
const { currentView, catalogId, keyword, currentModelState } = this.state; const { currentView, catalogId, keyword, currentModelState, searchProperties } = this.state;
this.setState({ loadingTableData: true }, () => { this.setState({ loadingTableData: true }, () => {
if (keyword === '') { if (keyword) {
const params = {
term: keyword,
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'datamodel.searchModel',
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: data||[] });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
} else if (!catalogId) {
dispatch({
type: 'datamodel.searchModelBySearchProperties',
payload: {
data: searchProperties
},
callback: data => {
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: data||[] });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
} else {
if (currentView === 'dir') { if (currentView === 'dir') {
const params = { const params = {
...@@ -232,28 +282,7 @@ class Model extends React.Component { ...@@ -232,28 +282,7 @@ class Model extends React.Component {
} }
}) })
} }
} else {
const params = {
term: keyword,
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'datamodel.searchModel',
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: data||[] });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
} }
}) })
} }
...@@ -281,6 +310,21 @@ class Model extends React.Component { ...@@ -281,6 +310,21 @@ class Model extends React.Component {
}); });
} }
onSearchPropertiesClick = () => {
this.setState({ selectSearchPropertiesVisible: true })
}
onSearchPropertiesCancel = (searchProperties) => {
this.setState({ selectSearchPropertiesVisible: false })
if (searchProperties) {
this.setState({ keyword: '', catalogId: '', searchProperties }, () => {
if (searchProperties.length !== 0) {
this.onTableChange()
}
})
}
}
onImportUnconditionBtnClick = () => { onImportUnconditionBtnClick = () => {
const { catalogId, currentView } = this.state; const { catalogId, currentView } = this.state;
...@@ -453,6 +497,8 @@ class Model extends React.Component { ...@@ -453,6 +497,8 @@ class Model extends React.Component {
window.open(`/api/datamodeler/easyDataModelerExport/excel?ids=${selectModelerIds.join(',')}`); window.open(`/api/datamodeler/easyDataModelerExport/excel?ids=${selectModelerIds.join(',')}`);
} else if (key === 'word') { } else if (key === 'word') {
window.open(`/api/datamodeler/easyDataModelerExport/word/template?ids=${selectModelerIds.join(',')}`); window.open(`/api/datamodeler/easyDataModelerExport/word/template?ids=${selectModelerIds.join(',')}`);
} else if (key === 'basicExcel') {
window.open(`/api/datamodeler/easyDataModelerExport/modelBaseDataExcel?ids=${selectModelerIds.join(',')}`);
} }
}); });
} }
...@@ -552,7 +598,7 @@ class Model extends React.Component { ...@@ -552,7 +598,7 @@ class Model extends React.Component {
axis='x' axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]} minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
> >
<ModelTree onViewChange={this.onViewChange} onSelect={this.onTreeSelect} importStockModel={this.importStockModel} keyword={keyword} {...this.props} /> <ModelTree onViewChange={this.onViewChange} onSelect={this.onTreeSelect} importStockModel={this.importStockModel} keyword={keyword} searchProperties={this.state.searchProperties} {...this.props} />
</ResizableBox> </ResizableBox>
<div className='tree-toggle-wrap'> <div className='tree-toggle-wrap'>
<div className='tree-toggle' onClick={this.treeToggleClick}> <div className='tree-toggle' onClick={this.treeToggleClick}>
...@@ -569,96 +615,83 @@ class Model extends React.Component { ...@@ -569,96 +615,83 @@ class Model extends React.Component {
}} }}
> >
<Space> <Space>
<Space> <PermissionButton
<PermissionButton defaultPermission={canAdd}
defaultPermission={canAdd} onClick={() => { this.setState({ importModalVisible: true }); }}
onClick={() => { this.setState({ importModalVisible: true }); }} >
> 新建
新建 </PermissionButton>
</PermissionButton>
</Space> <PermissionButton
defaultPermission={canExport}
<Space> tip={(selectModelerIds||[]).length===0?'请先选择模型':''}
<PermissionButton onClick={this.onExportOtherBtnClick}
defaultPermission={canExport} disabled={(selectModelerIds||[]).length===0}
tip={(selectModelerIds||[]).length===0?'请先选择模型':''} >
onClick={this.onExportOtherBtnClick} 导出
disabled={(selectModelerIds||[]).length===0} </PermissionButton>
>
导出 <PermissionButton
</PermissionButton> defaultPermission={canStartFlow}
</Space> tip={startFlowTip}
onClick={this.startFlow}
<Space> disabled={disableStartFlow}
<PermissionButton >
defaultPermission={canStartFlow} 送审
tip={startFlowTip} </PermissionButton>
onClick={this.startFlow}
disabled={disableStartFlow} <PermissionButton
> defaultPermission={canChangeCatalog}
送审 tip={(selectModelerIds||[]).length===0?'请先选择模型':''}
</PermissionButton> onClick={this.onRecatalogBtnClick}
</Space> disabled={(selectModelerIds||[]).length===0}
>
<Space> 变更目录
<PermissionButton </PermissionButton>
defaultPermission={canChangeCatalog}
tip={(selectModelerIds||[]).length===0?'请先选择模型':''}
onClick={this.onRecatalogBtnClick}
disabled={(selectModelerIds||[]).length===0}
>
变更目录
</PermissionButton>
</Space>
<Space> <PermissionButton
<Tooltip title={canDelete?((selectModelerIds||[]).length===0?'请先选择模型':''):'暂无权限'}> defaultPermission={canDelete}
<PermissionButton tip={(selectModelerIds||[]).length===0?'请先选择模型':''}
defaultPermission={canDelete} onClick={this.onBatchDeleteBtnClick}
tip={(selectModelerIds||[]).length===0?'请先选择模型':''} disabled={(selectModelerIds||[]).length===0}
onClick={this.onBatchDeleteBtnClick} >
disabled={(selectModelerIds||[]).length===0} 删除
> </PermissionButton>
删除
</PermissionButton> <Tooltip title={this.state.canBatchAddTag?((selectModelerIds||[]).length===0?'请先选择已发布的模型':''):'只有已发布的模型才能打标签'}>
</Tooltip> <Button
</Space> onClick={() => {
<Space> this.setState({
<Button onClick={this.onVisibleColSettingClick}>可见列设置</Button> tagSelectPopupParams: {
</Space> visible: true,
</Space> items: (selectModelerIds??[]).map(item => ({ resourceId: item }))
}
<Space>
{
(currentView==='dir'||keyword!=='') && <Space>
<Select
style={{ width: 120 }}
onChange={(value) => {
this.onModelStateChange(value);
}}
loading={loadingStates}
value={loadingStates? '': currentModelState}
>
{
(modelStates||[]).map(item => {
return (
<Option key={item.id} value={item.id}>{item.cnName||''}</Option>
);
}) })
} }}
</Select> disabled={(selectModelerIds||[]).length===0||!this.state.canBatchAddTag}
</Space> >
} 添加标签
<Space> </Button>
<InputDebounce </Tooltip>
placeholder="通过模型名称全文搜索"
allowClear <Button onClick={this.onVisibleColSettingClick}>可见列设置</Button>
value={keyword} </Space>
onChange={(value) => { this.onSearchInputChange(value); }}
style={{ width: inputWidth, marginLeft: 'auto' }}
/>
</Space>
<Space>
<TagSelect
options={this.state.tagSelectOptions}
onChange={(val) => {
this.setState({ tagSelectOptions: val })
}}
/>
<InputDebounce
placeholder="通过模型名称全文搜索"
allowClear
value={keyword}
onChange={(value) => { this.onSearchInputChange(value); }}
style={{ width: inputWidth, marginLeft: 'auto' }}
/>
<Button onClick={this.onSearchPropertiesClick}>筛选</Button>
</Space> </Space>
</div> </div>
...@@ -670,6 +703,8 @@ class Model extends React.Component { ...@@ -670,6 +703,8 @@ class Model extends React.Component {
catalogId={catalogId} catalogId={catalogId}
view={currentView} view={currentView}
data={filterTableData} data={filterTableData}
tagSelectOptions={this.state.tagSelectOptions}
batchAddTagChange={this.state.batchAddTagChange}
modelState={currentModelState} modelState={currentModelState}
offset={offset} offset={offset}
keyword={keyword} keyword={keyword}
...@@ -743,6 +778,29 @@ class Model extends React.Component { ...@@ -743,6 +778,29 @@ class Model extends React.Component {
visible={colSettingModalVisible} visible={colSettingModalVisible}
onCancel={this.onColSettingModalCancel} onCancel={this.onColSettingModalCancel}
/> />
<SelectSearchProperties
visible={this.state.selectSearchPropertiesVisible}
onCancel={this.onSearchPropertiesCancel}
/>
<TagSelectPopup
{...this.state.tagSelectPopupParams}
type='model'
onCancel={() => {
this.setState({
tagSelectPopupParams: {
visible: false,
items: undefined
}
})
}}
onChange={() => {
this.setState({
batchAddTagChange: !this.state.batchAddTagChange
})
}}
/>
</div> </div>
} }
</AppContext.Consumer> </AppContext.Consumer>
......
...@@ -104,13 +104,15 @@ const TemplateAction = (props) => { ...@@ -104,13 +104,15 @@ const TemplateAction = (props) => {
return ( return (
<Spin spinning={loading}> <Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}> <Tabs centered activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'> <TabPane tab='基本信息' key='1'>
<TemplateActionHeader <div style={{ padding: '0px 20px 0' }}>
form={form} <TemplateActionHeader
editable={action!=='detail'} form={form}
templateData={templateData||{}} editable={action!=='detail'}
/> templateData={templateData||{}}
/>
</div>
</TabPane> </TabPane>
<TabPane <TabPane
tab={ tab={
...@@ -127,20 +129,22 @@ const TemplateAction = (props) => { ...@@ -127,20 +129,22 @@ const TemplateAction = (props) => {
} }
key='2' key='2'
> >
<ImportActionTable <div style={{ height: (action!=='detail')?'calc(100vh - 44px - 64px - 150px)':'calc(100vh - 44px - 64px - 86px)', overflow: 'auto', padding: '0px 20px 0' }}>
type='template' <ImportActionTable
modelerData={templateData||{}} type='template'
supportedDatatypes={supportedDatatypes} modelerData={templateData||{}}
onChange={onTableChange} supportedDatatypes={supportedDatatypes}
editable={action!=='detail'} onChange={onTableChange}
/> editable={action!=='detail'}
/>
</div>
</TabPane> </TabPane>
</Tabs> </Tabs>
{ {
action!=='detail'&&( action!=='detail'&&(
<React.Fragment> <React.Fragment>
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<div className='flex' style={{ justifyContent: 'flex-end', height: 64, alignItems: 'center' }}> <div className='flex' style={{ justifyContent: 'flex-end', height: 64, alignItems: 'center', paddingRight: '20px' }}>
<Space size='small'> <Space size='small'>
<Button type='primary' onClick={prevStep} disabled={tabKey==='1'} danger>上一步</Button> <Button type='primary' onClick={prevStep} disabled={tabKey==='1'} danger>上一步</Button>
<Button type='primary' onClick={nextStep} disabled={tabKey==='2'} danger>下一步</Button> <Button type='primary' onClick={nextStep} disabled={tabKey==='2'} danger>下一步</Button>
......
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 [keyword, setKeywrod] = React.useState()
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={keyword}
bordered={true} allowClear
onChange={(e) => {
setKeywrod(e.target.value)
setArgs({ ...args, keyword: (e.target.value??'').trim() })
}}
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 React from 'react'
import { Input, Space, Modal } from 'antd' import { Input, Space, Modal, Divider, Tooltip, Typography } from 'antd'
import { dispatch } from '../../../../model' import { dispatch } from '../../../../model'
import PermissionButton from '../../../../util/Component/PermissionButton' import PermissionButton from '../../../../util/Component/PermissionButton'
...@@ -35,23 +35,101 @@ const FC = (props) => { ...@@ -35,23 +35,101 @@ const FC = (props) => {
{ {
title: '规则编号', title: '规则编号',
dataIndex: 'number', dataIndex: 'number',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
}, },
{ {
title: '规则中文名称', title: '规则中文名称',
dataIndex: 'name', 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: '规则描述', title: '规则描述',
dataIndex: 'remark', dataIndex: 'remark',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
}, },
{ {
title: '检查类型', title: '检查类型',
dataIndex: 'checkTypeName', dataIndex: 'checkTypeName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
}, },
{ {
title: '引用次数', title: '引用次数',
dataIndex: 'referenceCount', 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>
)
}
}
]) ])
}, []) }, [])
...@@ -126,7 +204,7 @@ const FC = (props) => { ...@@ -126,7 +204,7 @@ const FC = (props) => {
return ( return (
<div> <div>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}> <div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Space> <Space>
<PermissionButton <PermissionButton
onClick={onAddClick} onClick={onAddClick}
...@@ -157,6 +235,7 @@ const FC = (props) => { ...@@ -157,6 +235,7 @@ const FC = (props) => {
/> />
</div> </div>
<Table <Table
extraColWidth={32}
loading={loading} loading={loading}
columns={cols??[]} columns={cols??[]}
dataSource={_data} dataSource={_data}
......
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 React from "react"
import { Modal, Button, Spin, Form, Input, Select, Space, InputNumber } from "antd" import { Modal, Button, Spin, Form, Input, Select, Space, InputNumber, Row, Col } from "antd"
import { dispatch } from '../../../../model' import { dispatch } from '../../../../model'
import { IsArr } from "../../../../util"
const FC = (props) => { const FC = (props) => {
const { visible, type, item, onCancel } = props const { visible, type, item, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false) const [waiting, setWaiting] = React.useState(false)
const [data, setData] = React.useState()
const basicRef = React.useRef() const basicRef = React.useRef()
React.useEffect(() => {
if (item?.id) {
getDetail()
}
}, [item])
const title = React.useMemo(() => { const title = React.useMemo(() => {
if (type === 'add') return '新增检查规则' if (type === 'add') return '新增检查规则'
if (type === 'update') return '修改检查规则' if (type === 'edit') return '修改检查规则'
if (type === 'detail') return '检查规则详情' if (type === 'detail') return '检查规则详情'
return '' return ''
}, [type]) }, [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) => { const close = (refresh = false) => {
setLoading(false)
setWaiting(false) setWaiting(false)
setData()
onCancel?.(refresh) onCancel?.(refresh)
} }
...@@ -43,7 +71,7 @@ const FC = (props) => { ...@@ -43,7 +71,7 @@ const FC = (props) => {
dispatch({ dispatch({
type: 'datamodel.updateRuleTemplate', type: 'datamodel.updateRuleTemplate',
payload: { payload: {
data: {...item, rows} data: {...item, preCheckProperty: null, ...rows}
}, },
callback: data => { callback: data => {
close(true) close(true)
...@@ -73,15 +101,15 @@ const FC = (props) => { ...@@ -73,15 +101,15 @@ const FC = (props) => {
return ( return (
<Modal <Modal
visible={visible} visible={visible}
footer={footer} footer={type!=='detail'?footer:null}
width='80%' width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }} bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title={title} title={title}
centered destroyOnClose centered destroyOnClose
onCancel={() => { close() }} onCancel={() => { close() }}
> >
<Spin spinning={waiting}> <Spin spinning={waiting||loading}>
<Basic ref={basicRef} item={item} /> <Basic ref={basicRef} type={type} item={data} />
</Spin> </Spin>
</Modal> </Modal>
) )
...@@ -89,21 +117,17 @@ const FC = (props) => { ...@@ -89,21 +117,17 @@ const FC = (props) => {
export default FC export default FC
export const Basic = React.forwardRef(function ({ item }, ref) { export const Basic = React.forwardRef(function ({ type, item }, ref) {
const [loadingCheckTypes, setLoadingCheckTypes] = React.useState(false) const [loadingCheckTypes, setLoadingCheckTypes] = React.useState(false)
const [checkTypes, setCheckTypes] = React.useState() const [checkTypes, setCheckTypes] = React.useState()
const [loadingCheckPropertyTypes, setLoadingCheckPrpertyTypes] = React.useState(false) const [checkTypeValue, setCheckTypeValue] = React.useState()
const [checkPropertyTypes, setCheckPropertyTypes] = React.useState() const [preCatalog, setPreCatalog] = React.useState()
const [loadingExpressionTypes, setLoadingExpressionTypes] = React.useState(false)
const [expressionTypes, setExpressionTypes] = React.useState()
const [loadingExpressions, setLoadingExpressions] = React.useState(false)
const [expressions, setExpressions] = React.useState()
const [form] = Form.useForm() const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
validate: async () => { validate: async () => {
return await form.validateFields() return await form?.validateFields()
}, },
}), [form]) }), [form])
...@@ -111,6 +135,40 @@ export const Basic = React.forwardRef(function ({ item }, ref) { ...@@ -111,6 +135,40 @@ export const Basic = React.forwardRef(function ({ item }, ref) {
getCheckTypes() 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 = () => { const getCheckTypes = () => {
setLoadingCheckTypes(true) setLoadingCheckTypes(true)
dispatch({ dispatch({
...@@ -126,8 +184,34 @@ export const Basic = React.forwardRef(function ({ item }, ref) { ...@@ -126,8 +184,34 @@ export const Basic = React.forwardRef(function ({ item }, ref) {
} }
const onValuesChange = (changedValues, allValues) => { const onValuesChange = (changedValues, allValues) => {
console.log('all values', 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 ( return (
<Form <Form
...@@ -137,41 +221,99 @@ export const Basic = React.forwardRef(function ({ item }, ref) { ...@@ -137,41 +221,99 @@ export const Basic = React.forwardRef(function ({ item }, ref) {
autoComplete="off" autoComplete="off"
onValuesChange={onValuesChange} onValuesChange={onValuesChange}
> >
{
type !== 'add' && <Form.Item label='规则编号' style={{ marginBottom }}>
<span>{item?.number}</span>
</Form.Item>
}
<Form.Item name='name' label='规则中文名称' <Form.Item name='name' label='规则中文名称'
style={{ marginBottom }}
rules={[{ required: true, message: '请输入规则中文名称!' }]} rules={[{ required: true, message: '请输入规则中文名称!' }]}
> >
<Input placeholder='请输入规则中文名称' allowClear /> {
type === 'detail' ? <span>{item?.name}</span> : <Input placeholder='请输入规则中文名称' allowClear />
}
</Form.Item> </Form.Item>
<Form.Item name='remark' label='规则描述' <Form.Item name='remark' label='规则描述'
style={{ marginBottom }}
> >
<Input placeholder='请输入规则描述' allowClear /> {
type === 'detail' ? <span>{item?.remark}</span> : <Input placeholder='请输入规则描述' allowClear />
}
</Form.Item> </Form.Item>
<Form.Item name='alertContent' label='规则提示' <Form.Item name='alertContent' label='规则提示'
style={{ marginBottom }}
rules={[{ required: true, message: '请输入规则提示!' }]} rules={[{ required: true, message: '请输入规则提示!' }]}
> >
<Input placeholder='请输入规则提示' allowClear /> {
type === 'detail' ? <span>{item?.alertContent}</span> : <Input placeholder='请输入规则提示' allowClear />
}
</Form.Item> </Form.Item>
<Form.Item name='checkType' label='检查类型' <Form.Item name='checkType' label='检查类型'
style={{ marginBottom }}
rules={[{ required: true, message: '请选择检查类型!' }]} rules={[{ required: true, message: '请选择检查类型!' }]}
> >
<Select allowClear loading={loadingCheckTypes} {
placeholder='请选择检查类型' 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> { (checkTypes??[]).map((item, index) => (
)) } <Select.Option key={item.type} value={item.type}>{item.name}</Select.Option>
</Select> )) }
</Form.Item> </Select>
<Form.Item name='checkProperty' label='检查规则' }
rules={[{ required: true, message: '请选择检查类型!' }]}
>
<CheckItem />
</Form.Item> </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> </Form>
) )
}) })
const CheckItem = ({ value, onChange }) => { const CheckItem = ({ value, onChange, preCatalog }) => {
const [loadingCheckPropertyTypes, setLoadingCheckPrpertyTypes] = React.useState(false) const [loadingCheckPropertyTypes, setLoadingCheckPrpertyTypes] = React.useState(false)
const [checkPropertyTypes, setCheckPropertyTypes] = React.useState() const [checkPropertyTypes, setCheckPropertyTypes] = React.useState()
const [loadingExpressionTypes, setLoadingExpressionTypes] = React.useState(false) const [loadingExpressionTypes, setLoadingExpressionTypes] = React.useState(false)
...@@ -182,6 +324,9 @@ const CheckItem = ({ value, onChange }) => { ...@@ -182,6 +324,9 @@ const CheckItem = ({ value, onChange }) => {
const [currentCheckPropertyType, setCurrentCheckPropertyType] = React.useState() const [currentCheckPropertyType, setCurrentCheckPropertyType] = React.useState()
const [currentExpressionType, setCurrentExpressionType] = React.useState() const [currentExpressionType, setCurrentExpressionType] = React.useState()
const [currentExpression, setCurrentExpression] = React.useState() const [currentExpression, setCurrentExpression] = React.useState()
const [currentExpressionValue, setCurrentExpressionValue] = React.useState()
const mountRef = React.useRef(true)
React.useEffect(() => { React.useEffect(() => {
getCheckPropertyTypes() getCheckPropertyTypes()
...@@ -190,6 +335,28 @@ const CheckItem = ({ value, onChange }) => { ...@@ -190,6 +335,28 @@ const CheckItem = ({ value, onChange }) => {
}, []) }, [])
React.useEffect(() => { 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}) onChange?.({...currentCheckPropertyType||{}, ...currentExpressionType||{}, verifyExpression: currentExpression})
}, [currentCheckPropertyType, currentExpressionType, currentExpression]) }, [currentCheckPropertyType, currentExpressionType, currentExpression])
...@@ -201,6 +368,22 @@ const CheckItem = ({ value, onChange }) => { ...@@ -201,6 +368,22 @@ const CheckItem = ({ value, onChange }) => {
return [] return []
}, [expressionMapping, currentExpressionType]) }, [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 = () => { const getCheckPropertyTypes = () => {
setLoadingCheckPrpertyTypes(true) setLoadingCheckPrpertyTypes(true)
dispatch({ dispatch({
...@@ -244,76 +427,94 @@ const CheckItem = ({ value, onChange }) => { ...@@ -244,76 +427,94 @@ const CheckItem = ({ value, onChange }) => {
} }
return ( return (
<Space> <Row gutter={10}>
<Select allowClear loading={loadingCheckPropertyTypes} <Col span={6}>
value={currentCheckPropertyType?.propertyEnName} <Select allowClear loading={loadingCheckPropertyTypes}
onChange={(val) => { value={currentCheckPropertyType?.propertyEnName}
if (val) { onChange={(val) => {
const index = (checkPropertyTypes??[]).findIndex(item => item.propertyEnName === val) if (val) {
if (index !== -1) { const index = (checkPropertyTypes??[]).findIndex(item => item.propertyEnName === val)
setCurrentCheckPropertyType(checkPropertyTypes[index]) if (index !== -1) {
setCurrentCheckPropertyType(checkPropertyTypes[index])
}
} else {
setCurrentCheckPropertyType()
} }
} else { }}
setCurrentCheckPropertyType() placeholder='请选择检查对象'
} >
}} { (_checkPropertyTypes??[]).map((item, index) => (
placeholder='请选择检查对象' <Select.Option key={item.propertyEnName} value={item.propertyEnName}>{item.propertyCnName}</Select.Option>
> )) }
{ (checkPropertyTypes??[]).map((item, index) => ( </Select>
<Select.Option key={item.propertyEnName} value={item.propertyEnName}>{item.propertyCnName}</Select.Option> </Col>
)) } <Col span={6}>
</Select> <Select allowClear loading={loadingExpressionTypes}
<Select allowClear loading={loadingExpressionTypes} value={currentExpressionType?.expressionTypeEnName}
value={currentExpressionType?.expressionTypeEnName} onChange={(val) => {
onChange={(val) => { if (val) {
if (val) { const index = (expressionTypes??[]).findIndex(item => item.expressionTypeEnName === val)
const index = (expressionTypes??[]).findIndex(item => item.expressionTypeEnName === val) if (index !== -1) {
if (index !== -1) { setCurrentExpressionType(expressionTypes[index])
setCurrentExpressionType(expressionTypes[index]) }
} else {
setCurrentExpressionType()
} }
} else {
setCurrentExpressionType()
}
setCurrentExpression()
}}
placeholder='请选择检查属性'
>
{ (expressionTypes??[]).map((item, index) => (
<Select.Option key={item.expressionTypeEnName} value={item.expressionTypeEnName}>{item.expressionTypeCnName}</Select.Option>
)) }
</Select>
<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() setCurrentExpression()
} setCurrentExpressionValue()
}} }}
placeholder='请选择表达式' placeholder='请选择检查属性'
> >
{ (visibleExpressions??[]).map((item, index) => ( { (expressionTypes??[]).map((item, index) => (
<Select.Option key={item.enName} value={item.enName}>{item.cnName}</Select.Option> <Select.Option key={item.expressionTypeEnName} value={item.expressionTypeEnName}>{item.expressionTypeCnName}</Select.Option>
)) } )) }
</Select> </Select>
{ (currentExpression?.valueType === 'string') && <Input </Col>
onChange={(e) => { <Col span={6}>
setCurrentExpression({ <Select allowClear loading={loadingExpressionMapping}
...currentExpression, value: e.target.value value={currentExpression?.enName}
}) onChange={(val) => {
}}/> } if (val) {
{ (currentExpression?.valueType === 'int') && <InputNumber const index = (visibleExpressions??[]).findIndex(item => item.enName === val)
onChange={(e) => { 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({ setCurrentExpression({
...currentExpression, value: e.target.value ...currentExpression, value: e.target.value?e.target.value.split(';'):[]
}) })
}}/> } }} /> }
{ (currentExpression?.valueType === 'List<String>') && <Input /> } </Col>
</Space> </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
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, useMemo } from 'react';
import { Tabs, Spin } from 'antd'; import { Tabs, Spin } from 'antd';
import { dispatch } from '../../../model'; import { dispatch } from '../../../model';
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 RuleTemplateCURD from './Component/rule-template';
import PartitionCURD from './Component/PartitionCURD'; import PartitionCURD from './Component/PartitionCURD';
...@@ -25,6 +25,27 @@ const ModelConfig = () => { ...@@ -25,6 +25,27 @@ const ModelConfig = () => {
getPermissions(); getPermissions();
}, []) }, [])
const [showRule, showRuleTemplate] = useMemo(() => {
let [_showRule, _showRuleTemplate] = [false, false]
const ruleIndex = (permissions??[]).findIndex(item => item.privilegedObjectName === '规范配置')
if (ruleIndex !== -1) {
const ruleAllPermissionIndex = (permissions[ruleIndex].optionList??[]).findIndex(item => item.name === '全部权限')
if (ruleAllPermissionIndex !== -1) {
_showRule = permissions[ruleIndex].optionList[ruleAllPermissionIndex].enabled
}
}
const ruleTemplateIndex = (permissions??[]).findIndex(item => item.privilegedObjectName === '规则库管理')
if (ruleTemplateIndex !== -1) {
const ruleRuleAllPermissionIndex = (permissions[ruleTemplateIndex].optionList??[]).findIndex(item => item.name === '全部权限')
if (ruleRuleAllPermissionIndex !== -1) {
_showRuleTemplate = permissions[ruleTemplateIndex].optionList[ruleRuleAllPermissionIndex].enabled
}
}
return [_showRule, _showRuleTemplate]
}, [permissions])
const getPermissions = () => { const getPermissions = () => {
setLoading(true); setLoading(true);
dispatch({ dispatch({
...@@ -56,13 +77,17 @@ const ModelConfig = () => { ...@@ -56,13 +77,17 @@ const ModelConfig = () => {
<TabPane tab='数据表类型配置' key='2'> <TabPane tab='数据表类型配置' key='2'>
<TemplateCURD /> <TemplateCURD />
</TabPane> </TabPane>
<TabPane tab='规范配置' key='3'> {
<ConstraintDetail /> showRule && <TabPane tab='规范配置' key='3'>
</TabPane> <RuleCURD />
{/* <TabPane tab='规则库管理' key='4'> </TabPane>
<RuleTemplateCURD /> }
</TabPane> */} {
<TabPane tab='分区配置' key='4'> showRuleTemplate && <TabPane tab='规则库管理' key='4'>
<RuleTemplateCURD />
</TabPane>
}
<TabPane tab='分区配置' key='5'>
<PartitionCURD /> <PartitionCURD />
</TabPane> </TabPane>
</Tabs> </Tabs>
......
.model-config { .model-config {
height: calc(100vh - 64px - 30px); height: 100%;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
overflow: auto; overflow: auto;
......
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