peng.peng 2 years ago
parent
commit
1ebe07564f
  1. 136
      api/app/lib/controllers/latestMetadata/index.js
  2. 2
      api/app/lib/models/metadata_database.js
  3. 9
      api/app/lib/routes/latestMetadata/index.js
  4. 1
      web/client/assets/files/common/readme.txt
  5. 3
      web/client/src/components/UploadLocal/index.js
  6. 4
      web/client/src/sections/metadataAcquisition/containers/adapter.js
  7. 37
      web/client/src/sections/metadataManagement/actions/metadata.js
  8. 17
      web/client/src/sections/metadataManagement/components/metadataDatabaseModal.js
  9. 11
      web/client/src/sections/metadataManagement/components/metadataFileModal.js
  10. 196
      web/client/src/sections/metadataManagement/components/metadataRestapiModal.js
  11. 11
      web/client/src/sections/metadataManagement/components/metadataTagModal.js
  12. 29
      web/client/src/sections/metadataManagement/components/modelModal.js
  13. 7
      web/client/src/sections/metadataManagement/constants/index.js
  14. 27
      web/client/src/sections/metadataManagement/containers/databasesTable.js
  15. 39
      web/client/src/sections/metadataManagement/containers/filesTable.js
  16. 6
      web/client/src/sections/metadataManagement/containers/latestMetadata.js
  17. 25
      web/client/src/sections/metadataManagement/containers/metaModelManagement.js
  18. 232
      web/client/src/sections/metadataManagement/containers/restapisTable.js
  19. 4
      web/client/src/utils/webapi.js

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

