Browse Source

文件元数据页面查询搜索、导出等功能

master
zmh 2 years ago
parent
commit
79d0ccf1ce
  1. BIN
      web/client/assets/files/common/企业信息化项目组0301-0331月报.docx
  2. 325
      web/client/src/components/UploadLocal/index.js
  3. 4
      web/client/src/components/index.js
  4. 39
      web/client/src/sections/metadataManagement/actions/metadata.js
  5. 160
      web/client/src/sections/metadataManagement/components/metadataFileModal.js
  6. 2
      web/client/src/sections/metadataManagement/components/metadataTagModal.js
  7. 6
      web/client/src/sections/metadataManagement/containers/databasesTable.js
  8. 435
      web/client/src/sections/metadataManagement/containers/filesTable.js
  9. 2
      web/client/src/sections/metadataManagement/containers/metadataDetails.js
  10. 52
      web/client/src/utils/webapi.js
  11. 4
      web/routes/attachment/index.js

BIN
web/client/assets/files/common/企业信息化项目组0301-0331月报.docx

Binary file not shown.

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

@ -0,0 +1,325 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button } from 'antd';
import moment from 'moment';
import { PlusOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
class Uploads extends Component {
constructor(props) {
super(props);
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
delPicIng: false,
removeFilesList: []
};
}
dealName = (uploaded) => {
let realName = uploaded.split('/')[2]
let x1 = realName.split('.')
let x2 = x1[0].split('_')
let showName = `${x2[0]}.${x1[1]}`
return showName
}
// setFileList = (value) => {
// let defaultFileList = [];
// defaultFileList = value.map((u, index) => {
// let fileUrl = `${this.ApiRoot}/${u.url}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.url),
// status: 'done',
// storageUrl: u.url,
// url: fileUrl
// };
// });
// onChange(defaultFileList)
// this.setState({
// fileList: defaultFileList
// });
// };
componentDidMount() {
const { value } = this.props;
if (value) {
// this.setState(value);
this.setState({ fileList: value })
}
}
componentWillReceiveProps(np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData } = np;
const setFileList = () => {
let defaultFileList = [];
defaultFileList = nextEditData.map((u, index) => {
let fileUrl = u.filename;
return {
uid: -index - 1,
name: u.name,
status: 'done',
storageUrl: u.filename,
url: fileUrl,
size: u.size || -1
};
});
this.setState({
fileList: defaultFileList
});
};
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
setFileList();
} else if (nextEditData.length != thisEditData.length) {
setFileList();
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
setFileList();
}
}
}
// else{
// this.setState({
// fileList:[],
// })
// }
}
//删除文件
deleteFile(file) {
if (file.url) {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: file.url });
};
}
render() {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'pdf', 'png', 'jpg', 'rar', 'zip'],
report: ['doc', 'docx', 'xls', 'xlsx', 'pdf'],
data: ['txt', 'xls', 'xlsx'],
image: ['png', 'jpg', 'svg', 'jpeg'],
three: ['js'],
video: ['mp4']
};
/**
* uploadType string 主要区别文件上传路径 以及类型 web/routes/attachment/index.js UploadPath key 值为准;默认 project;
* disabled boolean 上传是否可用
* maxFilesNum number 最大上传数量
* fileTypes array[string] 可允许上传的文件类型;
* maxFileSize number 单个文件最大大小 M
* listType antd upload 组件的属性
* onChange function 文件数量变化时候回调 返回文件
* value array[obj] 编辑数据 [{url:'xxx', [size:999]}]
* onStateChange function 文件状态改变回调函数 上传中 return { uploading:true/false }
*/
const {
uploadType,
disabled,
maxFilesNum,
fileTypes,
maxFileSize,
listType,
onChange,
value,
showUploadList,
onStateChange
} = this.props;
const { fileList, curPreviewPic, delPicIng, removeFilesList } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action: "/_upload/new?type=project",
listType: listType || 'text',
disabled: disabled,
beforeUpload: (file) => {
if (fileList.length >= maxFilesNum_) {
message.warning(`最多选择${maxFilesNum_}个文件上传`);
return false;
}
if (file.name.length > 60) {
message.warning(`文件名过长(大于60字符),请修改后上传`);
return false;
}
const extNames = file.name.split('.');
var reg = /^[\.\s\u4e00-\u9fa5a-zA-Z0-9_-]{0,}$/;
// if (!reg.exec(file.name)) {
// message.warning(`文件名包含除字母、汉字、数字、中划线、下划线之外的字符,请修改后上传`);
// return false;
// }
let isDAE = false;
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
isDAE = defaultFileTypes.some((f) => f == fileType);
}
if (!isDAE) {
message.error(`只能上传 ${defaultFileTypes.join()} 格式的文件!`);
return false;
}
const isLt = file.size / 1024 / 1024 < (maxFileSize || 3);
if (!isLt) {
message.error(`文件必须小于${maxFileSize || 3}MB!`);
return false;
}
this.setState({
fileUploading: true
});
if (onStateChange) {
onStateChange({ uploading: true });
}
},
onChange(info) {
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { filename } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: info.file.name,
status: 'done',
storageUrl: filename,
url: filename,
size: size
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
} else if (status === 'error') {
that.setState({
fileUploading: false
});
message.error(`${info.file.name} 上传失败,请重试`);
if (onStateChange) {
onStateChange({ uploading: false });
}
}
},
onRemove(file) {
let nextFileList = [];
fileList.map((f, i) => {
if (f.uid != file.uid) {
nextFileList.push(f);
}
});
that.deleteFile(file);
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
onChange(nextFileList);
that.setState({
fileList: nextFileList,
removeFilesList: nextRemoveFiles
});
},
onPreview(file) {
let filePostfix = file.url.split('.').pop();
filePostfix = filePostfix.toLowerCase();
if (UploadPath.image.some((img) => img == filePostfix)) {
that.setState({
curPreviewPic: file.url
});
} else {
message.warn('仅支持图片预览');
}
}
};
let fileList_ = fileList
// .map(f => {
// if (f.storageUrl) {
// let realName = f.storageUrl.split('/').pop()
// if (f.name != realName) {
// f.name = realName
// }
// }
// return f
// })
return (
<div>
<Spin spinning={delPicIng}>
<Upload {...uploadProps} fileList={fileList_}>
{
disabled ? (
''
) :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{}}>
<PlusOutlined />
<div>上传图片</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<UploadOutlined />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card
bodyStyle={{
padding: 8
}}
>
<div style={{ marginBottom: 8 }} >
<span>文件预览</span>
<span
style={{ float: 'right' }}
onClick={() => { this.setState({ curPreviewPic: '' }); }}
>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic}></img>
</Card>
) : ''
}
</Spin>
</div>
);
}
}
function mapStateToProps(state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(Uploads);

4
web/client/src/components/index.js

@ -6,11 +6,13 @@ import Uploads from './Uploads';
import NoResource from './no-resource';
import ExportAndImport from './export';
import ButtonGroup from './buttonGroup';
import UploadLocal from './UploadLocal';
export {
Upload,
Uploads,
NoResource,
ExportAndImport,
ButtonGroup
ButtonGroup,
UploadLocal
};

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

@ -193,4 +193,41 @@ export function getMetadataResourceApplications(params) {
msg: { error: '获取元数据资源申请记录失败' },
reducer: { name: 'metadataResourceApplications' }
});
}
}
export function postMetadataFiles(data) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_METADATA_FILES',
url: ApiTable.postMetadataFiles,
msg: { option: '新建元数据' },
reducer: {}
});
}
export function putMetadataFiles(id, data) {
return dispatch => basicAction({
type: 'put',
data: data,
dispatch,
actionType: 'PUT_METADATA_FILES',
url: ApiTable.putMetadataFiles.replace('{id}', id),
msg: {
option: '修改元数据',
}
});
}
export function delMetadataFiles(id) {
return dispatch => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_METADATA_FILES',
url: ApiTable.delMetadataFiles.replace('{id}', id),
msg: {
option: '删除元数据',
}
});
}

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

