diff --git a/api/app/lib/controllers/latestMetadata/index.js b/api/app/lib/controllers/latestMetadata/index.js index 705a249..686dfdd 100644 --- a/api/app/lib/controllers/latestMetadata/index.js +++ b/api/app/lib/controllers/latestMetadata/index.js @@ -114,13 +114,15 @@ async function getMetadataDatabases(ctx) { const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc' } = ctx.query; const where = { catalog: catalog }; if (keywords) { - where[$or] = [{ name: { $like: keywords } }, { code: { $like: keywords } }, { type: { $like: keywords } }] + where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, + { code: { $iLike: `%${keywords}%` } }, + { type: { $iLike: `%${keywords}%` } }] } - const rslt = await models.MetadataDatabase.findAll({ + const findObj = { include: [ { model: models.User, - attributes: ['id', 'name'], + attributes: ['id', 'name', 'username'], }, { model: models.TagDatabase, @@ -130,10 +132,13 @@ async function getMetadataDatabases(ctx) { }], where: where, order: [[orderBy, orderDirection]], - offset: Number(offset) * Number(limit), - limit: Number(limit), distinct: true - }); + } + if (Number(limit) > 0 && Number(offset) >= 0) { + findObj.offset = Number(offset) * Number(limit); + findObj.limit = Number(limit); + } + const rslt = await models.MetadataDatabase.findAndCountAll(findObj); ctx.status = 200; ctx.body = rslt; } catch (error) { @@ -151,13 +156,13 @@ async function getMetadataFiles(ctx) { const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc' } = ctx.query; const where = { catalog: catalog }; if (keywords) { - where[$or] = [{ name: { $like: keywords } }, { type: { $like: keywords } }] + where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` } }] } - const rslt = await models.MetadataFile.findAll({ + const findObj = { include: [ { model: models.User, - attributes: ['id', 'name'], + attributes: ['id', 'name', 'username'], }, { model: models.TagFile, @@ -167,10 +172,13 @@ async function getMetadataFiles(ctx) { }], where: where, order: [[orderBy, orderDirection]], - offset: Number(offset) * Number(limit), - limit: Number(limit), distinct: true - }); + }; + if (Number(limit) > 0 && Number(offset) >= 0) { + findObj.offset = Number(offset) * Number(limit); + findObj.limit = Number(limit); + } + const rslt = await models.MetadataFile.findAndCountAll(findObj); ctx.status = 200; ctx.body = rslt; } catch (error) { @@ -188,13 +196,13 @@ async function getMetadataRestapis(ctx) { const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc' } = ctx.query; const where = { catalog: catalog }; if (keywords) { - where.name = { $like: keywords }; + where.name = { $iLike: `%${keywords}%` }; } - const rslt = await models.MetadataRestapi.findAll({ + const findObj = { include: [ { model: models.User, - attributes: ['id', 'name'], + attributes: ['id', 'name', 'username'], }, { model: models.TagRestapi, @@ -204,10 +212,13 @@ async function getMetadataRestapis(ctx) { }], where: where, order: [[orderBy, orderDirection]], - offset: Number(offset) * Number(limit), - limit: Number(limit), distinct: true - }); + }; + if (Number(limit) > 0 && Number(offset) >= 0) { + findObj.offset = Number(offset) * Number(limit); + findObj.limit = Number(limit); + } + const rslt = await models.MetadataRestapi.findAndCountAll(findObj); ctx.status = 200; ctx.body = rslt; } catch (error) { diff --git a/scripts/0.0.4/01_alter_t_metadata_database&t_resource_consumption.sql b/scripts/0.0.4/01_alter_t_metadata_database&t_resource_consumption.sql new file mode 100644 index 0000000..303dcd9 --- /dev/null +++ b/scripts/0.0.4/01_alter_t_metadata_database&t_resource_consumption.sql @@ -0,0 +1,2 @@ +alter table t_metadata_database alter column type type varchar(255) using type::varchar(255); +alter table t_resource_consumption alter column approve_state type varchar(20) using approve_state::varchar(20); diff --git a/web/client/src/components/buttonGroup/index.js b/web/client/src/components/buttonGroup/index.js new file mode 100644 index 0000000..d0335ad --- /dev/null +++ b/web/client/src/components/buttonGroup/index.js @@ -0,0 +1,51 @@ +'use strict'; + +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Button, Popover, Icon } from 'antd'; +import { EllipsisOutlined } from '@ant-design/icons'; + +class ButtonGroup extends Component { + constructor(props) { + super(props); + this.state = { + + }; + } + + content = () => { + + } + + render_ = () => { + const { children } = this.props + return ( +
+ + + +
+ ) + } + + render() { + const { children } = this.props + if (children) { + if (Array.isArray(children)) { + if (children.some(c => c)) { + return this.render_() + } + } else { + return this.render_() + } + } + return '' + } +} + +function mapStateToProps(state) { + return { + } +} + +export default connect(mapStateToProps)(ButtonGroup); \ No newline at end of file diff --git a/web/client/src/components/index.js b/web/client/src/components/index.js index 13a1672..c20bafa 100644 --- a/web/client/src/components/index.js +++ b/web/client/src/components/index.js @@ -4,11 +4,13 @@ import Upload from './Upload'; import Uploads from './Uploads'; import NoResource from './no-resource'; -import ExportAndImport from './export' +import ExportAndImport from './export'; +import ButtonGroup from './buttonGroup'; export { Upload, Uploads, NoResource, ExportAndImport, + ButtonGroup }; diff --git a/web/client/src/sections/metadataManagement/actions/metadata.js b/web/client/src/sections/metadataManagement/actions/metadata.js index bafacd9..653cc43 100644 --- a/web/client/src/sections/metadataManagement/actions/metadata.js +++ b/web/client/src/sections/metadataManagement/actions/metadata.js @@ -50,4 +50,40 @@ export function delResourceCatalog(id) { option: '删除资源目录', } }); +} + +export function getMetadataDatabases(params) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: params, + actionType: 'GET_METADATA_DATABASES_LIST', + url: ApiTable.getMetadataDatabases, + msg: { error: '获取库表元数据列表失败' }, + reducer: { name: 'metadataDatabases' } + }); +} + +export function getMetadataFiles(params) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: params, + actionType: 'GET_METADATA_FILES_LIST', + url: ApiTable.getMetadataFiles, + msg: { error: '获取文件元数据列表失败' }, + reducer: { name: 'metadataFiles' } + }); +} + +export function getMetadataRestapis(params) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: params, + actionType: 'GET_METADATA_RESTAPIS_LIST', + url: ApiTable.getMetadataRestapis, + msg: { error: '获取接口元数据列表失败' }, + reducer: { name: 'metadataRestapis' } + }); } \ No newline at end of file diff --git a/web/client/src/sections/metadataManagement/containers/databasesTable.js b/web/client/src/sections/metadataManagement/containers/databasesTable.js new file mode 100644 index 0000000..1446c40 --- /dev/null +++ b/web/client/src/sections/metadataManagement/containers/databasesTable.js @@ -0,0 +1,232 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Spin, Table, Popconfirm, Button, Input } from 'antd'; +import { ButtonGroup } from '$components'; +import moment from 'moment'; +import FileSaver from 'file-saver'; + +const DatabaseTable = (props) => { + const { user, dispatch, actions, clientHeight, resourceCatalogId, isRequesting } = 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([]); + + useEffect(() => { + setCreateAtSort('descend'); + initData({ limit, offset: currentPage - 1, orderDirection: SortValues[createAtSort] }); + }, [resourceCatalogId]); + + const initData = (query = {}) => { + dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId, keywords, orderBy: 'createAt', ...query })).then(res => { + if (res.success) { + setTableData(res.payload.data.rows); + setTableDataCount(res.payload.data.count); + } + }) + } + const onView = (id) => { } + const onEdit = (record) => { } + const confirmDelete = (id) => { } + const marking = (id) => { } + const applyResources = (id) => { } + + 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) => { + return + onView(id)}>查看 + onEdit(record)}>编辑 + confirmDelete(record.id)} + > 删除 + marking(record.id)}>打标 + applyResources(record.id)}>申请资源 + + } + }]; + + const onSearch = () => { + setSelectedRowKeys([]); + setSelectedRows([]); + setCurrentPage(1); + initData({ limit, offset: 0, orderDirection: SortValues[createAtSort] }); + } + const handleExport = (isAll = false) => { + let tableHeader = ``; + columns.filter(c => c.dataIndex != 'action').map(c => { tableHeader += `
${c.title}
`; }); + tableHeader += ''; + if (isAll) { + dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId })).then(res => { + if (res.success) { + handleExportTable(tableHeader, res.payload.data.rows, isAll); + } + }) + } 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, isAll = false) => { + let tableContent = ''; + contentData.map(cd => { + tableContent += ``; + tableContent += `
${cd.name}
`; + tableContent += `
${cd.code}
`; + tableContent += `
${cd.type}
`; + let tagName = cd.tagDatabases.map(tagSet => tagSet.tag.name); + tableContent += `
${tagName.join(',')}
`; + tableContent += `
${cd.user.username}
`; + tableContent += `
${moment(cd.createAt).format('YYYY-MM-DD HH:mm:ss')}
`; + tableContent += ``; + }) + let exportTable = `\uFEFF + ${tableHeader} + ${tableContent} +
`; + let tempStr = new Blob([exportTable], { type: 'text/plain;charset=utf-8' }); + FileSaver.saveAs(tempStr, `库表元数据导出.xls`); + } + return +
+ + {tableDataCount == 0 ? : + selectedRowKeys && selectedRowKeys.length ? + + : handleExport(true)} okText="确定" cancelText="取消"> + + + } + + setKeywords(e.target.value || '')} /> +
+ { return {`共${Math.ceil(total / limit)}页,${total}项`} }, + onShowSizeChange: (currentPage, pageSize) => { + setCurrentPage(currentPage); + setLimit(pageSize); + }, + onChange: (page, pageSize) => { + setSelectedRowKeys([]); + setSelectedRows([]); + setCurrentPage(page); + setLimit(pageSize); + let queryParams = { + orderDirection: SortValues[createAtSort], + page: page - 1, + size: pageSize + }; + initData(queryParams); + } + }} + rowSelection={{ + onChange: (selectedRowKeys, selectedRows) => { + setSelectedRowKeys(selectedRowKeys) + setSelectedRows(selectedRows); + }, + selectedRowKeys: selectedRowKeys + }} + > +
+
+ +} +function mapStateToProps(state) { + const { global, auth, metadataDatabases } = state; + return { + user: auth.user, + actions: global.actions, + clientHeight: global.clientHeight, + isRequesting: metadataDatabases.isRequesting, + }; +} +export default connect(mapStateToProps)(DatabaseTable) \ No newline at end of file diff --git a/web/client/src/sections/metadataManagement/containers/filesTable.js b/web/client/src/sections/metadataManagement/containers/filesTable.js new file mode 100644 index 0000000..1bbd6e3 --- /dev/null +++ b/web/client/src/sections/metadataManagement/containers/filesTable.js @@ -0,0 +1,105 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Spin, Table, Popconfirm } from 'antd'; +import moment from 'moment'; + +const FilesTable = (props) => { + const { user, dispatch, actions, clientHeight, resourceCatalogId, isRequesting } = props; + const { metadataManagement } = actions; + const [resourceCatalogData, setResourceCatalogData] = useState([]); + const [limit, setLimit] = useState(10) + const [offset, setOffset] = useState(0) + + useEffect(() => { + initData(resourceCatalogId); + }, []); + + const initData = (resourceCatalogId) => { + dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, limit, offset })).then(res => { + const { data } = res.payload; + if (res.success) { + + } + }) + } + const onEdit = (record) => { } + const confirmDelete = (id) => { } + const marking = (id) => { } + const applyResources = (id) => { } + const columns = [{ + title: '文件名称', + dataIndex: 'name', + key: 'name', + width: '20%' + }, { + title: '文件描述', + dataIndex: 'description', + key: 'description', + width: '20%' + }, { + title: '文件类型', + dataIndex: 'type', + key: 'type', + width: '20%' + }, { + title: '标签', + dataIndex: 'tags', + key: 'tags', + width: '20%' + }, { + title: '大小', + dataIndex: 'size', + key: 'size', + width: '20%' + }, { + title: '创建者', + dataIndex: 'createBy', + key: 'createBy', + width: '10%', + }, { + title: '修改时间', + dataIndex: 'updateAt', + key: 'updateAt', + width: '20%', + render: (text, record, index) => { + return text ? moment(text).format('YYYY-MM-DD HH:mm') : '' + } + }, { + title: '操作', + dataIndex: 'action', + width: '20%', + render: (text, record) => { + return
+ onEdit(record)}>编辑 +    + confirmDelete(record.id)} + > 删除 +    + marking(record.id)}>打标 +    + applyResources(record.id)}>申请资源 +
+ } + }]; + + return + +
+
+ +} +function mapStateToProps(state) { + const { global, auth, metadataFiles } = state; + return { + user: auth.user, + actions: global.actions, + clientHeight: global.clientHeight, + isRequesting: metadataFiles.isRequesting + }; +} +export default connect(mapStateToProps)(FilesTable) \ No newline at end of file diff --git a/web/client/src/sections/metadataManagement/containers/latestMetadata.js b/web/client/src/sections/metadataManagement/containers/latestMetadata.js index 4f9a791..12f8850 100644 --- a/web/client/src/sections/metadataManagement/containers/latestMetadata.js +++ b/web/client/src/sections/metadataManagement/containers/latestMetadata.js @@ -4,6 +4,7 @@ import { Spin, Row, Col, Tree, Button, Tooltip, Popconfirm } from 'antd'; import { PlusCircleOutlined, EditOutlined, MinusCircleOutlined } from '@ant-design/icons'; import theStyle from './style.css'; import ResourceCatalogModal from '../components/resourceCatalogModal'; +import MetadataTab from './metadataTab'; let expandedKeysData = []; const LatestMetadata = (props) => { @@ -14,6 +15,7 @@ const LatestMetadata = (props) => { const [expandedKeys, setExpandedKeys] = useState([]); const [modalVisible, setModalVisible] = useState(false); const [editData, setEditData] = useState({}); + const [resourceCatalogId, setResourceCatalogId] = useState(''); useEffect(() => { initData(true); @@ -28,6 +30,8 @@ const LatestMetadata = (props) => { if (configRefresh) { setExpandedKeys(expandedKeysData); setSelectedKeys(expandedKeysData); + let id = expandedKeysData[0].split('-').pop(); + setResourceCatalogId(id); } } else { setExpandedKeys([]); @@ -120,11 +124,12 @@ const LatestMetadata = (props) => { selectedKeys={selectedKeys} onSelect={(keys, e) => { if (e.selected) { - // setCurrentPage(1); setSelectedKeys(keys); // let keyArr = allTreeNodeKeys.filter(ak => ak.key.includes(keys[0])); // let ids = keyArr.map(key => key.id); // console.log('all-' + JSON.stringify(ids)) + let id = keys[0].split('-').pop(); + setResourceCatalogId(id); } }} onExpand={(keys) => { @@ -134,7 +139,7 @@ const LatestMetadata = (props) => { /> - + { diff --git a/web/client/src/sections/metadataManagement/containers/metadataTab.js b/web/client/src/sections/metadataManagement/containers/metadataTab.js new file mode 100644 index 0000000..01bd5ad --- /dev/null +++ b/web/client/src/sections/metadataManagement/containers/metadataTab.js @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Tabs } from 'antd'; +import DatabaseTable from './databasesTable'; +import FilesTable from './filesTable'; +import RestapisTable from './restapisTable'; + +const MetadataTab = (props) => { + const { resourceCatalogId, actions, dispatch } = props; + const [activeKey, setActiveKey] = useState('databases'); + useEffect(() => { + setActiveKey('databases'); + }, [resourceCatalogId]); + + const onTabChange = (key) => { + setActiveKey(key) + } + return <> +       库表      , + key: 'databases' + }, + { + label:       文件      , + key: 'files' + }, + { + label:       接口      , + key: 'restapis' + } + ]}> + + { + activeKey === 'databases' && resourceCatalogId ? : + activeKey === 'files' && resourceCatalogId ? : + activeKey === 'restapis' && resourceCatalogId ? < RestapisTable resourceCatalogId={resourceCatalogId} /> : null + } + +} +function mapStateToProps(state) { + const { global, auth } = state; + return { + user: auth.user, + actions: global.actions + }; +} +export default connect(mapStateToProps)(MetadataTab) \ No newline at end of file diff --git a/web/client/src/sections/metadataManagement/containers/restapisTable.js b/web/client/src/sections/metadataManagement/containers/restapisTable.js new file mode 100644 index 0000000..c7904d0 --- /dev/null +++ b/web/client/src/sections/metadataManagement/containers/restapisTable.js @@ -0,0 +1,102 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Spin, Table, Popconfirm } from 'antd'; +import moment from 'moment'; + +const RestapisTable = (props) => { + const { user, dispatch, actions, clientHeight, resourceCatalogId, isRequesting } = props; + const { metadataManagement } = actions; + const [resourceCatalogData, setResourceCatalogData] = useState([]); + const [limit, setLimit] = useState(10) + const [offset, setOffset] = useState(0) + + useEffect(() => { + initData(); + }, []); + + const initData = () => { + dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, limit, offset })).then(res => { + const { data } = res.payload; + if (res.success) { + + } + }) + } + const onEdit = (record) => { } + const confirmDelete = (id) => { } + const marking = (id) => { } + const applyResources = (id) => { } + const columns = [{ + title: '接口名称', + dataIndex: 'name', + key: 'name', + width: '20%' + }, { + title: '接口路由', + dataIndex: 'url', + key: 'url', + width: '20%' + }, { + title: '接口类型', + dataIndex: 'method', + key: 'method', + width: '10%' + }, { + title: '传参', + dataIndex: 'queryParam', + key: 'queryParam', + width: '20%' + }, { + title: '返回值', + dataIndex: 'return', + key: 'return', + width: '20%' + }, { + title: '标签', + dataIndex: 'tags', + key: 'tags', + width: '20%' + }, { + title: '状态', + dataIndex: 'enabled', + key: 'enabled', + width: '10%' + }, { + title: '操作', + dataIndex: 'action', + width: '20%', + render: (text, record) => { + return
+ onEdit(record)}>编辑 +    + confirmDelete(record.id)} + > 删除 +    + marking(record.id)}>打标 +    + applyResources(record.id)}>申请资源 +
+ } + }]; + + return + +
+
+ +} +function mapStateToProps(state) { + const { global, auth, metadataRestapis } = state; + return { + user: auth.user, + actions: global.actions, + clientHeight: global.clientHeight, + isRequesting: metadataRestapis.isRequesting + }; +} +export default connect(mapStateToProps)(RestapisTable) \ No newline at end of file