@ -90,9 +90,10 @@ async function delResourceCatalog(ctx) {
let databaseInfo = await models.MetadataDatabase.findOne({ where: { catalog: id } }); let databaseInfo = await models.MetadataDatabase.findOne({ where: { catalog: id } });
let fileInfo = await models.MetadataFile.findOne({ where: { catalog: id } }); let fileInfo = await models.MetadataFile.findOne({ where: { catalog: id } });
let restapiInfo = await models.MetadataRestapi.findOne({ where: { catalog: id } }); let restapiInfo = await models.MetadataRestapi.findOne({ where: { catalog: id } });
if (childResourceCatalogInfo || databaseInfo || fileInfo || restapiInfo) { let dataSourceInfo = await models.DataSource.findOne({ where: { mountPath: id } });
if (childResourceCatalogInfo || databaseInfo || fileInfo || restapiInfo || dataSourceInfo) {
ctx.status = 400; ctx.status = 400;
ctx.body = { message: '存在关联子类目录或元数据,请删除相关数据,再删除该资源目录' } ctx.body = { message: '存在关联数据,请删除相关数据,再删除该资源目录' }
deletable = false; deletable = false;
} }
if (deletable) { if (deletable) {
@ -169,6 +170,8 @@ async function getMetadataFiles(ctx) {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc' } = ctx.query; const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc' } = ctx.query;
const where = { catalog: catalog }; const where = { catalog: catalog };
//文件类型关键字查询时需匹配fileName不能为空。
//因存在编辑时将文件删除,但未重新上传直接点击取消的情况,此时文件已删除不可恢复,数据字段fileName更新为null
if (keywords) { if (keywords) {
where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` }, fileName: { $not: null } }] where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` }, fileName: { $not: null } }]
} }
@ -499,8 +502,8 @@ async function getTagMetadata(ctx) {
//申请资源 //申请资源
async function postMetadataResourceApplications(ctx) { async function postMetadataResourceApplications(ctx) {
try { try {
const { resourceName, applyBy } = ctx.request.body; const { resourceName, applyBy, resourceType } = ctx.request.body;
if (!resourceName || !applyBy) { if (!resourceName || !applyBy || !resourceType) {
ctx.status = 400; ctx.status = 400;
ctx.body = { message: '参数不全,请重新申请资源' } ctx.body = { message: '参数不全,请重新申请资源' }
} else { } else {
@ -577,19 +580,22 @@ async function postMetadataFiles(ctx) {
} }
} }
} }
//修改文件元数据 //修改文件元数据
async function putMetadataFiles(ctx) { async function putMetadataFiles(ctx) {
try { try {
const { id } = ctx.params; const { id } = ctx.params;
const { updateFileName } = ctx.query; const { updateFileName } = ctx.query;
const { catalog, name } = ctx.request.body; const { catalog, name, type } = ctx.request.body;
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
let metadataFileInfo = await models.MetadataFile.findOne({ where: { id } }); let metadataFileInfo = await models.MetadataFile.findOne({ where: { id } });
if (metadataFileInfo) { if (metadataFileInfo) {
if (updateFileName) {//编辑时,将文件删除,后又取消,则更新文件名为null if (updateFileName) {//编辑时,将文件删除,后又取消,则更新文件名为null
await models.MetadataFile.update({ updateAt: moment(), fileName: null }, { where: { id: id } }); await models.MetadataFile.update({ updateAt: moment(), fileName: null }, { where: { id: id } });
ctx.status = 204; ctx.status = 204;
} else {
if (!name || !catalog || !type) {
ctx.body = { message: '参数不全,请重新配置' }
ctx.status = 400;
} else { } else {
const putOne = await models.MetadataFile.findOne({ where: { id: { $not: id }, catalog: catalog, name: name } }); const putOne = await models.MetadataFile.findOne({ where: { id: { $not: id }, catalog: catalog, name: name } });
if (putOne) { if (putOne) {
@ -601,6 +607,7 @@ async function putMetadataFiles(ctx) {
ctx.body = { message: '修改元数据成功' } ctx.body = { message: '修改元数据成功' }
} }
} }
}
} else { } else {
ctx.status = 400; ctx.status = 400;
ctx.body = { message: '该元数据不存在' } ctx.body = { message: '该元数据不存在' }
@ -660,6 +667,118 @@ async function delMetadataFiles(ctx) {
ctx.body = { message: '删除元数据失败' } ctx.body = { message: '删除元数据失败' }
} }
} }
//新建接口元数据
async function postMetadataRestapis(ctx) {
try {
const { name, catalog, method, url } = ctx.request.body;
const models = ctx.fs.dc.models;
const postOne = await models.MetadataRestapi.findOne({
where: { name: name, catalog: catalog }
});
if (postOne) {
ctx.status = 400;
ctx.body = { message: '该资源目录下元数据名称已存在' }
} else {
if (!name || !catalog || !method || !url) {
ctx.body = { message: '参数不全,请重新配置' }
ctx.status = 400;
} else {
await models.MetadataRestapi.create({ createAt: moment(), ...ctx.request.body });
ctx.body = { message: '新建元数据成功' }
ctx.status = 200;
}
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "新建元数据失败"
}
}
}
//修改接口元数据
async function putMetadataRestapis(ctx) {
try {
const { id } = ctx.params;
const { catalog, name, method, url } = ctx.request.body;
const models = ctx.fs.dc.models;
let metadataRestapiInfo = await models.MetadataRestapi.findOne({ where: { id } });
if (metadataRestapiInfo) {
if (!name || !catalog || !method || !url) {
ctx.body = { message: '参数不全,请重新修改' }
ctx.status = 400;
} else {
const putOne = await models.MetadataRestapi.findOne({ where: { id: { $not: id }, catalog: catalog, name: name } });
if (putOne) {
ctx.status = 400;
ctx.body = { message: '该元数据名称已存在' }
} else {
await models.MetadataRestapi.update({ updateAt: moment(), ...ctx.request.body }, { where: { id: id } });
ctx.status = 200;
ctx.body = { message: '修改元数据成功' }
}
}
} else {
ctx.status = 400;
ctx.body = { message: '该元数据不存在' }
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "修改元数据失败"
}
}
}
//删除接口元数据
async function delMetadataRestapis(ctx) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
let metadataRestapiInfo = await models.MetadataRestapi.findOne({ where: { id } });
if (metadataRestapiInfo) {
let deletable = true;
let tagRestapiInfo = await models.TagRestapi.findOne({ where: { restapi: id } });
if (tagRestapiInfo) {
ctx.status = 400;
ctx.body = { message: '该元数据已被打标' }
deletable = false;
} else {
let resourceConsumptionInfo = await models.ResourceConsumption.findOne({
where: {
resourceName: metadataRestapiInfo.name,
resourceType: '接口'
}
});
if (resourceConsumptionInfo) {
ctx.status = 400;
ctx.body = { message: '该元数据存在资源申请' }
deletable = false;
}
}
if (deletable) {
await models.MetadataRestapi.destroy({
where: { id: id },
transaction
})
await transaction.commit();
ctx.status = 200;
ctx.body = { message: '删除元数据成功' }
}
} else {
ctx.status = 400;
ctx.body = { message: '该元数据不存在' }
}
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '删除元数据失败' }
}
}
module.exports = { module.exports = {
getResourceCatalog, getResourceCatalog,
postResourceCatalog, postResourceCatalog,
@ -679,5 +798,8 @@ module.exports = {
getMetadataResourceApplications, getMetadataResourceApplications,
postMetadataFiles, postMetadataFiles,
putMetadataFiles, putMetadataFiles,
delMetadataFiles delMetadataFiles,
postMetadataRestapis,
putMetadataRestapis,
delMetadataRestapis
} }

2
api/app/lib/models/metadata_database.js

@ -106,7 +106,7 @@ module.exports = dc => {
autoIncrement: false autoIncrement: false
}, },
updateAt: { updateAt: {
type: DataTypes.INTEGER, type: DataTypes.DATE,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
comment: "修改时间", comment: "修改时间",

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

@ -59,4 +59,13 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['DEL/metadata/files/:id'] = { content: '删除文件元数据', visible: true }; app.fs.api.logAttr['DEL/metadata/files/:id'] = { content: '删除文件元数据', visible: true };
router.delete('/metadata/files/:id', latestMetadata.delMetadataFiles); router.delete('/metadata/files/:id', latestMetadata.delMetadataFiles);
app.fs.api.logAttr['POST/metadata/restapis'] = { content: '新建接口元数据', visible: true };
router.post('/metadata/restapis', latestMetadata.postMetadataRestapis);
app.fs.api.logAttr['PUT/metadata/restapis/:id'] = { content: '修改接口元数据', visible: true };
router.put('/metadata/restapis/:id', latestMetadata.putMetadataRestapis);
app.fs.api.logAttr['DEL/metadata/restapis/:id'] = { content: '删除接口元数据', visible: true };
router.delete('/metadata/restapis/:id', latestMetadata.delMetadataRestapis);
}; };

1
web/client/assets/files/common/readme.txt

@ -0,0 +1 @@
03专项三期文件本地上传默认路径

3
web/client/src/components/UploadLocal/index.js

@ -50,11 +50,10 @@ class Uploads extends Component {
if (value) { if (value) {
// this.setState(value); // this.setState(value);
this.setState({ fileList: value }) this.setState({ fileList: value })
} }
} }
componentWillReceiveProps(np) { UNSAFE_componentWillReceiveProps(np) {
const { dispatch, value: thisEditData, onChange } = this.props; const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData } = np; const { value: nextEditData } = np;

4
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 { RELATION_DATABASE_TOOL_CONFIG } from '../constants/adapter';
import { useFsRequest, ApiTable } from '$utils'; import { useFsRequest, ApiTable } from '$utils';
const LatestMetadata = (props) => { const Adapter = (props) => {
const { history, actions, dispatch, adapters } = props; const { history, actions, dispatch, adapters } = props;
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [refreshTree, setRefreshTree] = useState(1); const [refreshTree, setRefreshTree] = useState(1);
@ -114,4 +114,4 @@ function mapStateToProps(state) {
dataSources: datasources?.data || {}, dataSources: datasources?.data || {},
}; };
} }
export default connect(mapStateToProps)(LatestMetadata) export default connect(mapStateToProps)(Adapter)

37
web/client/src/sections/metadataManagement/actions/metadata.js

@ -231,3 +231,40 @@ export function delMetadataFiles(id) {
} }
}); });
} }
export function postMetadataRestapis(data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_METADATA_RESTAPIS',
url: ApiTable.postMetadataRestapis,
msg: { option: '新建元数据' },
reducer: {}
});
}
export function putMetadataRestapis(id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_METADATA_RESTAPIS',
url: ApiTable.putMetadataRestapis.replace('{id}', id),
msg: {
option: '修改元数据',
}
});
}
export function delMetadataRestapis(id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_METADATA_RESTAPIS',
url: ApiTable.delMetadataRestapis.replace('{id}', id),
msg: {
option: '删除元数据',
}
});
}

