Commit c3cfe889 by zhaochengxiang

资产历史版本

parent dfcde47c
...@@ -49,7 +49,7 @@ module.exports = { ...@@ -49,7 +49,7 @@ module.exports = {
}, },
proxy: { proxy: {
'/api': { '/api': {
target: 'http://192.168.0.38:8089', target: 'http://192.168.0.179:8089',
changeOrigin: true, changeOrigin: true,
}, },
'/data-quality-szse': { '/data-quality-szse': {
......
...@@ -504,4 +504,12 @@ export function* auditTask(payload) { ...@@ -504,4 +504,12 @@ export function* auditTask(payload) {
export function* uploadDataAssetExcel(payload) { export function* uploadDataAssetExcel(payload) {
return yield call(service.uploadDataAssetExcel, payload); return yield call(service.uploadDataAssetExcel, payload);
}
export function* compareDataAssetWithDescribeInfo(payload) {
return yield call(service.compareDataAssetWithDescribeInfo, payload);
}
export function* listDataAssetHistoryTimeline(payload) {
return yield call(service.listDataAssetHistoryTimeline, payload);
} }
\ No newline at end of file
...@@ -511,4 +511,12 @@ export function auditTask(payload) { ...@@ -511,4 +511,12 @@ export function auditTask(payload) {
export function uploadDataAssetExcel(payload) { export function uploadDataAssetExcel(payload) {
return PostFile("/dataassetmanager/dataAssetCheckApi/uploadDataAssetExcel", payload, 'reportFile'); return PostFile("/dataassetmanager/dataAssetCheckApi/uploadDataAssetExcel", payload, 'reportFile');
}
export function compareDataAssetWithDescribeInfo(payload) {
return PostJSON("/dataassetmanager/versionApi/compareDataAssetWithDescribeInfo", payload);
}
export function listDataAssetHistoryTimeline(payload) {
return GetJSON("/dataassetmanager/versionApi/listDataAssetHistoryTimeline", payload);
} }
\ No newline at end of file
...@@ -25,6 +25,7 @@ import Table from '../../ResizeableTable'; ...@@ -25,6 +25,7 @@ import Table from '../../ResizeableTable';
import WorksheetModal from "./WorksheetModal"; import WorksheetModal from "./WorksheetModal";
import WorkbookDrawer from "./WorkbookDrawer"; import WorkbookDrawer from "./WorkbookDrawer";
import TagCell, { TagSelectPopup } from './tag-help' import TagCell, { TagSelectPopup } from './tag-help'
import HistoryAndVersionDrawer from "./HistoryAndVersionDrawer";
import "./AssetTable.less"; import "./AssetTable.less";
import 'react-contexify/dist/ReactContexify.css'; import 'react-contexify/dist/ReactContexify.css';
...@@ -153,6 +154,10 @@ const AssetTable = (props) => { ...@@ -153,6 +154,10 @@ const AssetTable = (props) => {
items: undefined items: undefined
}) })
const [resoureTagMap, setResourceTagMap] = React.useState() const [resoureTagMap, setResourceTagMap] = React.useState()
const [historyAndVersionParams, setHistoryAndVersionParams] = React.useState({
visible: false,
id: undefined
})
const [ modal, contextHolder ] = Modal.useModal(); const [ modal, contextHolder ] = Modal.useModal();
const anchorId = getQueryParam(AnchorId, props?.location?.search); const anchorId = getQueryParam(AnchorId, props?.location?.search);
...@@ -910,75 +915,24 @@ const AssetTable = (props) => { ...@@ -910,75 +915,24 @@ const AssetTable = (props) => {
} }
} }
const displayMenu = (e) => { const displayMenu = (event) => {
show({ show(event, {
event: e position: {
x: event.clientX + 30,
y: event.clientY - 10
}
}) })
} }
const handleItemClick = (id, record) => { const handleItemClick = ({ event, props, data, triggerEvent }) => {
if (id === 'uncombed') { const key = event.currentTarget.id;
modal.confirm({
title: '提示',
content: '是否将该条非资产目录的资源转为未梳理状态?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: record.id,
resourceState: 'uncombed'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false));
let event = new Event('storage');
event.key = 'assetResourceChange';
window?.dispatchEvent(event);
getDataAssets();
}
});
}
});
} else if (id === 'notRelatedAsset') {
modal.confirm({
title: '提示',
content: '是否将该条未梳理的资源转为非资产目录?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: record.id,
resourceState: 'notRelatedAsset'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false));
let event = new Event('storage');
event.key = 'assetResourceChange';
window?.dispatchEvent(event);
getDataAssets(); if (key === 'history') {
} setHistoryAndVersionParams({
}); visible: true,
} id: contextMenuItem?.id
});
} else if (id === 'authorization') {
app?.setGlobalState && app?.setGlobalState({
message: 'data-govern-assets-admit',
data: record
}) })
// if (record.metadata?.metadataTableId) { }
// app?.setGlobalState && app?.setGlobalState({
// message: 'data-govern-assets-admit',
// data: record
// })
// } else {
// showMessage("warn","该资产目录没有关联元数据信息");
// }
}
} }
const onBatchAddTagClick = () => { const onBatchAddTagClick = () => {
...@@ -1185,10 +1139,10 @@ const AssetTable = (props) => { ...@@ -1185,10 +1139,10 @@ const AssetTable = (props) => {
setAssetDetailDrawerVisible(true); setAssetDetailDrawerVisible(true);
}, },
onContextMenu: event => { onContextMenu: event => {
// if ((reference===AssetManageReference||(reference===AssetBrowseReference&&record.hasPermission)) && (record.resourceState==='uncombed'||record.resourceState==='notRelatedAsset')) { if (reference===AssetManageReference) {
// setContextMenuItem(record); setContextMenuItem(record);
// displayMenu(event); displayMenu(event);
// } }
}, },
} }
}} }}
...@@ -1323,21 +1277,20 @@ const AssetTable = (props) => { ...@@ -1323,21 +1277,20 @@ const AssetTable = (props) => {
getResourceTag() getResourceTag()
}} }}
/> />
<HistoryAndVersionDrawer
{...historyAndVersionParams}
onCancel={() => {
setHistoryAndVersionParams({
visible: false,
id: undefined,
})
}}
/>
<RcMenu id={MENU_ID}> <RcMenu id={MENU_ID}>
{/* { <RcItem id="history" onClick={handleItemClick}>
(contextMenuItem.resourceState==='notRelatedAsset') && <RcItem id="uncombed" onClick={handleItemClick}> 历史版本
转为未梳理 </RcItem>
</RcItem>
}
{
(contextMenuItem.resourceState==='uncombed') && <RcItem id="notRelatedAsset" onClick={handleItemClick}>
转为非资产
</RcItem>
} */}
{/* <RcItem id="authorization" onClick={handleItemClick}>
授权
</RcItem> */}
</RcMenu> </RcMenu>
{contextHolder} {contextHolder}
......
import { useState } from 'react';
import { Popover, Table } from 'antd';
import { MenuOutlined } from '@ant-design/icons';
const { Column } = Table;
const ColumnSelect = (props) => {
const { columns, onChange, defaultSelectedKeys } = props;
const [ selectedKeys, setSelectedKeys ] = useState(defaultSelectedKeys);
const changeSelect = (selectedRowKeys) => {
setSelectedKeys(selectedRowKeys);
onChange && onChange(selectedRowKeys);
}
const rowSelection = {
selectedRowKeys: selectedKeys,
onChange:changeSelect
}
return(
<div style={{width:230}}>
<Table
rowKey="title"
rowSelection={rowSelection}
showHeader={true}
pagination={false}
size="small"
scroll={{ y:350 }}
dataSource={columns}
>
<Column title="全选" dataIndex="title" key="title" />
</Table>
</div>
)
}
const FilterColumnAction = (props) => {
return (
<Popover
title="选择显示字段"
placement="leftTop"
content={
<ColumnSelect {...props} />
}>
<MenuOutlined />
</Popover>
);
}
export default FilterColumnAction;
\ No newline at end of file
import React from 'react';
import { Drawer, Tabs } from 'antd';
import VersionHistory from './VersionHistory';
import VersionCompare from './VersionCompare';
const { TabPane } = Tabs;
const HistoryAndVersionDrawer = (props) => {
const { onCancel, visible, id } = props;
return (
<Drawer
title=''
placement="right"
closable={true}
width={'90%'}
onClose={() => {
onCancel && onCancel();
}}
visible={visible}
>
{
visible && <Tabs defaultActiveKey="1" type="card" size='small'>
<TabPane tab="版本历史" key="1">
<VersionHistory id={id} />
</TabPane>
<TabPane tab="版本对比" key="2">
<VersionCompare id={id} />
</TabPane>
</Tabs>
}
</Drawer>
);
}
export default HistoryAndVersionDrawer;
\ No newline at end of file
import React, { useEffect, useState, useRef } from 'react';
import { Form, Select, Spin, Tooltip, Checkbox, Typography, Space, Button } from 'antd';
import { dispatch, dispatchLatest } from '../../../../model';
import { formatVersionDate, showMessage } from '../../../../util';
import VersionCompareHeader from './VersionCompareHeader';
import VersionCompareTable from './VersionCompareTable';
import VersionCompareIndex from './VersionCompareIndex';
import './VersionCompare.less';
const { Text, Paragraph } = Typography;
const { Option } = Select;
const VersionCompare = (props) => {
const { id } = props;
const [ basicVersion, setBasicVersion ] = useState('');
const [ basicVersions, setBasicVersions ] = useState([]);
const [ incVersion, setIncVersion ] = useState('');
const [ incVersions, setIncVersions ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ compareData, setCompareData ] = useState(null);
const [ loadingCompare, setLoadingCompare ] = useState(false);
const [ onlyShowChange, setOnlyShowChange ] = useState(true);
useEffect(() => {
if ((id||'') !== '') {
getVersions();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ id ])
const getVersions = () => {
setLoading(true);
dispatch({
type: 'assetmanage.listDataAssetHistoryTimeline',
payload: {
dataAssetId: id
},
callback: data => {
setLoading(false);
const newData = [];
(data||[]).forEach((item, index) => {
let name = item.name||'';
name = name + '_' + formatVersionDate(item.ts);
// if (index === 0 && item.id !== '-1') {
// name = name+'(当前版本)';
// }
// if (index === 1 && data[0].id === '-1') {
// name = name+'(当前版本)';
// }
newData.push({ id: item.versionId, name });
})
setBasicVersions(newData);
if (newData.length >= 2) {
const defaultBasicVersion = newData[1].id;
const defaultIncVersion = newData[0].id;
setBasicVersion(defaultBasicVersion);
let index = -1;
(newData||[]).forEach((version, i) => {
if (version.id === defaultBasicVersion) {
index = i;
}
})
setIncVersions((newData||[]).slice(0, index));
setIncVersion(defaultIncVersion);
getCompare(defaultBasicVersion, defaultIncVersion);
}
},
error: () => {
setLoading(false);
}
})
}
const onBasicChange = (value) => {
setBasicVersion(value);
setIncVersion('');
setCompareData(null);
let index = -1;
(basicVersions||[]).forEach((version, i) => {
if (version.id === value) {
index = i;
}
})
setIncVersions((basicVersions||[]).slice(0, index));
}
const onIncChange = (value) => {
setIncVersion(value);
getCompare(basicVersion, value);
}
const getCompare = (value1=basicVersion, value2=incVersion, value3=onlyShowChange) => {
setLoadingCompare(true);
dispatchLatest({
type: 'assetmanage.compareDataAssetWithDescribeInfo',
payload: {
params: {
dataAssetId: id,
versionId1: value1,
versionId2: value2,
includeSame: !value3
}
},
callback: data => {
setLoadingCompare(false);
setCompareData({...data, left: data?.leftValue, right: data?.rightValue});
},
error: () => {
setLoadingCompare(false);
}
})
}
const onOnlyShowChange = (e) => {
setOnlyShowChange(e.target.checked);
if (basicVersion!=='' && incVersion!=='') {
getCompare(basicVersion, incVersion, e.target.checked);
}
}
return (
<div className='model-version-compare'>
<Form layout='inline'>
<Form.Item label='基线版本'>
<Select loading={loading} value={basicVersion} style={{ width: 300 }} onChange={onBasicChange} >
{
(basicVersions||[]).map((version, index) => {
if (index === 0) {
return (
<Option key={index} value={version.id||''} disabled={true}>
<Tooltip title={'最近版本只能在增量版本中被选中'}>
{version.name||''}
</Tooltip>
</Option>
)
};
return (
<Option key={index} value={version.id||''}>
{version.name||''}
</Option>
);
})
}
</Select>
</Form.Item>
<Form.Item label='增量版本'>
<Select value={incVersion} style={{ width: 300 }} disabled={basicVersion===''} onChange={onIncChange}>
{
(incVersions||[]).map((version, index) => {
return (
<Option key={index} value={version.id||''}>{version.name||''}</Option>
);
})
}
</Select>
</Form.Item>
<Form.Item>
<Space>
<Checkbox onChange={onOnlyShowChange} checked={onlyShowChange}>仅显示差异</Checkbox>
</Space>
</Form.Item>
</Form>
<div className='py-5'>
<Spin spinning={loadingCompare} >
<CompareDetail data={compareData} showIndex={false} />
</Spin>
</div>
</div>
);
}
export default VersionCompare;
export const CompareDetail = ({ data, showIndex=true }) => {
const [selectedColumnTitles, setSelectedColumnTitles] = React.useState()
return (
<>
{
data && <div className='flex'>
<div style={{ flex: 1, borderRight: '1px solid #EFEFEF', paddingRight: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={data} />
<VersionCompareTable
data={data}
selectedColumnTitles={selectedColumnTitles}
onFilterChange={(val) => setSelectedColumnTitles(val)}
/>
{ showIndex && <VersionCompareIndex data={data} />}
</div>
<div style={{ flex: 1, paddingLeft: 10, overflow: 'hidden'}}>
<VersionCompareHeader data={data} direction='right' />
<VersionCompareTable
data={data}
selectedColumnTitles={selectedColumnTitles}
onFilterChange={(val) => setSelectedColumnTitles(val)}
direction='right'
/>
{ showIndex && <VersionCompareIndex data={data} direction='right'/> }
</div>
</div>
}
</>
)
}
\ No newline at end of file
.model-version-compare {
.yy-typography .delete, .delete {
color: red !important;
text-decoration: line-through !important;
}
.yy-typography .add, .add {
color: red !important;
}
}
\ No newline at end of file
import { Typography, Tooltip } from 'antd';
const { Text, Paragraph, Title } = Typography;
const VersionCompareHeader = (props) => {
const { data, direction = 'left' } = props;
return (
<Typography>
<div className='mb-3'>
<Paragraph>
<Title level={5}>基本信息</Title>
</Paragraph>
</div>
{
(data?.heads?.tableHead||[]).map((item, index) => {
let columnValue = {};
if (direction==='left' && (data?.left?.tableValue||[]).length>index) {
columnValue = data?.left?.tableValue[index];
} else if (direction==='right' && (data?.right?.tableValue||[]).length>index) {
columnValue = data?.right?.tableValue[index];
}
let stateClassName = '';
if (columnValue?.state==='ADD' || columnValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (columnValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Paragraph>
<Tooltip key={index} title={columnValue.value||''}>
<Text ellipsis={true}>
{item||''}:&nbsp;<Text className={stateClassName}>{columnValue.value||''}</Text>
</Text>
</Tooltip>
</Paragraph>
);
})
}
</Typography>
);
}
export default VersionCompareHeader;
import { useEffect, useState } from 'react';
import { Typography, Tooltip, Table } from 'antd';
const { Text, Paragraph, Title } = Typography;
const VersionCompareIndex = (props) => {
const { data, direction = 'left' } = props;
const [ columns, setColumns ] = useState([]);
const [ tableData, setTableData ] = useState([]);
useEffect(() => {
const newColumns = [];
(data?.heads?.indexHead||[]).forEach((item, index) => {
newColumns.push({
title: item||'',
dataIndex: `column${index}`,
render: (attrValue, record, index) => {
let stateClassName = '';
if (attrValue?.state==='ADD' || attrValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (attrValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Paragraph>
<Tooltip title={attrValue?.value||''}>
<Text className={stateClassName} ellipsis={true}>{attrValue?.value||''}</Text>
</Tooltip>
</Paragraph>
);
},
width: 60,
ellipsis: true,
});
})
setColumns(newColumns);
const newTableData = [];
let indexValue = [];
if (direction==='left') {
indexValue = data?.left?.indexValue||[];
} else if (direction==='right') {
indexValue = data?.right?.indexValue||[];
}
(indexValue||[]).forEach((attrItem) => {
let newAttrItem = {};
(attrItem||[]).forEach((item, index) => {
newAttrItem[`column${index}`] = item;
})
newTableData.push(newAttrItem);
})
setTableData(newTableData);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ data ])
return (
<div>
<div className='my-3'>
<Typography>
<Title level={5}>数据表索引</Title>
</Typography>
</div>
<Table
columns={columns||[]}
dataSource={tableData}
pagination={false}
/>
</div>
);
}
export default VersionCompareIndex;
\ No newline at end of file
import React from 'react';
import { Typography, Table, Tooltip } from 'antd';
import FilterColumnAction from './FilterColumnAction';
const { Title } = Typography;
export const defaultColumnTitles = ['序号', '中文名称', '英文名称', '类型', '业务含义'];
const VersionCompareTable = (props) => {
const { data, direction = 'left', selectedColumnTitles = defaultColumnTitles, onFilterChange } = props;
const [columns, setColumns] = React.useState()
React.useEffect(() => {
const newColumns = [];
for (const [index, item] of (data?.heads?.columnHead||[]).entries()) {
newColumns.push({
title: item||'',
dataIndex: `column${index}`,
render: (attrValue, record, index) => {
let stateClassName = '';
if (attrValue?.state==='ADD' || attrValue?.state==='UPDATE') {
stateClassName = 'add';
} else if (attrValue?.state === 'DELETE') {
stateClassName = 'delete';
}
return (
<Typography.Paragraph>
<Tooltip title={attrValue?.value||''}>
<Typography.Text className={stateClassName} ellipsis={true}>{attrValue?.value||''}</Typography.Text>
</Tooltip>
</Typography.Paragraph>
);
},
width: (item==='序号')?60: 150,
ellipsis: true,
option: true,
});
}
setColumns([...newColumns, {
title: <FilterColumnAction columns={newColumns} defaultSelectedKeys={defaultColumnTitles} onChange={(val) => onFilterChange?.(val)} />,
dataIndex: 'columnFilter',
render: (_, record, index) => {
return '';
},
width: 40,
ellipsis: true,
option: false
}])
}, [data])
const _columns = React.useMemo(() => {
return (columns??[]).filter(column => column.option===false || (selectedColumnTitles??[]).indexOf(column.title) !== -1)
}, [selectedColumnTitles, columns])
const tableData = React.useMemo(() => {
const newTableData = [];
let columnValue = (direction==='left') ? data?.left?.columnValue : data?.right?.columnValue;
for (const item of columnValue??[]) {
let newItem = {};
for (const [index, _item] of item.entries()) {
newItem[`column${index}`] = _item;
}
newTableData.push(newItem);
}
return newTableData
}, [direction, data])
return (
<div>
<div className='my-3'>
<Typography>
<Title level={5}>数据表结构</Title>
</Typography>
</div>
<Table
columns={_columns||[]}
dataSource={tableData}
pagination={false}
/>
</div>
);
}
export default VersionCompareTable;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Timeline, Spin } from 'antd';
import { dispatch } from '../../../../model';
import { formatVersionHistoryDate } from '../../../../util';
import { Action, ModelerId, VersionId } from '../../../../util/constant';
const VersionHistory = (props) => {
const { id } = props;
const [ versions, setVersions ] = useState([]);
const [ loading, setLoading ] = useState(false);
useEffect(() => {
if ((id||'') !== '') {
getVersions();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ id ])
const getVersions = () => {
setLoading(true);
dispatch({
type: 'assetmanage.listDataAssetHistoryTimeline',
payload: {
dataAssetId: id
},
callback: data => {
setLoading(false);
setVersions(data||[]);
},
error: () => {
setLoading(false);
}
})
}
const onVersionItemClick = (version) => {
window.open(`/data-govern/data-model-action?${Action}=detail-version&${ModelerId}=${version.dataModelId||''}&${VersionId}=${version.id||''}`);
}
return (
<Spin spinning={loading}>
<Timeline style={{ padding: 24 }}>
{
(versions||[]).map((version, index) => {
let name = version.name||'';
// if (index === 0 && version.id !== '-1') {
// name = name+'(当前版本)';
// }
// if (index === 1 && versions[0].id === '-1') {
// name = name+'(当前版本)';
// }
let color = '#77DEBF';
// if (version.state?.id === '1') {
// color = '#DE7777';
// } else if (version.state?.id === '2') {
// color = '#779BDE';
// } else if (version.state?.id === '4') {
// color = '#77DEBF';
// }
return <Timeline.Item key={index} color={ color } >
<div>
<div>
{/* <a onClick={()=>{ onVersionItemClick(version); }}>
{name}
</a> */}
{name}
</div>
<div className='pt-2'>
<span style={{ color }}>{version.state?.cnName||''}</span>
</div>
<div className='pt-2'>
<span>{version.editor||''}</span>
<span className='pl-2'>{formatVersionHistoryDate(version.ts)}</span>
</div>
</div>
</Timeline.Item>
})
}
</Timeline>
</Spin>
);
}
export default VersionHistory;
\ No newline at end of file
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