Browse Source

数据安全规范

master
wenlele 1 year ago
parent
commit
910f070028
  1. 376
      api/app/lib/controllers/dataQuality/index.js
  2. 93
      api/app/lib/controllers/safetySpecification/index.js
  3. 6
      api/app/lib/index.js
  4. 89
      api/app/lib/models/business_rule.js
  5. 67
      api/app/lib/models/data_security_specification.js
  6. 89
      api/app/lib/models/standard_doc.js
  7. 62
      api/app/lib/models/standard_doc_folder.js
  8. 34
      api/app/lib/routes/dataQuality/index.js
  9. 22
      api/app/lib/routes/safetySpecification/index.js
  10. 25
      scripts/0.0.7/03_alter_t_standard_doc_folder copy.sql
  11. 41
      scripts/0.0.7/04_alter_t_standard_doc.sql
  12. 51
      scripts/0.0.7/05_alter_ t_business_rule.sql
  13. 28
      scripts/0.0.7/06_alter_t_data_security_specification .sql
  14. BIN
      web/client/assets/files/common/1687322895851_3.jpg
  15. BIN
      web/client/assets/files/common/1687322905542_2.jpg
  16. 3
      web/client/src/layout/containers/layout/index.less
  17. 29
      web/client/src/sections/dataQuality/actions/approve.js
  18. 66
      web/client/src/sections/dataQuality/actions/documentLibrary.js
  19. 6
      web/client/src/sections/dataQuality/actions/index.js
  20. 52
      web/client/src/sections/dataQuality/actions/ruleLibrary.js
  21. 94
      web/client/src/sections/dataQuality/components/approveModal.js
  22. 118
      web/client/src/sections/dataQuality/components/fileModal.js
  23. 73
      web/client/src/sections/dataQuality/components/groupModal.js
  24. 104
      web/client/src/sections/dataQuality/components/ruleModal.js
  25. 270
      web/client/src/sections/dataQuality/containers/documentLibrary.js
  26. 42
      web/client/src/sections/dataQuality/containers/qualityMonitor.js
  27. 198
      web/client/src/sections/dataQuality/containers/ruleLibrary.js
  28. 4
      web/client/src/sections/dataQuality/index.js
  29. 15
      web/client/src/sections/metadataManagement/components/metadataFileModal.js
  30. 29
      web/client/src/sections/safetySpecification/actions/approve.js
  31. 4
      web/client/src/sections/safetySpecification/actions/index.js
  32. 40
      web/client/src/sections/safetySpecification/actions/specificationLibrary.js
  33. 94
      web/client/src/sections/safetySpecification/components/approveModal.js
  34. 114
      web/client/src/sections/safetySpecification/components/fileModal.js
  35. 208
      web/client/src/sections/safetySpecification/containers/specificationLibrary.js
  36. 12
      web/client/src/sections/safetySpecification/index.js
  37. 240
      web/client/src/utils/webapi.js
  38. 1
      web/package.json

376
api/app/lib/controllers/dataQuality/index.js

@ -0,0 +1,376 @@
'use strict';
const moment = require('moment')
function getStandardDocFolders (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { page, limit, resourceName, applyBy, keyword, parent } = ctx.query;
let errMsg = { message: '获取标准文档目录列表失败' }
try {
let option = {
where: { parent: parent || null },
order: [["id", "desc"]],
// distinct: true,
// include: [{
// model: models.User,
// as: 'applyUser',
// attributes: ['id', 'name']
// }, {
// model: models.User,
// as: 'approveUser',
// attributes: ['id', 'name']
// },]
}
// if (resourceName) {
// option.where.resourceName = { $iLike: `%${resourceName}%` }
// }
// if (applyBy) {
// option.include[0].where = { '$applyUser.name$': { $iLike: `%${applyBy}%` } }
// }
// if (limit) {
// option.limit = Number(limit)
// }
// if (page && limit) {
// option.offset = Number(page) * Number(limit)
// }
const res = await models.StandardDocFolder.findAll(option);
ctx.status = 200;
ctx.body = res;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errMsg
}
}
}
// 标准文档目录新增失败
function postStandardDocFolders (opts) {
return async function (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { name, parent } = ctx.request.body;
await models.StandardDocFolder.create({
name, parent, createAt: moment().format('YYYY-MM-DD HH:mm:ss')
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '标准文档目录新增失败' }
}
}
}
// 新增标准文档
function postStandardDocs (opts) {
return async function (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { docName, standardType, tags, folder, path } = ctx.request.body;
await models.StandardDoc.create({
docName, standardType, tags, folder, path, createAt: moment().format('YYYY-MM-DD HH:mm:ss')
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '新增标准文档失败' }
}
}
}
function getStandardDocs (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { keyword, folder } = ctx.query;
let errMsg = { message: '获取标准文档列表失败' }
try {
let option = {
where: { folder: folder || null },
order: [["id", "desc"]],
}
let type = ['国家标准', '行业标准', '地方标准']
if (keyword) {
option.where['$or'] = [{ docName: { $iLike: `%${keyword}%` } }, { standardType: { $in: type.filter(v => v.indexOf(keyword) != -1) } }]
}
const res = await models.StandardDoc.findAll(option);
ctx.status = 200;
ctx.body = res;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errMsg
}
}
}
function postBusinessRules (opts) {
return async function (ctx, next) {
let message = (ctx.request.body.id ? '编辑' : '新增') + '业务规则失败'
try {
const models = ctx.fs.dc.models;
const { id, name, description, problemType, problemLevel, ruleBasis } = ctx.request.body;
if (id) {
await models.BusinessRule.update({
name, description, problemType, problemLevel, ruleBasis
}, { where: { id: id } })
} else {
await models.BusinessRule.create({
name, description, problemType, problemLevel, ruleBasis, createAt: moment().format('YYYY-MM-DD HH:mm:ss')
})
}
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: message }
}
}
}
function getBusinessRules (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { page, limit, keyword, } = ctx.query;
let errMsg = { message: '查询业务规则列表失败' }
try {
let option = {
where: {},
order: [["id", "desc"]],
distinct: true,
include: [{
model: models.StandardDoc,
attributes: ['id', 'docName', 'path']
}]
}
if (keyword) {
option.where.name = { $iLike: `%${keyword}%` }
}
if (limit) {
option.limit = Number(limit)
}
if (page && limit) {
option.offset = Number(page) * Number(limit)
}
const res = await models.BusinessRule.findAndCount(option);
ctx.status = 200;
ctx.body = res;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errMsg
}
}
}
function delBusinessRules (opts) {
return async function (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
await models.BusinessRule.destroy({
where: {
id: id
}
})
ctx.status = 204;
ctx.body = { message: '删除业务规则成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '删除业务规则失败' }
}
}
}
function getRegularBasis (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { } = ctx.query;
try {
let folders = await models.StandardDocFolder.findAll({ attributes: ['id', 'name', 'parent'] }) || []
let files = await models.StandardDoc.findAll({ attributes: ['id', 'docName', 'folder'] }) || []
let res = []
let carousel = (id) => {
let list = []
let data = folders.filter(f => f.parent == id)
if (data.length > 0) {
data.map(c => {
list.push({
value: c.id,
title: c.name,
disabled: true,
children: carousel(c.id)
})
})
}
let filedata = files.filter(f => f.folder == id)
if (filedata.length > 0) {
filedata.map(f => {
list.push({
value: f.id,
title: f.docName
})
})
}
return list
}
if (folders.length > 0) {
folders.map(v => {
if (v.dataValues.parent == null) {
res.push({
value: v.id,
title: v.name,
disabled: true,
children: carousel(v.id)
})
}
})
}
if (files.length > 0) {
files.map(v => {
if (v.dataValues.folder == null) {
res.push({
value: v.id,
title: v.docName
})
}
})
}
ctx.status = 200;
ctx.body = res || [];
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '查询规则依据列表失败' }
}
}
}
function postFolderFile (opts) {
return async function (ctx, next) {
let body = { message: '删除业务规则失败' }
try {
const models = ctx.fs.dc.models;
const { folderId, fileId } = ctx.request.body;
let folderIds = folderId
let fileIds = fileId
let folderLists = await models.StandardDocFolder.findAll() || []
let carousel = (id) => {
let folderListId = []
folderLists.filter(d => {
if (id.includes(d.parent)) {
folderIds.push(d.id)
folderListId.push(d.id)
return true
} else {
return false
}
})
if (folderListId.length > 0) {
carousel(folderListId)
}
}
carousel(folderIds)
folderIds = [...new Set(folderIds)]
let fileList = await models.StandardDoc.findAll({
where: { $or: [{ folder: { $in: folderIds } }, { id: { $in: fileIds } }] },
distinct: true,
include: [{
model: models.BusinessRule
}]
})
let url = []
fileList.map(v => {
if (v.businessRules.length > 0) {
body.data = v.businessRules
body.message = '当前创建的业务规则与标准文档关联'
throw '前创建的业务规则与标准文档关联'
}
fileIds.push(v.id)
url.push(v.path)
})
fileIds = [...new Set(fileIds)]
await models.StandardDocFolder.destroy({
where: {
id: { $in: folderIds }
}
})
await models.StandardDoc.destroy({
where: {
id: { $in: fileIds }
}
})
ctx.status = 200;
ctx.body = url
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = body
}
}
}
module.exports = {
getStandardDocFolders,
postStandardDocFolders,
postStandardDocs,
getStandardDocs,
postBusinessRules,
getBusinessRules,
delBusinessRules,
getRegularBasis,
postFolderFile
}

