peng.peng 2 years ago
parent
commit
fc5f4dee3d
  1. 1
      api/.vscode/launch.json
  2. 1498
      api/app/lib/controllers/latestMetadata/index.js
  3. 3
      api/app/lib/routes/latestMetadata/index.js
  4. 6
      web/client/src/sections/dataQuality/containers/ruleLibrary.js
  5. 258
      web/client/src/sections/metadataManagement/actions/businessMetadata.js
  6. 237
      web/client/src/sections/metadataManagement/components/releaseModal.js
  7. 673
      web/client/src/sections/metadataManagement/containers/databasesTable.js
  8. 1
      web/client/src/utils/webapi.js

1
api/.vscode/launch.json

@ -16,6 +16,7 @@
"-p 4400",
//
"-g postgres://FashionAdmin:123456@10.8.30.39:5432/GovernmentDataResourceCenter",
// "-g postgres://FashionAdmin:123456@10.8.30.156:5432/gdrcenter",
"-b http://10.8.30.161:31420"
]
},

1498
api/app/lib/controllers/latestMetadata/index.js

File diff suppressed because it is too large

3
api/app/lib/routes/latestMetadata/index.js

@ -68,4 +68,7 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['DEL/metadata/restapis/:id'] = { content: '删除接口元数据', visible: true };
router.delete('/metadata/restapis/:id', latestMetadata.delMetadataRestapis);
app.fs.api.logAttr['GET/listStructuredData'] = { content: '获取对表的库与字段信息', visible: true };
router.get('/listStructuredData', latestMetadata.listStructuredData);
};

6
web/client/src/sections/dataQuality/containers/ruleLibrary.js

@ -97,7 +97,7 @@ function RuleLibrary ({ loading, clientHeight, actions, dispatch, }) {
return <>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 20 }}>
<Button onClick={() => {
setRuleModal(true);
}}>新建业务规则</Button>
@ -123,7 +123,7 @@ function RuleLibrary ({ loading, clientHeight, actions, dispatch, }) {
<Table
columns={columns}
dataSource={tableList?.rows || []}
scroll={{ scrollToFirstRowOnChange: true, y: clientHeight - 260 }}
scroll={{ scrollToFirstRowOnChange: true, y: clientHeight - 240 }}
pagination={{
current: query?.page + 1,
pageSize: query?.limit,
@ -146,7 +146,7 @@ function RuleLibrary ({ loading, clientHeight, actions, dispatch, }) {
close={() => {
setRuleModal(false);
setEditData({})
} }
}}
success={() => {
resourceData({ limit: 10, page: 0, keyword })
}

258
web/client/src/sections/metadataManagement/actions/businessMetadata.js

@ -3,151 +3,163 @@
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getBusinessMetadataDatabases(params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_DATABASES_LIST',
url: ApiTable.getBusinessMetadataDatabases,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataDatabases' }
});
export function getBusinessMetadataDatabases (params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_DATABASES_LIST',
url: ApiTable.getBusinessMetadataDatabases,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataDatabases' }
});
}
export function postBusinessMetadataDatabases(data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_DATABASES',
url: ApiTable.postBusinessMetadataDatabases,
msg: { option: '新建业务元数据' },
reducer: {}
});
export function postBusinessMetadataDatabases (data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_DATABASES',
url: ApiTable.postBusinessMetadataDatabases,
msg: { option: '新建业务元数据' },
reducer: {}
});
}
export function putBusinessMetadataDatabases(id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_DATABASES',
url: ApiTable.putBusinessMetadataDatabases.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
export function putBusinessMetadataDatabases (id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_DATABASES',
url: ApiTable.putBusinessMetadataDatabases.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
}
export function delBusinessMetadataDatabases(id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_DATABASES',
url: ApiTable.delBusinessMetadataDatabases.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
export function delBusinessMetadataDatabases (id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_DATABASES',
url: ApiTable.delBusinessMetadataDatabases.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
}
export function getBusinessMetadataFiles(params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_FILES_LIST',
url: ApiTable.getBusinessMetadataFiles,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataFiles' }
});
export function getBusinessMetadataFiles (params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_FILES_LIST',
url: ApiTable.getBusinessMetadataFiles,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataFiles' }
});
}
export function postBusinessMetadataFiles(data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_FILES',
url: ApiTable.postBusinessMetadataFiles,
msg: { option: '新建业务元数据' },
reducer: {}
});
export function postBusinessMetadataFiles (data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_FILES',
url: ApiTable.postBusinessMetadataFiles,
msg: { option: '新建业务元数据' },
reducer: {}
});
}
export function putBusinessMetadataFiles(id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_FILES',
url: ApiTable.putBusinessMetadataFiles.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
export function putBusinessMetadataFiles (id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_FILES',
url: ApiTable.putBusinessMetadataFiles.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
}
export function delBusinessMetadataFiles(id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_FILES',
url: ApiTable.delBusinessMetadataFiles.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
export function delBusinessMetadataFiles (id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_FILES',
url: ApiTable.delBusinessMetadataFiles.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
}
export function getBusinessMetadataRestapis(params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_RESTAPIS_LIST',
url: ApiTable.getBusinessMetadataRestapis,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataRestapis' }
});
export function getBusinessMetadataRestapis (params) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: params,
actionType: 'GET_BUSINESS_METADATA_RESTAPIS_LIST',
url: ApiTable.getBusinessMetadataRestapis,
msg: { error: '获取业务元数据列表失败' },
reducer: { name: 'businessMetadataRestapis' }
});
}
export function postBusinessMetadataRestapis(data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.postBusinessMetadataRestapis,
msg: { option: '新建业务元数据' },
reducer: {}
});
export function postBusinessMetadataRestapis (data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.postBusinessMetadataRestapis,
msg: { option: '新建业务元数据' },
reducer: {}
});
}
export function putBusinessMetadataRestapis(id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.putBusinessMetadataRestapis.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
export function putBusinessMetadataRestapis (id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.putBusinessMetadataRestapis.replace('{id}', id),
msg: {
option: '修改业务元数据',
}
});
}
export function delBusinessMetadataRestapis(id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.delBusinessMetadataRestapis.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
export function delBusinessMetadataRestapis (id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_BUSINESS_METADATA_RESTAPIS',
url: ApiTable.delBusinessMetadataRestapis.replace('{id}', id),
msg: {
option: '删除业务元数据',
}
});
}
export function listStructuredData (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_LIST_STRUCTURED_DATA',
url: ApiTable.listStructuredData,
msg: { error: '获取对表的库与字段信息失败' },
reducer: {}
});
}

