Commit 9cfe4f67 by zhaochengxiang

Merge branch 'modeler-ui' into 'master'

Modeler ui

See merge request !6
parents ebc4370c 34e44919
......@@ -10,6 +10,7 @@ import Home from './view/Home';
import Manage from './view/Manage';
import Map from './view/Manage/Map';
import Model from './view/Manage/Model';
import ModelConfig from './view/Manage/ModelConfig';
import AssetManage from './view/Manage/AssetManage';
import AssetBrowse from './view/Manage/AssetBrowse';
import AssetRecycle from './view/Manage/AssetRecycle';
......@@ -17,6 +18,7 @@ import DatasourceManage from './view/Manage/DatasourceManage';
import AssetDetail from './view/Manage/AssetManage/Component/AssetDetail';
import ImportAction from './view/Manage/Model/Component/ImportAction';
import EditModel from './view/Manage/Model/Component/EditModel';
import EditTemplate from './view/Manage/ModelConfig/Component/EditTemplate';
import AssetTree from './view/Manage/AssetManage/Component/AssetTree';
export const AppContext = React.createContext();
......@@ -81,10 +83,12 @@ export class App extends React.Component {
<Route path={`${ContextPath}/login`} component={Signin} exact />
<Route path={`${ContextPath}/home`} component={Home} />
<Route path={`${ContextPath}/manage`} component={Manage} />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/model-template-action`} component={EditTemplate} exact />
<Route path={'/center-home/view/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/view/data-model'} component={Model} exact />
<Route path={'/center-home/view/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/view/asset-map'} component={Map} exact />
<Route path={'/center-home/view/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact />
......@@ -92,6 +96,7 @@ export class App extends React.Component {
<Route path={'/center-home/menu/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/menu/data-model'} component={Model} exact />
<Route path={'/center-home/menu/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/menu/asset-map'} component={Map} exact />
<Route path={'/center-home/menu/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact />
......
......@@ -111,6 +111,30 @@ div[id^='__qiankun_microapp_wrapper_'] {
word-break: break-all;
}
.yy-table-thead > tr > th {
padding: 8px 16px !important;
}
.yy-table-tbody > tr > td {
padding: 12px 16px !important;
}
.yy-table-tbody > .yy-table-measure-row > td {
padding: 0px !important;
}
.yy-table-tbody > tr.yy-table-row-selected > td {
background: #fff !important;
}
tr.yy-table-expanded-row > td {
background: #fff !important;
}
.yy-table-thead > tr > th {
background-color: #F2F5FC !important;
}
.yy-popover-content {
pointer-events: auto !important;
}
......
......@@ -17,7 +17,11 @@ export const routes = [
},
{
name: 'data-model',
text: '数据模型',
text: '模型设计',
},
{
name: 'model-config',
text: '模型配置',
},
{
name: 'asset-map',
......
......@@ -11,4 +11,5 @@ export const ModelerData = 'mdata';
export const Editable = 'editable';
export const PermitCheckOut = 'permitCheckOut';
export const StateId = 'sid';
export const VersionId = 'vid';
\ No newline at end of file
export const VersionId = 'vid';
export const TemplateId = 'tid';
\ No newline at end of file
......@@ -9,6 +9,8 @@ import { getQueryParam, showMessage } from '../../../../util';
import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, VersionId } from '../../../../util/constant';
import HistoryAndVersionDrawer from './HistoryAndVersionDrawer';
import './EditModel.less';
const EditModel = (props) => {
const [ actionData, setActionData ] = useState({ action: '', catalogId: '', modelerId: '', hints: [], roughModelerData: null, permitCheckOut: false, editable: false, stateId: '', versionId: '' });
......@@ -159,6 +161,7 @@ const EditModel = (props) => {
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
......@@ -166,63 +169,53 @@ const EditModel = (props) => {
} else if (action === 'detail') {
actionsBtn = (
<Space>
<Button onClick={onHistory} >版本历史</Button>
{
(editable==='true') && <Button type='primary' onClick={edit} >
(editable==='true') && <Button type='primary' onClick={edit} danger >
编辑
</Button>
}
<Button type='primary' onClick={onHistory} danger >版本历史</Button>
</Space>
);
} else if (action === 'edit') {
actionsBtn = (
<Space>
<Button onClick={cancelEdit} >
取消
</Button>
<Button onClick={onHistory} >
版本历史
</Button>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
<Button type='primary' onClick={onHistory} danger >
版本历史
</Button>
<Button onClick={cancelEdit} >
取消
</Button>
</Space>
)
} else if (action === 'flow') {
actionsBtn = (
<Button onClick={onHistory} >
<Button type='primary' onClick={onHistory} danger>
版本历史
</Button>
);
}
return (
<div className='position-relative'>
<div
className='flex'
style={{
width: '100%',
height: 64,
padding: '0 15px',
backgroundColor: '#fff',
alignItems: 'center',
position: 'fixed',
justifyContent: 'space-between',
borderBottom: '1px solid #EFEFEF',
zIndex: 100
}}
>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#000' }}>{title}</span>
{actionsBtn}
<div className='edit-model position-relative'>
<div className='edit-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>{title}</span>
</div>
<div className='position-absolute' style={{ top: 64, width: '100%' }}>
<div className='position-relative' style={{ margin: 15 }}>
<div className='edit-container'>
<div className='edit-container-card'>
<ImportAction hints={hints} onChange={onActionChange} action={action} modelerId={modelerId} form={form} terms={terms} roughModelerData={roughModelerData} permitCheckOut={permitCheckOut} stateId={stateId} versionId={versionId} />
</div>
</div>
</div>
<div className='edit-footer'>
{actionsBtn}
</div>
<CatalogModal
visible={catalogModalVisible}
......
.edit-model {
.edit-header {
display: flex;
width: 100%;
height: 44px;
padding: 0 15px;
background-color: #464d6e;
align-items: center;
position: fixed;
justify-content: space-between;
border-bottom: 1px solid #EFEFEF;
z-index: 100;
}
.edit-container {
top: 44px;
width: 100%;
height: calc(100vh - 44px - 64px);
overflow: auto;
background: #EDF0F5;
padding: 10px 20px;
position: absolute;
}
.edit-container-card {
padding: 20px 20px 0;
background: #fff;
}
.edit-footer {
display: flex;
bottom: 0;
width: 100%;
height: 64px;
position: fixed;
justify-content: flex-end;
opacity: 0.9;
background: #fff;
box-shadow: 0 -1px 4px 0 #e5e9ea;
padding: 0 20px;
}
}
\ No newline at end of file
......@@ -284,7 +284,6 @@ class ExportDDLModal extends React.Component {
footer = ddlExportSuccess ? ([
<Button
key="0"
type="primary"
onClick={() => {
this.reset();
onCancel && onCancel();
......@@ -306,7 +305,6 @@ class ExportDDLModal extends React.Component {
]) : ([
<Button
key="0"
type="primary"
onClick={() => {
this.reset();
onCancel && onCancel();
......@@ -326,7 +324,6 @@ class ExportDDLModal extends React.Component {
footer = ddlExportSuccess ? ([
<Button
key="0"
type="primary"
onClick={() => {
this.reset();
onCancel && onCancel();
......@@ -344,7 +341,6 @@ class ExportDDLModal extends React.Component {
]) : ([
<Button
key="0"
type="primary"
onClick={() => {
this.reset();
onCancel && onCancel();
......
import React, { useState } from 'react';
import { Modal, Button, Form, Radio } from 'antd';
const exportModes = [
{ name: '导出Erwin', key: 'erwin' },
{ name: '导出Excel', key: 'excel' },
{ name: '导出Word', key: 'word' },
]
const ExportOtherModal = (props) => {
const { visible, onCancel } = props;
const [ modeKey, setModeKey ] = useState('');
const [ form ] = Form.useForm();
const onModeChange = (e) => {
setModeKey(e.target?.value);
}
const onOk = async() => {
try {
await form.validateFields();
reset();
onCancel && onCancel(modeKey);
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const cancel = () => {
reset();
onCancel && onCancel();
}
const reset = () => {
form.resetFields();
setModeKey('');
}
const footer = [
<Button
key="0"
onClick={cancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={onOk}
>
确定
</Button>,
];
return (
<Modal
forceRender
visible={visible}
title='模型导出'
width={520}
onCancel={cancel}
footer={footer}
>
<Form form={form}>
<Form.Item
name='mode'
label='导出方式'
rules={[
{
required: true,
message: '请选择导出方式',
},
]}
>
<Radio.Group onChange={onModeChange} value={modeKey}>
{
exportModes.map((item, index) => {
return (
<Radio
value={item.key}
>
{item.name}
</Radio>
);
})
}
</Radio.Group>
</Form.Item>
</Form>
</Modal>
)
}
export default ExportOtherModal;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Spin } from 'antd';
import { Spin, Tabs, Popover, Divider, Button, Space } from 'antd';
import LocalStorage from 'local-storage';
import { QuestionCircleOutlined } from '@ant-design/icons';
import ImportActionHeader from './ImportActionHeader';
import ImportActionTable from './ImportActionTable';
......@@ -9,6 +10,8 @@ import ImportActionPartition from './ImportActionPartition';
import { dispatch } from '../../../../model';
const { TabPane } = Tabs;
const ImportAction = (props) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, stateId, versionId, permitCheckOut } = props;
......@@ -20,6 +23,7 @@ const ImportAction = (props) => {
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ supportedPartitionTypes, setSupportedPartitionTypes ] = useState([]);
const [ validateReports, setValidateReports ] = useState([]);
const [ tabKey, setTabKey ] = useState('1');
const [ loading, setLoading ] = useState(false);
useEffect(() =>{
......@@ -334,54 +338,116 @@ const ImportAction = (props) => {
return newEasyDataModelerIndices.filter(item => (item.indexedEasyDataModelAttributes||[]).length > 0);
}
const container = (<React.Fragment>
<ImportActionHeader
form={form}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
modelerData={modelerData||{}}
constraints={constraints}
templates={templates}
validateReports={validateReports}
onTemplateChange={onTemplateChange}
onConstraintChange={onConstraintChange}
onChange={onHeaderChange}
terms={terms}
/>
<ImportActionTable
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
action={action}
terms={terms}
/>
<ImportActionIndex
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
onChange={onIndexChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
terms={terms}
/>
<ImportActionPartition
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedPartitionTypes={supportedPartitionTypes}
onChange={onPartitionChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
terms={terms}
/>
</React.Fragment>);
const onTabChange = (key) => {
setTabKey(key);
}
const prevStep = () => {
setTabKey(`${Number(tabKey)-1}`);
}
const nextStep = () => {
setTabKey(`${Number(tabKey)+1}`);
}
return (
<Spin spinning={loading}>
{ container }
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'>
<ImportActionHeader
form={form}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
modelerData={modelerData||{}}
constraints={constraints}
templates={templates}
validateReports={validateReports}
onTemplateChange={onTemplateChange}
onConstraintChange={onConstraintChange}
onChange={onHeaderChange}
terms={terms}
/>
</TabPane>
<TabPane
tab={
<span>
<span>数据表结构</span>
{
(action!=='detail'&&action!=='flow'&&action!=='detail-version') && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
}
</span>
}
key='2'
>
<ImportActionTable
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
action={action}
terms={terms}
/>
</TabPane>
<TabPane
tab={
<span>
<span>数据表索引</span>
{
(action!=='detail'&&action!=='flow'&&action!=='detail-version') && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
}
</span>
}
key='3'
>
<ImportActionIndex
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
onChange={onIndexChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
terms={terms}
/>
</TabPane>
<TabPane
tab='数据表分区'
key='4'
>
<ImportActionPartition
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedPartitionTypes={supportedPartitionTypes}
onChange={onPartitionChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
terms={terms}
/>
</TabPane>
</Tabs>
{
action!=='detail'&&action!=='flow'&&action!=='detail-version'&&(
<React.Fragment>
<Divider style={{ margin: 0 }} />
<div className='flex' style={{ justifyContent: 'flex-end', height: 64, alignItems: 'center' }}>
<Space size='small'>
<Button type='primary' onClick={prevStep} disabled={tabKey==='1'} danger>上一步</Button>
<Button type='primary' onClick={nextStep} disabled={tabKey==='4'} danger>下一步</Button>
</Space>
</div>
</React.Fragment>
)
}
</Spin>
);
};
......
......@@ -6,6 +6,7 @@ import { dispatchLatest } from '../../../../model';
import './ImportActionHeader.less';
const { TextArea } = Input;
const { Option } = Select;
const ConstraintSelect = ({ value = {}, constraints = [], onChange, ...restProps }) => {
......@@ -170,7 +171,7 @@ const ImportActionHeader = (props) => {
{...formItemLayout}
onValuesChange={onValuesChange}
>
<Row gutter={10}>
<Row >
<Col span={8}>
<Form.Item
label="中文名称"
......@@ -189,21 +190,10 @@ const ImportActionHeader = (props) => {
rules={[{ required: true, message: '请输入英文名称!' }]}
>
<AutoComplete options={options} onSearch={onSearch} style={{ width: 300 }} />
{/* <Input /> */}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label="描述"
name="remark"
labelAlign="left"
rules={[{ required: true, message: '请输入描述!' }]}
>
<Input style={{ width: 300 }} />
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Row >
<Col span={8}>
<Form.Item
label="规范"
......@@ -320,9 +310,21 @@ const ImportActionHeader = (props) => {
</Form.Item>
</Col>
</Row>
<Row>
<Col span={8}>
<Form.Item
label="描述"
name="remark"
labelAlign="left"
rules={[{ required: true, message: '请输入描述!' }]}
>
<TextArea row={4} style={{ width: 300 }} />
</Form.Item>
</Col>
</Row>
</Form>
) : (
<Descriptions column={5}>
<Descriptions column={2}>
<Descriptions.Item label="中文名称">{highlightSearchContentByTerms(modelerData.cnName||'', terms)}</Descriptions.Item>
<Descriptions.Item label="英文名称">
{
......
.model-import-action-header {
.yy-form-item {
margin-bottom: 10px;
margin-bottom: 24px;
}
.yy-form-item-has-error {
margin-bottom: 0px;
}
.yy-descriptions-row > th, .yy-descriptions-row > td {
padding-bottom: 10px;
padding-bottom: 20px;
}
}
\ No newline at end of file
import React, { useState, useCallback, useRef, useEffect } from 'react';
import { Input, Form, Typography, Divider, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table } from 'antd';
import { QuestionCircleOutlined, DeleteOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table } from 'antd';
import { DeleteOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
......@@ -422,7 +422,7 @@ const ImportActionIndex = (props) => {
title: '序号',
dataIndex: 'key',
editable: false,
width: 50,
width: 60,
render: (_, __, index) => {
return (index+1).toString();
}
......@@ -640,21 +640,12 @@ const ImportActionIndex = (props) => {
}
return (
<div className='model-import-action-index mt-7'>
<Divider orientation='left'>
<>
<span>数据表索引</span>
{ editable && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
<div className='model-import-action-index'>
<div className='d-flex mb-3' style={{ justifyContent: editable?'space-between':'flex-end' }}>
{
editable && <Button onClick={onAddClick} disabled={ editingKey!==null || keyword!=='' } >新建</Button>
}
</>
</Divider>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<div className='d-flex' style={{ alignItems: 'center' }}>
<span className='mr-3'>索引搜索:</span>
<Input
placeholder="请输入索引名称"
allowClear
......@@ -663,11 +654,8 @@ const ImportActionIndex = (props) => {
style={{ width: 230 }}
/>
</div>
{
editable && <Button className='ml-3' type="primary" onClick={onAddClick} disabled={ editingKey!==null || keyword!=='' } >新增索引</Button>
}
</div>
<div id="containerId">
<div className='mb-3' id="containerId">
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
......
import React, { useState, useEffect } from 'react';
import { Form, Typography, Divider, Button, Select, Row, Col, Tooltip, Table } from 'antd';
import { Form, Typography, Button, Select, Row, Col, Tooltip, Table } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { showMessage, highlightSearchContentByTerms } from '../../../../util';
......@@ -289,7 +289,7 @@ const ImportActionPartition = (props) => {
title: '序号',
dataIndex: 'key',
editable: false,
width: 50,
width: 60,
render: (_, __, index) => {
return (index+1).toString();
}
......@@ -393,18 +393,13 @@ const ImportActionPartition = (props) => {
}
return (
<div className='model-import-action-index mt-7'>
<Divider orientation='left'>
<>
<span>数据表分区</span>
</>
</Divider>
<div className='model-import-action-index'>
{
editable && <div className='d-flex mb-3' style={{ justifyContent: 'flex-end' }}>
<Button type="primary" onClick={onAddClick} disabled={ data||isEditing } >新增分区</Button>
editable && <div className='d-flex mb-3'>
<Button onClick={onAddClick} disabled={ data||isEditing } >新建</Button>
</div>
}
<div id="containerId">
<div className='mb-3' id="containerId">
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
components={{
......
import React, { useState, useCallback, useRef, useEffect } from 'react';
import { Input, Form, Typography, Radio, Divider, Button, Popconfirm, Select, Row, Col, Popover, Checkbox, Tooltip, Table } from 'antd';
import { QuestionCircleOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { Input, Form, Typography, Radio, Button, Popconfirm, Select, Row, Col, Popover, Checkbox, Tooltip, Table } from 'antd';
import { CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
......@@ -490,7 +490,7 @@ const ImportActionTable = (props) => {
title: '序号',
dataIndex: 'key',
editable: false,
width: 50,
width: 60,
fixed: 'left',
render: (text, record, index) => {
return (index+1).toString();
......@@ -554,7 +554,7 @@ const ImportActionTable = (props) => {
},
{
title: '可否为空',
width: 80,
width: 100,
dataIndex: 'nullable',
editable: (type==='model'?true:false),
render: (nullable, record, index) => {
......@@ -630,7 +630,7 @@ const ImportActionTable = (props) => {
},
{
title: '重点关注',
width: 80,
width: 100,
dataIndex: 'needAttention',
editable: (type==='model'?true:false),
render: (needAttention, record, index) => {
......@@ -934,20 +934,11 @@ const ImportActionTable = (props) => {
return (
<div className='model-import-action-table'>
<Divider orientation='left'>
<>
<span>数据表结构</span>
{ editable && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
<div className='d-flex mb-3' style={{ justifyContent: editable?'space-between':'flex-end' }}>
{
editable && <Button onClick={onAddClick} disabled={ editingKey!=='' || keyword!=='' } >新建</Button>
}
</>
</Divider>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<div className='d-flex' style={{ alignItems: 'center' }}>
<span className='mr-3'>字段搜索:</span>
<Input
placeholder="请输入中文名称或者英文名称"
allowClear
......@@ -956,11 +947,8 @@ const ImportActionTable = (props) => {
style={{ width: 230 }}
/>
</div>
{
editable && <Button className='ml-3' type="primary" onClick={onAddClick} disabled={ editingKey!=='' || keyword!=='' } >新增字段</Button>
}
</div>
<div id="containerId">
<div className='mb-3' id="containerId">
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
......
import React from 'react';
import { Button, Upload } from 'antd';
import { Button, Upload, Form, Row, Col } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
class ImportExcel extends React.Component {
......@@ -22,6 +22,11 @@ class ImportExcel extends React.Component {
window.open("/data-govern/docs/DataModel.xlsx");
}
normFile = (e) => {
const { fileList } = this.state;
return fileList;
};
render() {
const { onChange } = this.props;
......@@ -33,8 +38,6 @@ class ImportExcel extends React.Component {
const newFileList = fileList.slice();
newFileList.splice(index, 1);
onChange && onChange(newFileList);
this.setState({ fileList: newFileList });
},
beforeUpload: file => {
......@@ -42,7 +45,6 @@ class ImportExcel extends React.Component {
onChange && onChange([file]);
this.setState({ fileList: [file] });
console.log('file', file);
return false;
},
accept:".xlsx",
......@@ -50,20 +52,39 @@ class ImportExcel extends React.Component {
};
return (
<>
<div>
<Button icon={<DownloadOutlined />} onClick={ this.downloadTemplate }>
模版下载
</Button>
</div>
<div className='mt-3'>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
<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>
</Upload>
</div>
</>
</Col>
</Row>
</Form.Item>
)
}
}
......
import React, { useState } from 'react';
import { Modal, Button } from 'antd';
import { Modal, Button, Form, Radio, Tooltip } from 'antd';
import ImportWord from './ImportWord';
import ImportExcel from './ImportExcel';
import ImportExcelCopy from './ImportExcelCopy';
import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
const importModes = [
{ name: 'Word导入', key: 'word' },
{ name: 'Excel导入', key: 'excel' },
{ name: 'Excel复制粘贴', key: 'excel-copy' },
]
const ImportModal = (props) => {
const { visible, onCancel, addMode } = props;
const { view, catalogId, visible, onCancel } = props;
const [ excelFiles, setExcelFiles ] = useState([]);
const [ modeKey, setModeKey ] = useState('');
const [ hints, setHints ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const onOk = () => {
if (addMode==='excel') {
if ((excelFiles||[]).length === 0) {
showMessage('warn', '请先选择文件上传');
} else {
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,
},
fileList: row.upload
},
callback: data => {
setConfirmLoading(false);
reset();
onCancel && onCancel(true, [], data||{});
},
error: () => {
setConfirmLoading(false);
}
});
} else if (modeKey==='excel') {
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.extractExcelContent',
payload: { fileList: excelFiles },
payload: { fileList: row.upload },
callback: data => {
setConfirmLoading(false);
onCancel && onCancel(data||[]);
reset();
onCancel && onCancel(false, data||[]);
},
error: () => {
setConfirmLoading(false);
}
})
} else if (modeKey==='excel-copy') {
if ((hints||[]).length === 0) {
showMessage('warn', '请先从Excel文件中复制内容');
} else {
reset();
onCancel && onCancel(false, hints||[]);
}
}
return;
} else if (addMode==='excel-copy') {
if ((hints||[]).length === 0) {
showMessage('warn', '请先从Excel文件中复制内容');
} else {
onCancel && onCancel(hints||[]);
}
return;
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
}
const cancel = () => {
......@@ -53,32 +92,19 @@ const ImportModal = (props) => {
}
const reset = () => {
setExcelFiles([]);
form.resetFields();
setModeKey('');
setHints([]);
setConfirmLoading(false);
}
const onImportExcelChange = (files) => {
setExcelFiles(files||[]);
}
const onImportExcelCopyChange = (data) => {
setHints(data||[]);
}
let title = '';
if (addMode==='excel') {
title = 'Excel导入';
} else if (addMode==='excel-copy') {
title = 'Excel复制粘贴导入';
} else if (addMode==='ddl') {
title = 'DDL导入';
}
const footer = [
<Button
key="0"
type="primary"
onClick={cancel}
>
取消
......@@ -97,21 +123,60 @@ const ImportModal = (props) => {
<Modal
forceRender
visible={visible}
title={title}
title='模型导入'
width={520}
onCancel={cancel}
footer={footer}
>
{
addMode==='excel' && (
<ImportExcel onChange={onImportExcelChange} {...props} />
)
}
{
addMode==='excel-copy' && (
<ImportExcelCopy onChange={onImportExcelCopyChange} {...props} />
)
}
<Form form={form}>
<Form.Item
name='mode'
label='导入方式'
rules={[
{
required: true,
message: '请选择导入方式',
},
]}
>
<Radio.Group onChange={onModeChange} value={modeKey}>
{
importModes.map((item, index) => {
let title = '';
if (item.key==='word'&&(view!=='dir'||(catalogId||'') === '')) {
title = '请先选择主题';
}
return (
<Tooltip key={index} title={title}>
<Radio
value={item.key}
disabled={item.key==='word'&&(view!=='dir'||(catalogId||'') === '')}>
{item.name}
</Radio>
</Tooltip>
);
})
}
</Radio.Group>
</Form.Item>
{
modeKey==='word' && (
<ImportWord {...props} />
)
}
{
modeKey==='excel' && (
<ImportExcel {...props} />
)
}
{
modeKey==='excel-copy' && (
<ImportExcelCopy onChange={onImportExcelCopyChange} {...props} />
)
}
</Form>
</Modal>
)
}
......
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
import React, { useState } from 'react';
import { Button, Upload, Modal } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
const ImportWordModal = (props) => {
const { onCancel, visible, catalogId } = props;
const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
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.importWordGenerateModelDraft',
payload: {
params: {
catalogId,
},
fileList
},
callback: data => {
setConfirmLoading(false);
reset();
if (onCancel) {
onCancel(true, data||[]);
}
},
error: () => {
setConfirmLoading(false);
}
})
}
const reset = () => {
setConfirmLoading(false);
setFileList([]);
}
return (
<Modal
forceRender
visible={visible}
title='Word导入'
width={520}
confirmLoading={confirmLoading}
onCancel={() => {
reset();
onCancel && onCancel();
}}
onOk={handleOk}
>
<div className='mt-3'>
<span className='mr-3'>Word上传:</span>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</div>
</Modal>
)
}
export default ImportWordModal;
\ No newline at end of file
import React, { useState, useEffect, useRef } from "react";
import { Space, Button, Tooltip, Modal, Divider, Pagination, Table } from 'antd';
import { EditOutlined, DeleteOutlined, DownOutlined, UpOutlined, HistoryOutlined } from '@ant-design/icons';
import { Button, Tooltip, Modal, Pagination, Table, Dropdown, Menu, Divider } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import SmoothScroll from 'smooth-scroll';
import classnames from 'classnames';
import { dispatchLatest, dispatch } from '../../../../model';
import { showMessage, getQueryParam, paginate } from '../../../../util';
import { AnchorId, AnchorTimestamp } from '../../../../util/constant';
import { AnchorId, AnchorTimestamp, Action, CatalogId, ModelerId } from '../../../../util/constant';
import './ModelTable.less';
const ModelTable = (props) => {
const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, modelId = null } = props;
const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, modelId = null, view } = props;
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ subData, setSubData ] = useState([]);
const moreMenu = (record) => {
return <Menu onClick={(e) => { onMoreMenuClick(e, record); }}>
<Menu.Item key='history'>
历史版本
</Menu.Item>
<Menu.Item key='copy'>
复制模型
</Menu.Item>
{
(record?.state?.supportedActions||[]).length>0 && record?.state?.supportedActions.map((item, index) => {
return (
<Menu.Item key={`action-${index}`}>
{item.cnName||''}
</Menu.Item>
);
})
}
{
record?.deployable && <Menu.Item key='createTable'>
建表
</Menu.Item>
}
</Menu>
}
const columns = [
{
title: '序号',
......@@ -69,51 +94,68 @@ const ModelTable = (props) => {
{
title: '状态',
dataIndex: 'state',
width: 100,
width: 120,
ellipsis: true,
render: (_, record) => {
return record?.state?.cnName||'';
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: '操作',
key: 'action',
width: 230,
width: 180,
render: (_,record) => {
return (
<Space size='small'>
{
<React.Fragment>
<Tooltip placement='bottom' title={'修改'}>
<Button icon={<EditOutlined />} size='small' disabled={!record?.editable&&!record?.permitCheckOut} onClick={() => { editItem(record); }} />
</Tooltip>
<Tooltip placement='bottom' title={'删除'}>
<Button icon={<DeleteOutlined />} size='small' disabled={!record?.deletable} onClick={() => { deleteItem(record); }} />
</Tooltip>
<Tooltip placement='bottom' title={'版本历史'}>
<Button icon={<HistoryOutlined />} size='small' onClick={() => { historyItem(record); }} />
</Tooltip>
</React.Fragment>
}
{
(record?.state?.supportedActions||[]).length>0 && record?.state?.supportedActions.map((item, index) => {
return (
<React.Fragment>
{
(record.editable||record.permitCheckOut||record.deletable) && index===0 && <Divider type='vertical' />
}
<Button key={index} size='small' onClick={() => { stateAction(record, item); }} >{item.cnName||''}</Button>
</React.Fragment>
);
})
}
{
record?.deployable && <React.Fragment>
{ record.editable && <Divider type='vertical' /> }
<Button size='small' onClick={() => { deployAction(record); }} >建表</Button>
</React.Fragment>
}
</Space>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
type='link'
size='small'
disabled={!record?.editable&&!record?.permitCheckOut}
onClick={() => { editItem(record); }}
style={{ padding: 0 }}
>
编辑
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Button
type='link'
size='small'
disabled={!record?.deletable}
onClick={() => { deleteItem(record); }}
style={{ padding: 0 }}
>
删除
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Dropdown overlay={moreMenu(record)} trigger={['click']} placement="bottomLeft">
<Button
type='link'
size='small'
style={{ padding: 0 }}
>
更多<DownOutlined />
</Button>
</Dropdown>
</div>
)
}
}
......@@ -206,6 +248,21 @@ const ModelTable = (props) => {
onItemAction && onItemAction(record, 'detail');
}
const onMoreMenuClick = (e, record) => {
const { key } = e;
if (key === 'history') {
historyItem(record);
} else if (key === 'copy') {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(view==='dir')?(catalogId||''):''}&${ModelerId}=${record.id}`);
} else if (key === 'createTable') {
deployAction(record);
} else if (key.indexOf('action') !== -1) {
const index = (key.split('-'))[1];
const action = record.state?.supportedActions[index];
stateAction(record, action);
}
}
const deployAction = (record) => {
onAutoCreateTable && onAutoCreateTable(record);
}
......@@ -334,7 +391,7 @@ const ModelTable = (props) => {
sticky={!modelId}
/>
{
!modelId && <Pagination
!modelId && (data||[]).length>0 && <Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
......
......@@ -20,4 +20,12 @@
.yy-table {
height: auto !important;
}
.yy-table-placeholder {
display: none;
}
.yy-table-tbody > tr > td {
padding: 8px 16px !important;
}
}
\ No newline at end of file
......@@ -346,6 +346,9 @@ const ModelTree = (props) => {
setItem(null);
itemRef.current = null;
getDirTreeData();
},
error: () => {
setLoading(false);
}
});
}
......
import React, { useEffect, useState } from 'react';
import { Table, Space, Button, Tooltip, Modal, Form, Drawer } from 'antd';
import { EditOutlined, ReconciliationOutlined, DeleteOutlined } from '@ant-design/icons';
import TemplateAction from './TemplateAction';
import { dispatch, dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
const TemplateCURDDrawer = (props) => {
const { visible, onCancel } = props;
const [ templates, setTemplates ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ action, setAction ] = useState('');
const [ currentTemplate, setCurrentTemplate ] = useState('');
const [ step, setStep ] = useState(0);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
getTemplates();
}
}, [ visible ])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '模版名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
},
{
title: '中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
},
{
title: '模版描述',
dataIndex: 'remark',
ellipsis: true,
},
{
title: '更新时间',
dataIndex: 'modifiedTs',
width: 120,
ellipsis: true,
},
{
title: '操作',
key: 'action',
width: 120,
render: (text,record) => {
return (
<Space size='small'>
<Tooltip placement='bottom' title={'修改'}>
<Button icon={<EditOutlined />} size='small' onClick={() => { editItem(record); }} />
</Tooltip>
<Tooltip placement='bottom' title={'详情'}>
<Button icon={<ReconciliationOutlined />} size='small' onClick={() => { detailItem(record); }} />
</Tooltip>
<Tooltip placement='bottom' title={'删除'}>
<Button icon={<DeleteOutlined />} size='small' onClick={() => { deleteItem(record); }} />
</Tooltip>
</Space>
)
}
}
];
const getTemplates = () => {
setLoading(true);
dispatch({
type: 'datamodel.getAllTemplates',
callback: data => {
setTemplates(data||[]);
setLoading(false);
},
error: () => {
setLoading(false);
}
})
}
const editItem = (record) => {
setStep(1);
setAction('edit');
setCurrentTemplate(record);
}
const detailItem = (record) => {
setStep(1);
setAction('detail');
setCurrentTemplate(record);
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该模版吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteTemplate',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模版删除成功');
getTemplates();
}
})
}
});
}
const onAddClick = () => {
setStep(1);
setAction('add');
}
const cancel = () => {
console.log('cancel');
reset();
onCancel && onCancel(false);
}
const reset = () => {
setStep(0);
setCurrentTemplate({});
setConfirmLoading(false);
}
const onActionChange = (data) => {
setCurrentTemplate(data);
}
const prev = () => {
setStep(step-1);
}
const save = async () => {
try {
const row = await form.validateFields();
const newTemplateData = {...currentTemplate, ...row};
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.saveTemplate',
payload: {
data: newTemplateData
},
callback: () => {
setConfirmLoading(false);
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
let title = '';
if (step === 0) {
title = '模版配置';
}
if (step === 1) {
if (action === 'add') {
title = '新增模版';
} else if (action === 'edit') {
title = '模版编辑';
} else if (action === 'detail') {
title = '模版详情';
}
}
let actionComponent = null;
if (step === 0) {
actionComponent = (
<Button
type="primary"
onClick={onAddClick}
style={{ marginLeft: 'auto' }}
>
新增模版
</Button>
)
} else if (step === 1) {
if (action === 'detail') {
actionComponent = (
<Button
key="0"
onClick={prev}
style={{ marginLeft: 'auto' }}
>
返回
</Button>
)
} else {
actionComponent = (
<Space
style={{ marginLeft: 'auto' }}
>
<Button
key="0"
onClick={prev}
>
返回
</Button>,
<Button
key="1"
type="primary"
loading={confirmLoading}
onClick={save}
>
保存
</Button>
</Space>
)
}
}
return (
<Drawer
title={title}
visible={visible}
width={1200}
closable={true}
onClose={() => {
cancel();
}}
>
{
step === 0 && <React.Fragment>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
{actionComponent}
</div>
<Table
loading={loading}
columns={columns}
rowKey={'id'}
dataSource={templates||[]}
pagination={false}
sticky
/>
</React.Fragment>
}
{
step === 1 && <React.Fragment>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
{actionComponent}
</div>
<TemplateAction
onChange={onActionChange}
action={action}
id={currentTemplate.id}
form={form}
/>
</React.Fragment>
}
{ contextHolder }
</Drawer>
);
}
export default TemplateCURDDrawer;
\ No newline at end of file
.data-model {
display: flex;
background-color: #F0F2F5;
background-color: #fff;
height: 100%;
.left {
width: 230px;
background-color: #fff;
margin-right: 10px;
border-right: 1px solid #EFEFEF;
overflow: hidden;
}
.tree-toggle-wrap {
position: relative;
width: 20px;
height: calc(100vh - 94px);
.tree-toggle {
display: flex;
justify-content: center;
align-items: center;
left: 0;
right: 0;
background: #f2f5fc;
position: absolute;
top: calc(50% - 40px);
width: 12px;
height: 80px;
border-radius: 0 12px 12px 0;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
cursor: pointer;
}
}
.right {
width: calc(100% - 250px);
}
}
.data-model-collapse {
.left {
width: 0;
}
.right {
width:calc(100% - 240px);
background-color: #fff;
width: calc(100% - 20px);
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Input, Drawer, Table, Tooltip } from 'antd';
import { Input, Table, Tooltip } from 'antd';
import { dispatch } from '../../../../model';
const ConstraintDetailDrawer = (props) => {
const { onCancel, visible } = props;
const [loading, setLoading] = useState(false);
const [rules, setRules] = useState([]);
const [keyword, setKeyword] = useState('');
const ConstraintDetail = (props) => {
const [ loading, setLoading ] = useState(false);
const [ rules, setRules ] = useState([]);
const [ keyword, setKeyword ] = useState('');
useEffect(() => {
if (visible) {
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraints',
callback: data => {
setLoading(false);
const _rules = [];
(data||[]).forEach(constraint => {
(constraint.allRules||[]).forEach(rule => {
_rules.push({...constraint, ...{ ruleName: rule.name||'', ruleDesc: rule.desc||'' }});
});
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraints',
callback: data => {
setLoading(false);
const _rules = [];
(data||[]).forEach(constraint => {
(constraint.allRules||[]).forEach(rule => {
_rules.push({...constraint, ...{ ruleName: rule.name||'', ruleDesc: rule.desc||'' }});
});
});
setRules(_rules);
},
error: () => {
setLoading(false);
}
})
}
setRules(_rules);
},
error: () => {
setLoading(false);
}
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
}, [])
const columns = [
{
......@@ -101,18 +98,8 @@ const ConstraintDetailDrawer = (props) => {
}
return (
<Drawer
title='规范详情'
placement="right"
closable={true}
width={1200}
onClose={() => {
onCancel && onCancel();
}}
visible={visible}
>
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<span className='mr-3'>前置依赖搜索:</span>
<Input
placeholder="请输入前置依赖名称"
allowClear
......@@ -129,8 +116,8 @@ const ConstraintDetailDrawer = (props) => {
pagination={false}
sticky
/>
</Drawer>
</div>
);
}
export default ConstraintDetailDrawer;
\ No newline at end of file
export default ConstraintDetail;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Button, Space } from 'antd';
import LocalStorage from 'local-storage';
import TemplateAction from './TemplateAction';
import { dispatchLatest } from '../../../../model';
import { getQueryParam, showMessage } from '../../../../util';
import { Action, TemplateId } from '../../../../util/constant';
import '../../Model/Component/EditModel.less';
const EditTemplate = (props) => {
const [ actionData, setActionData ] = useState({ action: '', templateId: '', });
const [ templateData, setTemplateData ] = useState({});
const [ confirmLoading, setConfirmLoading ] = useState(false);
const { action, templateId } = actionData;
const [form] = Form.useForm();
useEffect(() => {
const _action = getQueryParam(Action, props.location.search);
const _templateId = getQueryParam(TemplateId, props.location.search);
setActionData({ action: _action, templateId: _templateId });
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const save = async (e) => {
try {
const row = await form.validateFields();
const newTemplateData = {...templateData, ...row};
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.saveTemplate',
payload: {
data: newTemplateData
},
callback: data => {
setConfirmLoading(false);
if (action === 'add') {
showMessage("success", '新增模型成功');
setActionData({ ...actionData, ...{ action: 'detail',templateId: data.id } });
} else {
showMessage("success", '保存模型成功');
setActionData({ ...actionData, ...{ action: 'detail', templateId: data.id||'' } });
}
LocalStorage.set('templateChange', !(LocalStorage.get('templateChange')||false));
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const edit = () => {
setActionData({ ...actionData, action: 'edit' });
}
const cancelEdit = () => {
setActionData({ ...actionData, action: 'detail' });
}
const onActionChange = (data) => {
setTemplateData(data);
}
let title = '';
if (action === 'add') {
title = '新增模版';
} else if (action === 'edit') {
title = '模版编辑';
} else if (action === 'detail') {
title = '模版详情';
}
let actionsBtn = null;
if (action==='add') {
actionsBtn = (
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
)
} else if (action === 'detail') {
actionsBtn = (
<Space>
<Button type='primary' onClick={edit} danger >
编辑
</Button>
</Space>
);
} else if (action === 'edit') {
actionsBtn = (
<Space>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
<Button onClick={cancelEdit} >
取消
</Button>
</Space>
)
}
return (
<div className='edit-model position-relative'>
<div className='edit-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>{title}</span>
</div>
<div className='edit-container'>
<div className='edit-container-card'>
<TemplateAction onChange={onActionChange} action={action} id={templateId} form={form} />
</div>
</div>
<div className='edit-footer'>
{actionsBtn}
</div>
</div>
);
}
export default EditTemplate;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Spin, Tabs, Popover } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import TemplateActionHeader from './TemplateActionHeader';
import ImportActionTable from './ImportActionTable';
import ImportActionTable from '../../Model/Component/ImportActionTable';
import { dispatchLatest, dispatch } from '../../../../model';
const { TabPane } = Tabs;
const TemplateAction = (props) => {
const { action, onChange, form, id } = props;
const [ templateData, setTemplateData ] = useState(null);
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ tabKey, setTabKey ] = useState('1');
const [ loading, setLoading ] = useState(false);
useEffect(() =>{
//初始化form状态
......@@ -30,6 +37,7 @@ const TemplateAction = (props) => {
}, [action, id ]);
const getCurrentTemplate = () => {
setLoading(true);
dispatchLatest({
type: 'datamodel.getTemplate',
payload: {
......@@ -49,15 +57,23 @@ const TemplateAction = (props) => {
remark: data.remark||'',
});
}
},
error: () => {
setLoading(false);
}
})
}
const getSupportedDatatypes = () => {
setLoading(true);
dispatch({
type: 'datamodel.getSupportedDatatypes',
callback: data => {
setLoading(false);
setSupportedDatatypes(data||[]);
},
error: () => {
setLoading(false);
}
});
}
......@@ -72,21 +88,45 @@ const TemplateAction = (props) => {
}
const onTabChange = (key) => {
setTabKey(key);
}
return (
<React.Fragment>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
</React.Fragment>
<Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
</TabPane>
<TabPane
tab={
<span>
<span>数据表结构</span>
{
(action!=='detail'&&action!=='flow'&&action!=='detail-version') && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
}
</span>
}
key='2'
>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
</TabPane>
</Tabs>
</Spin>
);
};
......
import React, { useEffect, useState } from 'react';
import { Table, Button, Tooltip, Modal, Divider } from 'antd';
import { Action, TemplateId } from '../../../../util/constant';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
const TemplateCURD = (props) => {
const [ templates, setTemplates ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
getTemplates();
window?.addEventListener("storage", (e) => {
if (e.key === 'templateChange') {
getTemplates();
}
});
}, [])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '模版名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
render: (text, record, _) => {
return (
<Tooltip title={text||''}>
<a onClick={()=>{detailItem(record);}}>
{text||''}
</a>
</Tooltip>
);
}
},
{
title: '中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
},
{
title: '模版描述',
dataIndex: 'remark',
ellipsis: true,
},
{
title: '更新时间',
dataIndex: 'modifiedTs',
width: 120,
ellipsis: true,
},
{
title: '操作',
key: 'action',
width: 120,
render: (text,record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
type='link'
size='small'
onClick={() => { editItem(record); }}
style={{ padding: 0 }}
>
编辑
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Button
type='link'
size='small'
onClick={() => { deleteItem(record); }}
style={{ padding: 0 }}
>
删除
</Button>
</div>
)
}
}
];
const getTemplates = () => {
setLoading(true);
dispatch({
type: 'datamodel.getAllTemplates',
callback: data => {
setTemplates(data||[]);
setLoading(false);
},
error: () => {
setLoading(false);
}
})
}
const editItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=edit&${TemplateId}=${record.id}`);
}
const detailItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=detail&${TemplateId}=${record.id}`);
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该模版吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteTemplate',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模版删除成功');
getTemplates();
}
})
}
});
}
const onAddClick = () => {
window.open(`/data-govern/model-template-action?${Action}=add`);
}
return (
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<Button
onClick={onAddClick}
>
新建
</Button>
</div>
<Table
loading={loading}
columns={columns}
rowKey={'id'}
dataSource={templates||[]}
pagination={false}
sticky
/>
{ contextHolder }
</div>
);
}
export default TemplateCURD;
\ No newline at end of file
import React, { useState } from 'react';
import { Button, Upload, Modal } from 'antd';
import { Button, Upload, Form, Row, Col } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
const WordTemplateModal = (props) => {
const { onCancel, visible } = props;
const WordTemplate = (props) => {
const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ form ] = Form.useForm();
const downloadTemplate = () => {
window.open("/api/datamodeler/easyDataModelerExport/word/downloadTemplate");
}
......@@ -32,62 +32,87 @@ const WordTemplateModal = (props) => {
accept:".doc,.docx",
};
const handleOk = () => {
const normFile = (e) => {
return fileList;
};
const handleOk = async() => {
try {
const row = await form.validateFields();
if ((fileList||[]).length === 0) {
showMessage('info', '请先选择模版上传');
return;
}
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.uploadWordTemplate',
payload: { fileList: row.upload },
callback: data => {
setConfirmLoading(false);
reset();
},
error: () => {
setConfirmLoading(false);
}
})
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.uploadWordTemplate',
payload: { fileList },
callback: data => {
setConfirmLoading(false);
reset();
onCancel && onCancel();
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const reset = () => {
setConfirmLoading(false);
form.resetFields();
setFileList([]);
}
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
return (
<Modal
forceRender
visible={visible}
title='Word模版配置'
width={520}
confirmLoading={confirmLoading}
onCancel={() => {
reset();
onCancel && onCancel();
}}
onOk={handleOk}
>
<div>
<span className='mr-3'>最新模版下载:</span>
<Button icon={<DownloadOutlined />} onClick={ downloadTemplate }>
下载
<Form form={form} {...layout} onFinish={handleOk}>
<Form.Item
label='最新模版上传'
required={true}
>
<Row>
<Col>
<Form.Item
name='upload'
valuePropName="fileList"
getValueFromEvent={normFile}
noStyle
rules={[
{
required: true,
message: '请选择文件上传',
},
]}
>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
</Col>
<Col>
<Button className='ml-3' icon={<DownloadOutlined />} onClick={ downloadTemplate }>
模版下载
</Button>
</Col>
</Row>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit" loading={confirmLoading}>
确定
</Button>
</div>
<div className='mt-3'>
<span className='mr-3'>最新模版上传:</span>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</div>
</Modal>
</Form.Item>
</Form>
)
}
export default WordTemplateModal;
\ No newline at end of file
export default WordTemplate;
\ No newline at end of file
import React, { useState } from 'react';
import { Tabs } from 'antd';
import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD';
import ConstraintDetail from './Component/ConstraintDetail';
import './index.less';
const { TabPane } = Tabs;
const ModelConfig = () => {
const [ tabKey, setTabKey ] = useState('1');
const onTabChange = (key) => {
setTabKey(key);
}
return (
<div className='model-config'>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='Word模版配置' key='1'>
<WordTemplate />
</TabPane>
<TabPane tab='数据表类型配置' key='2'>
<TemplateCURD />
</TabPane>
<TabPane tab='规范配置' key='3'>
<ConstraintDetail />
</TabPane>
</Tabs>
</div>
)
}
export default ModelConfig;
\ No newline at end of file
.model-config {
height: calc(100vh - 30px);
padding: 20px;
background: #fff;
overflow: auto;
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import { ManageLayout } from "../../layout";
import DatasourceManage from './DatasourceManage';
import Map from './Map';
import Model from './Model';
import ModelConfig from './ModelConfig';
import AssetManage from './AssetManage';
import AssetBrowse from './AssetBrowse';
import AssetRecycle from './AssetRecycle';
......@@ -27,6 +28,7 @@ class Manage extends Component {
<Switch>
<Route path={`${match.path}/datasource-manage`} component={DatasourceManage} />
<Route path={`${match.path}/data-model`} component={Model} />
<Route path={`${match.path}/model-config`} component={ModelConfig} />
<Route path={`${match.path}/asset-map`} component={Map} />
<Route path={`${match.path}/asset-manage`} component={AssetManage} />
<Route path={`${match.path}/asset-browse`} component={AssetBrowse} />
......
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