Commit 680cef7a by zhaochengxiang

去掉服务

parent a4fe2c5e
...@@ -10,9 +10,8 @@ import * as map from './map'; ...@@ -10,9 +10,8 @@ import * as map from './map';
import * as datamodel from './datamodel'; import * as datamodel from './datamodel';
import * as assetmanage from './assetmanage'; import * as assetmanage from './assetmanage';
import * as tag from './tag'; import * as tag from './tag';
import * as pds from './pds';
const funcs = Connect({ user, datamodel, map, assetmanage, datasource, tag, pds }) const funcs = Connect({ user, datamodel, map, assetmanage, datasource, tag })
function* request(args) { function* request(args) {
const { type, payload, callback, error } = args.args; const { type, payload, callback, error } = args.args;
......
import * as pds from '../service/pds';
import { call } from 'redux-saga/effects';
export function* refreshCatalog(payload) {
return yield call(pds.refreshCatalog, payload);
}
export function* loadDataServiceCatalog() {
return yield call(pds.loadDataServiceCatalog)
}
export function* loadStateCatalog(payload) {
return yield call(pds.loadStateCatalog, payload);
}
export function* saveCatalog(payload) {
return yield call(pds.saveCatalog, payload);
}
export function* deleteCatalog(payload) {
return yield call(pds.deleteCatalog, payload);
}
export function* upDownCatalog(payload) {
return yield call(pds.upDownCatalog, payload);
}
export function* getServices(payload) {
return yield call(pds.getServices, payload);
}
export function* getGrantedServices(payload) {
return yield call(pds.getGrantedServices, payload)
}
export function* getStateServices(payload) {
return yield call(pds.getStateServices, payload)
}
export function* searchService(payload) {
return yield call(pds.searchService, payload)
}
export function* getDataService(payload) {
return yield call(pds.getDataService, payload)
}
export function* deleteService(payload) {
return yield call(pds.deleteService, payload);
}
export function* recatalogService(payload) {
return yield call(pds.recatalogService, payload);
}
export function* nextState(payload) {
return yield call(pds.nextState, payload);
}
export function* checkoutService(payload) {
return yield call(pds.checkoutService, payload);
}
export function* getCheckoutService(payload) {
return yield call(pds.getCheckoutService, payload)
}
export function* getServiceDigest(payload) {
return yield call(pds.getServiceDigest, payload)
}
export function* getVersions(payload) {
return yield call(pds.getVersions, payload)
}
export function* getDataServiceLocation(payload) {
return yield call(pds.getDataServiceLocation, payload)
}
export function* getSample(payload) {
return yield call(pds.getSample, payload)
}
export function* enableOData(payload) {
return yield call(pds.enableOData, payload)
}
export function* disableOData(payload) {
return yield call(pds.disableOData, payload)
}
export function* authorize(payload) {
return yield call(pds.authorize, payload)
}
export function* release(payload) {
return yield call(pds.release, payload)
}
export function* offline(payload) {
return yield call(pds.offline, payload);
}
export function* getSmartBiUrl(payload) {
return yield call(pds.getSmartBiUrl, payload)
}
export function* getOwners() {
return yield call(pds.getOwners)
}
export function* getDepartments() {
return yield call(pds.getDepartments)
}
export function* saveOwner(payload) {
return yield call(pds.saveOwner, payload)
}
export function* changeOwner(payload) {
return yield call(pds.changeOwner, payload)
}
export function* saveCols(payload) {
return yield call(pds.saveCols, payload);
}
export function* getCols(payload) {
return yield call(pds.getCols, payload);
}
export function* getAttrs(payload) {
return yield call(pds.getAttrs, payload);
}
export function* getJdbcInformation() {
return yield call(pds.getJdbcInformation);
}
export function* subscribe(payload) {
return yield call(pds.subscribe, payload);
}
\ No newline at end of file
...@@ -16,10 +16,6 @@ export const routes = [ ...@@ -16,10 +16,6 @@ export const routes = [
text: '数据源管理', text: '数据源管理',
}, },
{ {
name: 'data-service',
text: '服务管理',
},
{
name: 'data-model', name: 'data-model',
text: '模型设计', text: '模型设计',
}, },
......
import { PostFile, GetJSON, PostJSON, Post, Get, GetJSONRaw, callFetchRaw } from "../util/axios"
import { ContextPath } from "../util";
export function refreshCatalog() {
return GetJSON("/pdataservice/pdsCURD/refreshDataServiceCatalog")
}
export function loadDataServiceCatalog() {
return GetJSON("/pdataservice/pdsCURD/loadDataServiceCatalog");
}
export function loadStateCatalog(payload) {
return GetJSON("/pdataservice/pdsCURD/loadDataServiceStateCatalog", payload)
}
export function saveCatalog(payload) {
return PostJSON("/pdataservice/pdsCURD/saveDataServiceCatalog", payload)
}
export function deleteCatalog(payload) {
return PostJSON("/pdataservice/pdsCURD/deleteDataServiceCatalog", payload)
}
export function upDownCatalog(payload) {
return GetJSON("/pdataservice/pdsCURD/upDownDataServiceCatalog", payload)
}
export function getServices(payload) {
return GetJSON("/pdataservice/pdsCURD/getCurrentDataServiceCatalog", payload)
}
export function getGrantedServices(payload) {
return GetJSON("/pdataservice/pdsCURD/getGrantedDataService", payload)
}
export function getStateServices(payload) {
return GetJSON("/pdataservice/pdsCURD/getCurrentDataServiceStateCatalog", payload)
}
export function searchService(payload) {
return GetJSON("/pdataservice/pdsCURD/searchPDSDataServicesByNaming", payload)
}
export function getDataService(payload) {
return GetJSON("/pdataservice/pdsCURD/getDataService", payload)
}
export function deleteService(payload) {
return PostJSON("/pdataservice/pdsCURD/deleteDataService", payload);
}
export function recatalogService(payload) {
return Post("/pdataservice/pdsCURD/recatalogDataService", payload);
}
export function nextState(payload) {
return GetJSON("/pdataservice/pdsCURD/nextState", payload);
}
export function checkoutService(payload) {
return PostJSON("/pdataservice/pdsCURD/checkOutDataService", payload)
}
export function getCheckoutService(payload) {
return GetJSON("/pdataservice/pdsCURD/getCheckoutDataService", payload)
}
export function getServiceDigest(payload) {
return GetJSON("/pdataservice/pdsCURD/getDataServiceDigest", payload)
}
export function getVersions(payload) {
return PostJSON("/pdataservice/pdsCURD/getVersions", payload)
}
export function getDataServiceLocation(payload) {
return GetJSON("/pdataservice/pdsCURD/getDataServiceLocation", payload)
}
export function getSample(payload) {
return PostJSON("/pdataservice/pdsCURD/getSample", payload)
}
export function enableOData(payload) {
return Post("/pdataservice/pdsOData/enableOData", payload)
}
export function disableOData(payload) {
return Post("/pdataservice/pdsOData/disableOData", payload)
}
export function authorize(payload) {
return PostJSON("/pdataservice/pdsWorkflow/kickoffAuthorize", payload)
}
export function release(payload) {
return PostJSON("/pdataservice/pdsWorkflow/kickoffRelease", payload)
}
export function offline(payload) {
return PostJSON("/pdataservice/pdsWorkflow/kickoffOffline", payload)
}
export function getSmartBiUrl(payload) {
return Get(`/${payload.url}`);
}
export function getOwners() {
return GetJSON("/informationmanagement/userData/findAll")
}
export function getDepartments() {
return GetJSON("/informationmanagement/userData/getAllDepartment")
}
export function saveOwner(payload) {
return GetJSON("/informationmanagement/userData/getUserDataAndInsert", payload);
}
export function changeOwner(payload) {
return PostJSON("/pdataservice/pdsCURD/changeOwnerOfDataService", payload)
}
export function saveCols(payload) {
return PostJSON("/pdataservice/pdsModel/saveVisibleTitle", payload);
}
export function getCols(payload) {
return GetJSON("/pdataservice/pdsModel/getVisibleTitle", payload);
}
export function getAttrs(payload) {
return GetJSON("/pdataservice/pdsModel/attrs", payload);
}
export function getJdbcInformation() {
return GetJSON('/pdataservice/pdsDriver/url');
}
export function subscribe(payload) {
return callFetchRaw('post', '/pdataservice/pdsSub/subscribeMsg', payload)
}
\ No newline at end of file
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';
import { showMessage } from '../../../../util';
const ColSettingModal = (props) => {
const {visible, onCancel} = props;
const [loadingAttrs, setLoadingAttrs] = useState(false);
const [attrs, setAttrs] = useState(undefined);
const [catagories, setCatagories] = useState(undefined);
const [checkedKeys, setCheckedKeys] = useState([]);
const [confirmLoading, setConfirmLoading] = useState(false);
useEffect(() => {
if (visible) {
getAttrs();
getPreference();
}
}, [visible]);
const getAttrs = () => {
setLoadingAttrs(true);
dispatch({
type: 'pds.getAttrs',
payload: {
modelName: 'DataService'
},
callback: data => {
setAttrs(data);
setCatagories(Array.from(new Set((data||[]).map(item => item.status))));
setLoadingAttrs(false);
},
error: () => {
setLoadingAttrs(false);
}
})
}
const getPreference = () => {
dispatch({
type: 'pds.getCols',
payload: {
modelName: 'DataService'
},
callback: data => {
setCheckedKeys(data?.map(item => item.titleCnName));
}
})
}
const onCheckAllChange = (checked) => {
const newCheckedKeys = [];
if (checked) {
attrs?.forEach(col => {
newCheckedKeys.push(col.name);
});
}
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 = () => {
if ((checkedKeys||[]).length === 0) {
showMessage('warn', '不可进行全不选操作');
return;
}
setConfirmLoading(true);
dispatch({
type: 'pds.saveCols',
payload: {
params: {modelName: 'DataService'},
data: checkedKeys?.map(item => {
return { titleCnName: item }
})
},
callback: () => {
setConfirmLoading(false);
onCancel && onCancel(true);
showMessage('success', '操作成功');
},
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' }}>
{
catagories?.map((catagory, index) => {
return (
<div key={index}>
<div className='flex' style={{ alignItems: 'center', padding: '5px 0 15px' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{catagory}</span>
</div>
<Row>
{
attrs?.filter(col => col.status===catagory)?.map((col, index) => {
return (
<Col className='mb-3' key={index} md={6}>
<div className='d-flex'>
<Checkbox checked={ checkedKeys.indexOf(col.name||'')!==-1 } value={col.name} onChange={onCheckChange} >
</Checkbox>
<Typography.Paragraph className='ml-1' title={col.name} ellipsis>
{col.name}
</Typography.Paragraph>
</div>
</Col>
)
})
}
</Row>
</div>
)
})
}
</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, useRef } from 'react';
import { Form, Button, Space, Tooltip } from 'antd';
import LocalStorage from 'local-storage';
import { useMount, useUnmount } from 'ahooks';
import ImportAction from './ImportAction';
import CatalogModal from './CatalogModal';
import { dispatchLatest } from '../../../../model';
import { getQueryParam, showMessage, showNotifaction } from '../../../../util';
import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, VersionId, Holder, DDL, ReadOnly } from '../../../../util/constant';
import HistoryAndVersionDrawer from './HistoryAndVersionDrawer';
import { EditModelContext } from './ContextManage';
import './EditModel.less';
const EditModel = (props) => {
const [ actionData, setActionData ] = useState({ action: '', catalogId: '', modelerId: '', hints: [], roughModelerData: null, permitCheckOut: false, editable: false, stateId: '', versionId: '', ddl: '', readOnly: false });
const [ modelerData, setModelerData ] = useState({});
const [ terms, setTerms ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ catalogModalVisible, setCatalogModalVisible ] = useState(false);
const [ historyAndVersionDrawerVisible, setHistoryAndVersionDrawerVisible ] = useState(false);
const [ autoTabKey, setAutoTabKey ] = useState(null);
const actionRef = useRef('');
const attrIsEditingRef = useRef(false);
const indexIsEditingRef = useRef(false);
const { action, catalogId, modelerId, hints, roughModelerData, permitCheckOut, editable, stateId, versionId, holder, ddl, readOnly } = actionData;
const [form] = Form.useForm();
useEffect(() => {
const _action = getQueryParam(Action, props.location.search);
const _catalogId = getQueryParam(CatalogId, props.location.search);
const _modelerId = getQueryParam(ModelerId, props.location.search);
const _hintsStr = getQueryParam(Hints, props.location.search);
const _modelerDataStr = getQueryParam(ModelerData, props.location.search)
const _permitCheckOut = getQueryParam(PermitCheckOut, props.location.search);
const _editable = getQueryParam(Editable, props.location.search);
const _stateId = getQueryParam(StateId, props.location.search);
const _versionId = getQueryParam(VersionId, props.location.search);
const _holder = getQueryParam(Holder, props.location.search);
const _ddl = getQueryParam(DDL, props.location.search);
const _readOnly = getQueryParam(ReadOnly, props.location.search);
let _hints = [];
if ((_hintsStr||'') !== '') {
_hints = _hintsStr.split(',');
}
let _roughModelerData = null;
if ((_modelerDataStr||'') !== '') {
_roughModelerData = JSON.parse(_modelerDataStr);
judgeAttributeRepeat(_roughModelerData.easyDataModelerDataModelAttributes);
}
setActionData({ action: _action, catalogId: _catalogId, modelerId: _modelerId, hints: _hints, roughModelerData: _roughModelerData, permitCheckOut: (_permitCheckOut==='true'), editable: (_editable==='true'), stateId: _stateId, versionId: _versionId, holder: _holder, ddl: _ddl, readOnly: _readOnly });
actionRef.current = _action;
const interval = setInterval(() => {
heartbeat();
}, 10*60*1000);
return () => {
clearInterval(interval);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useMount(() => {
window?.addEventListener('beforeunload', confirmQuit);
})
useUnmount(() => {
window?.removeEventListener('beforeunload', confirmQuit);
})
const heartbeat = () => {
dispatchLatest({
type: 'datamodel.heartbeat'
});
}
const confirmQuit = (e) => {
if (actionRef.current==='edit' || actionRef.current==='add') {
e.returnValue = '';
}
}
const judgeAttributeRepeat = (attrs) => {
const names = [], nameCountMap = {}, terms = [];
var repeat = false;
(attrs||[]).forEach(item => {
names.push(item.name||'');
if (nameCountMap[item.name]) {
nameCountMap[item.name]++;
} else {
nameCountMap[item.name] = 1;
}
})
Object.keys(nameCountMap).forEach(key => {
if (nameCountMap[key] > 1) {
repeat = true;
terms.push(key);
}
})
if (repeat) {
setAutoTabKey('2');
setTerms(terms);
}
}
const save = (e) => {
e.stopPropagation();
if (attrIsEditingRef.current) {
showMessage("warn", '还有字段正在编辑,需先保存该字段!');
} else if (indexIsEditingRef.current) {
showMessage("warn", '还有索引正在编辑,需先保存该索引!');
} else {
saveLogic();
}
}
const saveLogic = async (e, cid = '') => {
setAutoTabKey(null);
try {
const row = await form.validateFields();
const newModelerData = {...modelerData, ...row};
if (newModelerData.easyDataModelerModelingTemplate==='' || newModelerData.easyDataModelerModelingTemplate==={}) {
newModelerData.easyDataModelerModelingTemplate = null;
}
if (newModelerData.partition && ( !newModelerData.partition.partitionType || newModelerData.partition.partitionType==={} || (newModelerData.partition.keys||[]).length===0 )) {
newModelerData.partition = null;
}
if (action==='add' && (cid||'')==='' && ((catalogId||'')==='')) {
setCatalogModalVisible(true);
return ;
}
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.saveDataModel',
payload: {
data: newModelerData
},
callback: data => {
if (action === 'add') {
dispatchLatest({
type: 'datamodel.bindCatalogDataModel',
payload: {
params: {
easyDataModelCatalogId: ((cid||'')!=='')?cid:catalogId,
easyDataModelerDataModelIds: data.id||''
}
},
callback: message => {
setConfirmLoading(false);
setTerms([]);
if ((message||'')!=='' && (message||'')!=='ok') {
showNotifaction('提示', message, 5);
} else {
showMessage("success", '新增模型成功');
}
setActionData({ ...actionData, ...{ action: 'detail', modelerId: data.id||'', editable: data?.editable||false, stateId: data?.state?.id||'' } });
actionRef.current = 'detail';
LocalStorage.set('modelId', data.id||'');
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
},
error: () => {
setConfirmLoading(false);
}
})
} else {
setConfirmLoading(false);
setTerms([]);
showMessage("success", '保存模型成功');
const _action = getQueryParam(Action, props.location.search);
setActionData({ ...actionData, ...{ action: (_action==='flow')?'flow':'detail', modelerId: data.id||'', stateId: data?.state?.id||'', permitCheckOut: data?.permitCheckOut||false, editable: data?.editable||false } });
actionRef.current = (_action==='flow')?'flow':'detail';
LocalStorage.set('modelId', data.id||'');
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
}
},
error: (err) => {
setConfirmLoading(false);
setTerms([err.ApiError?.message||'']);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
setAutoTabKey('1');
}
}
const edit = () => {
setActionData({ ...actionData, action: 'edit' });
actionRef.current = 'edit';
}
const cancelEdit = () => {
const _action = getQueryParam(Action, props.location.search);
setActionData({ ...actionData, action: (_action==='flow')?'flow':'detail' });
actionRef.current = (_action==='flow')?'flow':'detail';
}
const onActionChange = (data) => {
setModelerData(data);
const _action = getQueryParam(Action, props.location.search);
if (_action === 'flow' || (permitCheckOut&&data?.permitCheckOut===false)) {
setActionData({ ...actionData, ...{modelerId: data?.id, editable: data?.editable||false, stateId: data?.state?.id||'', permitCheckOut: data?.permitCheckOut||false} });
}
}
const onCatalogModalCancel = (id = null) => {
setCatalogModalVisible(false);
if ((id||'') !== '') {
saveLogic(null, id);
}
}
const onHistory = () => {
setHistoryAndVersionDrawerVisible(true);
}
const onHistoryAndVersionDrawerCancel = () => {
setHistoryAndVersionDrawerVisible(false);
}
const attrIsEditingFunction = (value) => {
attrIsEditingRef.current = value;
}
const indexIsEditingFunction = (value) => {
indexIsEditingRef.current = value;
}
let title = '';
if (action === 'add') {
title = '新增模型';
} else if (action === 'edit') {
title = '模型编辑';
} else if (action === 'detail' || action === 'flow') {
title = '模型详情';
} else if (action === 'detail-version') {
title = '模型版本详情';
}
let actionsBtn = null;
if (action==='add') {
actionsBtn = (
<Space>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
</Space>
)
} else if (action === 'detail') {
let editTip = '';
if (!editable && stateId !== '4') {
if (stateId === '2') {
editTip = '待发布的模型不允许编辑';
}
}
if (!permitCheckOut && stateId === '4') {
editTip = `${holder||''}正在编辑中, 不允许再编辑`;
}
actionsBtn = (
<Space>
<Button type='primary' onClick={onHistory} danger >版本历史</Button>
{
(readOnly!=='true') && <Tooltip title={editTip}>
<Button type='primary' onClick={edit} disabled={(stateId==='4')?!permitCheckOut:!editable} danger >
编辑
</Button>
</Tooltip>
}
</Space>
);
} else if (action === 'edit') {
actionsBtn = (
<Space>
<Button onClick={cancelEdit} >
取消
</Button>
<Button type='primary' onClick={onHistory} danger >
版本历史
</Button>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
</Space>
)
} else if (action === 'flow') {
actionsBtn = (
<Space>
<Button type='primary' onClick={onHistory} danger>
版本历史
</Button>
{
editable && <Button type='primary' onClick={edit} danger >
编辑
</Button>
}
</Space>
);
}
return (
<EditModelContext.Provider value={{
attrIsEditingFunction,
indexIsEditingFunction,
}}>
<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'>
<ImportAction hints={hints} onChange={onActionChange} action={action} modelerId={modelerId} ddl={ddl} form={form} terms={terms} roughModelerData={roughModelerData} permitCheckOut={permitCheckOut} stateId={stateId} versionId={versionId} autoTabKey={autoTabKey} {...props} />
</div>
</div>
<div className='edit-footer'>
{actionsBtn}
</div>
<CatalogModal
visible={catalogModalVisible}
onCancel={onCatalogModalCancel}
/>
<HistoryAndVersionDrawer
id={modelerId}
visible={historyAndVersionDrawerVisible}
onCancel={onHistoryAndVersionDrawerCancel}
/>
</div>
</EditModelContext.Provider>
);
}
export default EditModel;
\ No newline at end of file
.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;
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
import React, { useState, useEffect } from 'react';
import { Modal, Button, Form, Descriptions, Space, Spin } from 'antd';
import { dispatch } from '../../../../model';
import SelectUsers from './SelectUsers';
import './ExchangeOwner.less';
const FC = ({ visible, id, onCancel }) => {
const [service, setService] = useState(undefined);
const [loading, setLoading] = useState(false);
const [owners, setOwners] = useState(undefined);
const [currentOwner, setCurrentOwner] = useState(undefined);
useEffect(() => {
if (visible) {
getService();
getOwners();
}
}, [visible, id])
const getService = () => {
setLoading(true);
dispatch({
type: 'pds.getDataService',
payload: {
id
},
callback: (data) => {
setLoading(false);
setService(data);
setCurrentOwner(data?.editor);
},
error: () => {
setLoading(false);
}
})
}
const getOwners = () => {
dispatch({
type: 'pds.getOwners',
callback: (data) => {
setOwners(data);
},
error: () => {
}
})
}
const onOwnerChange = (value) => {
setCurrentOwner(value);
if (value) {
dispatch({
type: 'pds.saveOwner',
payload: {
jobNumber: value
}
})
}
}
const cancel = () => {
reset();
onCancel?.();
}
const onOk = () => {
setLoading(true);
dispatch({
type: 'pds.changeOwner',
payload: {
params: {
id,
ownerName: currentOwner
}
},
callback: () => {
setLoading(false);
onCancel?.(true);
},
error: () => {
setLoading(false);
}
})
}
const reset = () => {
setService(undefined);
setLoading(false);
}
const footer = [
<Button
key="0"
onClick={cancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={onOk}
>
确定
</Button>,
];
return (
<Modal
className='exchange-owner'
forceRender
visible={visible}
title='更换管理人'
width={800}
onCancel={cancel}
footer={footer}
>
<Spin spinning={loading}>
<Descriptions title={<Space>
<div style={{ height: 18, width: 3, background: 'rgb(2, 105, 172)' }}></div>
<div>基本信息</div>
</Space>}>
<Descriptions.Item label="数据服务名称">{service?.name}</Descriptions.Item>
<Descriptions.Item label="数据服务技术名称">{service?.cnName}</Descriptions.Item>
<Descriptions.Item label="数据服务描述">{service?.remark}</Descriptions.Item>
</Descriptions>
<Descriptions title={<Space>
<div style={{ height: 18, width: 3, background: 'rgb(2, 105, 172)' }}></div>
<div>提供方信息</div>
</Space>} />
<Space>
<div>管理人:</div>
<div style={{ width: 200 }}>
<SelectUsers type='edit' users={owners} value={currentOwner} onChange={onOwnerChange} />
</div>
</Space>
</Spin>
</Modal>
)
}
export default FC;
\ No newline at end of file
.exchange-owner {
.yy-descriptions-header {
margin-bottom: 5px;
}
}
\ 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, getDataModelerRole } from '../../../../util';
import { DataModelerRoleReader } from '../../../../util/constant';
// import Tag from "../../Tag";
import './ModelTable.less';
const { Text } = Typography;
const { Column } = Table;
const ModelNameColumn = (props) => {
const { text, record, detailItem } = props;
const [ data, setData ] = useState(record);
const cols = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '字段中文名称',
width: 160,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
},
{
title: '字段英文名称',
width: 160,
dataIndex: 'name',
editable: true,
ellipsis: true,
},
];
let _textComponent = <span style={{ color: '#000' }}>{text}</span>;
if (data.digest) {
_textComponent = <div style={{ width: 500, maxHeight: 300, overflow: 'auto' }}>
<Table
rowKey='name'
dataSource={data.digest.attributeDigests||[]}
columns={cols}
loading={false}
pagination={false}
size='small'
rowClassName={(record, index) => {
if (record?.primaryKey) {
return 'primary-row';
}
return '';
}}
/>
</div>;
}
return (
<Tooltip
title={_textComponent}
overlayClassName='tooltip-common'
onVisibleChange={(visible) => {
if (visible && !record.digest) {
dispatch({
type: 'datamodel.getDataModelDigest',
payload: {
id: record.id
},
callback: _data => {
record.digest = _data;
setData({...record});
}
})
}
}}
>
<a onClick={()=>{detailItem(record);}}>
{text||''}
</a>
</Tooltip>
);
}
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', getDataModelerRole(user)===DataModelerRoleReader);
}
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 React from 'react';
import { Modal, Button, Select, Input } from 'antd';
import { showMessage } from '../../../../util';
import { dispatch } from '../../../../model';
const { Option } = Select;
class ExportDDLModal extends React.Component {
constructor() {
super();
this.state = {
loadingDDLGenerators: false,
ddlGenerators: [],
selectDDLGeneratorName: null,
selectModalerNameKey: 0,
confirmLoading: false,
ddlExportSuccess: false,
ddlExportStrings: [],
ddlExportString: '',
loadingDatabases: false,
databases: [],
filterDatabases: [],
currentDatabaseId: '',
loadingSchemas: false,
schemas: [],
currentSchemaId: ''
}
}
componentDidMount() {
this.init();
}
componentDidUpdate(preProps, preState) {
const { visible } = this.props;
if (visible!==preProps.visible && visible) {
this.init();
}
}
init = () => {
const { reference = 'exportDDL' } = this.props;
if (reference === 'exportDDL') {
this.getDDLGenerators();
} else if (reference === 'createTable') {
this.getDDLGeneratorsThenGetAllDatasource();
}
}
getDDLGeneratorsThenGetAllDatasource = () => {
this.getDDLGenerators(true);
}
getDDLGenerators = (needDB = false) => {
this.setState({ loadingDDLGenerators: true }, () => {
dispatch({
type: 'datamodel.ddlGenerators',
callback: data => {
this.setState({ ddlGenerators: data, loadingDDLGenerators: false, selectDDLGeneratorName: (data||[]).length>0?data[0].name:'' }, () => {
if (needDB) {
this.getDatabases();
}
});
},
error: () => {
this.setState({ loadingDDLGenerators: false });
}
});
})
}
getDatabases = () => {
const { env } = this.props;
const { selectDDLGeneratorName } = this.state;
console.log('env', env);
this.setState({ loadingDatabases: true }, () => {
dispatch({
type: 'datamodel.getDatasourcesByEnv',
payload: {
catalog: env?.value,
model: 'Catalog,Database'
},
callback: data => {
let _filterData = [];
if (selectDDLGeneratorName === 'Greenplum') {
_filterData = data.filter(item => item.databaseType==='PostgreSQL');
} else if (selectDDLGeneratorName === 'MySQL') {
_filterData = data.filter(item => item.databaseType==='MySQL');
}
this.setState({ loadingDatabases: false, databases: data, filterDatabases: _filterData, currentDatabaseId: ((_filterData||[]).length>0)?_filterData[0]._id:'' }, () => {
if ((_filterData||[]).length>0) {
this.getSchemas(_filterData[0]?._id);
} else {
this.setState({ schemas: [], currentSchemaId: '' });
}
});
},
error: () => {
this.setState({ loadingDatabases: false });
}
});
})
}
getSchemas = (dbId) => {
this.setState({ loadingSchemas: true }, () => {
dispatch({
type: 'datamodel.getSchemasByDatasourceId',
payload: {
parentId: dbId||''
},
callback: data => {
this.setState({ loadingSchemas: false, schemas: data, currentSchemaId: (data||[]).length>0?data[0]._id:'' });
},
error: () => {
this.setState({ loadingSchemas: false });
}
});
})
}
generatorDDLStrings = () => {
const { ids, reference } = this.props;
const { ddlGenerators, loadingDDLGenerators, selectDDLGeneratorName, currentSchemaId } = this.state;
if (loadingDDLGenerators) {
showMessage('info', '正在加载ddl支持的数据库类型');
return;
}
if ((ddlGenerators||[]).length===0) {
showMessage('info', 'ddl支持的数据库类型没有查找到,重新查找中');
this.getDDLGenerators();
return;
}
if (!selectDDLGeneratorName || selectDDLGeneratorName==='') {
showMessage('info', '请选择ddl支持的数据库类型');
return;
}
if (reference==='createTable' && (currentSchemaId||'')==='') {
showMessage('info', '请选择Schema');
return;
}
this.setState({ confirmLoading: true }, () => {
dispatch({
type: 'datamodel.exportDDLString',
payload: {
ids: ids.join(','),
ddlGeneratorName: selectDDLGeneratorName
},
callback: data => {
this.setState({ confirmLoading: false }, () => {
this.setState({ ddlExportSuccess: true, ddlExportStrings: data||[], selectModalerNameKey: 0, ddlExportString: data[0]||'' });
});
},
error: () => {
this.setState({ confirmLoading: false });
}
});
})
}
createTable = () => {
const { onCancel } = this.props;
const { databases, schemas, currentDatabaseId, currentSchemaId, ddlExportStrings, selectDDLGeneratorName } = this.state;
let _currentDatabase = {}, _currentSchema = {};
(databases||[]).forEach(item => {
if (item._id === currentDatabaseId) {
_currentDatabase = item;
}
});
(schemas||[]).forEach(item => {
if (item._id === currentSchemaId) {
_currentSchema = item;
}
});
let namespace = '', scope = '', dbType = '';
if ((_currentSchema?.namePathList||[]).length>0) {
namespace = _currentSchema.namePathList[0];
}
if ((_currentSchema?.sysList||[]).length>0) {
scope = _currentSchema.sysList[0];
}
if (selectDDLGeneratorName === 'Greenplum') {
dbType = 'PGTarget';
} else if (selectDDLGeneratorName === 'MySQL') {
dbType = 'MysqlTarget';
}
this.setState({ confirmLoading: true }, () => {
dispatch({
type: 'datamodel.autoCreateTable',
payload: {
params: {
namespace,
scope,
dataSourceName: _currentDatabase?.name,
schema: _currentSchema?.name,
dbType,
},
data: ddlExportStrings
},
callback: () => {
this.setState({ confirmLoading: false });
this.reset();
onCancel && onCancel();
},
error: () => {
this.setState({ confirmLoading: false });
}
});
})
}
reset = () => {
this.setState({ ddlExportSuccess: false, ddlExportStrings: [] });
}
onModalerNameChange = (value) => {
const { ddlExportStrings } = this.state;
this.setState({ selectModalerNameKey: value, ddlExportString: ddlExportStrings[value]||'' });
}
onDDLGeneratorChange = (value) => {
this.setState({ selectDDLGeneratorName: value||'' }, () => {
this.getDatabases();
});
}
onDatabaseChange = (value) => {
this.setState({ currentDatabaseId: value||'' }, () => {
this.getSchemas(value);
});
}
onSchemaChange = (value) => {
this.setState({ currentSchemaId: value||'' });
}
onDDLStringChange = (e) => {
const { reference = 'exportDDL' } = this.props;
const { ddlExportStrings, selectModalerNameKey } = this.state;
if (reference === 'createTable') {
const _newDDLExportStrings = [...ddlExportStrings];
_newDDLExportStrings[selectModalerNameKey] = e.target.value;
this.setState({ ddlExportString: e.target.value||'', ddlExportStrings: _newDDLExportStrings });
}
}
render() {
const { visible, onCancel, names, ids, reference } = this.props;
const { ddlGenerators, loadingDDLGenerators, confirmLoading, selectDDLGeneratorName, ddlExportSuccess, ddlExportString, selectModalerNameKey, loadingDatabases, loadingSchemas, schemas, currentDatabaseId, currentSchemaId, filterDatabases } = this.state;
let title = '';
if (reference === 'exportDDL') {
title = ddlExportSuccess ? 'DDL导出详情' : 'DDL导出';
} else if (reference === 'createTable') {
title = ddlExportSuccess ? '模型自动建表详情' : '模型自动建表';
}
let footer = null;
if (reference === 'exportDDL') {
footer = ddlExportSuccess ? ([
<Button
key="0"
onClick={() => {
this.reset();
onCancel && onCancel();
}}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={() => {
window.open(`/api/datamodeler/easyDataModelerExport/ddlStringAsFile?ids=${ids.join(',')}&ddlGeneratorName=${selectDDLGeneratorName}`);
this.reset();
onCancel && onCancel();
}}
>
导出到文件
</Button>
]) : ([
<Button
key="0"
onClick={() => {
this.reset();
onCancel && onCancel();
}}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={this.generatorDDLStrings}
>
预览
</Button>
])
} else if (reference === 'createTable') {
footer = ddlExportSuccess ? ([
<Button
key="0"
onClick={() => {
this.reset();
onCancel && onCancel();
}}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={this.createTable}
>
建表
</Button>
]) : ([
<Button
key="0"
onClick={() => {
this.reset();
onCancel && onCancel();
}}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={this.generatorDDLStrings}
>
预览
</Button>
])
}
return (
<Modal
visible={visible}
title={title}
loading={confirmLoading}
onCancel={() => {
this.reset();
onCancel && onCancel();
}}
footer={footer}
>
<React.Fragment>
{
ddlExportSuccess ? <React.Fragment>
<div className='d-flex mb-5' style={{ alignItems: 'center' }}>
<span className='mr-2'>模型名称:</span>
<Select
value={selectModalerNameKey}
style={{ width: 180 }}
placeholder='请选择模型名称'
onChange={this.onModalerNameChange}
>
{
names && names.map((name, index) => {
return <Option key={index} value={index}>{name||''}</Option>
})
}
</Select>
</div>
<Input.TextArea value={ddlExportString||''} autoSize={{minRows:4,maxRows:20}} onChange={this.onDDLStringChange} ></Input.TextArea>
</React.Fragment> : <React.Fragment>
<div className='d-flex' style={{ alignItems: 'center' }}>
<span className='mr-2' style={{ width: 85 }}>数据库类型: </span>
<Select
value={selectDDLGeneratorName}
loading={loadingDDLGenerators}
style={{ width: 180 }}
placeholder='请选择数据库类型'
onChange={this.onDDLGeneratorChange}
>
{
ddlGenerators && ddlGenerators.map((ddlGenerator, index) => {
return <Option key={index} value={ddlGenerator.name||''}>{ddlGenerator.cnName||''}</Option>
})
}
</Select>
</div>
{
reference==='createTable' && <React.Fragment>
<div className='d-flex mt-3' style={{ alignItems: 'center' }}>
<span className='mr-2' style={{ width: 85 }}>数据源名称:</span>
<Select
value={currentDatabaseId}
style={{ width: 180 }}
placeholder='请选择数据源名称'
onChange={this.onDatabaseChange}
loading={loadingDatabases}
>
{
filterDatabases && filterDatabases.map((item, index) => {
return <Option key={index} value={item._id}>{item.name||''}</Option>
})
}
</Select>
</div>
<div className='d-flex mt-3' style={{ alignItems: 'center' }}>
<span className='mr-2' style={{ width: 85 }}>Schema名称:</span>
<Select
value={currentSchemaId}
style={{ width: 180 }}
placeholder='请选择Schema名称'
onChange={this.onSchemaChange}
loading={loadingSchemas}
>
{
schemas && schemas.map((item, index) => {
return <Option key={index} value={item._id}>{item.name||''}</Option>
})
}
</Select>
</div>
</React.Fragment>
}
</React.Fragment>
}
</React.Fragment>
</Modal>
)
}
}
export default ExportDDLModal;
\ No newline at end of file
import React, { useState } from 'react';
import { Modal, Button, Form, Radio } from 'antd';
const exportModes = [
{ name: '导出DDL', key: 'ddl' },
{ 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={540}
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}
key={index}
>
{item.name}
</Radio>
);
})
}
</Radio.Group>
</Form.Item>
</Form>
</Modal>
)
}
export default ExportOtherModal;
\ 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
import React, { useEffect, useState, useContext, useMemo } from 'react';
import { Spin, Tooltip, Typography, Pagination, Table } from 'antd';
import { AppContext } from '../../../../App';
import { dispatch } from '../../../../model';
import { paginate } from '../../../../util';
const FC = (props) => {
const app = useContext(AppContext);
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [pagination, setPagination] = useState({pageNum: 1, pageSize: 20});
const {pageNum, pageSize} = pagination;
const cols = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '数据服务资产编码',
dataIndex: 'code',
ellipsis: true,
render: (_, record) => record.basicInfo?.code
},
{
title: '数据服务名称',
dataIndex: 'name',
ellipsis: true,
render: (_, record) => record.basicInfo?.name
},
{
title: 'URI',
dataIndex: 'odata',
ellipsis: true,
render: (text, _, __) => {
return (
<React.Fragment>
{
text ? <div className='flex'>
<Tooltip title={text||''} overlayClassName='tooltip-common'>
<Typography.Text ellipsis={true}>
{text||''}
</Typography.Text>
</Tooltip>
<Typography.Text copyable={{ text }}></Typography.Text>
</div> : ''
}
</React.Fragment>
);
}
},
{
title: '操作',
dataIndex: 'action',
width: 80,
fixed: 'right',
render: (_, record) => {
return (
<a onClick={() => {
app.openDetail?.({ service: record })
}}>详情</a>
)
}
}
];
useEffect(() => {
getServices();
}, [])
const tableData = useMemo(() => {
if (data) {
return paginate(data, pagination.pageNum, pagination.pageSize);
}
return [];
}, [data, pagination])
const getServices = () => {
setLoading(true);
dispatch({
type: 'pds.getGrantedServices',
payload: {
namespace: `${app?.env?.domainId}`
},
callback: data => {
setLoading(false);
setData(data);
},
error: () => {
setLoading(false);
}
});
}
return (
<div>
<Table
extraColWidth={10}
loading={loading}
columns={cols||[]}
dataSource={tableData}
pagination={false}
scroll={{y: (tableData||[]).length===0?null:'calc(100vh - 121px - 57px - 24px - 38px - 44px)'}}
/>
<Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum, pageSize: _pageSize || 20 });
}}
onShowSizeChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: 1, pageSize: _pageSize });
}}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={(data||[]).length}
pageSizeOptions={[10,20,50,100]}
showTotal={total => ` ${total} `}
/>
</div>
)
}
export default FC
\ 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 } from 'antd';
import VersionHistory from './VersionHistory';
import VersionCompare from './VersionCompare';
const { TabPane } = Tabs;
const HistoryAndVersionDrawer = (props) => {
const { onCancel, visible, id } = props;
return (
<Drawer
title=''
placement="right"
closable={true}
width={'40%'}
onClose={() => {
onCancel && onCancel();
}}
visible={visible}
>
{
visible && <Tabs defaultActiveKey="1" type="card" size='small'>
<TabPane tab="版本历史" key="1">
<VersionHistory id={id} />
</TabPane>
{/* <TabPane tab="版本对比" key="2">
<VersionCompare id={id} />
</TabPane> */}
</Tabs>
}
</Drawer>
);
}
export default HistoryAndVersionDrawer;
\ No newline at end of file
import React, { useState, useEffect, useRef } from 'react';
import { Spin } from 'antd';
import LocalStorage from 'local-storage';
import ImportActionHeader from './ImportActionHeader';
import { ImportActionTable } from './ImportActionTable';
import ImportActionIndex from './ImportActionIndex';
import { getQueryParam } from '../../../../util';
import { Action } from '../../../../util/constant';
import { dispatch } from '../../../../model';
const ImportAction = (props) => {
const { action, hints, onChange, form, modelerId, terms, ddl, roughModelerData, versionId, permitCheckOut } = props;
const [ constraints, setConstraints ] = useState([]);
const [ constraint, setConstraint ] = useState({});
const [ templates, setTemplates ] = useState([]);
const [ template, setTemplate ] = useState({});
const [ modelerData, setModelerData ] = useState(null);
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ supportedPartitionTypes, setSupportedPartitionTypes ] = useState([]);
const [ supportedIndextypes, setSupportedIndextypes ] = useState([]);
const [ validateReports, setValidateReports ] = useState([]);
const [ loading, setLoading ] = useState(false);
const mountRef = useRef(true);
const modelerDataRef = useRef(null);
useEffect(() =>{
if ((action||'')==='') return;
if (!mountRef.current && action === 'edit' && !permitCheckOut) {
return;
}
//流程打开模型详情的情况下,id由-1变成-2,会再次触发获取模型详情.这里直接return掉
if (!mountRef.current && action === 'flow') {
return;
}
mountRef.current = false;
//初始化form状态
if (action==='add'||action==='edit'||action==='flow') {
form.resetFields();
}
if (action === 'detail'|| action ==='flow' || action === 'detail-version') {
//把数据表结构中的数据清空,解决性能问题
const newModelerData = { ...modelerData, easyDataModelerDataModelAttributes: [] };
onChange && onChange(newModelerData);
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
}
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraintsAndTemplates',
callback: data => {
setConstraints(data.constraints||[]);
setTemplates(data.templates||[]);
if (action === 'add') {
setConstraint((data.constraints||[]).length>0?data.constraints[0]:{});
setTemplate({});
if ((hints||[]).length>0) {
getDraft((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,hints);
} else if ((ddl||'').length>0) {
getDraftUsingDDL((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,ddl);
} else if ((modelerId||'')!=='') {
getCurrentDataModel();
} else if (roughModelerData) {
setLoading(false);
getExtraData(roughModelerData);
} else {
getDraft((data.constraints||[]).length>0?data.constraints[0]:{}, {} ,[]);
}
} else if(action === 'edit' || action === 'detail' || action ==='flow' || action === 'detail-version') {
getCurrentDataModel();
}
},
error: () => {
setLoading(false);
}
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [action, hints, modelerId, ddl ]);
const getDraft = (_constraint, _template, _hints) => {
dispatch({
type: 'datamodel.getDraft',
payload: {
data: {
hints: _hints,
modelerModelingConstraint: _constraint,
easyDataModelerModelingTemplate: _template
}
},
callback: data => {
setLoading(false);
getExtraData(data);
},
error: () => {
setLoading(false);
}
})
}
const getDraftUsingDDL = (_constraint, _template, _ddl) => {
dispatch({
type: 'datamodel.getDraftUsingDDL',
payload: {
data: {
ddl: _ddl,
modelerModelingConstraint: _constraint,
easyDataModelerModelingTemplate: _template
}
},
callback: data => {
setLoading(false);
getExtraData(data);
},
error: () => {
setLoading(false);
}
});
}
const getConsult = (data) => {
dispatch({
type: 'datamodel.getConsult',
payload: {
data
},
callback: data => {
let newModelerData = {...(data||{})};
newModelerData = { ...newModelerData, ...getConsistentKeys(newModelerData) };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
validateDataModel(newModelerData);
}
})
}
const getCurrentDataModel = () => {
if ((modelerId||'') === '') {
setLoading(false);
return;
}
let type = 'datamodel.getDataModel';
let params = {
id: modelerId||''
};
if (action === 'add') {
type = 'datamodel.modelCopy';
} else if (action === 'flow') {
type = 'datamodel.getDataModelWithRecommendedDefinitionAndTermDiscovery';
} else if (action === 'detail-version') {
type = 'datamodel.getDataModelByVersionId';
params = {
params: {
id: modelerId||'',
versionId
}
}
} else if (action==='edit' && permitCheckOut) {
type = 'datamodel.checkOutDataModel';
} else if (action==='edit') {
const _action = getQueryParam(Action, props.location.search);
if (_action === 'flow') {
params.ignoreNamespace = true;
}
}
dispatch({
type,
payload: params,
callback: data => {
if (action==='edit' && permitCheckOut) {
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
}
setLoading(false);
getExtraData(data);
},
error: () => {
setLoading(false);
}
})
}
const getExtraData = (data) => {
const newModelerData = { ...(data||{}), ...getConsistentKeys(data||{}) };
setModelerData(newModelerData||{});
modelerDataRef.current = newModelerData||{};
setConstraint(newModelerData.easyDataModelerModelingConstraint||{});
setTemplate(newModelerData.easyDataModelerModelingTemplate||{});
onChange && onChange(newModelerData||{});
getSupportedDatatypes();
getSupportedPartitionTypes();
getSupportedIndextypes();
if (newModelerData) {
form.setFieldsValue({
cnName: newModelerData.cnName||'',
name: newModelerData.name||'',
remark: newModelerData.remark||'',
easyDataModelerModelingConstraint: newModelerData.easyDataModelerModelingConstraint||'',
easyDataModelerModelingTemplate: newModelerData.easyDataModelerModelingTemplate||'',
dataResidence: newModelerData.dataResidence||'',
tableType: newModelerData.tableType||'',
dataUpdatingTiming: newModelerData.dataUpdatingTiming||'',
maintenanceRecords: newModelerData.maintenanceRecords||'',
dataLoadingStrategy: newModelerData.dataLoadingStrategy||'',
primaryKeysDescription: newModelerData.primaryKeysDescription||'',
distributionKeysDescription: newModelerData.distributionKeysDescription||'',
dataCircumstances: newModelerData.dataCircumstances||'',
partitionsDescription: newModelerData.partitionsDescription||'',
semiPrimaryKeysDescription: newModelerData.semiPrimaryKeysDescription||'',
});
}
validateDataModel(newModelerData||{});
}
const onConstraintChange = (value) => {
let currentConstraint = null;
(constraints||[]).forEach((_constraint, index) => {
if (_constraint.name === value) {
currentConstraint = _constraint;
}
});
if (!currentConstraint) return;
form.setFieldsValue({
easyDataModelerModelingConstraint: currentConstraint
});
const newModelerData = {...modelerData, easyDataModelerModelingConstraint: currentConstraint };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
setConstraint(currentConstraint);
getConsult(newModelerData);
}
const onTemplateChange = (value) => {
let currentTemplate = null;
(templates||[]).forEach((_template, index) => {
if (_template.name === value) {
currentTemplate = _template;
}
});
form.setFieldsValue({
easyDataModelerModelingTemplate: currentTemplate||{}
});
const newModelerData = {...modelerData, easyDataModelerModelingTemplate: currentTemplate };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
setTemplate(currentTemplate);
getConsult(newModelerData);
}
const getSupportedDatatypes = () => {
dispatch({
type: 'datamodel.getSupportedDatatypes',
callback: data => {
setSupportedDatatypes(data||[]);
}
});
}
const getSupportedPartitionTypes = () => {
dispatch({
type: 'datamodel.getSupportedPartitionTypes',
payload: {
excludeBuiltin: false
},
callback: data => {
setSupportedPartitionTypes(data||[]);
}
});
}
const getSupportedIndextypes = () => {
dispatch({
type: 'datamodel.getSupportedIndextypes',
callback: data => {
setSupportedIndextypes(data||[]);
}
});
}
const onHeaderChange = (changedValues, allValues) => {
let newModelerData = {...modelerData, ...allValues};
if (newModelerData.easyDataModelerModelingTemplate==='' || newModelerData.easyDataModelerModelingTemplate==={}) {
newModelerData.easyDataModelerModelingTemplate = null;
}
//主键不允许为空
if (changedValues.hasOwnProperty('easyDataModelerPrimaryKey')) {
(newModelerData.easyDataModelerDataModelAttributes||[]).forEach(attribute => {
let ownPrimaryKey = false;
if (newModelerData?.easyDataModelerPrimaryKey) {
ownPrimaryKey = (newModelerData.easyDataModelerPrimaryKey.filter(item => item.name===attribute.name).length>0);
}
if (ownPrimaryKey) {
attribute.notNull = true;
}
})
}
//类主键自动创建索引
let autoEasyDataModelerIndexOrders = [];
(newModelerData.easyDataModelerSemiPrimaryKey||[]).forEach(item => {
autoEasyDataModelerIndexOrders.push('ASC');
});
let autoEasyDataModelerIndex = {
"name": `i_pk_${newModelerData.name}`,
"indextype": {
"name": "btree",
"displayName": "B-tree",
"supportedDBTypes": [
"Greenplum",
"MySQL"
],
"default": true
},
"indexedAttributeOrders": [...autoEasyDataModelerIndexOrders],
"indexedEasyDataModelAttributes": [...newModelerData.easyDataModelerSemiPrimaryKey],
"unique": false
};
let newEasyDataModelerIndices = [];
(newModelerData.easyDataModelerIndices||[]).forEach((easyDataModelerIndex, index) => {
if (easyDataModelerIndex.name!==`i_pk_${modelerDataRef.current?.name}` && easyDataModelerIndex.name!==`i_pk_${newModelerData.name}`) {
newEasyDataModelerIndices.push(easyDataModelerIndex);
}
});
newEasyDataModelerIndices.push(autoEasyDataModelerIndex);
newEasyDataModelerIndices = newEasyDataModelerIndices.filter(item => (item.indexedEasyDataModelAttributes||[]).length > 0);
newModelerData = {...newModelerData, easyDataModelerIndices: newEasyDataModelerIndices};
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
if (changedValues.hasOwnProperty('name') || changedValues.hasOwnProperty('cnName')) {
validateDataModel(newModelerData);
}
}
//validate 是否需要对字段进行校验
const onTableChange = (data, validate=false) => {
let newModelerData = {...modelerDataRef.current, ...{easyDataModelerDataModelAttributes: data}};
newModelerData = { ...newModelerData, ...getConsistentKeys(newModelerData) };
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
if (validate) {
validateDataModel(newModelerData);
}
}
const validateDataModel = (data) => {
const row = form.getFieldsValue();
dispatch({
type: 'datamodel.validateDataModel',
payload: {
data: (action==='detail'||action==='flow'||action==='detail-version')?data:{ ...data, ...{ name: row.name||'', cnName: row.cnName||'' } },
},
callback: _data => {
setValidateReports(_data||[]);
}
})
}
const onIndexChange = (data, validate=false) => {
const newModelerData = {...modelerDataRef.current, easyDataModelerIndices: data};
setModelerData(newModelerData);
modelerDataRef.current = newModelerData;
onChange && onChange(newModelerData);
if (validate) {
validateDataModel(newModelerData);
}
}
const getConsistentKeys = (newModelerData) => {
//分布键
let newDistribution = [];
(newModelerData.easyDataModelerDistributionKey||[]).forEach((item, index) => {
const _index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(_item => item.iid === _item.iid);
if (_index !== -1) {
newDistribution.push({...newModelerData.easyDataModelerDataModelAttributes[_index]});
}
})
//主键
let newPrimary = [];
(newModelerData.easyDataModelerPrimaryKey||[]).forEach((item, index) => {
const _index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(_item => item.iid === _item.iid);
if (_index !== -1) {
newPrimary.push({...newModelerData.easyDataModelerDataModelAttributes[_index]});
}
})
//分区
let newPartition = {...(newModelerData.partition||[])};
const newKeys = [];
(newPartition.keys||[]).forEach((item, index) => {
const _index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(_item => item.iid === _item.iid);
if (_index !== -1) {
newKeys.push({...newModelerData.easyDataModelerDataModelAttributes[_index]});
}
})
newPartition.keys = newKeys;
if ((newKeys||[]).length === 0) {
newPartition = null;
}
//类主键
let newSemiPrimary = [];
(newModelerData.easyDataModelerSemiPrimaryKey||[]).forEach((item, index) => {
const _index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(_item => item.iid === _item.iid);
if (_index !== -1) {
newSemiPrimary.push({...newModelerData.easyDataModelerDataModelAttributes[_index]});
}
})
//索引
let newEasyDataModelerIndices = [...(newModelerData.easyDataModelerIndices||[])];
(newModelerData.easyDataModelerIndices||[]).forEach((easyDataModelerIndex, index) => {
const newIndexedEasyDataModelAttributes = [], newIndexedAttributeOrders = [];
(easyDataModelerIndex.indexedEasyDataModelAttributes||[]).forEach((indexedEasyDataModelAttribute, _index) => {
const __index = (newModelerData.easyDataModelerDataModelAttributes||[]).findIndex(item => item.iid === indexedEasyDataModelAttribute.iid);
if (__index !== -1) {
newIndexedEasyDataModelAttributes.push({...newModelerData.easyDataModelerDataModelAttributes[__index]});
newIndexedAttributeOrders.push(easyDataModelerIndex.indexedAttributeOrders[_index]);
}
})
const item = newEasyDataModelerIndices[index];
newEasyDataModelerIndices.splice(index, 1, {...item, ...{ indexedEasyDataModelAttributes: newIndexedEasyDataModelAttributes, indexedAttributeOrders: newIndexedAttributeOrders }} )
})
newEasyDataModelerIndices = newEasyDataModelerIndices.filter(item => (item.indexedEasyDataModelAttributes||[]).length > 0);
return { partition: newPartition, easyDataModelerDistributionKey: newDistribution, easyDataModelerPrimaryKey: newPrimary, easyDataModelerIndices: newEasyDataModelerIndices, easyDataModelerSemiPrimaryKey: newSemiPrimary};
}
return (
<Spin spinning={loading}>
<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}
supportedPartitionTypes={supportedPartitionTypes}
/>
<ImportActionTable
modelerData={modelerData||{}}
constraint={constraint}
template={template}
validateReports={validateReports}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
action={action}
originAction={getQueryParam(Action, props?.location?.search)}
terms={terms}
/>
<ImportActionIndex
modelerData={modelerData||{}}
constraint={constraint}
template={template}
types={supportedIndextypes}
validateReports={validateReports}
onChange={onIndexChange}
editable={action!=='detail'&&action!=='flow'&&action!=='detail-version'}
terms={terms}
/>
</Spin>
);
};
export default ImportAction;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Input, Row, Col, Descriptions, Select, AutoComplete, Button, Divider } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { highlightSearchContentByTerms, generateUUID } from '../../../../util';
import { dispatch, dispatchLatest } from '../../../../model';
import DebounceInput from './DebounceInput';
import './ImportActionHeader.less';
const { TextArea } = Input;
const { Option } = Select;
const InputDebounce = DebounceInput(300)(Input);
const loadOptions = ['append', 'refresh', 'insert', 'update', 'full', 'chain', 'delete', '手工导入'];
const updateOptions = [
'每个交易日早间交易前加载上一交易日数据,历史资料入历史表',
'每个交易日早间交易前清空该表,交易期间实时加载,下午收市后入历史表',
'每个交易日7:30加载',
'每个交易日8:30加载',
'每个交易日15:00加载',
'每个交易日18:00加载',
'每个交易日19:00加载',
'每个交易日19:30加载',
'每个交易日20:00加载',
'每个交易日22:00加载',
'每个交易日23:00加载',
'每个交易日20:00加载T-2的数据',
'每日中午12:00加载T-3日数据',
'实时更新',
'收盘后加载',
'收市后加载',
'下午收市后加载',
'下午收市后入历史表',
'下午收市后入实时和历史表',
'晚间结算后加载',
'实时从mysql同步',
'手工维护',
'用户提交',
'作业提交',
'脚本加载',
];
const ConstraintSelect = ({ value = {}, constraints = [], onChange, ...restProps }) => {
return (
<Select
onChange={onChange}
value={value?.name || ''}
placeholder='请选择规范'
{...restProps}
>
{
(constraints||[]) && constraints.map((constraint, index) => {
return (
<Option key={index} value={constraint.name||''} >{constraint.cnName||''}</Option>
);
})
}
</Select>
)
}
// const TemplateSelect = ({ value = {}, templates = [], onChange, ...restProps }) => {
// return (
// <Select
// onChange={onChange}
// value={value?.name || ''}
// placeholder='请选择生成表类型'
// allowClear
// {...restProps}
// >
// {
// (templates||[]) && templates.map((template, index) => {
// return (
// <Option key={index} value={template.name||''} >{template.cnName||''}</Option>
// );
// })
// }
// </Select>
// )
// }
const AttributesSelect = ({ value = [], modelerData, onChange, mode = 'multiple', ...restProps }) => {
const onAttributeChange = (value) => {
let currentAttributes = [];
if (mode === 'multiple') {
currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
} else {
(value||[]).forEach(item => {
const filterAttributes = (newAttributes||[]).filter(attribute => item ===attribute.iid);
if (filterAttributes.length !== 0) {
currentAttributes = [...currentAttributes, ...filterAttributes];
} else {
currentAttributes = [...currentAttributes, { name: item, iid: generateUUID() }];
}
})
}
triggerChange(currentAttributes);
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
//value有可能为空
value = value ? value: [];
let attributeIds = [];
let newAttributes = [...(modelerData?.easyDataModelerDataModelAttributes||[])];
value.forEach(attribute => {
attributeIds.push(attribute.iid);
if (mode === 'tags') {
const filterAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(_attribute => attribute.iid ===_attribute.iid);
if (filterAttributes.length === 0) {
newAttributes = [...newAttributes, attribute];
}
}
})
return (
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode={mode}
allowClear={true}
>
{
(newAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={attribute.iid?attribute.iid:(attribute.name||'')}>{attribute.name||''}</Option>
);
})
}
</Select>
);
}
const PartitionSelect = ({ value = {}, modelerData, partitionTypes = [], onChange, ...restProps }) => {
const onPartitionTypeChange = (value) => {
let currentPartitionType = {};
(partitionTypes||[]).forEach((partitionType, index) => {
if (value === partitionType.name) {
currentPartitionType = partitionType;
}
})
triggerChange({ partitionType: currentPartitionType });
}
const onAttributeChange = (value) => {
const currentAttributes = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(attribute => (value||[]).indexOf(attribute.iid)!==-1);
triggerChange({ keys: currentAttributes });
}
const triggerChange = (changedValue) => {
onChange?.({
...value,
...changedValue,
});
};
//value有可能为空
value = value ? value: {};
let attributeIds = [];
(value?.keys||[]).forEach(attribute => {
attributeIds.push(attribute.iid);
})
return (
<Row gutter={10}>
<Col span={8}>
<Select
onChange={onPartitionTypeChange}
value={value?.partitionType?.name || ''}
placeholder='请选择分区类型'
allowClear={true}
>
{
(partitionTypes||[]).map((partitionType, index) => {
return (
<Option key={partitionType.name||''}>{partitionType.cnName||''}</Option>
);
})
}
</Select>
</Col>
<Col span={16}>
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={attributeIds}
placeholder='请选择字段名称'
mode='multiple'
allowClear={true}
>
{
(modelerData?.easyDataModelerDataModelAttributes||[]).map((attribute, index) => {
return (
<Option key={index} value={attribute.iid||''}>{attribute.name||''}</Option>
);
})
}
</Select>
</Col>
</Row>
);
}
const LoadSelect = ({ value = '', onChange, ...restProps }) => {
const onLoadChange = (value) => {
triggerChange(value.join('/'));
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
//value有可能为空
value = value ? value: '';
let loadNames = [];
if (value !== '') {
value.split('/').forEach(item => {
loadNames.push(item);
})
}
return (
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onLoadChange && onLoadChange(value) }}
value={loadNames}
placeholder='请选择选择或者手动输入加载方式'
allowClear={true}
>
{
loadOptions.map((item, index) => {
return (
<Option key={index} value={item}>{item}</Option>
);
})
}
</Select>
);
}
const UpdateSelect = ({ value = '', onChange, ...restProps }) => {
const onUpdateChange = (value) => {
triggerChange(value.join('/'));
}
const triggerChange = (changedValue) => {
onChange?.(changedValue);
};
//value有可能为空
value = value ? value: '';
let updateNames = [];
if (value !== '') {
value.split('/').forEach(item => {
updateNames.push(item);
})
}
return (
<Select
mode="tags"
tokenSeparators={['/', ' ']}
onChange={(value) => { onUpdateChange && onUpdateChange(value) }}
value={updateNames}
placeholder='请选择选择或者手动输入更新时间'
allowClear={true}
>
{
updateOptions.map((item, index) => {
return (
<Option key={index} value={item}>{item}</Option>
);
})
}
</Select>
);
}
const ImportActionHeader = (props) => {
const { editable, form, modelerData, constraints, templates, onConstraintChange, onTemplateChange, validateReports, onChange, terms, supportedPartitionTypes } = props;
const [ causes, setCauses ] = useState([]);
const [ options, setOptions ] = useState([]);
const [ autoTranslate, setAutoTranslate ] = useState(false);
const [ onlyShowRequireChange, setOnlyShowRequireChange ] = useState(true);
const [ maintenanceRecords, setMaintenanceRecords ] = useState(null);
useEffect(() => {
const causes = [];
(validateReports||[]).forEach(report => {
if (report.type === 'DataModel') {
(report.reportItems||[]).forEach((item) => {
causes.push(item.cause||'');
})
}
});
if (editable) {
form?.setFields([{ name: 'name', errors: causes }]);
} else {
setCauses(causes);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports])
useEffect(() => {
setAutoTranslate((modelerData.name||'')==='');
if (modelerData) {
form?.setFieldsValue(modelerData);
if ((modelerData.id||'')!=='' && maintenanceRecords===null) {
getMaintenanceRecords();
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData])
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
const getMaintenanceRecords = () => {
dispatch({
type: 'datamodel.getMaintenanceRecords',
payload: {
params: {
id: modelerData.id
}
},
callback: data => {
setMaintenanceRecords(data);
}
})
}
const onSearch = (searchText) => {
const _searchText = searchText.replace(/ /g,'');
if (_searchText !== '') {
dispatchLatest({
type: 'datamodel.autocomplete',
payload: {
params: {
word: _searchText,
isEasyDataModelerDataModelAttribute: false,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
})
setOptions(_options);
}
})
} else {
dispatchLatest({
type: 'datamodel.recommandEnglishWords',
payload: {
params: {
chineseWord: modelerData.cnName,
}
},
callback: data => {
const _options = [];
(data||[]).forEach(item => {
_options.push({ value: item });
})
setOptions(_options);
}
})
}
}
const onValuesChange = (changedValues, allValues) => {
onChange && onChange(changedValues, allValues);
//有手动编辑过英文名称并且有内容的情况下, 不能通过编辑中文名称自动翻译
if (changedValues.hasOwnProperty('name')) {
setAutoTranslate(changedValues.name==='');
} else if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslate) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
params: {
phaseInChinese: changedValues.cnName,
}
},
callback: data => {
if ((data?.translated||'') !== '') {
form?.setFieldsValue({ name: data?.translated||'' });
onChange && onChange({...changedValues, ...{name: data?.translated||''}}, {...allValues, ...{name: data?.translated||''}});
}
}
})
}
}
}
const onOnlyShowRequireChange = () => {
setOnlyShowRequireChange(!onlyShowRequireChange);
}
let distributionDescription = '', primaryDescription = '', partitionsDescription = '', semiPrimaryDescription = '', maintenanceDescription = '';
if (!editable && modelerData) {
//分布
if (modelerData?.easyDataModelerDistributionKey) {
(modelerData?.easyDataModelerDistributionKey||[]).forEach((item, index) => {
if (index > 0) {
distributionDescription += ',';
}
distributionDescription += item.name||'';
});
}
//主键
if (modelerData?.easyDataModelerPrimaryKey) {
(modelerData?.easyDataModelerPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
primaryDescription += ',';
}
primaryDescription += item.name||'';
})
}
//分区
if (modelerData?.partition?.keys) {
(modelerData?.partition?.keys||[]).forEach((item, index) => {
if (index > 0) {
partitionsDescription += ',';
}
partitionsDescription += item.name||'';
})
}
if (modelerData?.partition?.partitionType?.cnName) {
partitionsDescription += '/' + modelerData?.partition?.partitionType?.cnName||'';
}
//类主键
if (modelerData?.easyDataModelerSemiPrimaryKey) {
(modelerData?.easyDataModelerSemiPrimaryKey||[]).forEach((item, index) => {
if (index > 0) {
semiPrimaryDescription += ',';
}
semiPrimaryDescription += item.name||'';
})
}
}
if ((maintenanceRecords||[]).length>0) {
maintenanceRecords.forEach((record, index) => {
if (index !== 0) {
maintenanceDescription += '/';
}
maintenanceDescription += record;
})
form?.setFieldsValue({ maintenanceRecords: maintenanceDescription });
}
return (
<div className='model-import-action-header'>
<div
className='mb-3'
style={{
display: 'flex',
alignItems: 'center',
}}
>
<h2 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h2>
{
onlyShowRequireChange ? <Button type='text' style={{ padding: 0, color: '#0069AC' }} onClick={onOnlyShowRequireChange}>展开<DownOutlined /></Button> : <Button type='text' style={{ padding: 0, color: '#0069AC' }} onClick={onOnlyShowRequireChange}>收起<UpOutlined /></Button>
}
</div>
{
editable ? (
<Form
form={form}
{...formItemLayout}
onValuesChange={onValuesChange}
>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="中文名称"
name="cnName"
rules={[{ required: true, message: '请输入中文名称!' }]}
>
<InputDebounce />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="英文名称"
name="name"
rules={[{ required: true, message: '请输入英文名称!' }]}
>
<AutoComplete options={options} onSearch={onSearch} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="规范"
name="easyDataModelerModelingConstraint"
rules={[{ required: true, message: '请选择规范!' }]}
>
<ConstraintSelect
constraints={constraints}
onChange={onConstraintChange}
/>
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据内容"
name="remark"
rules={[{ required: true, message: '请输入数据内容!' }]}
style={{ marginBottom: 15 }}
>
<TextArea row={4} />
</Form.Item>
</Col>
</Row>
{
!onlyShowRequireChange && <Divider style={{ margin: '0 0 15px' }} />
}
{
!onlyShowRequireChange && <Row gutter={10}>
{/* <Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="生成表类型"
name="easyDataModelerModelingTemplate"
rules={[{ required: false, message: '请选择生成表类型!' }]}
>
<TemplateSelect
templates={templates}
onChange={onTemplateChange}
/>
</Form.Item>
</Col> */}
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据表类型"
name="tableType"
>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据平台"
name="dataResidence"
>
<Input disabled={true} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="数据情况"
name="dataCircumstances"
>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分布键"
name="easyDataModelerDistributionKey"
>
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="分区键"
name="partition"
>
<PartitionSelect modelerData={modelerData} partitionTypes={supportedPartitionTypes} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="技术主键"
name="easyDataModelerPrimaryKey"
>
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="类主键"
name="easyDataModelerSemiPrimaryKey"
>
<AttributesSelect modelerData={modelerData} mode='tags' />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="加载方式"
name="dataLoadingStrategy"
>
<LoadSelect />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="更新时间"
name="dataUpdatingTiming"
>
<UpdateSelect />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
<Form.Item
label="维护历史"
name="maintenanceRecords"
>
<TextArea rows={3} disabled={true} />
</Form.Item>
</Col>
</Row>
}
</Form>
) : (
<React.Fragment>
<Descriptions column={3}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>中文名称</div>} >{highlightSearchContentByTerms(modelerData.cnName||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>英文名称</div>}>
{
<div>
<div>{highlightSearchContentByTerms(modelerData.name||'', terms)}</div>
{
(causes||[]).map((cause, index) => {
return (
<div key={index} style={{ color: '#ff4d4f' }}>
{cause||''}
</div>
)
})
}
</div>
}
</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>规范</div>} >{modelerData.easyDataModelerModelingConstraint?(modelerData.easyDataModelerModelingConstraint.cnName||''):''}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据内容</div>}>{highlightSearchContentByTerms(modelerData.remark||'', terms)}</Descriptions.Item>
</Descriptions>
{
!onlyShowRequireChange && <Divider style={{ margin: '0 0 15px' }} />
}
{
!onlyShowRequireChange && <Descriptions column={3}>
{/* <Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>生成表类型</div>} >{modelerData.easyDataModelerModelingTemplate?(modelerData.easyDataModelerModelingTemplate.cnName||''):''}</Descriptions.Item> */}
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据表类型</div>} >{highlightSearchContentByTerms(modelerData.tableType||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据平台</div>} >{highlightSearchContentByTerms(modelerData.dataResidence||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据情况</div>} >{highlightSearchContentByTerms(modelerData.dataCircumstances||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>分布键</div>} >{highlightSearchContentByTerms(distributionDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>分区键</div>} >{highlightSearchContentByTerms(partitionsDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>技术主键</div>} >{highlightSearchContentByTerms(primaryDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>类主键</div>} >{highlightSearchContentByTerms(semiPrimaryDescription||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>加载方式</div>} >{highlightSearchContentByTerms(modelerData.dataLoadingStrategy||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>更新时间</div>} >{highlightSearchContentByTerms(modelerData.dataUpdatingTiming||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>维护历史</div>} >
<div>
{
(maintenanceRecords||[]).map((record, index) => {
return <div key={index}>{record||''}</div>;
})
}
</div>
</Descriptions.Item>
</Descriptions>
}
</React.Fragment>
)
}
</div>
)
}
export default ImportActionHeader;
\ No newline at end of file
.model-import-action-header {
.yy-form-item:nth-last-col {
margin-bottom: 24px;
}
.yy-form-item-has-error {
margin-bottom: 0px;
}
.yy-descriptions-row > th, .yy-descriptions-row > td {
padding-bottom: 15px;
}
}
\ No newline at end of file
import React, { useState, useCallback, useRef, useEffect, useContext } from 'react';
import { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table, Space } from 'antd';
import { DeleteOutlined, CloseOutlined, CheckOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
import { useClickAway } from 'ahooks';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import DebounceInput from './DebounceInput';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { showMessage, highlightSearchContentByTerms, inputWidth } from '../../../../util';
import { EditModelContext } from './ContextManage';
const { Option } = Select;
const type = 'DragableIndexBodyRow';
const MENU_ID = 'model-index-menu';
const InputDebounce = DebounceInput(300)(Input);
const TypesItem = ({ value, types, onChange }) => {
useEffect(() => {
if ((value?.name||'')==='' && (types||[]).length > 0) {
const filterTypes = (types||[]).filter(type => type.default===true);
if ((filterTypes||[]).length > 0) {
onChange && onChange(filterTypes[0]);
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
const onTypeChange = (val) => {
const filterTypes = (types||[]).filter(type => type.name===val);
if ((filterTypes||[]).length > 0) {
onChange && onChange(filterTypes[0]);
}
}
return (
<span onClick={e => e.stopPropagation()}>
<Select
onChange={(val) => { onTypeChange && onTypeChange(val) }}
value={value?.name}
placeholder='请选择排序方式'
>
{
(types||[]).map((type, index) => {
return (
<Option key={index} value={type.name}>{type.displayName}</Option>
);
})
}
</Select>
</span>
);
}
const AttributesInputItem = ({ indexedAttribute = null, indexedAttributeOrder = null, attributes, onAttributeChange, onOrderChange, onDelete , className }) => {
return (
<Row align='middle' className={className} >
<Col span={4}>
<span>字段名称:</span>
</Col>
<Col span={6}>
<span onClick={e => e.stopPropagation()}>
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={indexedAttribute ? (indexedAttribute.name||'') : ''}
placeholder='请选择字段名称'
>
{
(attributes||[]).map((attribute, index) => {
return (
((attribute.name||'')==='') ? null : <Option key={index} value={attribute.iid||''}>{attribute.name||''}</Option>
);
})
}
</Select>
</span>
</Col>
<Col span={1}></Col>
<Col span={2}>
<span>排序:</span>
</Col>
<Col span={6}>
<span onClick={e => e.stopPropagation()}>
<Select
onChange={(value) => { onOrderChange && onOrderChange(value) }}
value={indexedAttributeOrder||''}
placeholder='请选择排序方式'
>
<Option value='DESC'>DESC</Option>
<Option value='ASC'>ASC</Option>
</Select>
</span>
</Col>
<Col span={1}></Col>
<Col span={1}>
<Tooltip title="删除">
<Button type="text" icon={<DeleteOutlined />} onClick={onDelete} />
</Tooltip>
</Col>
</Row>
);
}
const AttributesInput = ({ value = {}, attributes, onChange }) => {
const { indexedEasyDataModelAttributes, indexedAttributeOrders } = value;
const onAttributeChange = (value, index) => {
if (indexedEasyDataModelAttributes.findIndex(item => item.iid === value) !== -1) {
showMessage('warn', '字段不能重复选择');
return;
}
const newIndexedEasyDataModelAttributes = [...indexedEasyDataModelAttributes];
const _index = attributes.findIndex(item => item.iid === value);
newIndexedEasyDataModelAttributes.splice(index, 1, {...attributes[_index]});
triggerChange({
indexedEasyDataModelAttributes: newIndexedEasyDataModelAttributes
});
}
const onOrderChange = (value, index) => {
const newIndexedAttributeOrders = [...indexedAttributeOrders];
newIndexedAttributeOrders.splice(index, 1, value);
triggerChange({
indexedAttributeOrders: newIndexedAttributeOrders
})
}
const onItemDelete = (index) => {
const newIndexedEasyDataModelAttributes = [...indexedEasyDataModelAttributes];
const newIndexedAttributeOrders = [...indexedAttributeOrders];
newIndexedEasyDataModelAttributes.splice(index, 1);
newIndexedAttributeOrders.splice(index, 1);
if (newIndexedEasyDataModelAttributes.length === 0) {
newIndexedEasyDataModelAttributes.push({});
}
if (newIndexedAttributeOrders.length === 0) {
newIndexedAttributeOrders.push('');
}
triggerChange({
indexedEasyDataModelAttributes: newIndexedEasyDataModelAttributes,
indexedAttributeOrders: newIndexedAttributeOrders,
});
}
const addAttribute = () => {
triggerChange({
indexedEasyDataModelAttributes: [...indexedEasyDataModelAttributes, {}],
indexedAttributeOrders: [...indexedAttributeOrders, ''],
})
}
const triggerChange = (changedValue) => {
onChange && onChange({
...value,
...changedValue,
});
};
return (
<React.Fragment>
{
(indexedEasyDataModelAttributes||[]).map((indexedAttribute, index) => {
return (
<AttributesInputItem
key={index}
className='mb-2'
indexedAttribute={indexedAttribute}
indexedAttributeOrder={indexedAttributeOrders[index]||''}
attributes={attributes}
onAttributeChange={(value) => { onAttributeChange(value, index) } }
onOrderChange={(value) => { onOrderChange(value, index) }}
onDelete={(event) => {
event.stopPropagation();
onItemDelete(index)
}}
/>
);
})
}
<Button onClick={addAttribute}>新增字段</Button>
</React.Fragment>
)
}
const EditableCell = ({
editing,
dataIndex,
colTitle,
inputType,
record,
index,
attributes,
types,
children,
...restProps
}) => {
let editingComponent = null;
if (editing) {
if (dataIndex!=='attributesWithOrders' && dataIndex!=='indextype') {
const inputNode = inputType === 'check' ? <Checkbox /> : <Input />
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={(inputType==='check')? 'checked': 'value'}
rules={[
{
required: (inputType === 'text'),
message: `请输入${colTitle}!`,
},
]}
>
{ inputNode }
</Form.Item>
);
} else if (dataIndex === 'indextype') {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: true,
message: `请输入${colTitle}!`,
},
]}
>
<TypesItem types={types} />
</Form.Item>
);
} else {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: true,
message: `请输入${colTitle}!`,
},
]}
>
<AttributesInput attributes={attributes} />
</Form.Item>
)
}
}
return (
<td {...restProps}>
{editing ? (
editingComponent
) : (
children
)}
</td>
);
};
const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop(
() => ({
accept: type,
collect: monitor => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: item => {
if (moveRow) {
moveRow(item.index, index);
}
},
}),
[index],
);
const [, drag] = useDrag(
() => ({
type,
item: () => {
addEventListenerForSidebar("containerId");
return { index };
},
end: (_, __) => {
removeEventListenerForSidebar();
},
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
}),
[index],
);
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
const ImportActionIndex = (props) => {
const { modelerData, onChange, editable, constraint, template, validateReports, terms, types } = props;
const [ attributes, setAttributes ] = useState([]);
const [ data, setData ] = useState([]);
const [ form ] = Form.useForm();
const [ editingKey, setEditingKey ] = useState(null);
const [ keywordCondition, setKeywordCondition ] = useState({ keyword: '', needFilter: true });
const { keyword, needFilter } = keywordCondition;
const [ filterData, setFilterData ] = useState([]);
const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null);
const { indexIsEditingFunction } = useContext(EditModelContext);
const dataRef = useRef();
dataRef.current = data;
const tableRef = useRef(null);
const termsRef = useRef(null);
const { show } = useContextMenu({
id: MENU_ID,
});
useClickAway(() => {
save();
}, tableRef);
useEffect(() => {
indexIsEditingFunction && indexIsEditingFunction(editingKey!==null);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ editingKey ])
//规则改变的时候 数据表为可编辑状态
useEffect(() => {
setEditingKey(null);
}, [constraint, template, modelerData])
useEffect(() => {
termsRef.current = terms;
setAttributes(modelerData.easyDataModelerDataModelAttributes||[]);
setData(modelerData.easyDataModelerIndices||[]);
dataRef.current = (modelerData.easyDataModelerIndices||[]);
const _filterData = (modelerData.easyDataModelerIndices||[]).filter(item => (item.name||'').indexOf(keyword)!==-1);
const __filterData = [];
(_filterData||[]).forEach(item => {
__filterData.push({...item, ...{ attributesWithOrders: {
indexedEasyDataModelAttributes: item.indexedEasyDataModelAttributes||[],
indexedAttributeOrders: item.indexedAttributeOrders||[],
} }});
})
setFilterData(__filterData);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData, terms])
useEffect(() => {
if (needFilter) {
const _filterData = (modelerData.easyDataModelerIndices||[]).filter(item => (item.name||'').indexOf(keyword)!==-1);
const __filterData = [];
(_filterData||[]).forEach(item => {
__filterData.push({...item, ...{ attributesWithOrders: {
indexedEasyDataModelAttributes: item.indexedEasyDataModelAttributes||[],
indexedAttributeOrders: item.indexedAttributeOrders||[],
} }});
})
setFilterData(__filterData);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [keywordCondition])
const isEditing = (record) => record.name === editingKey;
const onAddClick = (event) => {
event.stopPropagation();
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
const newData = [...dataRef.current, {name: ''}];
setFilterData(newData);
setInsertIndex(newData.length-1);
edit(newData[newData.length-1], false);
}
})
}
const insertToFront = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
let newData = [...dataRef.current];
const index = newData.findIndex((item) => record.name === item.name);
if (index === -1) {
if (insertIndex === 0) {
newData = [{name: ''}, ...newData];
edit(newData[0], false);
} else {
newData.splice(insertIndex, 0, {name: ''});
edit(newData[insertIndex], false);
}
} else if (index === 0) {
newData = [{name: ''}, ...newData];
setInsertIndex(0);
edit(newData[0], false);
} else {
newData.splice(index, 0, {name: ''});
setInsertIndex(index);
edit(newData[index], false);
}
setFilterData(newData);
}
})
}
const insertToBack = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
const newData = [...dataRef.current];
const index = newData.findIndex((item) => record.name === item.name);
if (index === -1) {
newData.splice(insertIndex+1, 0, {name: ''});
setFilterData(newData);
setInsertIndex(insertIndex+1);
edit(newData[insertIndex+1], false);
} else {
newData.splice(index+1, 0, {name: ''});
setFilterData(newData);
setInsertIndex(index+1);
edit(newData[index+1], false);
}
}
})
}
const editLogic = (record) => {
form.resetFields();
form.setFieldsValue({
name: '',
attributesWithOrders: {
indexedEasyDataModelAttributes: [{}],
indexedAttributeOrders: [''],
},
indextype: {},
unique: false,
...record,
});
setEditingKey(record?.name);
}
const edit = (record, needSave = true) => {
if (needSave) {
save().then(result => {
if (result) {
editLogic(record);
}
})
} else {
editLogic(record);
}
};
const removeLogic = (record) => {
const newData = [...dataRef.current];
const index = newData.findIndex((item) => record.name === item.name);
if (index !== -1) {
newData.splice(index, 1);
}
onChange && onChange(newData);
}
const remove = (record) => {
if (record.name === '') {
const newData = [...dataRef.current];
onChange && onChange(newData);
} else {
if (record.name !== editingKey) {
save().then(result => {
if (result) {
removeLogic(record);
}
})
} else {
removeLogic(record);
}
}
save().then(result => {
if (result) {
const newData = [...dataRef.current];
const index = newData.findIndex((item) => record.name === item.name);
if (index !== -1) {
newData.splice(index, 1);
}
onChange && onChange(newData);
}
})
}
const save = async() => {
try {
if (editingKey!==null) {
const row = await form.validateFields();
// console.log('row', row);
const newData = [...data];
const index = newData.findIndex((item) => editingKey === item.name);
//判断索引名称是否唯一
let _index;
if (index === -1) {
_index = (data||[]).findIndex(item => item.name === row.name);
} else {
const newDataExcludeSelf = [...data];
newDataExcludeSelf.splice(index, 1);
_index = (newDataExcludeSelf||[]).findIndex(item => item.name === row.name);
}
if (_index !== -1) {
form.setFields([{ name: 'name', errors: ['索引名称不能重复'] }]);
return;
}
const _indexedEasyDataModelAttributes = [], _indexedAttributeOrders = [];
row.attributesWithOrders.indexedEasyDataModelAttributes.forEach((item, index) => {
if ((item.iid||'')!=='') {
_indexedEasyDataModelAttributes.push(item);
_indexedAttributeOrders.push(row.attributesWithOrders.indexedAttributeOrders[index]);
}
})
if (_indexedEasyDataModelAttributes.length === 0) {
form.setFields([{ name: 'attributesWithOrders', errors: ['必须选择字段'] }]);
return;
}
if (index === -1) {
newData.splice(insertIndex, 0 , {
name: row.name,
unique: row.unique,
indextype: row.indextype,
indexedEasyDataModelAttributes: _indexedEasyDataModelAttributes,
indexedAttributeOrders: _indexedAttributeOrders,
});
} else {
newData.splice(index, 1, {...{
name: row.name,
unique: row.unique,
indextype: row.indextype,
indexedEasyDataModelAttributes: _indexedEasyDataModelAttributes,
indexedAttributeOrders: _indexedAttributeOrders,
}});
}
dataRef.current = newData;
onChange && onChange(newData, true);
setEditingKey(null);
}
return true;
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
return false;
}
};
const onValuesChange = (changedValues, allValues) => {
// console.log('changed values', changedValues);
// console.log('all values', allValues);
};
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
render: (_, __, index) => {
return (index+1).toString();
}
},
{
title: '索引名称',
width: 200,
dataIndex: 'name',
editable: true,
ellipsis: true,
render: (text, record, index) => {
return (
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
)
}
},
{
title: '索引类型',
width: 200,
dataIndex: 'indextype',
editable: true,
ellipsis: true,
render: (_, record, __) => {
return (
<Tooltip title={record.indextype?.displayName||''}>
<span >{highlightSearchContentByTerms(record.indextype?.displayName||'', termsRef.current)}</span>
</Tooltip>
)
}
},
{
title: '是否唯一索引',
width: 200,
dataIndex: 'unique',
editable: true,
render: (unique, _, __) => {
if (unique === false) {
return (
<CloseOutlined />
);
} else if (unique === true) {
return (
<CheckOutlined />
)
}
return '';
}
},
{
title: '索引字段列表',
dataIndex: 'attributesWithOrders',
editable: true,
ellipsis: true,
render: (_, record, index) => {
return (
<div>
{
(record.indexedEasyDataModelAttributes||[]).map((item, index) => {
const order = record.indexedAttributeOrders[index]||'';
return (
<Row key={index}>
<span>字段: </span>
{ highlightSearchContentByTerms(item.name||'', termsRef.current) }
<span>{` 排序: ${order||''}`}</span>
</Row>
)
})
}
</div>
);
}
},
];
const editableColumn = [
...columns,
{
title: '操作',
dataIndex: 'action',
width: 180,
render: (_, record) => {
if (!editable) return null;
return (
<React.Fragment>
<Button
className='mr-3'
size='small'
type='text'
icon={<PlusOutlined />}
onClick={(event) => {
event.stopPropagation();
insertToFront(record);
}}
/>
<Button
className='mr-3'
size='small'
type='text'
icon={<DeleteOutlined style={{ color: 'red' }} />}
onClick={(event) => {
event.stopPropagation();
remove(record);
}}
/>
</React.Fragment>
)
},
},
]
const validateColumn = {
title: '规范',
dataIndex: 'validate',
width: 180,
ellipsis: true,
render: (text, record, index) => {
let shortCauses = [];
let causes = [];
(validateReports||[]).forEach((report) => {
if (report.type==='Index' && report.iid === record.name) {
(report.reportItems||[]).forEach((item) => {
shortCauses.push(item.shortCause||'');
causes.push(item.cause||'');
})
}
});
return (
<div className='d-flex' key={index} style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<div>
{
shortCauses && shortCauses.map((shortCause, index) => {
return (
<Tooltip key={index} title={shortCause}>
<div className='textOverflow' style={{ width: 130, color: '#f5222d' }} title={shortCause} key={index}>
<span>{shortCause||''}</span>
</div>
</Tooltip>
);
})
}
</div>
{
(causes||[]).length>0 && <Popover
content={
<div style={{ maxWidth: 200, maxHeight: 200, wordWrap: 'break-word', overflow: 'auto' }}>
{
causes && causes.map((cause, index) => {
return (
<Typography.Paragraph key={index} >
{cause||''}
</Typography.Paragraph>
)
})
}
</div>
}
title="规则详情"
>
<a href="#">详情</a>
</Popover>
}
</div>
)
}
};
const includeValidateColumn = [
...columns,
validateColumn
]
const includeValidateEditableColumn = [
...editableColumn,
validateColumn
]
const mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='Index').length>0;
if (editable) {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
return _columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
inputType: (col.dataIndex==='unique') ? 'check' : 'text',
colTitle: col.title,
editing: isEditing(record),
attributes,
types,
}),
};
});
}
return hasValidateReports ? includeValidateColumn : columns;
}
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = dataRef.current[dragIndex];
const newData = update(dataRef.current, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragRow],
],
})
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[dataRef.current],
);
const onSearchInputChange = (value) => {
setEditingKey(null);
setKeywordCondition({ keyword: value||'', needFilter: true });
}
const displayMenu = (e) => {
show({
event: e
})
}
const handleItemClick = ({ id, event, props }) => {
if (id === 'up') {
insertToFront(currentItem);
} else if (id === 'down') {
insertToBack(currentItem);
}
}
return (
<div className='model-import-action-index'>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<Space>
<h2 style={{ marginBottom: 0 }}>数据表索引</h2>
{
editable && <Popover content='点击行进行编辑,表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
</Space>
<Space>
{
editable && <Tooltip>
<Button onClick={onAddClick} >新建</Button>
</Tooltip>
}
<div className='d-flex' style={{ alignItems: 'center' }}>
<InputDebounce
placeholder="请输入索引名称"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth }}
/>
</div>
</Space>
</div>
<div className='mb-3' id="containerId" ref={tableRef}>
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
components={{
body: {
cell: EditableCell,
//编辑或者搜索状态下不允许拖动
row: (editable&&editingKey===null&&keyword==='')?DragableBodyRow:null,
},
}}
onRow={(record, index) => {
let rowParams = {
index,
};
if (editable) {
rowParams = {...rowParams, onContextMenu: event => {
setCurrentItem(record);
displayMenu(event);
}
};
if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation();
edit(record);
}
}
if (keyword.length===0) {
rowParams = {...rowParams, moveRow};
}
}
}
return rowParams;
}}
dataSource={filterData||[]}
columns={mergedColumns()}
size='small'
rowKey='name'
rowClassName="editable-row"
pagination={false}
sticky
scroll={{
x: 1200
}}
/>
</Form>
</DndProvider>
<RcMenu id={MENU_ID} >
<RcItem id="up" onClick={handleItemClick}>
在上方插入行
</RcItem>
<RcItem id="down" onClick={handleItemClick}>
在下方插入行
</RcItem>
</RcMenu>
</div>
</div>
);
};
export default ImportActionIndex;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Typography, Button, Select, Row, Col, Tooltip, Table } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { showMessage, highlightSearchContentByTerms } from '../../../../util';
const { Option } = Select;
const PartitionTypeInput = ({ value = {}, partitionTypes, onChange }) => {
const onNameChange = (value) => {
let currentPartitionType = {};
(partitionTypes||[]).forEach((partitionType, index) => {
if (value === partitionType.name) {
currentPartitionType = partitionType;
}
})
triggerChange(currentPartitionType);
}
const triggerChange = (changedValue) => {
onChange && onChange(changedValue);
};
//value有可能为空
value = value ? value: {};
return (
<>
<Row align='middle'>
<Col span={9}>
<span>名称:</span>
</Col>
<Col span={15}>
<Select
onChange={onNameChange}
value={value.name || ''}
placeholder='请选择类型名称'
>
{
(partitionTypes||[]).map((partitionType, index) => {
return (
<Option key={partitionType.name||''}>{partitionType.name||''}</Option>
);
})
}
</Select>
</Col>
</Row>
</>
)
}
const AttributesInputItem = ({ indexedAttribute = null, attributes, onAttributeChange, onDelete , className }) => {
return (
<Row align='middle' className={className} >
<Col span={4}>
<span>字段名称:</span>
</Col>
<Col span={6}>
<Select
onChange={(value) => { onAttributeChange && onAttributeChange(value) }}
value={indexedAttribute ? (indexedAttribute.name||'') : ''}
placeholder='请选择字段名称'
>
{
(attributes||[]).map((attribute, index) => {
return (
((attribute.name||'')==='') ? null : <Option key={index} value={attribute.iid||''}>{attribute.name||''}</Option>
);
})
}
</Select>
</Col>
<Col span={1}></Col>
<Col span={1}>
<Tooltip title="删除">
<Button type="text" icon={<DeleteOutlined />} onClick={onDelete} />
</Tooltip>
</Col>
</Row>
);
}
const AttributesInput = ({ value = [], attributes, onChange }) => {
const indexedEasyDataModelAttributes = value;
const onAttributeChange = (value, index) => {
if (indexedEasyDataModelAttributes.findIndex(item => item.iid === value) !== -1) {
showMessage('warn', '字段不能重复选择');
return;
}
const newIndexedEasyDataModelAttributes = [...indexedEasyDataModelAttributes];
const _index = attributes.findIndex(item => item.iid === value);
newIndexedEasyDataModelAttributes.splice(index, 1, {...attributes[_index]});
triggerChange(newIndexedEasyDataModelAttributes);
}
const onItemDelete = (index) => {
const newIndexedEasyDataModelAttributes = [...indexedEasyDataModelAttributes];
newIndexedEasyDataModelAttributes.splice(index, 1);
if (newIndexedEasyDataModelAttributes.length === 0) {
newIndexedEasyDataModelAttributes.push({});
}
triggerChange(newIndexedEasyDataModelAttributes);
}
const addAttribute = () => {
triggerChange([...indexedEasyDataModelAttributes, {}])
}
const triggerChange = (changedValue) => {
onChange && onChange(changedValue);
};
return (
<>
{
(indexedEasyDataModelAttributes||[]).map((indexedAttribute, index) => {
return (
<AttributesInputItem
key={index}
className='mb-2'
indexedAttribute={indexedAttribute}
attributes={attributes}
onAttributeChange={(value) => { onAttributeChange(value, index) } }
onDelete={() => { onItemDelete(index) }}
/>
);
})
}
<Button onClick={addAttribute}>新增字段</Button>
</>
)
}
const EditableCell = ({
editing,
dataIndex,
colTitle,
inputType,
record,
index,
attributes,
partitionTypes,
children,
...restProps
}) => {
let editingComponent = null;
if (editing) {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: true,
message: `请输入${colTitle}!`,
},
]}
>
{ (dataIndex==='keys') ? <AttributesInput attributes={attributes} /> : <PartitionTypeInput partitionTypes={partitionTypes} /> }
</Form.Item>
)
}
return (
<td {...restProps}>
{editing ? (
editingComponent
) : (
children
)}
</td>
);
};
const ImportActionPartition = (props) => {
const { modelerData, onChange, editable, constraint, template, terms, supportedPartitionTypes } = props;
const [ attributes, setAttributes ] = useState([]);
const [ data, setData ] = useState(null);
const [ form ] = Form.useForm();
const [ isEditing, setIsEditing ] = useState(false);
//规则改变的时候 数据表为可编辑状态
useEffect(() => {
setIsEditing(false);
}, [constraint, template, modelerData])
useEffect(() => {
setAttributes(modelerData.easyDataModelerDataModelAttributes||[]);
setData(modelerData?.partition);
}, [modelerData])
const onAddClick = () => {
if (!data) {
setData({
partitionType: {},
keys: [],
});
form.setFieldsValue({
type: {},
keys: [],
});
setIsEditing(true);
}
}
const edit = (record) => {
form.setFieldsValue({type: record.partitionType, keys: record.keys});
setIsEditing(true);
}
const remove = (record) => {
setData(null);
onChange && onChange(null);
}
const cancel = () => {
setIsEditing(false);
if (!data?.partitionType?.name) {
setData(null);
}
};
const save = async() => {
try {
const row = await form.validateFields();
if (!row.type?.name) {
form.setFields([{ name: 'type', errors: ['必须选择分区类型'] }]);
return;
}
const _names = [];
(row.keys||[]).forEach(item => {
if ((item.name||'')!=='') {
_names.push(item.name);
}
})
if ((_names||[]).length === 0) {
form.setFields([{ name: 'keys', errors: ['必须选择字段'] }]);
return;
}
const newData = {
partitionType: row.type,
keys: row.keys
}
onChange && onChange(newData, true);
setIsEditing(false);
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
};
const onValuesChange = (changedValues, allValues) => {
// console.log('changed values', changedValues);
// console.log('all values', allValues);
};
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
render: (_, __, index) => {
return (index+1).toString();
}
},
{
title: '分区类型',
width: 200,
dataIndex: 'type', //注意 这里type改成partitionType会崩溃 暂不清楚是什么问题
editable: true,
ellipsis: true,
render: (text, record, index) => {
return (
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(record?.partitionType?.name, terms)}</span>
</Tooltip>
)
}
},
{
title: '分区字段列表',
dataIndex: 'keys',
editable: true,
ellipsis: true,
render: (_, record, index) => {
return (
<div>
{
(record.keys||[]).map((item, index) => {
return (
<Row key={index}>
<span>字段: </span>
{ highlightSearchContentByTerms(item.name||'', terms) }
</Row>
)
})
}
</div>
);
}
},
];
const editableColumn = [
...columns,
{
title: '操作',
dataIndex: 'action',
width: 180,
render: (_, record) => {
if (!editable) return null;
return isEditing ? (
<>
<Typography.Link className='mr-3' onClick={() => save()}>
保存
</Typography.Link>
<Typography.Link onClick={() => {cancel()}}>
取消
</Typography.Link>
</>
) : (
<>
<Typography.Link className='mr-3' onClick={() => edit(record)}>
编辑
</Typography.Link>
<Typography.Link className='mr-3' onClick={() => remove(record)}>
删除
</Typography.Link>
</>
);
},
},
]
const mergedColumns = () => {
if (editable) {
return editableColumn.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
inputType: (col.dataIndex==='unique') ? 'check' : 'text',
colTitle: col.title,
editing: isEditing,
attributes,
partitionTypes: supportedPartitionTypes,
}),
};
});
}
return columns;
}
let disableAdd = false, addTip = '';
if (isEditing) {
disableAdd = true;
addTip = '正在编辑中,不允许新建';
} else if (data) {
disableAdd = true;
addTip = '已存在分区,不允许再新建';
}
return (
<div className='model-import-action-index'>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<h2 style={{ marginBottom: 0 }}>数据表分区</h2>
{
editable && <Tooltip title={addTip}>
<Button onClick={onAddClick} disabled={disableAdd} >新建</Button>
</Tooltip>
}
</div>
<div className='mb-3' id="containerId">
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
components={{
body: {
cell: EditableCell,
row: null,
},
}}
dataSource={data?[data]:[]}
columns={mergedColumns()}
size='small'
rowClassName="editable-row"
pagination={false}
sticky
/>
</Form>
</div>
</div>
);
};
export default ImportActionPartition;
\ No newline at end of file
import React, { useState, useCallback, useRef, useEffect, useContext } from 'react';
import { Input, Form, Typography, Button, Select, Row, Col, Popover, Checkbox, Tooltip, Table, Pagination, Space } from 'antd';
import { CheckOutlined, PlusOutlined, QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
import { useClickAway } from 'ahooks';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import ResizeObserver from 'rc-resize-observer';
import { Resizable } from 'react-resizable';
import { generateUUID, highlightSearchContentByTerms, showMessage, inputWidth, paginate } from '../../../../util';
import { dispatch, dispatchLatest } from '../../../../model';
import { addEventListenerForSidebar, removeEventListenerForSidebar } from './Help';
import { AppContext } from '../../../../App';
import DebounceInput from './DebounceInput';
import SuggestTable from './SuggestTable';
import { AttentionSvg, UnAttentionSvg } from './ModelSvg';
import { EditModelContext } from './ContextManage';
import './ImportActionTable.less';
const { Option } = Select;
const type = `DragableTableBodyRow${generateUUID()}`;
const perSuggestCount = 5;
const supportMaxAttributeCountPerPage = 100;
const MENU_ID = 'model-attribute-menu';
const InputDebounce = DebounceInput(300)(Input);
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>
);
};
export const DatatypeInput = ({ value = {}, datatypes, onChange }) => {
const onNameChange = (value) => {
let currentDatatype = {};
(datatypes||[]).forEach((datatype, index) => {
if (value === datatype.name) {
currentDatatype = datatype;
}
})
triggerChange({
name: value,
cnName: currentDatatype.cnName,
parameterNames: currentDatatype.parameterNames,
parameterCnNames: currentDatatype.parameterCnNames,
parameterValues: currentDatatype.parameterValues
});
}
const onParameterValuesChange = (e, index) => {
const parameterValue = e.target.value;
const reg = /^-?\d*(\.\d*)?$/;
if ((!isNaN(parameterValue) && reg.test(parameterValue)) || parameterValue === '') {
const newParameterValues = [...(value.parameterValues||[])];
//默认为0
newParameterValues[index] = parameterValue;
triggerChange({ parameterValues: newParameterValues });
}
}
const triggerChange = (changedValue) => {
onChange && onChange({
...value,
...changedValue,
});
};
//value有可能为空
value = value ? value: {};
return (
<>
<Row align='middle'>
<Col span={9}>
<span>名称:</span>
</Col>
<Col span={15}>
<span onClick={e => e.stopPropagation()}>
<Select
onChange={onNameChange}
value={value.name || ''}
placeholder='请选择类型名称'
>
{
(datatypes||[]) && datatypes.map((_datatype, index) => {
return (
<Option key={_datatype.name||''}>{_datatype.name||''}</Option>
);
})
}
</Select>
</span>
</Col>
</Row>
{
(value.parameterCnNames||[]).map((parameterCnName, index) => {
//使用InputNumber:当value改变时 InputNumber显示值没改变 但实际值有改变 是ant design的bug 这里使用只能输入数字的Input
return (
<Row key={index} className='mt-2' align='middle'>
<Col span={9}>
<span>{`${parameterCnName||''}:`}</span>
</Col>
<Col span={15}>
<Input
onChange={(e) => {
onParameterValuesChange(e, index);
}}
value={value.parameterValues[index] || ''}
style={{ width: '100%' }}
placeholder='请输入一个整数'
/>
</Col>
</Row>
);
})
}
</>
)
}
export const EditableCell = ({
editing,
dataIndex,
colTitle,
inputType,
record,
index,
datatypes,
require,
children,
...restProps
}) => {
let editingComponent = null;
if (editing) {
if (dataIndex !== 'datatype') {
const inputNode = inputType === 'check' ? <Checkbox /> : <InputDebounce />
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={(inputType==='check')? 'checked': 'value'}
rules={[
{
required: (require===null)?false:require,
message: `请输入${colTitle}!`,
},
]}
>
{ inputNode }
</Form.Item>
);
} else {
editingComponent = (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
valuePropName={'value'}
rules={[
{
required: (require===null)?false:require,
message: `请输入${colTitle}!`,
},
]}
>
<DatatypeInput datatypes={datatypes} />
</Form.Item>
)
}
}
return (
<td {...restProps}>
{editing ? (
editingComponent
) : (
children
)}
</td>
);
};
export const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop(
() => ({
accept: type,
collect: monitor => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: item => {
if (moveRow) {
moveRow(item.index, index);
}
},
}),
[index, moveRow],
);
const [, drag] = useDrag(
() => ({
type,
item: () => {
addEventListenerForSidebar("containerId");
return { index };
},
end: (_, __) => {
removeEventListenerForSidebar();
},
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
}),
[index, moveRow],
);
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
export const ImportActionTable = (props) => {
const { modelerData, onChange, editable, supportedDatatypes, constraint, template, validateReports, type = 'model', terms, action, originAction } = props;
const [ data, setData ] = useState([]);
const [ form ] = Form.useForm();
const [ editingKey, setEditingKey ] = useState('');
const [ loadingSuggest, setLoadingSuggest ] = useState(false);
const [ suggests, setSuggests ] = useState([]);
const [ suggestHaveMore, setSuggestHaveMore ] = useState(false);
const [ suggestOffset, setSuggestOffset ] = useState(1);
const [ currentChangedValues, setCurrentChangedValues] = useState({});
const [ englishSuggests, setEnglishSuggests ] = useState([]);
const [ keywordCondition, setKeywordCondition ] = useState({ keyword: '', needFilter: true });
const { keyword, needFilter } = keywordCondition;
const [ tableWidth, setTableWidth ] = useState(0);
const [ filterPageCondition, setFilterPageCondition ] = useState({ pageNum: 1, pageSize: supportMaxAttributeCountPerPage, filterData: [] });
const [ filterPageData, setFilterPageData ] = useState([]);
const { pageNum, pageSize, filterData } = filterPageCondition;
const [ insertIndex, setInsertIndex ] = useState(0);
const [ currentItem, setCurrentItem ] = useState(null);
const { attrIsEditingFunction } = useContext(EditModelContext);
const moveRowRef = useRef({ data, pageNum, pageSize });
const tableRef = useRef(null);
const termsRef = useRef(null);
const autoTranslateRef = useRef(false);
const { show } = useContextMenu({
id: MENU_ID,
});
const cols = [
{
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
fixed: 'left',
render: (text, record, index) => {
return (index+1).toString();
}
},
{
title: '中文名称',
width: 200,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
require: true,
fixed: 'left',
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</span>
</Tooltip>
)
}
},
{
title: '英文名称',
width: 200,
dataIndex: 'name',
editable: true,
ellipsis: true,
require: true,
render: (text, record, index) => {
return (
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, termsRef.current)}</span>
</Tooltip>
)
}
},
{
title: '类型',
width: (editingKey!=='')?250:150,
dataIndex: 'datatype',
editable: true,
ellipsis: true,
require: true,
render: (_, record, __) => {
if (record?.datatype) {
if ((record?.datatype?.name==='Char'||record?.datatype?.name==='Varchar') && record?.datatype?.parameterValues?.length>0) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)})`;
} else if ((record?.datatype?.name==='Decimal'||record?.datatype?.name==='Numeric') && record?.datatype?.parameterValues?.length>1) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)},${(record?.datatype?.parameterValues[1]?record.datatype.parameterValues[1]:0)})`;
}
return record.datatype.name||'';
}
return '';
}
},
{
title: 'Not Null',
width: 80,
dataIndex: 'notNull',
editable: (type==='model'?true:false),
render: (notNull, record, index) => {
if (!notNull) {
return '-';
} else if (notNull) {
return (
<CheckOutlined />
)
}
return '';
}
},
{
title: '主键',
width: 50,
dataIndex: 'partOfPrimaryKeyLogically',
editable: (type==='model'?true:false),
render: (partOfPrimaryKeyLogically, record, index) => {
if (!partOfPrimaryKeyLogically) {
return '-';
} else if (partOfPrimaryKeyLogically === true) {
return (
<CheckOutlined />
)
}
return '';
}
},
{
title: '外键',
width: 50,
dataIndex: 'foreignKey',
editable: (type==='model'?true:false),
render: (foreignKey, record, index) => {
if (!foreignKey) {
return '-';
} else if (foreignKey === true) {
return (
<CheckOutlined />
)
}
return '';
}
},
// {
// title: '重点关注',
// width: 75,
// dataIndex: 'needAttention',
// editable: (type==='model'?true:false),
// render: (needAttention, record, index) => {
// if (!needAttention) {
// return '-';
// } else if (needAttention === true) {
// return (
// <CheckOutlined />
// )
// }
// return '';
// }
// },
{
title: '业务含义',
dataIndex: 'remark',
editable: true,
ellipsis: true,
require: true,
width: 200,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</span>
</Tooltip>
)
}
},
{
title: '默认值',
dataIndex: 'defaultValue',
editable: true,
ellipsis: true,
width: 100,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</span>
</Tooltip>
)
}
},
{
title: '计算规则',
dataIndex: 'definition',
editable: true,
ellipsis: true,
width: 200,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</span>
</Tooltip>
)
}
},
{
title: '值域',
dataIndex: 'valueRange',
editable: true,
ellipsis: true,
width: 100,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</span>
</Tooltip>
)
}
},
];
const [ columns, setColumns ] = useState(cols);
useClickAway(() => {
save();
}, tableRef);
useEffect(() => {
attrIsEditingFunction && attrIsEditingFunction(editingKey!=='');
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ editingKey ])
//规则改变的时候 数据表为可编辑状态
useEffect(() => {
setEditingKey('');
}, [constraint, template, modelerData])
useEffect(() => {
termsRef.current = terms;
setData(modelerData?.easyDataModelerDataModelAttributes||[]);
moveRowRef.current.data = (modelerData?.easyDataModelerDataModelAttributes||[]);
let _filterData = (modelerData?.easyDataModelerDataModelAttributes||[]).filter(item => (item?.name||'').indexOf(keyword)!==-1 || (item.cnName).indexOf(keyword)!==-1);
setFilterPageCondition({...filterPageCondition, filterData: _filterData});
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [modelerData, terms])
useEffect(() => {
if (needFilter) {
let _filterData = (modelerData.easyDataModelerDataModelAttributes||[]).filter(item => (item?.name||'').indexOf(keyword)!==-1 || (item.cnName).indexOf(keyword)!==-1);
setFilterPageCondition({...filterPageCondition, ...{filterData: _filterData, pageNum: 1} });
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [keywordCondition])
useEffect(() => {
moveRowRef.current.pageNum = pageNum;
moveRowRef.current.pageSize = pageSize;
if (filterData.length > supportMaxAttributeCountPerPage) {
let _filterPageData = paginate(filterData, pageNum, pageSize);
if ((_filterPageData||[]).length===0 && pageNum > 1) {
setFilterPageCondition({...filterPageCondition, pageNum: (pageNum-1)});
moveRowRef.current.pageNum = pageNum - 1;
} else {
setFilterPageData(_filterPageData);
}
} else {
setFilterPageData(filterData);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterPageCondition])
useEffect(() => {
mergedColumns();
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [validateReports, editable, editingKey])
const isEditing = (record) => record?.iid === editingKey;
const onAddClick = (event) => {
event.stopPropagation();
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
const iid = generateUUID();
const newData = [...moveRowRef.current.data, {iid}];
if (newData.length > supportMaxAttributeCountPerPage) {
const totalNum = parseInt(newData.length/supportMaxAttributeCountPerPage) + ((newData.length%supportMaxAttributeCountPerPage===0)?0:1);
setFilterPageCondition({...filterPageCondition, ...{ filterData: newData, pageNum: totalNum }});
} else {
setFilterPageCondition({...filterPageCondition, ...{ filterData: newData}});
}
setInsertIndex(newData.length-1);
edit(newData[newData.length-1], false);
setTimeout(() => {
document.getElementById(`field-${iid}`)?.scrollIntoView();
}, 200)
}
})
}
const insertToFront = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
let newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
const iid = generateUUID();
if (index === 0) {
newData = [{iid}, ...newData];
setInsertIndex(0);
edit(newData[0], false);
} else {
newData.splice(index, 0, {iid});
setInsertIndex(index);
edit(newData[index], false);
}
const _pageNum = parseInt((index+1)/supportMaxAttributeCountPerPage) + (((index+1)%supportMaxAttributeCountPerPage===0)?0:1);
setFilterPageCondition({...filterPageCondition, ...{ filterData: newData, pageNum: _pageNum}});
setTimeout(() => {
document.getElementById(`field-${iid}`)?.scrollIntoView();
}, 200)
}
})
}
const insertToBack = (record) => {
save().then(result => {
if (result) {
setKeywordCondition({ keyword: '', needFilter: false });
const newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
const iid = generateUUID();
newData.splice(index+1, 0, {iid});
const _pageNum = parseInt((index+2)/supportMaxAttributeCountPerPage) + (((index+2)%supportMaxAttributeCountPerPage===0)?0:1);
setFilterPageCondition({...filterPageCondition, ...{ filterData: newData, pageNum: _pageNum}});
setInsertIndex(index+1);
edit(newData[index+1], false);
setTimeout(() => {
document.getElementById(`field-${iid}`)?.scrollIntoView();
}, 200)
}
})
}
const editLogic = (record) => {
form.resetFields();
form.setFieldsValue(record);
setEditingKey(record?.iid);
autoTranslateRef.current = ((record?.name||'')==='');
if ((record?.cnName||'')!=='') {
onValuesChange({ cnName: record?.cnName }, record, 1, record?.iid);
} else if ((record?.name||'')!=='') {
onValuesChange({ name: record?.name }, record, 1, record?.iid);
}
}
const edit = (record, needSave = true) => {
if (needSave) {
save().then((result) => {
if (result) {
editLogic(record);
}
});
} else {
editLogic(record);
}
};
const removeLogic = (record) => {
const newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
if (index !== -1) {
newData.splice(index, 1);
}
onChange && onChange(newData);
}
const remove = (record) => {
if (record.iid !== editingKey) {
save().then(result => {
if (result) {
removeLogic(record);
}
})
} else {
removeLogic(record);
}
}
const preSaveDataModel = (attribute) => {
dispatch({
type: 'datamodel.preSaveDataModel',
payload: {
data: attribute
},
callback: remoteData => {
const newData = [...moveRowRef.current.data];
const index = (newData||[]).findIndex((item) => attribute.iid === item.iid);
if (index !== -1) {
newData.splice(index, 1, remoteData);
moveRowRef.current.data = newData;
onChange && onChange(newData, false);
}
},
})
}
const save = async() => {
try {
if (editingKey!=='') {
const row = await form.validateFields();
if ((row.datatype.name||'')==='') {
form.setFields([{ name: 'datatype', errors: ['必须选择类型'] }]);
return;
}
(row.datatype.parameterNames||[]).forEach((parameterName, index) => {
if (!row.datatype.parameterValues[index] || row.datatype.parameterValues[index]==='') {
row.datatype.parameterValues[index] = 0;
}
})
const newData = [...data];
const index = newData.findIndex((item) => editingKey === item.iid);
//判断字段名称是否唯一
let _index;
if (index === -1) {
_index = (data||[]).findIndex(item => item.name === row.name);
} else {
const newDataExcludeSelf = [...data];
newDataExcludeSelf.splice(index, 1);
_index = (newDataExcludeSelf||[]).findIndex(item => item.name === row.name);
}
if (_index !== -1) {
form.setFields([{ name: 'name', errors: ['字段名称不能重复'] }]);
return;
}
let attribute = {};
if (index === -1) {
attribute = {...row, iid: editingKey, modelingTemplateTag: null};
newData.splice(insertIndex, 0, attribute);
} else {
const item = newData[index];
attribute = { ...item, ...row, modelingTemplateTag: null };
newData.splice(index, 1, attribute);
}
moveRowRef.current.data = newData;
onChange && onChange(newData, true);
if (!attribute.needAttention) {
preSaveDataModel(attribute);
}
setEditingKey('');
setSuggests([]);
setEnglishSuggests([]);
}
return true;
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
return false;
}
};
const onValuesChange = (changedValues, allValues, offset = 1, iid = editingKey) => {
// console.log('changed values', changedValues);
// console.log('all values', allValues);
let ownPrimaryKey = false;
if (modelerData?.easyDataModelerPrimaryKey) {
ownPrimaryKey = (modelerData.easyDataModelerPrimaryKey.filter(item => item.name===allValues.name).length>0);
}
if (changedValues.hasOwnProperty('cnName') || changedValues.hasOwnProperty('name')) {
if (offset === 1) {
setSuggests([]);
}
const newData = [...data];
const index = newData.findIndex((item) => iid === item.iid);
if (index === -1) {
newData.splice(0, 0, { iid, ...allValues});
} else if (index !== -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...allValues });
}
setSuggestHaveMore(false);
setCurrentChangedValues(changedValues);
function getSuggest() {
setLoadingSuggest(true);
dispatchLatest({
type: 'datamodel.suggest',
payload: {
params: {
name: allValues.name||'',
cnName: allValues.cnName||'',
topN: (offset+perSuggestCount-1),
offset
}
},
callback: data => {
setLoadingSuggest(false);
if (changedValues.hasOwnProperty('cnName')) {
const moreSuggests = (data||[]).length>0?(data[0].suggestions||[]):[];
const newSuggests = (offset===1 ? moreSuggests : [...suggests, ...moreSuggests]);
if (moreSuggests.length === perSuggestCount) {
setSuggestHaveMore(true);
}
setSuggestOffset(newSuggests.length+1);
setSuggests(newSuggests);
} else if (changedValues.hasOwnProperty('name')) {
const moreSuggests = (data||[]).length>1?(data[1].suggestions||[]):[];
const newSuggests = (offset===1 ? moreSuggests : [...suggests, ...moreSuggests]);
if (moreSuggests.length === perSuggestCount) {
setSuggestHaveMore(true);
}
setSuggestOffset(newSuggests.length+1);
setSuggests(newSuggests);
}
},
error: () => {
setLoadingSuggest(false);
}
})
}
if (changedValues.hasOwnProperty('cnName')) {
if (autoTranslateRef.current) {
dispatchLatest({
type: 'datamodel.translatePhase',
payload: {
params: {
phaseInChinese: changedValues.cnName,
}
},
callback: data => {
if ((data?.translated||'') !== '') {
form.setFieldsValue({ name: data?.translated||'' });
}
getSuggest();
}
})
} else {
getSuggest();
}
} else if (changedValues.hasOwnProperty('name')) {
autoTranslateRef.current(changedValues.name==='');
getSuggest();
}
} else if(changedValues.hasOwnProperty('notNull') ) {
if (!changedValues.notNull && ownPrimaryKey) {
showMessage('info', '主键不允许为空');
}
}
if (ownPrimaryKey) {
form.setFieldsValue({
notNull: true
});
}
};
const onSuggestChange = (record) => {
form.resetFields();
form.setFieldsValue({
...record
});
setSuggests([]);
};
const editableColumn = [
...cols,
{
title: '操作',
dataIndex: 'action',
width: (action==='flow')?220: ((originAction==='flow')?90:130),
fixed: 'right',
render: (_, record) => {
return (
<AppContext.Consumer>
{
value => <React.Fragment>
{
(action==='flow') && <React.Fragment>
{
record?.isPossibleNewRecommendedDefinition?.possible && <Typography.Link className='mr-3' onClick={(event) => {
event.stopPropagation();
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: record,
type: record?.isPossibleNewRecommendedDefinition?.type
}
});
}}>
加入标准
</Typography.Link>
}
{
record?.isPossibleNewTerm?.possible && <Typography.Link className='mr-3' onClick={(event) => {
event.stopPropagation();
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: record,
type: record?.isPossibleNewTerm?.type
}
})
}}>
加入词汇
</Typography.Link>
}
</React.Fragment>
}
{
editable && <React.Fragment>
{
<React.Fragment>
{
(originAction!=='flow') && <Tooltip title={record.needAttention ? '取消送审关注': '送审关注'}>
<Button
className='mr-3'
size='small'
type='text'
icon={record.needAttention ? <AttentionSvg style={{ width: 15, height: 15 }} /> : <UnAttentionSvg style={{ width: 15, height: 15 }} />}
onClick={(event) => {
event.stopPropagation();
if (record.needAttention === null) {
record.needAttention = true;
} else {
record.needAttention = !record.needAttention;
}
const newData = [...moveRowRef.current.data];
const index = newData.findIndex((item) => record.iid === item.iid);
if (index !== -1) {
newData.splice(index, 1, {...record});
setData(newData);
moveRowRef.current.data = newData;
onChange && onChange(newData, false);
if (!record.needAttention) {
preSaveDataModel(record);
}
}
}}
/>
</Tooltip>
}
<Button
className='mr-3'
size='small'
type='text'
icon={<PlusOutlined className='default' />}
onClick={(event) => {
event.stopPropagation();
insertToFront(record);
}}
/>
<Button
className='mr-3'
size='small'
type='text'
icon={<DeleteOutlined style={{ color: 'red' }} />}
onClick={(event) => {
event.stopPropagation();
remove(record);
}}
/>
</React.Fragment>
}
</React.Fragment>
}
</React.Fragment>
}
</AppContext.Consumer>
)
},
},
]
const validateColumn = {
title: '规范',
dataIndex: 'validate',
//小屏幕下隐藏规范, 给编辑多点空间
width: (tableWidth<1300 && editable)?0:200,
fixed: 'right',
render: (text, record, index) => {
let shortCauses = [];
let causes = [];
(validateReports||[]).forEach((report) => {
if (report.type==='DataModelAttribute' && report.iid === record.iid) {
(report.reportItems||[]).forEach((item) => {
shortCauses.push(item.shortCause||'');
causes.push(item.cause||'');
})
}
});
return (
<div className='d-flex' key={index} style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<div>
{
shortCauses && shortCauses.map((shortCause, index) => {
return (
<Tooltip title={shortCause}>
<div className='textOverflow' style={{ width: 130, color: '#f5222d' }} key={index}>
<span>{shortCause||''}</span>
</div>
</Tooltip>
);
})
}
</div>
{
(causes||[]).length>0 && <Popover
content={
<div style={{ maxWidth: 200, maxHeight: 200, wordWrap: 'break-word', overflow: 'auto' }}>
{
causes && causes.map((cause, index) => {
return (
<Typography.Paragraph key={index} >
{cause||''}
</Typography.Paragraph>
)
})
}
</div>
}
title="规则详情"
>
<a href="#">详情</a>
</Popover>
}
</div>
)
}
};
const includeValidateColumn = [
...cols,
validateColumn
]
const includeValidateEditableColumn = [
...editableColumn,
validateColumn
]
const mergedColumns = () => {
const hasValidateReports = (validateReports||[]).filter(report => report.type==='DataModelAttribute').length>0;
if (editable) {
let _columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
_columns = _columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
inputType: (col.dataIndex==='notNull' || col.dataIndex==='partOfDistributionKey' || col.dataIndex==='partOfPrimaryKeyLogically' || col.dataIndex==='needAttention' || col.dataIndex==='foreignKey') ? 'check' : 'text',
colTitle: col.title,
editing: isEditing(record),
datatypes: supportedDatatypes,
require: col.require
}),
};
});
setColumns(_columns);
} else {
let _columns = hasValidateReports ? includeValidateColumn: cols;
if (action === 'flow') {
_columns = hasValidateReports ? includeValidateEditableColumn: editableColumn;
}
// _columns = _columns.filter((col) => col.dataIndex!=='needAttention');
setColumns(_columns);
}
}
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const { data, pageNum, pageSize } = moveRowRef.current;
let realDragIndex = dragIndex;
let realHoverIndex = hoverIndex;
if ((data||[]).length>supportMaxAttributeCountPerPage) {
realDragIndex = (pageNum-1)*pageSize + dragIndex;
realHoverIndex = (pageNum-1)*pageSize + hoverIndex;
}
const dragRow = data[realDragIndex];
const newData = update(data, {
$splice: [
[realDragIndex, 1],
[realHoverIndex, 0, dragRow],
],
})
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[moveRowRef.current],
);
const onSearchInputChange = (value) => {
setEditingKey('');
setKeywordCondition({ keyword: value||'', needFilter: true });
}
const loadMoreSuggests = (event) => {
event.stopPropagation();
onValuesChange(currentChangedValues, form.getFieldsValue(), suggestOffset);
}
const closeSuggests = (event) => {
event.stopPropagation();
setSuggestHaveMore(false);
setSuggests([]);
setSuggestOffset(1);
}
const displayMenu = (e) => {
show({
event: e
})
}
const handleItemClick = ({ id, event, props }) => {
if (id === 'up') {
insertToFront(currentItem);
} else if (id === 'down') {
insertToBack(currentItem);
}
}
const handleResize = index => (e, { size }) => {
const nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
setColumns(nextColumns);
};
const resizeColumns = () => {
return (
columns.map((column, index) => {
return {
...column,
onHeaderCell: column => ({
width: column.width,
onResize: handleResize(index),
}),
};
})
);
}
return (
<div className='model-import-action-table'>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<h2 style={{ marginBottom: 0 }}>数据表结构</h2>
{
editable && <Popover content={<span>
新增: 点击操作列的加号按钮在当前字段前新增一个字段<br />
修改: 点击字段表格中的一行任意位置进入该字段编辑页面<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输入中文名,自动推荐英文名,可选择采用或者手工修改<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输入中文名,自动推荐可参考引用的数据标准或者数据字典,可选择采用<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;支持拖拽方式调整模型字段顺序<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;点击表格外部任意位置退出字段编辑状态<br />
删除: 点击操作列垃圾桶图标删除该字段<br />
保存: 页面右下角保存按钮
</span>}>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
</Space>
<Space>
{
editable && <Tooltip>
<Button onClick={onAddClick}>新建</Button>
</Tooltip>
}
<div className='d-flex' style={{ alignItems: 'center' }}>
<InputDebounce
placeholder="请输入中文名称或者英文名称"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth }}
/>
</div>
</Space>
</div>
<div className='mb-3' id="containerId" ref={tableRef}>
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width);
}}
>
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false} onValuesChange={onValuesChange}>
<Table
components={{
header: {
cell: ResizeableHeaderCell,
},
body: {
cell: EditableCell,
//编辑或者搜索状态下不允许拖动
row: (editable&&editingKey===''&&keyword==='')?DragableBodyRow:null,
},
}}
rowClassName={(record, index) => {
if (type==='model' && record?.modelingTemplateTag && record?.modelingTemplateTag!=={} && !isEditing(record)) {
return 'editable-row template-highlight-row';
}
if (type==='model' && !isEditing(record)) {
if (record?.needAttention) {
return 'editable-row attention-row';
} else if (record?.partOfPrimaryKeyLogically) {
return 'editable-row primary-row';
}
}
return 'editable-row';
}}
onRow={(record, index) => {
let rowParams = {
index,
id: `field-${record.iid}`,
}
if (editable) {
rowParams = {...rowParams, onContextMenu: event => {
setCurrentItem(record);
displayMenu(event);
}
};
if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation();
edit(record);
}
}
if (keyword.length===0) {
rowParams = {...rowParams, moveRow};
}
}
}
return rowParams;
}}
dataSource={filterPageData||[]}
columns={resizeColumns()}
size='small'
rowKey='iid'
pagination={false}
sticky
scroll={{
x: 1500,
//解决屏幕尺寸窄时,字段不好横向拖动的问题
y: tableWidth>1500?'100%':630,
}}
expandable={{
columnWidth: 0,
expandedRowRender: record => (
<React.Fragment>
{
editingKey!=='' && <React.Fragment>
{
suggests && suggests.length>0 && (
<SuggestTable suggests={suggests} onSelect={onSuggestChange} />
)
}
</React.Fragment>
}
<div className='flex pt-3' style={{ justifyContent: 'center' }}>
<Tooltip title={!suggestHaveMore?'没有更多推荐字段': ''}>
<Button onClick={loadMoreSuggests} disabled={!suggestHaveMore} loading={loadingSuggest}>加载更多</Button>
</Tooltip>
<Button className='ml-3' onClick={closeSuggests}>收起推荐</Button>
</div>
</React.Fragment>
),
expandIcon: ({ expanded, onExpand, record }) => {
return null;
},
rowExpandable: record => (editingKey!==''&&((suggests||[]).length>0 || (englishSuggests||[]).length>0)),
expandedRowKeys: [editingKey]
}}
/>
</Form>
</DndProvider>
</ResizeObserver>
{
(filterData.length > supportMaxAttributeCountPerPage) && <Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={(_pageNum, _pageSize) => {
setFilterPageCondition({...filterPageCondition, ...{ pageNum: _pageNum, pageSize: _pageSize || supportMaxAttributeCountPerPage }})
}}
onShowSizeChange={(_pageNum, _pageSize) => {
setFilterPageCondition({...filterPageCondition, ...{ pageNum: _pageNum || 1, pageSize: _pageSize }})
}}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={(filterData||[]).length}
pageSizeOptions={[50, supportMaxAttributeCountPerPage]}
showTotal={total => ` ${(filterData||[]).length} `}
/>
}
<RcMenu id={MENU_ID} >
<RcItem id="up" onClick={handleItemClick}>
在上方插入行
</RcItem>
<RcItem id="down" onClick={handleItemClick}>
在下方插入行
</RcItem>
</RcMenu>
</div>
</div>
);
};
.model-import-action-table {
.yy-table-expanded-row {
.yy-radio-wrapper {
white-space: normal !important;
}
}
.yy-table {
.yy-card-body {
padding: 0 !important;
}
}
.template-highlight-row {
.yy-table-cell {
background-color: #e7f7ff !important;
}
}
.attention-row {
.yy-table-cell {
background-color: #fff9ed !important;
}
}
.primary-row {
.yy-table-cell {
background-color: #d3ebff !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/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(/[,,,_ ]/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
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 } = 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,
},
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!=='dir'||(catalogId||'') === '')) {
title = '请先选择主题';
}
if (isSzseEnv && item.key === 'ddl') {
return <></>;
}
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} />
)
}
{
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
import React, { useEffect, useState } from 'react';
import { Modal, Typography, Button, Space, Tooltip, Spin } from 'antd';
import { CopyOutlined, DownloadOutlined } from '@ant-design/icons';
import copy from 'copy-to-clipboard';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
const FC = (props) => {
const { visible, onCancel } = props;
const [information, setInformation] = useState(undefined);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (visible) {
getInformation();
}
}, [visible])
const getInformation = () => {
setLoading(true);
dispatch({
type: 'pds.getJdbcInformation',
callback: data => {
setLoading(false);
setInformation(data);
},
error: () => {
setLoading(false);
}
})
}
return (
<Modal
title='JDBC信息'
width={540}
visible={visible}
onCancel={onCancel}
footer={null}
>
<Spin spinning={loading}>
<div className='flex mb-3' style={{ justifyContent: 'space-between', alignItems: 'baseline' }}>
<div style={{ width: 400 }}>
<Typography.Text>
{`impala JDBC地址: ${information?.impala?.url}`}
</Typography.Text>
</div>
<Space>
<Tooltip title={information?.impala?.urlTip}>
<Button type='link' icon={<CopyOutlined />} onClick={() => {
if (information?.impala?.url) {
copy(information?.impala?.url);
showMessage('success', '复制成功');
}
}} />
</Tooltip>
<Tooltip title={information?.impala?.downloadTip}>
<Button type='link' icon={<DownloadOutlined />} onClick={() => {
if (information?.impala?.downloadUrl) {
copy(information?.impala?.downloadUrl);
showMessage('success', '复制成功');
}
}} />
</Tooltip>
</Space>
</div>
<div className='flex' style={{ justifyContent: 'space-between', alignItems: 'baseline' }}>
<div style={{ width: 400 }}>
<Typography.Text>
{`Hana JDBC地址: ${information?.hana?.url}`}
</Typography.Text>
</div>
<Space>
<Tooltip title={information?.hana?.urlTip}>
<Button type='link' icon={<CopyOutlined />} onClick={() => {
if (information?.hana?.url) {
copy(information?.hana?.url);
showMessage('success', '复制成功');
}
}} />
</Tooltip>
<Tooltip title={information?.hana?.downloadTip}>
<Button type='link' icon={<DownloadOutlined />} onClick={() => {
if (information?.hana?.downloadUrl) {
copy(information?.hana?.downloadUrl);
showMessage('success', '复制成功');
}
}} />
</Tooltip>
</Space>
</div>
</Spin>
</Modal>
)
}
export default FC;
\ 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='#c7000b'
/>
</svg>
)
import React, { useState, useEffect, useRef, useContext, useMemo } from "react";
import { Tooltip, Modal, Pagination, Typography, Space, Menu, Dropdown, Row, Col, Divider } from 'antd';
import { DownOutlined, UpOutlined, UnorderedListOutlined } from '@ant-design/icons';
import copy from "copy-to-clipboard";
import SmoothScroll from 'smooth-scroll';
import classnames from 'classnames';
import { Resizable } from 'react-resizable';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import ResizeObserver from 'rc-resize-observer';
import ServiceDetail from './ServiceDetailModal';
import SampleModal from './SampleModal';
import ExchangeOwnerModal from './ExchangeOwner';
import Table from '../../ResizeableTable';
import { dispatch } from '../../../../model';
import { showMessage, getQueryParam, paginate, isSzseEnv, formatDate, getDataModelerRole } from '../../../../util';
import { AnchorId, AnchorTimestamp, Action, CatalogId, ModelerId, DataModelerRoleReader } from '../../../../util/constant';
import { AppContext } from "../../../../App";
import SelectUser from "./SelectUsers";
// import Tag from "../../Tag";
import './ModelTable.less';
import 'react-contexify/dist/ReactContexify.css';
const { Text } = Typography;
const { Column } = Table;
const actions = [
{ title: '编辑', key: 'edit' },
{ title: '删除', key: 'delete' },
{ title: 'URI复制', key: 'copyUri' },
{ title: '样本数据', key: 'sample' },
{ title: '历史版本', key: 'history' },
{ title: '授权', key: 'admit' },
// { title: '申请', key: 'startFlow' },
// { title: '下载Tableau tds', key: 'downloadTds' },
// { title: '跳转至电子表格', key: 'smart' },
{ title: '更换管理', key: 'exchangeOwner' },
{ title: '拖拉创建字段', key: 'dragAttribute' },
{ title: '自定义sql创建字段', key: 'sqlAttribute' }
]
const ModelNameColumn = (props) => {
const { text, record, detailItem } = props;
const [ data, setData ] = useState(record);
const cols = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '字段中文名称',
width: 160,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
},
{
title: '字段英文名称',
width: 160,
dataIndex: 'name',
editable: true,
ellipsis: true,
},
];
let _textComponent = <span style={{ color: '#000' }}>{text}</span>;
if (data.digest) {
_textComponent = <div style={{ width: 500, maxHeight: 300, overflow: 'auto' }}>
<Table
dataSource={data.digest.attributeDigests||[]}
columns={cols}
loading={false}
pagination={false}
size='small'
rowClassName={(record, index) => {
if (record?.primaryKey) {
return 'primary-row';
}
return '';
}}
/>
</div>;
}
return (
<Tooltip
title={_textComponent}
overlayClassName='tooltip-common'
onVisibleChange={(visible) => {
if (visible && !record.digest) {
dispatch({
type: 'pds.getServiceDigest',
payload: {
id: record.id
},
callback: _data => {
record.digest = _data;
setData({...record});
}
})
}
}}
>
<a onClick={()=>{detailItem(record);}}>
{text||''}
</a>
</Tooltip>
);
}
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 ModelTable = (props) => {
const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, modelId = null, modelPid = null, view, selectModelerIds, onSubSelect, modelState, user, isOnlyEnding, visibleColNames } = props;
const MENU_ID = (((modelId||'') !== '') ? `model-table-contextmenu-${modelId}` : 'model-table-contextmenu');
const { show, hideAll } = useContextMenu({
id: MENU_ID,
});
const [ tableWidth, setTableWidth ] = useState(0);
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ subSelectedRowKeys, setSubSelectedRowKeys ] = useState([]);
// const [ mouseEnterKey, setMouseEnterKey ] = useState(null);
const [ sortRule, setSortRule ] = useState(null);
const [ filterData, setFilterData ] = useState([]);
const [ subData, setSubData ] = useState([]);
const [ serviceDetailParams, setServiceDetailParams ] = useState({ visible: false, id: '' })
const [ sampleParams, setSampleParams ] = useState({ visible: false, service: undefined });
const [ exchangeOwnerParams, setExchangeOwnerParams ] = useState({ visible: false, id: undefined });
const [attrs, setAttrs] = useState(undefined);
const app = useContext(AppContext);
const indexCol = {
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
filter: false,
}
const fixedCols = [
// {
// 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,
render: (editor, record) => {
const user = users?.filter((user)=>user.pernr===editor);
if (user && user.length > 0) {
return <Tooltip title={user[0].nachn?`${user[0].nachn}(${user[0].pernr})`:user[0].pernr}>
<Text ellipsis={true}>{user[0].nachn?`${user[0].nachn}(${user[0].pernr})`:user[0].pernr}</Text>
</Tooltip>
}
return editor;
}
},
]
const actionCol = {
title: '操作',
dataIndex: 'action',
width: 240,
fixed: 'right',
filter: false,
render: (_, record) => {
const authActionTitles = [];
if ((getDataModelerRole(user)!==DataModelerRoleReader) && view!=='grant' && !isOnlyEnding && (record.editable||record.permitCheckOut)) {
authActionTitles.push('编辑');
}
if ((getDataModelerRole(user)!==DataModelerRoleReader) && view!=='grant'&& !isOnlyEnding && record.deletable) {
authActionTitles.push('删除');
}
if (record.odata) {
authActionTitles.push('URI复制');
}
authActionTitles.push('样本数据');
authActionTitles.push('历史版本');
if (getDataModelerRole(user)!==DataModelerRoleReader&& view!=='grant'&&record.grantable) {
authActionTitles.push('授权');
}
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && isOnlyEnding && !currentItem?.grantable) {
// authActionTitles.push('申请');
// }
if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && record.supportChangeOwn) {
authActionTitles.push('更换管理');
}
if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && record.serviceDefinitionType === 'empty') {
authActionTitles.push('拖拉创建字段');
authActionTitles.push('自定义sql创建字段');
}
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && !isOnlyEnding && record.supportODataDisable) {
// authActionTitles.push('下载Tableau tds');
// }
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && record.supportSmartBIWebSpreadSheet) {
// authActionTitles.push('跳转至电子表格');
// }
const authActions = actions.filter(item => authActionTitles.indexOf(item.title) !== -1);
if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant') {
record.state?.supportedActions?.forEach((item, index) => {
authActions.push({ title: item.cnName, key: `action-${index}` });
});
}
const haveMore = authActions.length > 3;
const showActions = authActions.slice(0, 3);
const hiddenActions = authActions.slice(3, authActions.length);
return (
<Space size={5} split={<Divider type='vertical' style={{margin:0}} />}>
{
showActions.map((item, index) => {
return (
<div key={index} style={{ width: 50 }}>
<a
onClick={() => {handleItemClick(item.key, record)}}
>{item.title}</a>
</div>
)
})
}
{
haveMore && <Dropdown overlay={
<Menu onClick={({ key }) => {
handleItemClick(key, record);
}}>
{
hiddenActions.map((item, index) => {
return (
<Menu.Item key={item.key} >
<div style={{ textAlign: 'center' }}>
{item.title}
</div>
</Menu.Item>
)
})
}
</Menu>
}
placement="bottomLeft"
>
<UnorderedListOutlined className='default' style={{ fontSize:16,cursor:'pointer', marginLeft: 5 }} />
</Dropdown>
}
</Space>
)
}
}
// const [ columns, setColumns ] = useState([]);
// const [ includePathColumns, setIncludePathColumns ] = useState([]);
const [ pagination, setPagination ] = useState( { pageNum: 1, pageSize: 20 } );
const [ currentItem, setCurrentItem ] = useState(null);
const { pageNum, pageSize } = pagination;
const [ users, setUsers ] = useState([]);
const [modal, contextHolder] = Modal.useModal();
const anchorId = getQueryParam(AnchorId, props.location?.search);
const anchorTimestamp = getQueryParam(AnchorTimestamp, props.location?.search);
const shouldScrollRef = useRef(false);
useEffect(() => {
getUsers();
getAttrs();
if ((modelId||'') !== '') {
window?.addEventListener("storage", modelEventChange);
return () => {
window?.removeEventListener("storage", modelEventChange);
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if ((modelId||'') === '') {
onSelect && onSelect([]);
if ((keyword||'') === '') {
if (offset !== null) {
const _pageNum = parseInt(offset/pageSize + ((offset%pageSize===0)?0:1));
setPagination({...pagination, pageNum: _pageNum });
} else {
setPagination({...pagination, pageNum: 1 });
}
} else {
setPagination({...pagination, pageNum: 1 });
}
} else {
getCheckoutDataModel();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ catalogId, keyword, offset, modelState ]);
useEffect(() => {
if ((selectModelerIds||[]).length === 0) {
setSelectedRowKeys([]);
setSubSelectedRowKeys([]);
}
}, [selectModelerIds])
useEffect(() => {
if ((anchorId||'') !== '') {
shouldScrollRef.current = true;
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [anchorTimestamp])
useEffect(() => {
if (shouldScrollRef.current) {
SmoothScroll('a[href*="#"]');
const _id = getQueryParam(AnchorId, props.location.search);
var scroll = new SmoothScroll();
var anchor = document.querySelector(`#data-model-${_id}`);
if (anchor) {
scroll.animateScroll(anchor);
shouldScrollRef.current = false;
}
}
})
useEffect(() => {
const newData = [...data];
if (sortRule) {
if (sortRule.order === 'ascend') {
newData.sort((item1, item2) => {
if (sortRule.field === 'state') {
return (item1[sortRule.field]?.cnName||'').localeCompare(item2[sortRule.field]?.cnName||'');
} else if (sortRule.field === 'modifiedTs') {
return formatDate(item1[sortRule.field]).localeCompare(formatDate(item2[sortRule.field]));
}
return item1[sortRule.field].localeCompare(item2[sortRule.field]);
})
} else if (sortRule.order === 'descend') {
newData.sort((item1, item2) => {
if (sortRule.field === 'state') {
return (item2[sortRule.field]?.cnName||'').localeCompare(item1[sortRule.field]?.cnName||'');
} else if (sortRule.field === 'modifiedTs') {
return formatDate(item2[sortRule.field]).localeCompare(formatDate(item1[sortRule.field]));
}
return item2[sortRule.field].localeCompare(item1[sortRule.field]);
})
}
}
const _data = paginate(newData||[], pageNum, pageSize);
setFilterData(_data);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, pagination, sortRule])
const handleItemClick = (id, record) => {
if (id === 'edit') {
if (record?.editable) {
editItem(record);
} else if (record?.permitCheckOut) {
dispatch({
type: 'pds.checkoutService',
payload: {
params: {
id: record?.id
}
},
callback: data => {
onChange && onChange();
editItem(data);
}
})
}
} else if (id === 'delete') {
deleteItem(record);
} else if (id === 'sample') {
setSampleParams({visible: true, service: record})
} else if (id === 'history') {
historyItem(record);
} else if (id === 'copy') {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(view==='dir')?(catalogId||''):''}&${ModelerId}=${record.id}`);
} else if (id === 'createTable') {
deployAction(record);
} else if (id.indexOf('action') !== -1) {
const index = (id.split('-'))[1];
const action = record?.state?.supportedActions[index];
stateAction(record, action);
} else if (id === 'admit') {
app.openAdmit?.({ dirId: catalogId, service: record })
} else if (id === 'enableOData') {
startODataItem(record);
} else if (id === 'disableOData') {
disableODataItem(record);
} else if (id === 'startFlow') {
app.applyServer?.({ service: record });
} else if (id === 'downloadTds') {
window.open(`/api/pdataservice/pdsCURD/genTDS?id=${record?.id}`);
} else if (id === 'checkout') {
dispatch({
type: 'pds.checkoutService',
payload: {
params: {
id: record?.id
}
},
callback: () => {
showMessage('success', '检出成功');
onChange && onChange();
}
})
} else if (id === 'smart') {
dispatch({
type: 'pds.getSmartBiUrl',
payload: {
url: record?.smartBiWebSpreadSheetEntry
},
callback: (data) => {
window.open(data)
}
})
} else if (id === 'exchangeOwner') {
setExchangeOwnerParams({ visible: true, id: record?.id });
} else if (id === 'offline') {
offlineService(record);
} else if (id === 'copyUri') {
copy(record.odata);
showMessage('success', 'URI复制成功');
} else if (id === 'dragAttribute') {
app?.editServer?.({ service: record, dirId: catalogId, type: id });
} else if (id === 'sqlAttribute') {
app?.editServer?.({ service: record, dirId: catalogId, type: id });
}
}
const detailItem = (record) => {
// onItemAction && onItemAction(record, 'detail', getDataModelerRole(user)===DataModelerRoleReader);
app.openDetail?.({ service: record, isOnlyEnding })
// setServiceDetailParams({ visible: true, id: record.id })
}
const columns = useMemo(() => {
let newCols = [];
attrs?.forEach(item => {
let col = {
title: item.name,
dataIndex: item.key,
ellipsis: true,
width: 120,
render: (_, record) => {
return <Tooltip title={record.basicInfo ? record.basicInfo[item.key] : ''}>
<Text ellipsis={true}>{record.basicInfo ? record.basicInfo[item.key] : ''}</Text>
</Tooltip>
}
};
if (item.key === 'name') {
col.render = (text, record, index) => {
return (<ModelNameColumn text={record.basicInfo?record.basicInfo[item.key]:''} record={record} detailItem={detailItem} />);
};
}
if (item.userSelected === true) {
col.render = (_, record) => {
const user = users?.filter((user)=>(user.pernr===record.basicInfo?.[item.key]));
if (user && user.length > 0) {
return <Tooltip title={user[0].nachn?`${user[0].nachn}(${user[0].pernr})`:user[0].pernr}>
<Text ellipsis={true}>{user[0].nachn?`${user[0].nachn}(${user[0].pernr})`:user[0].pernr}</Text>
</Tooltip>
}
return record.basicInfo?.[item.key];
};
}
newCols.push(col);
});
if ((visibleColNames||[]).length > 0) {
newCols = newCols.filter(col => visibleColNames.indexOf(col.title)!==-1);
}
if (!modelId) {
newCols = [indexCol, ...newCols, ...fixedCols, actionCol];
} else {
newCols = [...newCols, ...fixedCols, actionCol];
}
return newCols;
}, [visibleColNames, attrs, indexCol, actionCol, modelId, detailItem, users])
const modelEventChange = (e) => {
if (e.key === 'modelChange') {
getCheckoutDataModel();
}
}
const getAttrs = () => {
dispatch({
type: 'pds.getAttrs',
payload: {
modelName: 'DataService'
},
callback: data => {
setAttrs(data);
},
error: () => {
}
})
}
const getUsers = () => {
dispatch({
type: 'pds.getOwners',
callback: (data) => {
setUsers(data);
}
})
}
const getCheckoutDataModel = () => {
dispatch({
type: 'pds.getCheckoutService',
payload: {
id: modelPid
},
callback: data => {
setSubData(data?[data]:[]);
},
error: () => {
}
})
}
// const getDataModel = () => {
// dispatch({
// type: 'datamodel.getDataModel',
// payload: {
// id: modelId
// },
// callback: data => {
// setSubData(data?[data]:[]);
// },
// error: () => {
// }
// })
// }
const editItem = (record) => {
// onItemAction && onItemAction(record, 'edit');
app.editServer?.({ dirId: catalogId, service: record })
}
const deployAction = (record) => {
onAutoCreateTable && onAutoCreateTable(record);
}
const stateAction = (record, action) => {
modal.confirm({
title: '提示!',
content: `您确定要${action.cnName||''}该服务吗?`,
onOk: () => {
dispatch({
type: 'pds.nextState',
payload: {
pdsDataServiceId: record.id,
actionId: action.id
},
callback: () => {
showMessage('success', `服务${action.cnName||''}成功`);
if ((modelId||'') === '') {
onChange && onChange();
const index = selectedRowKeys.findIndex((rowKey) => rowKey === record.id);
if (index !== -1) {
const newSelectedRowKeys = [...selectedRowKeys];
newSelectedRowKeys.splice(index, 1);
setSelectedRowKeys(newSelectedRowKeys);
onSelect && onSelect(newSelectedRowKeys);
}
} else {
if (action.id === '2') {
onChange && onChange();
} else {
getCheckoutDataModel();
}
}
}
})
}
});
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该服务吗?',
onOk: () => {
dispatch({
type: 'pds.deleteService',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '服务删除成功');
onChange && onChange();
if ((modelId||'') ==='') {
const index = selectedRowKeys.findIndex((rowKey) => rowKey === record.id);
if (index !== -1) {
const newSelectedRowKeys = [...selectedRowKeys];
newSelectedRowKeys.splice(index, 1);
setSelectedRowKeys(newSelectedRowKeys);
onSelect && onSelect(newSelectedRowKeys);
}
}
}
})
}
});
}
const historyItem = (record) => {
onHistory && onHistory(record.id);
}
const startODataItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要启动该服务的OData吗?',
onOk: () => {
dispatch({
type: 'pds.enableOData',
payload: {
params: {
pdsDataServiceId: record.id
}
},
callback: () => {
showMessage('success', '启动成功');
onChange && onChange();
}
})
}
});
}
const disableODataItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要停用该服务的OData吗?',
onOk: () => {
dispatch({
type: 'pds.disableOData',
payload: {
params: {
pdsDataServiceId: record.id
}
},
callback: () => {
showMessage('success', '停用成功');
onChange && onChange();
}
})
}
});
}
const offlineService = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要下线该服务吗?',
onOk: () => {
dispatch({
type: 'pds.offlineDataService',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '下线成功');
onChange && onChange();
}
})
}
});
}
const onSelectChange = keys => {
setSelectedRowKeys(keys);
if ((modelId||'') !== '') {
onSubSelect && onSubSelect(keys, subData[0].id);
} else {
onSelect && onSelect([...subSelectedRowKeys, ...keys]);
}
};
const onSubSelectChange = (keys, id) => {
if ((keys||[]).length === 0) {
const index = subSelectedRowKeys.findIndex((rowKey) => rowKey === id);
const newSubSelectedRowKeys = [...subSelectedRowKeys];
newSubSelectedRowKeys.splice(index, 1);
setSubSelectedRowKeys(newSubSelectedRowKeys);
onSelect && onSelect([...newSubSelectedRowKeys, ...selectedRowKeys]);
} else {
const newSubSelectedRowKeys = [...subSelectedRowKeys, id];
onSelect && onSelect([...newSubSelectedRowKeys, ...selectedRowKeys]);
}
}
const onTableChange = (pagination, filters, sorter, extra) => {
if (sorter) {
setSortRule(sorter);
}
}
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
hideSelectAll: (modelId||'') !=='',
};
const classes = classnames('model-table', {
'model-table-sub': modelId
});
let expandable = undefined;
let needExpand = false;
if (!modelId) {
(filterData||[]).forEach(record => {
if (record?.alreadyCheckedOut) {
needExpand = true;
}
})
if (needExpand) {
expandable = {
expandedRowRender: record => <ModelTable
view={view}
modelId={record?.checkedOutId}
modelPid={record?.id}
onSubSelect={onSubSelectChange}
{...props}
/>,
expandIcon: ({ expanded, onExpand, record }) => {
if (!record?.alreadyCheckedOut) return null;
return expanded ? <UpOutlined style={{ fontSize: 10 }} onClick={e => onExpand(record, e)} /> : <DownOutlined style={{ fontSize: 10 }} onClick={e => onExpand(record, e)} />
},
rowExpandable: record => {
return record?.alreadyCheckedOut;
}
};
}
} else {
expandable = {
expandedRowRender: record => <></>,
expandIcon: ({ expanded, onExpand, record }) => {
return null;
},
rowExpandable: record => {
return false;
}
}
}
const displayMenu = (e) => {
show({
event: e
})
}
const onServiceDetailClose = () => {
setServiceDetailParams({ visible: false, id: '' })
}
const onSampleClose = () => {
setSampleParams({ visible: false, service: undefined });
}
const onExchangeOwnerClose = (refresh = false) => {
setExchangeOwnerParams({ visible: false, id: undefined });
refresh && onChange?.();
}
let disableEdit = false, disableDelete = false, editTip = '', deleteTip = '', editMenuTitle = '编辑';
if (!currentItem?.editable && currentItem?.state?.id!=='4') {
disableEdit = true;
if (currentItem?.state?.id === '2') {
editTip = '待发布的服务不允许编辑';
}
}
// if (!currentItem?.permitCheckOut && currentItem?.state?.id==='4') {
// disableEdit = true;
// editTip = `${currentItem.holder||''}正在编辑中, 不允许再编辑`;
// editMenuTitle = `编辑(${currentItem.holder||''}正在编辑中)`;
// }
if (!currentItem?.deletable) {
disableDelete = true;
if (currentItem?.state?.id === '2') {
deleteTip = '待发布的服务不允许删除';
} else if (currentItem?.state?.id === '4') {
deleteTip = '已发布的服务不允许删除';
}
}
return (
<div className={classes}>
<Table
rowSelection={view!=='grant'?rowSelection:undefined}
rowKey={'id'}
extraColWidth={(modelId||needExpand)?85:32}
columns={columns||[]}
dataSource={modelId?(subData||[]):(filterData||[])}
pagination={false}
size={modelId?'small':'default'}
// rowClassName={(record, index) => 'cursor-contextmenu'}
onRow={(record, index) => {
return {
id: `data-model-${record?.id}`,
style: { backgroundColor: (record?.id===anchorId)?'#e7f7ff':'transparent' },
onContextMenu: event => {
setCurrentItem(record);
// displayMenu(event);
},
}
}}
scroll={{ y: modelId?null:((filterData||[]).length===0?null:'calc(100vh - 121px - 57px - 24px - 38px - 44px)') }}
onChange={onTableChange}
expandable={!isOnlyEnding ? expandable : undefined}
/>
{
!modelId && (data||[]).length>0 && <Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum, pageSize: _pageSize || 20 });
}}
onShowSizeChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: 1, pageSize: _pageSize });
}}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={(data||[]).length}
pageSizeOptions={[10,20,50]}
showTotal={total => ` ${total} `}
/>
}
<RcMenu id={MENU_ID}>
{/* {
(getDataModelerRole(user)!==DataModelerRoleReader) && view!=='grant' && !isOnlyEnding && <RcItem id="edit" disabled={!currentItem?.editable&&!currentItem?.permitCheckOut} onClick={handleItemClick}>
编辑
</RcItem>
}
{
(getDataModelerRole(user)!==DataModelerRoleReader) && view!=='grant'&& !isOnlyEnding && <RcItem id="delete" disabled={!currentItem?.deletable} onClick={handleItemClick}>
<Tooltip title={deleteTip}>
删除
</Tooltip>
</RcItem>
}
<RcItem id="sample" onClick={handleItemClick}>
样本数据
</RcItem>
<RcItem id="history" onClick={handleItemClick}>
历史版本
</RcItem>
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && (currentItem?.state?.supportedActions||[]).length>0 && currentItem?.state?.supportedActions.map((item, index) => {
return (
<RcItem id={`action-${index}`} onClick={handleItemClick}>
{item.cnName||''}
</RcItem>
);
})
}
{
getDataModelerRole(user)!==DataModelerRoleReader&& view!=='grant' && <RcItem id="admit" onClick={handleItemClick} disabled={!currentItem?.grantable}>
授权
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && isOnlyEnding && <RcItem id="startFlow" onClick={handleItemClick} disabled={currentItem?.grantable}>
申请
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && !isOnlyEnding && currentItem?.supportODataEnable && <RcItem id="enableOData" onClick={handleItemClick}>
启动OData
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && !isOnlyEnding && currentItem?.supportODataDisable && <RcItem id="disableOData" onClick={handleItemClick}>
停用OData
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && !isOnlyEnding && currentItem?.supportODataDisable && <RcItem id="downloadTds" onClick={handleItemClick}>
下载Tableau tds
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && currentItem?.supportSmartBIWebSpreadSheet && <RcItem id="smart" onClick={handleItemClick}>
跳转至电子表格
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && currentItem?.supportChangeOwn && <RcItem id="exchangeOwner" onClick={handleItemClick}>
更换管理
</RcItem>
} */}
</RcMenu>
<ServiceDetail visible={serviceDetailParams.visible} id={serviceDetailParams.id} onClose={onServiceDetailClose} />
<SampleModal visible={sampleParams.visible} service={sampleParams.service} onCancel={onSampleClose} />
<ExchangeOwnerModal visible={exchangeOwnerParams.visible} id={exchangeOwnerParams.id} onCancel={onExchangeOwnerClose} />
{ contextHolder }
</div>
);
}
export default ModelTable;
\ No newline at end of file
@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 React, { useState, useEffect, useContext } from "react";
import { Tooltip, Tree, Modal, Spin, Dropdown, Menu, Button, AutoComplete } from "antd";
import { PlusOutlined, SwapOutlined, ImportOutlined, UnorderedListOutlined, ReloadOutlined } from '@ant-design/icons';
import classnames from 'classnames';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import UpdateTreeItemModal from './UpdateTreeItemModal';
import { dispatch } from '../../../../model';
import { showMessage, getQueryParam, highlightSearchContentByTerms, getDataModelerRole } from '../../../../util';
import { AnchorDirId, AnchorId, AnchorTimestamp, DataModelerRoleAdmin } from '../../../../util/constant';
import { AppContext } from "../../../../App";
import './ModelTree.less';
import 'react-contexify/dist/ReactContexify.css';
const { Option } = AutoComplete;
const viewModes = [
{
key: 'dir',
name: '目录视角'
},
{
key: 'state',
name: '服务状态视角'
}
];
const ModelTree = (props) => {
const MENU_ID = 'model-tree';
const { show } = useContextMenu({
id: MENU_ID,
});
const { onSelect, onViewChange, refrence='', importStockModel, keyword, isOnlyEnding = false } = props;
const { user, env } = useContext(AppContext);
const [ loading, setLoading ] = useState(false);
const [ treeData, setTreeData ] = useState(null);
const [ item, setItem ] = useState(null);
const [ prevItem, setPrevItem ] = useState(null);
const [ visible, setVisible ] = useState(false);
const [ type, setType ] = useState(null);
const [ rootId, setRootId ] = useState('');
const [ expandedKeys, setExpandedKeys ] = useState([]);
const [ autoExpandParent, setAutoExpandParent ] = useState(false);
const [ viewSelectedKey, setViewSelectedKey ] = useState(viewModes[0].key);
const [ isSetRootId, setIsSetRootId ] = useState(true);
const [ domains, setDomains ] = useState([]);
const [ domainSelectedKey, setDomainSelectedKey ] = useState('');
const [ currentRightClickDir, setCurrentRightClickDir ] = useState({});
const [ searchKeyword, setSearchKeyword ] = useState('');
const [ dataList, setDataList ] = useState([]);
const [options, setOptions] = useState([]);
const [modal, contextHolder] = Modal.useModal();
const timestamp = getQueryParam(AnchorTimestamp, props.location?.search||'');
const id = getQueryParam(AnchorId, props.location?.search||'');
const did = getQueryParam(AnchorDirId, props.location?.search||'');
// useEffect(() => {
// getShowSyncAndDomains();
// //eslint-disable-next-line react-hooks/exhaustive-deps
// }, [])
useEffect(() => {
if (refrence === 'recatalog') {
getDirTreeData();
} else {
setViewSelectedKey(viewModes[0].key);
onViewChange && onViewChange(viewModes[0].key);
if ((id||'') !== '') {
getDataModelLocationThenGetDirTreeData();
} else if ((did||'') !== '') {
getDirTreeData(did);
} else {
getDirTreeData();
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [timestamp])
useEffect(() => {
if (keyword!=='') {
if (item && !prevItem) {
setPrevItem(item);
}
setItem(null);
} else {
if (prevItem && !item) {
setItem(prevItem);
onSelect && onSelect(prevItem?.key||'');
setPrevItem(null);
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ keyword ])
// const getShowSyncAndDomains = () => {
// dispatch({
// type: 'datamodel.isSetRootDomainId',
// callback: data => {
// if (data === 'false') {
// dispatch({
// type: 'user.getDomains',
// callback: _data => {
// setDomains(_data||[]);
// setIsSetRootId(false);
// }
// });
// } else {
// setIsSetRootId(true);
// }
// }
// });
// }
const getDataModelLocationThenGetDirTreeData = () => {
setLoading(true);
dispatch({
type: 'pds.getDataServiceLocation',
payload: {
pdsDataServiceId: id,
namespace: `${env?.domainId}`
},
callback: data => {
getDirTreeData(data.pdsDataServiceCatalogId||'', data.offset);
},
error: () => {
setLoading(false);
getDirTreeData();
}
});
}
const getDirTreeData = (defaultSelectedId='', offset=null, type='refresh') => {
setLoading(true);
dispatch({
type: (type==='refresh')?'pds.refreshCatalog':'pds.loadDataServiceCatalog',
callback: data => {
data.key = data.id||'';
data.title = data.name||'';
data.children = data.subCatalogs||[];
let defaultItem = null;
function recursion(subCatalogs) {
if ((subCatalogs||[]).length===0) return;
(subCatalogs||[]).forEach(catalog=> {
catalog.key = catalog.id||'';
catalog.title = catalog.name||'';
catalog.children = catalog.subCatalogs||[];
if (catalog.id === defaultSelectedId) {
defaultItem = catalog;
}
recursion(catalog.subCatalogs);
})
}
recursion(data.subCatalogs);
setLoading(false);
setTreeData(data.subCatalogs||[]);
setRootId(data.id||'');
const _dataList = [];
generateList(data.subCatalogs||[], _dataList);
setDataList(_dataList);
if (defaultItem) {
const expandedKeys = _dataList
.map(item => {
if (item.key.indexOf(defaultSelectedId) > -1) {
return getParentKey(item.key, data.subCatalogs||[]);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys([...expandedKeys, defaultSelectedId]);
setAutoExpandParent(true);
setItem(defaultItem);
onSelect && onSelect(defaultItem.key||'', offset);
} else if (refrence === '') {
const currentItem = (data.subCatalogs||[]).length>0?data.subCatalogs[0]: null;
setItem(currentItem);
if (currentItem && currentItem.key) {
setExpandedKeys([currentItem?.key]);
}
onSelect && onSelect(currentItem?(currentItem.key||''):'');
}
},
error: () => {
setLoading(false);
}
})
}
const getStateTreeData = () => {
setLoading(true);
dispatch({
type: 'pds.loadStateCatalog',
payload: {
isOnlyEnding
},
callback: data => {
setLoading(false);
let _treeData = data?.subCatalogs||[];
_treeData.forEach(item => {
item.title = item.cnName;
item.key = item.id;
})
setTreeData(_treeData);
setItem(_treeData.length>0?_treeData[0]:{});
onSelect && onSelect(_treeData.length>0?_treeData[0].key:'');
},
error: () => {
setLoading(false);
}
});
}
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 getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.id === key)) {
parentKey = node.id;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onViewClick = ({ key }) => {
if (viewSelectedKey && viewSelectedKey===key ) return;
setViewSelectedKey(key);
setSearchKeyword('');
onViewChange && onViewChange(key);
if (key === 'dir') {
getDirTreeData();
} else {
getStateTreeData();
}
}
// const onSyncMenuClick = ({ key }) => {
// setDomainSelectedKey(key);
// dispatch({
// type: 'datamodel.setRootDomainId',
// payload: {
// params: {
// domainId: key
// }
// },
// callback: () => {
// setIsSetRootId(true);
// getDirTreeData('', null, 'load');
// }
// });
// }
const onTreeSelect = (keys,data) => {
if ((keys||[]).length === 0) {
return;
}
const _item = {...data.selectedNodes[0]||[]};
setItem(_item);
onSelect && onSelect(_item.key);
}
const add = () => {
setVisible(true);
setType('add');
}
const update = () => {
setVisible(true);
setType('update');
}
const refresh = () => {
if (viewSelectedKey==='dir') {
getDirTreeData(item?.key||'');
} else {
getStateTreeData(item?.key||'');
}
}
const sync = () => {
getDirTreeData(item?.key||'', null, 'load');
}
const moveNode = (steps) => {
setLoading(true);
dispatch({
type: 'pds.upDownCatalog',
payload: {
pdsDataServiceCatalogId: currentRightClickDir.id,
steps
},
callback: () => {
showMessage('success', (steps===-1)?'上移目录成功':'下移目录成功');
getDirTreeData(item.id);
},
error: () => {
setLoading(false);
}
});
}
const deleteNode = () => {
modal.confirm({
title: '提示!',
content: '删除目录会删除相关的服务,您确定删除吗?',
onOk: () => {
setLoading(true);
dispatch({
type: 'pds.deleteCatalog',
payload: {
params: {
pdsDataServiceCatalogId: currentRightClickDir.id
}
},
callback: () => {
showMessage('success', '删除目录成功');
if (item && currentRightClickDir && item.id===currentRightClickDir.id) {
getDirTreeData();
} else {
getDirTreeData(item.id);
}
},
error: () => {
setLoading(false);
}
});
}
});
}
const onUpdateTreeItemModalOk = (id, updateItem) => {
setVisible(false);
getDirTreeData(id);
}
const onUpdateTreeItemModalCancel = () => {
setVisible(false);
}
const treeDirectoryChanged = (did) => {
let defaultItem = null;
function recursion(subCatalogs) {
if ((subCatalogs||[]).length===0) return;
(subCatalogs||[]).forEach(catalog=> {
catalog.key = catalog.id||'';
catalog.title = catalog.name||'';
catalog.children = catalog.subCatalogs||[];
if (catalog.id === did) {
defaultItem = catalog;
}
recursion(catalog.subCatalogs);
})
}
recursion(treeData||[]);
if (defaultItem) {
const expandedKeys = dataList
.map(item => {
if (item.key.indexOf(did) > -1) {
return getParentKey(item.key, treeData||[]);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys([...expandedKeys, did]);
setAutoExpandParent(true);
setItem(defaultItem);
onSelect && onSelect(defaultItem.key||'');
}
}
const onAutoCompleteSearch = (searchText) => {
setOptions(
!searchText ? [] : (dataList||[]).filter(item => item.title.indexOf(searchText)!==-1),
);
};
const onAutoCompleteChange = (value) => {
setSearchKeyword(value);
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/');
setSearchKeyword(paths[paths.length-1]);
treeDirectoryChanged(option.key);
};
const displayMenu = (e) => {
show({
event: e,
position: {
x: e.clientX + 30,
y: e.clientY - 10
}
});
}
const exportMenu = (
<Menu selectedKeys={[viewSelectedKey]} onClick={onViewClick}>
{
viewModes && viewModes.map(item => {
return (
<Menu.Item key={item.key} value={item.key} >
<div style={{ textAlign: 'center' }}>
{item.name}
</div>
</Menu.Item>
)
})
}
</Menu>
);
// const syncMenu = (
// <Menu selectedKeys={[domainSelectedKey]} onClick={onSyncMenuClick}>
// {
// domains && domains.map(domain => {
// return (
// <Menu.Item key={domain.domainId} value={domain.domainId} >{domain.domainName}</Menu.Item>
// )
// })
// }
// </Menu>
// );
const classes = classnames('model-tree', {
'model-tree-recatalog': (refrence === 'recatalog')
});
return (
<div className={classes}>
{
(refrence==='') && <div
className='p-3'
style={{
display: 'flex',
borderBottom: "1px solid #EFEFEF",
height: 57,
alignItems: 'center',
// justifyContent: (viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin)?'space-between':'',
}}
>
<Dropdown overlay={exportMenu} placement="bottomLeft">
<Tooltip title="视角">
<UnorderedListOutlined className='default' style={{ fontSize:16,cursor:'pointer' }} />
</Tooltip>
</Dropdown>
{
// (viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin && !isOnlyEnding) && (
// <Tooltip title="新增目录" className='ml-6'>
// <PlusOutlined className='default' onClick={add} style={{ fontSize:16,cursor:'pointer' }} />
// </Tooltip>
// )
}
{/* {
(viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin) && (
<Tooltip title="存量模型导入" className='ml-2'>
<ImportOutlined className='default' onClick={() => { importStockModel && importStockModel() }} style={{ fontSize:16,cursor:'pointer' }} />
</Tooltip>
)
} */}
<Tooltip title="刷新目录" className='ml-6'>
<Button type='text' icon={<ReloadOutlined className='default' />} size='small' onClick={refresh} />
</Tooltip>
{/* {
(viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin) && !isSetRootId && (
<Dropdown overlay={syncMenu} placement="bottomLeft">
<Tooltip title="同步目录">
<SwapOutlined className='default ml-2' style={{ fontSize:16,cursor:'pointer', transform: 'rotate(90deg)', }} />
</Tooltip>
</Dropdown>
)
} */}
{
(viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin) && isSetRootId && (
<Tooltip title="同步目录" className='ml-6'>
<Button type='text' icon={<SwapOutlined className='default' style={{ transform: 'rotate(90deg)' }} />} size='small' onClick={sync} />
</Tooltip>
)
}
</div>
}
<div className='p-3'>
<Spin spinning={loading} >
{
(viewSelectedKey==='dir') && <AutoComplete
allowClear
value={searchKeyword}
style={{ marginBottom: 10, width: '100%' }}
placeholder='搜索目录'
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onChange={onAutoCompleteChange}
onClear={() => { setSearchKeyword(''); }}
>
{
(options||[]).map((item, index) => {
return (
<Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.title, [searchKeyword])}
</div>
</Option>
);
})
}
</AutoComplete>
}
<Tree
className='tree-contextmenu'
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
showLine
showIcon={false}
onSelect={onTreeSelect}
treeData={treeData}
selectedKeys={[item?item.key:'']}
titleRender={(nodeData) => {
return <span title={nodeData?.remark||''} className='cursor-pointer-contextmenu'>{(nodeData.status===-1)?`${nodeData?.name}_已下线`:nodeData?.name||''}</span>;
}}
onRightClick={({event, node}) => {
if (viewSelectedKey==='dir') {
setCurrentRightClickDir(node);
displayMenu(event);
}
}}
/>
</Spin>
</div>
<UpdateTreeItemModal
visible={visible}
type={type}
item={(type==='add')?item:currentRightClickDir}
rootId={rootId}
onOk={onUpdateTreeItemModalOk}
onCancel={onUpdateTreeItemModalCancel}
/>
{
(refrence!=='recatalog') && <RcMenu id={MENU_ID}>
<RcItem id="edit" onClick={update}>
修改目录
</RcItem>
<RcItem id="up" onClick={() => { moveNode(-1); }}>
上移目录
</RcItem>
<RcItem id="down" onClick={() => { moveNode(1); }}>
下移目录
</RcItem>
<RcItem id="delete" onClick={deleteNode}>
删除目录
</RcItem>
</RcMenu>
}
{contextHolder}
</div>
);
}
export default ModelTree;
\ 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: 500px !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Modal, Button, Form, Input } from 'antd';
import { dispatch } from '../../../../model';
const FC = (props) => {
const {visible, service, onCancel} = props;
const [confirmLoading, setConfirmLoading] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
form?.setFieldsValue({name: service?.odata});
}
}, [visible])
const onOk = async() => {
try {
const row = await form.validateFields();
setConfirmLoading(true);
dispatch({
type: 'pds.enableOData',
payload: {
params: {
pdsDataServiceId: service?.id,
name: row.name,
}
},
callback: data => {
setConfirmLoading(false);
cancel(true);
},
error: () => {
setConfirmLoading(false);
}
});
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const reset = () => {
form.resetFields();
setConfirmLoading(false);
}
const cancel = (refresh = false) => {
reset();
onCancel?.(refresh);
}
const footer = [
<Button
key="0"
onClick={() => {cancel()}}
>
取消
</Button>,
<Button
key="1"
type="primary"
loading={confirmLoading}
onClick={onOk}
>
确定
</Button>,
];
return (
<Modal
forceRender
visible={visible}
title={`启用${service?.name}的OData`}
width={540}
onCancel={() => {cancel()}}
footer={footer}
>
<Form form={form}>
<Form.Item
name='name'
label='URI'
rules={[
{
required: true,
message: '请输入URI',
},
]}
>
<Input placeholder='请输入URI' />
</Form.Item>
</Form>
</Modal>
)
}
export default FC;
\ 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 { showMessage, showNotifaction } from '../../../../util';
const FC = (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: 'pds.offline',
payload: {
params: {
reason: values?.desc
},
data: ids
},
callback: () => {
reset();
showMessage('success', '下线成功')
onCancel && onCancel(true);
},
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 FC;
\ 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: 'pds.recatalogService',
payload: {
params: {
pdsDataServiceCatalogId: catalogId,
pdsDataServiceIds: ids.join(',')
},
},
callback: message => {
setConfirmLoading(false);
if ((message||'')!=='' && (message||'')!=='ok') {
showNotifaction('提示', message, 5);
}
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()
}}
onOk={ onOk }
>
<ModelTree
refrence='recatalog'
onSelect={onSelect}
/>
</Modal>
}
</div>
)
}
export default RecatalogModal;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Modal, Button, Form, Input, Table, Tooltip, Typography } from 'antd';
import { dispatch } from '../../../../model';
const FC = (props) => {
const {visible, service, onCancel} = props;
const [loading, setLoading] = useState(false);
const [data, setData] = useState(undefined);
const [cols, setCols] = useState([]);
useEffect(() => {
if (visible) {
getSample();
}
}, [visible])
const getSample = () => {
setLoading(true)
dispatch({
type: 'pds.getSample',
payload: {
params: {
pdsDataServiceId: service?.id,
}
},
callback: data => {
setLoading(false);
setData(data?.content||[]);
const newCols = [];
data?.headers?.forEach(item => {
if (item) {
newCols.push({
title: item,
dataIndex: item,
width: 150,
ellipsis: true,
render: (value, record) => <Tooltip title={value}>
<Typography.Text ellipsis={true}>{value}</Typography.Text>
</Tooltip>
});
}
});
setCols(newCols);
},
error: () => {
setLoading(false);
}
});
}
const cancel = () => {
setLoading(false);
setData(undefined);
onCancel?.();
}
const footer = [
<Button
key="0"
onClick={() => {cancel()}}
>
取消
</Button>,
];
return (
<Modal
forceRender
visible={visible}
title={`${service?.name}的样本数据`}
width={650}
onCancel={() => {cancel()}}
footer={footer}
>
<Table
loading={loading}
columns={cols}
dataSource={data||[]}
size='small'
scroll={{ x: 600, y: 300 }}
pagination={false}
/>
</Modal>
)
}
export default FC;
\ No newline at end of file
import React, { useState, useMemo, useEffect } from "react"
import {Select} from "antd"
import debounce from 'lodash/debounce';
const {Option} = Select
const FC = ({ value, onChange, data, ...restProps }) => {
const [searchValue, setSearchValue] = useState(undefined)
const debounceFetcher = useMemo(() => {
return debounce((value) => {
setSearchValue(value);
}, 800);
}, []);
const filterData = useMemo(() => {
if (searchValue) {
return data?.filter(item => item.includes(searchValue));
}
return data||[];
}, [data, searchValue])
const change = (value) => {
onChange?.(value)
}
return (
<Select
style={{width:'100%'}}
showSearch
value={value}
onChange={change}
allowClear
filterOption={false}
onSearch={debounceFetcher}
{...restProps}
>
{
filterData?.map((item, index)=>{
return(
<Option value={item} key={index}>{item}</Option>
)
})
}
</Select>
)
}
export default FC
\ No newline at end of file
import React, { useState, useMemo, useEffect } from "react"
import {Select} from "antd"
import debounce from 'lodash/debounce';
const {Option} = Select
const SelectUser:React.FC=(props)=>{
const {value,onChange,users,type,loading} = props
const [searchValue, setSearchValue] = useState(undefined)
const debounceFetcher = useMemo(() => {
return debounce((value) => {
setSearchValue(value);
}, 800);
}, []);
const filterUsers = useMemo(() => {
if (searchValue) {
return users?.filter(item => item.nachn?.includes(searchValue) || item.pernr?.includes(searchValue));
}
return users||[];
}, [users, searchValue])
const change=(value)=>{
onChange&&onChange(value)
}
if(type==='edit'){
return(
<Select
style={{width:'100%'}}
showSearch
value={value}
onChange={change}
loading={loading}
allowClear
filterOption={false}
onSearch={debounceFetcher}
>
{
filterUsers?.map((item)=>{
return(
<Option value={item.pernr} key={item.pernr}>{`${item.nachn}(${item.pernr})`}</Option>
)
})
}
</Select>
)
}else if(type==='detail'){
try {
const user = users?.filter((item)=>(item.pernr===value))
return `${user[0].nachn}(${user[0].pernr})`;
} catch (error) {
return value
}
}else{
return null
}
}
export default SelectUser
\ No newline at end of file
import React, { useState, useMemo, useEffect } from 'react'
import { Button, Modal, Spin, Popover, Table, Descriptions, Tooltip } from 'antd'
import { dispatch } from '../../../../model'
import { highlightSearchContentByTerms } from '../../../../util'
const FC = (props) => {
const { id, terms } = props
const [data, setData] = useState()
const [loading, setLoading] = useState(false)
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
fixed: 'left',
render: (text, record, index) => {
return (index+1).toString();
}
},
{
title: '中文名称',
width: 200,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
require: true,
fixed: 'left',
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>
{highlightSearchContentByTerms(text, terms)}
</span>
</Tooltip>
)
}
},
{
title: '英文名称',
width: 200,
dataIndex: 'name',
editable: true,
ellipsis: true,
require: true,
render: (text, record, index) => {
return (
<Tooltip title={text||''}>
<span style={{ fontWeight: 'bold' }} >{highlightSearchContentByTerms(text, terms)}</span>
</Tooltip>
)
}
},
{
title: '类型',
width: 150,
dataIndex: 'datatype',
editable: true,
ellipsis: true,
require: true,
render: (_, record, __) => {
if (record?.datatype) {
if ((record?.datatype?.name==='Char'||record?.datatype?.name==='Varchar') && record?.datatype?.parameterValues?.length>0) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)})`;
} else if ((record?.datatype?.name==='Decimal'||record?.datatype?.name==='Numeric') && record?.datatype?.parameterValues?.length>1) {
return `${record?.datatype?.name||''}(${(record?.datatype?.parameterValues[0]?record.datatype.parameterValues[0]:0)},${(record?.datatype?.parameterValues[1]?record.datatype.parameterValues[1]:0)})`;
}
return record.datatype.name||'';
}
return '';
}
},
]
useEffect(() => {
getService()
}, [])
const getService = () => {
setLoading(true)
dispatch({
type: 'pds.getDataService',
payload: {
id
},
callback: (data) => {
setLoading(false)
setData(data||[])
},
error: () => {
setLoading(false)
}
})
}
return (
<Spin spinning={loading}>
<h3 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h3>
<Descriptions className='mt-3' column={3}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>中文名称</div>} >{highlightSearchContentByTerms(data?.cnName||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>英文名称</div>}>{highlightSearchContentByTerms(data?.name||'', terms)}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>数据内容</div>}>{highlightSearchContentByTerms(data?.remark||'', terms)}</Descriptions.Item>
</Descriptions>
<h3 className='mr-3' style={{ marginBottom: 0 }}>字段列表</h3>
<Table
className='mt-3'
dataSource={data?.pdsdataServiceAttributes||[]}
columns={columns}
size='small'
rowKey='iid'
pagination={false}
sticky
/>
</Spin>
)
}
export default FC
import React, { useState, useMemo, useEffect } from 'react'
import { Button, Modal, Spin, Popover, Table, Descriptions, Tooltip } from 'antd'
import { dispatch } from '../../../../model'
import { highlightSearchContentByTerms } from '../../../../util'
import ServiceDetail from './ServiceDetail'
const FC = (props) => {
const { visible, id, onClose, terms } = props
const close = () => {
onClose?.()
}
return (
<Modal
width={1200}
visible={visible}
destroyOnClose
title='服务详情'
bodyStyle={{ minHeight: 300 }}
footer={<>
<Button onClick={close}>取消</Button>
</>}
onCancel={close}
>
{ visible && <ServiceDetail id={id} terms={terms} /> }
</Modal>
)
}
export default FC
import React, { useState } from 'react';
import { Modal, Form, Input } from 'antd';
import LocalStorage from 'local-storage';
import { dispatchLatest } from '../../../../model';
import { showNotifaction, showMessage } from '../../../../util';
const FC = (props) => {
const { visible, onCancel, services } = 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: 'pds.authorize',
payload: {
params: {
reason: values?.desc
},
data: services
},
callback: () => {
reset();
showMessage('success', '授权成功!');
onCancel && onCancel(true);
},
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 FC;
\ 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 } from 'react';
import { Modal, Form, Input } from 'antd';
import LocalStorage from 'local-storage';
import { dispatchLatest } from '../../../../model';
import { showMessage, showNotifaction } from '../../../../util';
const FC = (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: 'pds.release',
payload: {
params: {
reason: values?.desc
},
data: ids
},
callback: () => {
reset();
showMessage('success', '发布成功!')
onCancel && onCancel(true);
},
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 FC;
\ No newline at end of file
import React, { useState } from 'react';
import { Table, Tooltip } from 'antd';
import { Resizable } from 'react-resizable';
import ResizeObserver from 'rc-resize-observer';
import { isSzseEnv } from '../../../../util';
import { dispatch } from '../../../../model';
import './SuggestTable.less';
const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={(e) => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th
onClick={onClick}
{...restProps}
/>
</Resizable>
);
};
const SourceComponent = (props) => {
const { data, onClick, name } = props;
const moreSourceComponent = <div style={{ maxWidth: 400, maxHeight: 300, overflow: 'auto' }}>
{
(data||[]).map((source, index) => {
return (
<div
className='pointer'
key={index}
style={{
textDecoration: 'underline',
}}
onClick={(e) => {
e.stopPropagation();
onClick && onClick(source.sourceId, name);
}}
>
{source.sourcePath||''}
</div>
);
})
}
</div>;
return (
<Tooltip
title={moreSourceComponent}
overlayClassName='tooltip-common'
>
<a
href='#'
onClick={(e) => {
e.stopPropagation();
onClick && onClick(data[0].sourceId, name);
}}
>
{
(data||[]).length>0 && <span>{data[0].sourcePath||''}</span>
}
</a>
</Tooltip>
);
}
const SuggestTable = (props) => {
const { suggests, onSelect } = props;
const [ tableWidth, setTableWidth ] = useState(0);
const cols = [
{
title: '中文名称',
dataIndex: 'cnName',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '英文名称',
dataIndex: 'name',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '业务含义',
dataIndex: 'remark',
width: isSzseEnv?360:160,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '匹配度',
dataIndex: 'score',
width: 100,
render: (_, record, index) => {
return (
<React.Fragment>
<span style={{ color: '#f50' }}>{`${record.recommendedStats?.score}%`}</span>
{ index===0 && <span style={{ color: '#f50' }}> 推荐</span> }
</React.Fragment>
);
}
},
{
title: '使用次数',
dataIndex: 'referencesCount',
width: 80,
ellipsis: true,
render: (_, record) => {
return (
<span>{record.recommendedStats?.referencesCount}</span>
);
}
},
{
title: '来源',
dataIndex: 'source',
ellipsis: true,
render: (_, record) => {
return (
<SourceComponent data={record.recommendedStats?.sourceInfos||[]} name={record.name||''} onClick={sourceOnClick} />
);
}
},
];
const [ columns, setColumns ] = useState(cols);
const sourceOnClick = (id, name) => {
const timestamp = new Date().getTime();
const tempArray = id.split('=');
if (tempArray.length>=3) {
dispatch({
type: 'datamodel.getParent',
payload: {
id
},
callback: data => {
window.open(`/center-home/metadetail?mid=${encodeURIComponent(data._id)}&action=metadetail&type=detail&manager=false&activekey=1&name=${encodeURIComponent(name||'')}`);
}
})
} else {
window.open(`/center-home/menu/datastandard?id=${id}&timestamp=${timestamp}`);
}
}
const onTableSelect = (record, selected, selectedRows, nativeEvent) => {
onSelect && onSelect(record);
}
const handleResize = index => (e, { size }) => {
const nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
setColumns(nextColumns);
};
const mergedColumns = () => {
return (
columns.map((column, index) => {
return {
...column,
onHeaderCell: column => ({
width: column.width,
onResize: handleResize(index),
}),
};
})
);
}
const rowSelection = {
type: 'radio',
onSelect: onTableSelect,
};
return (
<div className='suggest-table'>
<ResizeObserver
onResize={({ width }) => {
if (tableWidth !== width) {
setTableWidth(width);
let newColumns = [...cols];
newColumns.forEach((column, index) => {
if (!column.width) {
const rowWidth = (cols.reduce((preVal, col) => (col.width?col.width:0) + preVal, 0)) + 50;
if (width > rowWidth) {
column.width = (width-rowWidth)>200?(width-rowWidth):200;
} else {
column.width = 200;
}
}
});
setColumns(newColumns);
}
}}
>
<Table
rowSelection={rowSelection}
dataSource={suggests||[]}
pagination={false}
loading={false}
rowKey='iid'
rowClassName={(record, index) => {
return 'pointer';
}}
components={{
header: {
cell: ResizeableHeaderCell,
}
}}
columns={mergedColumns()}
onRow={(record, index) => {
return {
onClick: (e) => {
onSelect && onSelect(record);
}
}
}}
/>
</ResizeObserver>
</div>
);
}
export default SuggestTable;
\ No newline at end of file
.suggest-table {
.yy-table {
margin: 0 !important;
max-height: 300px !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React, { 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="code"
>
<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: 'code', errors: [] }]);
if (type === 'add') {
form.setFieldsValue({ action: _action, name: '', remark: '', code: '' });
} else {
form.setFieldsValue({ action: '', name: item?item.name:'', remark: item?item.remark:'', code: item?.code });
}
}
//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||'',
code: values.code||'',
parentId: rootId
};
} else if (type === 'add') {
payload = {
name: values.name||'',
remark: values.remark||'',
code: values.code||'',
parentId: item.id
};
} else {
payload = {
...item,
name: values.name||'',
remark: values.remark||'',
code: values.code||'',
}
}
dispatchLatest({
type: 'pds.saveCatalog',
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 } from 'antd';
import { dispatch, dispatchLatest } from '../../../../model';
import { formatVersionDate } from '../../../../util';
import VersionCompareHeader from './VersionCompareHeader';
import VersionCompareTable from './VersionCompareTable';
import VersionCompareIndex from './VersionCompareIndex';
import FilterColumnAction from './FilterColumnAction';
import './VersionCompare.less';
const { Text, Paragraph } = Typography;
const { Option } = Select;
const defaultColumnTitles = ['序号', '中文名称', '英文名称', '类型', '业务含义'];
const VersionCompare = (props) => {
const { id } = 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 [ onlyShowChange, setOnlyShowChange ] = useState(true);
const [ attrFilterColumns, setAttrFilterColumns ] = useState([]);
const [ attrSelectedTitles, setAttrSelectedTitles ] = useState(defaultColumnTitles);
const attrColumnsRef = useRef([]);
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);
const newData = [];
(data||[]).forEach((item, index) => {
let name = item.name||'';
name = name + '_' + formatVersionDate(item.ts);
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('');
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);
const newAttrOptionColumns = [];
(data?.heads?.columnHead||[]).forEach((item, index) => {
newAttrOptionColumns.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: (item==='序号')?60: 150,
ellipsis: true,
option: true,
});
})
const newAttrColumns = [...newAttrOptionColumns, {
title: <FilterColumnAction columns={newAttrOptionColumns} defaultSelectedKeys={defaultColumnTitles} onChange={onFilterChange} />,
dataIndex: 'columnFilter',
render: (_, record, index) => {
return '';
},
width: 40,
ellipsis: true,
option: false
}];
attrColumnsRef.current = newAttrColumns;
const newFilterColumns = newAttrColumns.filter(column => column.option===false || attrSelectedTitles.indexOf(column.title) !== -1);
setAttrFilterColumns(newFilterColumns);
},
error: () => {
setLoadingCompare(false);
}
})
}
const onFilterChange = (values) => {
const newFilterColumns = attrColumnsRef.current.filter(column => column.option===false || values.indexOf(column.title) !== -1);
setAttrSelectedTitles(values);
setAttrFilterColumns(newFilterColumns);
}
const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked);
if (basicVersion!=='' && incVersion!=='') {
getCompare(basicVersion, incVersion, e.target.checked);
}
}
return (
<div className='model-version-compare'>
<Form layout='inline'>
<Form.Item label='基线版本'>
<Select loading={loading} value={basicVersion} style={{ width: 300 }} 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>
<Form.Item label='增量版本'>
<Select value={incVersion} style={{ width: 300 }} disabled={basicVersion===''} onChange={onIncChange}>
{
(incVersions||[]).map((version, index) => {
return (
<Option key={index} value={version.id||''}>{version.name||''}</Option>
);
})
}
</Select>
</Form.Item>
<Form.Item>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>仅显示差异</Checkbox>
</Form.Item>
</Form>
<div className='py-5'>
<Spin spinning={loadingCompare} >
{
compareData && <div className='flex'>
<div style={{ flex: 1, borderRight: '1px solid #EFEFEF', paddingRight: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={compareData} />
<VersionCompareTable data={compareData} columns={attrFilterColumns} />
<VersionCompareIndex data={compareData} />
</div>
<div style={{ flex: 1, paddingLeft: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={compareData} direction='right' />
<VersionCompareTable data={compareData} columns={attrFilterColumns} direction='right' />
<VersionCompareIndex data={compareData} direction='right'/>
</div>
</div>
}
</Spin>
</div>
</div>
);
}
export default VersionCompare;
\ No newline at end of file
.model-version-compare {
.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 } 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;
import { useEffect, useState } from 'react';
import { Typography, Tooltip, Table } 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;
\ No newline at end of file
import { useEffect, useState } from 'react';
import { Typography, Table } from 'antd';
const { Title } = Typography;
const VersionCompareTable = (props) => {
const { data, direction = 'left', columns } = props;
const [ tableData, setTableData ] = useState([]);
useEffect(() => {
const newTableData = [];
let columnValue = [];
if (direction==='left') {
columnValue = data?.left?.columnValue||[];
} else if (direction==='right') {
columnValue = data?.right?.columnValue||[];
}
(columnValue||[]).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 VersionCompareTable;
\ 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: 'pds.getVersions',
payload: {
params: {
id
}
},
callback: data => {
setLoading(false);
setVersions(data||[]);
},
error: () => {
setLoading(false);
}
})
}
const onVersionItemClick = (version) => {
// window.open(`/data-govern/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 { Button, Space, Spin, Input, Select, Tooltip, Dropdown, Menu, Modal } from 'antd';
import copy from "copy-to-clipboard";
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import { ResizableBox } from 'react-resizable';
import classNames from 'classnames';
import ModelTree from './Component/ModelTree';
import ModelTable from './Component/ModelTable';
import ImportModal from './Component/ImportModal';
import ImportStockWordDrawer from './Component/ImportStockWordDrawer';
import ExportDDLModal from './Component/ExportDDLModal';
import ExportOtherModal from './Component/ExportOtherModal';
import RecatalogModal from './Component/RecatalogModal';
import HistoryAndVersionDrawer from './Component/HistoryAndVersionDrawer';
import StartFlowModal from './Component/StartFlowModal';
import JDBCInformation from './Component/JDBCInformation';
import { showMessage, showNotifaction, inputWidth, DeleteTipModal, getDataModelerRole } from '../../../util';
import { dispatch, dispatchLatestHomepage } from '../../../model';
import { Action, CatalogId, ModelerId, Hints, ModelerData, PermitCheckOut, Editable, StateId, Holder, DDL, DataModelerRoleReader, ReadOnly } from '../../../util/constant';
import { AppContext } from '../../../App';
import DebounceInput from './Component/DebounceInput';
import ColSettingModal from './Component/ColSettingModal';
import StartAuthorize from './Component/StartAuthorize';
import StartRelease from './Component/StartRelease';
import Offline from './Component/Offline';
import './index.less';
const { Option } = Select;
const InputDebounce = DebounceInput(300)(Input);
class Model extends React.Component {
constructor(props) {
super(props);
this.state = {
importModalVisible: false,
importStockWordDrawerVisible: false,
exportDDLModalVisible: false,
exportOtherModalVisible: false,
recatalogModalVisible: false,
historyAndVersionDrawerVisible: false,
startFlowModalVisible: false,
catalogId: '',
importModalAction: '',
tableData: [],
filterTableData: [],
loadingTableData: false,
selectModelerIds: [],
selectModelerNames: [],
keyword: '',
hints: [],
loadingStates: false,
modelStates: [],
currentModelState: props.isOnlyEnding?'4_0':'',
currentODataState: '0',
currentView: '',
exportDDLModalReference: 'exportDDL',
currentModel: {},
offset: null,
expandTree: true,
showDeleteTip: false,
colSettingModalVisible: false,
visibleColNames: [],
startAuthorizeParams: {
visible: false,
services: []
},
startReleaseVisible: false,
offlineVisible: false,
jdbcInformationVisible: false,
}
}
componentDidMount() {
this.getModelStates();
this.getPreference();
window?.addEventListener("storage", this.modelEventChange);
}
componentWillUnmount() {
window?.removeEventListener("storage", this.modelEventChange);
}
modelEventChange = (e) => {
if (e.key === 'modelChange') {
this.onTableChange();
}
}
getModelStates = () => {
this.setState({ loadingStates: true }, () => {
dispatch({
type: 'pds.loadStateCatalog',
payload: {
isOnlyEnding: this.props.isOnlyEnding
},
callback: data => {
this.setState({
loadingStates: false,
modelStates: this.props.isOnlyEnding ? data?.subCatalogs||[] : [{ name: 'all', id: '', cnName: '所有状态' }, ...(data?.subCatalogs||[])]
});
},
error: () => {
this.setState({ loadingStates: false });
}
});
})
}
getPreference = () => {
dispatch({
type: 'pds.getCols',
payload: {
modelName: 'DataService'
},
callback: data => {
this.setState({visibleColNames: data?.map(item => item.titleCnName)});
}
})
}
onViewChange = (value) => {
this.setState({ currentView: value });
}
onModelStateChange = (value) => {
this.setState({ currentModelState: value, offset: null }, () => {
this.onTableChange();
})
}
onODataStateChange = (value) => {
this.setState({ currentODataState: value, offset: null }, () => {
this.onTableChange();
})
}
onTreeSelect = (key, offset=null) => {
this.setState({ catalogId: key, keyword: '', offset, currentModelState: this.state.currentModelState }, () => {
if (!key || key==='') {
this.setState({ tableData: [], filterTableData: [] });
} else {
this.onTableChange();
}
});
}
onTableChange = () => {
const { currentView, catalogId, keyword, currentModelState, currentODataState } = this.state;
this.setState({ loadingTableData: true }, () => {
if (keyword === '') {
if (currentView === 'dir') {
const params = {
pdsDataServiceCatalogId: catalogId,
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'pds.getServices',
payload: params,
callback: data => {
const filterData = data.pdsdataServices?.filter(service => {
if (currentODataState === '0') return true;
if (currentODataState === '1') return service.supportODataDisable;
if (currentODataState === '2') return !service.supportODataDisable;
return false;
})
this.setState({ loadingTableData: false, tableData: data.pdsdataServices||[], filterTableData: filterData });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
} else {
dispatchLatestHomepage({
type: 'pds.getStateServices',
payload: {
pdsDataServiceStateCatalogId: catalogId,
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
},
callback: data => {
const filterData = data.pdsdataServices?.filter(service => {
if (currentODataState === '0') return true;
if (currentODataState === '1') return service.supportODataDisable;
if (currentODataState === '2') return !service.supportODataDisable;
return false;
})
this.setState({ loadingTableData: false, tableData: data.pdsdataServices||[], filterTableData: filterData });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
}
} else {
const params = {
term: keyword,
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'pds.searchService',
payload: params,
callback: data => {
const filterData = data?.filter(service => {
if (currentODataState === '0') return true;
if (currentODataState === '1') return service.supportODataDisable;
if (currentODataState === '2') return !service.supportODataDisable;
return false;
})
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: filterData });
},
error: () => {
this.setState({ loadingTableData: false });
}
})
}
})
}
onTableSelect = (ids) => {
this.setState({ selectModelerIds: ids });
}
onTableItemAction = (record, action, readOnly=false) => {
this.setState({ importModalAction: action, modelerId: record.id }, () => {
const { catalogId, importModalAction, modelerId } = this.state;
window.open(`/data-govern/data-model-action?${Action}=${importModalAction}&${CatalogId}=${catalogId}&${ModelerId}=${modelerId}&${PermitCheckOut}=${record.permitCheckOut||false}&${Editable}=${record.editable||false}&${StateId}=${record.state?.id||''}&${Holder}=${record.holder||''}&${ReadOnly}=${readOnly}`);
});
}
onHistory = (id) => {
this.setState({ historyAndVersionDrawerVisible: true, modelerId: id });
}
onSearchInputChange = (value) => {
this.setState({ keyword: value||'', catalogId: '' }, () => {
if (value !== '') {
this.onTableChange();
}
});
}
onImportUnconditionBtnClick = () => {
const { catalogId, currentView } = this.state;
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(currentView==='dir')?(catalogId||''):''}`);
}
onExportDDLBtnClick = () => {
const { selectModelerIds, tableData } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
//服务名称在导出ddl的时候有使用
const _selectModelerNames = [];
(selectModelerIds||[]).forEach(id => {
(tableData||[]).forEach(item => {
if (item.id === id) {
_selectModelerNames.push(item.name||'');
}
});
});
this.setState({ exportDDLModalVisible: true, selectModelerNames: _selectModelerNames, exportDDLModalReference: 'exportDDL' });
}
onExportOtherBtnClick = () => {
this.setState({ exportOtherModalVisible: true });
}
startFlow = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
this.setState({ startFlowModalVisible: true });
}
onRecatalogBtnClick = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
this.setState({ recatalogModalVisible: true });
}
onAuthorizeBtnClick = () => {
const { selectModelerIds, tableData } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
const services = (tableData||[]).filter(item => selectModelerIds.indexOf(item.id) !== -1);
this.setState({ startAuthorizeParams: {
visible: true,
services
}})
}
onReleaseBtnClick = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
this.setState({ startReleaseVisible: true })
}
onSubscribeBtnClick = () => {
const { selectModelerIds } = this.state;
const { modal } = this.props;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
modal?.confirm({
title: '提示',
content: '是否确认订阅选中服务?',
onOk: () => {
dispatch({
type: 'pds.subscribe',
payload: {
params: {
ids: (selectModelerIds||[]).join(','),
}
},
callback: () => {
showMessage('success', '订阅成功');
this.setState({ selectModelerIds: [] });
}
})
},
});
}
onOfflineBtnClick = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
this.setState({ offlineVisible: true })
}
onBatchDeleteBtnClick = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
this.setState({ showDeleteTip: true });
}
onVisibleColSettingClick = () => {
this.setState({colSettingModalVisible: true});
}
onAutoCreateTable = (item) => {
this.setState({ exportDDLModalVisible: true, selectModelerNames: [item.name||''], exportDDLModalReference: 'createTable', currentModel: item });
}
onImportExcelVisibleChange = (visible = false, hint = null) => {
this.setState({ importExcelVisible: visible });
}
onImportModalCancel = (refresh = false, confirm = false, hints = []) => {
const { catalogId, currentView } = this.state;
this.setState({ importModalVisible: false }, () => {
refresh && this.onTableChange();
if (confirm) {
if ((hints||[]).length > 0) {
setTimeout(() => {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(currentView==='dir')?(catalogId||''):''}&${Hints}=${encodeURIComponent((hints||[]).join(','))}`);
}, 1000);
} else {
setTimeout(() => {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(currentView==='dir')?(catalogId||''):''}`);
}, 1000);
}
}
});
}
onImportModalCancelByWord = (refresh = false, wordData = {}) => {
const { catalogId } = this.state;
this.setState({ importModalVisible: false }, () => {
refresh && this.onTableChange();
if (wordData && ((wordData.msg||'')!=='')) {
showNotifaction('提示', wordData.msg);
}
if (wordData && (wordData.content||[]).length > 0) {
if ((wordData.content||[]).length > 5) {
showMessage('info', '最多只能同时编辑5条信息');
}
setTimeout(() => {
wordData.content.slice(0, 5).forEach(data => {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${catalogId}&${ModelerData}=${encodeURIComponent(JSON.stringify(data))}`, '_blank');
})
}, 2000);
}
});
}
onImportModalCancelByDDL = (confirm = false, ddl = '') => {
const { catalogId } = this.state;
this.setState({ importModalVisible: false }, () => {
if (confirm && (ddl||'') !== '') {
setTimeout(() => {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${catalogId}&${DDL}=${encodeURIComponent(ddl)}`, '_blank');
}, 1000);
}
});
}
onImportStockWordDrawerCancel = () => {
this.setState({ importStockWordDrawerVisible: false });
}
onImportStockWordSuccess = () => {
this.onTableChange();
}
onExportDDLModalCancel = () => {
this.setState({ exportDDLModalVisible: false });
}
onExportOtherModalCancel = (key='') => {
const { selectModelerIds } = this.state;
this.setState({ exportOtherModalVisible: false }, () => {
if (key === 'ddl') {
this.onExportDDLBtnClick();
} else if (key === 'erwin') {
dispatch({
type: 'datamodel.exportERWinString',
payload: {
ids: selectModelerIds.join(','),
},
callback: data => {
copy(JSON.stringify(data));
showNotifaction('提示', 'Erwin信息已成功复制到剪贴板', 5);
}
});
} else if (key === 'excel') {
window.open(`/api/datamodeler/easyDataModelerExport/excel?ids=${selectModelerIds.join(',')}`);
} else if (key === 'word') {
window.open(`/api/datamodeler/easyDataModelerExport/word/template?ids=${selectModelerIds.join(',')}`);
}
});
}
onRecatalogModalCancel = (refresh = false) => {
this.setState({ recatalogModalVisible: false });
if (refresh) {
this.setState({ selectModelerIds: [] }, () => {
this.onTableChange();
});
}
}
onStartAuthorizeCancel = (refresh = false) => {
this.setState({ startAuthorizeParams: {
visible: false,
services: []
}});
if (refresh) {
this.setState({ selectModelerIds: [] }, () => {
this.onTableChange();
});
}
}
onStartReleaseCancel = (refresh = false) => {
this.setState({ startReleaseVisible: false });
if (refresh) {
this.setState({ selectModelerIds: [] }, () => {
this.onTableChange();
});
}
}
onOfflineCancel = (refresh = false) => {
this.setState({ offlineVisible: false });
if (refresh) {
this.setState({ selectModelerIds: [] }, () => {
this.onTableChange();
});
}
}
onHistoryAndVersionDrawerCancel = () => {
this.setState({ historyAndVersionDrawerVisible: false });
}
onStartFlowModalCancel = (refresh) => {
this.setState({ startFlowModalVisible: false });
if (refresh) {
this.onTableChange();
this.setState({ selectModelerIds: [] });
}
}
importStockModel = () => {
const { catalogId } = this.state;
if ((catalogId||'') === '') {
showMessage('info', '请先选择服务目录');
return;
}
this.setState({ importStockWordDrawerVisible: true });
}
treeToggleClick = () => {
this.setState({ expandTree: !this.state.expandTree });
}
onDeleteTipModalCancel = (refresh=false) => {
const { selectModelerIds } = this.state;
this.setState({ showDeleteTip: false });
if (refresh) {
dispatch({
type: 'datamodel.deleteDataModels',
payload: {
params: {
easyDataModelerDataModelIds: selectModelerIds.join(',')
}
},
callback: (tip) => {
this.onTableChange();
this.setState({ selectModelerIds: [] });
if ((tip||'')!=='') {
showNotifaction('提示', tip, 5);
}
}
})
}
}
onColSettingModalCancel = (refresh = false) => {
this.setState({ colSettingModalVisible: false }, () => {
if (refresh) {
this.getPreference();
}
});
}
render() {
const { app, isOnlyEnding } = this.props;
const { importModalVisible, catalogId, loadingTableData, selectModelerIds, keyword, filterTableData, selectModelerNames, exportDDLModalVisible, exportOtherModalVisible, importStockWordDrawerVisible , loadingStates, modelStates, currentModelState, currentView, recatalogModalVisible, exportDDLModalReference, currentModel, offset, historyAndVersionDrawerVisible, modelerId, startFlowModalVisible, expandTree, showDeleteTip, colSettingModalVisible, visibleColNames, currentODataState } = this.state;
const classes = classNames('data-model', {
'data-model-collapse': !expandTree
});
let disableStartFlow = false, startFlowTip = '';
if ((currentView==='dir'&&currentModelState==='2')||(currentView!=='dir'&&catalogId==='2')) {
disableStartFlow = true;
startFlowTip = '只有草稿状态下的服务才能送审';
} else if ((selectModelerIds||[]).length===0) {
disableStartFlow = true;
startFlowTip = '请先选择服务';
}
return (
<div className={classes}>
<ResizableBox
className='left'
width={230}
height={Infinity}
axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
>
<ModelTree onViewChange={this.onViewChange} onSelect={this.onTreeSelect} importStockModel={this.importStockModel} keyword={keyword} {...this.props} />
</ResizableBox>
<div className='tree-toggle-wrap'>
<div className='tree-toggle' onClick={this.treeToggleClick}>
{ expandTree ? <CaretLeftOutlined /> : <CaretRightOutlined /> }
</div>
</div>
<div className='right'>
<div
className='d-flex p-3'
style={{
borderBottom: '1px solid #EFEFEF',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<Space>
{
currentView==='dir' && (getDataModelerRole(app?.user)!==DataModelerRoleReader) && !isOnlyEnding && <React.Fragment>
<Space>
<Dropdown
overlay={
<Menu>
<Menu.Item onClick={() => {
app?.editServer?.({ dirId: catalogId, type: 'register' });
}}>注册服务</Menu.Item>
<Menu.Item onClick={() => {
app?.editServer?.({ dirId: catalogId, type: 'drag' });
}}>拖拉创建服务</Menu.Item>
<Menu.Item onClick={() => {
app?.editServer?.({ dirId: catalogId, type: 'sql' });
}}>自定义sql创建服务</Menu.Item>
</Menu>
}
placement="bottomLeft"
trigger={['click']}
>
<Button>新建</Button>
</Dropdown>
</Space>
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onExportOtherBtnClick} disabled={(selectModelerIds||[]).length===0}>导出</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Tooltip title={startFlowTip}>
<Button onClick={this.startFlow} disabled={disableStartFlow}>送审</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onRecatalogBtnClick} disabled={(selectModelerIds||[]).length===0}>变更目录</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onAuthorizeBtnClick} disabled={(selectModelerIds||[]).length===0}>授权</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onReleaseBtnClick} disabled={(selectModelerIds||[]).length===0}>发布</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onBatchDeleteBtnClick} disabled={(selectModelerIds||[]).length===0}>删除</Button>
</Tooltip>
</Space> */}
{/* <Space>
<Button onClick={this.onVisibleColSettingClick}>可见列设置</Button>
</Space> */}
</React.Fragment>
}
<Space>
<Button onClick={this.onVisibleColSettingClick}>可见列设置</Button>
</Space>
<Space>
<Button onClick={() => { this.setState({jdbcInformationVisible: true}); }}>JDBC信息</Button>
</Space>
{
(getDataModelerRole(app?.user)!==DataModelerRoleReader) && isOnlyEnding &&
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onSubscribeBtnClick} disabled={(selectModelerIds||[]).length===0}>订阅</Button>
</Tooltip>
}
{
(getDataModelerRole(app?.user)!==DataModelerRoleReader) && isOnlyEnding &&
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onOfflineBtnClick} disabled={(selectModelerIds||[]).length===0}>下线</Button>
</Tooltip>
}
</Space>
<Space>
{
(currentView==='dir'||keyword!=='') && <Space>
<span>发布状态:</span>
<Select
style={{ width: 120 }}
onChange={(value) => {
this.onModelStateChange(value);
}}
loading={loadingStates}
value={loadingStates? '': currentModelState}
>
{
(modelStates||[]).map(item => {
return (
<Option key={item.id} value={item.id}>{item.cnName||''}</Option>
);
})
}
</Select>
</Space>
}
<Space>
{/* <span>OData状态:</span>
<Select
style={{ width: 120 }}
onChange={(value) => {
this.onODataStateChange(value);
}}
value={currentODataState}
>
<Option value='0'>所有状态</Option>
<Option value='1'>已启动</Option>
<Option value='2'>未启动</Option>
</Select> */}
</Space>
<Space>
<InputDebounce
placeholder="通过服务名称全文搜索"
allowClear
value={keyword}
onChange={(value) => { this.onSearchInputChange(value); }}
style={{ width: inputWidth, marginLeft: 'auto' }}
/>
</Space>
</Space>
</div>
<div className='p-3'>
<Spin spinning={loadingTableData}>
<ModelTable
loading={loadingTableData}
user={app?.user}
isOnlyEnding={isOnlyEnding}
catalogId={catalogId}
view={currentView}
data={filterTableData}
modelState={currentModelState}
offset={offset}
keyword={keyword}
selectModelerIds={selectModelerIds}
visibleColNames={visibleColNames}
onChange={this.onTableChange}
onSelect={this.onTableSelect}
onItemAction={this.onTableItemAction}
onAutoCreateTable={this.onAutoCreateTable}
onHistory={this.onHistory}
{...this.props} />
</Spin>
</div>
</div>
<RecatalogModal
visible={recatalogModalVisible}
ids={selectModelerIds}
onCancel={this.onRecatalogModalCancel}
/>
<HistoryAndVersionDrawer
id={modelerId}
visible={historyAndVersionDrawerVisible}
onCancel={this.onHistoryAndVersionDrawerCancel}
/>
<StartAuthorize
visible={this.state.startAuthorizeParams.visible}
services={this.state.startAuthorizeParams.services}
onCancel={this.onStartAuthorizeCancel}
/>
<StartRelease
visible={this.state.startReleaseVisible}
ids={selectModelerIds}
onCancel={this.onStartReleaseCancel}
/>
<Offline
visible={this.state.offlineVisible}
ids={selectModelerIds}
onCancel={this.onOfflineCancel}
/>
<ColSettingModal
visible={colSettingModalVisible}
onCancel={this.onColSettingModalCancel}
/>
<JDBCInformation
visible={this.state.jdbcInformationVisible}
onCancel={() => { this.setState({ jdbcInformationVisible: false }) }}
/>
</div>
);
}
}
const WrapModel = (props) => {
const [modal, contextHolder] = Modal.useModal();
return (
<AppContext.Consumer>
{
value => <React.Fragment>
<Model app={value} modal={modal} {...props} />
{contextHolder}
</React.Fragment>
}
</AppContext.Consumer>
)
}
export default WrapModel;
.data-model {
display: flex;
background-color: #fff;
width: 100%;
height: 100%;
.left {
flex: 0 0 auto;
border-right: 1px solid #EFEFEF;
overflow: hidden;
}
.tree-toggle-wrap {
position: relative;
width: 20px;
height: 100%;
.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 {
flex: 1;
overflow: hidden;
}
}
.data-model-collapse {
.left {
width: 0 !important;
}
}
\ No newline at end of file
...@@ -9,7 +9,6 @@ import DatasourceManage from './DatasourceManage'; ...@@ -9,7 +9,6 @@ import DatasourceManage from './DatasourceManage';
import Map from './Map'; import Map from './Map';
import Model from './Model'; import Model from './Model';
import ModelConfig from './ModelConfig'; import ModelConfig from './ModelConfig';
import DataService from './Pdata';
import AssetManage from './AssetManage'; import AssetManage from './AssetManage';
import AssetResourceBrowse from './AssetResourceBrowse'; import AssetResourceBrowse from './AssetResourceBrowse';
import AssetBrowse from './AssetBrowse'; import AssetBrowse from './AssetBrowse';
...@@ -31,7 +30,6 @@ class Manage extends Component { ...@@ -31,7 +30,6 @@ class Manage extends Component {
session && session.userId ? ( session && session.userId ? (
<Switch> <Switch>
<Route path={`${match.path}/datasource-manage`} component={DatasourceManage} /> <Route path={`${match.path}/datasource-manage`} component={DatasourceManage} />
<Route path={`${match.path}/data-service`} component={DataService} />
<Route path={`${match.path}/data-model`} component={Model} /> <Route path={`${match.path}/data-model`} component={Model} />
<Route path={`${match.path}/model-config`} component={ModelConfig} /> <Route path={`${match.path}/model-config`} component={ModelConfig} />
<Route path={`${match.path}/asset-map`} component={Map} /> <Route path={`${match.path}/asset-map`} component={Map} />
......
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