@ -0,0 +1,160 @@
import React, { useEffect, useState } from 'react';
import { Modal, Input, Form, Select, InputNumber, Tooltip, Tag } from 'antd';
import { UploadLocal } from '$components';
const { TextArea } = Input;
const MetadataFileModal = (props) => {
const { onConfirm, onCancel, editData, metadataModels } = props;
const [form] = Form.useForm();
const [editUrl, setEditUrl] = useState([]);
useEffect(() => {
}, []);
const handleOk = () => {
form.validateFields().then(values => {
if (onConfirm) {
let dataSave = JSON.parse(JSON.stringify(values));
dataSave.attributesParam = {};
metadataModels.map(m => {
dataSave.attributesParam[m.attributeCode] = values[m.attributeCode];
delete dataSave[m.attributeCode];
})
onConfirm(dataSave);
}
})
}
const 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}
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 = '';
if (m.length) {
while (m.length > 0) {
maxValue += '9'
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}
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}
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;
}
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([])
}
}
const handleCancel = () => {
if (editData.add) {
if (form.getFieldValue('files') && form.getFieldValue('files').length) {
onCancel(form.getFieldValue('files')[0]);
} else {
onCancel(null);
}
}
}
return (
<Modal title={editData.title} open={true} destroyOnClose
okText='确定' width={800}
onOk={() => handleOk(null)}
onCancel={() => handleCancel()}>
<Form form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} initialValues={editData.record || {}}>
<Form.Item
label='文件名称'
name='name'
rules={[{ required: true, message: '' }, { max: 50, message: `文件名称不超过50个字符` },
({ getFieldValue, validateFields }) => ({
validator(_, value) { return validatorNull(_, value, getFieldValue, validateFields, '文件名称') }
})]}>
<Input style={{ width: '90%' }} placeholder={`请输入文件名称`} />
</Form.Item>
<Form.Item
label='文件描述'
name='description'
rules={[{ max: 255, message: `文件描述不超过255个字符` }]}>
<TextArea rows={4} style={{ width: '90%' }} placeholder={`请输入文件描述`} />
</Form.Item>
<Form.Item
label='文件'
name='files'
rules={[{ required: true, message: '文件不可为空' }]}>
<UploadLocal
isLocal={true}
maxFilesNum={1}
maxFileSize={40}
onChange={vsjunct}
fileTypes={["jpg", "png", "gif", "txt", "doc", "docx", "pdf", "xls", "xlsx", "zip", "rar"]}
value={editUrl}
defaultValue={editUrl}
fileList={editData.record.files || []}
/>
</Form.Item>
<Form.Item style={{ paddingLeft: 190 }}>
<Tag color="orange">文件大小不超过40MB开放资源包含多个文件建议将文件进行压缩形成压缩包再上传</Tag>
<Tag color="orange">支持的文件格式jpg,png,gif,txt,doc,docx,pdf,xsl,xlsx,zip,rar</Tag>
</Form.Item>
{renderModelItems()}
</Form>
</Modal >
)
}
export default MetadataFileModal;

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

