Commit 6f0d6020 by 和金晶

添加手工数据维护的数据表定义及数据表配置模块

parent 93358566
...@@ -15,7 +15,9 @@ const Signin = loadable(()=> import('./view/Signin')); ...@@ -15,7 +15,9 @@ const Signin = loadable(()=> import('./view/Signin'));
const Home = loadable(()=> import('./view/Home')); const Home = loadable(()=> import('./view/Home'));
const Manage = loadable(()=> import('./view/Manage')); const Manage = loadable(()=> import('./view/Manage'));
const Model = loadable(()=> import('./view/Manage/Model')); const Model = loadable(()=> import('./view/Manage/Model'));
const ManualDataModel = loadable(()=> import('./view/Manage/Manual'));
const ModelConfig = loadable(()=> import('./view/Manage/ModelConfig')); const ModelConfig = loadable(()=> import('./view/Manage/ModelConfig'));
const ManualModelConfig = loadable(()=> import('./view/Manage/ManualModelConfig'));
const ModelCompare = loadable(()=> import('./view/Manage/ModelCompare')); const ModelCompare = loadable(()=> import('./view/Manage/ModelCompare'));
const DataTypeConfig = loadable(()=> import('./view/Manage/DataTypeConfig')); const DataTypeConfig = loadable(()=> import('./view/Manage/DataTypeConfig'));
const AssetResourceManage = loadable(()=> import('./view/Manage/AssetResourceManage')); const AssetResourceManage = loadable(()=> import('./view/Manage/AssetResourceManage'));
...@@ -204,6 +206,8 @@ export class App extends React.Component { ...@@ -204,6 +206,8 @@ export class App extends React.Component {
<Route path={'/center-home/view/model-config'} component={ModelConfig} exact /> <Route path={'/center-home/view/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/view/data-model-bj'} component={Model} exact /> <Route path={'/center-home/view/data-model-bj'} component={Model} exact />
<Route path={'/center-home/view/model-config-bj'} component={ModelConfig} exact /> <Route path={'/center-home/view/model-config-bj'} component={ModelConfig} exact />
<Route path={'/center-home/view/manual-data-model-bj'} component={ManualDataModel} exact />
<Route path={'/center-home/view/manual-model-config-bj'} component={ManualModelConfig} exact />
<Route path={'/center-home/view/asset-manage'} component={AssetManage} exact /> <Route path={'/center-home/view/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/view/asset-resource-browse'} component={AssetResourceBrowse} exact /> <Route path={'/center-home/view/asset-resource-browse'} component={AssetResourceBrowse} exact />
<Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact /> <Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact />
...@@ -214,6 +218,8 @@ export class App extends React.Component { ...@@ -214,6 +218,8 @@ export class App extends React.Component {
<Route path={'/center-home/menu/model-config'} component={ModelConfig} exact /> <Route path={'/center-home/menu/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/menu/data-model-bj'} component={Model} exact /> <Route path={'/center-home/menu/data-model-bj'} component={Model} exact />
<Route path={'/center-home/menu/model-config-bj'} component={ModelConfig} exact /> <Route path={'/center-home/menu/model-config-bj'} component={ModelConfig} exact />
<Route path={'/center-home/menu/manual-data-model-bj'} component={ManualDataModel} exact />
<Route path={'/center-home/menu/manual-model-config-bj'} component={ManualModelConfig} exact />
<Route path={'/center-home/menu/model-compare'} component={ModelCompare} exact /> <Route path={'/center-home/menu/model-compare'} component={ModelCompare} exact />
<Route path={'/center-home/menu/data-type-config'} component={DataTypeConfig} exact /> <Route path={'/center-home/menu/data-type-config'} component={DataTypeConfig} exact />
<Route path={'/center-home/menu/asset-resource-manage'} component={AssetResourceManage} exact /> <Route path={'/center-home/menu/asset-resource-manage'} component={AssetResourceManage} exact />
......
import React, { useState } from "react";
import { Modal } from "antd";
import ModelTree from './ModelTree';
import { showMessage } from '../../../../util';
const CatalogModal = (props) => {
const { onCancel, visible } = props;
const [ catalogId, setCatalogId ] = useState('');
const onSelect = (value) => {
setCatalogId(value);
}
const onOk = () => {
if ((catalogId||'') === '') {
showMessage('warn', '请先选择模型目录');
return;
}
onCancel && onCancel(catalogId);
}
const reset = () => {
setCatalogId('');
}
return(
<Modal
title='选择目录'
visible={ visible }
width={ 400 }
onCancel={()=>{
reset();
onCancel && onCancel()
}}
onOk={ onOk }
>
{
visible && <ModelTree
refrence='recatalog'
onSelect={onSelect}
/>
}
</Modal>
)
}
export default CatalogModal;
\ No newline at end of file
import { useState, useEffect } from 'react';
import { Modal, Button, Switch, Row, Col, Checkbox, Typography } from 'antd';
import { dispatch } from '../../../../model';
const cols = [
{title: '模型名称', require: true},
{title: '中文名称'},
{title: '路径'},
{title: '状态'},
{title: '创建人'},
{title: '版本号'},
{title: '模型描述'},
];
const ColSettingModal = (props) => {
const {visible, onCancel} = props;
const [checkedKeys, setCheckedKeys] = useState([]);
const [confirmLoading, setConfirmLoading] = useState(false);
useEffect(() => {
if (visible) {
getPreference();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible]);
const getPreference = () => {
dispatch({
type: 'datamodel.getPreference',
callback: data => {
if ((data.cols||'') === '') {
onCheckAllChange(true);
} else {
setCheckedKeys(data.cols.split(','));
}
}
})
}
const onCheckAllChange = (checked) => {
const newCheckedKeys = [];
if (checked) {
cols.forEach(col => {
newCheckedKeys.push(col.title);
});
} else {
cols.forEach(col => {
if (col.require) {
newCheckedKeys.push(col.title);
}
});
}
setCheckedKeys(newCheckedKeys);
}
const onCheckChange = (e) => {
if (e.target.checked) {
setCheckedKeys([...checkedKeys, e.target.value]);
} else {
const index = checkedKeys.findIndex(key => key === e.target.value);
const newCheckedKeys = [...checkedKeys];
newCheckedKeys.splice(index, 1)
setCheckedKeys(newCheckedKeys);
}
}
const onModalCancel = () => {
onCancel && onCancel();
}
const onModalOk = () => {
setConfirmLoading(true);
dispatch({
type: 'datamodel.savePreference',
payload: {
data: {
cols: checkedKeys.join(',')
}
},
callback: () => {
setConfirmLoading(false);
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
}
return (
<Modal
title='可见列设置'
visible={visible}
onCancel={onModalCancel}
footer={[
<Button
key="0"
onClick={onModalCancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={onModalOk}
loading={confirmLoading}
>
确定
</Button>,
]}
>
<div className='d-flex'>
<Switch
checkedChildren="全不选"
unCheckedChildren="全选"
onChange={onCheckAllChange}
style={{ marginLeft: 'auto' }}
/>
</div>
<div className='mt-3' style={{ maxHeight: 450, overflow: 'auto' }}>
<Row>
{
cols.map((col, index) => {
return (
<Col className='mb-3' key={index} md={6}>
<div className='d-flex'>
<Checkbox checked={ checkedKeys.indexOf(col.title||'')!==-1 } value={col.title} onChange={onCheckChange} disabled={col.require} >
</Checkbox>
<Typography.Paragraph className='ml-1' title={col.title} ellipsis>
{col.title}
</Typography.Paragraph>
</div>
</Col>
)
})
}
</Row>
</div>
</Modal>
)
}
export default ColSettingModal;
\ No newline at end of file
import React from 'react';
export const EditModelContext = React.createContext({
attrIsEditingFunction: null,
indexIsEditingFunction: null,
});
\ No newline at end of file
import React from "react";
import _ from "lodash";
/* eslint import/no-anonymous-default-export: [2, {"allowArrowFunction": true}] */
export default (triggerMs = 0) => {
return ReactElement => {
class DebounceInput extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.state.value = props.value;
}
componentDidUpdate(preProps, preState) {
if (preProps.value !== this.props.value) {
this.setState({ value: this.props.value });
}
}
onChange = (() => {
let updb = this.props.onChange;
if (triggerMs >= 0) {
updb = _.debounce(value => {
this.props.onChange(value);
}, triggerMs);
}
return updb;
})();
handleOnChange = event => {
const {
target: { value }
} = event;
this.setState({ value });
return this.onChange(value);
};
render() {
const theProps = {
...this.props,
...this.state
};
theProps.onChange = this.handleOnChange;
return <ReactElement {...theProps} />;
}
}
return DebounceInput;
};
};
import React, { useState, useEffect } from "react";
import { Modal, Space, Button, Form, Tabs, Checkbox } from "antd";
import LocalStorage from 'local-storage';
import ImportAction from "./ImportAction";
import { inheritanceHistoricalType, inheritanceZipperType } from "./ImportActionRelation";
import { dispatch } from '../../../../model';
import { showMessage } from "../../../../util";
const FC = (props) => {
const { visible, modelerData, onCancel } = props;
const [activeKey, setActiveKey] = useState(inheritanceHistoricalType);
const [historicalModelerData, setHistoricalModelerData] = useState(undefined);
const [zipperModelerData, setZipperModelerData] = useState(undefined);
const [createHistorical, setCreateHistorical] = useState(false);
const [createZipper, setCreateZipper] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [historicalForm] = Form.useForm();
const [zipperForm] = Form.useForm();
useEffect(() => {
if (modelerData) {
setCreateHistorical((modelerData?.inheritedEasyDataModelerDataModels?.historical)?true:false);
setCreateZipper((modelerData?.inheritedEasyDataModelerDataModels?.zipper)?true:false);
if (modelerData.inheritedEasyDataModelerDataModels?.historical) {
setHistoricalModelerData(modelerData.inheritedEasyDataModelerDataModels?.historical);
} else {
getInheriteDataModel(inheritanceHistoricalType);
}
if (modelerData.inheritedEasyDataModelerDataModels?.zipper) {
setZipperModelerData(modelerData.inheritedEasyDataModelerDataModels?.zipper);
} else {
getInheriteDataModel(inheritanceZipperType);
}
}
}, [modelerData])
const getInheriteDataModel = (type) => {
dispatch({
type: 'datamodel.inheriteDataModel',
payload: {
params: {
id: modelerData.id,
inheritanceTypeName: type
}
},
callback: data => {
if (type === inheritanceHistoricalType) {
setHistoricalModelerData(data);
} else if (type === inheritanceZipperType) {
setZipperModelerData(data);
}
}
})
}
const onHistoricalChange = (newModelerData) => {
setHistoricalModelerData(newModelerData);
}
const onZipperChange = (newModelerData) => {
setZipperModelerData(newModelerData);
}
const onOk = async () => {
try {
createHistorical && await historicalForm.validateFields();
createZipper && await zipperForm.validateFields();
const newModelerData = {...modelerData};
newModelerData.inheritedEasyDataModelerDataModels = {
...newModelerData.inheritedEasyDataModelerDataModels,
historical: createHistorical?historicalModelerData: null,
zipper: createZipper?zipperModelerData: null,
}
setConfirmLoading(true);
dispatch({
type: 'datamodel.saveDataModel',
payload: {
data: newModelerData
},
callback: data => {
setConfirmLoading(false);
reset();
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
showMessage('success', '保存成功')
onCancel?.(true);
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const reset = () => {
setHistoricalModelerData(undefined);
setZipperModelerData(undefined);
setActiveKey(inheritanceHistoricalType);
}
return (
<Modal
forceRender
visible={visible}
onCancel={() => {
reset();
onCancel?.();
}}
width='95%'
bodyStyle={{ padding: 0, background: '#F2F2F2' }}
footer={
<Space>
<Button onClick={ () => {
reset();
onCancel?.();
}}>取消</Button>
<Button type="primary" onClick={ onOk } loading={confirmLoading}>确定</Button>
</Space>
}
maskClosable={false}
centered={true}
>
{
visible && <Tabs
activeKey={activeKey}
onChange={(key) => { setActiveKey(key); }}
tabBarExtraContent={{
left: <span className='mr-5' style={{ fontSize: 16, fontWeight: 500 }}>编辑历史存储形式</span>
}}
tabBarStyle={{ backgroundColor: '#FFF', padding: '5px 20px', margin: 0 }}
>
<Tabs.TabPane
tab={
<span>
<Checkbox
className='mr-2'
checked={createHistorical}
onChange={(e) => {
setCreateHistorical(e.target.checked);
}}
>
</Checkbox>历史表
</span>
}
key={inheritanceHistoricalType}
>
{ historicalModelerData && <ImportAction form={historicalForm} action='edit-inherite-modal' roughModelerData={historicalModelerData} onChange={onHistoricalChange} /> }
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Checkbox
className='mr-2'
checked={createZipper}
onChange={(e) => {
setCreateZipper(e.target.checked);
}}
></Checkbox>拉链表
</span>
}
key={inheritanceZipperType}
>
{ zipperModelerData && <ImportAction form={zipperForm} action='edit-inherite-modal' roughModelerData={zipperModelerData} onChange={onZipperChange} /> }
</Tabs.TabPane>
</Tabs>
}
</Modal>
)
}
export default FC;
\ No newline at end of file
.edit-model {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100vh;
.edit-header {
display: flex;
flex: none;
width: 100%;
height: 44px;
padding: 0 15px;
background-color: #464d6e;
align-items: center;
justify-content: space-between;
}
.edit-container {
flex: 1;
background: #F2F2F2;
overflow: hidden;
}
.edit-container-card {
margin: 10px;
overflow: hidden;
}
.edit-footer {
display: flex;
flex: none;
width: 100%;
min-height: 64px;
max-height: 500px;
justify-content: flex-end;
background: #fff;
box-shadow: 0 -1px 4px 0 #e5e9ea;
padding: 10px 20px;
}
}
\ No newline at end of file
import React, { useState, useEffect } from "react";
import { Tooltip, Table, Typography } from 'antd';
import classnames from 'classnames';
import { Resizable } from 'react-resizable';
import ResizeObserver from 'rc-resize-observer';
import { dispatch } from '../../../../model';
import { isSzseEnv, formatDate } from '../../../../util';
// import Tag from "../../Tag";
import './ModelTable.less';
import { ModelNameColumn } from "./ModelTable";
const { Text } = Typography;
const { Column } = Table;
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 ExpandedModelTable = (props) => {
const { onItemAction, onExpandedSelect, onExpandedChange, id = null, pid = null, checked, dataMap, onContextMenu, user, visibleColNames } = props;
const [ tableWidth, setTableWidth ] = useState(0);
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ data, setData ] = useState([]);
const [ columns, setColumns ] = useState([]);
const cols = [
{
title: '模型名称',
dataIndex: 'name',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, record, index) => {
return (<ModelNameColumn text={text} record={record} detailItem={detailItem} />);
}
},
{
title: '中文名称',
dataIndex: 'cnName',
width: isSzseEnv?420:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<Text ellipsis={true}>{text||''}</Text>
</Tooltip>
)
}
},
{
title: '路径',
dataIndex: 'path',
width: 120,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<Text ellipsis={true}>{text||''}</Text>
</Tooltip>
)
}
},
{
title: '状态',
dataIndex: 'state',
width: 100,
ellipsis: true,
render: (_, record) => {
let color = '';
if (record?.state?.id === '1') {
color = '#DE7777';
} else if (record?.state?.id === '2') {
color = '#779BDE';
} else if (record?.state?.id === '4') {
color = '#77DEBF';
}
return (
<span>
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: 5, marginRight: 5, backgroundColor: color }}></span>
<span>{record?.state?.cnName||''}</span>
</span>
);
}
},
{
title: '创建人',
dataIndex: 'editor',
width: 100,
ellipsis: true,
},
{
title: '版本号',
dataIndex: 'modifiedTs',
width: 170,
ellipsis: true,
render: (_,record) => {
return `V_${formatDate(record.modifiedTs)}`;
}
},
// {
// title: '标签',
// dataIndex: 'tag',
// width: 200,
// onCell: (record) => ({
// onMouseEnter: event => {
// setMouseEnterKey(record.id);
// },
// onMouseLeave: event => {
// setMouseEnterKey(null);
// },
// }),
// render: (_,record) => {
// return (
// record.id===mouseEnterKey?<Tag styleType='complex' id={record.id} />:<Tag id={record.id} />
// );
// }
// },
{
title: '模型描述',
dataIndex: 'remark',
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''} overlayClassName='tooltip-common'>
<Text ellipsis={true}>{text||''}</Text>
</Tooltip>
);
}
},
];
useEffect(() => {
setSelectedRowKeys(checked?[id]:[]);
if (dataMap.has(id)) {
setData([dataMap.get(id)]);
} else {
getCheckoutDataModel();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if (tableWidth>0) {
let newColumns = [...cols];
if ((visibleColNames||[]).length > 0) {
newColumns = newColumns.filter(col => visibleColNames.indexOf(col.title)!==-1);
}
newColumns.forEach((column, index) => {
if (!column.width) {
const rowWidth = (newColumns.reduce((preVal, col) => (col.width?col.width:0) + preVal, 0)) + 97;
if (tableWidth - rowWidth > 200) {
column.width = tableWidth-rowWidth;
} else {
newColumns.width = 200;
}
}
});
setColumns([ ...newColumns, <Column key='auto' />]);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ tableWidth, visibleColNames ])
const getCheckoutDataModel = () => {
dispatch({
type: 'datamodel.getCheckoutDataModel',
payload: {
id: pid
},
callback: data => {
setData(data?[data]:[]);
onExpandedChange && onExpandedChange(id, data);
}
})
}
const detailItem = (record) => {
onItemAction && onItemAction(record, 'detail');
}
const onExpandedSelectChange = keys => {
setSelectedRowKeys(keys);
onExpandedSelect && onExpandedSelect(keys, data[0].id);
};
const handleResize = index => (e, { size }) => {
let nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
setColumns(nextColumns);
};
const rowSelection = {
selectedRowKeys,
onChange: onExpandedSelectChange,
hideSelectAll: true,
};
const mergedColumns = () => {
let newColumns = [...columns];
return (
newColumns.map((col, index) => ({
...col,
onHeaderCell: column => ({
width: column.width,
onResize: handleResize(index),
}),
}))
);
}
const classes = classnames('model-table', {
'model-table-sub': id
});
return (
<div className={classes}>
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width);
}}
>
<Table
rowSelection={rowSelection}
components={{
header: {
cell: ResizeableHeaderCell,
}
}}
columns={mergedColumns()}
rowKey={'id'}
dataSource={data||[]}
pagination={false}
size='small'
onRow={(record, index) => {
return {
onContextMenu: event => {
onContextMenu && onContextMenu(event, record);
},
}
}}
/>
</ResizeObserver>
</div>
);
}
export default ExpandedModelTable;
\ No newline at end of file
import { useState } from 'react';
import { Popover, Table } from 'antd';
import { MenuOutlined } from '@ant-design/icons';
const { Column } = Table;
const ColumnSelect = (props) => {
const { columns, onChange, defaultSelectedKeys } = props;
const [ selectedKeys, setSelectedKeys ] = useState(defaultSelectedKeys);
const changeSelect = (selectedRowKeys) => {
setSelectedKeys(selectedRowKeys);
onChange && onChange(selectedRowKeys);
}
const rowSelection = {
selectedRowKeys: selectedKeys,
onChange:changeSelect
}
return(
<div style={{width:230}}>
<Table
rowKey="title"
rowSelection={rowSelection}
showHeader={true}
pagination={false}
size="small"
scroll={{ y:350 }}
dataSource={columns}
>
<Column title="全选" dataIndex="title" key="title" />
</Table>
</div>
)
}
const FilterColumnAction = (props) => {
return (
<Popover
title="选择显示字段"
placement="leftTop"
content={
<ColumnSelect {...props} />
}>
<MenuOutlined />
</Popover>
);
}
export default FilterColumnAction;
\ No newline at end of file
/*------------------------------------------------*/
/* LIBRARIES
/*------------------------------------------------*/
import throttle from "lodash/throttle";
/*------------------------------------------------*/
/* CONSTANTS
/*------------------------------------------------*/
const OFFSET = 1; // This is the top/bottom offset you use to start scrolling in the div.
const PX_DIFF = 1;
/*------------------------------------------------*/
/* GLOBAL VARIABLES
/*------------------------------------------------*/
let scrollIncrement = 0;
let isScrolling = false;
let sidebarElement = null;
let scrollHeightSidebar = 0;
let clientRectBottom = 0;
let clientRectTop = 0;
/*------------------------------------------------*/
/* METHODS
/*------------------------------------------------*/
/**
* Scroll up in the sidebar.
*/
const goUp = () => {
scrollIncrement -= PX_DIFF;
sidebarElement.scrollTop = scrollIncrement;
if (isScrolling && scrollIncrement >= 0) {
window.requestAnimationFrame(goUp);
}
};
/**
* Scroll down in the sidebar.
*/
const goDown = () => {
scrollIncrement += PX_DIFF;
sidebarElement.scrollTop = scrollIncrement;
if (isScrolling && scrollIncrement <= scrollHeightSidebar) {
window.requestAnimationFrame(goDown);
}
};
const onDragOver = (event) => {
const isMouseOnTop =
scrollIncrement >= 0 &&
event.clientY > clientRectTop &&
event.clientY < clientRectTop + OFFSET;
const isMouseOnBottom =
scrollIncrement <= scrollHeightSidebar &&
event.clientY > clientRectBottom - OFFSET &&
event.clientY <= clientRectBottom;
if (!isScrolling && (isMouseOnTop || isMouseOnBottom)) {
isScrolling = true;
scrollIncrement = sidebarElement.scrollTop;
if (isMouseOnTop) {
window.requestAnimationFrame(goUp);
} else {
window.requestAnimationFrame(goDown);
}
} else if (!isMouseOnTop && !isMouseOnBottom) {
isScrolling = false;
}
};
/**
* The "throttle" method prevents executing the same function SO MANY times.
*/
const throttleOnDragOver = throttle(onDragOver, 150);
export const addEventListenerForSidebar = (elementId) => {
// In Chrome the scrolling works.
if (navigator.userAgent.indexOf("Chrome") === -1) {
sidebarElement = document.getElementById(elementId);
scrollHeightSidebar = sidebarElement.scrollHeight;
const clientRect = sidebarElement.getBoundingClientRect();
clientRectTop = clientRect.top;
clientRectBottom = clientRect.bottom;
sidebarElement.addEventListener("dragover", throttleOnDragOver);
}
};
export const removeEventListenerForSidebar = () => {
isScrolling = false;
if (sidebarElement) {
sidebarElement.removeEventListener("dragover", throttleOnDragOver);
}
};
import React from 'react';
import { Drawer, Tabs, Space, Checkbox, Button } from 'antd';
import VersionHistory from './VersionHistory';
import VersionCompare from './VersionCompare';
const { TabPane } = Tabs;
const HistoryAndVersionDrawer = (props) => {
const { onCancel, visible, id } = props;
const [activeKey, setActiveKey] = React.useState('1')
const [onlyShowChange, setOnlyShowChange] = React.useState(false)
const [alterTrigger, setAlterTrigger] = React.useState(false)
const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked);
}
const onAlterClick = () => {
setAlterTrigger(!alterTrigger)
}
return (
<Drawer
title=''
placement="right"
closable={true}
width={'90%'}
onClose={() => {
setActiveKey('1');
onCancel && onCancel();
}}
visible={visible}
>
{
visible && <Tabs activeKey={activeKey} type="card" size='small' tabBarExtraContent={
activeKey==='2'?<Space>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>仅显示差异</Checkbox>
<Button size='small' onClick={onAlterClick}>生成ALTER</Button>
</Space>:null
}
onChange={(val) => { setActiveKey(val) }}
>
<TabPane tab="版本历史" key="1">
<VersionHistory id={id} />
</TabPane>
<TabPane tab="版本对比" key="2">
<VersionCompare id={id} onlyShowChange={onlyShowChange} alterTrigger={alterTrigger} />
</TabPane>
</Tabs>
}
</Drawer>
);
}
export default HistoryAndVersionDrawer;
\ No newline at end of file
.import-action {
.yy-tabs-nav {
background-color: #EBEBEB;
margin: 0;
}
.yy-tabs-nav-list {
width: 80%;
justify-content: space-between;
}
.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 {
background-color: #FFF;
padding: 20px;
}
.model-import-action-technical,
.model-import-action-table,
.model-import-action-index,
.model-import-action-manage,
.model-import-action-relation,
.model-import-action-comment {
margin-top: 7px;
}
}
.import-action-fullsearch {
.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 {
padding: 0 20px;
}
.model-import-action-technical,
.model-import-action-table,
.model-import-action-index,
.model-import-action-manage,
.model-import-action-relation,
.model-import-action-comment {
margin-top: 0px;
}
}
\ 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 { importActionSubject } from "./ImportAction"
import './ImportActionHeader.less'
import './ImportActionComment.less'
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?.id])
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 => {
if (uploading) {
showMessage('warn', '文件上传中,请稍后')
return
}
const isLt20M = file.size / 1024 / 1024 <= 100
if (!isLt20M) {
showMessage('error', '上传文件限制最大100M')
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'>
<Space>
<h3 style={{ width: 120, 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>
{
!isCollapse && <div className='mt-3'>
<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 loading={uploading} 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='mt-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>
</div>
}
{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-control {
overflow: hidden;
}
}
.model-import-action-header-readolny {
.yy-form-item-label > label {
height: auto;
}
.yy-form-item-control-input {
min-height: 0;
}
}
.edit-template .model-import-action-table {
padding: 0;
}
\ No newline at end of file
import React from 'react'
import { Button, Form, Descriptions, Input, Row, Col, Space, Select, Spin } from 'antd'
import { DownOutlined, UpOutlined } from '@ant-design/icons'
import { Subject } from 'rxjs'
import { useDebounceEffect } from 'ahooks'
import { dispatch } from '../../../../model'
import { importActionSubject } from './ImportAction'
import './ImportActionHeader.less';
export const ImportActionHeaderSubject = new Subject();
const FC = (props) => {
const { editable, form, modelerData, action, onChange } = 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?.id])
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: {
id: modelerData?.id
},
callback: data => {
setMaintenanceRecords(data);
}
})
}
const onValuesChange = (changedValues, allValues) => {
onChange?.(changedValues, allValues);
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
return (
<div className='model-import-action-manage'>
<Space>
<h3 style={{ width: 120, marginBottom: 0 }}>管理信息</h3>
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space>
{
!isCollapse && <div className='mt-3'>
{
editable ? (
<Form form={form} {...formItemLayout} onValuesChange={onValuesChange}>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item label='技术负责人' name='technicalManagerUser' style={{ marginBottom: 15 }}>
<UsersItem />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item label='业务负责人' name='businessManagerUser' style={{ marginBottom: 15 }}>
<UsersItem />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item label='安全等级' style={{ marginBottom: 15 }}>
<span>{modelerData?.aggregateSecurityScale?.cnName}</span>
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="维护历史"
style={{ marginBottom: 0 }}
>
<Input.TextArea rows={3} disabled={true} value={maintenanceDescription} />
</Form.Item>
</Col>
</Row>
</Form>
) : (
<Descriptions column={3} size='small'>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 106 }} >技术负责人</div>}>
{`${modelerData?.technicalManagerUser?.dname}(${modelerData?.technicalManagerUser?.name})`}
</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 106 }} >业务负责人</div>}>
{`${modelerData?.businessManagerUser?.dname}(${modelerData?.businessManagerUser?.name})`}
</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 106 }} >安全等级</div>}>
{modelerData?.aggregateSecurityScale?.cnName}
</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 106 }} >维护历史</div>} >
<div style={{ maxHeight: 70, overflow: 'auto' }}>
{
(maintenanceRecords||[]).map((record, index) => {
return <div key={index}>{record||''}</div>;
})
}
</div>
</Descriptions.Item>
</Descriptions>
)
}
</div>
}
</div>
)
}
export default FC
const UsersItem = ({ value, onChange }) => {
const [loading, setLoading] = React.useState(false)
const [searchValue, setSearchValue] = React.useState()
const [options, setOptions] = React.useState()
useDebounceEffect(() => {
if (searchValue) {
getUsers()
} else {
setOptions()
}
}, [searchValue], { wait: 300 })
const getUsers = () => {
setLoading(true)
dispatch({
type: 'datamodel.getCooperationUsers',
payload: {
match: searchValue,
},
callback: data => {
setLoading(false)
setOptions(
(data??[]).map(item => ({
label: `${item.dname}(${item.name})`,
value: `${item.dname}(${item.name})`,
...item
}))
)
},
error: () => {
setLoading(false)
}
})
}
return (
<Select allowClear showSearch
placeholder='请选择用户'
value={value?`${value.dname}(${value.name})`:undefined}
searchValue={searchValue}
onSearch={(val) => {
setSearchValue(val)
}}
onClear={() => {
setSearchValue()
}}
filterOption={false}
notFoundContent={loading ? <Spin size="small" /> : null}
options={options}
onChange={(val) => {
if (val) {
const index = (options??[]).findIndex(item => item.value === val)
if (index !== -1) {
onChange?.(options[index])
}
} else {
onChange?.()
}
}}
/>
)
}
\ No newline at end of file
import React from "react"
import { Button, Descriptions, Space, Popover } from "antd"
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { Action, ModelerId, PermitCheckOut, Editable, StateId, ReadOnly } from '../../../../util/constant'
import { importActionSubject } from "./ImportAction"
import { dispatch } from '../../../../model';
import './ImportActionHeader.less'
import { openMetadataDetail } from "../../../../util"
export const inheritanceHistoricalType = 'historical'
export const inheritanceZipperType = 'zipper'
const FC = (props) => {
const { modelerData, action } = props
const [isCollapse, setCollapse] = React.useState(true)
const [relationModelerDatas, setRelationModelerDatas] = React.useState([])
const [associationMetadata, setMetadata] = React.useState()
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 = [];
newRelationModelerDatas.push(modelerData?.inheritedFromEasyDataModelerDataModel);
if (modelerData?.otherEasyDataModelerDataModelsInheritedFromSameOrigin) {
Object.keys(modelerData.otherEasyDataModelerDataModelsInheritedFromSameOrigin).forEach(key => {
newRelationModelerDatas.push(modelerData.otherEasyDataModelerDataModelsInheritedFromSameOrigin[key]);
})
}
setRelationModelerDatas(newRelationModelerDatas);
} else {
const newRelationModelerDatas = [];
if (modelerData?.inheritedEasyDataModelerDataModels?.historical) {
newRelationModelerDatas.push(modelerData?.inheritedEasyDataModelerDataModels?.historical);
}
if (modelerData?.inheritedEasyDataModelerDataModels?.zipper) {
newRelationModelerDatas.push(modelerData?.inheritedEasyDataModelerDataModels?.zipper);
}
setRelationModelerDatas(newRelationModelerDatas);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
React.useEffect(() => {
if (modelerData?.id) {
getAssociationMetadata()
}
}, [modelerData?.id])
const getAssociationMetadata = () => {
dispatch({
type: 'datamodel.findAssociationMetadataByModelId',
payload: {
easyDataModelerDataModelId: modelerData?.id,
},
callback: data => {
setMetadata(data)
}
})
}
return (
<div className='model-import-action-relation'>
<Space>
<Space style={{ width: 120 }}>
<h3 style={{ marginBottom: 0 }}>关联对象</h3>
{
action==='add' && <Popover content='保存当前模型后方可选择历史存储形式'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
</Space>
{
isCollapse ? <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={() => {
setCollapse(!isCollapse)
}}>收起<UpOutlined /></Button>
}
</Space>
{
!isCollapse && <Descriptions className='mt-3' column={3}>
<Descriptions.Item
label={
<div style={{ textAlign: 'right', width: 106 }}>
元数据
</div>}
>
<span>
{
!associationMetadata?.metadataPath ? '暂无信息' : <a className='mr-3' onClick={() => {
openMetadataDetail(associationMetadata?.metadataId)
}}>
{associationMetadata?.metadataPath}
</a>
}
</span>
</Descriptions.Item>
<Descriptions.Item
label={
<div style={{ textAlign: 'right', width: 106 }}>
历史存储形式
</div>}
>
<span>
{
relationModelerDatas?.length===0 ? '暂无信息' :
relationModelerDatas?.map((item, index) => (
<a className='mr-3' key={index} onClick={() => {
window.open(`/data-govern-bj/data-model-action?${Action}=detail&${ModelerId}=${item.id}&${PermitCheckOut}=${item.permitCheckOut||false}&${Editable}=${item.editable||false}&${StateId}=${item.state?.id||''}&${ReadOnly}=false`);
}}>
{item.cnName}
</a>
))
}
</span>
</Descriptions.Item>
</Descriptions>
}
</div>
)
}
export default FC
\ No newline at end of file
.model-import-action-table {
.yy-table-expanded-row {
.yy-radio-wrapper {
white-space: normal !important;
}
}
.yy-table {
.yy-card-body {
padding: 0 !important;
}
}
.primary-row {
.yy-table-cell {
background-color: #d3ebff !important;
}
}
.gray-row {
.yy-table-cell {
background-color: #f7f7f7 !important;
}
}
}
\ No newline at end of file
import React from 'react';
import { Input } from 'antd';
class ImportDDL extends React.Component {
constructor() {
super();
this.state = {
inputValue: ''
};
}
componentDidUpdate(preProps, preState) {
const { visible } = this.props;
if (!visible && visible !== preProps.visible) {
this.setState({ inputValue: '' });
}
}
onInputChange = (e) => {
const { onChange } = this.props;
this.setState({ inputValue: e.target.value }, () => {
onChange && onChange(e.target.value);
});
}
render() {
const { inputValue } = this.state;
const _placeholder = '支持两种方式创建\n方式一: DDL内容复制粘贴\n方式二: 手动输入DDL';
return (
<Input.TextArea
value={inputValue||''}
onChange={this.onInputChange}
autoSize={{minRows:4,maxRows:20}}
placeholder={_placeholder}
>
</Input.TextArea>
)
}
}
export default ImportDDL;
\ No newline at end of file
import React from 'react';
import { Button, Upload, Form, Row, Col } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
class ImportExcel extends React.Component {
constructor() {
super();
this.state = {
fileList: []
};
}
componentDidUpdate(preProps, preState) {
const { visible } = this.props;
if (!visible && visible !== preProps.visible) {
this.setState({ fileList: [] });
}
}
downloadTemplate = () => {
window.open("/data-govern-bj/docs/DataModel.xlsx");
}
normFile = (e) => {
const { fileList } = this.state;
return fileList;
};
render() {
const { onChange } = this.props;
const { fileList } = this.state;
const uploadProps = {
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
this.setState({ fileList: newFileList });
},
beforeUpload: file => {
onChange && onChange([file]);
this.setState({ fileList: [file] });
return false;
},
accept:".xlsx",
fileList: fileList||[]
};
return (
<Form.Item
label='文件上传'
required={true}
>
<Row>
<Col span={9}>
<Form.Item
name='upload'
valuePropName="fileList"
getValueFromEvent={this.normFile}
noStyle
rules={[
{
required: true,
message: '请选择文件上传',
},
]}
>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
</Col>
<Col span={6}>
<Button icon={<DownloadOutlined />} onClick={ this.downloadTemplate }>
模版下载
</Button>
</Col>
</Row>
</Form.Item>
)
}
}
export default ImportExcel;
\ No newline at end of file
import React from 'react';
import { Input, Row, Col, Descriptions } from 'antd';
class ImportExcelCopy extends React.Component {
constructor() {
super();
this.state = {
inputValue: '',
translateValues: []
};
}
componentDidUpdate(preProps, preState) {
const { visible } = this.props;
if (!visible && visible !== preProps.visible) {
this.setState({ inputValue: '', translateValues: [] });
}
}
onInputChange = (e) => {
const { onChange } = this.props;
this.setState({ inputValue: e.target.value }, () => {
const _translateValues = e.target.value.replace(/[\s,,,]/g,'\n').split('\n').filter(value => value!==''&&value!==' ');
onChange && onChange(_translateValues);
this.setState({ translateValues: _translateValues });
});
}
render() {
const { inputValue, translateValues } = this.state;
const _placeholder = '请在此输入表名、字段中文名,支持以空格、逗号或换行符作为分隔符。如"证券资料表 证券名称 账户代码,证券代码"';
let _modelName = '', _attrsStr = '';
if (translateValues.length>0) {
_modelName= translateValues[0];
}
translateValues.forEach((item, index) => {
if (index === 0) {
_attrsStr = '';
} else if (index === 1) {
_attrsStr = item;
} else {
_attrsStr += `,${item}`;
}
})
return (
<Row gutter={10}>
<Col span={14}>
<Input.TextArea
value={inputValue||''}
onChange={this.onInputChange}
autoSize={{minRows:4,maxRows:20}}
placeholder={_placeholder}
>
</Input.TextArea>
</Col>
<Col span={10}>
<Descriptions className='excel-copy-descritpion' column={1} size='small' style={{ maxHeight: 450, overflow: 'auto' }}>
<Descriptions.Item label='模型名称'>
{ _modelName }
</Descriptions.Item>
<Descriptions.Item label='字段名称'>
{ _attrsStr }
</Descriptions.Item>
</Descriptions>
</Col>
</Row>
)
}
}
export default ImportExcelCopy;
\ No newline at end of file
import React from 'react';
import { Table, Space, Tooltip, Button, Pagination, Divider } from 'antd';
import { ReconciliationOutlined } from '@ant-design/icons';
import { paginate } from '../../../../util';
const columns = [
{
title: '导入名称',
dataIndex: 'name',
},
{
title: '导入方式',
dataIndex: 'model',
},
{
title: '开始时间',
dataIndex: 'startTime',
},
{
title: '结束时间',
dataIndex: 'endTime'
},
{
title: '状态',
dataIndex: 'status'
},
{
title: '操作',
key: 'action',
render: (text,record) => {
return (
<Space size='small'>
<Tooltip placement='bottom' title={'详情'}>
<Button icon={<ReconciliationOutlined />} size='small' />
</Tooltip>
</Space>
)
}
}
];
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `t_szse_transaction${i}`,
model: 'excel导入',
startTime: '2020-01-01 00:00:01',
endTime: '2020-01-01 00:10:01',
status: '成功'
});
}
class ImportLog extends React.Component {
constructor() {
super();
this.state = {
pageNum: 1,
pageSize: 10
};
}
render() {
const { pageNum, pageSize } = this.state;
const _data = paginate(data, pageNum, pageSize);
return (
<>
<Divider orientation="left">导入日志</Divider>
<Table
columns={columns}
dataSource={_data}
pagination={false}
size='small'
/>
<Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={(_pageNum, _pageSize) => {
this.setState({ pageNum: _pageNum, pageSize: _pageSize || 10 });
}}
onShowSizeChange={(_pageNum, _pageSize) => {
this.setState({ pageNum: _pageNum || 1, pageSize: _pageSize });
}}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={(data||[]).length}
showTotal={total => `共 ${total} 条`}
/>
</>
)
}
}
export default ImportLog;
\ No newline at end of file
import React from 'react';
import { Select, Pagination, Input, Table, Row, Col } from "antd"
import { dispatchLatest } from '../../../../model';
const { Option } = Select;
class ImportMetadata extends React.Component {
constructor() {
super();
this.state = {
systems: [],
dbs: [{ value: '', name: '所有数据源' }],
schemas: [{ value: '', name: '所有Schema' }],
modelPaths: [
{ value: '', name: '所有类型' },
{ value: 'Catalog,Database,Schema,HanaView', name: 'HANA视图' },
{ value: 'Catalog,Database,Schema,Table', name: '数据表' },
{ value: 'Catalog,Database,Schema,View', name: '数据视图' },
{ value: 'Catalog,Database,Schema,Function', name: '函数' },
{ value: 'Catalog,Database,Schema,Procedure', name: '存储过程' },
],
dataTables: [],
totalDataTables: 0,
dataFileds: [],
system: '',
db: '',
schema: '',
modelPath: '',
keyboard: '',
pageNumDataTables: 1,
pageSizeDataTables: 20,
loadingDataTable: false,
loadingDataFiled: false,
loadingSystem: false,
loadingDb: false,
loadingSchema: false,
selectedDataTableRowKeys: [],
selectedDataFiledRowKeys: [],
dataTableColumns: [
{ title: '名称', dataIndex: 'name', key: 'name' },
{ title: '中文名称', dataIndex: 'cnName', key: 'cnName' },
{ title: '描述', dataIndex: 'comment', key: 'comment' },
],
dataFiledColumns: [
{ title: '目标表字段', dataIndex: 'name', key: 'name' },
]
};
}
componentDidMount() {
this.setState({ loadingSystem: true, loadingDb: true }, () => {
dispatchLatest({
type: 'datamodel.getAllSystemAndDatabase',
callback: data => {
if (data && data.systems && data.systems.length) {
const _system = data.systems[0].scopeId;
this.setState({ loadingSystem: false, loadingDb: false, systems: data.systems, system: _system, dbs: data.dbs, db: '', schema: '' }, () => {
this.fetchAllDataTable();
})
} else {
this.setState({ loadingSystem: false });
}
},
error: () => {
this.setState({ loadingSystem: false });
}
})
})
}
systemOnChanged = (value) => {
if (value === '') {
this.setState({
system: value,
dbs: [{ value: '', name: '所有数据源' }],
schemas: [{ value: '', name: '所有Schema' }],
db: '',
schema: ''
})
} else {
this.setState({ loadingDb: true, system: value }, () => {
dispatchLatest({
type: "datamodel.getAllSystemDatabase",
payload: {
sysCodedataTables: value
},
callback: dbs => {
this.setState({ loadingDb: false, dbs: dbs||[], db: '', schema: '' })
},
erorr: () => {
this.setState({ loadingDb: false });
}
});
});
}
}
dbOnChanged = (value) => {
if (value === '') {
this.setState({ db: value, schemas: [{ value: '', name: '所有Schema' }], schema: '' })
} else {
this.setState({ loadingSchema: true, db: value }, () => {
dispatchLatest({
type: "datamodel.getAllScheme",
payload: {
db: value
},
callback: schemas => {
this.setState({ loadingSchema: false, schemas, schema: '' });
},
error: () => {
this.setState({ loadingSchema: false });
}
});
})
}
}
schemaOnChanged = (value) => {
this.setState({ schema: value });
}
modelPathOnChanged = (value) => {
this.setState({ modelPath: value });
}
sizeChangeOnDataTable = (pageNum, pageSize=20) => {
this.setState({ pageNumDataTables: pageNum, pageSizeDataTables: pageSize });
}
sizeChangeOnDataField = (pageNum, pageSize=20) => {
this.setState({ pageNumDataFileds: pageNum, pageSizeDataFileds: pageSize });
}
fetchAllDataTable = () => {
const { system, db, schema, modelPath, pageNumDataTables, pageSizeDataTables, keyboard } = this.state;
let _db = '';
if(db !== '' && db.split(',').length > 1) {
_db = db.split(',')[1];
}
this.setState({ loadingDataTable: true }, () => {
dispatchLatest({
type: "datamodel.getAllDataTable",
payload: {
pageNum: pageNumDataTables,
pageSize: pageSizeDataTables,
name: keyboard,
sysId: system,
database: _db,
schema,
modelPath
},
callback: data => {
this.setState({ loadingDataTable: false, dataTables: (data||[]).content||[], totalDataTables: (data||[]).totalElements||0 })
},
error: () => {
this.setState({ loadingDataTable: false });
}
});
})
}
render() {
const { systems, dbs, schemas, modelPaths, system, db, schema, modelPath, pageNumDataTables, pageSizeDataTables, loadingDataTable, loadingDataFiled, loadingSystem, loadingDb, loadingSchema, dataTables, dataFileds, totalDataTables, dataTableColumns, dataFiledColumns } = this.state;
const rowDataTableSelection = {
type: 'radio',
onChange: (selectedRowKeys, selectedRows) => {
this.setState({ selectedDataTableRowKeys: selectedRowKeys, loadingDataFiled: true }, () => {
dispatchLatest({
type: "datamodel.getAllFileds",
payload: {
parentId: selectedRowKeys[0],
model:'Column,InterfaceColumn,HanaViewColumn'
},
callback: data => {
this.setState({ loadingDataFiled: false, dataFileds: data||[] })
},
error: () => {
this.setState({ loadingDataFiled: false });
}
});
})
},
};
const rowDataFiledSelection = {
type: 'radio',
onChange: (selectedRowKeys, selectedRows) => {
this.setState({ selectedDataFiledRowKeys: selectedRowKeys });
},
};
return (
<>
<div style={{paddingTop:10}}>
<Select
value={system}
style={{ width: 146 }}
loading={loadingSystem}
onChange={this.systemOnChanged}
>
{
systems && systems.map((item, index) => {
return <Option key={index} value={item.scopeId}>{item.scopeName||''}</Option>;
})
}
</Select>
<Select
value={db}
onChange={this.dbOnChanged}
loading={loadingDb}
style={{ width: 150,marginLeft:10 }}
>
{
dbs && dbs.map((item, index) => {
return <Option key={index} value={item.value===''?'':(item._id+','+item.name)}>{item.name}</Option>;
})
}
</Select>
<Select
value={schema}
loading={loadingSchema}
onChange={this.schemaOnChanged}
style={{ width: 150,marginLeft:10 }}
>
{
schemas && schemas.map((item, index) => {
return <Option key={index} value={item.value===''?'':item.name}>{item.name}</Option>;
})
}
</Select>
<Select
value={modelPath}
onChange={this.modelPathOnChanged}
style={{ width: 150,marginLeft:10 }}
>
{
modelPaths && modelPaths.map((item, index) => {
return <Option key={index} value={item.value}>{item.name}</Option>;
})
}
</Select>
</div>
<div style={{margin:'10px 0'}}>
<Input.Search style={{width:180,marginLeft:10}}
onChange={e=>{ this.setState({ keyboard: e.target.value })}}
placeholder={"请输入关键字"}
enterButton
onSearch={()=>{this.fetchAllDataTable()}}
/>
</div>
<Row gutter={10}>
<Col span={12}>
<Table
columns={dataTableColumns}
rowKey="_id"
size="small"
loading={loadingDataTable}
dataSource={dataTables}
pagination={false}
rowSelection={rowDataTableSelection}
/>
<Pagination
showTotal={total => `共 ${total} 条`}
showSizeChanger
size="small"
pageSize={pageSizeDataTables}
pageSizeOptions={['20','60','100']}
current={pageNumDataTables}
onShowSizeChange={this.sizeChangeOnDataTable}
onChange={this.sizeChangeOnDataTable}
total={totalDataTables}
style={{marginTop:10}}
/>
</Col>
<Col span={12}>
<Table
columns={dataFiledColumns}
rowKey="_id"
size="small"
loading={loadingDataFiled}
dataSource={dataFileds}
pagination={false}
rowSelection={rowDataFiledSelection}
/>
</Col>
</Row>
</>
)
}
}
export default ImportMetadata;
\ No newline at end of file
import React, { useState } from 'react';
import { Modal, Button, Form, Radio, Tooltip } from 'antd';
import ImportWord from './ImportWord';
import ImportExcel from './ImportExcel';
import ImportExcelCopy from './ImportExcelCopy';
import ImportDDL from './ImportDDL';
import { dispatchLatest } from '../../../../model';
import { isSzseEnv } from '../../../../util';
const importModes = [
{ name: '快速创建', key: 'excel-copy' },
{ name: '空白创建', key: 'no-condition' },
{ name: 'Word导入', key: 'word' },
{ name: 'Excel导入', key: 'excel' },
{ name: 'DDL导入', key: 'ddl' },
]
const ImportModal = (props) => {
const { view, catalogId, visible, onCancel, onCancelByWord, onCancelByDDL, branchId } = props;
const [ modeKey, setModeKey ] = useState('excel-copy');
const [ hints, setHints ] = useState([]);
const [ ddl, setDDL ] = useState('');
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ form ] = Form.useForm();
const onModeChange = (e) => {
setModeKey(e.target?.value);
}
const onOk = async() => {
try {
const row = await form.validateFields();
if (modeKey==='word') {
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.importWordGenerateModelDraft',
payload: {
params: {
catalogId,
branchId,
},
fileList: row.upload
},
callback: data => {
setConfirmLoading(false);
reset();
onCancelByWord && onCancelByWord(true, data||{});
},
error: () => {
setConfirmLoading(false);
}
});
} else if (modeKey==='excel') {
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.extractExcelContent',
payload: { fileList: row.upload },
callback: data => {
setConfirmLoading(false);
reset();
onCancel && onCancel(false, true, data||[]);
},
error: () => {
setConfirmLoading(false);
}
})
} else if (modeKey==='excel-copy') {
reset();
onCancel && onCancel(false, true, hints||[]);
} else if (modeKey==='no-condition') {
reset();
onCancel && onCancel(false, true, []);
} else if (modeKey==='ddl') {
reset();
onCancelByDDL && onCancelByDDL(true, ddl);
}
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const cancel = () => {
reset();
onCancel && onCancel();
}
const reset = () => {
form.resetFields();
form.setFieldsValue({mode: 'excel-copy'});
setModeKey('excel-copy');
setHints([]);
setDDL('');
setConfirmLoading(false);
}
const onImportExcelCopyChange = (data) => {
setHints(data||[]);
}
const onImportDDLChange = (data) => {
setDDL(data);
}
const footer = [
<Button
key="0"
onClick={cancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
loading={confirmLoading}
onClick={onOk}
>
确定
</Button>,
];
return (
<Modal
forceRender
visible={visible}
title='新建模型'
width={isSzseEnv?540:630}
onCancel={cancel}
footer={footer}
>
<Form form={form}>
<Form.Item
name='mode'
label='新建方式'
rules={[
{
required: true,
message: '请选择新建方式',
},
]}
initialValue={modeKey}
>
<Radio.Group onChange={onModeChange} value={modeKey}>
{
importModes.map((item, index) => {
let title = '';
if (item.key==='word'&&(view==='state'||!catalogId)) {
title = '请先选择主题';
}
if (isSzseEnv && item.key === 'ddl') {
return <></>;
}
return (
<Tooltip key={index} title={title}>
<Radio
value={item.key}
disabled={item.key==='word'&&(view==='state'||!catalogId)}>
{item.name}
</Radio>
</Tooltip>
);
})
}
</Radio.Group>
</Form.Item>
{
modeKey==='word' && (
<ImportWord {...props} />
)
}
{
modeKey==='excel' && (
<ImportExcel {...props} />
)
}
{
modeKey==='excel-copy' && (
<ImportExcelCopy onChange={onImportExcelCopyChange} {...props} />
)
}
{
modeKey==='ddl' && (
<ImportDDL onChange={onImportDDLChange} />
)
}
</Form>
</Modal>
)
}
export default ImportModal;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Button, Upload, Drawer, Table, Pagination, Form, Tooltip, Typography } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { Resizable } from 'react-resizable';
import { dispatch, dispatchLatest } from '../../../../model';
import { showMessage, formatDate } from '../../../../util';
const { Text } = Typography;
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 ImportStockWordModal = (props) => {
const { onCancel, onSuccess, visible, catalogId } = props;
const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ loading, setLoading ] = useState(false);
const [ logs, setLogs ] = useState([]);
const [ pagination, setPagination ] = useState( { pageNum: 1, pageSize: 20 } );
const { pageNum, pageSize } = pagination;
const [ total, setTotal ] = useState(0);
const cols = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '导入文件名',
dataIndex: 'fileName',
width: 200,
ellipsis: true,
render: (text, _, __) => {
return <Tooltip title={text||''}>
<Text ellipsis={true}>{text||''}</Text>
</Tooltip>
}
},
{
title: '开始时间',
dataIndex: 'startTime',
width: 170,
ellipsis: true,
render: (_, record, __) => {
return formatDate(record.startTime);
}
},
{
title: '结束时间',
dataIndex: 'endTime',
width: 170,
ellipsis: true,
render: (_, record, __) => {
return formatDate(record.endTime);
}
},
{
title: '耗时',
dataIndex: 'costTime',
width: 100,
ellipsis: true,
render: (_, record, __) => {
return record.costTime?`${Number(record.costTime/1000)}秒`:'';
}
},
{
title: '导入人',
dataIndex: 'operator',
width: 100,
ellipsis: true,
},
{
title: '导入状态',
dataIndex: 'state',
width: 100,
ellipsis: true,
},
{
title: '',
dataIndex: 'auto',
}
]
const [ columns, setColumns ] = useState(cols);
useEffect(() => {
if (visible) {
setPagination({ pageNum: 1, pageSize: 20 });
getLogs();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
const getLogs = (p = 1, s = 20) => {
setLoading(true);
dispatch({
type: 'datamodel.importWordLogs',
payload: {
params: {
page: p,
pageSize: s
}
},
callback: data => {
setLoading(false);
setTotal(data.totalElements);
setLogs(data.content||[]);
},
error: () => {
setLoading(false);
}
})
}
const onRefreshClick = () => {
getLogs();
}
const uploadProps = {
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
beforeUpload: file => {
setFileList([file]);
return false;
},
fileList: fileList || [],
accept: ".doc,.docx",
};
const handleOk = () => {
if ((fileList || []).length === 0) {
showMessage('info', '请先选择Word文件上传');
return;
}
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.importWordGenerateModel',
payload: {
params: {
catalogId,
stateId: ''
},
fileList
},
callback: data => {
setConfirmLoading(false);
setFileList([]);
getLogs(pageNum, pageSize);
onSuccess && onSuccess();
},
error: () => {
setConfirmLoading(false);
}
})
}
const reset = () => {
setConfirmLoading(false);
setFileList([]);
}
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),
}),
};
})
);
}
return (
<Drawer
forceRender
visible={ visible }
title='存量模型导入'
width={1000}
placement="right"
closable={ true }
onClose={() => {
reset();
onCancel && onCancel();
}}
>
<div className='mt-3'>
<Form layout='inline'>
<Form.Item label='Word上传:'>
<Upload style={{ display: 'inline' }} {...uploadProps }>
<Button icon={
<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
<Form.Item>
<Button type='primary' onClick={handleOk} loading={confirmLoading}>确定导入</Button>
</Form.Item>
</Form>
</div>
<div className='d-flex my-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<h3 style={{ marginBottom: 0 }}>导入日志</h3>
<Button onClick={onRefreshClick}>刷新</Button>
</div>
<Table
className='mt-3'
components={{
header: {
cell: ResizeableHeaderCell,
}
}}
columns={mergedColumns()}
rowKey={'id'}
dataSource={logs||[]}
pagination={false}
loading={loading}
expandable={{
expandedRowRender: record => <p style={{ margin: 0 }}>{record.message||''}</p>
}}
sticky
/>
<Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum||1, pageSize: _pageSize || 20 });
getLogs(_pageNum||1, _pageSize||20);
}}
onShowSizeChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum || 1, pageSize: _pageSize || 20 });
getLogs(_pageNum||1, _pageSize||20);
}}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={total}
pageSizeOptions={[10,20]}
showTotal={total => `共 ${total} 条`}
/>
</Drawer>
)
}
export default ImportStockWordModal;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Button, Upload, Form } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
const ImportWord = (props) => {
const { onChange, visible } = props;
const [ fileList, setFileList ] = useState([]);
useEffect(() => {
setFileList([]);
}, [visible])
const normFile = () => {
return fileList;
}
const uploadProps = {
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
onChange && onChange(newFileList);
setFileList(newFileList);
},
beforeUpload: file => {
setFileList([file]);
return false;
},
fileList: fileList||[],
accept:".doc,.docx",
};
return (
<Form.Item
name='upload'
label='文件上传'
valuePropName="fileList"
getValueFromEvent={normFile}
rules={[
{
required: true,
message: '请选择文件上传',
},
]}
>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
)
}
export default ImportWord;
\ No newline at end of file
export const UnAttentionSvg = (props) => (
<svg
className="icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
{...props}
>
<defs>
<style>
{
'@font-face{font-family:feedback-iconfont;src:url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944) format("woff2"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944) format("woff"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944) format("truetype")}'
}
</style>
</defs>
<path
d="M873.813 989.867c-3.413 0-8.533-1.707-11.946-3.414L512 805.547 162.133 986.453c-8.533 3.414-17.066 3.414-25.6-1.706-6.826-5.12-11.946-13.654-11.946-22.187V126.293c0-51.2 40.96-92.16 92.16-92.16h588.8c51.2 0 92.16 40.96 92.16 92.16v837.974c0 8.533-5.12 17.066-11.947 22.186-3.413 1.707-6.827 3.414-11.947 3.414zM512 750.933c3.413 0 8.533 1.707 11.947 3.414L848.213 921.6V126.293c0-22.186-18.773-40.96-40.96-40.96H216.747c-22.187 0-40.96 18.774-40.96 40.96V921.6l324.266-167.253c3.414-1.707 8.534-3.414 11.947-3.414z"
fill="#333"
/>
<path
d="M605.867 590.507c-6.827 0-13.654-1.707-20.48-5.12L512 546.133l-75.093 39.254c-13.654 8.533-32.427 6.826-44.374-3.414-13.653-10.24-20.48-25.6-17.066-40.96l13.653-81.92-61.44-59.733c-11.947-11.947-15.36-27.307-10.24-42.667s18.773-25.6 34.133-29.013l83.627-11.947 37.547-75.093c6.826-15.36 22.186-23.893 37.546-23.893s30.72 8.533 37.547 23.893l37.547 75.093 83.626 11.947c15.36 1.707 29.014 13.653 34.134 29.013s1.706 32.427-10.24 42.667l-61.44 59.733 13.653 81.92c3.413 15.36-3.413 32.427-17.067 40.96-5.12 5.12-13.653 8.534-22.186 8.534zM512 493.227c6.827 0 13.653 1.706 20.48 5.12l63.147 34.133-11.947-68.267c-1.707-13.653 1.707-27.306 11.947-37.546l51.2-51.2-69.974-10.24c-13.653-1.707-25.6-10.24-32.426-23.894L512 276.48l-30.72 64.853c-6.827 11.947-18.773 20.48-32.427 23.894l-71.68 10.24 51.2 51.2c10.24 10.24 15.36 23.893 11.947 37.546l-11.947 68.267 63.147-34.133c6.827-3.414 13.653-5.12 20.48-5.12z"
fill="#333"
/>
</svg>
)
export const AttentionSvg = (props) => (
<svg
className="icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
{...props}
>
<defs>
<style>
{
'@font-face{font-family:feedback-iconfont;src:url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944) format("woff2"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944) format("woff"),url(//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944) format("truetype")}'
}
</style>
</defs>
<path
d="M826.027 34.133H197.973c-37.546 0-68.266 30.72-68.266 68.267v887.467L512 791.893l382.293 197.974V102.4c0-37.547-30.72-68.267-68.266-68.267zm-148.48 337.92L612.693 435.2c-3.413 3.413-5.12 10.24-5.12 15.36l15.36 87.04c1.707 13.653-11.946 23.893-23.893 17.067L520.533 512c-5.12-3.413-10.24-3.413-15.36 0l-78.506 42.667c-11.947 6.826-27.307-3.414-23.894-17.067l15.36-87.04c1.707-5.12 0-10.24-5.12-15.36l-64.853-63.147c-10.24-10.24-5.12-27.306 8.533-29.013l88.747-13.653c5.12 0 10.24-3.414 11.947-8.534l39.253-80.213c6.827-11.947 23.893-11.947 30.72 0l39.253 80.213c1.707 5.12 6.827 8.534 11.947 8.534l88.747 13.653c13.653 1.707 18.773 18.773 10.24 29.013z"
fill='#196AD2'
/>
</svg>
)
@import '../../../../variables.less';
.model-table {
.yy-pro-table {
.yy-card-body {
padding: 0 !important;
}
}
.yy-divider-vertical {
margin: 0 2px !important;
}
}
.model-table-sub {
.yy-table {
height: auto !important;
}
.yy-table-placeholder {
display: none;
}
.yy-table-tbody tr:not(.yy-table-measure-row) td {
padding: 8px 8px !important;
}
}
.model-digest-descritpion {
.yy-descriptions-row > td {
padding-bottom: 0px !important;
}
}
.excel-copy-descritpion {
.yy-descriptions-row > td {
padding-bottom: 0px !important;
}
}
\ No newline at end of file
@import '../../../../variables.less';
.model-tree {
.yy-tree-list {
height: calc(100vh - @header-height - @breadcrumb-height - 25px - 57px - @pm-3 - 40px) !important;
overflow: auto !important;
}
}
.model-tree-recatalog {
.yy-tree-list {
height: calc(60vh) !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React, { useState } from "react";
import { Modal } from "antd";
import { dispatch } from '../../../../model';
import ModelTree from './ModelTree';
import { showMessage, showNotifaction } from '../../../../util';
const RecatalogModal = (props) => {
const { onCancel, visible, ids } = props;
const [ catalogId, setCatalogId ] = useState('');
const [ confirmLoading, setConfirmLoading ] = useState(false);
const onSelect = (value) => {
setCatalogId(value);
}
const onOk = () => {
if ((catalogId||'') === '') {
showMessage('warn', '请先选择模型目录');
return;
}
setConfirmLoading(true);
dispatch({
type: 'datamodel.recatalogDataModel',
payload: {
params: {
easyDataModelCatalogId: catalogId,
easyDataModelerDataModelIds: ids.join(',')
},
},
callback: message => {
setConfirmLoading(false);
if ((message||'')!=='' && (message||'')!=='ok') {
showNotifaction('提示', message, 5);
} else {
showMessage('success', '变更目录成功');
}
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
}
const reset = () => {
setConfirmLoading(false);
setCatalogId('');
}
return(
<div>
{
visible && <Modal
title='变更目录详情'
visible={ visible }
width={ 400 }
confirmLoading={ confirmLoading }
onCancel={()=>{
reset();
onCancel && onCancel()
}}
centered destroyOnClose
onOk={ onOk }
bodyStyle={{ padding: '15px' }}
>
<ModelTree
refrence='recatalog'
onSelect={onSelect}
/>
</Modal>
}
</div>
)
}
export default RecatalogModal;
\ No newline at end of file
import React, { useState } from 'react';
import { Modal, Form, Input } from 'antd';
import LocalStorage from 'local-storage';
import { dispatchLatest } from '../../../../model';
import { showNotifaction } from '../../../../util';
const StartFlowModal = (props) => {
const { visible, onCancel, ids } = props;
const [ form ] = Form.useForm();
const [ confirmLoading, setConfirmLoading ] = useState(false);
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
const handleOk = async () => {
try {
const values = await form.validateFields();
setConfirmLoading(true);
dispatchLatest({
type: 'user.fetchSessionInfo',
callback: session => {
dispatchLatest({
type: 'datamodel.startFlow',
payload: {
params: {
user: session?.userName,
modelIds: ids.join(','),
flowDesc: values?.desc
}
},
callback: data => {
reset();
if ((data||'') !== '') {
showNotifaction('送审提示', data, 5);
}
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
let event = new Event('storage');
event.key = 'modelChange';
window?.dispatchEvent(event);
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
}
}
const reset = () => {
setConfirmLoading(false);
form.resetFields();
}
return (
<Modal
forceRender
visible={visible}
title='模型送审'
width={520}
confirmLoading={confirmLoading}
onCancel={() => {
reset();
onCancel && onCancel();
}}
onOk={handleOk}
>
<Form
{...formItemLayout}
form={form}
>
<Form.Item
label="送审内容"
name="desc"
rules={[{ required: true, message: '请填写送审内容' }]}
>
<Input.TextArea rows={6} />
</Form.Item>
</Form>
</Modal>
);
}
export default StartFlowModal;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Radio } from 'antd';
import { dispatchLatest } from '../../../../model';
class UpdateTreeItemForm extends React.Component {
constructor(props){
super(props);
this.state = {
radioDisable: false
}
}
componentDidMount() {
this.radioState();
}
componentDidUpdate(preProps, preState) {
const { item } = this.props;
if (item!==preProps.item) {
this.radioState();
}
}
radioState = () => {
const { item } = this.props;
this.setState({ radioDisable: item? false: true })
}
render() {
const { type, form } = this.props;
const { radioDisable } = this.state;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
return (
<Form
{...formItemLayout}
form={form}
>
{
type==='add'&&<Form.Item label="目录类型" name="action">
<Radio.Group disabled={radioDisable} >
<Radio value='root'>根目录</Radio>
<Radio value='sub'>子目录</Radio>
</Radio.Group>
</Form.Item>
}
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="描述"
name="remark"
rules={[{ required: true, message: '请输入描述!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="数据平台"
name="platformInfo"
>
<Input />
</Form.Item>
</Form>
);
}
}
const UpdateTreeItemModal = (props) => {
const { onOk, type, item, onCancel, visible, rootId } = props;
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
let _action = '';
if (type === 'add') {
_action = item ? 'sub' : 'root';
}
form.setFields([{ name: 'name', errors: [] }, { name: 'remark', errors: [] }, { name: 'platformInfo', errors: [] }]);
if (type === 'add') {
form.setFieldsValue({ action: _action, name: '', remark: '', platformInfo: '' });
} else {
form.setFieldsValue({ action: '', name: item?item.name:'', remark: item?item.remark:'', platformInfo: item?.platformInfo });
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
const handleOk = async () => {
setConfirmLoading(true);
try {
const values = await form.validateFields();
let payload = null;
if (type === 'add' && values.action==='root') {
payload = {
name: values.name||'',
remark: values.remark||'',
platformInfo: values.platformInfo||'',
parentId: rootId
};
} else if (type === 'add') {
payload = {
name: values.name||'',
remark: values.remark||'',
platformInfo: values.platformInfo||'',
parentId: item.id
};
} else {
payload = {
...item,
name: values.name||'',
remark: values.remark||'',
platformInfo: values.platformInfo||'',
}
}
dispatchLatest({
type: 'datamodel.saveDataModelCatalog',
payload: {
data: payload
},
callback: id => {
setConfirmLoading(false);
if (onOk) {
onOk(id, payload);
}
},
error: () => {
setConfirmLoading(false);
}
});
} catch (errInfo) {
setConfirmLoading(false);
}
}
return (
<Modal
forceRender
confirmLoading={confirmLoading}
visible={visible}
title={type==='add'?"新增目录":"编辑目录"}
onOk={handleOk}
onCancel={() => {
setConfirmLoading(false);
onCancel && onCancel();
}}
>
<UpdateTreeItemForm form={form} item={item} type={type} />
</Modal>
);
}
export default UpdateTreeItemModal;
\ No newline at end of file
import React, { useEffect, useState, useRef } from 'react';
import { Form, Select, Spin, Tooltip, Checkbox, Typography, Space, Button, Row, Col } from 'antd';
import { dispatch, dispatchLatest } from '../../../../model';
import { formatVersionDate, showMessage } from '../../../../util';
import VersionCompareHeader, { VersionCompareSelectedHeader } from './VersionCompareHeader';
import VersionCompareTable, { VersionCompareSelectedTable } from './VersionCompareTable';
import VersionCompareIndex, { VersionCompareSelectedIndex } from './VersionCompareIndex';
import VersionDdlAlter from './version-ddl-alter';
import './VersionCompare.less';
const { Text, Paragraph } = Typography;
const { Option } = Select;
const VersionCompare = (props) => {
const { id, onlyShowChange, alterTrigger } = props;
const [ basicVersion, setBasicVersion ] = useState('');
const [ basicVersions, setBasicVersions ] = useState([]);
const [ incVersion, setIncVersion ] = useState('');
const [ incVersions, setIncVersions ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ compareData, setCompareData ] = useState(null);
const [ loadingCompare, setLoadingCompare ] = useState(false);
const [ddlAlterParams, setAlterParams] = useState({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
const mountRef = React.useRef(false)
useEffect(() => {
if ((id||'') !== '') {
getVersions();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ id ])
useEffect(() => {
if (basicVersion!=='' && incVersion!=='') {
getCompare(basicVersion, incVersion, onlyShowChange);
}
}, [onlyShowChange])
useEffect(() => {
if (mountRef.current) {
setAlterParams({
visible: true,
id,
versions: basicVersions,
defaultBasicVersion: basicVersion,
defaultIncVersion: incVersion,
})
}
mountRef.current = true
}, [alterTrigger])
const getVersions = () => {
setLoading(true);
dispatch({
type: 'datamodel.getVersions',
payload: {
params: {
id
}
},
callback: data => {
setLoading(false);
const newData = [];
(data||[]).forEach((item, index) => {
let name = item.name||'';
name = name + '_' + new Date(item.ts).toLocaleString();
if (index === 0 && item.id !== '-1') {
name = name+'(当前版本)';
}
if (index === 1 && data[0].id === '-1') {
name = name+'(当前版本)';
}
newData.push({ id: item.id, name });
})
setBasicVersions(newData);
if (newData.length >= 2) {
const defaultBasicVersion = newData[1].id;
const defaultIncVersion = newData[0].id;
setBasicVersion(defaultBasicVersion);
let index = -1;
(newData||[]).forEach((version, i) => {
if (version.id === defaultBasicVersion) {
index = i;
}
})
setIncVersions((newData||[]).slice(0, index));
setIncVersion(defaultIncVersion);
getCompare(defaultBasicVersion, defaultIncVersion);
}
},
error: () => {
setLoading(false);
}
})
}
const onBasicChange = (value) => {
setBasicVersion(value);
setIncVersion('');
setCompareData(null);
let index = -1;
(basicVersions||[]).forEach((version, i) => {
if (version.id === value) {
index = i;
}
})
setIncVersions((basicVersions||[]).slice(0, index));
}
const onIncChange = (value) => {
setIncVersion(value);
getCompare(basicVersion, value);
}
const getCompare = (value1=basicVersion, value2=incVersion, value3=onlyShowChange) => {
setLoadingCompare(true);
dispatchLatest({
type: 'datamodel.compare',
payload: {
params: {
id,
versionId1: value1,
versionId2: value2,
includeSame: !value3
}
},
callback: data => {
setLoadingCompare(false);
setCompareData(data);
},
error: () => {
setLoadingCompare(false);
}
})
}
return (
<div className='model-version-compare'>
<Form>
<Row>
<Col flex='1' style={{ overflow: 'hidden' }}>
<Form.Item label='基线版本' style={{ marginBottom: 0 }}>
<Select loading={loading} value={basicVersion} style={{ width: '100%' }} onChange={onBasicChange} >
{
(basicVersions||[]).map((version, index) => {
if (index === 0) {
return (
<Option key={index} value={version.id||''} disabled={true}>
<Tooltip title={'最近版本只能在增量版本中被选中'}>
{version.name||''}
</Tooltip>
</Option>
)
};
return (
<Option key={index} value={version.id||''}>
{version.name||''}
</Option>
);
})
}
</Select>
</Form.Item>
</Col>
<Col flex='15px' >
</Col>
<Col flex='1' style={{ overflow: 'hidden' }}>
<Form.Item label='增量版本' style={{ marginBottom: 0 }}>
<Select value={incVersion} style={{ width: '100%' }} disabled={basicVersion===''} onChange={onIncChange}>
{
(incVersions||[]).map((version, index) => {
return (
<Option key={index} value={version.id||''}>{version.name||''}</Option>
);
})
}
</Select>
</Form.Item>
</Col>
</Row>
</Form>
<div className='py-5'>
<Spin spinning={loadingCompare} >
<CompareDetail data={compareData} />
</Spin>
</div>
<VersionDdlAlter
{...ddlAlterParams}
onCancel={() => {
setAlterParams({
visible: false,
id: undefined,
versions: undefined,
basicVersion: undefined,
incVersion: undefined,
})
}}
/>
</div>
);
}
export default VersionCompare;
export const CompareDetail = ({ data, showIndex=true, checkable=false }) => {
const [selectedColumnTitles, setSelectedColumnTitles] = React.useState()
return (
<>
{
data && <div className='flex'>
<div style={{ flex: 1, borderRight: '1px solid #EFEFEF', paddingRight: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={data} />
<VersionCompareTable
data={data}
selectedColumnTitles={selectedColumnTitles}
onFilterChange={(val) => setSelectedColumnTitles(val)}
/>
{ showIndex && <VersionCompareIndex data={data} />}
</div>
<div style={{ flex: 1, paddingLeft: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={data} direction='right' />
<VersionCompareTable
data={data}
selectedColumnTitles={selectedColumnTitles}
onFilterChange={(val) => setSelectedColumnTitles(val)}
direction='right'
/>
{ showIndex && <VersionCompareIndex data={data} direction='right'/> }
</div>
{
checkable && <div style={{ width: 44, flex: 'none' }}>
<VersionCompareSelectedHeader data={data} />
<VersionCompareSelectedTable data={data} />
{ showIndex && <VersionCompareSelectedIndex data={data} /> }
</div>
}
</div>
}
</>
)
}
\ No newline at end of file
.model-version-compare {
position: relative;
.yy-typography .delete, .delete {
color: red !important;
text-decoration: line-through !important;
}
.yy-typography .add, .add {
color: red !important;
}
}
\ No newline at end of file
import { Typography, Tooltip, Checkbox } from 'antd';
const { Text, Paragraph, Title } = Typography;
const VersionCompareHeader = (props) => {
const { data, direction = 'left' } = props;
return (
<Typography>
<div className='mb-3'>
<Paragraph>
<Title level={5}>基本信息</Title>
</Paragraph>
</div>
{
(data?.heads?.tableHead||[]).map((item, index) => {
let columnValue = {};
if (direction==='left' && (data?.left?.tableValue||[]).length>index) {
columnValue = data?.left?.tableValue[index];
} else if (direction==='right' && (data?.right?.tableValue||[]).length>index) {
columnValue = data?.right?.tableValue[index];
}
let stateClassName = '';
if (columnValue?.state==='ADD' || columnValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (columnValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Paragraph>
<Tooltip key={index} title={columnValue.value||''}>
<Text ellipsis={true}>
{item||''}:&nbsp;<Text className={stateClassName}>{columnValue.value||''}</Text>
</Text>
</Tooltip>
</Paragraph>
);
})
}
</Typography>
);
}
export default VersionCompareHeader;
export const VersionCompareSelectedHeader = (props) => {
const { data } = props;
return (
<div>
<div style={{ height: 36 }} />
{
(data?.heads?.tableHead||[]).map((item, index) => {
let columnValue = {};
if ((data?.left?.tableValue||[]).length > index) {
columnValue = data?.left?.tableValue[index];
}
return (
<div index={index} style={{ height: 22, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{ (columnValue.state&&columnValue.state!=='EQUAL') && <Checkbox /> }
</div>
);
})
}
</div>
)
}
import { useEffect, useState } from 'react';
import { Typography, Tooltip, Table, Checkbox } from 'antd';
const { Text, Paragraph, Title } = Typography;
const VersionCompareIndex = (props) => {
const { data, direction = 'left' } = props;
const [ columns, setColumns ] = useState([]);
const [ tableData, setTableData ] = useState([]);
useEffect(() => {
const newColumns = [];
(data?.heads?.indexHead||[]).forEach((item, index) => {
newColumns.push({
title: item||'',
dataIndex: `column${index}`,
render: (attrValue, record, index) => {
let stateClassName = '';
if (attrValue?.state==='ADD' || attrValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (attrValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Paragraph>
<Tooltip title={attrValue?.value||''}>
<Text className={stateClassName} ellipsis={true}>{attrValue?.value||''}</Text>
</Tooltip>
</Paragraph>
);
},
width: 60,
ellipsis: true,
});
})
setColumns(newColumns);
const newTableData = [];
let indexValue = [];
if (direction==='left') {
indexValue = data?.left?.indexValue||[];
} else if (direction==='right') {
indexValue = data?.right?.indexValue||[];
}
(indexValue||[]).forEach((attrItem) => {
let newAttrItem = {};
(attrItem||[]).forEach((item, index) => {
newAttrItem[`column${index}`] = item;
})
newTableData.push(newAttrItem);
})
setTableData(newTableData);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ data ])
return (
<div>
<div className='my-3'>
<Typography>
<Title level={5}>数据表索引</Title>
</Typography>
</div>
<Table
columns={columns||[]}
dataSource={tableData}
pagination={false}
/>
</div>
);
}
export default VersionCompareIndex;
export const VersionCompareSelectedIndex= (props) => {
const { data } = props;
return (
<div>
<div style={{ height: 87 }} />
{
(data?.left?.indexValue||[]).map((item, index) => {
let showCheckbox = false;
(item??[]).forEach(_item => {
if (_item.state && _item.state !== 'EQUAL') {
showCheckbox = true
}
});
return (
<div index={index} style={{ height: 51, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{ showCheckbox && <Checkbox /> }
</div>
);
})
}
</div>
)
}
\ No newline at end of file
import React from 'react';
import { Typography, Table, Tooltip, Checkbox } from 'antd';
import FilterColumnAction from './FilterColumnAction';
const { Title } = Typography;
export const defaultColumnTitles = ['序号', '中文名称', '英文名称', '类型', '业务含义'];
const VersionCompareTable = (props) => {
const { data, direction = 'left', selectedColumnTitles = defaultColumnTitles, onFilterChange } = props;
const [columns, setColumns] = React.useState()
React.useEffect(() => {
const newColumns = [];
for (const [index, item] of (data?.heads?.columnHead||[]).entries()) {
newColumns.push({
title: item||'',
dataIndex: `column${index}`,
render: (attrValue, record, index) => {
let stateClassName = '';
if (attrValue?.state==='ADD' || attrValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (attrValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Typography.Paragraph>
<Tooltip title={attrValue?.value||''}>
<Typography.Text className={stateClassName} ellipsis={true}>{attrValue?.value||''}</Typography.Text>
</Tooltip>
</Typography.Paragraph>
);
},
width: (item==='序号')?60: 150,
ellipsis: true,
option: true,
});
}
setColumns([...newColumns, {
title: <FilterColumnAction columns={newColumns} defaultSelectedKeys={defaultColumnTitles} onChange={(val) => onFilterChange?.(val)} />,
dataIndex: 'columnFilter',
render: (_, record, index) => {
return '';
},
width: 40,
ellipsis: true,
option: false
}])
}, [data])
const _columns = React.useMemo(() => {
return (columns??[]).filter(column => column.option===false || (selectedColumnTitles??[]).indexOf(column.title) !== -1)
}, [selectedColumnTitles, columns])
const tableData = React.useMemo(() => {
const newTableData = [];
let columnValue = (direction==='left') ? data?.left?.columnValue : data?.right?.columnValue;
for (const item of columnValue??[]) {
let newItem = {};
for (const [index, _item] of item.entries()) {
newItem[`column${index}`] = _item;
}
newTableData.push(newItem);
}
return newTableData
}, [direction, data])
return (
<div>
<div className='my-3'>
<Typography>
<Title level={5}>数据表结构</Title>
</Typography>
</div>
<Table
columns={_columns||[]}
dataSource={tableData}
pagination={false}
/>
</div>
);
}
export default VersionCompareTable;
export const VersionCompareSelectedTable = (props) => {
const { data } = props;
return (
<div>
<div style={{ height: 87 }} />
{
(data?.left?.columnValue||[]).map((item, index) => {
let showCheckbox = false;
(item??[]).forEach(_item => {
if (_item.state && _item.state !== 'EQUAL') {
showCheckbox = true
}
});
return (
<div index={index} style={{ height: 51, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{ showCheckbox && <Checkbox /> }
</div>
);
})
}
</div>
)
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Timeline, Spin } from 'antd';
import { dispatch } from '../../../../model';
import { formatVersionHistoryDate } from '../../../../util';
import { Action, ModelerId, VersionId } from '../../../../util/constant';
const VersionHistory = (props) => {
const { id } = props;
const [ versions, setVersions ] = useState([]);
const [ loading, setLoading ] = useState(false);
useEffect(() => {
if ((id||'') !== '') {
getVersions();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ id ])
const getVersions = () => {
setLoading(true);
dispatch({
type: 'datamodel.getVersions',
payload: {
params: {
id
}
},
callback: data => {
setLoading(false);
setVersions(data||[]);
},
error: () => {
setLoading(false);
}
})
}
const onVersionItemClick = (version) => {
window.open(`/data-govern-bj/data-model-action?${Action}=detail-version&${ModelerId}=${version.dataModelId||''}&${VersionId}=${version.id||''}`);
}
return (
<Spin spinning={loading}>
<Timeline style={{ padding: 24 }}>
{
(versions||[]).map((version, index) => {
let name = version.name||'';
if (index === 0 && version.id !== '-1') {
name = name+'(当前版本)';
}
if (index === 1 && versions[0].id === '-1') {
name = name+'(当前版本)';
}
let color = '';
if (version.state?.id === '1') {
color = '#DE7777';
} else if (version.state?.id === '2') {
color = '#779BDE';
} else if (version.state?.id === '4') {
color = '#77DEBF';
}
return <Timeline.Item key={index} color={ color } >
<div>
<div>
<a onClick={()=>{ onVersionItemClick(version); }}>
{name}
</a>
</div>
<div className='pt-2'>
<span style={{ color }}>{version.state?.cnName||''}</span>
</div>
<div className='pt-2'>
<span>{version.editor||''}</span>
<span className='pl-2'>{formatVersionHistoryDate(version.ts)}</span>
</div>
</div>
</Timeline.Item>
})
}
</Timeline>
</Spin>
);
}
export default VersionHistory;
\ No newline at end of file
import React from "react"
import { Modal, Button } from 'antd'
import Draggable from 'react-draggable'
import { QuestionCircleFilled } from '@ant-design/icons'
import { dispatch } from '../../../../model'
const FC = (props) => {
const { type } = props
const [tip, setTip] = React.useState()
const [visible, setVisible] = React.useState(false)
const [disabled, setDisabled] = React.useState(true)
const [bounds, setBounds] = React.useState({ left: 0, top: 0, bottom: 0, right: 0 })
const draggleRef = React.useRef()
React.useEffect(() => {
getTip()
}, [])
const title = React.useMemo(() => {
return type==='design' ? '设计评审提示' : '规范评审提示'
}, [type])
const getTip = () => {
dispatch({
type: 'datamodel.getPhysicalModelApprovalTip',
payload: {
type
},
callback: data => {
setTip(data?.tip)
}
})
}
const handleCancel = (e) => {
setVisible(false)
}
const onStart = (_event, uiData) => {
const { clientWidth, clientHeight } = window.document.documentElement;
const targetRect = draggleRef.current?.getBoundingClientRect();
if (!targetRect) {
return;
}
setBounds({
left: -targetRect.left + uiData.x,
right: clientWidth - (targetRect.right - uiData.x),
top: -targetRect.top + uiData.y,
bottom: clientHeight - (targetRect.bottom - uiData.y),
});
}
return (
<>
{
tip && <Button icon={<QuestionCircleFilled style={{ fontSize: 18, color: '#3B80D6' }} />} onClick={() => {
setVisible(true)
}} />
}
<Modal mask={false} zIndex={100}
title={
<div
style={{
width: '100%',
cursor: 'move',
}}
onMouseOver={() => {
if (disabled) {
setDisabled(false);
}
}}
onMouseOut={() => {
setDisabled(true);
}}
// fix eslintjsx-a11y/mouse-events-have-key-events
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md
onFocus={() => {}}
onBlur={() => {}}
// end
>
{ title }
</div>
}
visible={visible}
onCancel={handleCancel}
footer={null}
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
modalRender={modal => (
<Draggable
disabled={disabled}
bounds={bounds}
onStart={(event, uiData) => onStart(event, uiData)}
>
<div ref={draggleRef}>{modal}</div>
</Draggable>
)}
>
<div>
{ (tip??'').split('\n').map((item, index) => (<div key={index}>{item}</div>)) }
</div>
</Modal>
</>
)
}
export default FC
\ No newline at end of file
import React from "react"
import { Modal, Button, Spin, Form, Select } from "antd"
import {produce} from "immer"
import { useDebounceEffect } from "ahooks"
import { dispatch } from '../../../../model'
const FC = (props) => {
const { item, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [cooperators, setCooperators] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible && item?.id) {
getCooperators()
}
}, [visible, item])
const getCooperators = () => {
setLoading(true)
dispatch({
type: 'datamodel.getCooperators',
payload: {
id: item?.id
},
callback: (data) => {
setLoading(false)
setCooperators(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setCooperators()
onCancel?.(refresh)
}
const save = async () => {
try {
const rows = await basicRef.current.validate()
setWaiting(true)
dispatch({
type: 'datamodel.setCooperators',
payload: {
params: {
id: item?.id,
},
data: rows.cooperators,
},
callback: (data) => {
setWaiting(false)
close(true)
},
error: () => {
setWaiting(false)
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>确定</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width='800px'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='权限共享'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} item={item} cooperators={cooperators} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ cooperators, item }, ref) {
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
React.useEffect(() => {
if ((cooperators??[]).length>0) {
form.setFieldsValue({ cooperators })
}
}, [cooperators])
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
>
<Form.Item
name='cooperators'
label='选择用户'
extra="权限共享后,您与共享用户均可编辑该模型"
>
<UsersItem modelId={item?.id} />
</Form.Item>
</Form>
)
})
//注意 这里使用name作为唯一标识 id有可能为空
const UsersItem = ({ modelId, value, onChange, readonly = false }) => {
const [loading, setLoading] = React.useState(false)
const [searchValue, setSearchValue] = React.useState()
const [options, setOptions] = React.useState()
useDebounceEffect(() => {
if (searchValue) {
getUsers()
} else {
setOptions()
}
}, [searchValue], { wait: 300 })
const getUsers = () => {
setLoading(true)
dispatch({
type: 'datamodel.getCooperatorCandidates',
payload: {
id: modelId,
match: searchValue,
},
callback: data => {
setLoading(false)
setOptions(
(data??[]).map(item => ({
label: `${item.dname}(${item.name})`,
value: `${item.dname}(${item.name})`,
...item
}))
)
},
error: () => {
setLoading(false)
}
})
}
return (
<>
{
readonly ? <span>{value?.map(item => `${item.dname}(${item.name})`).toString()}</span> : <Select mode='multiple' allowClear
placeholder='请选择用户'
value={(value??[]).map(item => `${item.dname}(${item.name})`)}
searchValue={searchValue}
onSearch={(val) => {
setSearchValue(val)
}}
onClear={() => {
setSearchValue()
}}
filterOption={false}
notFoundContent={loading ? <Spin size="small" /> : null}
options={options}
onChange={(val) => {
const newValue = [
...(value??[]).filter(item => (val??[]).indexOf(`${item.dname}(${item.name})`) !== -1),
...(options??[]).filter(item => (val??[]).indexOf(item.value) !== -1)
];
const uniqueValue = newValue.reduce((acc, current) => {
const index = acc.findIndex(item => `${item.dname}(${item.name})` === `${current.dname}(${current.name})`)
if (index === -1) {
acc.push(current);
}
return acc;
}, []);
onChange?.(uniqueValue)
}}
/>
}
</>
)
}
import React from "react"
import { Modal, Button, Spin, Form, Select } from "antd"
import {produce} from "immer"
import { useDebounceEffect } from "ahooks"
import { dispatch } from '../../../../model'
const FC = (props) => {
const { item, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [owner, setOwner] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible && item?.id) {
getOwner()
}
}, [visible, item])
const getOwner = () => {
setLoading(true)
dispatch({
type: 'datamodel.getOwner',
payload: {
id: item?.id
},
callback: (data) => {
setLoading(false)
setOwner(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setOwner()
onCancel?.(refresh)
}
const save = async () => {
try {
const rows = await basicRef.current.validate()
setWaiting(true)
dispatch({
type: 'datamodel.setOwner',
payload: {
params: {
id: item?.id,
},
data: rows.owner,
},
callback: (data) => {
setWaiting(false)
close(true)
},
error: () => {
setWaiting(false)
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>确定</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width='800px'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='权限转移'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} owner={owner} item={item} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ owner, item }, ref) {
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
React.useEffect(() => {
if (owner) {
form.setFieldsValue({ owner })
}
}, [owner])
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
>
<Form.Item
name='owner'
label='选择用户'
extra="权限转移后,您将无法编辑该模型"
rules={[{ required: true, message: '请选择用户'}]}
>
<UsersItem modelId={item?.id} />
</Form.Item>
</Form>
)
})
const UsersItem = ({ modelId, value, onChange }) => {
const [loading, setLoading] = React.useState(false)
const [searchValue, setSearchValue] = React.useState()
const [options, setOptions] = React.useState()
useDebounceEffect(() => {
if (modelId && searchValue) {
getUsers()
} else {
setOptions()
}
}, [searchValue], { wait: 300 })
const getUsers = () => {
setLoading(true)
dispatch({
type: 'datamodel.getCooperatorCandidates',
payload: {
id: modelId,
match: searchValue,
},
callback: data => {
setLoading(false)
setOptions(
(data??[]).map(item => ({
label: `${item.dname}(${item.name})`,
value: `${item.dname}(${item.name})`,
...item
}))
)
},
error: () => {
setLoading(false)
}
})
}
return (
<Select showSearch allowClear
placeholder='请选择用户'
value={value?`${value.dname}(${value.name})`:undefined}
searchValue={searchValue}
onSearch={(val) => {
setSearchValue(val)
}}
onClear={() => {
setSearchValue()
}}
filterOption={false}
notFoundContent={loading ? <Spin size="small" /> : null}
options={options}
onChange={(val) => {
if (val) {
const index = (options??[]).findIndex(item => item.value === val)
if (index !== -1) {
onChange?.(options[index])
}
} else {
onChange?.()
}
}}
/>
)
}
\ No newline at end of file
import React from "react"
import { Modal, Button, Tree, Spin, AutoComplete } from "antd"
import {produce} from "immer"
import { dispatch } from '../../../../model'
import { highlightSearchContentByTerms, showMessage } from "../../../../util"
import './branch-add-model.less'
const FC = ({ visible, onCancel }) => {
const basicRef = React.useRef()
const close = (val = null) => {
onCancel?.(val)
}
const save = () => {
const node = basicRef.current.node
if (!node) {
showMessage('warn', '请先选择物理平台')
return
}
close(node)
}
const footer = React.useMemo(() => {
return [
<Button key='cancel'
onClick={() => close()}
>取消</Button>,
<Button key='save' type='primary'
onClick={() => save()}
>保存</Button>
]
}, [close, save])
return (
<Modal
className='branch-add-model'
title='选择物理平台'
visible={visible}
footer={footer}
width={400}
bodyStyle={{ padding: '15px', overflowX: 'auto' }}
centered destroyOnClose
onCancel={() => { close() }}
>
<Basic ref={basicRef} />
</Modal>
)
}
export default FC
const Basic = React.forwardRef(function ({}, ref) {
const [loadingTreeData, setLoadingTreeData] = React.useState(false)
const [treeData, setTreeData] = React.useState()
const [dataList, setDataList] = React.useState()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
const [node, setNode] = React.useState()
const [options, setOptions] = React.useState()
const [keyword, setKeyword] = React.useState()
React.useImperativeHandle(ref, () => ({
node
}), [node])
React.useEffect(() => {
getTreeData()
}, [])
const getTreeData = () => {
setLoadingTreeData(true)
dispatch({
type: 'datamodel.refreshDataModelCatalog',
callback: (data) => {
setLoadingTreeData(false)
const newTreeData = produce(data?.subCatalogs??[], draft => {
const setNode = (g) => {
g.key = g.id;
g.title = g.name;
g.children = [];
(g.subCatalogs??[]).forEach((child) => {
setNode(child)
g.children.push(child)
});
}
draft.forEach((child) => {
setNode(child)
})
})
setTreeData(newTreeData)
if ((newTreeData??[]).length > 0) {
const newDataList = []
generateList(newTreeData, newDataList)
setDataList(newDataList)
const firstNode = newTreeData[0]
setExpandedKeys([firstNode.id])
setAutoExpandParent(true)
}
},
error: () => {
setLoadingTreeData(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setNode(selectedNodes[0])
}
const generateList = (treeData, list, path = null) => {
for (let i = 0; i < treeData.length; i++) {
const node = treeData[i];
const { id, name } = node;
const currentPath = path ? `${path}/${name}` : name;
list.push({ key: id , title: currentPath, value: currentPath });
if (node.children) {
generateList(node.children, list, currentPath);
}
}
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
const newExpandedKeys = [...expandedKeys, option.key]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
}
return (
<Spin spinning={loadingTreeData}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
className='tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData}
selectedKeys={node?[node.id]:[]}
expandedKeys={expandedKeys}
onSelect={onTreeSelect}
onExpand={onTreeExpand}
/>
</Spin>
)
})
\ No newline at end of file
.branch-add-model {
.yy-tree-list {
height: calc(60vh) !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Checkbox, Typography, Tooltip, Row, Col, Tree, } from 'antd'
import { dispatch } from '../../../../model'
import { CompareDetail } from './VersionCompare'
import './VersionCompare.less'
import { formatDate } from '../../../../util'
import { Action, ConflictItemId, IsConflict, ModelerId, ModelerMergeId } from '../../../../util/constant'
const FC = (props) => {
const { type = '', item, visible, title = '基线模型变更提醒',readonly = false, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const close = (val) => {
setWaiting(false)
onCancel?.(val)
}
const save = () => {
setWaiting(true)
dispatch({
type: 'datamodel.getForkedModel',
payload: {
id: item?.id,
},
callback: data => {
setWaiting(false)
if (type === 'notice') {
window.open(`/data-govern-bj/data-model-action?${Action}=edit&${ModelerId}=${item?.id}&${ModelerMergeId}=${data?.id}&${ConflictItemId}=${item?.conflictItemId}`);
} else {
window.open(`/data-govern-bj/data-model-action?${Action}=edit&${ModelerId}=${item?.id}&${ModelerMergeId}=${data?.id}`);
}
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>同步</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={readonly?null:footer}
width='80%'
bodyStyle={{ padding: '15px', height: '80vh', overflow: 'auto' }}
title={title}
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic item={item} />
</Spin>
</Modal>
)
}
export default FC
const Basic = ({ item }) => {
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [onlyShowChange, setOnlyShowChange] = React.useState(true)
React.useEffect(() => {
if (item) {
getDiffForked()
}
}, [item, onlyShowChange])
const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked)
}
const getDiffForked = () => {
setLoading(true)
dispatch({
type: 'datamodel.getBranchDiffForked',
payload: {
id: item?.id,
includeSame: !onlyShowChange,
isComparatorOnLeft: true,
},
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<div className='model-version-compare'>
<div className='flex'>
<div style={{ flex: 1, paddingRight: 10, overflow: 'hidden'}}>
{`基线版本:${data?.['left-version']?.name??''}${data?.['left-version']?.ts?` V_${new Date(data?.['left-version']?.ts).toLocaleString()}`:''}`}
</div>
<div style={{ flex: 1, paddingLeft: 10, overflow: 'hidden'}}>
<div className='flex' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<span>{`分支版本:${data?.['right-version']?.name??''}${data?.['right-version']?.ts?` V_${new Date(data?.['right-version']?.ts).toLocaleString()}`:''}`}</span>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>
仅显示差异
</Checkbox>
</div>
</div>
</div>
<div className='py-5'>
<Spin spinning={loading} >
<CompareDetail data={data} />
</Spin>
</div>
</div>
)
}
\ No newline at end of file
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