93
api/app/lib/controllers/safetySpecification/index.js

@ -0,0 +1,93 @@
'use strict';
const moment = require('moment')
function getSpecifications (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { page, limit, keyword, } = ctx.query;
let errMsg = { message: '查询数据安全规范列表失败' }
try {
let option = {
where: {},
order: [["id", "desc"]],
}
if (keyword) {
option.where.fileName = { $iLike: `%${keyword}%` }
}
if (limit) {
option.limit = Number(limit)
}
if (page && limit) {
option.offset = Number(page) * Number(limit)
}
const res = await models.DataSecuritySpecification.findAndCount(option);
ctx.status = 200;
ctx.body = res;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errMsg
}
}
}
function delSpecifications (opts) {
return async function (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { fileIds } = ctx.params;
await models.DataSecuritySpecification.destroy({
where: {
id: { $in: fileIds.split(',') }
}
})
ctx.status = 204;
ctx.body = { message: '删除数据安全规范文件成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '删除数据安全规范文件失败' }
}
}
}
// 新增标准文档
function postSpecifications (opts) {
return async function (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { fileName, tags, path } = ctx.request.body;
await models.DataSecuritySpecification.create({
fileName, tags, path, createAt: moment().format('YYYY-MM-DD HH:mm:ss')
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '新增数据安全规范失败' }
}
}
}
module.exports = {
getSpecifications,
postSpecifications,
delSpecifications
}

6
api/app/lib/index.js

@ -56,7 +56,7 @@ module.exports.models = function (dc) {
const {
DataSource, AcquisitionTask, Adapter, User, MetadataDatabase, MetadataFile, MetadataRestapi, AcquisitionLog, ResourceCatalog,
BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi,ResourceConsumption
BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi,ResourceConsumption,BusinessRule,StandardDoc
} = dc.models;
AcquisitionTask.belongsTo(DataSource, { foreignKey: 'dataSourceId', targetKey: 'id' });
@ -88,4 +88,8 @@ module.exports.models = function (dc) {
ResourceConsumption.belongsTo(User, { foreignKey: 'applyBy', targetKey: 'id' ,as:"applyUser"});
ResourceConsumption.belongsTo(User, { foreignKey: 'approveBy', targetKey: 'id',as:'approveUser' });
BusinessRule.belongsTo(StandardDoc, { foreignKey: 'ruleBasis', targetKey: 'id' });
StandardDoc.hasMany(BusinessRule, { foreignKey: 'ruleBasis', targetKey: 'id' });
};

89
api/app/lib/models/business_rule.js

@ -0,0 +1,89 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const BusinessRule = sequelize.define("businessRule", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_business_rule_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "业务规则名称",
primaryKey: false,
field: "name",
autoIncrement: false,
},
description: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "业务规则描述",
primaryKey: false,
field: "description",
autoIncrement: false,
},
problemType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "标签",
primaryKey: false,
field: "problem_type",
autoIncrement: false,
},
ruleBasis: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "制定依据",
primaryKey: false,
field: "rule_basis",
autoIncrement: false,
references: {
key: "id",
model: "tStandardDoc"
}
},
problemLevel: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "问题级别",
primaryKey: false,
field: "problem_level",
autoIncrement: false,
},
createAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "创建时间",
primaryKey: false,
field: "create_at",
autoIncrement: false,
},
}, {
tableName: "t_business_rule",
comment: "",
indexes: []
});
dc.models.BusinessRule = BusinessRule;
// const { StandardDoc } = dc.models;
// BusinessRule.belongsTo(StandardDoc, { foreignKey: 'ruleBasis', targetKey: 'id' });
// MetadataDatabase.hasMany(TagDatabase, { foreignKey: 'database', sourceKey: 'id' });
return BusinessRule;
};

67
api/app/lib/models/data_security_specification.js

@ -0,0 +1,67 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const DataSecuritySpecification = sequelize.define("dataSecuritySpecification", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_data_security_specification_id_uindex"
},
fileName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件名",
primaryKey: false,
field: "file_name",
autoIncrement: false,
},
tags: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "标签",
primaryKey: false,
field: "tags",
autoIncrement: false,
},
createAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "文件创建时间",
primaryKey: false,
field: "create_at",
autoIncrement: false,
},
path: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件路径",
primaryKey: false,
field: "path",
autoIncrement: false,
},
}, {
tableName: "t_data_security_specification",
comment: "",
indexes: []
});
dc.models.DataSecuritySpecification = DataSecuritySpecification;
// const { StandardDoc } = dc.models;
// BusinessRule.belongsTo(StandardDoc, { foreignKey: 'ruleBasis', targetKey: 'id' });
// MetadataDatabase.hasMany(TagDatabase, { foreignKey: 'database', sourceKey: 'id' });
return DataSecuritySpecification;
};

89
api/app/lib/models/standard_doc.js

@ -0,0 +1,89 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const StandardDoc = sequelize.define("standardDoc", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_standard_doc__id_uindex"
},
docName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文档名称",
primaryKey: false,
field: "doc_name",
autoIncrement: false,
},
standardType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "标准类型",
primaryKey: false,
field: "standard_type",
autoIncrement: false,
},
tags: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "标签",
primaryKey: false,
field: "tags",
autoIncrement: false,
},
folder: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "归属的文件夹",
primaryKey: false,
field: "folder",
autoIncrement: false,
references: {
key: "id",
model: "tStandardDocFolder"
}
},
path: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文档存储路径",
primaryKey: false,
field: "path",
autoIncrement: false,
},
createAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "文件夹创建时间",
primaryKey: false,
field: "create_at",
autoIncrement: false,
},
}, {
tableName: "t_standard_doc",
comment: "",
indexes: []
});
dc.models.StandardDoc = StandardDoc;
// const { StandardDocFolder } = dc.models;
// StandardDoc.belongsTo(StandardDocFolder, { foreignKey: 'folder', targetKey: 'id' });
// MetadataDatabase.hasMany(TagDatabase, { foreignKey: 'database', sourceKey: 'id' });
return StandardDoc;
};

62
api/app/lib/models/standard_doc_folder.js

@ -0,0 +1,62 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const StandardDocFolder = sequelize.define("standardDocFolder", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_standard_doc_folder_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件夹名称",
primaryKey: false,
field: "name",
autoIncrement: false,
},
createAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "文件夹创建时间",
primaryKey: false,
field: "create_at",
autoIncrement: false,
},
parent: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "父级文件夹",
primaryKey: false,
field: "parent",
autoIncrement: false,
references: {
key: "id",
model: "tStandardDocFolder"
}
}
}, {
tableName: "t_standard_doc_folder",
comment: "",
indexes: []
});
dc.models.StandardDocFolder = StandardDocFolder;
// const { MetadataDatabase } = dc.models;
// StandardDocFolder.belongsTo(StandardDocFolder, { foreignKey: 'parent', targetKey: 'id' });
// MetadataDatabase.hasMany(TagDatabase, { foreignKey: 'database', sourceKey: 'id' });
return StandardDocFolder;
};

34
api/app/lib/routes/dataQuality/index.js

@ -0,0 +1,34 @@
'use strict';
const model = require('../../controllers/dataQuality/index');
module.exports = function (app, router, opts, AuthCode) {
app.fs.api.logAttr['POST/standard-doc-folders'] = { content: '标准文档目录新增', visible: true };
router.post('/standard-doc-folders', model.postStandardDocFolders(opts))
app.fs.api.logAttr['GET/standard-doc-folders'] = { content: '标准文档目录列表', visible: true };
router.get('/standard-doc-folders', model.getStandardDocFolders(opts));
app.fs.api.logAttr['POST/standard-docs'] = { content: '新增标准文档', visible: true };
router.post('/standard-docs', model.postStandardDocs(opts))
app.fs.api.logAttr['GET/standard-docs'] = { content: '标准文档列表', visible: true };
router.get('/standard-docs', model.getStandardDocs(opts));
app.fs.api.logAttr['POST/postFolderFile'] = { content: '删除文件夹或文件', visible: true };
router.post('/postFolderFile', model.postFolderFile(opts))
app.fs.api.logAttr['POST/business-rules'] = { content: '新增/修改业务规则', visible: true };
router.post('/business-rules', model.postBusinessRules(opts))
app.fs.api.logAttr['GET/business-rules'] = { content: '查询业务规则列表', visible: true };
router.get('/business-rules', model.getBusinessRules(opts));
app.fs.api.logAttr['DEL/business-rules/:id'] = { content: '删除业务规则', visible: true };
router.del('/business-rules/:id', model.delBusinessRules(opts))
app.fs.api.logAttr['GET/regular-basis'] = { content: '查询规则依据列表', visible: true };
router.get('/regular-basis', model.getRegularBasis(opts));
};

22
api/app/lib/routes/safetySpecification/index.js