@ -9,7 +9,7 @@ const MetadataDatabaseTagModal = (props) => {
const handleOk = () => {
form.validateFields().then(values => {
if (onConfirm) {
onConfirm({ database: editData.record.id, ...values });
onConfirm({ ...values });
}
})
}

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

@ -56,7 +56,7 @@ const DatabaseTable = (props) => {
setView({ path: '/' + resourceCatalogPath.join('/'), ...record });
}
const onEdit = (record) => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes })).then(res => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ title: '修改库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), ...record, ...record.attributesParam } });
setModalVisible(true);
@ -84,7 +84,7 @@ const DatabaseTable = (props) => {
})
}
const onConfirmTag = (values) => {
dispatch(metadataManagement.postTagMetadata(values)).then(res => {
dispatch(metadataManagement.postTagMetadata({ database: editTagData.record.id, ...values })).then(res => {
if (res.success) {
onSearch(); setTagModalVisible(false);
}
@ -251,7 +251,7 @@ const DatabaseTable = (props) => {
return <Spin spinning={isRequesting}>
<div style={{ marginBottom: 16 }}>
<Button type='primary' onClick={() => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes })).then(res => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') })).then(res => {
if (res.success) {
setEditData({ add: true, title: '新建库表元数据', record: { path: '/' + resourceCatalogPath.join('/'), type: '目录' } });
setModalVisible(true);

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

@ -1,105 +1,386 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Spin, Table, Popconfirm } from 'antd';
import { Spin, Table, Popconfirm, Button, Input } from 'antd';
import { ButtonGroup } from '$components';
import moment from 'moment';
import FileSaver from 'file-saver';
import MetadataFileModal from '../components/metadataFileModal';
import MetadataTagModal from '../components/metadataTagModal';
import MetadataResourceModal from '../components/metadataResourceModal';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
const FilesTable = (props) => {
const { user, dispatch, actions, clientHeight, resourceCatalogId, isRequesting } = props;
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
isRequesting, metadataModels, tagList, metadataResourceApplications } = props;
const { metadataManagement } = actions;
const [resourceCatalogData, setResourceCatalogData] = useState([]);
const [limit, setLimit] = useState(10)
const [offset, setOffset] = useState(0)
const SortValues = { 'ascend': 'asc', 'descend': 'desc' };
const [tableData, setTableData] = useState([]);
const [tableDataCount, setTableDataCount] = useState(0);//Table数据
const [updateAtSort, setUpdateAtSort] = useState('descend');
const [keywords, setKeywords] = useState('');
const [limit, setLimit] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [editData, setEditData] = useState({});
const [tagModalVisible, setTagModalVisible] = useState(false);
const [editTagData, setEditTagData] = useState({});
const [resourceModalVisible, setResourceModalVisible] = useState(false);
const [editResourceData, setEditResourceData] = useState({});
useEffect(() => {
initData(resourceCatalogId);
}, []);
dispatch(metadataManagement.getTagList());
setUpdateAtSort('descend');
initData({ limit, offset: currentPage - 1, orderDirection: SortValues[updateAtSort] });
}, [resourceCatalogId]);
const initData = (resourceCatalogId) => {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, limit, offset })).then(res => {
const { data } = res.payload;
const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, keywords, orderBy: 'updateAt', ...query })).then(res => {
if (res.success) {
setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count);
let resourceNames = [];
res.payload.data.rows.map(r => {
resourceNames.push(r.name);
})
if (resourceNames.length)
dispatch(metadataManagement.getMetadataResourceApplications({ resourceNames: resourceNames.join(','), type: '文件' }))
}
})
}
const onEdit = (record) => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: '文件' })).then(res => {
if (res.success) {
setEditData({
title: '修改文件元数据', record: {
...record, ...record.attributesParam,
files: [{
url: "\\assets\\files\\common\\" + record.name + '.' + record.type, name: record.name
}]
}
});
setModalVisible(true);
}
})
}
const onEdit = (record) => { }
const confirmDelete = (id) => { }
const marking = (id) => { }
const applyResources = (id) => { }
const columns = [{
title: '文件名称',
dataIndex: 'name',
key: 'name',
width: '20%'
}, {
title: '文件描述',
dataIndex: 'description',
key: 'description',
width: '20%'
}, {
title: '文件类型',
dataIndex: 'type',
key: 'type',
width: '20%'
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
width: '20%'
}, {
title: '大小',
dataIndex: 'size',
key: 'size',
width: '20%'
}, {
title: '创建者',
dataIndex: 'createBy',
key: 'createBy',
width: '10%',
}, {
title: '修改时间',
dataIndex: 'updateAt',
key: 'updateAt',
width: '20%',
render: (text, record, index) => {
return text ? moment(text).format('YYYY-MM-DD HH:mm') : ''
}
}, {
title: '操作',
dataIndex: 'action',
width: '20%',
render: (text, record) => {
return <div>
<a onClick={() => onEdit(record)}>编辑</a>
&nbsp;&nbsp;
<Popconfirm
title="是否确认删除该元数据?若确认删除则关联的数据将一并删除!"
onConfirm={() => confirmDelete(record.id)}
> <a>删除</a></Popconfirm>
&nbsp;&nbsp;
<a onClick={() => marking(record.id)}>打标</a>
&nbsp;&nbsp;
<a onClick={() => applyResources(record.id)}>申请资源</a>
</div>
const confirmDelete = (record) => {
dispatch(metadataManagement.delMetadataFiles(record.id)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
deleteFile({ url: "\\assets\\files\\common\\" + record.name + '.' + record.type })
}
});
}
//删除文件
const deleteFile = (file) => {
if (file.url) {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: file.url });
};
}
const marking = (id) => {
dispatch(metadataManagement.getTagMetadata(id, 'file')).then(res => {
if (res.success) {
const obj = { tagSet: [], tags: [], id: id };
if (res.payload.data.length) {
obj.tagSet = res.payload.data.map(d => d.tagSet);
obj.tags = res.payload.data.map(d => d.id);
}
setEditTagData({ record: obj });
setTagModalVisible(true);
}
})
}
const onConfirmTag = (values) => {
dispatch(metadataManagement.postTagMetadata({ file: editTagData.record.id, ...values })).then(res => {
if (res.success) {
onSearch(); setTagModalVisible(false);
}
});
}
const applyResources = (record) => {
setEditResourceData({ record: { resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '文件' } });
setResourceModalVisible(true);
}
const onConfirmResource = (values) => {
dispatch(metadataManagement.postMetadataResourceApplications(values)).then(res => {
if (res.success) {
onSearch(); setResourceModalVisible(false);
}
});
}
const onTableChange = (pagination, filters, sorter) => {
let limit = Number.parseInt(pagination.pageSize);
let offset = Number.parseInt(pagination.current) - 1;
setCurrentPage(pagination.current);
setLimit(limit);
let query = { offset, limit, orderDirection: SortValues[updateAtSort] };
if (sorter.columnKey === 'updateAt') {
query.orderDirection = SortValues[sorter.order];
setUpdateAtSort(sorter.order);
}
}];
setSelectedRowKeys([]);
setSelectedRows([]);
initData(query);
}
const getfilesize = (size) => {
if (!size)
return "0K";
var num = 1024.00; //byte
if (size < num)
return size + "B";
if (size < Math.pow(num, 2))
return (size / num).toFixed(2) + "KB"; //kb
if (size < Math.pow(num, 3))
return (size / Math.pow(num, 2)).toFixed(2) + "M"; //M
if (size < Math.pow(num, 4))
return (size / Math.pow(num, 3)).toFixed(2) + "G"; //G
return (size / Math.pow(num, 4)).toFixed(2) + "T"; //T
}
const columns = [
{
title: '文件名称',
dataIndex: 'name',
key: 'name',
width: '16%',
ellipsis: true
}, {
title: '文件描述',
dataIndex: 'description',
key: 'description',
width: '29%',
ellipsis: true
}, {
title: '类型',
dataIndex: 'type',
key: 'type',
width: '10%'
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
width: '18%',
ellipsis: true,
render: (text, record, index) => {
let tagName = record.tagFiles.map(tagSet => tagSet.tag.name);
return tagName.join(',');
}
}, {
title: '大小',
dataIndex: 'size',
key: 'size',
width: '10%',
render: (text) => <span>{getfilesize(text)}</span>
}, {
title: '修改时间',
dataIndex: 'updateAt',
key: 'updateAt',
width: '18%',
sortOrder: updateAtSort,
sorter: (a, b) => moment(a.updateAt).valueOf() - moment(b.updateAt).valueOf(),
sortDirections: ['descend', 'ascend', 'descend'],
render: (text, record, index) => {
return text && moment(text).format('YYYY-MM-DD HH:mm:ss') || ''
}
}, {
title: '操作',
dataIndex: 'action',
width: '8%',
render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name);
return <ButtonGroup>
<a style={{ marginLeft: 10 }} onClick={() => onEdit(record)}>编辑</a>
<Popconfirm
title="是否确认删除该元数据?"
onConfirm={() => confirmDelete(record.id)}
> <a style={{ marginLeft: 10 }}>删除</a></Popconfirm>
<a style={{ marginLeft: 10 }} onClick={() => marking(record.id)}>打标</a>
{resourceApplicationsRecords.length === 0 ?
<a style={{ marginLeft: 10 }} onClick={() => applyResources(record)}>申请资源</a> :
<span style={{ marginLeft: 10, color: "#c0c0c0" }} title='已存在资源申请'>申请资源</span>}
</ButtonGroup>
}
}];
const onSearch = () => {
setSelectedRowKeys([]);
setSelectedRows([]);
setCurrentPage(1);
initData({ limit, offset: 0, orderDirection: SortValues[updateAtSort] });
}
const handleExport = (isAll = false) => {
let tableHeader = `<tr>`;
columns.filter(c => c.dataIndex != 'action').map(c => { tableHeader += `<th><div>${c.title}</div></th>`; });
tableHeader += '</tr>';
if (isAll) {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId })).then(res => {
if (res.success) {
handleExportTable(tableHeader, res.payload.data.rows, isAll);
}
})
} else {
let data = []
if (updateAtSort === 'descend') {
data = selectedRows.sort((a, b) => moment(b.updateAt).valueOf() - moment(a.updateAt).valueOf());
} else {
data = selectedRows.sort((a, b) => moment(a.updateAt).valueOf() - moment(b.updateAt).valueOf());
}
handleExportTable(tableHeader, data);
}
}
const handleExportTable = (tableHeader, contentData, isAll = false) => {
let tableContent = '';
contentData.map(cd => {
tableContent += `<tr>`;
tableContent += `<th style="font-weight:600"><div>${cd.name}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.description}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${cd.type}</div></th>`;
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>${cd.size}</div></th>`;
tableContent += `<th style="font-weight:600"><div>${moment(cd.updateAt).format('YYYY-MM-DD HH:mm:ss')}</div></th>`;
tableContent += `</tr>`;
})
let exportTable = `\uFEFF<table border="1">
${tableHeader}
${tableContent}
</table>`;
let tempStr = new Blob([exportTable], { type: 'text/plain;charset=utf-8' });
FileSaver.saveAs(tempStr, `文件元数据导出.xls`);
}
//新建、修改
const onConfirm = (values) => {
let obj = {}
if (editData.add) {
obj = { createBy: user.id, catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
if (values.files && values.files.length) {
obj.type = values.files[0].name.split('.').pop();
obj.size = values.files[0].size;
}
dispatch(metadataManagement.postMetadataFiles(obj)).then(() => {
onSearch(); setModalVisible(false);
});
} else {
obj = { catalog: resourceCatalogId, catalogKey: resourceCatalogKey, ...values }
if (values.files && values.files.length) {
obj.type = values.files[0].name.split('.').pop();
obj.size = values.files[0].size;
}
dispatch(metadataManagement.putMetadataFiles(editData.record.id, obj)).then(res => {
if (res.success) {
onSearch(); setModalVisible(false);
}
});
}
}
return <Spin spinning={isRequesting}>
<Table scroll={{ y: clientHeight - 320 }}
rowKey='filesId'
<div style={{ marginBottom: 16 }}>
<Button type='primary' onClick={() => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: '文件' })).then(res => {
if (res.success) {
setEditData({ add: true, title: '新建文件元数据', record: {} });
setModalVisible(true);
}
})
}}>新建</Button>
{
tableDataCount == 0 ? <Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button> :
selectedRowKeys && selectedRowKeys.length ?
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }} onClick={() => handleExport()}>导出</Button>
: <Popconfirm title={'是否导出全部?'} onConfirm={() => handleExport(true)} okText="确定" cancelText="取消">
<Button disabled={tableDataCount == 0} style={{ marginLeft: 16 }}> 导出</Button>
</Popconfirm>
}
<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}
dataSource={[]}>
dataSource={tableData}
onChange={onTableChange}
pagination={{
current: currentPage,
pageSize: limit,
total: tableDataCount,
showSizeChanger: true,
// showQuickJumper: true,
showTotal: (total) => { return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / limit)}页,${total}`}</span> },
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={{
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRows(selectedRows);
},
selectedRowKeys: selectedRowKeys
}}
>
</Table>
</Spin>
{
modalVisible ?
<MetadataFileModal
metadataModels={metadataModels.filter(m => m.modelType === '文件')}
editData={editData}
onCancel={(file) => {
setModalVisible(false)
if (file)
deleteFile(file);
}}
onConfirm={onConfirm} /> : ''
}
{
tagModalVisible ?
<MetadataTagModal
tagList={tagList}
editData={editTagData}
onCancel={() => setTagModalVisible(false)}
onConfirm={onConfirmTag} /> : ''
}
{
resourceModalVisible ?
<MetadataResourceModal
editData={editResourceData}
onCancel={() => setResourceModalVisible(false)}
onConfirm={onConfirmResource} /> : ''
}
</Spin >
}
function mapStateToProps(state) {
const { global, auth, metadataFiles } = state;
const { global, auth, metadataDatabases, metadataModels, tagList, tagMetadata, metadataResourceApplications } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
isRequesting: metadataFiles.isRequesting
isRequesting: metadataDatabases.isRequesting || metadataModels.isRequesting || tagList.isRequesting
|| tagMetadata.isRequesting || metadataResourceApplications.isRequesting,
metadataModels: metadataModels.data,
tagList: tagList.data || [],
tagMetadata: tagMetadata.data || [],
metadataResourceApplications: metadataResourceApplications.data || []
};
}
export default connect(mapStateToProps)(FilesTable)