237
web/client/src/sections/metadataManagement/components/releaseModal.js

@ -0,0 +1,237 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Modal, Input, Form, Tabs, Avatar, Checkbox, Button, Select } from 'antd';
import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
const { TabPane } = Tabs;
const { TextArea } = Input;
const CheckboxGroup = Checkbox.Group;
const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} }) => {
const { metadataManagement } = actions;
const [activeKey, setActiveKeyl] = useState("field")
const [fieldList, setFiedList] = useState([])
const [fieldData, setFiedData] = useState([])
const [database, setDatabase] = useState({})
const [fieldValue, setFiedValue] = useState([])
const [indeterminate, setIndeterminate] = useState(false)
const [fromData, setFromData] = useState([{ value1: '', value2: '', value3: '' }])
const [sql, setSql] = useState()
const [form] = Form.useForm();
useEffect(() => {
dispatch(metadataManagement.listStructuredData({ id: editData?.id, parent: editData?.parent })).then(res => {
if (res.success) {
setFiedData(res.payload.data?.field || [])
setFiedList(res.payload.data?.field?.map(v => v.code) || [])
setDatabase(res.payload.data?.database || {})
}
})
}, []);
let operator = [{ value: '=', label: '=' },
{ value: '!=', label: '!=' },
{ value: '>', label: '>' },
{ value: '<', label: '<' },
{ value: '>=', label: '>=' },
{ value: '<=', label: '<=' },
{ value: 'BETWEEN', label: 'BETWEEN' },
{ value: 'LIKE', label: 'LIKE' },
{ value: 'IN', label: 'IN' }]
return (
<Modal title={'发布REST服务'} open={true} destroyOnClose
width={800}
// onOk={() => handleOk(null)}
onCancel={onCancel}
footer={null}
>
<div style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-around' }}>
{[{ name: '选择字段', key: "field" },
{ name: '配置条件', key: "condition" },
{ name: '预览SQL', key: "SQL" },
{ name: '服务发布', key: "release" }].map((v, index) => {
let title = []
if (index > 0) {
title.push(<div style={{ width: '14%', border: '1px solid #0000004a' }}></div>)
}
title.push(<div style={{ width: '14%', color: activeKey == v.key ? '#1e6ee5' : "", }}>
<Avatar style={{
backgroundColor: activeKey == v.key ? "rgb(30 110 229 / 74%)" : 'rgb(200 192 185)'
}}
size="large" icon={index + 1} />
{v.name}
</div>)
return title
})}
</div>
<Tabs activeKey={activeKey} onChange={v => {
setActiveKeyl(v)
}} >
<TabPane key="field">
<div style={{ fontSize: 20, fontWeight: 600 }}>选择字段</div>
{/* <div style={{ marginLeft: 20 }}> */}
<Checkbox indeterminate={indeterminate} style={{ margin: '20px 40px' }} onChange={e => {
setFiedValue(e.target.checked ? fieldList : []);
setIndeterminate(e.target.checked);
}}>
全选
</Checkbox>
<div style={{ display: 'flex', marginLeft: 40 }}>
<div style={{ marginRight: 8 }}>字段:</div>
<CheckboxGroup options={fieldList} value={fieldValue} onChange={v => {
setFiedValue(v)
if (v?.length == fieldList?.length) {
setIndeterminate(true)
} else {
setIndeterminate(false)
}
}} />
</div>
</TabPane>
<TabPane key="condition">
<div style={{ fontSize: 20, margin: '20px 0', fontWeight: 600 }}>配置条件</div>
{fromData?.map((f, index) => {
return <div key={'key' + index} style={{ display: 'flex', marginBottom: 16 }}>
<Select
style={{ width: 200, marginRight: 10 }} value={f.value1 || null}
placeholder="请选择字段"
onChange={v => {
fromData?.splice(index, 1, { value1: v, value2: f.value2, value3: f.value3 })
setFromData([...fromData])
}}
options={fieldValue?.map(d => ({ value: d, label: d })) || []}
allowClear
/>
<Select
style={{ width: 200, marginRight: 10 }} value={f.value2 || null}
placeholder="请选择条件"
onChange={v => {
fromData?.splice(index, 1, { value1: f.value1, value2: v, value3: f.value3 })
setFromData([...fromData])
}}
options={operator}
allowClear
/>
<Input
style={{ width: 200, marginRight: 10 }} value={f.value3}
placeholder="请输入值"
onChange={v => {
fromData?.splice(index, 1, { value1: f.value1, value2: f.value2, value3: v?.target?.value })
setFromData([...fromData])
}}
allowClear
/>
{index == 0 ?
<PlusCircleOutlined style={{ fontSize: 20, marginTop: 6 }} onClick={() => {
fromData?.splice(index, 0, { value1: '', value2: '', value3: '' })
setFromData([...fromData])
}} />
: <MinusCircleOutlined style={{ fontSize: 20, marginTop: 6 }} onClick={() => {
fromData?.splice(index, 1)
setFromData([...fromData])
}} />
}
</div>
})}
</TabPane>
<TabPane key="SQL">
<div style={{ fontSize: 20, marginBottom: 20, fontWeight: 600, display: 'flex', justifyContent: 'space-between' }}>预览SQL<Button onClick={() => {
console.log(editData);
console.log(database);
console.log(fromData);
let whereOption = []
fromData.map(s => {
whereOption.push(`${editData?.code}.${s.value1} ${s.value2} ${s.value3}`);
})
let sqlData = `SELECT * FROM ${editData?.code} ${fromData.length ? 'WHERE ' + whereOption.join(' AND ') : ''}`
setSql(sqlData)
}}>生成SQL</Button></div>
<TextArea value={sql} autoSize={{ minRows: 5 }} disabled={true} />
</TabPane>
<TabPane key="release">
<div style={{ fontSize: 20, marginBottom: 10, fontWeight: 600 }}>服务发布</div>
<div style={{ display: 'flex' }}>接口名称<Input style={{ width: 180 }} placeholder='请输入' /></div>
<div style={{ display: 'flex', margin: '10px 0 10px 30px' }}>URL <Select
style={{ width: 200, marginRight: 10 }}
onChange={v => {
}}
defaultValue={"GET"}
options={[{ value: "GET", label: "GET" },
{ value: "POST", label: "POST" },
{ value: "PUT", label: "PUT" },
{ value: "DEL", label: "DEL" },]}
allowClear
/>
<Input style={{ width: 180, marginLeft: 20 }} placeholder='请输入' />
</div>
<div style={{ display: 'flex', marginLeft: 20 }}>返回值
<div style={{ display: 'flex', width: 250, flexDirection: 'column', alignItems: 'center' }}>
<div key={'fieldData' + index} style={{ display: 'flex' }}>
<div style={{ width: 80, border: '1px solid #dcc' }}>名称</div>
<div style={{ width: 80, border: '1px solid #dcc' }}>意义</div>
<div style={{ width: 80, border: '1px solid #dcc' }}>说明</div>
</div>
{fieldData?.map((s, index) => {
return <div key={'fieldData' + index} style={{ display: 'flex' }}>
<div style={{ width: 80, border: '1px solid #dcc' }}>{s.code}</div>
<div style={{ width: 80, border: '1px solid #dcc' }}>{s.name}</div>
<div style={{ width: 80, border: '1px solid #dcc' }}>{s.type}</div>
</div>
})}
</div>
</div>
</TabPane>
</Tabs >
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 20 }}>
{activeKey == 'condition' || activeKey == 'SQL' || activeKey == 'release' ? <Button type="primary" onClick={() => {
if (activeKey == 'condition') {
setActiveKeyl('field')
} else if (activeKey == 'SQL') {
setActiveKeyl('condition')
} else if (activeKey == 'release') {
setActiveKeyl('SQL')
}
}}>上一步</Button> : ""}
{activeKey == 'condition' || activeKey == 'SQL' || activeKey == 'field' ? <Button style={{ marginLeft: 10 }} type="primary" onClick={() => {
if (activeKey == 'field') {
setActiveKeyl('condition')
} else if (activeKey == 'condition') {
setActiveKeyl('SQL')
} else if (activeKey == 'SQL') {
setActiveKeyl('release')
}
}}>下一步</Button> : ""}
{activeKey == 'release' ? <Button type="primary" style={{ marginLeft: 10 }} onClick={() => {
}}>完成 </Button> : ""}
<Button style={{ marginLeft: 10 }} onClick={() => onCancel()}>取消</Button>
</div>
</Modal >
)
}
function mapStateToProps (state) {
const { global, auth, } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
};
}
export default connect(mapStateToProps)(ReleaseModal)