@ -0,0 +1,22 @@
'use strict';
const model = require('../../controllers/safetySpecification/index');
module.exports = function (app, router, opts, AuthCode) {
// app.fs.api.logAttr['POST/meta/model'] = { content: '增加模型信息', visible: true };
// router.post('/meta/model', model.addModelManagement(opts))
app.fs.api.logAttr['POST/data-security/specifications'] = { content: '新增数据安全规范', visible: true };
router.post('/data-security/specifications', model.postSpecifications(opts))
app.fs.api.logAttr['GET/data-security/specifications'] = { content: '查询数据安全规范列表', visible: true };
router.get('/data-security/specifications', model.getSpecifications(opts));
app.fs.api.logAttr['del/data-security/specifications/:fileIds'] = { content: '查询数据安全规范列表', visible: true };
router.del('/data-security/specifications/:fileIds', model.delSpecifications(opts));
};

25
scripts/0.0.7/03_alter_t_standard_doc_folder copy.sql

@ -0,0 +1,25 @@
create table t_standard_doc_folder
(
id serial not null,
name varchar(255) not null,
create_at timestamp with time zone not null,
parent integer
);
comment on table t_standard_doc_folder is '标准文档文件夹';
comment on column t_standard_doc_folder.id is 'ID唯一标识';
comment on column t_standard_doc_folder.name is '文件夹名称';
comment on column t_standard_doc_folder.create_at is '文件夹创建时间';
comment on column t_standard_doc_folder.parent is '父级文件夹';
create unique index t_standard_doc_folder_id_uindex
on t_standard_doc_folder (id);
alter table t_standard_doc_folder
add constraint t_standard_doc_folder_pk
primary key (id);

41
scripts/0.0.7/04_alter_t_standard_doc.sql

@ -0,0 +1,41 @@
CREATE TYPE public."enum_standard_type" AS ENUM (
'国家标准',
'行业标准',
'地方标准'
);
create table t_standard_doc
(
id serial not null,
doc_name varchar(255) not null,
standard_type enum_standard_type not null,
tags varchar(255),
folder integer,
path varchar(255) not null,
create_at timestamp with time zone not null
);
comment on table t_standard_doc is '文档管理文件';
comment on column t_standard_doc.id is 'ID唯一标识';
comment on column t_standard_doc.doc_name is '文档名称';
comment on column t_standard_doc.standard_type is '标准类型';
comment on column t_standard_doc.tags is '标签';
comment on column t_standard_doc.folder is '归属的文件夹';
comment on column t_standard_doc.path is '文档存储路径';
comment on column t_standard_doc.create_at is '文档创建时间';
create unique index t_standard_doc_id_uindex
on t_standard_doc (id);
alter table t_standard_doc
add constraint t_standard_doc_pk
primary key (id);

51
scripts/0.0.7/05_alter_ t_business_rule.sql

@ -0,0 +1,51 @@
CREATE TYPE public."enum_problem_type" AS ENUM (
'一致性',
'准确性',
'完整性',
'有效性',
'及时性',
'规范性'
);
CREATE TYPE public."enum_problem_level" AS ENUM (
'一般',
'重要',
'严重'
);
create table t_business_rule
(
id serial not null,
name varchar(255) not null,
description varchar(255) not null,
problem_type enum_problem_type not null,
problem_level enum_problem_level not null,
rule_basis integer not null,
create_at timestamp with time zone not null
);
comment on table t_business_rule is '业务规则表';
comment on column t_business_rule.id is 'ID唯一标识';
comment on column t_business_rule.name is '业务规则名称';
comment on column t_business_rule.description is '业务规则描述';
comment on column t_business_rule.problem_type is '问题类型';
comment on column t_business_rule.problem_level is '问题级别';
comment on column t_business_rule.rule_basis is '制定依据';
comment on column t_business_rule.create_at is '创建时间';
create unique index t_business_rule_id_uindex
on t_business_rule (id);
alter table t_business_rule
add constraint t_business_rule_pk
primary key (id);

28
scripts/0.0.7/06_alter_t_data_security_specification .sql

@ -0,0 +1,28 @@
create table t_data_security_specification
(
id serial not null,
file_name varchar(255) not null,
tags varchar(255),
create_at timestamp with time zone not null,
path varchar(255) not null
);
comment on table t_data_security_specification is '数据安全规范';
comment on column t_data_security_specification.id is 'ID唯一标识';
comment on column t_data_security_specification.file_name is '文件名';
comment on column t_data_security_specification.tags is '标签';
comment on column t_data_security_specification.create_at is '文件创建时间';
comment on column t_data_security_specification.path is '文件路径';
create unique index t_data_security_specification_id_uindex
on t_data_security_specification (id);
alter table t_data_security_specification
add constraint t_data_security_specification_pk
primary key (id);

BIN
web/client/assets/files/common/1687322895851_3.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
web/client/assets/files/common/1687322905542_2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

3
web/client/src/layout/containers/layout/index.less

@ -1,2 +1,3 @@
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import '~nprogress/nprogress.css';
@import '~nprogress/nprogress.css';
@import '~simplebar-react/dist/simplebar.min.css';

29
web/client/src/sections/dataQuality/actions/approve.js

@ -1,29 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getApproveList (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_APPROVE_LIST',
url: `${ApiTable.approveList}`,
msg: { error: '获取资源消费列表失败' },
reducer: { name: '' }
});
}
export function postApprove (data = {}) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_APPROVE',
url: `${ApiTable.approveList}`,
msg: { option: '资源审批' },
reducer: { name: '' }
});
}

66
web/client/src/sections/dataQuality/actions/documentLibrary.js

@ -0,0 +1,66 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getStandardDocFolders (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_STANDARD_DOC_FOLDERS',
url: `${ApiTable.standardDocFolders}`,
msg: { error: '获取标准文档目录列表失败' },
reducer: { name: '' }
});
}
export function postStandardDocFolders (data = {}) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_STANDARD_DOC_FOLDERS',
url: `${ApiTable.standardDocFolders}`,
msg: { option: '标准文档目录新增' },
reducer: { name: '' }
});
}
export function postStandardDocs (data = {}) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_STANDARD_DOCS',
url: `${ApiTable.standardDocs}`,
msg: { option: '新增标准文档' },
reducer: { name: '' }
});
}
export function getStandardDocs (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_STANDARD_DOCS',
url: `${ApiTable.standardDocs}`,
msg: { error: '获取标准文档列表失败' },
reducer: { name: '' }
});
}
export function postFolderFile (data) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_FOLDER_FILE',
url: ApiTable.postFolderFile,
msg: { option: '删除文件夹或文件' },
reducer: { name: '' }
});
}

6
web/client/src/sections/dataQuality/actions/index.js

@ -1,9 +1,11 @@
'use strict';
import * as example from './example'
import * as approve from './approve'
import * as documentLibrary from './documentLibrary'
import * as ruleLibrary from './ruleLibrary'
export default {
...example,
...approve,
...documentLibrary,
...ruleLibrary,
}

52
web/client/src/sections/dataQuality/actions/ruleLibrary.js

@ -0,0 +1,52 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function postBusinessRules (data = {}) {
let reminder = data?.id ? '修改业务规则' : '新增业务规则'
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_BUSINESS_RULES',
url: `${ApiTable.businessRules}`,
msg: { option: reminder },
reducer: { name: '' }
});
}
export function getBusinessRules (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_BUSINESS_RULES',
url: `${ApiTable.businessRules}`,
msg: { error: '查询业务规则列表失败' },
reducer: { name: '' }
});
}
export function delBusinessRules (id) {
return dispatch => basicAction({
type: 'del',
dispatch: dispatch,
actionType: 'del_BUSINESS_RULES',
url: `${ApiTable.delBusinessRules.replace('{id}', id)}`,
msg: { option: '删除业务规则' },
reducer: { name: '' }
});
}
export function getRegularBasis (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_REGULAR_BASIS',
url: `${ApiTable.regularBasis}`,
msg: { error: '查询规则依据列表失败' },
reducer: { name: '' }
});
}

94
web/client/src/sections/dataQuality/components/approveModal.js

