Commit 82cac42b by zhaochengxiang

增加服务

parent ee27b8a2
......@@ -15,7 +15,6 @@ const Signin = loadable(()=> import('./view/Signin'));
const Home = loadable(()=> import('./view/Home'));
const Manage = loadable(()=> import('./view/Manage'));
const Model = loadable(()=> import('./view/Manage/Model'));
const ModelConfig = loadable(()=> import('./view/Manage/ModelConfig'));
const AssetManage = loadable(()=> import('./view/Manage/AssetManage'));
const AssetResourceBrowse = loadable(()=> import('./view/Manage/AssetResourceBrowse'));
const AssetBrowse = loadable(()=> import('./view/Manage/AssetBrowse'));
......@@ -24,11 +23,10 @@ const DatasourceManage = loadable(()=> import('./view/Manage/DatasourceManage'))
const AssetDetailPage = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetailPage'));
const AssetDetail = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetail'));
const ImportAction = loadable(()=> import('./view/Manage/Model/Component/ImportAction'));
const EditModel = loadable(()=> import('./view/Manage/Model/Component/EditModel'));
const EditTemplate = loadable(()=> import('./view/Manage/ModelConfig/Component/EditTemplate'));
const AssetTree = loadable(()=> import('./view/Manage/AssetManage/Component/AssetManageTree'));
const DataMasterDefine = loadable(()=> import('./view/Manage/DataMaster/Define'));
const DataMasterManage = loadable(()=> import('./view/Manage/DataMaster/Manage'));
const DataService = loadable(()=> import('./view/Manage/Model'));
const DataServiceDetail = loadable(()=> import('./view/Manage/Model/Component/ServiceDetail'));
const GrantedDataServiceList = loadable(()=> import('./view/Manage/Model/Component/GrantedList'));
export class App extends React.Component {
constructor() {
......@@ -67,21 +65,6 @@ export class App extends React.Component {
terms = hostParams.terms||[];
}
if (message === 'showDataModelDetail') {
return (
<AppContext.Provider value={{
setGlobalState,
onGlobalStateChange
}}>
<ImportAction
modelerId={id}
action='detail'
terms={terms}
/>
</AppContext.Provider>
);
}
if (message === 'showAssetDetail') {
return (
<AppContext.Provider value={{
......@@ -123,6 +106,57 @@ export class App extends React.Component {
);
}
if (message === 'showDataService' || message === 'showDataServiceManage') {
return (
<AppContext.Provider value={{
env: hostParams?.env,
user: hostParams?.user,
openAdmit: hostParams?.openAdmit,
openDetail: hostParams?.openDetail,
editServer: hostParams?.editServer,
applyServer: hostParams?.applyServer,
setGlobalState,
onGlobalStateChange
}}>
<DataService
isOnlyEnding={message==='showDataServiceManage'}
location={this.props.location}
/>
</AppContext.Provider>
);
}
if (message === 'showDataServiceDetail') {
return (
<AppContext.Provider value={{
setGlobalState,
onGlobalStateChange
}}>
<DataServiceDetail
id={id}
terms={terms}
/>
</AppContext.Provider>
);
}
if (message === 'showGrantedDataService') {
return (
<AppContext.Provider value={{
env: hostParams?.env,
user: hostParams?.user,
openAdmit: hostParams?.openAdmit,
openDetail: hostParams?.openDetail,
editServer: hostParams?.editServer,
applyServer: hostParams?.applyServer,
setGlobalState,
onGlobalStateChange
}}>
<GrantedDataServiceList/>
</AppContext.Provider>
);
}
return (
<AppContext.Provider value={{
env: hostParams?.env,
......@@ -135,29 +169,14 @@ export class App extends React.Component {
<Route path={`${ContextPath}/login`} component={Signin} exact />
<Route path={`${ContextPath}/home`} component={Home} />
<Route path={`${ContextPath}/manage`} component={Manage} />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/model-template-action`} component={EditTemplate} exact />
<Route path={`${ContextPath}/asset-detail`} component={AssetDetailPage} exact />
<Route path={'/center-home/view/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/view/data-model'} component={Model} exact />
<Route path={'/center-home/view/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/view/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/view/asset-resource-browse'} component={AssetResourceBrowse} exact />
<Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact />
<Route path={'/center-home/view/asset-recycle'} component={AssetRecycle} exact />
<Route path={'/center-home/menu/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/menu/data-model'} component={Model} exact />
<Route path={'/center-home/menu/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/menu/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/menu/asset-resource-browse'} component={AssetResourceBrowse} exact />
<Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact />
<Route path={'/center-home/menu/asset-recycle'} component={AssetRecycle} exact />
<Route path={'/center-home/menu/msd-define'} component={DataMasterDefine} exact />
<Route path={'/center-home/menu/msd-manage'} component={DataMasterManage} exact />
<Route path={'/center-home/data-model-action'} component={EditModel} exact />
<Route path={'/center-home/asset-detail'} component={AssetDetailPage} exact />
</Switch>
</Router>
......
......@@ -11,8 +11,9 @@ import * as datamodel from './datamodel';
import * as assetmanage from './assetmanage';
import * as tag from './tag';
import * as msd from './msd';
import * as pds from './pds';
const funcs = Connect({ user, datamodel, map, assetmanage, datasource, tag, msd })
const funcs = Connect({ user, datamodel, map, assetmanage, datasource, tag, msd, pds })
function* request(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* loadDataServiceCatalogServiceCount() {
return yield call(pds.loadDataServiceCatalogServiceCount)
}
export function* loadStateCatalog(payload) {
return yield call(pds.loadStateCatalog, payload);
}
export function* loadDataServiceStateCatalogServiceCount() {
return yield call(pds.loadDataServiceStateCatalogServiceCount)
}
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);
}
export function* addImportWithConfirm(payload) {
return yield call(pds.addImportWithConfirm, payload);
}
export function* importConfirm(payload) {
return yield call(pds.importConfirm, payload);
}
export function* getImportLogs(payload) {
return yield call(pds.getImportLogs, payload);
}
export function* exportAll(payload) {
return yield call(pds.exportAll, payload);
}
\ No newline at end of file
......@@ -16,14 +16,6 @@ export const routes = [
text: '数据源管理',
},
{
name: 'data-model',
text: '模型设计',
},
{
name: 'model-config',
text: '模型配置',
},
{
name: 'asset-manage',
text: '资产管理',
},
......@@ -40,12 +32,8 @@ export const routes = [
text: '未挂载资产',
},
{
name: 'msd-define',
text: '主数据定义'
},
{
name: 'msd-manage',
text: '主数据管理'
name: 'data-service',
text: '服务管理',
},
]
}
......
import { PostFile, GetJSON, PostJSON, Post, Get, GetJSONRaw, callFetchRawByFormData, 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 loadDataServiceCatalogServiceCount() {
return GetJSON("/pdataservice/pdsCURD/loadDataServiceCatalogServiceCount");
}
export function loadStateCatalog(payload) {
return GetJSON("/pdataservice/pdsCURD/loadDataServiceStateCatalog", payload)
}
export function loadDataServiceStateCatalogServiceCount() {
return GetJSON("/pdataservice/pdsCURD/loadDataServiceStateCatalogServiceCount");
}
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 callFetchRawByFormData('post', '/pdataservice/pdsSub/subscribeMsg', payload);
}
export function addImportWithConfirm(payload) {
return PostFile("/pdataservice/pdsCURD/addWithConfirm", payload);
}
export function importConfirm(payload) {
return PostJSON("/pdataservice/pdsCURD/confirm", payload);
}
export function getImportLogs(payload) {
return GetJSON("/pdataservice/pdsCURD/log", payload);
}
export function exportAll(payload) {
return callFetchRaw('post', '/pdataservice/pdsCURD/export/all', payload)
}
\ No newline at end of file
......@@ -177,3 +177,47 @@ export function PostFile(url, payload, fileName='file') {
callback
)
}
export const callFetchRaw = (method, url, options) => {
const { params, ...reqConfig } = options;
const config = {
baseURL,
timeout: 300000,
cache: 'no-cache',
}
return axios.request({
method,
url,
params,
...config,
...reqConfig
})
}
export const callFetchRawByFormData = (method, url, options) => {
const { params, ...reqConfig } = options;
const config = {
baseURL,
timeout: 300000,
headers: {
'Content-Type': 'multipart/form-data',
},
cache: 'no-cache',
}
var bodyFormData = new FormData();
Object.keys(params||{}).forEach(key => {
bodyFormData.append(key, params[key]);
});
return axios.request({
method,
url,
data: bodyFormData,
...config,
...reqConfig
})
}
\ No newline at end of file
import { AxiosResponse } from "axios"
export default function (res: AxiosResponse<any>) {
const blob = res.data
const headers = res.headers
let tempName = headers["content-disposition"]
?.split(";")?.[1]
?.split("filename=")?.[1];
tempName = decodeURI(tempName);
// const blob = new Blob([content], { type: 'application/octet-stream' })
var url = (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(blob) : window.webkitURL.createObjectURL(blob);
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', tempName); //or any other extension
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href) // 释放URL 对象
document.body.removeChild(link)
}
\ No newline at end of file
import { useEffect, useMemo, useState } from 'react';
import { Space, Button, Input, Pagination, Tooltip } from 'antd';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import ResizeableTable from '../../../ResizeableTable';
import DebounceInput from '../../../Model/Component/DebounceInput';
import { UpdateTemplateModal } from './UpdateTemplateModal';
import { inputWidth, showMessage } from '../../../../../util';
import './DefineTable.less';
import 'react-contexify/dist/ReactContexify.css';
import { dispatch } from '../../../../../model';
const InputDebounce = DebounceInput(300)(Input);
const DefineTable = (props) => {
const {nodeId} = props;
const [keyword, setKeyword] = useState('');
const [checkedKeys, setCheckedKeys] = useState([]);
const [isTemplateModalVisible, setIsTemplateModalVisible] = useState(false);
const [currentTemplate, setCurrentTemplate] = useState({});
const [action, setAction] = useState('');
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const [tableData, setTableData] = useState([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState({pageNum: 1, pageSize: 20});
const {pageNum, pageSize} = pagination;
useEffect(() => {
setCheckedKeys([]);
setPagination({...pagination, pageNum: 1});
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodeId])
useEffect(() => {
if (!nodeId || nodeId==='') {
setTableData([]);
setTotal(0);
} else {
getTemplates();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [keyword, pagination])
const columns = useMemo(() => {
return ([
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '模版名称',
dataIndex: 'name',
width: 200,
ellipsis: true,
render: (text, record, index) => {
return <a onClick={() => {onItemClick(record);}}>{text}</a>
},
},
{
title: '模版中文名称',
dataIndex: 'cnName',
width: 200,
ellipsis: true,
},
{
title: '修改时间',
dataIndex: 'lastUpdateTime',
width: 200,
ellipsis: true,
},
{
title: '模版描述',
dataIndex: 'comment',
width: 200,
ellipsis: true,
}
]);
}, []);
const MENU_ID = 'data-master-table-contextmenu';
const { show } = useContextMenu({
id: MENU_ID,
});
const getTemplates = () => {
setLoading(true);
dispatch({
type: 'msd.getModels',
payload: {
params: {
nodeId,
keyword,
pageNum,
pageSize
}
},
callback: (data) => {
setLoading(false);
setTableData(data?.content||[]);
setTotal(data?.totalElements||0);
},
error: () => {
setLoading(false);
}
});
}
const onAddClick = () => {
setAction('add');
setCurrentTemplate(null);
setIsTemplateModalVisible(true);
}
const onItemClick = (record) => {
setAction('detail');
setCurrentTemplate(record);
setIsTemplateModalVisible(true);
}
const onTemplateModalCancel = (refresh = false) => {
setIsTemplateModalVisible(false);
if (refresh) {
getTemplates();
}
}
const onBatchDeleteClick = () => {
setDeleteLoading(true);
dispatch({
type: 'msd.deleteModels',
payload: {
params: {
modelIds: (checkedKeys||[]).join(',')
}
},
callback: (data) => {
setDeleteLoading(false);
showMessage('success', '删除成功');
getTemplates();
},
error: () => {
setDeleteLoading(false);
}
});
}
const onSearchInputChange = (value) => {
setKeyword(value);
}
const onTableSelectChange = keys => {
setCheckedKeys(keys);
};
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
if (key === 'update') {
setAction('update');
setIsTemplateModalVisible(true);
}
}
const changeCurrent = (page,size) => {
setCheckedKeys([]);
setPagination({ pageNum: page, pageSize: size });
}
const rowSelection = {
selectedRowKeys: checkedKeys,
onChange: onTableSelectChange,
};
return (
<div className='data-master-table'>
<div className='data-master-header px-3'>
<Space>
<Space>
<Button onClick={onAddClick}>新建</Button>
</Space>
<Space>
<Tooltip title={(checkedKeys||[]).length===0?'请先选择模版':''}>
<Button onClick={onBatchDeleteClick} disabled={(checkedKeys||[]).length===0} loading={deleteLoading}>删除</Button>
</Tooltip>
</Space>
</Space>
<InputDebounce
placeholder="请输入关键字"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth, marginLeft: 'auto' }}
/>
</div>
<div className='data-master-content p-3'>
<ResizeableTable
loading={loading}
rowKey='_id'
rowSelection={rowSelection}
columns={columns}
dataSource={tableData}
onRow={(record) => {
return {
onContextMenu: event => {
setCurrentTemplate(record);
show(event);
},
};
}}
pagination={false}
scroll={{ y: 'calc(100vh - 94px - 32px - 57px - 24px - 39px - 36px)' }}
/>
<Pagination
size="small"
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={changeCurrent}
onShowSizeChange={changeCurrent}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={total}
showTotal={total => `共 ${total} 条`}
/>
</div>
<UpdateTemplateModal
action={action}
nodeId={nodeId}
template={currentTemplate}
visible={isTemplateModalVisible}
onCancel={onTemplateModalCancel}
/>
<RcMenu id={MENU_ID}>
<RcItem id="update" onClick={handleItemClick}>
编辑
</RcItem>
</RcMenu>
</div>
);
}
export default DefineTable;
\ No newline at end of file
.data-master-table {
height: 100%;
.data-master-header {
display: flex;
flex: none;
height: 57px;
border-bottom: 1px solid #EFEFEF;
justify-content: space-between;
align-items: center;
}
}
\ No newline at end of file
import {useEffect, useMemo, useState, useRef} from 'react';
import {Tooltip, Spin, AutoComplete, Tree, Modal} from 'antd';
import {PlusOutlined, ReloadOutlined} from '@ant-design/icons';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import { dispatch } from '../../../../../model';
import {highlightSearchContentByTerms, showMessage} from '../../../../../util';
import UpdateDefineTreeNodeModal from './UpdateDefineTreeNodeModal';
import 'react-contexify/dist/ReactContexify.css';
import './DefineTree.less';
const {Option} = AutoComplete;
const DefineTree = (props) => {
const {onClick} = props;
const [loading, setLoading] = useState(false);
const [data, setData] = useState();
const [options, setOptions] = useState([]);
const [keyword, setKeyword] = useState('');
const [expandedKeys, setExpandedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(false);
const [updateNodeModalVisible, setUpdateNodeModalVisible] = useState(false);
const [updateNodeType, setUpdateNodeType] = useState('');
const [rightSelectNode, setRightSelectNode] = useState({});
const selectedKeysRef = useRef([]);
const [modal, contextHolder] = Modal.useModal();
const MENU_ID = 'msd-define-tree';
const { show } = useContextMenu({
id: MENU_ID,
});
useEffect(() => {
getTreeNodes();
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const treeData = useMemo(() => {
const loop = (data) =>
(data||[]).map((item) => {
if (item.children) {
return {
title: item.name||'',
key: item._id||'',
origin: item,
children: loop(item.children),
};
}
return {
title: item.name||'',
key: item._id||'',
};
});
return loop(data);
}, [data])
const treeList = useMemo(() => {
const generateList = (data, list, path = null) => {
(data||[]).forEach(node => {
const {_id, name} = node;
const currentPath = path ? `${path}/${name}` : name;
list.push({key: _id, title: currentPath});
if (node.children) {
generateList(node.children, list, currentPath);
}
});
};
const newTreeList = [];
generateList(data, newTreeList);
return newTreeList;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
const selectNode = useMemo(() => {
const generateList = (data, list) => {
(data||[]).forEach(node => {
list.push({...node});
if (node.children) {
generateList(node.children, list);
}
});
};
const newTreeList = [];
generateList(data, newTreeList);
const filterNodes = newTreeList.filter(item => selectedKeys.indexOf(item._id)!==-1);
return (filterNodes||[]).length>0 ? filterNodes[0]:{};
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, selectedKeys])
const getTreeNodes = () => {
setLoading(true);
dispatch({
type: 'msd.getTreeNodes',
callback: (data) => {
setLoading(false);
setData(data||[]);
if ((selectedKeysRef.current||[]).length===0 && (data||[]).length>0) {
onTreeSelect([data[0]._id]);
}
if ((expandedKeys||[]).length===0 && (data||[]).length>0) {
setExpandedKeys([data[0]._id]);
} else {
setExpandedKeys(Array.from(new Set([...expandedKeys, ...selectedKeysRef.current])));
setAutoExpandParent(true);
}
},
error: () => {
setLoading(false);
}
})
}
const onAddClick = () => {
setUpdateNodeType('add');
setUpdateNodeModalVisible(true);
}
const onRefreshClick = () => {
getTreeNodes();
}
const updateNode = () => {
setUpdateNodeType('update');
setUpdateNodeModalVisible(true);
}
const upOrDownNode = (steps = 1) => {
setLoading(true);
dispatch({
type: 'msd.treeNodeSeq',
payload: {
params: {
id: rightSelectNode?._id||'',
steps
}
},
callback: () => {
rightSelectNode?._id && onTreeSelect([rightSelectNode?._id]);
getTreeNodes();
},
error: () => {
setLoading(false);
}
})
}
const deleteNode = () => {
modal.confirm({
title: '提示!',
content: '删除目录会删除相关的模版,您确定删除吗?',
onOk: () => {
setLoading(true);
dispatch({
type: 'msd.deleteTreeNode',
payload: {
params: {
nodeId: rightSelectNode?._id
}
},
callback: () => {
showMessage('success', '删除目录成功');
setLoading(false);
if (selectedKeysRef.current.indexOf(rightSelectNode?._id)!==-1) {
setSelectedKeys([]);
selectedKeysRef.current = [];
}
getTreeNodes();
},
error: () => {
setLoading(false);
}
});
}
});
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText);
setOptions(treeList.filter(item => item.title.indexOf(searchText)!==-1));
};
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/');
setKeyword(paths[paths.length-1]);
onTreeSelect([option.key]);
setExpandedKeys(Array.from(new Set([...expandedKeys, option.key])));
setAutoExpandParent(true);
};
const onAutoCompleteClear = () => {
setKeyword('');
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
}
const onTreeSelect = (selectedKeys) => {
if (selectedKeys.length === 0) {
return;
}
setSelectedKeys(selectedKeys);
selectedKeysRef.current = selectedKeys;
onClick && onClick(selectedKeys[0]);
}
const onUpdateNodeModalCancel = (refresh = false, nodeId = null) => {
setUpdateNodeModalVisible(false);
if (refresh) {
nodeId && onTreeSelect([nodeId]);
getTreeNodes();
}
}
return (
<div className='data-master-tree'>
<div className='header p-3'>
<Tooltip title="新增目录">
<PlusOutlined
className='default'
onClick={onAddClick}
style={{
fontSize:16,
cursor:'pointer',
flex: 1
}}
/>
</Tooltip>
<Tooltip title="刷新目录" className='ml-2'>
<ReloadOutlined
className='default'
onClick={onRefreshClick}
style={{
fontSize:16,
cursor:'pointer',
flex: 1
}}
/>
</Tooltip>
<div style={{flex: 3}} />
</div>
<div className='content p-3'>
<Spin spinning={loading}>
<AutoComplete
className='content-search'
allowClear
value={keyword}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={onAutoCompleteClear}
>
{
(options||[]).map((item, index) => {
return (
<Option key={item.key} value={item.title}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.title, [keyword])}
</div>
</Option>
);
})
}
</AutoComplete>
<Tree
className='content-tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData}
onExpand={onTreeExpand}
onSelect={onTreeSelect}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onRightClick={({event, node}) => {
setRightSelectNode(node?.origin);
show(event, {
position: {
x: event.clientX + 30,
y: event.clientY - 10
}
});
}}
/>
</Spin>
</div>
<UpdateDefineTreeNodeModal
visible={updateNodeModalVisible}
onCancel={onUpdateNodeModalCancel}
type={updateNodeType}
node={(updateNodeType==='add')?selectNode:rightSelectNode}
/>
<RcMenu id={MENU_ID}>
<RcItem id="edit" onClick={updateNode}>
修改目录
</RcItem>
<RcItem id="up" onClick={() => { upOrDownNode(-1); }}>
上移目录
</RcItem>
<RcItem id="down" onClick={() => { upOrDownNode(1); }}>
下移目录
</RcItem>
<RcItem id="delete" onClick={deleteNode}>
删除目录
</RcItem>
</RcMenu>
{contextHolder}
</div>
);
}
export default DefineTree;
\ No newline at end of file
@import '../../../../../variables.less';
.data-master-tree {
height: 100%;
.header {
display: inline-flex;
flex: none;
width: 100%;
height: 57px;
justify-content: flex-start;
align-items: center;
border-bottom: 1px solid #EFEFEF;
}
.content-search {
margin-bottom: 10px;
width: 100%;
}
.content-tree {
height: calc(100vh - @header-height - @breadcrumb-height - 25px - 57px - 66px);
overflow: auto;
}
}
\ No newline at end of file
import { Form, Input, Row, Col, Descriptions } from "antd";
const { TextArea } = Input;
const UpdateBasicInfo = (props) => {
const {form, editable, template} = props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
return (
<div>
<h3>基本信息</h3>
{
editable ? <Form
form={form}
{...formItemLayout}
>
<Row gutter={10}>
<Col xs={24} sm={24} lg={12}>
<Form.Item label='中文名称' name='cnName' rules={[{ required: true, message: '请填写模版中文名称' }]}>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12}>
<Form.Item label='英文名称' name='name' rules={[{ required: true, message: '请填写模版名称' }]}>
<Input />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12}>
<Form.Item label='模版描述' name='comment'>
<TextArea row={4} />
</Form.Item>
</Col>
</Row>
</Form> : <Descriptions column={2}>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>中文名称</div>} >{template?.cnName||''}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>英文名称</div>}>{template?.name||''}</Descriptions.Item>
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>模版描述</div>}>{template?.comment||''}</Descriptions.Item>
</Descriptions>
}
</div>
);
}
export default UpdateBasicInfo;
\ No newline at end of file
import { useState, useEffect } from 'react';
import { Modal, Form } from 'antd';
import UpdateNodeForm from './UpdateNodeForm';
import { dispatch } from '../../../../../model';
const UpdateDefineTreeNodeModal = (props) => {
const {visible, onCancel, type, node} = props;
const [confirmLoading, setConfirmLoading] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
if (type === 'add') {
form.setFieldsValue({action: node ? 'sub' : 'root', name: '', comment: ''});
} else {
form.setFieldsValue({action: '', name: node?.name||'', comment: node?.comment||''});
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, node, type])
const handleCancel = () => {
setConfirmLoading(false);
form.setFields([{ name: 'name', errors: [] }, { name: 'comment', errors: [] }]);
onCancel && onCancel();
}
const handleOk = async () => {
setConfirmLoading(true);
try {
const values = await form.validateFields();
let payload = null;
if (type === 'add') {
payload = {
name: values.name||'',
comment: values.comment||'',
parentId: (values.action==='root')?'root':node?._id
};
} else {
payload = {
...node,
name: values.name||'',
comment: values.comment||'',
}
}
let url = (type === 'add')?'msd.addTreeNode':'msd.updateTreeNode';
dispatch({
type: url,
payload: {
data: payload
},
callback: id => {
setConfirmLoading(false);
onCancel && onCancel(true, id);
},
error: () => {
setConfirmLoading(false);
}
});
} catch (errInfo) {
setConfirmLoading(false);
}
}
return (
<Modal
confirmLoading={confirmLoading}
visible={visible}
title={type==='add'?"新增目录":"更新目录"}
onOk={handleOk}
onCancel={handleCancel}
>
<UpdateNodeForm type={type} node={node} form={form} />
</Modal>
);
}
export default UpdateDefineTreeNodeModal;
\ No newline at end of file
import React, { useState, useRef, useEffect, useMemo, useCallback, useContext } from "react";
import { Tooltip, Table, Space, Popover, Input, Button, Form, Checkbox } from "antd";
import { QuestionCircleOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { useClickAway } from 'ahooks';
import update from 'immutability-helper';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import DebounceInput from "../../../Model/Component/DebounceInput";
import { inputWidth } from "../../../../../util";
import { DragableBodyRow, DatatypeInput } from "../../../Model/Component/ImportActionTable";
import { dispatch } from "../../../../../model";
import { EditTemplateContext } from "./UpdateTemplateModal";
const InputDebounce = DebounceInput(300)(Input);
const FieldTypeComp = (props) => {
const {dataType = {}} = props;
const [value, setValue] = useState('');
useEffect(() => {
try {
let dataTypeJson = JSON.parse(dataType);
if ((dataTypeJson?.name==='Char'||dataTypeJson?.name==='Varchar') && dataTypeJson?.parameterValues?.length>0) {
setValue(`${dataTypeJson?.name||''}(${(dataTypeJson?.parameterValues[0]?dataTypeJson?.parameterValues[0]:0)})`);
} else if ((dataTypeJson?.name==='Decimal'||dataTypeJson?.name==='Numeric') && dataTypeJson?.parameterValues?.length>1) {
setValue(`${dataTypeJson?.name||''}(${(dataTypeJson?.parameterValues[0]?dataTypeJson?.parameterValues[0]:0)},${(dataTypeJson?.parameterValues[1]?dataTypeJson?.parameterValues[1]:0)})`);
} else {
setValue(dataTypeJson?.name||'');
}
} catch(error) {
setValue('');
}
}, [dataType])
return (
<span>{value}</span>
)
}
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>
);
};
const UpdateField = (props) => {
const {editable = true, template, visible, onChange} = props;
const [data, setData] = useState([]);
const [keyword, setKeyword] = useState('');
const [editingKey, setEditingKey] = useState(null);
const [supportedDatatypes, setSupportedDatatypes] = useState([]);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const tableRef = useRef(null);
const dataRef = useRef([]);
const editingKeyRef = useRef(null);
const { attrIsEditingFunction } = useContext(EditTemplateContext);
const cols = [
{
title: '序号',
dataIndex: 'key',
editable: false,
width: 60,
render: (_, __, index) => {
return (index+1).toString();
}
},
{
title: '中文名称',
width: 200,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
require: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text}</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' }} >{text}</span>
</Tooltip>
)
}
},
{
title: '类型',
width: (editingKey!==null)?250:150,
dataIndex: 'dataType',
editable: true,
ellipsis: true,
require: true,
render: (_, record, __) => {
return <FieldTypeComp dataType={record?.dataType} />;
}
},
{
title: '业务含义',
dataIndex: 'comment',
editable: true,
ellipsis: true,
require: true,
width: 200,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text}</span>
</Tooltip>
)
}
}
];
const actionCol = {
title: '操作',
dataIndex: 'action',
width: 90,
fixed: 'right',
render: (_, record) => {
return (
<React.Fragment>
{
editable && <React.Fragment>
{
<React.Fragment>
<Button
className='mr-3'
size='small'
type='text'
icon={<DeleteOutlined style={{ color: 'red' }} />}
onClick={(event) => {
event.stopPropagation();
removeItem(record);
}}
/>
</React.Fragment>
}
</React.Fragment>
}
</React.Fragment>
)
},
};
useEffect(() => {
if (visible) {
setEditingKey(null);
editingKeyRef.current = null;
if (!template) {
setData([]);
dataRef.current = [];
} else {
getFileds();
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, template])
const columns = useMemo(() => {
const newColumns = [...cols];
if (editable) {
newColumns.push(actionCol);
}
return newColumns;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [editable, data, editingKey]);
useClickAway(() => {
save();
}, tableRef);
useEffect(() => {
getSupportedDatatypes();
}, [])
useEffect(() => {
attrIsEditingFunction && attrIsEditingFunction(editingKey!=null);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [editingKey])
const filterData = useMemo(() => {
return (data||[]).filter(item => (item?.name||'').indexOf(keyword)!==-1 || (item.cnName).indexOf(keyword)!==-1);
}, [keyword, data])
const isEditing = (record) => record?.name === editingKey;
const getSupportedDatatypes = () => {
dispatch({
type: 'msd.getTypes',
callback: data => {
setSupportedDatatypes(data||[]);
}
});
}
const getFileds = () => {
setLoading(true);
dispatch({
type: 'msd.getColumns',
payload: {
params: {
modelId: template?._id||''
}
},
callback: data => {
setLoading(false);
function compare(val1, val2) {
var a = val1.seq;
var b = val2.seq;
return (a - b);
}
(data||[]).sort(compare);
setData(data||[]);
dataRef.current = data||[];
onChange && onChange(data||[]);
},
error: () => {
setLoading(false);
}
});
}
const onAddClick = (event) => {
event.stopPropagation();
save().then(result => {
if (result) {
setKeyword('');
const newData = [...dataRef.current, {name: ''}];
setData(newData);
dataRef.current = newData;
editItem(newData[newData.length-1]);
setTimeout(() => {
document.getElementById(`field-`)?.scrollIntoView();
}, 200)
}
})
}
const removeItemLogic = (record) => {
const newData = [...data];
const index = newData.findIndex((item) => record.name === item.name);
if (index !== -1) {
newData.splice(index, 1);
}
setData(newData);
dataRef.current = newData;
onChange && onChange(newData);
}
const removeItem = (record) => {
if (record.name !== editingKey) {
removeItemLogic(record);
} else {
setEditingKey(null);
editingKeyRef.current = null;
removeItemLogic(record);
}
}
const onSearchInputChange = (value) => {
save().then(result => {
if (result) {
setKeyword(value);
}
});
}
const editItemLogic = (record) => {
form.resetFields();
try {
let dataTypeJson = JSON.parse(record?.dataType);
form.setFieldsValue({...record, dataType: dataTypeJson});
setEditingKey(record?.name);
editingKeyRef.current = record?.name;
} catch {
form.setFieldsValue({...record, dataType: {}});
setEditingKey(record?.name);
editingKeyRef.current = record?.name;
}
}
const editItem = (record) => {
save().then((result) => {
if (result) {
editItemLogic(record);
}
});
};
const save = async() => {
try {
if (editingKeyRef.current !== null) {
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 = [...dataRef.current];
const index = newData.findIndex((item) => editingKeyRef.current === item.name);
const newDataExcludeSelf = [...dataRef.current];
newDataExcludeSelf.splice(index, 1);
//判断字段名称是否唯一
let _index = (newDataExcludeSelf||[]).findIndex(item => item.name === row.name);
if (_index !== -1) {
form.setFields([{ name: 'name', errors: ['字段名称不能重复'] }]);
return false;
}
row.dataType = JSON.stringify(row.dataType);
const item = newData[index];
let attribute = { ...item, ...row};
newData.splice(index, 1, attribute);
setEditingKey(null);
editingKeyRef.current = null;
setData(newData);
dataRef.current = newData;
onChange && onChange(newData);
}
return true;
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
return false;
}
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = data[dragIndex];
const newData = update(data, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragRow],
],
});
setData(newData);
dataRef.current = newData;
onChange && onChange(newData);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const onTableRow = (record, index) => {
let rowParams = {
index,
id: `field-${record.name}`,
};
if (editable) {
if (!isEditing(record)) {
rowParams = {...rowParams, onClick: (event) => {
event.stopPropagation();
editItem(record);
}
}
if (keyword.length===0) {
rowParams = {...rowParams, moveRow};
}
}
}
return rowParams;
}
const mergedColumns = columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
colTitle: col.title,
editing: isEditing(record),
datatypes: supportedDatatypes,
require: col.require
}),
};
});
return (
<div>
<div className='d-flex mb-3' style={{ justifyContent: 'space-between' }}>
<Space>
<h3 style={{ marginBottom: 0 }}>字段信息</h3>
{
editable && <Popover content='点击行进行编辑,表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='pointer' />
</Popover>
}
</Space>
<Space>
{
editable && <Button onClick={onAddClick}>新建</Button>
}
<div className='d-flex' style={{ alignItems: 'center' }}>
<InputDebounce
placeholder="请输入中文名称或者英文名称"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth }}
/>
</div>
</Space>
</div>
<div id="containerId" ref={tableRef}>
<DndProvider backend={HTML5Backend} >
<Form form={form} component={false}>
<Table
rowKey='_id'
loading={loading}
dataSource={filterData}
columns={mergedColumns}
pagination={false}
components={{
body: {
cell: EditableCell,
//编辑或者搜索状态下不允许拖动
row: (editable&&editingKey===null&&keyword==='')?DragableBodyRow:null,
},
}}
onRow={onTableRow}
/>
</Form>
</DndProvider>
</div>
</div>
);
}
export default UpdateField;
\ No newline at end of file
import { useMemo } from "react";
import { Form, Input, Radio } from 'antd';
const UpdateNodeForm = (props) => {
const {form, type, node} = props;
const radioDisable = useMemo(() => {
return !node;
}, [node])
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="comment"
rules={[{ required: true, message: '请输入描述!' }]}
>
<Input />
</Form.Item>
</Form>
);
}
export default UpdateNodeForm;
\ No newline at end of file
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Modal, Form, Button } from "antd";
import UpdateBasicInfo from "./UpdateBasicInfo";
import UpdateField from "./UpdateField";
import { showMessage } from '../../../../../util';
import { dispatch } from '../../../../../model';
import './UpdateTemplateModal.less';
export const EditTemplateContext = React.createContext({
attrIsEditingFunction: null,
});
export const UpdateTemplateModal = (props) => {
const { visible, onCancel, action = 'add', template, nodeId } = props;
const [form] = Form.useForm();
const [fields, setFields] = useState([]);
const [confirmLoading, setConfirmLoading] = useState(false);
const attrIsEditingRef = useRef(false);
useEffect(() => {
if (visible) {
form?.setFieldsValue({name: template?.name||'', cnName: template?.cnName||'', comment: template?.comment||''});
setFields([]);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
const title = useMemo(() => {
if (action === 'add') {
return '新建模版';
}
if (action === 'detail') {
return '模版详情';
}
if (action === 'update') {
return '编辑模版';
}
return '';
}, [action])
const attrIsEditingFunction = (value) => {
attrIsEditingRef.current = value;
}
const onFieldChange = (values) => {
setFields(values);
}
const save = (e) => {
e.stopPropagation();
if (attrIsEditingRef.current) {
showMessage("warn", '还有字段正在编辑,需先保存该字段!');
} else {
saveLogic();
}
}
const saveLogic = async () => {
try {
const row = await form.validateFields();
if ((fields||[]).length === 0) {
showMessage('warn', '请新增字段');
return;
}
setConfirmLoading(true);
let url = '', newTemplate = {};
if (action === 'add') {
url = 'msd.addModel';
newTemplate = {...row, nodeId: nodeId||''};
} else {
url = 'msd.updateModel';
newTemplate = {...template, ...row}
}
dispatch({
type: url,
payload: {
data: newTemplate
},
callback: id => {
(fields||[]).forEach((field, index) => {
field.parentId = id;
field.seq = index;
});
dispatch({
type: 'msd.saveColumns',
payload: {
params: {
modelId: id
},
data: fields
},
callback: () => {
setConfirmLoading(false);
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
});
},
error: () => {
setConfirmLoading(false);
}
});
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
return (
<EditTemplateContext.Provider value={{
attrIsEditingFunction,
}}>
<Modal
forceRender
className='update-template-modal'
width='80%'
title={title}
visible={visible}
onCancel={() => {onCancel && onCancel();}}
onOk={save}
footer={[
<Button key="cancel" onClick={() => {onCancel && onCancel();}}>
取消
</Button>,
action!=='detail' && <Button
key="ok"
type="primary"
loading={confirmLoading}
onClick={save}
>
确定
</Button>,
]}
>
<UpdateBasicInfo form={form} editable={action!=='detail'} template={template} />
<UpdateField editable={action!=='detail'} template={template} onChange={onFieldChange} visible={visible} />
</Modal>
</EditTemplateContext.Provider>
);
}
.update-template-modal {
.yy-modal-body {
max-height: 70vh;
overflow: auto;
}
.dynamic-delete-button {
position: relative;
top: 4px;
margin: 0 8px;
color: #999;
font-size: 24px;
cursor: pointer;
transition: all 0.3s;
}
.dynamic-delete-button:hover {
color: #777;
}
}
\ No newline at end of file
import { useMemo, useState } from 'react';
import classNames from 'classnames';
import { ResizableBox } from 'react-resizable';
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import DefineTree from './Component/DefineTree';
import DefineTable from './Component/DefineTable';
import './index.less';
const DataMasterDefine = (props) => {
const [collapse, setCollapse] = useState(false);
const [nodeId, setNodeId] = useState('');
const classes = useMemo(() => {
return classNames('data-master', {
'data-master-collapse': collapse,
});
}, [collapse]);
const onTreeClick = (value) => {
setNodeId(value);
}
const onCollapseClick = () => {
setCollapse(!collapse);
}
return (
<div className={classes}>
<ResizableBox
className='left-wrap'
width={230}
height={Infinity}
axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
>
<DefineTree onClick={onTreeClick} />
</ResizableBox>
<div className='left-collapse-wrap'>
<div className='left-collapse' onClick={onCollapseClick}>
{ collapse ? <CaretRightOutlined /> : <CaretLeftOutlined /> }
</div>
</div>
<div className='right-wrap'>
<DefineTable nodeId={nodeId} />
</div>
</div>
)
}
export default DataMasterDefine;
\ No newline at end of file
.data-master {
display: flex;
flex: auto;
height: 100%;
background-color: #fff;
.left-wrap {
flex: none;
overflow: hidden;
height: 100%;
border-right: 1px solid #EFEFEF;
}
.left-collapse-wrap {
flex: none;
position: relative;
width: 20px;
height: 100%;
.left-collapse {
display: inline-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-wrap {
flex: 1;
overflow: hidden;
height: 100%;
}
}
.data-master-collapse {
.left-wrap {
width: 0 !important;
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Button, Upload, Drawer, Pagination, Form, Spin } from 'antd';
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
import ResizeableTable from '../../../ResizeableTable';
import { dispatch, dispatchLatest } from '../../../../../model';
import { showMessage, formatDate } from '../../../../../util';
const ExpandedRow = (props) => {
const {data} = props;
const [loading, setLoading] = useState(false);
const [log, setLog] = useState({});
useEffect(() => {
getLog();
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
const getLog = () => {
setLoading(true);
dispatch({
type: 'msd.importLog',
payload: {
logId: data?.id
},
callback: data => {
setLoading(false);
setLog(data||{});
},
error: () => {
setLoading(false);
}
})
}
return <Spin spinning={loading}>
<p style={{ margin: 0 }}>{log?.message||''}</p>
</Spin>
}
const ImportDataDrawer = (props) => {
const { onCancel, onSuccess, visible, nodeId } = 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 columns = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
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,
render: (text, record, __) => {
return text.split(':')[0];
}
},
{
title: '导入状态',
dataIndex: 'state',
width: 100,
ellipsis: true,
}
]
useEffect(() => {
if (visible) {
setPagination({ pageNum: 1, pageSize: 20 });
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
useEffect(() => {
if (visible) {
getLogs();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [pagination])
const getLogs = () => {
setLoading(true);
dispatch({
type: 'msd.importLogs',
payload: {
modelId: nodeId,
page: pageNum,
size: pageSize
},
callback: data => {
setLoading(false);
setTotal(data.totalElements);
setLogs(data.content||[]);
},
error: () => {
setLoading(false);
}
})
}
const onRefreshClick = () => {
getLogs();
}
const changeCurrent = (page,size) => {
setPagination({ pageNum: page, pageSize: size });
}
const downloadTemplate = () => {
window.open(`/api/metadatarepo/rest/msdMgr/exportTemplate?modelId=${nodeId}`);
}
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: ".xlsx",
};
const handleOk = () => {
if ((fileList || []).length === 0) {
showMessage('info', '请先选择Excel文件上传');
return;
}
setConfirmLoading(true);
dispatchLatest({
type: 'msd.importData',
payload: {
params: {
modelId: nodeId,
},
fileList
},
callback: data => {
setConfirmLoading(false);
setFileList([]);
getLogs();
onSuccess && onSuccess();
},
error: () => {
setConfirmLoading(false);
}
})
}
const reset = () => {
setConfirmLoading(false);
setFileList([]);
}
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='Excel上传:'>
<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.Item>
<Button icon={<DownloadOutlined />} onClick={downloadTemplate }>
模版下载
</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>
<ResizeableTable
className='mt-3'
columns={columns}
rowKey={'id'}
dataSource={logs||[]}
pagination={false}
loading={loading}
expandable={{
expandedRowRender: record => <ExpandedRow data={record} />
}}
sticky
/>
<Pagination
className="text-center mt-3"
showSizeChanger
showQuickJumper
onChange={changeCurrent}
onShowSizeChange={changeCurrent}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={total}
pageSizeOptions={[10,20]}
showTotal={total => `共 ${total} 条`}
/>
</Drawer>
)
}
export default ImportDataDrawer;
\ No newline at end of file
import { useMemo, useState, useEffect } from 'react';
import { Space, Button, Input, Pagination, Tooltip } from 'antd';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import ResizeableTable from '../../../ResizeableTable';
import DebounceInput from '../../../Model/Component/DebounceInput';
import UpdateDataMasterModal from './UpdateDataMasterModal';
import ImportDataDrawer from './ImportDataDrawer';
import { inputWidth, showMessage } from '../../../../../util';
import { dispatch } from '../../../../../model';
import '../../Define/Component/DefineTable.less';
import 'react-contexify/dist/ReactContexify.css';
const InputDebounce = DebounceInput(300)(Input);
const ManageTable = (props) => {
const {nodeId} = props;
const [keyword, setKeyword] = useState('');
const [checkedKeys, setCheckedKeys] = useState([]);
const [isDataMasterModalVisible, setIsDataMasterModalVisible] = useState(false);
const [currentData, setCurrentData] = useState(null);
const [action, setAction] = useState('');
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const [fields, setFields] = useState([]);
const [tableData, setTableData] = useState([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState({pageNum: 1, pageSize: 20});
const [importDataDrawerVisible, setImportDataDrawerVisible] = useState(false);
const {pageNum, pageSize} = pagination;
const columns = useMemo(() => {
const newColumns = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
}
];
(fields||[]).forEach(item => {
newColumns.push({
title: item.name,
dataIndex: item.name,
width: 200,
ellipsis: true,
});
})
return newColumns;
}, [fields]);
const MENU_ID = 'data-master-manage-table-contextmenu';
const { show } = useContextMenu({
id: MENU_ID,
});
useEffect(() => {
setCheckedKeys([]);
setPagination({...pagination, pageNum: 1});
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodeId])
useEffect(() => {
if (!nodeId || nodeId==='') {
setFields([]);
setTableData([]);
setTotal(0);
} else {
getFiledsAndDatas();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [keyword, pagination])
const getFiledsAndDatas = () => {
setLoading(true);
dispatch({
type: 'msd.getColumns',
payload: {
params: {
modelId: nodeId
}
},
callback: (data) => {
setFields(data||[]);
const newKeyList = [];
(data||[]).forEach(item => {
newKeyList.push(item.name||'');
});
dispatch({
type: 'msd.getDatas',
payload: {
params: {
modelId: nodeId,
keyList: newKeyList.join(','),
keyword,
pageNum,
pageSize
}
},
callback: (data) => {
setLoading(false);
setTableData(data?.content||[]);
setTotal(data?.totalElements||0);
},
error: () => {
setLoading(false);
}
});
},
error: () => {
setLoading(false);
}
});
}
const onAddClick = () => {
setAction('add');
setCurrentData(null);
setIsDataMasterModalVisible(true);
}
const onImportClick = () => {
setImportDataDrawerVisible(true);
}
const onDataMasterModalCancel = (refresh = false) => {
setIsDataMasterModalVisible(false);
if (refresh) {
getFiledsAndDatas();
}
}
const onBatchDeleteClick = () => {
setDeleteLoading(true);
dispatch({
type: 'msd.deleteDatas',
payload: {
data: checkedKeys||[]
},
callback: (data) => {
setDeleteLoading(false);
showMessage('success', '删除成功');
getFiledsAndDatas();
},
error: () => {
setDeleteLoading(false);
}
});
}
const onSearchInputChange = (value) => {
setKeyword(value);
}
const onTableSelectChange = keys => {
setCheckedKeys(keys);
};
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
if (key === 'detail') {
setAction('detail');
setIsDataMasterModalVisible(true);
} if (key === 'update') {
setAction('update');
setIsDataMasterModalVisible(true);
}
}
const changeCurrent = (page,size) => {
setCheckedKeys([]);
setPagination({ pageNum: page, pageSize: size });
}
const onImportDataDrawerCancel = () => {
setImportDataDrawerVisible(false);
}
const onImportDataSuccess = () => {
getFiledsAndDatas();
}
const rowSelection = {
selectedRowKeys: checkedKeys,
onChange: onTableSelectChange,
};
return (
<div className='data-master-table'>
<div className='data-master-header px-3'>
<Space>
<Space>
<Button onClick={onAddClick}>新建</Button>
</Space>
<Space>
<Button onClick={onImportClick}>导入</Button>
</Space>
<Space>
<Tooltip title={(checkedKeys||[]).length===0?'请先选择主数据':''}>
<Button onClick={onBatchDeleteClick} disabled={(checkedKeys||[]).length===0} loading={deleteLoading}>删除</Button>
</Tooltip>
</Space>
</Space>
<InputDebounce
placeholder="请输入关键字"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth, marginLeft: 'auto' }}
/>
</div>
<div className='data-master-content p-3'>
<ResizeableTable
rowKey='_id'
loading={loading}
rowSelection={rowSelection}
columns={columns}
dataSource={tableData}
onRow={(record) => {
return {
onContextMenu: event => {
setCurrentData(record);
show(event);
},
};
}}
pagination={false}
scroll={{ y: 'calc(100vh - 94px - 32px - 57px - 24px - 39px - 36px)' }}
/>
<Pagination
size="small"
className="text-center m-3"
showSizeChanger
showQuickJumper
onChange={changeCurrent}
onShowSizeChange={changeCurrent}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={total}
showTotal={total => `共 ${total} 条`}
/>
</div>
<UpdateDataMasterModal
action={action}
data={currentData}
fields={fields}
nodeId={nodeId}
visible={isDataMasterModalVisible}
onCancel={onDataMasterModalCancel}
/>
<ImportDataDrawer
visible={importDataDrawerVisible}
nodeId={nodeId}
onCancel={onImportDataDrawerCancel}
onSuccess={onImportDataSuccess}
/>
<RcMenu id={MENU_ID}>
<RcItem id="detail" onClick={handleItemClick}>
详情
</RcItem>
<RcItem id="update" onClick={handleItemClick}>
编辑
</RcItem>
</RcMenu>
</div>
);
}
export default ManageTable;
\ No newline at end of file
import {useEffect, useMemo, useState, useRef} from 'react';
import {Tooltip, Spin, AutoComplete, Tree} from 'antd';
import {ReloadOutlined} from '@ant-design/icons';
import { dispatch } from '../../../../../model';
import {highlightSearchContentByTerms} from '../../../../../util';
import '../../Define/Component/DefineTree.less';
const {Option} = AutoComplete;
const ManageTree = (props) => {
const {onClick} = props;
const [loading, setLoading] = useState(false);
const [data, setData] = useState();
const [options, setOptions] = useState([]);
const [keyword, setKeyword] = useState('');
const [expandedKeys, setExpandedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(false);
const selectedKeysRef = useRef([]);
useEffect(() => {
getTreeNodes();
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const treeData = useMemo(() => {
const loop = (data) =>
(data||[]).map((item) => {
if (item.children) {
return {
title: item.name||'',
key: item._id||'',
origin: item,
children: loop(item.children),
};
}
return {
title: item.name||'',
key: item._id||'',
};
});
return loop(data);
}, [data])
const treeList = useMemo(() => {
const generateList = (data, list, path = null) => {
(data||[]).forEach(node => {
const {_id, name} = node;
const currentPath = path ? `${path}/${name}` : name;
list.push({key: _id, title: currentPath});
if (node.children) {
generateList(node.children, list, currentPath);
}
});
};
const newTreeList = [];
generateList(data, newTreeList);
return newTreeList;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
const getTreeNodes = () => {
setLoading(true);
dispatch({
type: 'msd.getMgrTreeNodes',
callback: (data) => {
setLoading(false);
setData(data||[]);
const firstModelNodeId = findFirstModelNode(data||[]);
if ((selectedKeysRef.current||[]).length===0 && (data||[]).length>0) {
onTreeSelect([firstModelNodeId]);
}
if ((expandedKeys||[]).length===0 && (data||[]).length>0) {
setExpandedKeys([firstModelNodeId]);
setAutoExpandParent(true);
} else {
setExpandedKeys(Array.from(new Set([...expandedKeys, ...selectedKeysRef.current])));
setAutoExpandParent(true);
}
},
error: () => {
setLoading(false);
}
})
}
const onRefreshClick = () => {
getTreeNodes();
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText);
setOptions(treeList.filter(item => item.title.indexOf(searchText)!==-1));
};
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/');
setKeyword(paths[paths.length-1]);
if (isModelNode(option.key)) {
onTreeSelect([option.key]);
}
setExpandedKeys(Array.from(new Set([...expandedKeys, option.key])));
setAutoExpandParent(true);
};
const onAutoCompleteClear = () => {
setKeyword('');
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
}
const onTreeSelect = (selectedKeys, e = null) => {
if (selectedKeys.length === 0) {
return;
}
if (e && e?.node && !isModelNode(e?.node?.key)) {
return;
}
setSelectedKeys(selectedKeys);
selectedKeysRef.current = selectedKeys;
onClick && onClick(selectedKeys[0]);
}
const isModelNode = (value) => {
return ((value||'').indexOf('Model=') !== -1);
}
const findFirstModelNode = (values) => {
const generateList = (data, list) => {
(data||[]).forEach(node => {
list.push(node);
if (node.children) {
generateList(node.children, list);
}
});
};
const newTreeList = [];
generateList(values, newTreeList);
let firstModelNodeId = '';
newTreeList.some(item => {
if (isModelNode(item._id)) {
firstModelNodeId = item._id;
}
return isModelNode(item._id);
})
return firstModelNodeId;
}
return (
<div className='data-master-tree'>
<div className='header p-3'>
<Tooltip title="刷新目录" className='ml-2'>
<ReloadOutlined
className='default'
onClick={onRefreshClick}
style={{
fontSize:16,
cursor:'pointer',
flex: 1
}}
/>
</Tooltip>
<div style={{flex: 3}} />
</div>
<div className='content p-3'>
<Spin spinning={loading}>
<AutoComplete
className='content-search'
allowClear
value={keyword}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={onAutoCompleteClear}
>
{
(options||[]).map((item, index) => {
return (
<Option key={item.key} value={item.title}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.title, [keyword])}
</div>
</Option>
);
})
}
</AutoComplete>
<Tree
className='content-tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData}
onExpand={onTreeExpand}
onSelect={onTreeSelect}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
/>
</Spin>
</div>
</div>
);
}
export default ManageTree;
\ No newline at end of file
import { useEffect, useState, useMemo } from "react";
import { Modal, Form, Input, Button, Descriptions } from "antd";
import { dispatch } from "../../../../../model";
import './UpdateDataMasterModal.less';
const UpdateDataMasterModal = (props) => {
const {visible, onCancel, data, fields, action = 'add', nodeId} = props;
const [confirmLoading, setConfirmLoading] = useState(false);
const [form] = Form.useForm();
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 },
},
};
useEffect(() => {
if (visible) {
let newFieldsValue = {};
(fields||[]).forEach(item => {
newFieldsValue[item.name] = data?data[item.name]:'';
});
form?.setFieldsValue(newFieldsValue);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, data, fields])
const title = useMemo(() => {
if (action === 'add') {
return '新建主数据';
}
if (action === 'detail') {
return '主数据详情';
}
if (action === 'update') {
return '编辑主数据';
}
return '';
}, [action])
const onFormFinish = async() => {
try {
const row = await form.validateFields();
setConfirmLoading(true);
let url = '', newData = {};
if (action === 'add') {
url = 'msd.addData';
newData = row;
} else {
url = 'msd.updateData';
newData = {...data, ...row};
}
dispatch({
type: url,
payload: {
params: {
modelId: nodeId,
},
data: newData
},
callback: () => {
setConfirmLoading(false);
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
});
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
return (
<Modal
className='update-data-master-modal'
title={title}
visible={visible}
onCancel={() => { onCancel && onCancel(false); }}
onOk={onFormFinish}
footer={[
<Button key="cancel" onClick={() => {onCancel && onCancel();}}>
取消
</Button>,
action!=='detail' && <Button
key="ok"
type="primary"
loading={confirmLoading}
onClick={onFormFinish}
>
确定
</Button>,
]}
>
{
(action!=='detail') ? <Form
form={form}
{...formItemLayout}
>
{
(fields||[]).map((item, index) => {
return (
<Form.Item key={index} label={item.name} name={item.name} rules={[{ required: false }]}>
<Input />
</Form.Item>
)
})
}
</Form> : <Descriptions column={1}>
{
(fields||[]).map((item, index) => {
return (
<Descriptions.Item label={<div style={{ textAlign: 'right', width: 85 }}>{item.name||''}</div>} >{data?data[item.name||'']:''}</Descriptions.Item>
);
})
}
</Descriptions>
}
</Modal>
);
}
export default UpdateDataMasterModal;
\ No newline at end of file
.update-data-master-modal {
.yy-modal-body {
max-height: 70vh;
overflow: auto;
}
}
\ No newline at end of file
import { useMemo, useState } from 'react';
import classNames from 'classnames';
import { ResizableBox } from 'react-resizable';
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import ManageTree from './Component/ManageTree';
import ManageTable from './Component/ManageTable';
import '../Define/index.less';
const DataMasterManage = (props) => {
const [collapse, setCollapse] = useState(false);
const [nodeId, setNodeId] = useState('');
const classes = useMemo(() => {
return classNames('data-master', {
'data-master-collapse': collapse,
});
}, [collapse]);
const onTreeClick = (value) => {
setNodeId(value);
}
const onCollapseClick = () => {
setCollapse(!collapse);
}
return (
<div className={classes}>
<ResizableBox
className='left-wrap'
width={230}
height={Infinity}
axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
>
<ManageTree onClick={onTreeClick} />
</ResizableBox>
<div className='left-collapse-wrap'>
<div className='left-collapse' onClick={onCollapseClick}>
{ collapse ? <CaretRightOutlined /> : <CaretLeftOutlined /> }
</div>
</div>
<div className='right-wrap'>
<ManageTable nodeId={nodeId} />
</div>
</div>
)
}
export default DataMasterManage;
\ No newline at end of file
......@@ -2,40 +2,50 @@ import { useState, useEffect } from 'react';
import { Modal, Button, Switch, Row, Col, Checkbox, Typography } from 'antd';
import { dispatch } from '../../../../model';
const cols = [
{title: '模型名称', require: true},
{title: '中文名称'},
{title: '路径'},
{title: '状态'},
{title: '创建人'},
{title: '版本号'},
{title: '模型描述'},
];
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();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible]);
const getPreference = () => {
const getAttrs = () => {
setLoadingAttrs(true);
dispatch({
type: 'datamodel.getPreference',
type: 'pds.getAttrs',
payload: {
modelName: 'DataService'
},
callback: data => {
if ((data.cols||'') === '') {
onCheckAllChange(true);
} else {
setCheckedKeys(data.cols.split(','));
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));
}
})
}
......@@ -44,14 +54,8 @@ const ColSettingModal = (props) => {
const newCheckedKeys = [];
if (checked) {
cols.forEach(col => {
newCheckedKeys.push(col.title);
});
} else {
cols.forEach(col => {
if (col.require) {
newCheckedKeys.push(col.title);
}
attrs?.forEach(col => {
newCheckedKeys.push(col.name);
});
}
......@@ -74,17 +78,24 @@ const ColSettingModal = (props) => {
}
const onModalOk = () => {
if ((checkedKeys||[]).length === 0) {
showMessage('warn', '不可进行全不选操作');
return;
}
setConfirmLoading(true);
dispatch({
type: 'datamodel.savePreference',
type: 'pds.saveCols',
payload: {
data: {
cols: checkedKeys.join(',')
}
params: {modelName: 'DataService'},
data: checkedKeys?.map(item => {
return { titleCnName: item }
})
},
callback: () => {
setConfirmLoading(false);
onCancel && onCancel(true);
showMessage('success', '操作成功');
},
error: () => {
setConfirmLoading(false);
......@@ -123,16 +134,24 @@ const ColSettingModal = (props) => {
/>
</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>
{
cols.map((col, index) => {
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.title||'')!==-1 } value={col.title} onChange={onCheckChange} disabled={col.require} >
<Checkbox checked={ checkedKeys.indexOf(col.name||'')!==-1 } value={col.name} onChange={onCheckChange} >
</Checkbox>
<Typography.Paragraph className='ml-1' title={col.title} ellipsis>
{col.title}
<Typography.Paragraph className='ml-1' title={col.name} ellipsis>
{col.name}
</Typography.Paragraph>
</div>
</Col>
......@@ -141,6 +160,10 @@ const ColSettingModal = (props) => {
}
</Row>
</div>
)
})
}
</div>
</Modal>
)
}
......
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 [loadingOwners, setLoadingOwners] = 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 = () => {
setLoadingOwners(true);
dispatch({
type: 'pds.getOwners',
callback: (data) => {
setLoadingOwners(false);
setOwners(data);
},
error: () => {
setLoadingOwners(false);
}
})
}
const onOwnerChange = (value) => {
setCurrentOwner(value);
if (value) {
dispatch({
type: 'pds.saveOwner',
payload: {
jobNumber: value
}
})
}
}
const cancel = (refresh = false) => {
reset();
onCancel?.(refresh);
}
const onOk = () => {
setLoading(true);
dispatch({
type: 'pds.changeOwner',
payload: {
params: {
id,
ownerName: currentOwner
}
},
callback: () => {
setLoading(false);
cancel(true);
},
error: () => {
setLoading(false);
}
})
}
const reset = () => {
setService(undefined);
setLoading(false);
setLoadingOwners(false);
}
const footer = [
<Button
key="0"
onClick={() => { cancel() }}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={onOk}
>
确定
</Button>,
];
return (
<Modal
className='exchange-owner'
visible={visible}
title='更换管理人'
width={800}
destroyOnClose
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' loading={loadingOwners} 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
......@@ -30,14 +30,14 @@ const ModelNameColumn = (props) => {
ellipsis: true,
},
{
title: '字段中文名称',
title: '名称',
width: 160,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
},
{
title: '字段英文名称',
title: '技术ID(英文名称)',
width: 160,
dataIndex: 'name',
editable: true,
......@@ -193,7 +193,7 @@ const ExpandedModelTable = (props) => {
}
},
{
title: '创建人',
title: '管理人',
dataIndex: 'editor',
width: 100,
ellipsis: true,
......
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
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}
showTotal={total => ` ${total} `}
/>
</div>
)
}
export default FC
\ No newline at end of file
......@@ -14,7 +14,7 @@ const HistoryAndVersionDrawer = (props) => {
title=''
placement="right"
closable={true}
width={'90%'}
width={'40%'}
onClose={() => {
onCancel && onCancel();
}}
......@@ -25,9 +25,9 @@ const HistoryAndVersionDrawer = (props) => {
<TabPane tab="版本历史" key="1">
<VersionHistory id={id} />
</TabPane>
<TabPane tab="版本对比" key="2">
{/* <TabPane tab="版本对比" key="2">
<VersionCompare id={id} />
</TabPane>
</TabPane> */}
</Tabs>
}
</Drawer>
......
......@@ -103,8 +103,6 @@ const AttributesSelect = ({ value = [], modelerData, onChange, mode = 'multiple'
})
}
console.log('currentAttributes', currentAttributes)
triggerChange(currentAttributes);
}
......@@ -523,7 +521,7 @@ const ImportActionHeader = (props) => {
>
<h2 className='mr-3' style={{ marginBottom: 0 }}>基本信息</h2>
{
onlyShowRequireChange ? <Button type='primary' size='small' onClick={onOnlyShowRequireChange}>展开<DownOutlined /></Button> : <Button type='primary' size='small' onClick={onOnlyShowRequireChange}>收起<UpOutlined /></Button>
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>
{
......@@ -637,7 +635,7 @@ const ImportActionHeader = (props) => {
label="技术主键"
name="easyDataModelerPrimaryKey"
>
<AttributesSelect modelerData={modelerData} mode='tags' />
<AttributesSelect modelerData={modelerData} />
</Form.Item>
</Col>
<Col xs={24} sm={24} lg={12} xl={8}>
......
......@@ -882,7 +882,7 @@ const ImportActionIndex = (props) => {
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[dataRef.current, onChange],
[dataRef.current],
);
const onSearchInputChange = (value) => {
......@@ -891,15 +891,15 @@ const ImportActionIndex = (props) => {
}
const displayMenu = (e) => {
show(e);
show({
event: e
})
}
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
if (key === 'up') {
const handleItemClick = ({ id, event, props }) => {
if (id === 'up') {
insertToFront(currentItem);
} else if (key === 'down') {
} else if (id === 'down') {
insertToBack(currentItem);
}
}
......
......@@ -167,15 +167,9 @@ export const EditableCell = ({
let editingComponent = null;
if (editing) {
let inputNode = <InputDebounce />;
if (dataIndex !== 'datatype') {
if (inputType === 'check') {
inputNode = <Checkbox />;
} else if (inputType === 'textarea') {
inputNode = <Input.TextArea autoSize={{ minRows: 1, maxRows: 6 }} />;
} else if (inputType === 'datatype') {
inputNode = <DatatypeInput datatypes={datatypes} />;
}
const inputNode = inputType === 'check' ? <Checkbox /> : <InputDebounce />
editingComponent = (
<Form.Item
......@@ -194,6 +188,26 @@ export const EditableCell = ({
{ 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>
)
}
}
......@@ -440,9 +454,9 @@ export const ImportActionTable = (props) => {
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<Typography.Text ellipsis={true}>
<span>
{highlightSearchContentByTerms(text, termsRef.current)}
</Typography.Text>
</span>
</Tooltip>
)
}
......@@ -930,7 +944,7 @@ export const ImportActionTable = (props) => {
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: {...record, ...{ modelName: modelerData?.name, modelCnName: modelerData?.cnName }},
column: record,
type: record?.isPossibleNewRecommendedDefinition?.type
}
});
......@@ -1107,27 +1121,12 @@ export const ImportActionTable = (props) => {
return col;
}
let inputType = 'text';
if (
col.dataIndex==='notNull'||
col.dataIndex==='partOfDistributionKey' ||
col.dataIndex==='partOfPrimaryKeyLogically' ||
col.dataIndex==='needAttention' ||
col.dataIndex==='foreignKey'
) {
inputType = 'check';
} else if (col.dataIndex === 'remark') {
inputType = 'textarea';
} else if (col.dataIndex === 'datatype') {
inputType = 'datatype';
}
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
inputType,
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,
......@@ -1175,7 +1174,7 @@ export const ImportActionTable = (props) => {
onChange && onChange(newData);
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[moveRowRef.current, onChange],
[moveRowRef.current],
);
const onSearchInputChange = (value) => {
......@@ -1199,15 +1198,15 @@ export const ImportActionTable = (props) => {
}
const displayMenu = (e) => {
show(e);
show({
event: e
})
}
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
if (key === 'up') {
const handleItemClick = ({ id, event, props }) => {
if (id === 'up') {
insertToFront(currentItem);
} else if (key === 'down') {
} else if (id === 'down') {
insertToBack(currentItem);
}
}
......
......@@ -79,7 +79,7 @@ class ImportExcel extends React.Component {
</Col>
<Col span={6}>
<Button icon={<DownloadOutlined />} onClick={ this.downloadTemplate }>
下载
下载
</Button>
</Col>
</Row>
......
......@@ -281,7 +281,6 @@ class ImportMetadata extends React.Component {
<Pagination
showTotal={total => `共 ${total} 条`}
showSizeChanger
size="small"
pageSize={pageSizeDataTables}
pageSizeOptions={['20','60','100']}
current={pageNumDataTables}
......
import React from 'react'
import { Drawer, Form, Pagination, Divider, Upload, Button, Typography, Modal, Spin } from 'antd'
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons'
import { dispatch } from '../../../../model'
import { AppContext } from '../../../../App'
import { showMessage } from '../../../../util'
import Table from '../../ResizeableTable'
const FC = (props) => {
const { onCancel, onSuccess, visible } = props
const [fileList, setFileList] = React.useState([])
const [confirmLoading, setConfirmLoading] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [logs, setLogs] = React.useState([])
const [pagination, setPagination] = React.useState({ pageNum: 1, pageSize: 20 })
const [total, setTotal] = React.useState(0)
const [users, setUsers] = React.useState([])
const [confirmParams, setConfirmParams] = React.useState({
visible: false,
message: undefined,
fileId: undefined,
})
const app = React.useContext(AppContext)
const columns = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '开始时间',
dataIndex: 'startTime',
width: 200,
ellipsis: true,
render: (_, record) => record.startTime ? new Date(record.startTime).toLocaleString() : ''
},
{
title: '结束时间',
dataIndex: 'endTime',
width: 200,
ellipsis: true,
render: (_, record) => record.endTime ? new Date(record.endTime).toLocaleString() : ''
},
{
title: '耗时',
dataIndex: 'costTime',
width: 100,
ellipsis: true,
render: (_, record, __) => {
return record.costTime?`${Number(record.costTime/1000)}秒`:'';
}
},
{
title: '导入人',
dataIndex: 'operator',
width: 100,
ellipsis: true,
render: (_, record) => {
const temp = (record.operator??'').split(':')
if ((temp??[]).length >= 3) {
const user = (users??[]).filter((user)=>user.pernr === temp[2])
if (user && user.length > 0) {
return user[0].nachn?`${user[0].nachn}(${user[0].pernr})`:user[0].pernr
}
return temp[2]
}
return ''
}
},
{
title: '导入状态',
dataIndex: 'state',
ellipsis: true,
},
]
React.useEffect(() => {
if (visible) {
getLogs()
getUsers()
}
}, [visible])
const getUsers = () => {
dispatch({
type: 'pds.getOwners',
callback: (data) => {
setUsers(data);
}
})
}
const downloadTemplate = () => {
window.open('/api/pdataservice/pdsCURD/exportPDSDataServiceTemplate?model=DataService')
}
const getLogs = () => {
setLoading(true)
setPagination(prev => {
dispatch({
type: 'pds.getImportLogs',
payload: {
page: prev.pageNum,
size: prev.pageSize
},
callback: data => {
setLoading(false)
setTotal(data.totalElements)
setLogs(data.content||[])
},
error: () => {
setLoading(false)
}
})
return prev
})
}
const uploadProps = {
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
beforeUpload: file => {
const isLt2OM = file.size / 1024 / 1024 < 20;
if (!isLt2OM) {
showMessage('error', '上传文件必须小于20M');
setFileList([]);
return false;
}
setFileList([file]);
return false;
},
fileList: fileList || [],
accept:".xlsx",
};
const handleOk = () => {
if ((fileList||[]).length === 0) {
showMessage('info', '请先选择文件上传')
return
}
setConfirmLoading(true)
dispatch({
type: 'pds.addImportWithConfirm',
payload: {
fileList: fileList,
params: {
namespace: app?.env?.domainId
},
},
callback: data => {
setConfirmLoading(false)
setFileList([])
if (data?.fileId) {
setConfirmParams({
visible: true,
message: data?.message,
fileId: data?.fileId,
})
} else {
showMessage('error', '上传文件失败')
}
},
error: () => {
setConfirmLoading(false)
}
})
}
const reset = () => {
setConfirmLoading(false)
setPagination({ pageNum: 1, pageSize: 20 })
setFileList([])
}
return (
<Drawer
visible={visible}
title='服务导入'
width={900}
placement="right"
closable
destroyOnClose
onClose={() => {
reset()
onCancel?.()
}}
>
<div className='mt-3'>
<Form layout='inline'>
<Form.Item label='Excel导入:'>
<Button className='mr-2' icon={<DownloadOutlined />} onClick={ downloadTemplate }>
模板下载
</Button>
<Upload style={{ display: 'inline' }} {...uploadProps }>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
<Form.Item>
<Button type='primary' onClick={handleOk} loading={confirmLoading}>确定导入</Button>
</Form.Item>
<Button onClick={() => getLogs()} style={{ marginLeft: 'auto' }}>刷新日志</Button>
</Form>
</div>
<Divider orientation="left">导入日志</Divider>
<Table
className='mt-3'
columns={columns||[]}
rowKey={'id'}
dataSource={logs||[]}
pagination={false}
loading={loading}
expandable={{
expandedRowRender: record => <React.Fragment>
{record.message?.split('\n').map((info, index) => {
return <Typography.Paragraph key={index}>{info}</Typography.Paragraph>
})}
</React.Fragment>
}}
sticky
/>
<Pagination
className="text-center mt-3"
showSizeChanger
onChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum||1, pageSize: _pageSize || 20 });
getLogs();
}}
onShowSizeChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum || 1, pageSize: _pageSize || 20 });
getLogs();
}}
current={pagination.pageNum}
pageSize={pagination.pageSize}
defaultCurrent={1}
total={total}
showTotal={total => `共 ${total} 条`}
/>
<Confirm
{...confirmParams}
onCancel={(refresh) => {
setConfirmParams({
visible: false,
message: undefined,
fileId: undefined,
})
getLogs()
refresh && onSuccess?.()
}}
/>
</Drawer>
)
}
export default FC
const Confirm = ({ visible, message, fileId, onCancel }) => {
const [waiting, setWaiting] = React.useState(false)
const close = () => {
setWaiting(true)
dispatch({
type: 'pds.importConfirm',
payload: {
params: {
fileId,
isContinued: false,
},
},
callback: data => {
showMessage('success', '取消导入成功')
setWaiting(false)
onCancel?.()
},
error: () => {
setWaiting(false)
}
})
}
const save = () => {
setWaiting(true)
dispatch({
type: 'pds.importConfirm',
payload: {
params: {
fileId,
isContinued: true,
},
},
callback: data => {
showMessage('success', '上传文件成功,正在导入。。。')
setWaiting(false)
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消导入</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>继续导入</Button>
]
}, [close, save, waiting])
return (
<Modal
title='导入确认'
width={600}
visible={visible}
footer={footer}
destroyOnClose
maskClosable={false}
onCancel={() => {
close()
}}
bodyStyle={{
height: 400,
overflow: 'auto'
}}
>
<Spin spinning={waiting}>
{
(message??'').split('\n').map((info, index) => {
return <Typography.Paragraph key={index}>{info}</Typography.Paragraph>
})
}
</Spin>
</Modal>
)
}
\ 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
......@@ -43,7 +43,7 @@ export const AttentionSvg = (props) => (
</defs>
<path
d="M826.027 34.133H197.973c-37.546 0-68.266 30.72-68.266 68.267v887.467L512 791.893l382.293 197.974V102.4c0-37.547-30.72-68.267-68.266-68.267zm-148.48 337.92L612.693 435.2c-3.413 3.413-5.12 10.24-5.12 15.36l15.36 87.04c1.707 13.653-11.946 23.893-23.893 17.067L520.533 512c-5.12-3.413-10.24-3.413-15.36 0l-78.506 42.667c-11.947 6.826-27.307-3.414-23.894-17.067l15.36-87.04c1.707-5.12 0-10.24-5.12-15.36l-64.853-63.147c-10.24-10.24-5.12-27.306 8.533-29.013l88.747-13.653c5.12 0 10.24-3.414 11.947-8.534l39.253-80.213c6.827-11.947 23.893-11.947 30.72 0l39.253 80.213c1.707 5.12 6.827 8.534 11.947 8.534l88.747 13.653c13.653 1.707 18.773 18.773 10.24 29.013z"
fill='#196AD2'
fill='#c7000b'
/>
</svg>
)
......
import React, { useState, useEffect, useRef, useMemo } from "react";
import { Tooltip, Modal, Table, Typography } from 'antd';
import LocalStorage from 'local-storage';
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 DataGrid, { defaultPageSize } from '../../VirtualTable';
import { dispatch } from '../../../../model';
import { showMessage, getQueryParam, isSzseEnv, formatDate, getDataModelerRole } from '../../../../util';
import { showMessage, getQueryParam, paginate, isSzseEnv, formatDate, getDataModelerRole } from '../../../../util';
import { AnchorId, AnchorTimestamp, Action, CatalogId, ModelerId, DataModelerRoleReader } from '../../../../util/constant';
import ExpandedModelTable from "./ExpandedModelTable";
import { AppContext } from "../../../../App";
import SelectUser from "./SelectUsers";
// import Tag from "../../Tag";
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import './ModelTable.less';
import 'react-contexify/dist/ReactContexify.css';
const { Paragraph, Text } = Typography;
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;
......@@ -30,14 +55,14 @@ const ModelNameColumn = (props) => {
ellipsis: true,
},
{
title: '字段中文名称',
title: '名称',
width: 160,
dataIndex: 'cnName',
editable: true,
ellipsis: true,
},
{
title: '字段英文名称',
title: '技术ID(英文名称)',
width: 160,
dataIndex: 'name',
editable: true,
......@@ -49,7 +74,6 @@ const ModelNameColumn = (props) => {
if (data.digest) {
_textComponent = <div style={{ width: 500, maxHeight: 300, overflow: 'auto' }}>
<Table
rowKey='name'
dataSource={data.digest.attributeDigests||[]}
columns={cols}
loading={false}
......@@ -73,7 +97,7 @@ const ModelNameColumn = (props) => {
onVisibleChange={(visible) => {
if (visible && !record.digest) {
dispatch({
type: 'datamodel.getDataModelDigest',
type: 'pds.getServiceDigest',
payload: {
id: record.id
},
......@@ -92,219 +116,539 @@ const ModelNameColumn = (props) => {
);
}
const ModelTable = (props) => {
const { data, onChange, onItemAction, onSelect, onHistory, catalogId, keyword, onAutoCreateTable, offset = null, view, modelState, user, selectModelerIds, visibleColNames } = props;
const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...restProps } = props;
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]);
const [ currentItem, setCurrentItem ] = useState(null);
const [ scrollRowIndex, setScrollRowIndex ] = useState();
if (!width) {
return <th {...restProps} />;
}
const expandedDataMapRef = useRef(new Map());
const shouldScrollRef = useRef(false);
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 anchorId = getQueryParam(AnchorId, props?.location?.search);
const anchorTimestamp = getQueryParam(AnchorTimestamp, props?.location?.search);
const gridRef = useRef();
const ModelTable = (props) => {
const [modal, contextHolder] = Modal.useModal();
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 = 'model-table-contextmenu';
const { show } = useContextMenu({
const MENU_ID = (((modelId||'') !== '') ? `model-table-contextmenu-${modelId}` : 'model-table-contextmenu');
const { show, hideAll } = useContextMenu({
id: MENU_ID,
});
const cols = [
{
name: '序号',
key: 'index',
width: 60,
sortable: false,
resizable: true,
},
{
name: '模型名称',
key: 'name',
width: isSzseEnv?360:160,
sortable: true,
resizable: true,
formatter(props) {
return (<ModelNameColumn text={props.row.name} record={props.row} detailItem={detailItem} />);
}
},
{
name: '中文名称',
key: 'cnName',
width: isSzseEnv?420:160,
sortable: true,
resizable: true,
formatter(props) {
return (
<Tooltip title={props.row.cnName||''}>
<Text ellipsis={true}>{props.row.cnName||''}</Text>
</Tooltip>
)
}
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();
},
{
name: '路径',
key: 'path',
width: 120,
sortable: true,
resizable: true,
formatter(props) {
return (
<Tooltip title={props.row.path||''}>
<Text ellipsis={true}>{props.row.path||''}</Text>
</Tooltip>
)
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>
// )
// }
// },
{
name: '状态',
key: 'state',
title: '状态',
dataIndex: 'state',
width: 100,
sortable: true,
resizable: true,
formatter(props) {
ellipsis: true,
render: (_, record) => {
let color = '';
if (props.row.state?.id === '1') {
if (record?.state?.id === '1') {
color = '#DE7777';
} else if (props.row.state?.id === '2') {
} else if (record?.state?.id === '2') {
color = '#779BDE';
} else if (props.row.state?.id === '4') {
} 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>{props.row.state?.cnName||''}</span>
<span>{record?.state?.cnName||''}</span>
</span>
);
}
},
{
name: '创建人',
key: 'editor',
title: '管理人',
dataIndex: 'editor',
width: 100,
sortable: true,
resizable: true,
},
{
name: '版本号',
key: 'modifiedTs',
width: 170,
sortable: true,
resizable: true,
formatter(props) {
return `V_${formatDate(props.row.modifiedTs)}`;
}
},
{
name: '模型描述',
key: 'remark',
sortable: true,
resizable: true,
formatter(props) {
return (
<Tooltip title={props.row.remark||''} overlayClassName='tooltip-common'>
<Text ellipsis={true}>{props.row.remark||''}</Text>
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 columns = useMemo(() => {
let newCols = [...cols];
if ((visibleColNames||[]).length > 0) {
newCols = newCols.filter(col => visibleColNames.indexOf(col.name)!==-1 || col.name==='序号');
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 (visibleColNames.indexOf('模型描述') === -1) {
newCols[newCols.length-1].width = null;
if (record.odata) {
authActionTitles.push('URI复制');
}
authActionTitles.push('样本数据');
authActionTitles.push('历史版本');
if (getDataModelerRole(user)!==DataModelerRoleReader&& view!=='grant'&&record.grantable) {
authActionTitles.push('授权');
}
return newCols;
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibleColNames])
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && isOnlyEnding && !currentItem?.grantable) {
// authActionTitles.push('申请');
// }
const summarySelectedCount = useMemo(() => {
let newSelectedRowKeys = Array.from(new Set([...selectedRowKeys, ...expandedSelectedRowKeys]));
if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && record.supportChangeOwn) {
authActionTitles.push('更换管理');
}
const ids = [];
(data||[]).forEach(item => {
ids.push(item.id);
if (item.alreadyCheckedOut) {
ids.push(item.checkedOutId);
if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant'&& !isOnlyEnding && record.serviceDefinitionType === 'empty') {
authActionTitles.push('拖拉创建字段');
authActionTitles.push('自定义sql创建字段');
}
})
newSelectedRowKeys = (newSelectedRowKeys||[]).filter(key => ids.indexOf(key) !== -1);
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && !isOnlyEnding && record.supportODataDisable) {
// authActionTitles.push('下载Tableau tds');
// }
return (newSelectedRowKeys||[]).length;
}, [selectedRowKeys, expandedSelectedRowKeys, data])
// if (getDataModelerRole(user)!==DataModelerRoleReader && view!=='grant' && record.supportSmartBIWebSpreadSheet) {
// authActionTitles.push('跳转至电子表格');
// }
useEffect(() => {
if (data && gridRef.current) {
setTimeout(() => {
gridRef.current?.scrollToRow(0);
}, 100)
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}` });
});
}
}, [data])
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(() => {
setSelectedRowKeys([]);
setExpandedSelectedRowKeys([]);
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) {
if ((selectModelerIds||[]).length === 0) {
setSelectedRowKeys([]);
setExpandedSelectedRowKeys([]);
setSubSelectedRowKeys([]);
}
}, [selectModelerIds])
useEffect(() => {
if ((anchorId||'') !== '') {
shouldScrollRef.current = true;
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [anchorTimestamp])
useEffect(() => {
if (shouldScrollRef.current && gridRef.current && offset!==null && (data||[]).length>0) {
setScrollRowIndex(offset);
setTimeout(() => {
gridRef.current?.scrollToRow((offset-1)%defaultPageSize);
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;
}, 300)
}
}
})
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: 200,
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') {
expandedDataMapRef.current.delete(LocalStorage.get('modelId'));
getCheckoutDataModel();
}
}
const editItem = (record) => {
onItemAction && onItemAction(record, 'edit');
const getAttrs = () => {
dispatch({
type: 'pds.getAttrs',
payload: {
modelName: 'DataService'
},
callback: data => {
setAttrs(data);
},
error: () => {
}
})
}
const detailItem = (record) => {
onItemAction && onItemAction(record, 'detail', getDataModelerRole(user)===DataModelerRoleReader);
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) => {
......@@ -314,17 +658,18 @@ const ModelTable = (props) => {
const stateAction = (record, action) => {
modal.confirm({
title: '提示!',
content: `您确定要${action.cnName||''}模型吗?`,
content: `您确定要${action.cnName||''}服务?`,
onOk: () => {
dispatch({
type: 'datamodel.nextState',
type: 'pds.nextState',
payload: {
easyDataModelerDataModelId: record.id,
pdsDataServiceId: record.id,
actionId: action.id
},
callback: () => {
showMessage('success', `模型${action.cnName||''}成功`);
showMessage('success', `服务${action.cnName||''}成功`);
if ((modelId||'') === '') {
onChange && onChange();
const index = selectedRowKeys.findIndex((rowKey) => rowKey === record.id);
......@@ -334,6 +679,14 @@ const ModelTable = (props) => {
setSelectedRowKeys(newSelectedRowKeys);
onSelect && onSelect(newSelectedRowKeys);
}
} else {
if (action.id === '2') {
onChange && onChange();
} else {
getCheckoutDataModel();
}
}
}
})
}
......@@ -341,21 +694,23 @@ const ModelTable = (props) => {
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该模型吗?',
content: '您确定要删除该服务吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteDataModel',
type: 'pds.deleteService',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模型删除成功');
showMessage('success', '服务删除成功');
onChange && onChange();
if ((modelId||'') ==='') {
const index = selectedRowKeys.findIndex((rowKey) => rowKey === record.id);
if (index !== -1) {
const newSelectedRowKeys = [...selectedRowKeys];
......@@ -364,6 +719,8 @@ const ModelTable = (props) => {
onSelect && onSelect(newSelectedRowKeys);
}
}
}
})
}
});
......@@ -373,32 +730,113 @@ const ModelTable = (props) => {
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);
onSelect && onSelect([...expandedSelectedRowKeys, ...keys]);
if ((modelId||'') !== '') {
onSubSelect && onSubSelect(keys, subData[0].id);
} else {
onSelect && onSelect([...subSelectedRowKeys, ...keys]);
}
};
const onExpandedTableSelectChange = (keys, id) => {
const onSubSelectChange = (keys, id) => {
if ((keys||[]).length === 0) {
const index = expandedSelectedRowKeys.findIndex((rowKey) => rowKey === id);
const newExpandedSelectedRowKeys = [...expandedSelectedRowKeys];
newExpandedSelectedRowKeys.splice(index, 1);
setExpandedSelectedRowKeys(newExpandedSelectedRowKeys);
onSelect && onSelect([...newExpandedSelectedRowKeys, ...selectedRowKeys]);
const index = subSelectedRowKeys.findIndex((rowKey) => rowKey === id);
const newSubSelectedRowKeys = [...subSelectedRowKeys];
newSubSelectedRowKeys.splice(index, 1);
setSubSelectedRowKeys(newSubSelectedRowKeys);
onSelect && onSelect([...newSubSelectedRowKeys, ...selectedRowKeys]);
} else {
const newExpandedSelectedRowKeys = [...expandedSelectedRowKeys, id];
setExpandedSelectedRowKeys(newExpandedSelectedRowKeys);
onSelect && onSelect([...newExpandedSelectedRowKeys, ...selectedRowKeys]);
const newSubSelectedRowKeys = [...subSelectedRowKeys, id];
onSelect && onSelect([...newSubSelectedRowKeys, ...selectedRowKeys]);
}
}
const onExpandedDataMapChange = (id, value) => {
expandedDataMapRef.current.set(id, value);
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;
(data||[]).forEach(record => {
if (!modelId) {
(filterData||[]).forEach(record => {
if (record?.alreadyCheckedOut) {
needExpand = true;
}
......@@ -406,57 +844,53 @@ const ModelTable = (props) => {
if (needExpand) {
expandable = {
expandedRowHeight: 100,
rowExpandable: (row) => {
return row?.alreadyCheckedOut;
},
expandRowRender: (row) => {
return (
<div style={{ padding: 10 }}>
<ExpandedModelTable
id={row?.checkedOutId}
pid={row?.id}
checked={expandedSelectedRowKeys.indexOf(row?.checkedOutId)!==-1}
onContextMenu={onExpandedTableContextMenu}
dataMap={expandedDataMapRef.current}
visibleColNames={visibleColNames}
onExpandedSelect={onExpandedTableSelectChange}
onExpandedChange={onExpandedDataMapChange}
expandedRowRender: record => <ModelTable
view={view}
modelId={record?.checkedOutId}
modelPid={record?.id}
onSubSelect={onSubSelectChange}
{...props}
/>
</div>
)
/>,
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 onExpandedTableContextMenu = (e, item) => {
setCurrentItem(item);
displayMenu(e);
}
const displayMenu = (e) => {
show(e);
show({
event: e
})
}
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
const onServiceDetailClose = () => {
setServiceDetailParams({ visible: false, id: '' })
}
if (key === 'edit') {
editItem(currentItem);
} else if (key === 'delete') {
deleteItem(currentItem);
} else if (key === 'history') {
historyItem(currentItem);
} else if (key === 'copy') {
window.open(`/data-govern/data-model-action?${Action}=add&${CatalogId}=${(view==='dir')?(catalogId||''):''}&${ModelerId}=${currentItem.id}`);
} else if (key === 'createTable') {
deployAction(currentItem);
} else if (key.indexOf('action') !== -1) {
const index = (key.split('-'))[1];
const action = currentItem?.state?.supportedActions[index];
stateAction(currentItem, action);
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 = '编辑';
......@@ -465,90 +899,89 @@ const ModelTable = (props) => {
disableEdit = true;
if (currentItem?.state?.id === '2') {
editTip = '待发布的模型不允许编辑';
editTip = '待发布的服务不允许编辑';
}
}
if (!currentItem?.permitCheckOut && currentItem?.state?.id==='4') {
disableEdit = true;
editTip = `${currentItem.holder||''}正在编辑中, 不允许再编辑`;
editMenuTitle = `编辑(${currentItem.holder||''}正在编辑中)`;
}
// 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 = '待发布的模型不允许删除';
deleteTip = '待发布的服务不允许删除';
} else if (currentItem?.state?.id === '4') {
deleteTip = '已发布的模型不允许删除';
deleteTip = '已发布的服务不允许删除';
}
}
return (
<div>
<div className='flex' style={{ height: 20, alignItems: 'center', marginBottom: 12 }}>
<Paragraph style={{ overflow: 'hidden' }}>
<Text className='title-color' ellipsis={true}>
总数:
<Text className='text-color'>{(data||[]).length}</Text>
</Text>
</Paragraph>
<Paragraph style={{ overflow: 'hidden', marginLeft: 20 }}>
<Text className='title-color' ellipsis={true}>
已选数:
<Text className='text-color'>{summarySelectedCount}</Text>
</Text>
</Paragraph>
</div>
<DataGrid
gridRef={gridRef}
style={{ blockSize: 'calc(100vh - 94px - 37px - 57px - 24px - 32px)' }}
checkable
columns={columns}
// rows={Array.from({ length: 10000 }).map((_, i) => ({
// name: `test${i}`,
// }))}
rows={data||[]}
rowHeight={51}
rowClassName={(row) => {
return (row.id === anchorId)?'anchor':''
<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}`,
onContextMenu: event => {
setCurrentItem(record);
// displayMenu(event);
},
}
}}
rowClassName={(record, index) => (record?.id===anchorId)?'yy-table-select-row':''}
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
onChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: _pageNum, pageSize: _pageSize || 20 });
}}
scrollRowIndex={scrollRowIndex}
expandable={expandable}
onContextMenu={(e, row) => {
setCurrentItem(row);
displayMenu(e);
onShowSizeChange={(_pageNum, _pageSize) => {
setPagination({ pageNum: 1, pageSize: _pageSize });
}}
selectedRows={selectedRowKeys}
onSelectedRowsChange={onSelectChange}
getComparator={getComparator}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={(data||[]).length}
showTotal={total => ` ${total} `}
/>
}
<RcMenu id={MENU_ID}>
{
(getDataModelerRole(user)!==DataModelerRoleReader) && <RcItem id="edit" disabled={disableEdit} onClick={handleItemClick}>
<Tooltip title={editTip}>
{ editMenuTitle }
</Tooltip>
{/* {
(getDataModelerRole(user)!==DataModelerRoleReader) && view!=='grant' && !isOnlyEnding && <RcItem id="edit" disabled={!currentItem?.editable&&!currentItem?.permitCheckOut} onClick={handleItemClick}>
编辑
</RcItem>
}
{
(getDataModelerRole(user)!==DataModelerRoleReader) && <RcItem id="delete" disabled={disableDelete} onClick={handleItemClick}>
(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) && <RcItem id="copy" onClick={handleItemClick}>
复制模型
</RcItem>
}
{
getDataModelerRole(user)!==DataModelerRoleReader && (currentItem?.state?.supportedActions||[]).length>0 && currentItem?.state?.supportedActions.map((item, index) => {
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||''}
......@@ -557,38 +990,49 @@ const ModelTable = (props) => {
})
}
{
getDataModelerRole(user)!==DataModelerRoleReader &&currentItem?.deployable && <RcItem id='createTable' onClick={handleItemClick}>
建表
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
function getComparator(sortColumn) {
switch (sortColumn) {
case 'name':
case 'cnName':
case 'editor':
case 'remark':
case 'path':
return (a, b) => {
return a[sortColumn].localeCompare(b[sortColumn]);
};
case 'state':
return (a, b) => {
return a[sortColumn].id.localeCompare(b[sortColumn].id);
};
case 'modifiedTs':
return (a, b) => {
return a[sortColumn] - b[sortColumn];
};
default:
throw new Error(`unsupported sortColumn: "${sortColumn}"`);
}
}
import React, { useState, useEffect, useContext } from "react";
import React, { useState, useEffect, useContext, useMemo } from "react";
import { Tooltip, Tree, Modal, Spin, Dropdown, Menu, Button, AutoComplete } from "antd";
import { PlusOutlined, SyncOutlined, ImportOutlined, UnorderedListOutlined, ReloadOutlined } from '@ant-design/icons';
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";
......@@ -22,7 +22,7 @@ const viewModes = [
},
{
key: 'state',
name: '模型状态视角'
name: '服务状态视角'
}
];
......@@ -34,11 +34,12 @@ const ModelTree = (props) => {
id: MENU_ID,
});
const { onSelect, onViewChange, refrence='', importStockModel, keyword } = props;
const { onSelect, onViewChange, refrence='', importStockModel, keyword, isOnlyEnding = false } = props;
const { user } = useContext(AppContext);
const { user, env } = useContext(AppContext);
const [ loading, setLoading ] = useState(false);
const [ treeData, setTreeData ] = useState(null);
const [rootItem, setRootItem] = useState();
const [ item, setItem ] = useState(null);
const [ prevItem, setPrevItem ] = useState(null);
const [ visible, setVisible ] = useState(false);
......@@ -56,16 +57,18 @@ const ModelTree = (props) => {
const [ dataList, setDataList ] = useState([]);
const [options, setOptions] = useState([]);
const [serviceCountMapping, setServiceCountMapping] = 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(() => {
// getShowSyncAndDomains();
// //eslint-disable-next-line react-hooks/exhaustive-deps
// }, [])
useEffect(() => {
if (refrence === 'recatalog') {
......@@ -97,7 +100,7 @@ const ModelTree = (props) => {
} else {
if (prevItem && !item) {
setItem(prevItem);
onSelect && onSelect(prevItem?.key||'');
onSelect && onSelect(prevItem?.key||'', null, prevItem?.key===rootId);
setPrevItem(null);
}
}
......@@ -105,20 +108,46 @@ const ModelTree = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ keyword ])
const getShowSyncAndDomains = () => {
useEffect(() => {
getServiceCount()
}, [viewSelectedKey])
// 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 getServiceCount = () => {
dispatch({
type: 'datamodel.isSetRootDomainId',
type: (viewSelectedKey==='dir') ? 'pds.loadDataServiceCatalogServiceCount': 'pds.loadDataServiceStateCatalogServiceCount',
callback: data => {
if (data === 'false') {
dispatch({
type: 'user.getDomains',
callback: _data => {
setDomains(_data||[]);
setIsSetRootId(false);
if (viewSelectedKey === 'dir') {
let newData = {}
for (const key in (data||{})) {
let total = 0
const item = data[key]
for (const id in item) {
total = total + item[id]??0
}
});
newData[key] = total
}
setServiceCountMapping(newData)
} else {
setIsSetRootId(true);
setServiceCountMapping(data)
}
}
});
......@@ -127,12 +156,13 @@ const ModelTree = (props) => {
const getDataModelLocationThenGetDirTreeData = () => {
setLoading(true);
dispatch({
type: 'datamodel.getDataModelLocation',
type: 'pds.getDataServiceLocation',
payload: {
id
pdsDataServiceId: id,
namespace: `${env?.domainId}`
},
callback: data => {
getDirTreeData(data.easyDataModelerDataModelCatalogId||'', data.offset);
getDirTreeData(data.pdsDataServiceCatalogId||'', data.offset);
},
error: () => {
setLoading(false);
......@@ -145,7 +175,7 @@ const ModelTree = (props) => {
setLoading(true);
dispatch({
type: (type==='refresh')?'datamodel.refreshDataModelCatalog':'datamodel.loadDataModelCatalog',
type: (type==='refresh')?'pds.refreshCatalog':'pds.loadDataServiceCatalog',
callback: data => {
data.key = data.id||'';
......@@ -175,6 +205,7 @@ const ModelTree = (props) => {
setLoading(false);
setTreeData(data.subCatalogs||[]);
setRootId(data.id||'');
setRootItem(data);
const _dataList = [];
generateList(data.subCatalogs||[], _dataList);
......@@ -198,14 +229,14 @@ const ModelTree = (props) => {
} else if (refrence === '') {
const currentItem = (data.subCatalogs||[]).length>0?data.subCatalogs[0]: null;
setItem(currentItem);
if (currentItem && currentItem.key) {
setExpandedKeys([currentItem?.key]);
}
// const currentItem = (data.subCatalogs||[]).length>0?data.subCatalogs[0]: null;
onSelect && onSelect(currentItem?(currentItem.key||''):'');
// setItem(currentItem);
// if (currentItem && currentItem.key) {
// setExpandedKeys([currentItem?.key]);
// }
setItem(data);
onSelect && onSelect(data?.id, null, true);
}
},
......@@ -218,7 +249,10 @@ const ModelTree = (props) => {
const getStateTreeData = () => {
setLoading(true);
dispatch({
type: 'datamodel.loadDataModelStateCatalog',
type: 'pds.loadStateCatalog',
payload: {
isOnlyEnding
},
callback: data => {
setLoading(false);
let _treeData = data?.subCatalogs||[];
......@@ -285,27 +319,32 @@ const ModelTree = (props) => {
}
}
const onSyncMenuClick = ({ key }) => {
// const onSyncMenuClick = ({ key }) => {
setDomainSelectedKey(key);
dispatch({
type: 'datamodel.setRootDomainId',
payload: {
params: {
domainId: key
}
},
callback: () => {
setIsSetRootId(true);
getDirTreeData('', null, 'load');
}
});
// setDomainSelectedKey(key);
// dispatch({
// type: 'datamodel.setRootDomainId',
// payload: {
// params: {
// domainId: key
// }
// },
// callback: () => {
// setIsSetRootId(true);
// getDirTreeData('', null, 'load');
// }
// });
}
// }
const onTreeSelect = (keys,data) => {
if ((keys||[]).length === 0) {
if (viewSelectedKey === 'dir') {
setItem(rootItem);
onSelect && onSelect(rootId, null, true);
}
return;
}
......@@ -331,6 +370,8 @@ const ModelTree = (props) => {
} else {
getStateTreeData(item?.key||'');
}
getServiceCount();
}
const sync = () => {
......@@ -340,9 +381,9 @@ const ModelTree = (props) => {
const moveNode = (steps) => {
setLoading(true);
dispatch({
type: 'datamodel.upDownModelCatalog',
type: 'pds.upDownCatalog',
payload: {
modelCatalogId: currentRightClickDir.id,
pdsDataServiceCatalogId: currentRightClickDir.id,
steps
},
callback: () => {
......@@ -358,14 +399,14 @@ const ModelTree = (props) => {
const deleteNode = () => {
modal.confirm({
title: '提示!',
content: '删除目录会删除相关的模型,您确定删除吗?',
content: '删除目录会删除相关的服务,您确定删除吗?',
onOk: () => {
setLoading(true);
dispatch({
type: 'datamodel.deleteDataModelCatalog',
type: 'pds.deleteCatalog',
payload: {
params: {
easyDataModelerCatalogId: currentRightClickDir.id
pdsDataServiceCatalogId: currentRightClickDir.id
}
},
callback: () => {
......@@ -453,7 +494,8 @@ const ModelTree = (props) => {
};
const displayMenu = (e) => {
show(e, {
show({
event: e,
position: {
x: e.clientX + 30,
y: e.clientY - 10
......@@ -477,17 +519,17 @@ const ModelTree = (props) => {
</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 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')
......@@ -503,7 +545,7 @@ const ModelTree = (props) => {
borderBottom: "1px solid #EFEFEF",
height: 57,
alignItems: 'center',
justifyContent: (viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin)?'space-between':'',
// justifyContent: (viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin)?'space-between':'',
}}
>
<Dropdown overlay={exportMenu} placement="bottomLeft">
......@@ -513,38 +555,38 @@ const ModelTree = (props) => {
</Dropdown>
{
(viewSelectedKey==='dir' && getDataModelerRole(user)===DataModelerRoleAdmin) && (
<Tooltip title="新增目录" className='ml-2'>
<PlusOutlined className='default' onClick={add} style={{ fontSize:16,cursor:'pointer' }} />
</Tooltip>
)
// (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-2'>
<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="同步目录">
<SyncOutlined className='default ml-2' style={{ fontSize:16,cursor:'pointer' }} />
<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-2'>
<Button type='text' icon={<SyncOutlined className='default' />} size='small' onClick={sync} />
<Tooltip title="同步目录" className='ml-6'>
<Button type='text' icon={<SwapOutlined className='default' style={{ transform: 'rotate(90deg)' }} />} size='small' onClick={sync} />
</Tooltip>
)
}
......@@ -557,6 +599,7 @@ const ModelTree = (props) => {
allowClear
value={searchKeyword}
style={{ marginBottom: 10, width: '100%' }}
placeholder='搜索目录'
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onChange={onAutoCompleteChange}
......@@ -576,6 +619,7 @@ const ModelTree = (props) => {
</AutoComplete>
}
<Tree
className='tree-contextmenu'
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
......@@ -585,10 +629,19 @@ const ModelTree = (props) => {
treeData={treeData}
selectedKeys={[item?item.key:'']}
titleRender={(nodeData) => {
return <span title={nodeData?.remark||''}>{nodeData?.name||''}</span>;
let title = nodeData?.name
if (nodeData.status === -1) {
title = `${title}_已停用`
}
if (serviceCountMapping?.[nodeData.id] !== null && serviceCountMapping?.[nodeData.id] !== undefined) {
title = `${title} (${serviceCountMapping?.[nodeData.id]})`
}
return <span title={nodeData?.remark||''} className='cursor-pointer-contextmenu'>{title}</span>;
}}
onRightClick={({event, node}) => {
if (viewSelectedKey==='dir'&& getDataModelerRole(user)===DataModelerRoleAdmin) {
if (viewSelectedKey==='dir') {
setCurrentRightClickDir(node);
displayMenu(event);
}
......
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, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { Modal, Form, Input } from 'antd';
import LocalStorage from 'local-storage';
import { dispatch } from '../../../../model';
import { dispatchLatest } from '../../../../model';
import { showMessage, showNotifaction } from '../../../../util';
const UpdatePartitionModal = (props) => {
const { visible, onCancel, item, action = 'add' } = props;
const FC = (props) => {
const { visible, onCancel, ids } = props;
const [ form ] = Form.useForm();
const [ confirmLoading, setConfirmLoading ] = useState(false);
......@@ -20,38 +22,25 @@ const UpdatePartitionModal = (props) => {
},
};
useEffect(() => {
if (visible) {
if (action !== 'add') {
form.setFieldsValue({ name: item?.name||'', cnName: item?.cnName||'', partitionMethod: item?.partitionMethod||'', definition: item?.definition||'', remark: item?.remark||'' });
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
const handleOk = async () => {
try {
const values = await form.validateFields();
let newItem = null;
if (action === 'add') {
newItem = {...values};
} else {
newItem = {...item, ...values};
}
setConfirmLoading(true);
dispatch({
type: 'datamodel.savePartitionType',
dispatchLatest({
type: 'pds.offline',
payload: {
data: newItem
params: {
reason: values?.desc
},
callback: data => {
setConfirmLoading(false);
data: ids
},
callback: () => {
reset();
showMessage('success', '停用成功')
onCancel && onCancel(true);
},
error: () => {
......@@ -73,7 +62,7 @@ const UpdatePartitionModal = (props) => {
<Modal
forceRender
visible={visible}
title={action==='add'?'新增分区':'编辑分区'}
title='服务停用'
width={520}
confirmLoading={confirmLoading}
onCancel={() => {
......@@ -87,41 +76,15 @@ const UpdatePartitionModal = (props) => {
form={form}
>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入分区名称' }]}
>
<Input />
</Form.Item>
<Form.Item
label="中文名称"
name="cnName"
rules={[{ required: true, message: '请输入分区中文名称' }]}
>
<Input />
</Form.Item>
<Form.Item
label="分区方式"
name="partitionMethod"
rules={[{ required: true, message: '请输入分区方式' }]}
>
<Input />
</Form.Item>
<Form.Item
label="定义"
name="definition"
>
<Input.TextArea row={4} />
</Form.Item>
<Form.Item
label="描述"
name="remark"
label="停用原因"
name="desc"
// rules={[{ required: true, message: '请填写停用原因' }]}
>
<Input.TextArea row={4} />
<Input.TextArea rows={6} />
</Form.Item>
</Form>
</Modal>
);
}
export default UpdatePartitionModal;
\ No newline at end of file
export default FC;
\ No newline at end of file
......@@ -17,17 +17,17 @@ const RecatalogModal = (props) => {
const onOk = () => {
if ((catalogId||'') === '') {
showMessage('warn', '请先选择模型目录');
showMessage('warn', '请先选择服务目录');
return;
}
setConfirmLoading(true);
dispatch({
type: 'datamodel.recatalogDataModel',
type: 'pds.recatalogService',
payload: {
params: {
easyDataModelCatalogId: catalogId,
easyDataModelerDataModelIds: ids.join(',')
pdsDataServiceCatalogId: catalogId,
pdsDataServiceIds: ids.join(',')
},
},
callback: message => {
......
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, Typography} from "antd"
import debounce from 'lodash/debounce';
import { highlightSearchContentByTerms } from '../../../../util';
const {Option} = Select
const SelectUser:React.FC=(props)=>{
const {value,onChange,users,type,loading=false, terms,publishedValue} = 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'){
return <span>
<Typography.Text>{highlightSearchContentByTerms(value, terms)}</Typography.Text>
{
publishedValue && publishedValue!==value && <Typography.Text className='ml-2' type='danger'><del>{publishedValue}</del></Typography.Text>
}
</span>
// try {
// const user = (users??[]).filter((item)=>(item.pernr===value))
// let publishedUser = null;
// if (publishedValue && publishedValue !== value) {
// publishedUser = (users??[]).filter((item)=>(item.pernr===publishedValue))
// }
// return <span>
// <Typography.Text>{highlightSearchContentByTerms(`${user[0].nachn}(${user[0].pernr})`, terms)}</Typography.Text>
// {
// publishedUser && <Typography.Text className='ml-2' type='danger'><del>{`${publishedUser[0].nachn}(${publishedUser[0].pernr})`}</del></Typography.Text>
// }
// </span>;
// } catch (error) {
// return <span>
// <Typography.Text>{highlightSearchContentByTerms(value, terms)}</Typography.Text>
// {
// publishedValue && publishedValue!==value && <Typography.Text className='ml-2' type='danger'><del>{publishedValue}</del></Typography.Text>
// }
// </span>
// }
}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 { 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, Typography } from 'antd';
import { Table, Tooltip } from 'antd';
import { Resizable } from 'react-resizable';
import ResizeObserver from 'rc-resize-observer';
......@@ -123,7 +123,7 @@ const SuggestTable = (props) => {
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<Typography.Text ellipsis={true}>{text||''}</Typography.Text>
<span>{text||''}</span>
</Tooltip>
)
}
......
......@@ -70,6 +70,12 @@ class UpdateTreeItemForm extends React.Component {
>
<Input />
</Form.Item>
<Form.Item
label="编码"
name="code"
>
<Input />
</Form.Item>
</Form>
);
}
......@@ -89,12 +95,12 @@ const UpdateTreeItemModal = (props) => {
_action = item ? 'sub' : 'root';
}
form.setFields([{ name: 'name', errors: [] }, { name: 'remark', errors: [] }]);
form.setFields([{ name: 'name', errors: [] }, { name: 'remark', errors: [] }, { name: 'code', errors: [] }]);
if (type === 'add') {
form.setFieldsValue({ action: _action, name: '', remark: '' });
form.setFieldsValue({ action: _action, name: '', remark: '', code: '' });
} else {
form.setFieldsValue({ action: '', name: item?item.name:'', remark: item?item.remark:'' });
form.setFieldsValue({ action: '', name: item?item.name:'', remark: item?item.remark:'', code: item?.code });
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -112,12 +118,14 @@ const UpdateTreeItemModal = (props) => {
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 {
......@@ -125,11 +133,12 @@ const UpdateTreeItemModal = (props) => {
...item,
name: values.name||'',
remark: values.remark||'',
code: values.code||'',
}
}
dispatchLatest({
type: 'datamodel.saveDataModelCatalog',
type: 'pds.saveCatalog',
payload: {
data: payload
},
......
......@@ -22,7 +22,7 @@ const VersionHistory = (props) => {
const getVersions = () => {
setLoading(true);
dispatch({
type: 'datamodel.getVersions',
type: 'pds.getVersions',
payload: {
params: {
id
......@@ -39,7 +39,7 @@ const VersionHistory = (props) => {
}
const onVersionItemClick = (version) => {
window.open(`/data-govern/data-model-action?${Action}=detail-version&${ModelerId}=${version.dataModelId||''}&${VersionId}=${version.id||''}`);
// window.open(`/data-govern/data-model-action?${Action}=detail-version&${ModelerId}=${version.dataModelId||''}&${VersionId}=${version.id||''}`);
}
return (
......
import React from 'react';
import { Button, Space, Spin, Input, Select, Tooltip } from 'antd';
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';
......@@ -14,12 +14,18 @@ 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 ImportServices from './Component/ImportServices';
import download from '../../../util/download';
import './index.less';
......@@ -29,8 +35,8 @@ const InputDebounce = DebounceInput(300)(Input);
class Model extends React.Component {
constructor() {
super();
constructor(props) {
super(props);
this.state = {
importModalVisible: false,
importStockWordDrawerVisible: false,
......@@ -50,7 +56,8 @@ class Model extends React.Component {
hints: [],
loadingStates: false,
modelStates: [],
currentModelState: '',
currentModelState: props.isOnlyEnding?'4_0':'',
currentODataState: '0',
currentView: '',
exportDDLModalReference: 'exportDDL',
currentModel: {},
......@@ -59,6 +66,15 @@ class Model extends React.Component {
showDeleteTip: false,
colSettingModalVisible: false,
visibleColNames: [],
startAuthorizeParams: {
visible: false,
services: []
},
startReleaseVisible: false,
offlineVisible: false,
jdbcInformationVisible: false,
isRoot: false,
importServicesVisible: false,
}
}
......@@ -82,11 +98,14 @@ class Model extends React.Component {
getModelStates = () => {
this.setState({ loadingStates: true }, () => {
dispatch({
type: 'datamodel.loadDataModelStateCatalog',
type: 'pds.loadStateCatalog',
payload: {
isOnlyEnding: this.props.isOnlyEnding
},
callback: data => {
this.setState({
loadingStates: false,
modelStates: [{ name: 'all', id: '', cnName: '所有状态' }, ...(data?.subCatalogs||[])]
modelStates: this.props.isOnlyEnding ? data?.subCatalogs||[] : [{ name: 'all', id: '', cnName: '所有状态' }, ...(data?.subCatalogs||[])]
});
},
error: () => {
......@@ -98,14 +117,12 @@ class Model extends React.Component {
getPreference = () => {
dispatch({
type: 'datamodel.getPreference',
type: 'pds.getCols',
payload: {
modelName: 'DataService'
},
callback: data => {
this.setState();
if ((data.cols||'') === '') {
this.setState({visibleColNames: []});
} else {
this.setState({visibleColNames: data.cols.split(',')});
}
this.setState({visibleColNames: data?.map(item => item.titleCnName)});
}
})
}
......@@ -120,9 +137,14 @@ class Model extends React.Component {
})
}
onTreeSelect = (key, offset=null) => {
onODataStateChange = (value) => {
this.setState({ currentODataState: value, offset: null }, () => {
this.onTableChange();
})
}
this.setState({ catalogId: key, keyword: '', offset, currentModelState: (offset!==null)?'':this.state.currentModelState }, () => {
onTreeSelect = (key, offset=null, isRoot = false) => {
this.setState({ catalogId: key, keyword: '', offset, currentModelState: this.state.currentModelState, isRoot }, () => {
if (!key || key==='') {
this.setState({ tableData: [], filterTableData: [] });
} else {
......@@ -132,14 +154,16 @@ class Model extends React.Component {
}
onTableChange = () => {
const { currentView, catalogId, keyword, currentModelState } = this.state;
const { currentView, catalogId, keyword, currentModelState, currentODataState } = this.state;
this.setState({ loadingTableData: true }, () => {
if (keyword === '') {
if (currentView === 'dir') {
const params = {
easyDataModelerCatalogId: catalogId,
pdsDataServiceCatalogId: catalogId,
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
};
if (currentModelState !== '') {
......@@ -147,10 +171,19 @@ class Model extends React.Component {
}
dispatchLatestHomepage({
type: 'datamodel.getCurrentDataModelCatalog',
type: 'pds.getServices',
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data.easyDataModelerDataModels||[], filterTableData: data.easyDataModelerDataModels||[] });
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 });
......@@ -158,12 +191,22 @@ class Model extends React.Component {
})
} else {
dispatchLatestHomepage({
type: 'datamodel.getCurrentDataModelStateCatalog',
type: 'pds.getStateServices',
payload: {
easyDataModelerStateCatalogId: catalogId
pdsDataServiceStateCatalogId: catalogId,
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
},
callback: data => {
this.setState({ loadingTableData: false, tableData: data.easyDataModelerDataModels||[], filterTableData: data.easyDataModelerDataModels||[] });
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 });
......@@ -173,7 +216,9 @@ class Model extends React.Component {
} else {
const params = {
term: keyword,
term: encodeURIComponent(keyword),
namespace: `${this.props.app?.env?.domainId}`,
isExcludeOtherOwner: !this.props.isOnlyEnding
};
if (currentModelState !== '') {
......@@ -181,10 +226,18 @@ class Model extends React.Component {
}
dispatchLatestHomepage({
type: 'datamodel.searchModel',
type: 'pds.searchService',
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: 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 });
......@@ -212,7 +265,7 @@ class Model extends React.Component {
}
onSearchInputChange = (value) => {
this.setState({ keyword: value||'', catalogId: '' }, () => {
this.setState({ keyword: value||'', catalogId: '', isRoot: true }, () => {
if (value !== '') {
this.onTableChange();
}
......@@ -228,11 +281,11 @@ class Model extends React.Component {
onExportDDLBtnClick = () => {
const { selectModelerIds, tableData } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择模型');
showMessage('info', '请先选择服务');
return;
}
//模型名称在导出ddl的时候有使用
//服务名称在导出ddl的时候有使用
const _selectModelerNames = [];
(selectModelerIds||[]).forEach(id => {
......@@ -255,7 +308,7 @@ class Model extends React.Component {
startFlow = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择模型');
showMessage('info', '请先选择服务');
return;
}
......@@ -265,18 +318,111 @@ class Model extends React.Component {
onRecatalogBtnClick = () => {
const { selectModelerIds } = this.state;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择模型');
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 })
}
onImportClick = () => {
this.setState({ importServicesVisible: true });
}
onExportClick = () => {
const { modal } = this.props;
const { catalogId } = this.state;
modal?.confirm({
title: '提示',
content: '是否确认导表格中所有数据?',
onOk: () => {
dispatch({
type: 'pds.exportAll',
payload: {
responseType: 'blob',
params: {
pdsDataServiceCatalogId: catalogId,
namespace: this.props.app?.env?.domainId,
isExcludeOtherOwner: this.props.isOnlyEnding
}
},
callback: (res) => {
download(res)
}
})
},
});
}
onSubscribeBtnClick = () => {
const { selectModelerIds } = this.state;
const { modal } = this.props;
if ((selectModelerIds||[]).length === 0) {
showMessage('info', '请先选择服务');
return;
}
modal?.confirm({
title: '提示',
content: '是否确认收藏选中的服务?',
onOk: () => {
dispatch({
type: 'assetmanage.addSubscribe',
payload: {
params: {
appType: 'pDataService',
resourceIds: (selectModelerIds??[]).toString(),
env: this.props.app?.env?.domainId,
}
},
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', '请先选择模型');
showMessage('info', '请先选择服务');
return;
}
......@@ -404,6 +550,36 @@ class Model extends React.Component {
}
}
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 });
}
......@@ -421,7 +597,7 @@ class Model extends React.Component {
const { catalogId } = this.state;
if ((catalogId||'') === '') {
showMessage('info', '请先选择模型目录');
showMessage('info', '请先选择服务目录');
return;
}
......@@ -464,7 +640,8 @@ class Model extends React.Component {
}
render() {
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 } = this.state;
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
......@@ -473,16 +650,14 @@ class Model extends React.Component {
let disableStartFlow = false, startFlowTip = '';
if ((currentView==='dir'&&currentModelState==='2')||(currentView!=='dir'&&catalogId==='2')) {
disableStartFlow = true;
startFlowTip = '只有草稿状态下的模型才能送审';
startFlowTip = '只有草稿状态下的服务才能送审';
} else if ((selectModelerIds||[]).length===0) {
disableStartFlow = true;
startFlowTip = '请先选择模型';
startFlowTip = '请先选择服务';
}
return (
<AppContext.Consumer>
{
value => <div className={classes}>
<div className={classes}>
<ResizableBox
className='left'
width={230}
......@@ -508,44 +683,104 @@ class Model extends React.Component {
>
<Space>
{
(getDataModelerRole(value?.user)!==DataModelerRoleReader) && <React.Fragment>
currentView==='dir' && (getDataModelerRole(app?.user)!==DataModelerRoleReader) && !isOnlyEnding && <React.Fragment>
<Space>
<Button onClick={() => { this.setState({ importModalVisible: true }); }}>新建</Button>
<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']}
>
<Tooltip title={this.state.isRoot?'请先选择目录':''}>
<Button disabled={this.state.isRoot}>新建</Button>
</Tooltip>
</Dropdown>
</Space>
<Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择模型':''}>
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onExportOtherBtnClick} disabled={(selectModelerIds||[]).length===0}>导出</Button>
</Tooltip>
</Space>
</Space> */}
<Space>
{/* <Space>
<Tooltip title={startFlowTip}>
<Button onClick={this.startFlow} disabled={disableStartFlow}>送审</Button>
</Tooltip>
</Space>
</Space> */}
<Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择模型':''}>
{/* <Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择服务':''}>
<Button onClick={this.onRecatalogBtnClick} disabled={(selectModelerIds||[]).length===0}>变更目录</Button>
</Tooltip>
</Space>
</Space> */}
<Space>
<Tooltip title={(selectModelerIds||[]).length===0?'请先选择模型':''}>
{/* <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> */}
{/* <Space>
<Button onClick={this.onVisibleColSettingClick}>可见列设置</Button>
</Space> */}
</React.Fragment>
}
<Space>
<Button onClick={this.onVisibleColSettingClick}>可见列设置</Button>
</Space>
</React.Fragment>
<Space>
<Button onClick={() => { this.setState({jdbcInformationVisible: true}); }}>JDBC信息</Button>
</Space>
{
(getDataModelerRole(app?.user)!==DataModelerRoleReader) && !isOnlyEnding && <Button onClick={this.onImportClick}>导入</Button>
}
{
(getDataModelerRole(app?.user)!==DataModelerRoleReader) && !isOnlyEnding && (currentView==='dir') &&
<Tooltip title={this.state.isRoot?'请先选择目录':''}>
<Button onClick={this.onExportClick} disabled={this.state.isRoot}>导出</Button>
</Tooltip>
}
{
(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) => {
......@@ -565,8 +800,22 @@ class Model extends React.Component {
</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="通过模型名称全文搜索"
placeholder="通过服务名称全文搜索"
allowClear
value={keyword}
onChange={(value) => { this.onSearchInputChange(value); }}
......@@ -581,7 +830,8 @@ class Model extends React.Component {
<Spin spinning={loadingTableData}>
<ModelTable
loading={loadingTableData}
user={value?.user}
user={app?.user}
isOnlyEnding={isOnlyEnding}
catalogId={catalogId}
view={currentView}
data={filterTableData}
......@@ -600,35 +850,6 @@ class Model extends React.Component {
</div>
</div>
<ImportModal
view={currentView}
catalogId={catalogId}
visible={importModalVisible}
onCancel={this.onImportModalCancel}
onCancelByWord={this.onImportModalCancelByWord}
onCancelByDDL={this.onImportModalCancelByDDL}
/>
<ImportStockWordDrawer
visible={importStockWordDrawerVisible}
onCancel={this.onImportStockWordDrawerCancel}
catalogId={catalogId}
onSuccess={this.onImportStockWordSuccess}
/>
<ExportDDLModal
visible={exportDDLModalVisible}
reference={exportDDLModalReference}
ids={(exportDDLModalReference==='exportDDL')?selectModelerIds:[currentModel.id]}
names={selectModelerNames}
env={value?.env}
onCancel={this.onExportDDLModalCancel}
/>
<ExportOtherModal
visible={exportOtherModalVisible}
onCancel={this.onExportOtherModalCancel}
/>
<RecatalogModal
visible={recatalogModalVisible}
......@@ -642,28 +863,60 @@ class Model extends React.Component {
onCancel={this.onHistoryAndVersionDrawerCancel}
/>
<StartFlowModal
<StartAuthorize
visible={this.state.startAuthorizeParams.visible}
services={this.state.startAuthorizeParams.services}
onCancel={this.onStartAuthorizeCancel}
/>
<StartRelease
visible={this.state.startReleaseVisible}
ids={selectModelerIds}
visible={startFlowModalVisible}
onCancel={this.onStartFlowModalCancel}
onCancel={this.onStartReleaseCancel}
/>
<DeleteTipModal
visible={showDeleteTip}
tip='您确定要删除这些模型吗?'
onCancel={this.onDeleteTipModalCancel}
<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 }) }}
/>
<ImportServices
visible={this.state.importServicesVisible}
onCancel={() => { this.setState({ importServicesVisible: false }) }}
onSuccess={() => {
this.onTableChange()
}}
/>
</div>
}
</AppContext.Consumer>
);
}
}
export default Model;
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;
import React, { useEffect, useState } from 'react';
import { Input, Table, Tooltip } from 'antd';
import { inputWidth } from '../../../../util';
import { dispatch } from '../../../../model';
const ConstraintDetail = (props) => {
const [ loading, setLoading ] = useState(false);
const [ rules, setRules ] = useState([]);
const [ keyword, setKeyword ] = useState('');
useEffect(() => {
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraints',
callback: data => {
setLoading(false);
const _rules = [];
(data||[]).forEach(constraint => {
(constraint.allRules||[]).forEach(rule => {
_rules.push({...constraint, ...{ ruleName: rule.name||'', ruleDesc: rule.desc||'' }});
});
});
setRules(_rules);
},
error: () => {
setLoading(false);
}
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (_, __, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '规范名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '规范中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '前置依赖',
dataIndex: 'ruleName',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '描述',
dataIndex: 'ruleDesc',
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
}
]
const onSearchInputChange = (e) => {
setKeyword(e.target.value||'');
}
return (
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<Input
placeholder="请输入前置依赖名称"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: inputWidth }}
/>
</div>
<Table
loading={loading}
columns={columns}
rowKey={'ruleName'}
dataSource={(rules||[]).filter(item => (item.ruleName||'').indexOf(keyword)!==-1)}
pagination={false}
sticky
/>
</div>
);
}
export default ConstraintDetail;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Button, Space } from 'antd';
import LocalStorage from 'local-storage';
import TemplateAction from './TemplateAction';
import { dispatchLatest } from '../../../../model';
import { getQueryParam, showMessage } from '../../../../util';
import { Action, TemplateId } from '../../../../util/constant';
import '../../Model/Component/EditModel.less';
const EditTemplate = (props) => {
const [ actionData, setActionData ] = useState({ action: '', templateId: '', });
const [ templateData, setTemplateData ] = useState({});
const [ confirmLoading, setConfirmLoading ] = useState(false);
const { action, templateId } = actionData;
const [form] = Form.useForm();
useEffect(() => {
const _action = getQueryParam(Action, props.location.search);
const _templateId = getQueryParam(TemplateId, props.location.search);
setActionData({ action: _action, templateId: _templateId });
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const save = async (e) => {
try {
const row = await form.validateFields();
const newTemplateData = {...templateData, ...row};
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.saveTemplate',
payload: {
data: newTemplateData
},
callback: data => {
setConfirmLoading(false);
if (action === 'add') {
showMessage("success", '新增模型成功');
setActionData({ ...actionData, ...{ action: 'detail',templateId: data.id } });
} else {
showMessage("success", '保存模型成功');
setActionData({ ...actionData, ...{ action: 'detail', templateId: data.id||'' } });
}
LocalStorage.set('templateChange', !(LocalStorage.get('templateChange')||false));
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const edit = () => {
setActionData({ ...actionData, action: 'edit' });
}
const cancelEdit = () => {
setActionData({ ...actionData, action: 'detail' });
}
const onActionChange = (data) => {
setTemplateData(data);
}
let title = '';
if (action === 'add') {
title = '新增模版';
} else if (action === 'edit') {
title = '模版编辑';
} else if (action === 'detail') {
title = '模版详情';
}
let actionsBtn = null;
if (action==='add') {
actionsBtn = (
<Space>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
</Space>
)
} else if (action === 'detail') {
actionsBtn = (
<Space>
<Button type='primary' onClick={edit} danger >
编辑
</Button>
</Space>
);
} else if (action === 'edit') {
actionsBtn = (
<Space>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
<Button onClick={cancelEdit} >
取消
</Button>
</Space>
)
}
return (
<div className='edit-model position-relative'>
<div className='edit-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>{title}</span>
</div>
<div className='edit-container'>
<div className='edit-container-card'>
<TemplateAction onChange={onActionChange} action={action} id={templateId} form={form} />
</div>
</div>
<div className='edit-footer'>
{actionsBtn}
</div>
</div>
);
}
export default EditTemplate;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Table, Button, Tooltip, Modal, Divider } from 'antd';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
import UpdatePartitionModal from './UpdatePartitionModal';
const PartitionCURD = (props) => {
const [ partitions, setPartitions ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ actionData, setActionData ] = useState({ action: '', updataPartitionModalVisible: false, item: null });
const { action, updataPartitionModalVisible, item } = actionData;
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
getPartitions();
}, [])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '分区名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '分区中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '分区方式',
dataIndex: 'partitionMethod',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '定义',
dataIndex: 'definition',
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip
placement='topLeft'
title={
<div style={{ maxWidth: 400, maxHeight: 300, overflow: 'auto' }}>
{text||''}
</div>
}
overlayClassName='tooltip-common'
>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '描述',
dataIndex: 'remark',
width: 180,
ellipsis: true,
render: (text, _, __) => {
return (
<Tooltip title={text||''}>
<span>{text||''}</span>
</Tooltip>
)
}
},
{
title: '操作',
key: 'action',
width: 120,
render: (text,record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
type='link'
size='small'
onClick={() => { editItem(record); }}
style={{ padding: 0 }}
>
编辑
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Button
type='link'
size='small'
onClick={() => { deleteItem(record); }}
style={{ padding: 0 }}
>
删除
</Button>
</div>
)
}
}
];
const getPartitions = () => {
setLoading(true);
dispatch({
type: 'datamodel.getSupportedPartitionTypes',
payload: {
excludeBuiltin: true
},
callback: data => {
setPartitions(data||[]);
setLoading(false);
},
error: () => {
setLoading(false);
}
})
}
const editItem = (record) => {
setActionData({...actionData, ...{action: 'edit', updataPartitionModalVisible: true, item: record }});
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该分区吗?',
onOk: () => {
dispatch({
type: 'datamodel.deletePartitionType',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模版分区成功');
getPartitions();
}
})
}
});
}
const onAddClick = () => {
setActionData({...actionData, ...{action: 'add', updataPartitionModalVisible: true }});
}
const onUpdatePartitionModalCancel = (refresh = false) => {
setActionData({...actionData, updataPartitionModalVisible: false} );
refresh && getPartitions();
}
return (
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<Button
onClick={onAddClick}
>
新建
</Button>
</div>
<Table
loading={loading}
columns={columns}
rowKey={'id'}
dataSource={partitions||[]}
pagination={false}
sticky
/>
<UpdatePartitionModal
visible={updataPartitionModalVisible}
action={action}
item={item}
onCancel={onUpdatePartitionModalCancel}
/>
{ contextHolder }
</div>
);
}
export default PartitionCURD;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Spin, Tabs, Popover, Divider, Button, Space } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import TemplateActionHeader from './TemplateActionHeader';
import { ImportActionTable } from '../../Model/Component/ImportActionTable';
import { dispatchLatest, dispatch } from '../../../../model';
const { TabPane } = Tabs;
const TemplateAction = (props) => {
const { action, onChange, form, id } = props;
const [ templateData, setTemplateData ] = useState(null);
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ tabKey, setTabKey ] = useState('1');
const [ loading, setLoading ] = useState(false);
useEffect(() =>{
//初始化form状态
if (action==='add'||action==='edit') {
form.setFieldsValue({
cnName: '',
name: '',
remark: '',
});
}
if (action === 'add') {
getSupportedDatatypes();
} else if ((action === 'edit'||action === 'detail') && id ) {
getCurrentTemplate();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [action, id ]);
const getCurrentTemplate = () => {
setLoading(true);
dispatchLatest({
type: 'datamodel.getTemplate',
payload: {
params: {
id
}
},
callback: data => {
setTemplateData(data||{});
onChange && onChange(data||{});
getSupportedDatatypes();
if (action === 'edit') {
form.setFieldsValue({
cnName: data.cnName||'',
name: data.name||'',
remark: data.remark||'',
});
}
},
error: () => {
setLoading(false);
}
})
}
const getSupportedDatatypes = () => {
setLoading(true);
dispatch({
type: 'datamodel.getSupportedDatatypes',
callback: data => {
setLoading(false);
setSupportedDatatypes(data||[]);
},
error: () => {
setLoading(false);
}
});
}
//模版不需要对字段进行校验
const onTableChange = (data) => {
let newTemplateData = {...templateData, ...{easyDataModelerDataModelAttributes: data}};
setTemplateData(newTemplateData);
onChange && onChange(newTemplateData);
}
const onTabChange = (key) => {
setTabKey(key);
}
const prevStep = () => {
setTabKey(`${Number(tabKey)-1}`);
}
const nextStep = () => {
setTabKey(`${Number(tabKey)+1}`);
}
return (
<Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
</TabPane>
<TabPane
tab={
<span>
<span>数据表结构</span>
{
(action!=='detail'&&action!=='flow'&&action!=='detail-version') && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
}
</span>
}
key='2'
>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
</TabPane>
</Tabs>
{
action!=='detail'&&(
<React.Fragment>
<Divider style={{ margin: 0 }} />
<div className='flex' style={{ justifyContent: 'flex-end', height: 64, alignItems: 'center' }}>
<Space size='small'>
<Button type='primary' onClick={prevStep} disabled={tabKey==='1'} danger>上一步</Button>
<Button type='primary' onClick={nextStep} disabled={tabKey==='2'} danger>下一步</Button>
</Space>
</div>
</React.Fragment>
)
}
</Spin>
);
};
export default TemplateAction;
\ No newline at end of file
import React from 'react';
import { Form, Input, Row, Col, Descriptions } from 'antd';
const { TextArea } = Input;
const TemplateActionHeader = (props) => {
const { editable, form, templateData } = props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 19 },
},
};
return (
editable ? (
<Form
form={form}
{...formItemLayout}
>
<Row>
<Col span={12}>
<Form.Item
label="中文名称"
name="cnName"
labelAlign="left"
rules={[{ required: true, message: '请输入中文名称!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="英文名称"
name="name"
labelAlign="left"
rules={[{ required: true, message: '请输入英文名称!' }]}
>
<Input />
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="描述"
name="remark"
labelAlign="left"
rules={[{ required: true, message: '请输入描述!' }]}
>
<TextArea row={4} />
</Form.Item>
</Col>
</Row>
</Form>
) : (
<Descriptions column={2}>
<Descriptions.Item label="中文名称">{templateData.cnName||''}</Descriptions.Item>
<Descriptions.Item label="英文名称">{templateData.name||''}</Descriptions.Item>
<Descriptions.Item label="描述">{templateData.remark||''}</Descriptions.Item>
</Descriptions>
)
)
}
export default TemplateActionHeader;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Table, Button, Tooltip, Modal, Divider } from 'antd';
import { Action, TemplateId } from '../../../../util/constant';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
const TemplateCURD = (props) => {
const [ templates, setTemplates ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
getTemplates();
window?.addEventListener("storage", (e) => {
if (e.key === 'templateChange') {
getTemplates();
}
});
}, [])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '模版名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
render: (text, record, _) => {
return (
<Tooltip title={text||''}>
<a onClick={()=>{detailItem(record);}}>
{text||''}
</a>
</Tooltip>
);
}
},
{
title: '中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
},
{
title: '模版描述',
dataIndex: 'remark',
ellipsis: true,
},
{
title: '更新时间',
dataIndex: 'modifiedTs',
width: 120,
ellipsis: true,
},
{
title: '操作',
key: 'action',
width: 120,
render: (text,record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
type='link'
size='small'
onClick={() => { editItem(record); }}
style={{ padding: 0 }}
>
编辑
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Button
type='link'
size='small'
onClick={() => { deleteItem(record); }}
style={{ padding: 0 }}
>
删除
</Button>
</div>
)
}
}
];
const getTemplates = () => {
setLoading(true);
dispatch({
type: 'datamodel.getAllTemplates',
callback: data => {
setTemplates(data||[]);
setLoading(false);
},
error: () => {
setLoading(false);
}
})
}
const editItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=edit&${TemplateId}=${record.id}`);
}
const detailItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=detail&${TemplateId}=${record.id}`);
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该模版吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteTemplate',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模版删除成功');
getTemplates();
}
})
}
});
}
const onAddClick = () => {
window.open(`/data-govern/model-template-action?${Action}=add`);
}
return (
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<Button
onClick={onAddClick}
>
新建
</Button>
</div>
<Table
loading={loading}
columns={columns}
rowKey={'id'}
dataSource={templates||[]}
pagination={false}
sticky
/>
{ contextHolder }
</div>
);
}
export default TemplateCURD;
\ No newline at end of file
import React, { useState } from 'react';
import { Button, Upload, Form, Row, Col } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../../model';
const WordTemplate = (props) => {
const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ form ] = Form.useForm();
const downloadTemplate = () => {
window.open("/api/datamodeler/easyDataModelerExport/word/downloadTemplate");
}
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 normFile = (e) => {
return fileList;
};
const handleOk = async() => {
try {
const row = await form.validateFields();
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.uploadWordTemplate',
payload: { fileList: row.upload },
callback: data => {
setConfirmLoading(false);
reset();
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const reset = () => {
setConfirmLoading(false);
form.resetFields();
setFileList([]);
}
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
return (
<Form form={form} {...layout} onFinish={handleOk}>
<Form.Item
label='最新模版上传'
required={true}
>
<Row>
<Col>
<Form.Item
name='upload'
valuePropName="fileList"
getValueFromEvent={normFile}
noStyle
rules={[
{
required: true,
message: '请选择文件上传',
},
]}
>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
</Col>
<Col>
<Button className='ml-3' icon={<DownloadOutlined />} onClick={ downloadTemplate }>
模版下载
</Button>
</Col>
</Row>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit" loading={confirmLoading}>
确定
</Button>
</Form.Item>
</Form>
)
}
export default WordTemplate;
\ No newline at end of file
import React, { useState } from 'react';
import { Tabs } from 'antd';
import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD';
import ConstraintDetail from './Component/ConstraintDetail';
import PartitionCURD from './Component/PartitionCURD';
import './index.less';
const { TabPane } = Tabs;
const ModelConfig = () => {
const [ tabKey, setTabKey ] = useState('1');
const onTabChange = (key) => {
setTabKey(key);
}
return (
<div className='model-config'>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='Word模版配置' key='1'>
<WordTemplate />
</TabPane>
<TabPane tab='生成表类型配置' key='2'>
<TemplateCURD />
</TabPane>
<TabPane tab='规范配置' key='3'>
<ConstraintDetail />
</TabPane>
<TabPane tab='分区配置' key='4'>
<PartitionCURD />
</TabPane>
</Tabs>
</div>
)
}
export default ModelConfig;
\ No newline at end of file
.model-config {
height: calc(100vh - 64px - 30px);
padding: 20px;
background: #fff;
overflow: auto;
}
\ No newline at end of file
......@@ -6,14 +6,11 @@ import { GetSession } from "../../util";
import { ManageLayout } from "../../layout";
import DatasourceManage from './DatasourceManage';
import Model from './Model';
import ModelConfig from './ModelConfig';
import AssetManage from './AssetManage';
import AssetResourceBrowse from './AssetResourceBrowse';
import AssetBrowse from './AssetBrowse';
import AssetRecycle from './AssetRecycle';
import DataMasterDefine from "./DataMaster/Define";
import DataMasterManage from "./DataMaster/Manage";
import DataService from './Model';
class Manage extends Component {
......@@ -29,14 +26,11 @@ class Manage extends Component {
session && session.userId ? (
<Switch>
<Route path={`${match.path}/datasource-manage`} component={DatasourceManage} />
<Route path={`${match.path}/data-model`} component={Model} />
<Route path={`${match.path}/model-config`} component={ModelConfig} />
<Route path={`${match.path}/asset-manage`} component={AssetManage} />
<Route path={`${match.path}/asset-resource-browse`} component={AssetResourceBrowse} />
<Route path={`${match.path}/asset-browse`} component={AssetBrowse} />
<Route path={`${match.path}/asset-recycle`} component={AssetRecycle} />
<Route path={`${match.path}/msd-define`} component={DataMasterDefine} />
<Route path={`${match.path}/msd-manage`} component={DataMasterManage} />
<Route path={`${match.path}/data-service`} component={DataService} />
</Switch>
) : (
<GetSession {...this.props} />
......
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