17
web/client/src/sections/metadataManagement/components/metadataDatabaseModal.js

@ -12,7 +12,7 @@ const MetadataDatabaseModal = (props) => {
if (onConfirm) { if (onConfirm) {
let dataSave = JSON.parse(JSON.stringify(values)); let dataSave = JSON.parse(JSON.stringify(values));
dataSave.attributesParam = {}; dataSave.attributesParam = {};
metadataModels.map(m => { metadataModels.filter(mm => mm.modelType === type).map(m => {
dataSave.attributesParam[m.attributeCode] = values[m.attributeCode]; dataSave.attributesParam[m.attributeCode] = values[m.attributeCode];
delete dataSave[m.attributeCode]; delete dataSave[m.attributeCode];
}) })
@ -41,16 +41,18 @@ const MetadataDatabaseModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={rules}> rules={rules}>
<Input style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} /> <Input style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item> </Form.Item>
} else if (m.control === '数字输入框') { } else if (m.control === '数字输入框') {
const rules = [{ required: !m.nullable, message: `${m.attributeName}不可空` }] const rules = [{ required: !m.nullable, message: `${m.attributeName}不可空` }]
let maxValue = ''; let maxValue = '';
if (m.length) { let length = m.length;
while (m.length > 0) { if (length) {
while (length > 0) {
maxValue += '9' maxValue += '9'
m.length--; length--;
} }
} }
return <Form.Item return <Form.Item
@ -58,6 +60,7 @@ const MetadataDatabaseModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={rules}> rules={rules}>
<InputNumber min={0} max={maxValue ? parseInt(maxValue) : 0} precision={0} style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} /> <InputNumber min={0} max={maxValue ? parseInt(maxValue) : 0} precision={0} style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item> </Form.Item>
@ -67,6 +70,7 @@ const MetadataDatabaseModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}> rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}>
<Select <Select
placeholder={`请选择${m.attributeName}`} placeholder={`请选择${m.attributeName}`}
@ -94,6 +98,7 @@ const MetadataDatabaseModal = (props) => {
<Form.Item <Form.Item
label='元数据名称' label='元数据名称'
name='name' name='name'
key='name'
rules={[{ required: true, message: '' }, { max: 50, message: `元数据名称不超过50个字符` }, rules={[{ required: true, message: '' }, { max: 50, message: `元数据名称不超过50个字符` },
({ getFieldValue, validateFields }) => ({ ({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '元数据名称') } validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '元数据名称') }
@ -103,6 +108,7 @@ const MetadataDatabaseModal = (props) => {
<Form.Item <Form.Item
label='元数据代码' label='元数据代码'
name='code' name='code'
key='code'
rules={[{ required: true, message: '' }, { max: 50, message: `元数据代码不超过50个字符` }, rules={[{ required: true, message: '' }, { max: 50, message: `元数据代码不超过50个字符` },
({ getFieldValue, validateFields }) => ({ ({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '元数据代码') } validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '元数据代码') }
@ -112,6 +118,7 @@ const MetadataDatabaseModal = (props) => {
<Form.Item <Form.Item
label="元数据类型" label="元数据类型"
name='type' name='type'
key='type'
rules={[{ required: true, message: '请选择元数据类型' }]}> rules={[{ required: true, message: '请选择元数据类型' }]}>
<Select <Select
placeholder='请选择元数据类型' placeholder='请选择元数据类型'
@ -130,6 +137,7 @@ const MetadataDatabaseModal = (props) => {
<Form.Item <Form.Item
label='上下文路径' label='上下文路径'
name='path' name='path'
key='path'
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<TextArea disabled rows={1} style={{ width: '90%' }} /> <TextArea disabled rows={1} style={{ width: '90%' }} />
@ -137,6 +145,7 @@ const MetadataDatabaseModal = (props) => {
<Form.Item <Form.Item
label='详情' label='详情'
name='description' name='description'
key='description'
rules={[{ max: 255, message: `详情不超过255个字符` }]}> rules={[{ max: 255, message: `详情不超过255个字符` }]}>
<TextArea rows={4} style={{ width: '90%' }} placeholder={`请输入详情`} /> <TextArea rows={4} style={{ width: '90%' }} placeholder={`请输入详情`} />
</Form.Item> </Form.Item>

11
web/client/src/sections/metadataManagement/components/metadataFileModal.js

@ -13,7 +13,7 @@ const MetadataFileModal = (props) => {
if (onConfirm) { if (onConfirm) {
let dataSave = JSON.parse(JSON.stringify(values)); let dataSave = JSON.parse(JSON.stringify(values));
dataSave.attributesParam = {}; dataSave.attributesParam = {};
metadataModels.map(m => { metadataModels.filter(mm => mm.modelType === '文件').map(m => {
dataSave.attributesParam[m.attributeCode] = values[m.attributeCode]; dataSave.attributesParam[m.attributeCode] = values[m.attributeCode];
delete dataSave[m.attributeCode]; delete dataSave[m.attributeCode];
}) })
@ -42,6 +42,7 @@ const MetadataFileModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={rules}> rules={rules}>
<Input style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} /> <Input style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item> </Form.Item>
@ -60,6 +61,7 @@ const MetadataFileModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={rules}> rules={rules}>
<InputNumber min={0} max={maxValue ? parseInt(maxValue) : 0} precision={0} style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} /> <InputNumber min={0} max={maxValue ? parseInt(maxValue) : 0} precision={0} style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item> </Form.Item>
@ -69,6 +71,7 @@ const MetadataFileModal = (props) => {
{m.attributeName.substring(0, 10) + '...'} {m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName} </Tooltip> : m.attributeName}
name={m.attributeCode} name={m.attributeCode}
key={m.attributeCode}
rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}> rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}>
<Select <Select
placeholder={`请选择${m.attributeName}`} placeholder={`请选择${m.attributeName}`}
@ -134,6 +137,7 @@ const MetadataFileModal = (props) => {
<Form.Item <Form.Item
label='文件名称' label='文件名称'
name='name' name='name'
key='name'
rules={[{ required: true, message: '' }, { max: 50, message: `文件名称不超过50个字符` }, rules={[{ required: true, message: '' }, { max: 50, message: `文件名称不超过50个字符` },
({ getFieldValue, validateFields }) => ({ ({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '文件名称') } validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '文件名称') }
@ -143,12 +147,14 @@ const MetadataFileModal = (props) => {
<Form.Item <Form.Item
label='文件描述' label='文件描述'
name='description' name='description'
key='description'
rules={[{ max: 255, message: `文件描述不超过255个字符` }]}> rules={[{ max: 255, message: `文件描述不超过255个字符` }]}>
<TextArea rows={4} style={{ width: '90%' }} placeholder={`请输入文件描述`} /> <TextArea rows={4} style={{ width: '90%' }} placeholder={`请输入文件描述`} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label='文件' label='文件'
name='files' name='files'
key='files'
rules={[{ required: true, message: '文件不可为空' }]}> rules={[{ required: true, message: '文件不可为空' }]}>
<UploadLocal <UploadLocal
addNew={editData.add || !editData.record.files.length} addNew={editData.add || !editData.record.files.length}
@ -158,11 +164,10 @@ const MetadataFileModal = (props) => {
onChange={vsjunct} onChange={vsjunct}
fileTypes={["jpg", "png", "gif", "txt", "doc", "docx", "pdf", "xls", "xlsx", "zip", "rar"]} fileTypes={["jpg", "png", "gif", "txt", "doc", "docx", "pdf", "xls", "xlsx", "zip", "rar"]}
value={editUrl} value={editUrl}
defaultValue={editUrl}
fileList={editData.record.files || []} fileList={editData.record.files || []}
/> />
</Form.Item> </Form.Item>
<Form.Item style={{ paddingLeft: 190 }}> <Form.Item style={{ paddingLeft: 190 }} key='tip'>
<Tag color="orange">文件大小不超过40MB开放资源包含多个文件建议将文件进行压缩形成压缩包再上传</Tag> <Tag color="orange">文件大小不超过40MB开放资源包含多个文件建议将文件进行压缩形成压缩包再上传</Tag>
<Tag color="orange">支持的文件格式jpg,png,gif,txt,doc,docx,pdf,xsl,xlsx,zip,rar</Tag> <Tag color="orange">支持的文件格式jpg,png,gif,txt,doc,docx,pdf,xsl,xlsx,zip,rar</Tag>
</Form.Item> </Form.Item>

196
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.filter(mm => mm.modelType === '接口').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 <Form.Item
label={m.attributeName.length > 10 ? <Tooltip title={m.attributeName}>
{m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName}
name={m.attributeCode}
key={m.attributeCode}
rules={rules}>
<Input style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item>
} 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 <Form.Item
label={m.attributeName.length > 10 ? <Tooltip title={m.attributeName}>
{m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName}
name={m.attributeCode}
key={m.attributeCode}
rules={rules}>
<InputNumber min={0} max={maxValue ? parseInt(maxValue) : 0} precision={0} style={{ width: '90%' }} placeholder={`请输入${m.attributeName}`} />
</Form.Item>
} else {
return <Form.Item
label={m.attributeName.length > 10 ? <Tooltip title={m.attributeName}>
{m.attributeName.substring(0, 10) + '...'}
</Tooltip> : m.attributeName}
name={m.attributeCode}
key={m.attributeCode}
rules={[{ required: !m.nullable, message: `${m.attributeName}不可空` }]}>
<Select
placeholder={`请选择${m.attributeName}`}
style={{ width: '90%' }}
showSearch
optionFilterProp='children'
getPopupContainer={triggerNode => triggerNode.parentNode}
filterOption={(input, option) => option.props.children
.toLowerCase().indexOf(input.toLowerCase()) >= 0}
>
<Select.Option value={'是'} key={'是'}></Select.Option>
<Select.Option value={'否'} key={'否'}></Select.Option>
</Select>
</Form.Item >
}
})
return items;
}
return (
<Modal title={editData.title} open={true} destroyOnClose
okText='确定' width={800}
onOk={() => handleOk(null)}
onCancel={onCancel}>
<Form form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} initialValues={editData.record || {}}>
<Form.Item
label='接口名称'
name='name'
key='name'
rules={[{ required: true, message: '' }, { max: 255, message: `接口名称不超过255个字符` },
({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '接口名称') }
})]}>
<Input style={{ width: '90%' }} placeholder={`请输入接口名称`} />
</Form.Item>
<Form.Item
label='接口路由'
name='url'
key='url'
rules={[{ required: true, message: '' }, { max: 255, message: `接口路由不超过255个字符` },
({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '接口路由') }
})]}>
<Input style={{ width: '90%' }} placeholder={`请输入接口路由`} />
</Form.Item>
<Form.Item
label="接口类型"
name='method'
key='method'
rules={[{ required: true, message: '请选择接口类型' }]}>
<Select
placeholder='请选择接口类型'
style={{ width: '90%' }}
showSearch
optionFilterProp='children'
getPopupContainer={triggerNode => triggerNode.parentNode}
filterOption={(input, option) => option.props.children
.toLowerCase().indexOf(input.toLowerCase()) >= 0}
onChange={value => {
if (value === 'post' || value === 'put') {
setBodyParamRequired(true)
setReturnRequired(false)
}
if (value === 'get') {
setBodyParamRequired(false)
setReturnRequired(true)
}
if (value === 'delete') {
setBodyParamRequired(false)
setReturnRequired(false)
}
}}
>
{RestapiMethods.map(m => <Select.Option value={m} key={m}>{m}</Select.Option>)}
</Select>
</Form.Item>
<Form.Item
label='传参'
name='queryParam'
key='queryParam'
rules={[{ max: 255, message: `传参不超过255个字符` }]}>
<TextArea rows={3} style={{ width: '90%' }} />
</Form.Item>
<Form.Item
label='请求实体'
name='bodyParam'
key='bodyParam'
rules={[{ required: bodyParamRequired }]}>
<TextArea rows={3} style={{ width: '90%' }} placeholder={`请输入请求实体`} />
</Form.Item>
<Form.Item
label='返回值'
name='return'
key='return'
rules={[{ required: returnRequired }]}>
<TextArea rows={3} style={{ width: '90%' }} placeholder={`请输入返回值`} />
</Form.Item>
<Form.Item
label='状态'
name='enabled'
key='enabled'
valuePropName='checked'>
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
{renderModelItems()}
</Form>
</Modal >
)
}
export default MetadataRestapiModal;

11
web/client/src/sections/metadataManagement/components/metadataTagModal.js

@ -15,15 +15,21 @@ const MetadataDatabaseTagModal = (props) => {
} }
const renderTagItems = () => { const renderTagItems = () => {
let tags = []; let tags = [];
const tagIds = form.getFieldValue('tags') || editData.record.tags || [];
let tagsValue = []
if (tagSet.length) { if (tagSet.length) {
tagList.map(t => { tagList.map(t => {
if (tagSet.includes(t.tagSetId)) { if (tagSet.includes(t.tagSetId)) {
t.tags.map(t => { t.tags.map(t => {
tags.push(<Select.Option value={t.id} key={`tag-${t.id}`}>{t.name}</Select.Option>) tags.push(<Select.Option value={t.id} key={`tag-${t.id}`}>{t.name}</Select.Option>);
if (tagIds.includes(t.id)) {
tagsValue.push(t.id);
}
}) })
} }
}); });
} }
form.setFieldValue('tags', tagsValue);
return tags; return tags;
} }
return ( return (
@ -46,8 +52,7 @@ const MetadataDatabaseTagModal = (props) => {
filterOption={(input, option) => option.props.children filterOption={(input, option) => option.props.children
.toLowerCase().indexOf(input.toLowerCase()) >= 0} .toLowerCase().indexOf(input.toLowerCase()) >= 0}
onChange={value => { onChange={value => {
setTagSet(value) setTagSet(value);
form.setFieldValue('tags', []);
}} }}
mode="multiple" mode="multiple"
> >

29
web/client/src/sections/metadataManagement/components/modelModal.js

@ -1,5 +1,6 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Button, Form } from 'antd'; import { Button, Form } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { import {
ModalForm, ModalForm,
ProFormSelect, ProFormSelect,
@ -17,7 +18,7 @@ const DATA_TYPES = {
"下拉框": "布尔型", "下拉框": "布尔型",
} }
export default (props) => { export default (props) => {
const { title, triggerRender, editData = null, onFinish, readOnly, models } = props; const { title, triggerRender, editData = null, onFinish, paramsCode, paramsName } = props;
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
const initialValues = editData ? { const initialValues = editData ? {
...editData, ...editData,
@ -50,17 +51,43 @@ export default (props) => {
<ProFormText <ProFormText
rules={[{ required: true, message: '请输入属性名称' }, rules={[{ required: true, message: '请输入属性名称' },
{ max: 255, message: '属性名称长度不能大于255个字符' }, { max: 255, message: '属性名称长度不能大于255个字符' },
{
validator: (rule, value, callback) => {
if (paramsName && paramsName.includes(value)) {
callback('不可与元数据基础信息名称一致');
} else {
callback();
}
}
}
]} ]}
name="attributeName" name="attributeName"
label="属性名称" label="属性名称"
tooltip={{
title: `不可与元数据基础信息名称(${paramsName && paramsName.join(',')})一致`,
icon: <InfoCircleOutlined />,
}}
/> />
<ProFormText <ProFormText
rules={[{ required: true, message: '请输入属性代码' }, rules={[{ required: true, message: '请输入属性代码' },
{ max: 255, message: '属性代码长度不能大于255个字符' }, { max: 255, message: '属性代码长度不能大于255个字符' },
{
validator: (rule, value, callback) => {
if (paramsCode && paramsCode.includes(value)) {
callback('不可与元数据基础信息代码一致');
} else {
callback();
}
}
}
]} ]}
name="attributeCode" name="attributeCode"
label="属性代码" label="属性代码"
tooltip={{
title: `不可与元数据基础信息代码(${paramsCode && paramsCode.join(',')})一致`,
icon: <InfoCircleOutlined />,
}}
/> />
<ProFormSelect <ProFormSelect

7
web/client/src/sections/metadataManagement/constants/index.js

@ -6,3 +6,10 @@ export const ConfigurableTypes = {
'库': ['视图', '表'], '库': ['视图', '表'],
'表': ['字段', '索引', '外键', '主键', '唯一约束'] '表': ['字段', '索引', '外键', '主键', '唯一约束']
} }
export const RestapiMethods = ['get', 'post', 'put', 'delete'];
export const RestapiBasicParamsCode = ['name', 'url', 'method', 'queryParam', 'bodyParam', 'return', 'enabled'];
export const RestapiBasicParamsName = ['接口名称', '接口路由', '接口类型', '传参', '请求实体', '返回值', '状态'];
export const FileBasicParamsCode = ['name', 'description', 'files'];
export const FileBasicParamsName = ['文件名称', '文件描述', '文件'];
export const DatabaseBasicParamsCode = ['name', 'code', 'type', 'path', 'description'];
export const DatabaseBasicParamsName = ['元数据名称', '元数据代码', '元数据类型', '上下文路径', '详情'];

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

@ -75,7 +75,8 @@ const DatabaseTable = (props) => {
if (res.success) { if (res.success) {
const obj = { tagSet: [], tags: [], id: id }; const obj = { tagSet: [], tags: [], id: id };
if (res.payload.data.length) { if (res.payload.data.length) {
obj.tagSet = res.payload.data.map(d => d.tagSet); const tagSetIds = res.payload.data.map(d => d.tagSet)
obj.tagSet = [...new Set(tagSetIds)];
obj.tags = res.payload.data.map(d => d.id); obj.tags = res.payload.data.map(d => d.id);
} }
setEditTagData({ record: obj }); setEditTagData({ record: obj });
@ -198,7 +199,7 @@ const DatabaseTable = (props) => {
if (isAll) { if (isAll) {
dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId })).then(res => { dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId })).then(res => {
if (res.success) { if (res.success) {
handleExportTable(tableHeader, res.payload.data.rows, isAll); handleExportTable(tableHeader, res.payload.data.rows);
} }
}) })
} else { } else {
@ -211,7 +212,7 @@ const DatabaseTable = (props) => {
handleExportTable(tableHeader, data); handleExportTable(tableHeader, data);
} }
} }
const handleExportTable = (tableHeader, contentData, isAll = false) => { const handleExportTable = (tableHeader, contentData) => {
let tableContent = ''; let tableContent = '';
contentData.map(cd => { contentData.map(cd => {
tableContent += `<tr>`; tableContent += `<tr>`;
@ -267,7 +268,7 @@ const DatabaseTable = (props) => {
</Popconfirm> </Popconfirm>
} }
<Button type='primary' style={{ marginLeft: 16, float: 'right' }} onClick={onSearch}>查询</Button> <Button type='primary' style={{ marginLeft: 16, float: 'right' }} onClick={onSearch}>查询</Button>
<Input style={{ width: 220, float: 'right' }} placeholder="输入名称/代码/类型" <Input style={{ width: 220, float: 'right' }} placeholder="名称/代码/类型"
allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} /> allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} />
</div > </div >
<Table <Table
@ -283,22 +284,7 @@ const DatabaseTable = (props) => {
showSizeChanger: true, showSizeChanger: true,
// showQuickJumper: true, // showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> }, showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
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={{ rowSelection={{
onChange: (selectedRowKeys, selectedRows) => { onChange: (selectedRowKeys, selectedRows) => {
@ -345,7 +331,6 @@ function mapStateToProps(state) {
|| tagMetadata.isRequesting || metadataResourceApplications.isRequesting, || tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
metadataModels: metadataModels.data, metadataModels: metadataModels.data,
tagList: tagList.data || [], tagList: tagList.data || [],
tagMetadata: tagMetadata.data || [],
metadataResourceApplications: metadataResourceApplications.data || [] metadataResourceApplications: metadataResourceApplications.data || []
}; };
} }

39
web/client/src/sections/metadataManagement/containers/filesTable.js

@ -34,7 +34,7 @@ const FilesTable = (props) => {
dispatch(metadataManagement.getTagList()); dispatch(metadataManagement.getTagList());
setUpdateAtSort('descend'); setUpdateAtSort('descend');
initData({ limit, offset: currentPage - 1, orderDirection: SortValues[updateAtSort] }); initData({ limit, offset: currentPage - 1, orderDirection: SortValues[updateAtSort] });
}, [resourceCatalogId]); }, []);
const initData = (query = {}) => { const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, keywords, orderBy: 'updateAt', ...query })).then(res => { dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, keywords, orderBy: 'updateAt', ...query })).then(res => {
@ -85,7 +85,8 @@ const FilesTable = (props) => {
if (res.success) { if (res.success) {
const obj = { tagSet: [], tags: [], id: id }; const obj = { tagSet: [], tags: [], id: id };
if (res.payload.data.length) { if (res.payload.data.length) {
obj.tagSet = res.payload.data.map(d => d.tagSet); const tagSetIds = res.payload.data.map(d => d.tagSet)
obj.tagSet = [...new Set(tagSetIds)];
obj.tags = res.payload.data.map(d => d.id); obj.tags = res.payload.data.map(d => d.id);
} }
setEditTagData({ record: obj }); setEditTagData({ record: obj });
@ -232,7 +233,7 @@ const FilesTable = (props) => {
if (isAll) { if (isAll) {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId })).then(res => { dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId })).then(res => {
if (res.success) { if (res.success) {
handleExportTable(tableHeader, res.payload.data.rows, isAll); handleExportTable(tableHeader, res.payload.data.rows);
} }
}) })
} else { } else {
@ -245,16 +246,16 @@ const FilesTable = (props) => {
handleExportTable(tableHeader, data); handleExportTable(tableHeader, data);
} }
} }
const handleExportTable = (tableHeader, contentData, isAll = false) => { const handleExportTable = (tableHeader, contentData) => {
let tableContent = ''; let tableContent = '';
contentData.map(cd => { contentData.map(cd => {
tableContent += `<tr>`; tableContent += `<tr>`;
tableContent += `<th style="font-weight:600"><div>${cd.name}</div></th>`; tableContent += `<th style="font-weight:600"><div>${cd.name}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.description}</div></th>`; tableContent += `<th style="font-weight:600"><div>${cd.description || ''}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.type}</div></th>`; tableContent += `<th style="font-weight:600"><div>${cd.fileName ? cd.type : ''}</div></th>`;
let tagName = cd.tagFiles.map(tagSet => tagSet.tag.name); let tagName = cd.tagFiles.map(tagSet => tagSet.tag.name);
tableContent += `<th style="font-weight:600"><div>${tagName.join(',')}</div></th>`; tableContent += `<th style="font-weight:600"><div>${tagName.join(',')}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.size}</div></th>`; tableContent += `<th style="font-weight:600"><div>${cd.fileName && cd.size ? getfilesize(cd.size) : ''}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${moment(cd.updateAt).format('YYYY-MM-DD HH:mm:ss')}</div></th>`; tableContent += `<th style="font-weight:600"><div>${moment(cd.updateAt).format('YYYY-MM-DD HH:mm:ss')}</div></th>`;
tableContent += `</tr>`; tableContent += `</tr>`;
}) })
@ -315,7 +316,7 @@ const FilesTable = (props) => {
</Popconfirm> </Popconfirm>
} }
<Button type='primary' style={{ marginLeft: 16, float: 'right' }} onClick={onSearch}>查询</Button> <Button type='primary' style={{ marginLeft: 16, float: 'right' }} onClick={onSearch}>查询</Button>
<Input style={{ width: 220, float: 'right' }} placeholder="输入名称/类型" <Input style={{ width: 220, float: 'right' }} placeholder="名称/类型"
allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} /> allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} />
</div > </div >
<Table <Table
@ -331,22 +332,7 @@ const FilesTable = (props) => {
showSizeChanger: true, showSizeChanger: true,
// showQuickJumper: true, // showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> }, showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
onShowSizeChange: (currentPage, pageSize) => {
setCurrentPage(currentPage);
setLimit(pageSize);
},
onChange: (page, pageSize) => {
setSelectedRowKeys([]);
setSelectedRows([]);
setCurrentPage(page);
setLimit(pageSize);
let queryParams = {
orderDirection: SortValues[updateAtSort],
page: page - 1,
size: pageSize
};
initData(queryParams);
}
}} }}
rowSelection={{ rowSelection={{
onChange: (selectedRowKeys, selectedRows) => { onChange: (selectedRowKeys, selectedRows) => {
@ -394,16 +380,15 @@ const FilesTable = (props) => {
</Spin > </Spin >
} }
function mapStateToProps(state) { function mapStateToProps(state) {
const { global, auth, metadataDatabases, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state; const { global, auth, metadataFiles, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return { return {
user: auth.user, user: auth.user,
actions: global.actions, actions: global.actions,
clientHeight: global.clientHeight, clientHeight: global.clientHeight,
isRequesting: metadataDatabases.isRequesting || metadataModels.isRequesting || tagList.isRequesting isRequesting: metadataFiles.isRequesting || metadataModels.isRequesting || tagList.isRequesting
|| tagMetadata.isRequesting || metadataResourceApplications.isRequesting, || tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
metadataModels: metadataModels.data, metadataModels: metadataModels.data,
tagList: tagList.data || [], tagList: tagList.data || [],
tagMetadata: tagMetadata.data || [],
metadataResourceApplications: metadataResourceApplications.data || [] metadataResourceApplications: metadataResourceApplications.data || []
}; };
} }

6
web/client/src/sections/metadataManagement/containers/latestMetadata.js

@ -92,7 +92,7 @@ const LatestMetadata = (props) => {
const renderTreeNode = (ds, dataSource) => { const renderTreeNode = (ds, dataSource) => {
return <div className={theStyle.icon} style={{ width: 180 }}> return <div className={theStyle.icon} style={{ width: 180 }}>
{setTreeNodeTitle(ds.name)} {setTreeNodeTitle(ds.name)}
<EditOutlined title='修改' style={{ marginLeft: 10 }} className={theStyle.tip} onClick={() => { <EditOutlined title='修改' className={theStyle.tip} onClick={() => {
let record = JSON.parse(JSON.stringify(ds)); let record = JSON.parse(JSON.stringify(ds));
let parentData = dataSource.filter(rc => rc.id === record.parent); let parentData = dataSource.filter(rc => rc.id === record.parent);
record.parentName = parentData.length ? parentData[0].name : ''; record.parentName = parentData.length ? parentData[0].name : '';
@ -107,9 +107,9 @@ const LatestMetadata = (props) => {
setModalVisible(false); setModalVisible(false);
}); });
}} okText="确定" cancelText="取消"> }} okText="确定" cancelText="取消">
<MinusCircleOutlined title='删除' style={{ marginLeft: 10 }} className={theStyle.tip} /> <MinusCircleOutlined title='删除' className={theStyle.tip} />
</Popconfirm> </Popconfirm>
<PlusCircleOutlined title='新建' style={{ marginLeft: 10 }} className={theStyle.tip} onClick={() => { <PlusCircleOutlined title='新建' className={theStyle.tip} onClick={() => {
setEditData({ record: { parent: ds.id, parentName: ds.name }, title: '新建子类', child: true, add: true }); setEditData({ record: { parent: ds.id, parentName: ds.name }, title: '新建子类', child: true, add: true });
setModalVisible(true); setModalVisible(true);
}} /> }} />

25
web/client/src/sections/metadataManagement/containers/metaModelManagement.js

@ -3,13 +3,18 @@ import { Spin, Popconfirm, Tree, Row, Col, Button } from 'antd';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ProTable from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table';
import MetaModelModal from '../components/modelModal' import MetaModelModal from '../components/modelModal'
import { ModelTypes as DATABASE_TYPE } from '../constants/index'; import {
ModelTypes as DATABASE_TYPE, RestapiBasicParamsName, RestapiBasicParamsCode, FileBasicParamsName, FileBasicParamsCode,
DatabaseBasicParamsName, DatabaseBasicParamsCode
} from '../constants/index';
const TreeNode = Tree.TreeNode; const TreeNode = Tree.TreeNode;
function MetaModelManagement(props) { function MetaModelManagement(props) {
const { loading, clientHeight, actions, dispatch, metaModel } = props; const { loading, clientHeight, actions, dispatch, metaModel } = props;
const [selectedKeys, setSelectKeys] = useState([DATABASE_TYPE[0]]) const [selectedKeys, setSelectKeys] = useState([DATABASE_TYPE[0]])
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [paramsName, setParamsName] = useState(DatabaseBasicParamsName);
const [paramsCode, setParamsCode] = useState(DatabaseBasicParamsCode);
const queryData = (search) => { const queryData = (search) => {
const query = { const query = {
@ -123,8 +128,9 @@ function MetaModelManagement(props) {
editData={record} editData={record}
title="编辑模型" title="编辑模型"
onFinish={onFinish} onFinish={onFinish}
models={metaModel?.rows || []}
key="editModel" key="editModel"
paramsName={paramsName}
paramsCode={paramsCode}
/>) />)
options.push( options.push(
@ -154,7 +160,17 @@ function MetaModelManagement(props) {
onSelect={(keys, e) => { onSelect={(keys, e) => {
if (e.selected) { if (e.selected) {
setCurrentPage(1); setCurrentPage(1);
setSelectKeys(keys) setSelectKeys(keys);
if (keys[0] === '接口') {
setParamsCode(RestapiBasicParamsCode);
setParamsName(RestapiBasicParamsName);
} else if (keys[0] === '文件') {
setParamsCode(FileBasicParamsCode);
setParamsName(FileBasicParamsName);
} else {
setParamsCode(DatabaseBasicParamsCode);
setParamsName(DatabaseBasicParamsName);
}
} }
}} }}
defaultExpandAll={true} defaultExpandAll={true}
@ -212,7 +228,8 @@ function MetaModelManagement(props) {
title="新建模型" title="新建模型"
onFinish={onFinish} onFinish={onFinish}
key="addModel" key="addModel"
models={metaModel?.rows || []} paramsName={paramsName}
paramsCode={paramsCode}
/> />
]} ]}
options={false} options={false}

232
web/client/src/sections/metadataManagement/containers/restapisTable.js

@ -1,41 +1,107 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Spin, Table, Popconfirm } from 'antd'; import { Spin, Table, Popconfirm, Button, Input } from 'antd';
import moment from 'moment'; import { ButtonGroup } from '$components';
import MetadataRestapiModal from '../components/metadataRestapiModal';
import MetadataTagModal from '../components/metadataTagModal';
import MetadataResourceModal from '../components/metadataResourceModal';
const RestapisTable = (props) => { 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 { metadataManagement } = actions;
const [resourceCatalogData, setResourceCatalogData] = useState([]); const [tableData, setTableData] = useState([]);
const [limit, setLimit] = useState(10) const [tableDataCount, setTableDataCount] = useState(0);//Table数据
const [offset, setOffset] = useState(0) 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(() => { useEffect(() => {
initData(); dispatch(metadataManagement.getTagList());
initData({ limit, offset: currentPage - 1 });
}, []); }, []);
const initData = () => { const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, limit, offset })).then(res => { dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, keywords, ...query })).then(res => {
const { data } = res.payload;
if (res.success) { 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 onConfirmTag = (values) => {
const confirmDelete = (id) => { } dispatch(metadataManagement.postTagMetadata({ restapi: editTagData.record.id, ...values })).then(res => {
const marking = (id) => { } if (res.success) {
const applyResources = (id) => { } 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 = [{ const columns = [{
title: '接口名称', title: '接口名称',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '20%' ellipsis: true,
width: '23%'
}, { }, {
title: '接口路由', title: '接口路由',
dataIndex: 'url', dataIndex: 'url',
key: 'url', key: 'url',
width: '20%' ellipsis: true,
width: '23%'
}, { }, {
title: '接口类型', title: '接口类型',
dataIndex: 'method', dataIndex: 'method',
@ -45,58 +111,152 @@ const RestapisTable = (props) => {
title: '传参', title: '传参',
dataIndex: 'queryParam', dataIndex: 'queryParam',
key: 'queryParam', key: 'queryParam',
ellipsis: true,
width: '20%' width: '20%'
}, { }, {
title: '返回值', title: '返回值',
dataIndex: 'return', dataIndex: 'return',
key: 'return', key: 'return',
width: '20%' ellipsis: true,
width: '23%'
}, { }, {
title: '标签', title: '标签',
dataIndex: 'tags', dataIndex: 'tags',
key: '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: '状态', title: '状态',
dataIndex: 'enabled', dataIndex: 'enabled',
key: 'enabled', key: 'enabled',
width: '10%' width: '10%',
render: (text) => <span>{text ? '启用' : '禁用'}</span>
}, { }, {
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
width: '20%', width: '8%',
render: (text, record) => { render: (text, record) => {
return <div> let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
<a onClick={() => onEdit(record)}>编辑</a> ra.applyBy == user.id && ra.resourceName === record.name);
&nbsp;&nbsp; return <ButtonGroup>
<a style={{ marginLeft: 10 }} onClick={() => onEdit(record)}>编辑</a>
<Popconfirm <Popconfirm
title="是否确认删除该元数据?若确认删除则关联的数据将一并删除!" title="是否确认删除该元数据?"
onConfirm={() => confirmDelete(record.id)} onConfirm={() => confirmDelete(record.id)}
> <a>删除</a></Popconfirm> > <a style={{ marginLeft: 10 }}>删除</a></Popconfirm>
&nbsp;&nbsp; <a style={{ marginLeft: 10 }} onClick={() => marking(record.id)}>打标</a>
<a onClick={() => marking(record.id)}>打标</a> {resourceApplicationsRecords.length === 0 ?
&nbsp;&nbsp; <a style={{ marginLeft: 10 }} onClick={() => applyResources(record)}>申请资源</a> :
<a onClick={() => applyResources(record.id)}>申请资源</a> <span style={{ marginLeft: 10, color: "#c0c0c0" }} title='已存在资源申请'>申请资源</span>}
</div> </ButtonGroup>
} }
}]; }];
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 <Spin spinning={isRequesting}> return <Spin spinning={isRequesting}>
<Table scroll={{ y: clientHeight - 320 }} <div style={{ marginBottom: 16 }}>
rowKey='restapisId' <Button type='primary' onClick={() => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: '接口' })).then(res => {
if (res.success) {
setEditData({ add: true, title: '新建接口元数据', record: {} });
setModalVisible(true);
}
})
}}>新建</Button>
<Button type='primary' style={{ marginLeft: 16, float: 'right' }} onClick={onSearch}>查询</Button>
<Input style={{ width: 220, float: 'right' }} placeholder="名称"
allowClear onPressEnter={onSearch} onChange={e => setKeywords(e.target.value || '')} />
</div >
<Table
scroll={{ y: clientHeight - 320 }}
rowKey='id'
columns={columns} columns={columns}
dataSource={[]}> dataSource={tableData}
pagination={{
current: currentPage,
pageSize: limit,
total: tableDataCount,
showSizeChanger: true,
// showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
onShowSizeChange: (currentPage, pageSize) => {
setCurrentPage(currentPage);
setLimit(pageSize);
},
onChange: (page, pageSize) => {
setCurrentPage(page);
setLimit(pageSize);
let queryParams = {
offset: page - 1,
limit: pageSize
};
initData(queryParams);
}
}}
>
</Table> </Table>
{
modalVisible ?
<MetadataRestapiModal
metadataModels={metadataModels.filter(m => m.modelType === '接口')}
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) { function mapStateToProps(state) {
const { global, auth, metadataRestapis } = state; const { global, auth, metadataRestapis, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return { return {
user: auth.user, user: auth.user,
actions: global.actions, actions: global.actions,
clientHeight: global.clientHeight, 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) export default connect(mapStateToProps)(RestapisTable)

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

@ -40,6 +40,10 @@ export const ApiTable = {
postMetadataFiles: 'metadata/files', postMetadataFiles: 'metadata/files',
putMetadataFiles: 'metadata/files/{id}', putMetadataFiles: 'metadata/files/{id}',
delMetadataFiles: 'metadata/files/{id}', delMetadataFiles: 'metadata/files/{id}',
//接口元数据增删改
postMetadataRestapis: 'metadata/restapis',
putMetadataRestapis: 'metadata/restapis/{id}',
delMetadataRestapis: 'metadata/restapis/{id}',
//元数据采集-数据源管理 //元数据采集-数据源管理
pgCheckConnect: 'adapter/check/connect', pgCheckConnect: 'adapter/check/connect',

Loading…
Cancel
Save