@ -1,94 +0,0 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid'
import { Tabs, Form, Input, DatePicker, Button, Modal, Radio } from 'antd';
function ApproveModal ({ loading, clientHeight, user, actions, dispatch, close, success, editData, }) {
const { resourceConsumption } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
useEffect(() => {
}, [])
return <>
<Modal title="数据消费审批" open={true}
onOk={e => {
form.validateFields().then(v => {
console.log(v);
dispatch(resourceConsumption.postApprove({
id: editData?.id, ...v,
approveAt: moment().format('YYYY-MM-DD HH:mm:ss'),
approveBy: user?.id,
approveState: '已审批'
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
console.log(v);
if (v.approve) {
setApprove(v.approve)
}
// setFormData(v)
}}
autoComplete="off"
>
<Form.Item label="审批意见" name="approve" rules={[{ required: true, message: '请选择审批意见' }]} >
<Radio.Group>
<Radio value="true"> 同意 </Radio>
<Radio value="false"> 不同意 </Radio>
</Radio.Group>
</Form.Item>
{!approve || approve == 'false' ?
<Form.Item label="意见内容" name="approveRemarks" rules={[{ required: true, message: '请输入意见内容' }]}>
<Input allowClear placeholder='请填写意见内容' style={{ width: 300, marginRight: 16 }} />
</Form.Item> : ""}
{!approve || approve == 'true' ? <div style={{ position: 'relative' }}>
<Form.Item label="访问令牌" name="token" rules={[{ required: true, message: '请生成令牌' }]}>
<Input allowClear placeholder='生成令牌' disabled={true} style={{ width: 300, marginRight: 16 }} />
</Form.Item>
<Button type="primary" style={{ position: 'absolute', top: 0, right: 0 }} onClick={() => {
form.setFieldsValue({ token: uuidv4() })
}}>生成</Button>
</div> : ""}
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(ApproveModal)

118
web/client/src/sections/dataQuality/components/fileModal.js

@ -0,0 +1,118 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { UploadLocal } from '$components';
import { Tabs, Form, Input, DatePicker, Button, Modal, Select, Tag } from 'antd';
function FileModal ({ loading, parent, user, actions, editData = {}, dispatch, close, success,remove }) {
const { dataQuality } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
const [editUrl, setEditUrl] = useState([]);
useEffect(() => {
}, [])
const vsjunct = (params) => {
if (params.length) {
let appendix = []
for (let p of params) {
appendix.push({
fName: p.name,
size: p.size,
fileSize: p.size,
storageUrl: p.storageUrl,//必须有storageUrl
})
}
setEditUrl(appendix)
} else {
setEditUrl([])
}
}
return <>
<Modal title="标准文档上传" open={true} width={600}
onOk={e => {
form.validateFields().then(v => {
dispatch(dataQuality.postStandardDocs({
...v,
path: v?.files[0]?.url, docName: v?.files[0]?.name,
folder: parent || null,
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
if (form.getFieldValue('files') && form.getFieldValue('files').length) {
remove(form.getFieldValue('files')[0]?.url)
}
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
}}
autoComplete="off"
labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}
>
<Form.Item label="标准类型" name="standardType" rules={[{ required: true, message: '请选择标准类型' }]}>
<Select style={{ width: 200, }} allowClear
options={[{ value: '国家标准', label: '国家标准', }, { value: '行业标准', label: '行业标准', }, { value: '地方标准', label: '地方标准', },]}
/>
</Form.Item>
<Form.Item label="标签" name="tags" >
<Input allowClear placeholder='请输入标签' style={{ width: 300, marginRight: 16 }} />
</Form.Item>
<Form.Item
label='文件'
name='files'
key='files'
rules={[{ required: true, message: '文件不可为空' }]}>
<UploadLocal
// addNew={editData.add || !editData.record.files.length}
isLocal={true}
maxFilesNum={1}
maxFileSize={40}
onChange={vsjunct}
fileTypes={["jpg", "png", "gif", "txt", "doc", "docx", "pdf", "xls", "xlsx", "zip", "rar"]}
value={editUrl}
// fileList={editData.record.files || []}
/>
</Form.Item>
<Form.Item style={{ marginLeft: 42 }} key='tip'>
<Tag color="orange">文件大小不超过40MB开放资源包含多个文件建议将文件进行压缩形成压缩包再上传</Tag>
<Tag color="orange">支持的文件格式jpg,png,gif,txt,doc,docx,pdf,xsl,xlsx,zip,rar</Tag>
</Form.Item>
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(FileModal)

73
web/client/src/sections/dataQuality/components/groupModal.js

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid'
import { Tabs, Form, Input, DatePicker, Button, Modal, Radio } from 'antd';
function GroupModal ({ loading, parent, user, actions, dispatch, close, success, }) {
const { dataQuality } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
useEffect(() => {
}, [])
return <>
<Modal title="新建分组" open={true}
onOk={e => {
form.validateFields().then(v => {
console.log(v);
dispatch(dataQuality.postStandardDocFolders({
...v,
parent: parent || null,
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
}}
autoComplete="off"
>
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入分组名称' }]}>
<Input allowClear placeholder='请输入分组名称' style={{ width: 300, }} />
</Form.Item>
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(GroupModal)

104
web/client/src/sections/dataQuality/components/ruleModal.js

@ -0,0 +1,104 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid'
import { Tabs, Form, Input, DatePicker, Button, Modal, Radio, Select, TreeSelect } from 'antd';
const { TextArea } = Input;
const { Option, OptGroup } = Select;
function RuleModal ({ loading, parent, user, actions, dispatch, close, success, treeList, editData }) {
const { dataQuality } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
useEffect(() => {
}, [])
return <>
<Modal title={editData?.id ? '编辑业务规则' : "新建业务规则"} open={true}
onOk={e => {
form.validateFields().then(v => {
// console.log(v);
dispatch(dataQuality.postBusinessRules({
...v, id: editData?.id
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
}}
autoComplete="off"
labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}
>
<Form.Item label="名称" name="name" initialValue={editData?.name} rules={[{ required: true, message: '请输入分组名称' }]}>
<Input allowClear placeholder='请输入分组名称' style={{ width: 300 }} />
</Form.Item>
<Form.Item label="描述" name="description" initialValue={editData?.description} rules={[{ required: true, message: '请输入分组名称' }]}>
<TextArea allowClear autoSize={{ minRows: 2 }} placeholder='描述' style={{ width: 300, }} />
</Form.Item>
<Form.Item label="问题类型" name="problemType" initialValue={editData?.problemType} rules={[{ required: true, message: '请输入分组名称' }]}>
<Select style={{ width: 300, }} placeholder='问题类型'
options={[{ value: '一致性', label: '一致性', }, { value: '准确性', label: '准确性', }, { value: '完整性', label: '完整性', }, { value: '有效性', label: '有效性', }, { value: '及时性', label: '及时性', }, { value: '规范性', label: '规范性', },]}
/>
</Form.Item>
<Form.Item label="问题级别" name="problemLevel" initialValue={editData?.problemLevel} rules={[{ required: true, message: '请输入分组名称' }]}>
<Select style={{ width: 300, }} placeholder='问题级别'
options={[{ value: '一般', label: '一般', }, { value: '重要', label: '重要', }, { value: '严重', label: '严重', },]}
/>
</Form.Item>
<Form.Item label="制定依据" name="ruleBasis" initialValue={editData?.ruleBasis} rules={[{ required: true, message: '请输入分组名称' }]}>
<TreeSelect
showSearch
style={{
width: 300,
}}
// value={}
dropdownStyle={{
maxHeight: 300,
overflow: 'auto',
}}
placeholder=""
allowClear
treeDefaultExpandAll
// onChange={onChange}
treeData={treeList || []}
/>
</Form.Item>
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(RuleModal)

270
web/client/src/sections/dataQuality/containers/documentLibrary.js

@ -1,80 +1,282 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import ApproveModal from '../components/approveModal';
import GroupModal from '../components/groupModal';
import FileModal from '../components/fileModal';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
import { Tabs, Form, Input, Space, Button, Table } from 'antd';
import { Tabs, Form, Input, Space, Button, Table, Breadcrumb, message, Popconfirm } from 'antd';
const { Search } = Input;
import { v1 } from 'uuid';
import { CreditCardFilled, FilePdfOutlined } from '@ant-design/icons';
import { agent } from 'superagent';
let clicks = 0
function Approve ({ loading, clientHeight, actions, dispatch, }) {
const { resourceConsumption } = actions
const { dataQuality } = actions
const [checkAll, setCheckAll] = useState(false)
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [formData, setFormData] = useState({})
const [approveModal, setApproveModal] = useState(false)
const [folderData, setFolderData] = useState([]);
const [fileData, setFileData] = useState([])
const [groupModal, setGroupModal] = useState(false)
const [fileModal, setFileModal] = useState(false)
const [editData, setEditData] = useState({})
const [column, setColumn] = useState([])
const [parent, setParent] = useState(null)
const [keywords, setKeywords] = useState()
const [breadcrumbData, setBreadcrumbData] = useState([{ name: '根目录', parent: null }])
const [folderIds, setFolderIds] = useState([])
const [fileIds, setFileIds] = useState([])
const fileId = useRef([])
const folderId = useRef([])
useEffect(() => {
resourceData()
resourceData(parent)
}, [])
let resourceData = (params) => {
// let data = params || query
// dispatch(resourceConsumption.getApproveList({ approveState: tabsKey == 'stay' ? "审批中" : '已审批', ...formData, ...data, })).then(res => {
// if (res.success) {
// setProTableList(res.payload.data)
// }
// })
}
let resourceData = async (parent, keyword) => {
dispatch(dataQuality.getStandardDocFolders({ parent: parent })).then(res => {
if (res.success) {
setFolderData(res.payload.data)
}
})
dispatch(dataQuality.getStandardDocs({ folder: parent, keyword: keyword })).then(res => {
if (res.success) {
setFileData(res.payload.data)
}
})
}
let clickEvent = (id, folderId) => {
let myDiv = document.getElementById(id);
let clicks = 0;
// 单击事件
myDiv.addEventListener("click", function () {
clicks++;
setTimeout(function () {
if (clicks === 1) {
console.log("单击");
} else if (clicks === 2) {
console.log("双击");
}
clicks = 0;
}, 300);
});
}
useEffect(() => {
}, [])
return <>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Space size={'small'}>
<Button>{checkAll ? "全选" : "取消全选"}</Button>
<Button>新建分组</Button>
<Button type="primary">上传</Button>
<Button onClick={() => {
if (!checkAll) {
let fileAll = fileData?.map(s => s.id)
let folderAll = folderData?.map(s => s.id)
fileId.current = fileAll
setFileIds(fileAll)
folderId.current = folderAll
setFolderIds(folderAll)
} else {
fileId.current = []
setFileIds([])
folderId.current = []
setFolderIds([])
}
setCheckAll(!checkAll)
}}>{checkAll ? "取消全选" : "全选"}</Button>
<Button onClick={() => {
setGroupModal(true)
}}>新建分组</Button>
<Button type="primary" onClick={() => {
setFileModal(true)
}}>上传</Button>
<Button type="primary">下载</Button>
<Button type="primary">删除</Button>
<Popconfirm
title="是否删除文件夹或文件?当有创建的业务规则与标准文档关联时则不允许删除。"
onConfirm={() => {
console.log(folderId.current,fileId.current);
if (folderId.current?.length || fileId.current?.length) {
dispatch(dataQuality.postFolderFile({ folderId: folderId.current, fileId: fileId.current })).then(res => {
if (res.success) {
resourceData(parent)
setCheckAll(false)
console.log(res);
res.payload.data?.map(v => {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: v });
})
}
})
} else {
message.warning({
duration: 1,
content: '未选择文件夹或文件',
});
}
}}
>
<Button type="primary">删除</Button>
</Popconfirm>
</Space>
<Search
placeholder="标准类型或文件文件名称关键字"
placeholder="标准类型或文件名称关键字"
value={keywords}
onChange={e => {
setKeywords(e?.target?.value)
}}
onSearch={(value, event) => {
console.log(value, event);
setKeywords(value)
resourceData(parent, value)
}}
style={{
width: 266,
}}
/>
</div >
<div style={{ height: 30, display: 'flex', alignItems: 'center' }}>
<Breadcrumb>
{breadcrumbData?.map((s, i) => {
return (
<Breadcrumb.Item style={{ cursor: 'pointer' }} onClick={() => {
let data = []
let stop = false
breadcrumbData?.map(v => {
if (!stop) {
data.push({ ...v })
if (v.name == s.name) {
stop = true
}
}
})
setBreadcrumbData([...data])
resourceData(s.parent, null)
setKeywords(null)
setParent(s.parent || null)
fileId.current = []
setFileIds([])
folderId.current = []
setFolderIds([])
}}>{s.name}</Breadcrumb.Item>
)
})
}
</Breadcrumb>
</div>
<div style={{}}>
{
folderData?.map((v, i) => {
return <div style={{ width: 310, display: 'inline-block', margin: '0 18px 10px 0' }} >
<div id={'folder' + i} style={{ display: 'flex', padding: '10px 0', border: `1px solid ${folderId.current?.includes(v.id) ? 'rgb(42 207 98)' : '#fff'}` }}
onClick={() => {
clicks++
setTimeout(function () {
if (clicks === 1) {
if (folderId.current?.includes(v.id)) {
folderId.current = folderId.current?.filter(c => c != v.id)
setFolderIds(folderId.current?.filter(c => c != v.id))
} else {
folderId.current = [...folderId.current, v.id]
setFolderIds([...folderId.current, v.id])
}
} else if (clicks === 2) {
breadcrumbData?.push({ name: v.name, parent: v.id || null })
setBreadcrumbData([...breadcrumbData])
resourceData(v.id, null)
setKeywords(null)
setParent(v.id || null)
}
clicks = 0;
}, 300);
}}
ondblclick={() => {
return false
}} >
<CreditCardFilled style={{ fontSize: 96, color: 'rgb(238 200 44)', marginRight: 8 }} />
<div style={{ width: 200, display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly' }}>
<div style={{
width: 100, whiteSpace: 'nowrap', overflow: 'hidden', fontWeight: 400,
textOverflow: 'ellipsis', fontSize: 18, color: 'rgb(51 161 34)'
}}>
{v.name}
</div>
<div>创建时间{v.createAt && moment(v.createAt).format('YYYY-MM-DD HH:mm:ss') || '--'}</div>
</div>
</div>
</div>
})
}
{
fileData?.map((v, i) => {
return <div style={{ width: 310, display: 'inline-block', margin: '0 18px 10px 0' }}>
<div style={{ display: 'flex', padding: '10px 0', border: `1px solid ${fileId.current?.includes(v.id) ? 'rgb(42 207 98)' : '#fff'}` }} onClick={() => {
if (fileId.current?.includes(v.id)) {
fileId.current = fileId.current?.filter(c => c != v.id)
setFileIds(fileId.current?.filter(c => c != v.id))
} else {
fileId.current = [...fileId.current, v.id]
setFileIds([...fileId.current, v.id])
}
}}>
<FilePdfOutlined style={{ fontSize: 96, color: 'rgb(33 211 180)', marginRight: 8 }} />
<div style={{ width: 200, display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly' }}>
<div style={{
width: 100, whiteSpace: 'nowrap', overflow: 'hidden', fontWeight: 400,
textOverflow: 'ellipsis', fontSize: 18, color: 'rgb(51 161 34)'
}}>
{v.docName}
</div>
<div>标签{v.tags || '--'}</div>
<div>标准类型{v.standardType}</div>
<div>创建时间{v.createAt && moment(v.createAt).format('YYYY-MM-DD HH:mm:ss') || '--'}</div>
</div>
</div>
</div>
})
}
</div >
{
approveModal ?
<ApproveModal
editData={editData}
groupModal ?
<GroupModal
parent={parent}
close={
() => {
setGroupModal(false);
}
}
success={
() => {
resourceData(parent)
}
}
/> : ""
}
{
fileModal ?
<FileModal
parent={parent}
close={() => {
setApproveModal(false);
setEditData({})
setFileModal(false);
}}
success={() => {
resourceData({ limit: 10, page: 0 })
setQuery({ limit: 10, page: 0 });
resourceData(parent)
}}
remove={url => {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: url });
}}
/> : ""
}

42
web/client/src/sections/dataQuality/containers/qualityMonitor.js

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import ApproveModal from '../components/approveModal';
import ApproveModal from '../components/groupModal';
import { Tabs, Form, Input, DatePicker, Button, Table, Select } from 'antd';
@ -36,46 +36,6 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
return <>
{/* <Form
style={{ display: 'flex' }}
onFinish={v => {
setFormData({ ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
resourceData({ limit: 10, page: 0, ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
setQuery({ limit: 10, page: 0 });
}}
autoComplete="off"
>
<Form.Item label="资源名称" name="resourceName" >
<Input allowClear placeholder='资源名称关键字' style={{ width: 200, marginRight: 16 }} />
</Form.Item>
<Form.Item label="审批状态" name="state" >
<Select allowClear placeholder="全部" style={{ width: 130, marginRight: 16 }}
options={[
{
value: 1,
label: '审批通过',
},
{
value: 2,
label: '审批不通过',
},
{
value: 3,
label: '审批中',
}]}
/>
</Form.Item>
<Form.Item label="申请日期" name="applyAt" >
<DatePicker style={{ width: 140, marginRight: 16 }} />
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit"> 查询 </Button>
</Form.Item>
</Form> */}
</>
}
function mapStateToProps (state) {

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

@ -1,79 +1,167 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import ApproveModal from '../components/approveModal';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
import { Tabs, Form, Input, Space, Button, Table, Popconfirm } from 'antd';
const { Search } = Input;
import { CreditCardFilled, FilePdfOutlined } from '@ant-design/icons';
import { agent } from 'superagent';
import { Tabs, Form, Input, DatePicker, Button, Table, Select } from 'antd';
import { v1 } from 'uuid';
import RuleModal from '../components/ruleModal';
function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
const { resourceConsumption } = actions
function RuleLibrary ({ loading, clientHeight, actions, dispatch, }) {
const { dataQuality } = actions
const [checkAll, setCheckAll] = useState(false)
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [formData, setFormData] = useState({})
const [folderData, setFolderData] = useState([]);
const [fileData, setFileData] = useState([])
const [ruleModal, setRuleModal] = useState(false)
const [fileModal, setFileModal] = useState(false)
const [editData, setEditData] = useState({})
const [column, setColumn] = useState([])
const [parent, setParent] = useState(null)
const [keyword, setKeyword] = useState()
const [tableList, setTableList] = useState({ rows: [], count: 0 });
const [treeList, setTreeLista] = useState([])
useEffect(() => {
resourceData()
dispatch(dataQuality.getRegularBasis()).then(res => {
if (res.success) {
setTreeLista(res.payload.data)
}
})
}, [])
let resourceData = (params) => {
// let data = params || query
// dispatch(resourceConsumption.getApproveList({ applyById: user?.id, ...formData, ...data, })).then(res => {
// if (res.success) {
// setProTableList(res.payload.data)
// }
// })
}
let resourceData = (data) => {
let params = data || query
dispatch(dataQuality.getBusinessRules({ keyword: keyword, ...params, })).then(res => {
if (res.success) {
setTableList({ rows: res.payload.data?.rows, count: res.payload.data?.count })
}
})
}
const columns = [{
title: '名称',
dataIndex: 'name',
}, {
title: '描述',
dataIndex: 'description',
}, {
title: '问题类型',
dataIndex: 'problemType',
}, {
title: '问题级别',
dataIndex: 'problemLevel'
}, {
title: '制定依据',
dataIndex: 'ruleBasis',
render: (text, record) => record?.standardDoc?.docName
}, {
title: '创建时间',
dataIndex: 'createAt',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss');
},
sorter: {
compare: (a, b) => moment(b?.createAt).valueOf() - moment(a?.createAt).valueOf(),
// multiple: 2,
},
}, {
title: '操作',
dataIndex: 'handle',
// ellipsis: true,
render: (text, record) => <div style={{ width: 126 }}>
<Button type="link" onClick={() => {
setEditData(record)
setRuleModal(true);
}}>编辑</Button>
<Popconfirm
title="是否确认删除该业务规则?"
onConfirm={() => {
dispatch(dataQuality.delBusinessRules(record.id)).then(res => {
if (res.success) {
setQuery({ limit: 10, page: 0 });
resourceData({ limit: 10, page: 0, keyword })
}
})
}}
// okText="Yes"
// cancelText="No"
>
<Button type="link">删除</Button>
</Popconfirm>
</div>
},
];
return <>
{/* <Form
style={{ display: 'flex' }}
onFinish={v => {
setFormData({ ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
resourceData({ limit: 10, page: 0, ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
setQuery({ limit: 10, page: 0 });
}}
autoComplete="off"
>
<Form.Item label="资源名称" name="resourceName" >
<Input allowClear placeholder='资源名称关键字' style={{ width: 200, marginRight: 16 }} />
</Form.Item>
<Form.Item label="审批状态" name="state" >
<Select allowClear placeholder="全部" style={{ width: 130, marginRight: 16 }}
options={[
{
value: 1,
label: '审批通过',
},
{
value: 2,
label: '审批不通过',
},
{
value: 3,
label: '审批中',
}]}
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button onClick={() => {
setRuleModal(true);
}}>新建业务规则</Button>
<div style={{ display: 'flex', }}>
<Input
placeholder="名称关键字"
value={keyword}
onChange={e => {
setKeyword(e?.target?.value)
}}
style={{
width: 266, marginRight: 10
}}
/>
</Form.Item>
<Form.Item label="申请日期" name="applyAt" >
<DatePicker style={{ width: 140, marginRight: 16 }} />
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit"> 查询 </Button>
</Form.Item>
</Form> */}
<Button onClick={() => {
setQuery({ limit: 10, page: 0 });
resourceData({ limit: 10, page: 0, keyword })
}}>搜索</Button>
</div>
</div>
<Table
columns={columns}
dataSource={tableList?.rows || []}
scroll={{ scrollToFirstRowOnChange: true, y: clientHeight - 260 }}
pagination={{
current: query?.page + 1,
pageSize: query?.limit,
total: tableList?.count,
showSizeChanger: true,
// showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / query?.limit)}页,${total}`}</span> },
onChange: (page, pageSize) => {
setQuery({ limit: pageSize, page: page - 1 });
resourceData({ limit: pageSize, page: page - 1, keyword });
}
}}
/>
{
ruleModal ?
<RuleModal
treeList={treeList}
editData={editData}
close={() => {
setRuleModal(false);
setEditData({})
} }
success={() => {
resourceData({ limit: 10, page: 0, keyword })
}
}
/> : ""
}
</>
}
@ -87,4 +175,4 @@ function mapStateToProps (state) {
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(MyApplication)
export default connect(mapStateToProps)(RuleLibrary)

4
web/client/src/sections/dataQuality/index.js

@ -6,8 +6,8 @@ import actions from './actions';
import { getNavItem } from './nav-item';
export default {
key: 'resourceConsumption',
name: '资源消费',
key: 'dataQuality',
name: '数据质量',
reducers: reducers,
routes: routes,
actions: actions,

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

@ -11,13 +11,14 @@ const MetadataFileModal = (props) => {
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);
console.log(values);
// 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);
}
})
}

29
web/client/src/sections/safetySpecification/actions/approve.js

@ -1,29 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getApproveList (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_APPROVE_LIST',
url: `${ApiTable.approveList}`,
msg: { error: '获取资源消费列表失败' },
reducer: { name: '' }
});
}
export function postApprove (data = {}) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_APPROVE',
url: `${ApiTable.approveList}`,
msg: { option: '资源审批' },
reducer: { name: '' }
});
}

4
web/client/src/sections/safetySpecification/actions/index.js

@ -1,9 +1,9 @@
'use strict';
import * as example from './example'
import * as approve from './approve'
import * as specificationLibrary from './specificationLibrary'
export default {
...example,
...approve,
...specificationLibrary,
}

40
web/client/src/sections/safetySpecification/actions/specificationLibrary.js

@ -0,0 +1,40 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function postSpecifications (data = {}) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_SPECIFICATIONS',
url: `${ApiTable.specifications}`,
msg: { option: '新增数据安全规范' },
reducer: { name: '' }
});
}
export function getSpecifications (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_SPECIFICATIONS',
url: `${ApiTable.specifications}`,
msg: { error: '查询数据安全规范列表失败' },
reducer: { name: '' }
});
}
export function delSpecifications (id) {
return dispatch => basicAction({
type: 'del',
dispatch: dispatch,
actionType: 'del_SPECIFICATIONS',
url: `${ApiTable.delSpecifications.replace('{fileIds}', id)}`,
msg: { option: '删除数据安全规范文件' },
reducer: { name: '' }
});
}

94
web/client/src/sections/safetySpecification/components/approveModal.js

@ -1,94 +0,0 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid'
import { Tabs, Form, Input, DatePicker, Button, Modal, Radio } from 'antd';
function ApproveModal ({ loading, clientHeight, user, actions, dispatch, close, success, editData, }) {
const { resourceConsumption } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
useEffect(() => {
}, [])
return <>
<Modal title="数据消费审批" open={true}
onOk={e => {
form.validateFields().then(v => {
console.log(v);
dispatch(resourceConsumption.postApprove({
id: editData?.id, ...v,
approveAt: moment().format('YYYY-MM-DD HH:mm:ss'),
approveBy: user?.id,
approveState: '已审批'
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
console.log(v);
if (v.approve) {
setApprove(v.approve)
}
// setFormData(v)
}}
autoComplete="off"
>
<Form.Item label="审批意见" name="approve" rules={[{ required: true, message: '请选择审批意见' }]} >
<Radio.Group>
<Radio value="true"> 同意 </Radio>
<Radio value="false"> 不同意 </Radio>
</Radio.Group>
</Form.Item>
{!approve || approve == 'false' ?
<Form.Item label="意见内容" name="approveRemarks" rules={[{ required: true, message: '请输入意见内容' }]}>
<Input allowClear placeholder='请填写意见内容' style={{ width: 300, marginRight: 16 }} />
</Form.Item> : ""}
{!approve || approve == 'true' ? <div style={{ position: 'relative' }}>
<Form.Item label="访问令牌" name="token" rules={[{ required: true, message: '请生成令牌' }]}>
<Input allowClear placeholder='生成令牌' disabled={true} style={{ width: 300, marginRight: 16 }} />
</Form.Item>
<Button type="primary" style={{ position: 'absolute', top: 0, right: 0 }} onClick={() => {
form.setFieldsValue({ token: uuidv4() })
}}>生成</Button>
</div> : ""}
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(ApproveModal)

114
web/client/src/sections/safetySpecification/components/fileModal.js

@ -0,0 +1,114 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import { UploadLocal } from '$components';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
import { Tabs, Form, Input, DatePicker, Button, Modal, Select, Tag } from 'antd';
function FileModal ({ loading, parent, user, actions, editData = {}, dispatch, close, success, remove }) {
const { resourceRetrieval } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [approve, setApprove] = useState()
const [form] = Form.useForm();
const [editUrl, setEditUrl] = useState([]);
useEffect(() => {
}, [])
const vsjunct = (params) => {
if (params.length) {
let appendix = []
for (let p of params) {
appendix.push({
fName: p.name,
size: p.size,
fileSize: p.size,
storageUrl: p.storageUrl,//必须有storageUrl
})
}
setEditUrl(appendix)
} else {
setEditUrl([])
}
}
return <>
<Modal title="数据安全规范上传" open={true} width={600}
onOk={e => {
form.validateFields().then(v => {
dispatch(resourceRetrieval.postSpecifications({
...v,
fileName: v?.files[0]?.name,path:v?.files[0]?.url,
})).then(res => {
if (res.success) {
close()
success()
}
})
})
}}
onCancel={() => {
if (form.getFieldValue('files') && form.getFieldValue('files').length) {
remove(form.getFieldValue('files')[0]?.url)
}
close()
}}
>
<Form
style={{ marginLeft: 20 }}
form={form}
onValuesChange={v => {
}}
autoComplete="off"
labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}
>
<Form.Item label="标签" name="tags" >
<Input allowClear placeholder='请输入标签' style={{ width: 300, marginRight: 16 }} />
</Form.Item>
<Form.Item
label='文件'
name='files'
key='files'
rules={[{ required: true, message: '文件不可为空' }]}>
<UploadLocal
// addNew={editData.add || !editData.record.files.length}
isLocal={true}
maxFilesNum={1}
maxFileSize={40}
onChange={vsjunct}
fileTypes={["jpg", "png", "gif", "txt", "doc", "docx", "pdf", "xls", "xlsx", "zip", "rar"]}
value={editUrl}
// fileList={editData.record.files || []}
/>
</Form.Item>
<Form.Item style={{ marginLeft: 42 }} key='tip'>
<Tag color="orange">文件大小不超过40MB开放资源包含多个文件建议将文件进行压缩形成压缩包再上传</Tag>
<Tag color="orange">支持的文件格式jpg,png,gif,txt,doc,docx,pdf,xsl,xlsx,zip,rar</Tag>
</Form.Item>
</Form>
</Modal >
</>
}
function mapStateToProps (state) {
const { global, auth, resourceCatalog } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
// resourceCatalog: resourceCatalog?.data || [],
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(FileModal)

208
web/client/src/sections/safetySpecification/containers/specificationLibrary.js

@ -1,84 +1,176 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import { connect } from 'react-redux';
import moment from 'moment';
import ApproveModal from '../components/approveModal';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
import SimpleBar from 'simplebar-react';
import FileModal from '../components/fileModal';
import { Tabs, Form, Input, Space, Button, Table, Checkbox, message, Pagination } from 'antd';
const { Search } = Input;
import { CreditCardFilled, FilePdfOutlined } from '@ant-design/icons';
import { agent } from 'superagent';
const CheckboxGroup = Checkbox.Group;
import { Tabs, Form, Input, DatePicker, Button, Table } from 'antd';
import { v1 } from 'uuid';
function SpecificationLibrary ({ loading, clientHeight, actions, dispatch, }) {
const { resourceRetrieval } = actions
const [checkAll, setCheckAll] = useState(false)
const [query, setQuery] = useState({ page: 0, limit: 20 });
const [folderData, setFolderData] = useState([]);
const [fileData, setFileData] = useState({ data: [], count: 0 })
const [groupModal, setGroupModal] = useState(false)
const [fileModal, setFileModal] = useState(false)
function Approve ({ loading, clientHeight, actions, dispatch, }) {
const [keyword, setKeywords] = useState()
const [fileIds, setFileIds] = useState([])
const fileId = useRef([])
const { resourceConsumption } = actions
const [tabsKey, setTabsKey] = useState("stay")
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [formData, setFormData] = useState({})
const [approveModal, setApproveModal] = useState(false)
const [editData, setEditData] = useState({})
const [column, setColumn] = useState([])
useEffect(() => {
resourceData()
}, [])
let resourceData = (params) => {
// let data = params || query
// dispatch(resourceConsumption.getApproveList({ approveState: tabsKey == 'stay' ? "审批中" : '已审批', ...formData, ...data, })).then(res => {
// if (res.success) {
// setProTableList(res.payload.data)
// }
// })
let resourceData = (data) => {
let params = data || query
dispatch(resourceRetrieval.getSpecifications({ keyword: keyword, ...params, })).then(res => {
if (res.success) {
setFileData({ data: res.payload.data?.rows, count: res.payload.data?.count })
}
})
}
return <>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 20 }}>
<Space size={'small'}>
<Button onClick={() => {
if (!checkAll) {
let fileAll = fileData?.data?.map(s => s.id)
fileId.current = fileAll
setFileIds(fileAll)
} else {
fileId.current = []
setFileIds([])
}
setCheckAll(!checkAll)
}}>{checkAll ? "取消全选" : "全选"}</Button>
<Button type="primary" onClick={() => {
setFileModal(true)
}}>上传</Button>
<Button type="primary">下载</Button>
<Button type="primary" onClick={() => {
if (fileId.current?.length) {
dispatch(resourceRetrieval.delSpecifications(fileId.current)).then(res => {
if (res.success) {
let url = []
fileData?.data?.map(f => {
if (fileId.current?.includes(f.id)) {
url.push(f.path)
}
})
url.map(d => {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: d });
})
resourceData({ page: 0, limit: 10 })
fileId.current = []
setFileIds([])
setFileIds(false)
setCheckAll(false)
}
})
} else {
message.warning({
duration: 1,
content: '未选择文件',
});
}
}}>删除</Button>
</Space>
useEffect(() => {
}, [])
<Search
placeholder="文件名称关键字"
value={keyword}
onChange={e => {
setKeywords(e?.target?.value)
}}
onSearch={(value, event) => {
setKeywords(value)
resourceData({ page: 0, limit: 10, keyword: value })
}}
return <>
{/* <Form
style={{ display: 'flex' }}
onFinish={v => {
setFormData({ ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
resourceData({ limit: 10, page: 0, ...v, applyAt: v.applyAt ? moment(v.applyAt).format('YYYY-MM-DD HH:mm:ss') : "" })
setQuery({ limit: 10, page: 0 });
console.log(v);
style={{
width: 266,
}}
/>
</div >
<SimpleBar
style={{
// 容器高度
maxHeight: clientHeight - 130,
}}
autoComplete="off"
// 允许的滚动方向
forceVisible="y"
>
<Form.Item label="资源名称" name="resourceName" >
<Input allowClear placeholder='资源名称关键字' style={{ width: 200, marginRight: 16 }} />
</Form.Item>
<Form.Item label="申请人" name="applyBy" >
<Input allowClear placeholder='申请人关键字' style={{ width: 140, marginRight: 16 }} />
</Form.Item>
<Form.Item label="申请日期" name="applyAt" >
<DatePicker style={{ width: 140, marginRight: 16 }} />
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit"> 查询 </Button>
</Form.Item>
</Form> */}
{
fileData?.data?.map((v, i) => {
return <div style={{ width: 310, display: 'inline-block', margin:'0 18px 10px 0', }}>
<div style={{ display: 'flex', padding: '10px 0', border: `1px solid ${fileId.current?.includes(v.id) ? 'rgb(42 207 98)' : '#fff'}` }} onClick={() => {
if (fileId.current?.includes(v.id)) {
fileId.current = fileId.current?.filter(c => c != v.id)
setFileIds(fileId.current?.filter(c => c != v.id))
} else {
fileId.current = [...fileId.current, v.id]
setFileIds([...fileId.current, v.id])
}
}}>
<FilePdfOutlined style={{ fontSize: 96, color: 'rgb(33 211 180)', marginRight: 8 }} />
<div style={{ width: 200, display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly' }}>
<div style={{
width: 100, whiteSpace: 'nowrap', overflow: 'hidden', fontWeight: 400,
textOverflow: 'ellipsis', fontSize: 18, color: 'rgb(51 161 34)'
}}>
{v.fileName}
</div>
<div>标签{v.tags || '--'}</div>
<div>创建时间{v.createAt && moment(v.createAt).format('YYYY-MM-DD HH:mm:ss') || '--'}</div>
</div>
</div>
</div>
})
}
{fileData?.count > 20 && <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Pagination
total={fileData?.count || 0}
showSizeChanger
showQuickJumper
current={query?.page + 1 || 1}
pageSizeOptions={[20, 30, 50]}
showTotal={(total) => `${total} 个文件`}
onChange={(page, pageSize) => {
console.log({ page: page - 1, limit: pageSize, });
setQuery({ page: page - 1, limit: pageSize, })
resourceData({ page: page - 1, limit: pageSize, keyword: keyword })
}}
/>
</div>}
</SimpleBar>
{
approveModal ?
<ApproveModal
editData={editData}
fileModal ?
<FileModal
close={() => {
setApproveModal(false);
setEditData({})
setFileModal(false);
}}
success={() => {
resourceData({ limit: 10, page: 0 })
setQuery({ limit: 10, page: 0 });
resourceData({ page: 0, limit: 10, keyword: keyword })
}}
remove={url => {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: url });
}}
/> : ""
}
@ -95,4 +187,4 @@ function mapStateToProps (state) {
// isRequesting: resourceCatalog.isRequesting
};
}
export default connect(mapStateToProps)(Approve)
export default connect(mapStateToProps)(SpecificationLibrary)

12
web/client/src/sections/safetySpecification/index.js

@ -6,10 +6,10 @@ import actions from './actions';
import { getNavItem } from './nav-item';
export default {
key: 'resourceConsumption',
name: '资源消费',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
key: 'resourceRetrieval',
name: '数据安全规范',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

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

@ -2,139 +2,151 @@
import request from 'superagent';
export const ApiTable = {
login: 'login',
logout: 'logout',
validatePhone: 'validate/phone',
//标签管理
getTags: 'tags',
postTagSets: 'tag-sets',
putTagSets: 'tag-sets/{id}',
delTagSets: 'tag-sets/{id}',
postTags: 'tags',
putTags: 'tags/{id}',
delTags: 'tags/{id}',
//元数据管理-模型管理
getMetaModelList: 'meta/models',
addMetaModel: 'meta/model',
modifyMetaModel: 'meta/model/{id}',
//最新元数据-资源目录
getResourceCatalog: 'resource-catalog',
postResourceCatalog: 'resource-catalog',
putResourceCatalog: 'resource-catalog/{id}',
delResourceCatalog: 'resource-catalog/{id}',
//最新元数据-元数据列表查询
getMetadataDatabases: 'metadata/databases',
getMetadataFiles: 'metadata/files',
getMetadataRestapis: 'metadata/restapis',
getMetadataModels: 'metadata/models',
//库表元数据增删改、等配置
postMetadataDatabases: 'metadata/databases',
putMetadataDatabases: 'metadata/databases/{id}',
delMetadataDatabases: 'metadata/databases/{id}',
getMetadataDatabasesById: 'metadata/databases/{id}',
postTagMetadata: 'tag/metadata',
getTagMetadata: 'tag/metadata/{id}',
getMetadataResourceApplications: 'resource-consumption/applications',
postMetadataResourceApplications: 'resource-consumption/applications',
//文件元数据增删改
postMetadataFiles: 'metadata/files',
putMetadataFiles: 'metadata/files/{id}',
delMetadataFiles: 'metadata/files/{id}',
//接口元数据增删改
postMetadataRestapis: 'metadata/restapis',
putMetadataRestapis: 'metadata/restapis/{id}',
delMetadataRestapis: 'metadata/restapis/{id}',
login: 'login',
logout: 'logout',
validatePhone: 'validate/phone',
//标签管理
getTags: 'tags',
postTagSets: 'tag-sets',
putTagSets: 'tag-sets/{id}',
delTagSets: 'tag-sets/{id}',
postTags: 'tags',
putTags: 'tags/{id}',
delTags: 'tags/{id}',
//元数据管理-模型管理
getMetaModelList: 'meta/models',
addMetaModel: 'meta/model',
modifyMetaModel: 'meta/model/{id}',
//最新元数据-资源目录
getResourceCatalog: 'resource-catalog',
postResourceCatalog: 'resource-catalog',
putResourceCatalog: 'resource-catalog/{id}',
delResourceCatalog: 'resource-catalog/{id}',
//最新元数据-元数据列表查询
getMetadataDatabases: 'metadata/databases',
getMetadataFiles: 'metadata/files',
getMetadataRestapis: 'metadata/restapis',
getMetadataModels: 'metadata/models',
//库表元数据增删改、等配置
postMetadataDatabases: 'metadata/databases',
putMetadataDatabases: 'metadata/databases/{id}',
delMetadataDatabases: 'metadata/databases/{id}',
getMetadataDatabasesById: 'metadata/databases/{id}',
postTagMetadata: 'tag/metadata',
getTagMetadata: 'tag/metadata/{id}',
getMetadataResourceApplications: 'resource-consumption/applications',
postMetadataResourceApplications: 'resource-consumption/applications',
//文件元数据增删改
postMetadataFiles: 'metadata/files',
putMetadataFiles: 'metadata/files/{id}',
delMetadataFiles: 'metadata/files/{id}',
//接口元数据增删改
postMetadataRestapis: 'metadata/restapis',
putMetadataRestapis: 'metadata/restapis/{id}',
delMetadataRestapis: 'metadata/restapis/{id}',
//业务元数据管理
getBusinessMetadataDatabases: 'business/metadata/databases',
postBusinessMetadataDatabases: 'business/metadata/databases',
putBusinessMetadataDatabases: 'business/metadata/databases/{id}',
delBusinessMetadataDatabases: 'business/metadata/databases/{id}',
getBusinessMetadataFiles: 'business/metadata/files',
postBusinessMetadataFiles: 'business/metadata/files',
putBusinessMetadataFiles: 'business/metadata/files/{id}',
delBusinessMetadataFiles: 'business/metadata/files/{id}',
getBusinessMetadataRestapis: 'business/metadata/restapis',
postBusinessMetadataRestapis: 'business/metadata/restapis',
putBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
delBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
//业务元数据管理
getBusinessMetadataDatabases: 'business/metadata/databases',
postBusinessMetadataDatabases: 'business/metadata/databases',
putBusinessMetadataDatabases: 'business/metadata/databases/{id}',
delBusinessMetadataDatabases: 'business/metadata/databases/{id}',
getBusinessMetadataFiles: 'business/metadata/files',
postBusinessMetadataFiles: 'business/metadata/files',
putBusinessMetadataFiles: 'business/metadata/files/{id}',
delBusinessMetadataFiles: 'business/metadata/files/{id}',
getBusinessMetadataRestapis: 'business/metadata/restapis',
postBusinessMetadataRestapis: 'business/metadata/restapis',
putBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
delBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
//元数据采集-数据源管理
pgCheckConnect: 'adapter/check/connect',
addDataSource: 'meta/acq/dataSource',
getAdapters: 'meta/acq/adapters',
getDataSources: 'meta/acq/dataSources',
modifyDataSource: 'acq/dataSource/{id}',
//元数据采集-数据源管理
pgCheckConnect: 'adapter/check/connect',
addDataSource: 'meta/acq/dataSource',
getAdapters: 'meta/acq/adapters',
getDataSources: 'meta/acq/dataSources',
modifyDataSource: 'acq/dataSource/{id}',
//元数据采集-采集任务管理
addTask: 'meta/acq/task',
getTasks: 'meta/acq/tasks',
modifyTask: 'acq/task/{id}',
runTask: 'run/acq/task',
//元数据采集-采集任务管理
addTask: 'meta/acq/task',
getTasks: 'meta/acq/tasks',
modifyTask: 'acq/task/{id}',
runTask: 'run/acq/task',
//采集日志
getLogs: "meta/acq/logs",
//采集日志
getLogs: "meta/acq/logs",
//资源消费
approveList: 'resource/approve',
//用户管理
getUserList: 'meta/members',
addUser: 'meta/member',
modifyUser: 'meta/member/{id}',
//资源消费
approveList: 'resource/approve',
//用户管理
getUserList: 'meta/members',
addUser: 'meta/member',
modifyUser: 'meta/member/{id}',
//元数据检索
searchMetadata: "meta/data/search",
//元数据检索
searchMetadata: "meta/data/search",
//数据质量
standardDocFolders: 'standard-doc-folders',
standardDocs: 'standard-docs',
postFolderFile:'postFolderFile',
businessRules:'business-rules',
delBusinessRules:'business-rules/{id}',
regularBasis:'regular-basis',
//数据安全规范上传
specifications:'data-security/specifications',
delSpecifications:'data-security/specifications/{fileIds}',
};
export const RouteTable = {
fileUpload: '/_upload/new',
cleanUpUploadTrash: '/_upload/cleanup',
fileUpload: '/_upload/new',
cleanUpUploadTrash: '/_upload/cleanup',
};
const resultHandler = (resolve, reject) => (err, res) => {
if (err) {
if (err.status == 401) {
// 退出到登录页
const user = JSON.parse(sessionStorage.getItem('user'));
sessionStorage.clear();
window.document.location.replace('/login');
reject('unauth');
} else {
reject({
status: err.status || 0,
body: err.response ? err.response.body : err.message
});
}
reject({
if (err) {
if (err.status == 401) {
// 退出到登录页
const user = JSON.parse(sessionStorage.getItem('user'));
sessionStorage.clear();
window.document.location.replace('/login');
reject('unauth');
} else {
reject({
status: err.status || 0,
body: err.response ? err.response.body : err.message
});
} else {
resolve(res.body);
}
});
}
reject({
status: err.status || 0,
body: err.response ? err.response.body : err.message
});
} else {
resolve(res.body);
}
};
export const buildRoute = (url) => {
const user = JSON.parse(sessionStorage.getItem('user'));
if (user == null) {
return url;
}
let connector = url.indexOf('?') === -1 ? '?' : '&';
return `${url}${connector}token=${user.token}`;
const user = JSON.parse(sessionStorage.getItem('user'));
if (user == null) {
return url;
}
let connector = url.indexOf('?') === -1 ? '?' : '&';
return `${url}${connector}token=${user.token}`;
};
export class RouteRequest {
static get = (url, query) =>
new Promise((resolve, reject) => {
request.get(buildRoute(url)).query(query).end(resultHandler(resolve, reject));
});
static get = (url, query) =>
new Promise((resolve, reject) => {
request.get(buildRoute(url)).query(query).end(resultHandler(resolve, reject));
});
static post = (url, data, query) =>
new Promise((resolve, reject) => {
request.post(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
static post = (url, data, query) =>
new Promise((resolve, reject) => {
request.post(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
static delete = (url, data, query) =>
new Promise((resolve, reject) => {
request.delete(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
static delete = (url, data, query) =>
new Promise((resolve, reject) => {
request.delete(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
}

1
web/package.json

@ -88,6 +88,7 @@
"moment": "^2.22.0",
"npm": "^7.20.6",
"react-router-breadcrumbs-hoc": "^4.0.1",
"simplebar-react": "^3.2.4",
"superagent": "^6.1.0",
"uuid": "^8.3.1",
"webpack-dev-server": "^3.11.2",

Loading…
Cancel
Save