673
web/client/src/sections/metadataManagement/containers/databasesTable.js

@ -8,343 +8,370 @@ import MetadataDatabaseModal from '../components/metadataDatabaseModal';
import { ModelTypes } from '../constants/index';
import MetadataTagModal from '../components/metadataTagModal';
import MetadataResourceModal from '../components/metadataResourceModal';
import ReleaseModal from '../components/releaseModal';
const DatabaseTable = (props) => {
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
resourceCatalogPath, isRequesting, metadataModels, setView, tagList, metadataResourceApplications, params } = props;
const { metadataManagement } = actions;
const SortValues = { 'ascend': 'asc', 'descend': 'desc' };
const [tableData, setTableData] = useState([]);
const [tableDataCount, setTableDataCount] = useState(0);//Table数据
const [createAtSort, setCreateAtSort] = useState('descend');
const [keywords, setKeywords] = useState('');
const [limit, setLimit] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [editData, setEditData] = useState({});
const [tagModalVisible, setTagModalVisible] = useState(false);
const [editTagData, setEditTagData] = useState({});
const [resourceModalVisible, setResourceModalVisible] = useState(false);
const [editResourceData, setEditResourceData] = useState({});
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
resourceCatalogPath, isRequesting, metadataModels, setView, tagList, metadataResourceApplications, params } = props;
const { metadataManagement } = actions;
const SortValues = { 'ascend': 'asc', 'descend': 'desc' };
const [tableData, setTableData] = useState([]);
const [tableDataCount, setTableDataCount] = useState(0);//Table数据
const [createAtSort, setCreateAtSort] = useState('descend');
const [keywords, setKeywords] = useState('');
const [limit, setLimit] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [editData, setEditData] = useState({});
const [tagModalVisible, setTagModalVisible] = useState(false);
const [editTagData, setEditTagData] = useState({});
const [resourceModalVisible, setResourceModalVisible] = useState(false);
const [editResourceData, setEditResourceData] = useState({});
const [releaseModal, setReleaseModal] = useState(false);
useEffect(() => {
dispatch(metadataManagement.getTagList());
setCreateAtSort('descend');
onSearch('descend');
}, [resourceCatalogId]);
useEffect(() => {
dispatch(metadataManagement.getTagList());
setCreateAtSort('descend');
onSearch('descend');
}, [resourceCatalogId]);
const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataDatabases({ resourceId: params?.type == 'databases' ? params?.resourceId : null, catalog: resourceCatalogId, keywords, orderBy: 'createAt', ...query })).then(res => {
if (res.success) {
setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count);
let resourceNames = [];
res.payload.data.rows.map(r => {
if (r.type === '表') {
resourceNames.push(r.name);
}
})
if (resourceNames.length)
dispatch(metadataManagement.getMetadataResourceApplications({ resourceNames: resourceNames.join(','), type: '库表' }))
}
const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataDatabases({ resourceId: params?.type == 'databases' ? params?.resourceId : null, catalog: resourceCatalogId, keywords, orderBy: 'createAt', ...query })).then(res => {
if (res.success) {
setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count);
let resourceNames = [];
res.payload.data.rows.map(r => {
if (r.type === '表') {
resourceNames.push(r.name);
}
})
if (resourceNames.length)
dispatch(metadataManagement.getMetadataResourceApplications({ resourceNames: resourceNames.join(','), type: '库表' }))
}
})
}
const onView = (record) => {
setView({ path: '/' + resourceCatalogPath.join('/'), ...record });
}
const onEdit = (record) => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ title: '修改库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), ...record, ...record.attributesParam } });
setModalVisible(true);
})
}
const onView = (record) => {
setView({ path: '/' + resourceCatalogPath.join('/'), ...record });
}
const onEdit = (record) => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ title: '修改库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), ...record, ...record.attributesParam } });
setModalVisible(true);
}
})
}
const confirmDelete = (id) => {
dispatch(metadataManagement.delMetadataDatabases(id)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
}
const marking = (id) => {
dispatch(metadataManagement.getTagMetadata(id, 'database')).then(res => {
if (res.success) {
const obj = { tagSet: [], tags: [], id: id }
if (res.payload.data.length) {
const tagSetIds = res.payload.data.map(d => d.tagSet)
obj.tagSet = [...new Set(tagSetIds)];
obj.tags = res.payload.data.map(d => d.id);
}
})
}
const confirmDelete = (id) => {
dispatch(metadataManagement.delMetadataDatabases(id)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
}
const marking = (id) => {
dispatch(metadataManagement.getTagMetadata(id, 'database')).then(res => {
if (res.success) {
const obj = { tagSet: [], tags: [], id: id };
if (res.payload.data.length) {
const tagSetIds = res.payload.data.map(d => d.tagSet)
obj.tagSet = [...new Set(tagSetIds)];
obj.tags = res.payload.data.map(d => d.id);
}
setEditTagData({ record: obj });
setTagModalVisible(true);
}
})
}
const onConfirmTag = (values) => {
dispatch(metadataManagement.postTagMetadata({ database: editTagData.record.id, ...values })).then(res => {
if (res.success) {
onSearch(); setTagModalVisible(false);
}
});
}
const applyResources = (record) => {
setEditResourceData({ record: { resourceId: record.id, resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '库表' } });
setResourceModalVisible(true);
}
setEditTagData({ record: obj });
setTagModalVisible(true);
}
})
}
const onConfirmTag = (values) => {
dispatch(metadataManagement.postTagMetadata({ database: editTagData.record.id, ...values })).then(res => {
if (res.success) {
onSearch(); setTagModalVisible(false);
}
});
}
const applyResources = (record) => {
setEditResourceData({ record: { resourceId: record.id, resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '库表' } });
setResourceModalVisible(true);
}
const onConfirmResource = (values) => {
dispatch(metadataManagement.postMetadataResourceApplications(values)).then(res => {
if (res.success) {
onSearch(); setResourceModalVisible(false);
}
});
}
const onTableChange = (pagination, filters, sorter) => {
let limit = Number.parseInt(pagination.pageSize);
let offset = Number.parseInt(pagination.current) - 1;
setCurrentPage(pagination.current);
setLimit(limit);
let query = { offset, limit, orderDirection: SortValues[createAtSort] };
if (sorter.columnKey === 'createAt') {
query.orderDirection = SortValues[sorter.order];
setCreateAtSort(sorter.order);
}
setSelectedRowKeys([]);
setSelectedRows([]);
initData(query);
}
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: '16%',
ellipsis: true
}, {
title: '代码',
dataIndex: 'code',
key: 'code',
width: '16%',
ellipsis: true
}, {
title: '元数据类型',
dataIndex: 'type',
key: 'type',
width: '10%'
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
width: '18%',
ellipsis: true,
render: (text, record, index) => {
let tagName = record.tagDatabases.map(tagSet => tagSet.tag.name);
return tagName.join(',');
}
}, {
title: '创建者',
dataIndex: 'createBy',
key: 'createBy',
width: '14%',
render: (text, record, index) => {
return record.user.username
}
}, {
title: '创建时间',
dataIndex: 'createAt',
key: 'createAt',
width: '18%',
sortOrder: createAtSort,
sorter: (a, b) => moment(a.createAt).valueOf() - moment(b.createAt).valueOf(),
sortDirections: ['descend', 'ascend', 'descend'],
render: (text, record, index) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss')
}
}, {
title: '操作',
dataIndex: 'action',
width: '8%',
render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name && ra.resourceId == record.id && !!record.token);
return <ButtonGroup>
<a onClick={() => onView(record)}>查看</a>
{user.role == '数据消费者' ? null :
<>
<a style={{ marginLeft: 10 }} onClick={() => onEdit(record)}>编辑</a>
<Popconfirm
title="是否确认删除该元数据?"
onConfirm={() => confirmDelete(record.id)}
> <a style={{ marginLeft: 10 }}>删除</a></Popconfirm>
{record.type === '表' ? <a style={{ marginLeft: 10 }} onClick={() => marking(record.id)}>打标</a> : null}
</>
}
{user.role !== '数据消费者' ? null : record.type === '表' ? resourceApplicationsRecords.length === 0 ?
<a style={{ marginLeft: 10 }} onClick={() => applyResources(record)}>申请资源</a> :
<span style={{ marginLeft: 10, color: "#c0c0c0" }} title='已存在资源申请'>申请资源</span> : null}
</ButtonGroup>
}
}];
const servicePublication = (record) => {
console.log(record);
setEditData(record)
setReleaseModal(true)
}
const onSearch = (sort) => {
setSelectedRowKeys([]);
setSelectedRows([]);
setCurrentPage(1);
initData({ limit, offset: 0, orderDirection: SortValues[sort || createAtSort] });
}
const handleExport = (isAll = false) => {
let tableHeader = `<tr>`;
columns.filter(c => c.dataIndex != 'action').map(c => { tableHeader += `<th><div>${c.title}</div></th>`; });
tableHeader += '</tr>';
if (isAll) {
dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId })).then(res => {
if (res.success) {
handleExportTable(tableHeader, res.payload.data.rows);
}
})
} else {
let data = []
if (createAtSort === 'descend') {
data = selectedRows.sort((a, b) => moment(b.createAt).valueOf() - moment(a.createAt).valueOf());
} else {
data = selectedRows.sort((a, b) => moment(a.createAt).valueOf() - moment(b.createAt).valueOf());
const onConfirmResource = (values) => {
dispatch(metadataManagement.postMetadataResourceApplications(values)).then(res => {
if (res.success) {
onSearch(); setResourceModalVisible(false);
}
});
}
const onTableChange = (pagination, filters, sorter) => {
let limit = Number.parseInt(pagination.pageSize);
let offset = Number.parseInt(pagination.current) - 1;
setCurrentPage(pagination.current);
setLimit(limit);
let query = { offset, limit, orderDirection: SortValues[createAtSort] };
if (sorter.columnKey === 'createAt') {
query.orderDirection = SortValues[sorter.order];
setCreateAtSort(sorter.order);
}
setSelectedRowKeys([]);
setSelectedRows([]);
initData(query);
}
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: '16%',
ellipsis: true
}, {
title: '代码',
dataIndex: 'code',
key: 'code',
width: '16%',
ellipsis: true
}, {
title: '元数据类型',
dataIndex: 'type',
key: 'type',
width: '10%'
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
width: '18%',
ellipsis: true,
render: (text, record, index) => {
let tagName = record.tagDatabases.map(tagSet => tagSet.tag.name);
return tagName.join(',');
}
}, {
title: '创建者',
dataIndex: 'createBy',
key: 'createBy',
width: '14%',
render: (text, record, index) => {
return record.user.username
}
}, {
title: '创建时间',
dataIndex: 'createAt',
key: 'createAt',
width: '18%',
sortOrder: createAtSort,
sorter: (a, b) => moment(a.createAt).valueOf() - moment(b.createAt).valueOf(),
sortDirections: ['descend', 'ascend', 'descend'],
render: (text, record, index) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss')
}
}, {
title: '操作',
dataIndex: 'action',
width: '8%',
render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name && ra.resourceId == record.id);
return <ButtonGroup>
<a onClick={() => onView(record)}>查看</a>
{user.role == '数据消费者' ? null :
<>
<a style={{ marginLeft: 10 }} onClick={() => onEdit(record)}>编辑</a>
<Popconfirm
title="是否确认删除该元数据?"
onConfirm={() => confirmDelete(record.id)}
> <a style={{ marginLeft: 10 }}>删除</a></Popconfirm>
{record.type === '表' ? <a style={{ marginLeft: 10 }} onClick={() => marking(record.id)}>打标</a> : null}
</>
}
{user.role !== '数据消费者' ? null : record.type === '表' ? resourceApplicationsRecords.length === 0 ?
<a style={{ marginLeft: 10 }} onClick={() => applyResources(record)}>申请资源</a> :
<span style={{ marginLeft: 10, color: "#c0c0c0" }} title='已存在资源申请'>申请资源</span> : null}
{user.role == '数据消费者' ? null :
record.type === '表' ?
<a style={{ marginLeft: 10 }} onClick={() => servicePublication(record)}>REST服务发布</a> : null
}
</ButtonGroup>
}
}];
const onSearch = (sort) => {
setSelectedRowKeys([]);
setSelectedRows([]);
setCurrentPage(1);
initData({ limit, offset: 0, orderDirection: SortValues[sort || createAtSort] });
}
const handleExport = (isAll = false) => {
let tableHeader = `<tr>`;
columns.filter(c => c.dataIndex != 'action').map(c => { tableHeader += `<th><div>${c.title}</div></th>`; });
tableHeader += '</tr>';
if (isAll) {
dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId })).then(res => {
if (res.success) {
handleExportTable(tableHeader, res.payload.data.rows);
}
handleExportTable(tableHeader, data);
}
}
const handleExportTable = (tableHeader, contentData) => {
let tableContent = '';
contentData.map(cd => {
tableContent += `<tr>`;
tableContent += `<th style="font-weight:600"><div>${cd.name}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.code}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.type}</div></th>`;
let tagName = cd.tagDatabases.map(tagSet => tagSet.tag.name);
tableContent += `<th style="font-weight:600"><div>${tagName.join(',')}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.user.username}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${moment(cd.createAt).format('YYYY-MM-DD HH:mm:ss')}</div></th>`;
tableContent += `</tr>`;
})
let exportTable = `\uFEFF<table border="1">
})
} else {
let data = []
if (createAtSort === 'descend') {
data = selectedRows.sort((a, b) => moment(b.createAt).valueOf() - moment(a.createAt).valueOf());
} else {
data = selectedRows.sort((a, b) => moment(a.createAt).valueOf() - moment(b.createAt).valueOf());
}
handleExportTable(tableHeader, data);
}
}
const handleExportTable = (tableHeader, contentData) => {
let tableContent = '';
contentData.map(cd => {
tableContent += `<tr>`;
tableContent += `<th style="font-weight:600"><div>${cd.name}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.code}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.type}</div></th>`;
let tagName = cd.tagDatabases.map(tagSet => tagSet.tag.name);
tableContent += `<th style="font-weight:600"><div>${tagName.join(',')}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.user.username}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${moment(cd.createAt).format('YYYY-MM-DD HH:mm:ss')}</div></th>`;
tableContent += `</tr>`;
})
let exportTable = `\uFEFF<table border="1">
${tableHeader}
${tableContent}
</table>`;
let tempStr = new Blob([exportTable], { type: 'text/plain;charset=utf-8' });
FileSaver.saveAs(tempStr, `库表元数据导出.xls`);
}
//新建、修改
const onConfirm = (values) => {
let obj = {}
if (editData.add) {
obj = { createBy: user.id, catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
dispatch(metadataManagement.postMetadataDatabases(obj)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
} else {
obj = { catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
dispatch(metadataManagement.putMetadataDatabases(editData.record.id, obj)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
}
}
return <Spin spinning={isRequesting}>
<Row style={{ marginBottom: 16 }}>
<Col span={12}>
{user.role == '数据消费者' ? null : <>
<Button type='primary' onClick={() => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ add: true, title: '新建库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), type: '目录' } });
setModalVisible(true);
}
})
}}>新建</Button>
{
tableDataCount == 0 ? <Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button> :
selectedRowKeys && selectedRowKeys.length ?
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button>
: <Popconfirm title={'是否导出全部?'} onConfirm={() => handleExport(true)} okText="确定" cancelText="取消">
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }}> 导出</Button>
</Popconfirm>
}
</>}
</Col>
<Col span={12} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Input style={{ width: 220 }} placeholder="名称/代码/类型"
allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} />
<Button type='primary' style={{ marginLeft: 16 }} onClick={onSearch}>查询</Button>
</Col>
</Row >
<Table
scroll={{ y: clientHeight - 320 }}
rowKey='id'
columns={columns}
dataSource={tableData}
onChange={onTableChange}
pagination={{
current: currentPage,
pageSize: limit,
total: tableDataCount,
showSizeChanger: true,
// showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
let tempStr = new Blob([exportTable], { type: 'text/plain;charset=utf-8' });
FileSaver.saveAs(tempStr, `库表元数据导出.xls`);
}
//新建、修改
const onConfirm = (values) => {
let obj = {}
if (editData.add) {
obj = { createBy: user.id, catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
dispatch(metadataManagement.postMetadataDatabases(obj)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
} else {
obj = { catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
dispatch(metadataManagement.putMetadataDatabases(editData.record.id, obj)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
}
}
return <Spin spinning={isRequesting}>
<Row style={{ marginBottom: 16 }}>
<Col span={12}>
{user.role == '数据消费者' ? null : <>
<Button type='primary' onClick={() => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ add: true, title: '新建库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), type: '目录' } });
setModalVisible(true);
}
})
}}>新建</Button>
{
tableDataCount == 0 ? <Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button> :
selectedRowKeys && selectedRowKeys.length ?
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button>
: <Popconfirm title={'是否导出全部?'} onConfirm={() => handleExport(true)} okText="确定" cancelText="取消">
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }}> 导出</Button>
</Popconfirm>
}
</>}
</Col>
<Col span={12} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Input style={{ width: 220 }} placeholder="名称/代码/类型"
allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} />
<Button type='primary' style={{ marginLeft: 16 }} onClick={onSearch}>查询</Button>
</Col>
</Row >
<Table
scroll={{ y: clientHeight - 320 }}
rowKey='id'
columns={columns}
dataSource={tableData}
onChange={onTableChange}
pagination={{
current: currentPage,
pageSize: limit,
total: tableDataCount,
showSizeChanger: true,
// showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
}}
rowSelection={{
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRows(selectedRows);
},
selectedRowKeys: selectedRowKeys
}}
>
</Table>
{
modalVisible ?
<MetadataDatabaseModal
modelTypes={ModelTypes.filter(m => m === '目录')}
metadataModels={metadataModels}
editData={editData}
onCancel={() => setModalVisible(false)}
onConfirm={onConfirm} /> : ''
}
{
tagModalVisible ?
<MetadataTagModal
tagList={tagList}
editData={editTagData}
onCancel={() => setTagModalVisible(false)}
onConfirm={onConfirmTag} /> : ''
}
{
resourceModalVisible ?
<MetadataResourceModal
editData={editResourceData}
onCancel={() => setResourceModalVisible(false)}
onConfirm={onConfirmResource} /> : ''
}
{
releaseModal ?
<ReleaseModal
editData={editData}
onCancel={() => {
setReleaseModal(false)
setEditData({})
}}
onConfirm={() => {
}}
rowSelection={{
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRows(selectedRows);
},
selectedRowKeys: selectedRowKeys
}}
>
</Table>
{
modalVisible ?
<MetadataDatabaseModal
modelTypes={ModelTypes.filter(m => m === '目录')}
metadataModels={metadataModels}
editData={editData}
onCancel={() => setModalVisible(false)}
onConfirm={onConfirm} /> : ''
}
{
tagModalVisible ?
<MetadataTagModal
tagList={tagList}
editData={editTagData}
onCancel={() => setTagModalVisible(false)}
onConfirm={onConfirmTag} /> : ''
}
{
resourceModalVisible ?
<MetadataResourceModal
editData={editResourceData}
onCancel={() => setResourceModalVisible(false)}
onConfirm={onConfirmResource} /> : ''
}
</Spin >
}}
/>
: ''
}
</Spin >
}
function mapStateToProps(state) {
const { global, auth, metadataDatabases, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
isRequesting: metadataDatabases.isRequesting || metadataModels.isRequesting || tagList.isRequesting
|| tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
metadataModels: metadataModels.data,
tagList: tagList.data || [],
metadataResourceApplications: metadataResourceApplications.data || []
};
function mapStateToProps (state) {
const { global, auth, metadataDatabases, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
isRequesting: metadataDatabases.isRequesting || metadataModels.isRequesting || tagList.isRequesting
|| tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
metadataModels: metadataModels.data,
tagList: tagList.data || [],
metadataResourceApplications: metadataResourceApplications.data || []
};
}
export default connect(mapStateToProps)(DatabaseTable)

1
web/client/src/utils/webapi.js

@ -22,6 +22,7 @@ export const ApiTable = {
postResourceCatalog: 'resource-catalog',
putResourceCatalog: 'resource-catalog/{id}',
delResourceCatalog: 'resource-catalog/{id}',
listStructuredData: 'listStructuredData',
//最新元数据-元数据列表查询
getMetadataDatabases: 'metadata/databases',
getMetadataFiles: 'metadata/files',

Loading…
Cancel
Save