Commit d07d320e by zhaochengxiang

Merge branch 'modeler-v2' into 'master'

Modeler v2

See merge request !13
parents 75c5a171 9b61897e
......@@ -74,6 +74,12 @@ code {
color: white;
}
.word-wrap {
overflow: hidden;
word-wrap: break-word;
white-space:normal;
}
div[id^='__qiankun_microapp_wrapper_'] {
height: 100%;
}
......
......@@ -206,6 +206,10 @@ export function* getCheckoutDataModel(payload) {
return yield call(datamodelerService.getCheckoutDataModel, payload);
}
export function* dataModelRollback(payload) {
return yield call(datamodelerService.dataModelRollback, payload);
}
export function* getDataModelLocation(payload) {
return yield call(datamodelerService.getDataModelLocation, payload);
}
......@@ -218,6 +222,10 @@ export function* exportDDLString(payload) {
return yield call(datamodelerService.exportDDLString, payload);
}
export function* ddlChangeString(payload) {
return yield call(datamodelerService.ddlChangeString, payload);
}
export function* exportERWinString(payload) {
return yield call(datamodelerService.exportERWinString, payload);
}
......@@ -376,4 +384,98 @@ export function* getRuleTemplateAllVerifyExpressionTypes() {
export function* getRuleTemplateAllVertifyExpressions() {
return yield call(datamodelerService.getRuleTemplateAllVertifyExpressions);
}
/* rule catalog */
export function* addRuleCatalog(payload) {
return yield call(datamodelerService.addRuleCatalog, payload)
}
export function* updateRuleCatalog(payload) {
return yield call(datamodelerService.updateRuleCatalog, payload)
}
export function* upRuleCatalog(payload) {
return yield call(datamodelerService.upRuleCatalog, payload)
}
export function* downRuleCatalog(payload) {
return yield call(datamodelerService.downRuleCatalog, payload)
}
export function* deleteRuleCatalog(payload) {
return yield call(datamodelerService.deleteRuleCatalog, payload)
}
export function* getRuleCatalogList() {
return yield call(datamodelerService.getRuleCatalogList)
}
export function* getRuleCatalogEnableList() {
return yield call(datamodelerService.getRuleCatalogEnableList)
}
export function* getRuleCatalogVersionList(payload) {
return yield call(datamodelerService.getRuleCatalogVersionList, payload)
}
export function* compareRuleCatalogVersion(payload) {
return yield call(datamodelerService.compareRuleCatalogVersion, payload)
}
export function* getRuleCatalogStatus() {
return yield call(datamodelerService.getRuleCatalogStatus)
}
/* rule */
export function* addRule(payload) {
return yield call(datamodelerService.addRule, payload)
}
export function* updateRule(payload) {
return yield call(datamodelerService.updateRule, payload)
}
export function* deleteRules(payload) {
return yield call(datamodelerService.deleteRules, payload)
}
export function* getRuleList(payload) {
return yield call(datamodelerService.getRuleList, payload)
}
export function* getRuleAlertTypes() {
return yield call(datamodelerService.getRuleAlertTypes)
}
export function* getRuleStatus() {
return yield call(datamodelerService.getRuleStatus)
}
export function* addComment(payload) {
return yield call(datamodelerService.addComment, payload)
}
export function* deleteComment(payload) {
return yield call(datamodelerService.deleteComment, payload)
}
export function* getComments(payload) {
return yield call(datamodelerService.getComments, payload)
}
export function* uploadCommentFile(payload) {
return yield call(datamodelerService.uploadCommentFile, payload)
}
export function* deleteCommentFile(payload) {
return yield call(datamodelerService.deleteCommentFile, payload)
}
export function* getSearchProperties() {
return yield call(datamodelerService.getSearchProperties)
}
export function* searchModelBySearchProperties(payload) {
return yield call(datamodelerService.searchModelBySearchProperties, payload)
}
\ No newline at end of file
......@@ -197,6 +197,10 @@ export function getCheckoutDataModel(payload) {
return GetJSON("/datamodeler/easyDataModelerCURD/getCheckoutDataModel", payload);
}
export function dataModelRollback(payload) {
return PostJSON("/datamodeler/easyDataModelerCURD/reset", payload);
}
export function ddlGenerators() {
return GetJSON("/datamodeler/easyDataModelerExport/ddlGenerators");
}
......@@ -205,6 +209,10 @@ export function exportDDLString(payload) {
return GetJSON("/datamodeler/easyDataModelerExport/ddlString", payload);
}
export function ddlChangeString(payload) {
return Get("/datamodeler/easyDataModelerExport/ddlChangeString", payload);
}
export function exportERWinString(payload) {
return GetJSON("/datamodeler/easyDataModelerExport/erWinPluginString", payload);
}
......@@ -290,41 +298,136 @@ export function getPrivilegeAdmin() {
}
export function getRuleTemplateList() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/list");
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/list");
}
/* rule template */
export function addRuleTemplate(payload) {
return PostJSON("/shandatamodeler/easyDataModelerRuleTemplate/add", payload);
return PostJSON("/datamodeler/easyDataModelerRuleTemplate/add", payload);
}
export function updateRuleTemplate(payload) {
return PostJSON("/shandatamodeler/easyDataModelerRuleTemplate/update", payload);
return PostJSON("/datamodeler/easyDataModelerRuleTemplate/update", payload);
}
export function deletesRuleTemplate(payload) {
return Delete("/shandatamodeler/easyDataModelerRuleTemplate/dels", payload);
return Delete("/datamodeler/easyDataModelerRuleTemplate/dels", payload);
}
export function deleteRuleTemplate(payload) {
return Delete("/shandatamodeler/easyDataModelerRuleTemplate/del", payload);
return Delete("/datamodeler/easyDataModelerRuleTemplate/del", payload);
}
export function getRuleTemplateDetail(payload) {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getById", payload);
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getById", payload);
}
export function getRuleTemplateCheckTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getCheckTypes");
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getCheckTypes");
}
export function getRuleTemplateAllCheckPropertyTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getAllCheckPropertyTypes");
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllCheckPropertyTypes");
}
export function getRuleTemplateAllVerifyExpressionTypes() {
return GetJSON("/shandatamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressionTypes")
return GetJSON("/datamodeler/easyDataModelerRuleTemplate/getAllVerifyExpressionTypes")
}
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,
}}
onRow={(record, index) => {
return {
id: record?.id,
onClick: event => {
onRowClick?.(event, record)
},
......
......@@ -14,7 +14,7 @@ import { DataModelerRoleAdmin, DataModelerRoleUser, DataModelerRoleReader, Asset
//元曜公网环境 isSzseEnv false
export const isSzseEnv = false;
export const inputWidth = isSzseEnv?360:240;
export const inputWidth = isSzseEnv?360:200;
export const ContextPath = '/data-govern';
......@@ -508,4 +508,41 @@ export function getValidString(strs) {
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'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
......@@ -154,7 +155,14 @@ const FC = (props) => {
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/>
</div>
}
......
......@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
......@@ -276,7 +277,14 @@ const FC = (props) => {
id={record.id}
did={record.dirId}
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'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
......@@ -121,7 +122,14 @@ const FC = () => {
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/>
</div>
}
......
......@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
......@@ -154,7 +155,14 @@ const FC = (props) => {
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
tags={resoureTagMap?.[record.id]}
onChange={(val) => {
setResourceTagMap((prevResourceTagMap) => {
return produce(prevResourceTagMap||{}, (draft) => {
draft[record.id] = val
})
})
}}
/>
</div>
}
......
......@@ -5,6 +5,7 @@ import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import produce from 'immer'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
......@@ -366,7 +367,14 @@ const FC = (props) => {
id={record.id}
did={record.dirId}
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";
import LocalStorage from 'local-storage';
import ImportAction from "./ImportAction";
import { inheritanceHistoricalType, inheritanceZipperType } from "./ImportActionInherited";
import { inheritanceHistoricalType, inheritanceZipperType } from "./ImportActionRelation";
import { dispatch } from '../../../../model';
const FC = (props) => {
......@@ -150,9 +150,7 @@ const FC = (props) => {
}
key={inheritanceHistoricalType}
>
<div style={{ height: '75vh', overflowX: 'hidden' }}>
{ historicalModelerData && <ImportAction form={historicalForm} action='edit-inherite-modal' roughModelerData={historicalModelerData} onChange={onHistoricalChange} /> }
</div>
</Tabs.TabPane>
<Tabs.TabPane
tab={
......@@ -168,9 +166,7 @@ const FC = (props) => {
}
key={inheritanceZipperType}
>
<div style={{ height: '75vh', overflowX: 'hidden' }}>
{ zipperModelerData && <ImportAction form={zipperForm} action='edit-inherite-modal' roughModelerData={zipperModelerData} onChange={onZipperChange} /> }
</div>
</Tabs.TabPane>
</Tabs>
}
......
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 { useMount, useUnmount } from 'ahooks';
import ImportAction from './ImportAction';
import CatalogModal from './CatalogModal';
import { dispatchLatest } from '../../../../model';
import { dispatchLatest, dispatch } from '../../../../model';
import { getQueryParam, showMessage, showNotifaction } from '../../../../util';
import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, VersionId, Holder, DDL, ReadOnly } from '../../../../util/constant';
import HistoryAndVersionDrawer from './HistoryAndVersionDrawer';
import { EditModelContext } from './ContextManage';
import EditInherited from './EditInherited';
import { ImportActionHeaderSubject } from './ImportActionHeader';
import { ImportActionHeaderSubject } from './ImportActionManage';
import PermissionButton from '../../../../util/Component/PermissionButton';
import './EditModel.less';
......@@ -35,6 +35,7 @@ const EditModel = (props) => {
const { action, catalogId, modelerId, hints, roughModelerData, permitCheckOut, editable, stateId, versionId, holder, ddl, readOnly } = actionData;
const [form] = Form.useForm();
const [modal, contextHolder] = Modal.useModal()
const importActionRef = useRef(undefined);
useEffect(() => {
......@@ -261,6 +262,32 @@ const EditModel = (props) => {
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) => {
attrIsEditingRef.current = value;
}
......@@ -430,6 +457,14 @@ const EditModel = (props) => {
}
</Space>
);
} else if (action === 'detail-version') {
actionsBtn = (
<Space>
<Button type='primary' danger onClick={onRollback} >
回退到该版本
</Button>
</Space>
)
}
return (
......@@ -469,6 +504,7 @@ const EditModel = (props) => {
}
}}
/>
{contextHolder}
</div>
</EditModelContext.Provider>
);
......
......@@ -6,24 +6,21 @@
padding: 0 15px;
background-color: #464d6e;
align-items: center;
position: fixed;
// position: fixed;
justify-content: space-between;
border-bottom: 1px solid #EFEFEF;
z-index: 100;
// z-index: 100;
}
.edit-container {
top: 44px;
width: 100%;
height: calc(100vh - 44px - 64px);
overflow: auto;
background: #EDF0F5;
padding: 10px 20px;
position: absolute;
height: calc(100vh - 44px - 64px);
overflow: hidden;
}
.edit-container-card {
padding: 20px;
margin: 10px 20px;
height: calc(100vh - 44px - 64px - 20px);
background: #fff;
}
......
......@@ -6,6 +6,7 @@ const exportModes = [
{ name: '导出Erwin', key: 'erwin' },
{ name: '导出Excel', key: 'excel' },
{ name: '导出Word', key: 'word' },
{ name: '导出模型信息', key: 'basicExcel' },
]
const ExportOtherModal = (props) => {
......@@ -62,7 +63,7 @@ const ExportOtherModal = (props) => {
forceRender
visible={visible}
title='模型导出'
width={540}
width={700}
onCancel={cancel}
footer={footer}
>
......
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 { Subject } from 'rxjs';
import ImportActionHeader from './ImportActionHeader';
import ImportActionInherited from './ImportActionInherited';
import { ImportActionTable } from './ImportActionTable';
import ImportActionIndex from './ImportActionIndex';
import { getQueryParam } from '../../../../util';
import ImportActionManage from './ImportActionManage';
import ImportActionRelation from './ImportActionRelation';
import ImportActionComment from './ImportActionComment';
import { getInternalCurrentAnchor, getQueryParam } from '../../../../util';
import { Action } from '../../../../util/constant';
import { dispatch } from '../../../../model';
import './ImportAction.less'
export const importActionSubject = new Subject()
const ImportAction = React.forwardRef((props, ref) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut, catalogId } = props;
......@@ -23,9 +30,12 @@ const ImportAction = React.forwardRef((props, ref) => {
const [ supportedIndextypes, setSupportedIndextypes ] = useState([]);
const [ validateReports, setValidateReports ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [container, setContainer] = useState();
const [activeValue, setActiveValue] = useState();
const mountRef = useRef(true);
const modelerDataRef = useRef(null);
const animating = useRef(false);
useImperativeHandle(ref, () => ({
isLoading: () => {
......@@ -106,6 +116,40 @@ const ImportAction = React.forwardRef((props, ref) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [constraint])
React.useEffect(() => {
if (container) {
handleScroll();
container?.addEventListener('scroll', handleScroll);
return () => {
container?.removeEventListener('scroll', handleScroll);
};
}
}, [container]);
const handleScroll = React.useCallback(() => {
if (animating.current) {
animating.current = false;
return;
}
const currentActiveLink = getInternalCurrentAnchor(
[
'model-import-action-basic',
'model-import-action-technical',
'model-import-action-table',
'model-import-action-index',
'model-import-action-manage',
'model-import-action-relation',
'model-import-action-comment',
],
20,
5,
container
);
setActiveValue(currentActiveLink)
}, [container]);
const getTemplates = () => {
dispatch({
type: 'datamodel.getAllTemplates',
......@@ -280,25 +324,21 @@ const ImportAction = React.forwardRef((props, ref) => {
const onConstraintChange = (value) => {
let currentConstraint = null;
(constraints||[]).forEach((_constraint, index) => {
if (_constraint.name === value) {
currentConstraint = _constraint;
}
});
if (!currentConstraint) return;
form.setFieldsValue({
easyDataModelerModelingConstraint: currentConstraint
});
const newModelerData = {...modelerData, easyDataModelerModelingConstraint: currentConstraint };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
setConstraint(currentConstraint);
getConsult(newModelerData);
const index = (constraints??[]).findIndex(item => item.id === value)
if (index !== -1) {
currentConstraint = constraints[index]
form.setFieldsValue({
easyDataModelerModelingConstraint: currentConstraint
});
const newModelerData = {...modelerData, easyDataModelerModelingConstraint: currentConstraint };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
setConstraint(currentConstraint);
getConsult(newModelerData);
}
}
const onTemplateChange = (value, isCustom = false) => {
......@@ -542,46 +582,85 @@ const ImportAction = React.forwardRef((props, ref) => {
return (
<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?'':'暂无权限'}
</span> : <React.Fragment>
<ImportActionHeader
form={form}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
modelerData={modelerData||{}}
constraints={constraints}
templates={templates}
validateReports={validateReports}
onTemplateChange={onTemplateChange}
onConstraintChange={onConstraintChange}
onChange={onHeaderChange}
terms={terms}
supportedPartitionTypes={supportedPartitionTypes}
/>
<ImportActionInherited modelerData={modelerData} action={action} />
<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}
/>
</React.Fragment>
</div> : <div className='import-action'>
<Tabs activeKey={activeValue} centered onChange={(val) => {
setActiveValue(val);
var targetElement = container?.querySelector(`.${val}`); // 找到目标元素
if (targetElement) {
importActionSubject.next({ type: 'expand', key: val, action })
setTimeout(() => {
animating.current = true;
targetElement.scrollIntoView();
}, 100)
}
}}>
<Tabs.TabPane tab='基本信息' key="model-import-action-basic" />
<Tabs.TabPane tab='技术信息' key="model-import-action-technical" />
<Tabs.TabPane tab='数据表结构' key="model-import-action-table" />
<Tabs.TabPane tab='数据表索引' key="model-import-action-index" />
<Tabs.TabPane tab='管理信息' key="model-import-action-manage" />
<Tabs.TabPane tab='关联对象' key="model-import-action-relation" />
{
modelerData?.id && <Tabs.TabPane tab='模型评论' key="model-import-action-comment" />
}
</Tabs>
<div ref={setContainer} style={{ height: action==='edit-inherite-modal'?'60vh':'calc(100vh - 44px - 64px - 66px)', overflow: 'auto', padding: '20px 20px 0' }}>
<ImportActionHeader
form={form}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'&&action!=='edit-inherited'}
modelerData={modelerData||{}}
constraints={constraints}
templates={templates}
validateReports={validateReports}
onTemplateChange={onTemplateChange}
onConstraintChange={onConstraintChange}
onChange={onHeaderChange}
terms={terms}
supportedPartitionTypes={supportedPartitionTypes}
action={action}
/>
<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>
);
......
.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
.model-import-action-header {
.yy-form-item:nth-last-col {
margin-bottom: 24px;
.model-import-action-header-readolny {
.yy-form-item-label > label {
height: auto;
}
.yy-form-item-has-error {
margin-bottom: 0px;
}
.yy-descriptions-row > th, .yy-descriptions-row > td {
padding-bottom: 15px;
.yy-form-item-control-input {
min-height: 0;
}
}
\ No newline at end of file
import React 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 { Popover } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import React from "react"
import { Button, Descriptions, Space, Popover } from "antd"
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { Action, ModelerId, PermitCheckOut, Editable, StateId, Holder, ReadOnly } from '../../../../util/constant';
import { Action, ModelerId, PermitCheckOut, Editable, StateId, Holder, ReadOnly } from '../../../../util/constant'
import { importActionSubject } from "./ImportAction"
export const inheritanceHistoricalType = 'historical';
export const inheritanceZipperType = 'zipper';
export const inheritanceHistoricalType = 'historical'
export const inheritanceZipperType = 'zipper'
const FC = (props) => {
const { modelerData, action } = props;
const [relationModelerDatas, setRelationModelerDatas] = useState([]);
const { modelerData, action } = props
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) {
const newRelationModelerDatas = [];
......@@ -39,24 +53,49 @@ const FC = (props) => {
}, [modelerData])
return (
<div className='model-import-action-inherited'>
<div className='mb-3'>
<div className='flex' style={{ alignItems: 'center' }}>
<h2 className={action==='add'?'mr-1':'mr-3'} style={{ marginBottom: 0 }}>历史存储形式</h2>
<div>
<div className='model-import-action-relation mb-3'>
<Space>
<h3 style={{ marginBottom: 0 }}>关联对象</h3>
{
action==='add' && <Popover className='mr-3' content='保存当前模型后方可选择历史存储形式'>
action==='add' && <Popover content='保存当前模型后方可选择历史存储形式'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
{
relationModelerDatas?.length===0 ? <span>暂无信息</span> : 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>)
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</div>
</Space>
</div>
{
!isCollapse && <Descriptions column={3}>
<Descriptions.Item
label={
<div style={{ textAlign: 'right', width: 100 }}>
历史存储形式
</div>}
>
<span>
{
relationModelerDatas?.length===0 ? '暂无信息' :
relationModelerDatas?.map((item, index) => (
<a className='mr-3' key={index} onClick={() => {
window.open(`/data-govern/data-model-action?${Action}=detail&${ModelerId}=${item.id}&${PermitCheckOut}=${item.permitCheckOut||false}&${Editable}=${item.editable||false}&${StateId}=${item.state?.id||''}&${Holder}=${item.holder||''}&${ReadOnly}=false`);
}}>
{item.cnName}
</a>
))
}
</span>
</Descriptions.Item>
</Descriptions>
}
</div>
)
}
export default FC;
\ No newline at end of file
export default FC
\ No newline at end of file
......@@ -13,6 +13,7 @@ import TagCell from './tag-help';
import './ModelTable.less';
import 'react-contexify/dist/ReactContexify.css';
import produce from "immer";
const { Paragraph, Text } = Typography;
......@@ -134,7 +135,7 @@ const ModelNameColumn = (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 [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]);
......@@ -144,6 +145,7 @@ const ModelTable = (props) => {
const expandedDataMapRef = useRef(new Map());
const shouldScrollRef = useRef(false);
const mountRef = React.useRef(false);
const anchorId = getQueryParam(AnchorId, props?.location?.search);
const anchorTimestamp = getQueryParam(AnchorTimestamp, props?.location?.search);
......@@ -266,7 +268,14 @@ const ModelTable = (props) => {
props.row?.state?.id === '4' ? <TagCell
id={props.row?.id}
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
)
}
......@@ -318,6 +327,14 @@ const ModelTable = (props) => {
}, [data])
useEffect(() => {
if (!mountRef.current) {
mountRef.current = true;
} else {
getResourceTag();
}
}, [batchAddTagChange])
useEffect(() => {
window?.addEventListener("storage", modelEventChange);
return () => {
window?.removeEventListener("storage", modelEventChange);
......@@ -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 ids = (data??[]).map(item => item.id);
if (ids.length > 0) {
......@@ -585,7 +621,7 @@ const ModelTable = (props) => {
<Paragraph style={{ overflow: 'hidden' }}>
<Text className='title-color' ellipsis={true}>
总数:
<Text className='text-color'>{(data||[]).length}</Text>
<Text className='text-color'>{(tableData||[]).length}</Text>
</Text>
</Paragraph>
<Paragraph style={{ overflow: 'hidden', marginLeft: 20 }}>
......@@ -603,7 +639,7 @@ const ModelTable = (props) => {
// rows={Array.from({ length: 10000 }).map((_, i) => ({
// name: `test${i}`,
// }))}
rows={data||[]}
rows={tableData||[]}
rowHeight={51}
rowClassName={(row) => {
return (row.id === anchorId)?'anchor':''
......
......@@ -34,7 +34,7 @@ const ModelTree = (props) => {
id: MENU_ID,
});
const { onSelect, onViewChange, refrence='', importStockModel, keyword } = props;
const { onSelect, onViewChange, refrence='', importStockModel, keyword, searchProperties } = props;
const { user } = useContext(AppContext);
const [ loading, setLoading ] = useState(false);
......@@ -89,7 +89,6 @@ const ModelTree = (props) => {
}, [timestamp])
useEffect(() => {
if (keyword!=='') {
if (item && !prevItem) {
setPrevItem(item);
......@@ -107,6 +106,23 @@ const ModelTree = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ 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(() => {
return (item?.optionList||[]).findIndex(option => option.name==='存量模型导入'&&option.enabled) !== -1
}, [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 { 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 { formatVersionDate } from '../../../../util';
import { formatVersionDate, showMessage } from '../../../../util';
import VersionCompareHeader from './VersionCompareHeader';
import VersionCompareTable from './VersionCompareTable';
import VersionCompareIndex from './VersionCompareIndex';
import FilterColumnAction from './FilterColumnAction';
import VersionDdlAlter from './version-ddl-alter';
import './VersionCompare.less';
......@@ -28,6 +29,13 @@ const VersionCompare = (props) => {
const [ onlyShowChange, setOnlyShowChange ] = useState(true);
const [ attrFilterColumns, setAttrFilterColumns ] = useState([]);
const [ attrSelectedTitles, setAttrSelectedTitles ] = useState(defaultColumnTitles);
const [ddlAlterParams, setAlterParams] = useState({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
const attrColumnsRef = useRef([]);
......@@ -183,6 +191,16 @@ const VersionCompare = (props) => {
setAttrFilterColumns(newFilterColumns);
}
const onAlterClick = () => {
setAlterParams({
visible: true,
id,
versions: basicVersions,
defaultBasicVersion: basicVersion,
defaultIncVersion: incVersion,
})
}
const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked);
if (basicVersion!=='' && incVersion!=='') {
......@@ -230,7 +248,10 @@ const VersionCompare = (props) => {
</Select>
</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>
......@@ -252,6 +273,18 @@ const VersionCompare = (props) => {
}
</Spin>
</div>
<VersionDdlAlter
{...ddlAlterParams}
onCancel={() => {
setAlterParams({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
}}
/>
</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 { Tooltip, Tag, Typography, Space, Button, Modal, Spin } from 'antd'
import { Tooltip, Tag, Typography, Space, Button, Modal, Spin, Select, } from 'antd'
import {
EllipsisOutlined,
PlusOutlined,
} from '@ant-design/icons'
import produce from 'immer'
import DataQuality, { DataQualityFeighTagSelect } from '../../../QianKun/data-quality'
import { showMessage } from '../../../../util'
import { dispatch } from '../../../../model'
import { AppContext } from '../../../../App'
const FC = ({ type, id, did, tags }) => {
const FC = ({ type, id, did, tags, onChange }) => {
const [data, setData] = React.useState([])
const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({
visible: false,
......@@ -46,6 +48,7 @@ const FC = ({ type, id, did, tags }) => {
},
callback: data => {
setData(data?.data?.[`${id}`]);
onChange?.(data?.data?.[`${id}`]);
}
});
}
......@@ -169,7 +172,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) {
}}>
{
_value?.map((item, index) => <TagRender
key={index}
key={item.id}
data={item}
closable={!readonly&&item?.deleteAble}
onClose={(e) => {
......@@ -183,7 +186,7 @@ export function TagCell({ value, readonly = false, onChange, onAdd }) {
<Space wrap={true}>
{
value?.map((item, index) => <TagRender
key={index}
key={item.id}
data={item}
closable={!readonly&&item?.deleteAble}
onClose={(e) => {
......@@ -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 [selectedRows, setSelectedRows] = React.useState([])
const [showDataQuality, setShowDataQuality] = React.useState(false)
......@@ -241,6 +309,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
showMessage('warn', '请先选择标签')
return
}
setWaiting(true)
dispatch({
type: 'tag.addTags',
......@@ -251,7 +320,7 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
type,
catalogId: (items??[]).length>0?items[0].resourceCatalogId:'',
creator: app?.user?.userName,
overrideAll: true,
overrideAll: (items??[]).length === 1 ? true : false,
}
},
callback: () => {
......@@ -296,7 +365,8 @@ export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
type={DataQualityFeighTagSelect}
data={{
type,
items
items,
value,
}}
onChange={(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
......@@ -104,13 +104,15 @@ const TemplateAction = (props) => {
return (
<Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<Tabs centered activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
<div style={{ padding: '0px 20px 0' }}>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
</div>
</TabPane>
<TabPane
tab={
......@@ -127,20 +129,22 @@ const TemplateAction = (props) => {
}
key='2'
>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
<div style={{ height: (action!=='detail')?'calc(100vh - 44px - 64px - 150px)':'calc(100vh - 44px - 64px - 86px)', overflow: 'auto', padding: '0px 20px 0' }}>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
</div>
</TabPane>
</Tabs>
{
action!=='detail'&&(
<React.Fragment>
<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'>
<Button type='primary' onClick={prevStep} disabled={tabKey==='1'} 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 { Modal } from "antd"
import RuleCURD from './rule'
const FC = (props) => {
const { visible, onCancel } = props
const close = () => {
onCancel?.()
}
return (
<Modal
visible={visible}
footer={null}
width='80%'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='规范详情'
centered destroyOnClose
onCancel={() => { close() }}
>
<RuleCURD readonly={true} />
</Modal>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Input, Space, Modal } from 'antd'
import { Input, Space, Modal, Divider, Tooltip, Typography } from 'antd'
import { dispatch } from '../../../../model'
import PermissionButton from '../../../../util/Component/PermissionButton'
......@@ -35,23 +35,101 @@ const FC = (props) => {
{
title: '规则编号',
dataIndex: 'number',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '规则中文名称',
dataIndex: 'name',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={() => {
setUpdateParams({
visible: true,
type: 'detail',
item: record
})
}}>
{ text }
</a>
</Typography.Text>
</Tooltip>
)
},
{
title: '规则描述',
dataIndex: 'remark',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '检查类型',
dataIndex: 'checkTypeName',
render: (text, record) => (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{ text }
</Typography.Text>
</Tooltip>
)
},
{
title: '引用次数',
dataIndex: 'referenceCount',
},
{
title: '操作',
key: 'action',
width: 120,
render: (text, record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<PermissionButton
type='link'
size='small'
onClick={() => {
setUpdateParams({
visible: true,
type: 'edit',
item: record,
})
}}
style={{ padding: 0 }}
// permissionKey='编辑'
// permissions={permissions}
defaultPermission={true}
>
编辑
</PermissionButton>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<PermissionButton
type='link'
size='small'
onClick={() => { onDeteteClick(record); }}
style={{ padding: 0 }}
// permissionKey='删除'
// permissions={permissions}
defaultPermission={true}
>
删除
</PermissionButton>
</div>
)
}
}
])
}, [])
......@@ -126,7 +204,7 @@ const FC = (props) => {
return (
<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>
<PermissionButton
onClick={onAddClick}
......@@ -157,6 +235,7 @@ const FC = (props) => {
/>
</div>
<Table
extraColWidth={32}
loading={loading}
columns={cols??[]}
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 { 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 { dispatch } from '../../../model';
import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD';
import ConstraintDetail from './Component/ConstraintDetail';
import RuleCURD from './Component/rule';
import RuleTemplateCURD from './Component/rule-template';
import PartitionCURD from './Component/PartitionCURD';
......@@ -25,6 +25,27 @@ const ModelConfig = () => {
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 = () => {
setLoading(true);
dispatch({
......@@ -56,13 +77,17 @@ const ModelConfig = () => {
<TabPane tab='数据表类型配置' key='2'>
<TemplateCURD />
</TabPane>
<TabPane tab='规范配置' key='3'>
<ConstraintDetail />
</TabPane>
{/* <TabPane tab='规则库管理' key='4'>
<RuleTemplateCURD />
</TabPane> */}
<TabPane tab='分区配置' key='4'>
{
showRule && <TabPane tab='规范配置' key='3'>
<RuleCURD />
</TabPane>
}
{
showRuleTemplate && <TabPane tab='规则库管理' key='4'>
<RuleTemplateCURD />
</TabPane>
}
<TabPane tab='分区配置' key='5'>
<PartitionCURD />
</TabPane>
</Tabs>
......
.model-config {
height: calc(100vh - 64px - 30px);
height: 100%;
padding: 20px;
background: #fff;
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