Commit a97633ae by zhaochengxiang

Merge branch 'asset-ui' into 'master'

Asset ui

See merge request !7
parents c657a0f2 e8552565
......@@ -15,6 +15,7 @@ import AssetManage from './view/Manage/AssetManage';
import AssetBrowse from './view/Manage/AssetBrowse';
import AssetRecycle from './view/Manage/AssetRecycle';
import DatasourceManage from './view/Manage/DatasourceManage';
import AssetDetailPage from './view/Manage/AssetManage/Component/AssetDetailPage';
import AssetDetail from './view/Manage/AssetManage/Component/AssetDetail';
import ImportAction from './view/Manage/Model/Component/ImportAction';
import EditModel from './view/Manage/Model/Component/EditModel';
......@@ -38,23 +39,30 @@ export class App extends React.Component {
if (message === 'showDataModelDetail') {
return (
<ImportAction
modelerId={id}
action='detail'
terms={terms}
/>
<AppContext.Provider value={{
setGlobalState,
onGlobalStateChange
}}>
<ImportAction
modelerId={id}
action='detail'
terms={terms}
/>
</AppContext.Provider>
);
}
if (message === 'showAssetDetail') {
return (
<AssetDetail
id={id}
reference='search'
action='detail'
terms={terms}
visible={true}
/>
<AppContext.Provider value={{
setGlobalState,
onGlobalStateChange
}}>
<AssetDetail
id={id}
terms={terms}
/>
</AppContext.Provider>
);
}
......@@ -85,7 +93,9 @@ export class App extends React.Component {
<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}/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 />
......@@ -103,6 +113,7 @@ export class App extends React.Component {
<Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact />
<Route path={'/center-home/menu/asset-recycle'} component={AssetRecycle} exact />
<Route path={'/center-home/data-model-action'} component={EditModel} exact />
<Route path={'/center-home/asset-detail'} component={AssetDetailPage} exact />
</Switch>
</Router>
</AppContext.Provider>
......
......@@ -202,6 +202,10 @@ export function* importWordGenerateModelDraft(payload) {
return yield call(datamodelerService.importWordGenerateModelDraft, payload);
}
export function* heartbeat() {
return yield call(datamodelerService.heartbeat);
}
export function* validateDataModel(payload) {
return yield call(datamodelerService.validateDataModel, payload);
}
......
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { all, call, takeLatest, takeEvery } from 'redux-saga/effects'
import { all, call, takeLatest, takeEvery, delay } from 'redux-saga/effects'
import { Connect, showErrorNotifaction } from '../util';
import { reducers } from './reducer';
......@@ -16,6 +16,7 @@ function* request(args) {
const { type, payload, callback, error } = args.args;
try {
yield delay(100);
const rs = yield call(funcs[type], payload)
if (callback)
yield call(callback, rs)
......
......@@ -181,6 +181,10 @@ export function importWordGenerateModelDraft(payload) {
return PostFile("/datamodeler/easyDataModelerExport/word/draft", payload);
}
export function heartbeat() {
return Get("/datamodeler/easyDataModelerExport/heartbeat");
}
export function validateDataModel(payload) {
return PostJSON("/datamodeler/easyDataModelerConstraint/validateDataModel", payload);
}
......@@ -212,3 +216,4 @@ export function getParent(payload) {
export function autoCreateTable(payload) {
return PostJSON("/metadataharvester/datasource/createTableByDDLList", payload);
}
import React, { useEffect, useState } from "react";
import { Modal, Typography, Spin } from "antd";
import AssetDetailItem from './AssetDetailItem';
import { dispatch } from '../../../../model';
const AssetDetail = (props)=>{
const { onCancel, visible, id, reference=null, terms } = props;
const [ asset, setAsset ] = useState('');
const [ assetName, setAssetName ] = useState('');
const [ loading, setLoading ] = useState(false);
useEffect(() => {
if (visible && (id||'') !== '') {
getAssetThenGetAssetName();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible, id ])
const getAssetThenGetAssetName = () => {
setLoading(true);
dispatch({
type: 'assetmanage.getDataAssetDetail',
payload: {
dataAssetId: id
},
callback: data => {
setLoading(false);
setAsset(data);
getAssetName(data);
},
error: () => {
setLoading(false);
}
})
}
const getAssetName = (data) => {
if (data) {
const index = (data.elements||[]).findIndex(element => element.name==='中文名称');
if (index !== -1) {
setAssetName(data.elements[index].value||'');
}
}
}
return(
<>
{
!reference && <Modal
title={
<Typography.Paragraph
className='mr-5'
title={`资产: ${assetName||''}`}
ellipsis
>
{`资产: ${assetName||''}`}
</Typography.Paragraph>}
visible={visible}
width={800}
onCancel={()=>{ onCancel && onCancel()}}
footer={null}
>
<Spin spinning={loading}>
<AssetDetailItem data={asset} />
</Spin>
</Modal>
}
{
reference && <Spin spinning={loading}>
<AssetDetailItem data={asset} terms={terms} />
</Spin>
}
</>
)
}
import React, { useEffect, useState } from "react";
import { Spin, Tabs, Descriptions } from "antd";
import MetadataInfo from './MetadataInfo';
import { highlightSearchContentByTerms } from '../../../../util';
import { dispatch } from '../../../../model';
const { TabPane } = Tabs;
const AssetDetail = (props)=>{
const { id, terms } = props;
const [ asset, setAsset ] = useState('');
const [ types, setTypes ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ tabKey, setTabKey ] = useState('0');
useEffect(() => {
if ((id||'') !== '') {
getAssetThenGetAssetName();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ id ])
const getAssetThenGetAssetName = () => {
setLoading(true);
dispatch({
type: 'assetmanage.getDataAssetDetail',
payload: {
dataAssetId: id
},
callback: data => {
setLoading(false);
setAsset(data);
const _types = [];
(data?.elements||[]).forEach(element => {
if (_types.indexOf(element.type) === -1) {
_types.push(element.type);
}
})
setTypes(_types);
},
error: () => {
setLoading(false);
}
})
}
const onTabChange = (key) => {
setTabKey(key);
}
return(
<Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}>
{
(types||[]).map((type, index) => {
const _currentValues = (asset.elements||[]).filter(element => element.type===type);
return (
<TabPane tab={type} key={index}>
<Descriptions column={2}>
{
(_currentValues||[]).map((item, index) => {
return (
<Descriptions.Item label={item.name||''} key={index}>
{
item.name==='资产项' ? <MetadataInfo config={false} value={item.value||''} /> : <span>{highlightSearchContentByTerms(item.value||'', terms)}</span>
}
</Descriptions.Item>
);
})
}
</Descriptions>
</TabPane>
)
})
}
</Tabs>
</Spin>
)
}
export default AssetDetail;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Row, Col, Typography } from 'antd';
import { highlightSearchContentByTerms } from '../../../../util';
import MetadataInfo from './MetadataInfo';
import './AssetItem.less';
const AssetItem = (props) => {
const { data, terms } = props;
const [ typesOfElements, setTypesOfElements ] = useState([]);
useEffect(() => {
if (data) {
convertData();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ data ]);
const convertData = () => {
const _typesOfElements = [];
const _types = [];
data && (data.elements||[]).forEach(element => {
if (_types.indexOf(element.type||'') === -1) {
_types.push(element.type||'');
const _elements = (data.elements||[]).filter(_element => (_element.type||'') === (element.type||''));
_typesOfElements.push({ type: element.type||'', elements: _elements||[] });
}
})
setTypesOfElements(_typesOfElements);
}
return (
<div className='asset-item'>
{
(typesOfElements||[]).map((elementGroup, index) => {
const _type = elementGroup.type||'';
return (
<div>
<div className='flex' style={{ alignItems: 'center', padding: '15px 0' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{_type||''}</span>
</div>
<Row>
{
elementGroup && elementGroup.elements && elementGroup.elements.map((element, _index) => {
return (
<Col className='mb-3' key={_index} md={8}>
<Typography.Paragraph title={ `${element.name||''}: ${element.value||''}` } style={{ color: '#464646' }} ellipsis>
{ `${element.name||''}: `}
{
(element.name==='资产项')?<MetadataInfo config={false} value={element.value||''} />:highlightSearchContentByTerms(element.value||'', terms)
}
</Typography.Paragraph>
</Col>
);
})
}
</Row>
<div style={{ width: '100%', height: 2, backgroundColor: '#ededed' }} />
</div>
)
})
}
</div>
);
}
export default AssetItem;
import React, { useEffect, useState } from "react";
import AssetDetail from './AssetDetail';
import { getQueryParam } from '../../../../util';
import './AssetDetailPage.less';
const AssetDetailPage = (props)=>{
const [ id, setId ] = useState('');
useEffect(() => {
const _id = getQueryParam('id', props.location.search);
setId(_id);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return(
<div className='asset-detail position-relative'>
<div className='detail-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>资产详情</span>
</div>
<div className='detail-container'>
<div className='detail-container-card'>
<AssetDetail id={id} />
</div>
</div>
</div>
)
}
export default AssetDetailPage;
\ No newline at end of file
.asset-detail {
.detail-header {
display: flex;
width: 100%;
height: 44px;
padding: 0 15px;
background-color: #464d6e;
align-items: center;
position: fixed;
justify-content: space-between;
border-bottom: 1px solid #EFEFEF;
z-index: 100;
}
.detail-container {
top: 44px;
width: 100%;
height: calc(100vh - 44px);
overflow: auto;
background: #EDF0F5;
padding: 10px 20px;
position: absolute;
}
.detail-container-card {
padding: 20px 20px 0;
background: #fff;
}
}
\ No newline at end of file
@import '../../../../variables.less';
.asset-list {
.yy-list-item-action {
text-align: right;
}
.yy-card-head-title {
font-weight: normal;
font-size: 14px;
padding: 0;
}
.yy-list-vertical .yy-list-item-action > li {
padding: 0 ;
}
.data-asset-spin {
height: calc(100vh - @header-height - @pm-4 - 53px - 52px + 1px);
}
.yy-checkbox-group {
height: calc(100vh - @header-height - @pm-4 - 53px - 52px - 53px) !important;
overflow: auto !important;
}
.yy-divider-horizontal {
margin: 0 !important;
}
.yy-list-vertical {
.yy-list-item-meta, .yy-list-item-meta-title {
margin-bottom: 0 !important;
.highlight-row {
.yy-table-cell {
background-color: #e7f7ff !important;
}
}
.yy-list-item {
padding: 0 !important;
}
}
}
\ No newline at end of file
......@@ -76,6 +76,7 @@ const AssetTree = (props) => {
},
callback: data => {
setCheckedKeys(data.dirIds||[]);
onCheck && onCheck(data.dirIds||[]);
},
})
}
......
......@@ -182,7 +182,7 @@ const AttributeRelationModal = (props) => {
<Space>
<Button onClick={() => {
reset();
onCancel && onCancel();
onCancel && onCancel();
}}>返回</Button>
{
!readOnly && <Button type="primary" onClick={ onOk } loading={ confirmLoading }>确定</Button>
......@@ -207,7 +207,7 @@ const AttributeRelationModal = (props) => {
name={element.id||''}
key={index}
>
<Select>
<Select allowClear>
{
(attributes||[]).map((attribute, _index) => {
return (
......
import React, { useEffect, useState } from 'react';
import { Row, Col, Checkbox, Typography, Space, Button, Switch } from 'antd';
import { Row, Col, Checkbox, Typography, Button, Switch, Modal } from 'antd';
import { dispatch } from '../../../../model';
import './FilterElement.less';
import './FilterElementModal.less';
const FilterElement = (props) => {
const FilterElementModal = (props) => {
const { onCancel } = props;
const { visible, onCancel } = props;
const [ elements, setElements ] = useState([]);
const [ typesOfElements, setTypesOfElements ] = useState([]);
const [ selectedKeys, setSelectedKeys ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
useEffect(() => {
getAllFilterElementIdsThenGetAllElements();
if (visible) {
getAllFilterElementIdsThenGetAllElements();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [visible]);
const getAllFilterElementIdsThenGetAllElements = () => {
dispatch({
......@@ -87,19 +90,56 @@ const FilterElement = (props) => {
const onOk = () => {
setConfirmLoading(true);
dispatch({
type: 'assetmanage.setupFilterElementIds',
payload: {
data: selectedKeys
},
callback: () => {
onCancel && onCancel(false, true);
reset();
onCancel && onCancel(true);
},
error: () => {
reset();
}
})
}
const cancel = () => {
reset();
onCancel && onCancel();
}
const reset = () => {
setConfirmLoading(false);
}
return (
<div className='filter-element' style={{ width: 500 }}>
<Modal
forceRender
visible={visible}
title='资产要素过滤'
width={520}
onCancel={cancel}
footer={[
<Button
key="0"
onClick={cancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
onClick={onOk}
loading={confirmLoading}
>
确定
</Button>,
]}
>
<div className='d-flex'>
<Switch
checkedChildren="全不选"
......@@ -108,13 +148,13 @@ const FilterElement = (props) => {
style={{ marginLeft: 'auto' }}
/>
</div>
<div style={{ maxHeight: 450, overflow: 'auto' }}>
<div className='mt-3' style={{ maxHeight: 450, overflow: 'auto' }}>
{
(typesOfElements||[]).map((typeOfElements, index) => {
const _type = typeOfElements.type||'';
return (
<div>
<div key={index}>
<div className='flex' style={{ alignItems: 'center', padding: '15px 0' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{_type||''}</span>
......@@ -141,16 +181,8 @@ const FilterElement = (props) => {
})
}
</div>
<div className='mt-3 d-flex pt-3' style={{ borderTop: '1px solid rgba(0, 0, 0, 0.06)' }} >
<Space style={{ marginLeft: 'auto' }}>
<Button onClick={() => {
onCancel && onCancel();
}}>取消</Button>
<Button type='primary' onClick={onOk} >确定</Button>
</Space>
</div>
</div>
</Modal>
);
}
export default FilterElement;
\ No newline at end of file
export default FilterElementModal;
\ No newline at end of file
......@@ -19,7 +19,7 @@ const MetadataInfo = ({ value = '', config = true }) => {
value => <span>
{
(typeof metadata==='string') ? <span style={{ marginRight: 5 }}>{metadata||''}</span> : <a onClick={() => {
value?.setGlobalState({
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-metadata-message',
data: metadata
})
......
......@@ -7,7 +7,7 @@ import { showMessage } from '../../../../util';
const AssetMount = (props) => {
const { onCancel, visible, id, recycleIds, refrence = 'asset-manage' } = props;
const { onCancel, visible, ids, refrence = 'asset-manage' } = props;
const [ dirIds, setDirIds ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
......@@ -28,7 +28,7 @@ const AssetMount = (props) => {
params: {
dirId: dirIds.join(","),
},
data: (refrence==='asset-recycle') ? recycleIds : [id||'']
data: ids
},
callback: data => {
setConfirmLoading(false);
......@@ -48,7 +48,7 @@ const AssetMount = (props) => {
return(
<Modal
title='挂载详情'
title='变更目录详情'
visible={ visible }
width={ 400 }
confirmLoading={ confirmLoading }
......@@ -63,7 +63,7 @@ const AssetMount = (props) => {
checkable={true}
showCustom={false}
onCheck={onCheck}
tableId={refrence==='asset-manage'?id:''}
tableId={(refrence==='asset-manage'&&(ids||[].length>0))?ids[0]:''}
reference='mount'
{...props}
/>
......
......@@ -3,7 +3,6 @@ import { Table, Pagination, Space, Tooltip, Button, Modal } from 'antd';
import { ReconciliationOutlined, DeleteOutlined, UndoOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../model';
import AssetDetail from '../AssetManage/Component/AssetDetail';
import AssetMount from './Component/AssetMount';
import { showMessage } from '../../../util';
......@@ -16,7 +15,6 @@ const AssetRecycle = (props) => {
const [ total, setTotal ] = useState(0);
const [ pagination, setPagination ] = useState( { pageNum: 1, pageSize: 20 } );
const [ currentAssetId, setCurrentAssetId ] = useState('');
const [ assetDetailVisible, setAssetDetailVisible ] = useState(false);
const [ assetMountVisible, setAssetMountVisible ] = useState(false);
const [ selectedRowKeys, setSelectedRowKeys ] = useState([]);
const [ batchMount, setBatchMount ] = useState(false);
......@@ -108,15 +106,7 @@ const AssetRecycle = (props) => {
const detailItem = (record) => {
setCurrentAssetId(record.id);
const index = selectedRowKeys.findIndex((rowKey) => rowKey === record.id);
if (index !== -1) {
const newSelectedRowKeys = [...selectedRowKeys];
newSelectedRowKeys.splice(index, 1);
setSelectedRowKeys(newSelectedRowKeys);
}
setAssetDetailVisible(true);
window.open(`/center-home/asset-detail?id=${record.id}`);
}
const mountItem = (record) => {
......@@ -179,10 +169,6 @@ const AssetRecycle = (props) => {
});
}
const onAssetDetailCancel = () => {
setAssetDetailVisible(false);
}
const onAssetMountCancel = (refresh = false) => {
setAssetMountVisible(false);
......@@ -240,15 +226,10 @@ const AssetRecycle = (props) => {
showTotal={total => `共 ${total} 条`}
/>
</div>
<AssetDetail
visible={ assetDetailVisible }
id={ currentAssetId }
onCancel={ onAssetDetailCancel }
/>
<AssetMount
refrence='asset-recycle'
visible={ assetMountVisible }
recycleIds={ batchMount ? selectedRowKeys : [ currentAssetId ] }
ids={ batchMount ? selectedRowKeys : [ currentAssetId ] }
onCancel={ onAssetMountCancel }
{...props}
/>
......
......@@ -47,10 +47,24 @@ const EditModel = (props) => {
}
setActionData({ action: _action, catalogId: _catalogId, modelerId: _modelerId, hints: _hints, roughModelerData: _roughModelerData, permitCheckOut: _permitCheckOut, editable: _editable, stateId: _stateId, versionId: _versionId });
const interval = setInterval(() => {
heartbeat();
}, 10*60*1000);
return () => {
clearInterval(interval);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const heartbeat = () => {
dispatchLatest({
type: 'datamodel.heartbeat'
});
}
const save = async (e, cid = '') => {
try {
const row = await form.validateFields();
......
......@@ -83,7 +83,7 @@ const ImportActionHeader = (props) => {
setAutoTranslate((modelerData.name||'')==='');
if (modelerData) {
form.setFieldsValue(modelerData);
form?.setFieldsValue(modelerData);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -159,7 +159,7 @@ const ImportActionHeader = (props) => {
},
callback: data => {
if ((data?.translated||'') !== '') {
form.setFieldsValue({ name: data?.translated||'' });
form?.setFieldsValue({ name: data?.translated||'' });
}
onChange && onChange(changedValues, allValues);
}
......
......@@ -753,7 +753,7 @@ const ImportActionTable = (props) => {
}
{
record?.isPossibleNewTerm?.possible && <Typography.Link className='mr-3' onClick={() => {
value?.setGlobalState({
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-standard-create',
data: {
column: record,
......
......@@ -135,7 +135,7 @@ const ModelTable = (props) => {
if (record?.state?.id === '2') {
editTip = '待发布的模型不允许编辑';
} else if (record?.state?.id === '4') {
editTip = '已发布模型下存在未发布的模型,不允许再编辑';
editTip = `${record.holder||''}正在编辑中, 不允许再编辑`;
}
}
......
......@@ -45,7 +45,7 @@ class Model extends React.Component {
hints: [],
loadingStates: false,
modelStates: [],
currentModelState: '4',
currentModelState: '',
currentView: '',
exportDDLModalReference: 'exportDDL',
currentModel: {},
......@@ -72,8 +72,7 @@ class Model extends React.Component {
callback: data => {
this.setState({
loadingStates: false,
// modelStates: [{ name: 'all', id: '', cnName: '所有状态' }, ...(data?.subCatalogs||[])]
modelStates: data?.subCatalogs||[],
modelStates: [{ name: 'all', id: '', cnName: '所有状态' }, ...(data?.subCatalogs||[])]
});
},
error: () => {
......@@ -93,9 +92,9 @@ class Model extends React.Component {
})
}
onTreeSelect = (key, offset) => {
onTreeSelect = (key, offset=null) => {
this.setState({ catalogId: key, keyword: '', offset }, () => {
this.setState({ catalogId: key, keyword: '', offset, currentModelState: (offset!==null)?'':this.state.currentModelState }, () => {
if (!key || key==='') {
this.setState({ tableData: [], filterTableData: [] });
} else {
......@@ -110,12 +109,18 @@ class Model extends React.Component {
this.setState({ loadingTableData: true }, () => {
if (keyword === '') {
if (currentView === 'dir') {
const params = {
easyDataModelerCatalogId: catalogId,
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'datamodel.getCurrentDataModelCatalog',
payload: {
easyDataModelerCatalogId: catalogId,
stateId: currentModelState
},
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data.easyDataModelerDataModels||[], filterTableData: data.easyDataModelerDataModels||[] });
},
......@@ -138,12 +143,18 @@ class Model extends React.Component {
})
}
} else {
const params = {
term: keyword,
};
if (currentModelState !== '') {
params.stateId = currentModelState;
}
dispatchLatestHomepage({
type: 'datamodel.searchModel',
payload: {
term: keyword,
stateId: currentModelState
},
payload: params,
callback: data => {
this.setState({ loadingTableData: false, tableData: data||[], filterTableData: data||[] });
},
......
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