2
web/client/src/sections/metadataManagement/containers/metadataDetails.js

@ -20,7 +20,7 @@ const MetadataDetails = (props) => {
const [editData, setEditData] = useState({});
const initData = () => {
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes }));
dispatch(metadataManagement.getMetadataModels({ modelTypes: ModelTypes.join(',') }));
dispatch(metadataManagement.getMetadataDatabasesById(match.params.id)).then(res => {
if (res.success) {
setDatabasesRecord(res.payload.data);

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

@ -36,6 +36,10 @@ export const ApiTable = {
getTagMetadata: 'tag/metadata/{id}',
getMetadataResourceApplications: 'resource-consumption/applications',
postMetadataResourceApplications: 'resource-consumption/applications',
//文件元数据增删改
postMetadataFiles: 'metadata/files',
putMetadataFiles: 'metadata/files/{id}',
delMetadataFiles: 'metadata/files/{id}',
//元数据采集-数据源管理
pgCheckConnect: 'adapter/check/connect',
@ -58,3 +62,51 @@ export const RouteTable = {
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({
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}`;
};
export class RouteRequest {
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 delete = (url, data, query) =>
new Promise((resolve, reject) => {
request.delete(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
}

4
web/routes/attachment/index.js

@ -15,7 +15,7 @@ const UploadPath = {
video: ['.mp4']
};
const ext = {
project: ['.txt', '.dwg', '.doc', '.docx', '.xls', '.xlsx', ".csv", '.pdf', '.pptx', '.png', '.jpg', '.svg', '.rar', '.zip', '.jpeg', '.mp4'],
project: ['.txt', '.dwg', '.doc', '.docx', '.xls', '.xlsx', ".csv", '.pdf', '.pptx', '.png', '.jpg', '.gif', '.svg', '.rar', '.zip', '.jpeg', '.mp4'],
report: [".doc", ".docx", ".xls", ".xlsx", ".pdf"],
data: [".txt", ".xls", ".xlsx"],
image: [".png", ".jpg", ".svg"],
@ -90,7 +90,7 @@ module.exports = {
}
const date = new Date().toLocaleDateString();
const time = new Date().getTime();
let fileName = time + '_' + file.filename;
let fileName = file.filename;
let saveFile = path.join(__dirname, '../../', `/client/assets/files/${fileFolder}`, fileName);
const pathUrl = `./client/assets/files/${fileFolder}`;

Loading…
Cancel
Save