diff --git a/api/app/lib/controllers/latestMetadata/index.js b/api/app/lib/controllers/latestMetadata/index.js
index 270d518..707480a 100644
--- a/api/app/lib/controllers/latestMetadata/index.js
+++ b/api/app/lib/controllers/latestMetadata/index.js
@@ -169,6 +169,8 @@ async function getMetadataFiles(ctx) {
const models = ctx.fs.dc.models;
const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc' } = ctx.query;
const where = { catalog: catalog };
+ //文件类型关键字查询时需匹配fileName不能为空。
+ //因存在编辑时将文件删除,但未重新上传直接点击取消的情况,此时文件已删除不可恢复,数据字段fileName更新为null
if (keywords) {
where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` }, fileName: { $not: null } }]
}
@@ -746,7 +748,7 @@ async function delMetadataRestapis(ctx) {
} else {
let resourceConsumptionInfo = await models.ResourceConsumption.findOne({
where: {
- resourceName: metadataFileInfo.name,
+ resourceName: metadataRestapiInfo.name,
resourceType: '接口'
}
});
diff --git a/web/client/src/sections/metadataAcquisition/containers/adapter.js b/web/client/src/sections/metadataAcquisition/containers/adapter.js
index 5855927..6601978 100644
--- a/web/client/src/sections/metadataAcquisition/containers/adapter.js
+++ b/web/client/src/sections/metadataAcquisition/containers/adapter.js
@@ -7,7 +7,7 @@ import moment from 'moment';
import { RELATION_DATABASE_TOOL_CONFIG } from '../constants/adapter';
import { useFsRequest, ApiTable } from '$utils';
-const LatestMetadata = (props) => {
+const Adapter = (props) => {
const { history, actions, dispatch, adapters } = props;
const [isModalOpen, setIsModalOpen] = useState(false);
const [refreshTree, setRefreshTree] = useState(1);
@@ -114,4 +114,4 @@ function mapStateToProps(state) {
dataSources: datasources?.data || {},
};
}
-export default connect(mapStateToProps)(LatestMetadata)
\ No newline at end of file
+export default connect(mapStateToProps)(Adapter)
\ No newline at end of file
diff --git a/web/client/src/sections/metadataManagement/components/metadataRestapiModal.js b/web/client/src/sections/metadataManagement/components/metadataRestapiModal.js
new file mode 100644
index 0000000..841989a
--- /dev/null
+++ b/web/client/src/sections/metadataManagement/components/metadataRestapiModal.js
@@ -0,0 +1,196 @@
+import React, { useEffect, useState } from 'react';
+import { Modal, Input, Form, Select, InputNumber, Tooltip, Switch } from 'antd';
+import { RestapiMethods } from '../constants/index';
+const { TextArea } = Input;
+const MetadataRestapiModal = (props) => {
+ const { onConfirm, onCancel, editData, metadataModels } = props;
+ const [form] = Form.useForm();
+ const [bodyParamRequired, setBodyParamRequired] = useState(false);
+ const [returnRequired, setReturnRequired] = useState(false);
+ useEffect(() => {
+ // form.setFieldValue('enabled', editData.record?.enabled || false);
+ }, []);
+ const handleOk = () => {
+ form.validateFields().then(values => {
+ if (onConfirm) {
+ let dataSave = JSON.parse(JSON.stringify(values));
+ dataSave.attributesParam = {};
+ metadataModels.map(m => {
+ dataSave.attributesParam[m.attributeCode] = values[m.attributeCode];
+ delete dataSave[m.attributeCode];
+ })
+ onConfirm(dataSave);
+ }
+ })
+ }
+ const isObjectString = (value) => {
+ if (typeof value !== "string") {
+ return false;
+ }
+ try {
+ JSON.parse(value);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ const validatorNull = (rule, value, getFieldValue, validateFields, label) => {
+ if (!value || !value.trim().length) {
+ return Promise.reject(new Error(`${label}不可空字符串`));
+ }
+ return Promise.resolve();
+ }
+ const renderModelItems = () => {
+ const items = metadataModels.filter(mm => mm.modelType === '接口').map(m => {
+ if (m.control === '文本框') {
+ const rules = [{ required: !m.nullable, message: '' }]
+ if (!m.nullable) {
+ rules.push(({ getFieldValue, validateFields }) => ({
+ validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, m.attributeName) }
+ }))
+ rules.push({ max: m.length, message: `${m.attributeName}不超过${m.length}个字符` })
+ }
+ return
10 ?
+ {m.attributeName.substring(0, 10) + '...'}
+ : m.attributeName}
+ name={m.attributeCode}
+ key={m.attributeCode}
+ rules={rules}>
+
+
+ } else if (m.control === '数字输入框') {
+ const rules = [{ required: !m.nullable, message: `${m.attributeName}不可空` }]
+ let maxValue = '';
+ let length = m.length;
+ if (length) {
+ while (length > 0) {
+ maxValue += '9'
+ length--;
+ }
+ }
+ return 10 ?
+ {m.attributeName.substring(0, 10) + '...'}
+ : m.attributeName}
+ name={m.attributeCode}
+ key={m.attributeCode}
+ rules={rules}>
+
+
+ } else {
+ return 10 ?
+ {m.attributeName.substring(0, 10) + '...'}
+ : m.attributeName}
+ name={m.attributeCode}
+ key={m.attributeCode}
+ rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}>
+
+
+ }
+ })
+ return items;
+ }
+ return (
+ handleOk(null)}
+ onCancel={onCancel}>
+ ({
+ validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '接口名称') }
+ })]}>
+
+
+ ({
+ validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '接口路由') }
+ })]}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderModelItems()}
+
+
+ )
+}
+export default MetadataRestapiModal;
\ No newline at end of file
diff --git a/web/client/src/sections/metadataManagement/constants/index.js b/web/client/src/sections/metadataManagement/constants/index.js
index dfcc4bf..8275fcd 100644
--- a/web/client/src/sections/metadataManagement/constants/index.js
+++ b/web/client/src/sections/metadataManagement/constants/index.js
@@ -1,6 +1,7 @@
'use strict';
import React from 'react';
export const ModelTypes = ['目录', '库', '视图', '表', '字段', '索引', '外键', '主键', '唯一约束'];
+export const RestapiMethods = ['get', 'post', 'put', 'delete'];
export const ConfigurableTypes = {
'目录': ['库'],
'库': ['视图', '表'],
diff --git a/web/client/src/sections/metadataManagement/containers/restapisTable.js b/web/client/src/sections/metadataManagement/containers/restapisTable.js
index c7904d0..b55c8a7 100644
--- a/web/client/src/sections/metadataManagement/containers/restapisTable.js
+++ b/web/client/src/sections/metadataManagement/containers/restapisTable.js
@@ -1,41 +1,107 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
-import { Spin, Table, Popconfirm } from 'antd';
-import moment from 'moment';
+import { Spin, Table, Popconfirm, Button, Input } from 'antd';
+import { ButtonGroup } from '$components';
+import MetadataRestapiModal from '../components/metadataRestapiModal';
+import MetadataTagModal from '../components/metadataTagModal';
+import MetadataResourceModal from '../components/metadataResourceModal';
const RestapisTable = (props) => {
- const { user, dispatch, actions, clientHeight, resourceCatalogId, isRequesting } = props;
+ const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
+ isRequesting, metadataModels, tagList, metadataResourceApplications } = props;
const { metadataManagement } = actions;
- const [resourceCatalogData, setResourceCatalogData] = useState([]);
- const [limit, setLimit] = useState(10)
- const [offset, setOffset] = useState(0)
+ const [tableData, setTableData] = useState([]);
+ const [tableDataCount, setTableDataCount] = useState(0);//Table数据
+ const [keywords, setKeywords] = useState('');
+ const [limit, setLimit] = useState(10);
+ const [currentPage, setCurrentPage] = useState(1);
+ 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({});
useEffect(() => {
- initData();
+ dispatch(metadataManagement.getTagList());
+ initData({ limit, offset: currentPage - 1 });
}, []);
- const initData = () => {
- dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, limit, offset })).then(res => {
- const { data } = res.payload;
+ const initData = (query = {}) => {
+ dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, keywords, ...query })).then(res => {
if (res.success) {
+ setTableData(res.payload.data.rows);
+ setTableDataCount(res.payload.data.count);
+ let resourceNames = [];
+ res.payload.data.rows.map(r => {
+ resourceNames.push(r.name);
+ })
+ if (resourceNames.length)
+ dispatch(metadataManagement.getMetadataResourceApplications({ resourceNames: resourceNames.join(','), type: '接口' }))
+ }
+ })
+ }
+ const onEdit = (record) => {
+ dispatch(metadataManagement.getMetadataModels({ modelTypes: '接口' })).then(res => {
+ if (res.success) {
+ setEditData({ title: '修改接口元数据', record: { ...record, ...record.attributesParam } });
+ setModalVisible(true);
+ }
+ })
+ }
+ const confirmDelete = (id) => {
+ dispatch(metadataManagement.delMetadataRestapis(id)).then(res => {
+ if (res.success) {
+ onSearch(); setModalVisible(false);
+ }
+ });
+ }
+ const marking = (id) => {
+ dispatch(metadataManagement.getTagMetadata(id, 'restapi')).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 onEdit = (record) => { }
- const confirmDelete = (id) => { }
- const marking = (id) => { }
- const applyResources = (id) => { }
+ const onConfirmTag = (values) => {
+ dispatch(metadataManagement.postTagMetadata({ restapi: editTagData.record.id, ...values })).then(res => {
+ if (res.success) {
+ onSearch(); setTagModalVisible(false);
+ }
+ });
+ }
+ const applyResources = (record) => {
+ setEditResourceData({ record: { 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 columns = [{
title: '接口名称',
dataIndex: 'name',
key: 'name',
- width: '20%'
+ ellipsis: true,
+ width: '23%'
}, {
title: '接口路由',
dataIndex: 'url',
key: 'url',
- width: '20%'
+ ellipsis: true,
+ width: '23%'
}, {
title: '接口类型',
dataIndex: 'method',
@@ -45,58 +111,152 @@ const RestapisTable = (props) => {
title: '传参',
dataIndex: 'queryParam',
key: 'queryParam',
+ ellipsis: true,
width: '20%'
}, {
title: '返回值',
dataIndex: 'return',
key: 'return',
- width: '20%'
+ ellipsis: true,
+ width: '23%'
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
- width: '20%'
+ width: '23%',
+ ellipsis: true,
+ render: (text, record, index) => {
+ let tagName = record.tagRestapis.map(tagSet => tagSet.tag.name);
+ return tagName.join(',');
+ }
}, {
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
- width: '10%'
+ width: '10%',
+ render: (text) => {text ? '启用' : '禁用'}
}, {
title: '操作',
dataIndex: 'action',
- width: '20%',
+ width: '8%',
render: (text, record) => {
- return
+ > 删除
+ marking(record.id)}>打标
+ {resourceApplicationsRecords.length === 0 ?
+ applyResources(record)}>申请资源 :
+ 申请资源}
+
}
}];
+ const onSearch = () => {
+ setCurrentPage(1);
+ initData({ limit, offset: 0 });
+ }
+ //新建、修改
+ const onConfirm = (values) => {
+ let obj = {}
+ if (editData.add) {
+ obj = { createBy: user.id, catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
+ dispatch(metadataManagement.postMetadataRestapis(obj)).then(() => {
+ onSearch(); setModalVisible(false);
+ });
+ } else {
+ obj = { catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
+ dispatch(metadataManagement.putMetadataRestapis(editData.record.id, obj)).then(res => {
+ if (res.success) {
+ onSearch(); setModalVisible(false);
+ }
+ });
+ }
+ }
return
-
+
+
+ setKeywords(e.target.value || '')} />
+
+
+ dataSource={tableData}
+ pagination={{
+ current: currentPage,
+ pageSize: limit,
+ total: tableDataCount,
+ showSizeChanger: true,
+ // showQuickJumper: true,
+ showTotal: (total) => { return {`共${Math.ceil(total / limit)}页,${total}项`} },
+ onShowSizeChange: (currentPage, pageSize) => {
+ setCurrentPage(currentPage);
+ setLimit(pageSize);
+ },
+ onChange: (page, pageSize) => {
+ setCurrentPage(page);
+ setLimit(pageSize);
+ let queryParams = {
+ offset: page - 1,
+ limit: pageSize
+ };
+ initData(queryParams);
+ }
+ }}
+ >
-
-
+ {
+ modalVisible ?
+ m.modelType === '接口')}
+ editData={editData}
+ onCancel={() => {
+ setModalVisible(false);
+ }}
+ onConfirm={onConfirm} /> : ''
+ }
+ {
+ tagModalVisible ?
+ setTagModalVisible(false)}
+ onConfirm={onConfirmTag} /> : ''
+ }
+ {
+ resourceModalVisible ?
+ setResourceModalVisible(false)}
+ onConfirm={onConfirmResource} /> : ''
+ }
+
}
function mapStateToProps(state) {
- const { global, auth, metadataRestapis } = state;
+ const { global, auth, metadataRestapis, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
- isRequesting: metadataRestapis.isRequesting
+ isRequesting: metadataRestapis.isRequesting || metadataModels.isRequesting || tagList.isRequesting
+ || tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
+ metadataModels: metadataModels.data,
+ tagList: tagList.data || [],
+ metadataResourceApplications: metadataResourceApplications.data || []
};
}
export default connect(mapStateToProps)(RestapisTable)
\ No newline at end of file