Browse Source

资料库

dev
wenlele 2 years ago
parent
commit
9feb7e4252
  1. 8
      api/.vscode/launch.json
  2. 155
      api/app/lib/controllers/means/index.js
  3. 61
      api/app/lib/models/project_folder.js
  4. 70
      api/app/lib/models/project_folder_file.js
  5. 24
      api/app/lib/routes/means/index.js
  6. 39
      script/0.16/schema/1.alter_project_file.sql
  7. BIN
      web/client/assets/images/icon/icon_cb_数据@2x.png
  8. BIN
      web/client/assets/images/icon/project-icon.png
  9. 517
      web/client/src/components/Uploads/index.js
  10. 2
      web/client/src/components/index.js
  11. 1
      web/client/src/index.less
  12. 6
      web/client/src/sections/means/actions/index.js
  13. 75
      web/client/src/sections/means/actions/means.js
  14. 86
      web/client/src/sections/means/components/fileModal.jsx
  15. 468
      web/client/src/sections/means/containers/projectMeans.jsx
  16. 9
      web/client/src/sections/means/containers/style.less
  17. 2
      web/client/src/sections/means/index.js
  18. 7
      web/client/src/utils/webapi.js
  19. 5
      web/package.json
  20. 33
      web/routes/attachment/index.js

8
api/.vscode/launch.json

@ -16,9 +16,9 @@
"-p 4600",
"-f http://localhost:4600",
//
// "-g postgres://postgres:123@10.8.30.32:5432/orational_service",
"-g postgres://postgres:123@10.8.30.32:5432/orational_service",
//
"-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS",
// "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS",
"-k 10.8.30.72:29092,10.8.30.73:29092,10.8.30.74:29092",
"--iotaProxy http://10.8.30.157:17007",
"--redisHost localhost",
@ -57,8 +57,8 @@
// "--clickHouseIot iot",
//
"--clickHouseAnxincloud anxinyun1",
"--clickHousePepEmis pepca8",
"--clickHouseProjectManage peppm8",
"--clickHousePepEmis pepca9",
"--clickHouseProjectManage peppm",
"--clickHouseVcmp video_access_dev",
"--clickHouseDataAlarm default",
"--clickHouseIot iota",

155
api/app/lib/controllers/means/index.js

@ -0,0 +1,155 @@
'use strict';
async function addEditFile (ctx, next) {
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
const { higherFileId, fileName } = data
let onefind = await models.ProjectFolder.findOne({
where: {
higherFileId: higherFileId || null,
fileName
}
})
if (onefind) {
throw '文件夹名称重复'
}
if (data && data.id) {
await models.ProjectFolder.update(data, {
where: {
id: data.id
}
})
} else {
await models.ProjectFolder.create(data)
}
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '添加文件夹失败'
}
}
}
async function fileList (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { projectId, limit, page } = ctx.query;
let options = { where: {}, }
if (projectId) {
options.where.projectId = projectId
}
let res = await models.ProjectFolder.findAll(options)
ctx.status = 200;
ctx.body = res || []
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '添加文件夹失败'
}
}
}
async function delFile (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
await models.ProjectFolder.destroy({ where: { id } })
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '删除文件夹失败'
}
}
}
async function addFile (ctx, next) {
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
await models.ProjectFolderFile.create(data)
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '添加文件夹失败'
}
}
}
async function folderFileList (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { fileId, limit, page } = ctx.query;
let options = { where: {}, }
if (JSON.parse(fileId).length) {
options.where.fileId = { $in: JSON.parse(fileId) }
}
if (limit) {
options.limit = Number(limit)
}
if (page && limit) {
options.offset = Number(page) * Number(limit)
}
let res = await models.ProjectFolderFile.findAndCountAll(options)
ctx.status = 200
ctx.body = res || {}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '添加文件列表失败'
}
}
}
async function delfolderFile (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
await models.ProjectFolderFile.destroy({ where: { id } })
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`)
ctx.status = 400;
ctx.body = {
"message": '删除文件失败'
}
}
}
module.exports = {
addEditFile,
fileList,
delFile,
addFile,
folderFileList,
delfolderFile
}

61
api/app/lib/models/project_folder.js

@ -0,0 +1,61 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const ProjectFolder = sequelize.define("projectFolder", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "project_folder_id_uindex"
},
projectId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "project_id",
autoIncrement: false,
},
higherFileId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "higher_file_id",
autoIncrement: false,
},
type: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "type",
autoIncrement: false,
},
fileName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "file_name",
autoIncrement: false
}
}, {
tableName: "project_folder",
comment: "",
indexes: []
});
dc.models.ProjectFolder = ProjectFolder;
return ProjectFolder;
};

70
api/app/lib/models/project_folder_file.js

@ -0,0 +1,70 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const ProjectFolderFile = sequelize.define("projectFolderFile", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "project_folder_file_id_uindex"
},
fileId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "file_id",
autoIncrement: false,
},
size: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "size",
autoIncrement: false,
},
uploadTime: {
type:DataTypes.DATE,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "upload_time",
autoIncrement: false,
},
name: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
url: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "url",
autoIncrement: false
}
}, {
tableName: "project_folder_file",
comment: "",
indexes: []
});
dc.models.ProjectFolderFile = ProjectFolderFile;
return ProjectFolderFile;
};

24
api/app/lib/routes/means/index.js

@ -0,0 +1,24 @@
'use strict';
const means = require('../../controllers/means/index');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/file/list'] = { content: '获取文件夹列表', visible: true };
router.get('/file/list', means.fileList);
app.fs.api.logAttr['POST/file/addEdit'] = { content: '添加/编辑文件夹', visible: true };
router.post('/file/addEdit', means.addEditFile);
app.fs.api.logAttr['DEL/file/del/:id'] = { content: '删除文件夹', visible: true };
router.del('/file/del/:id', means.delFile);
app.fs.api.logAttr['POST/file'] = { content: '添加文件', visible: true };
router.post('/file', means.addFile);
app.fs.api.logAttr['GET/file'] = { content: '获取文件列表', visible: true };
router.get('/file', means.folderFileList);
app.fs.api.logAttr['DEL/file/:id'] = { content: '删除文件夹', visible: true };
router.del('/file/:id', means.delfolderFile);
};

39
script/0.16/schema/1.alter_project_file.sql

@ -0,0 +1,39 @@
create table project_folder
(
id serial not null,
file_name varchar not null,
project_id integer not null,
higher_file_id integer,
type int not null
);
comment on column project_folder.project_id is '自定义项目或者项企项目';
comment on column project_folder.higher_file_id is '上级文件id';
comment on column project_folder.type is '1.项目资料
2.FQA
3.
4.';
create unique index project_folder_id_uindex
on project_folder (id);
alter table project_folder
add constraint project_folder_pk
primary key (id);
create table project_folder_file
(
id serial not null,
name varchar,
size integer,
upload_time timestamp,
file_id integer,
url varchar
);
create unique index project_folder_file_id_uindex
on project_folder_file (id);

BIN
web/client/assets/images/icon/icon_cb_数据@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
web/client/assets/images/icon/project-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

517
web/client/src/components/Uploads/index.js

@ -0,0 +1,517 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button } from '@douyinfe/semi-ui';
import moment from 'moment';
import { IconPlus, IconCloudUploadStroked, IconCrossStroked } from '@douyinfe/semi-icons';
import OSS from 'ali-oss';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
import { v4 as uuidv4 } from 'uuid';
import request from 'superagent';
class Uploads extends Component {
constructor(props) {
super(props);
this.ApiRoot = localStorage.getItem('tyApiRoot')
this.qnDomain = localStorage.getItem('qnDomain');
this.aliAdmin = localStorage.getItem('aliAdmin');
this.aliBucket = localStorage.getItem('aliBucket');
this.aliRegion = localStorage.getItem('aliRegion');
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
curPreviewVideo: '',
delPicIng: false,
removeFilesList: [],
stsRes: {}
};
}
dealName = (uploaded) => {
let realName = uploaded && uploaded.split('/')[2]
// let x1 = realName.split('.')
// let postfix = x1.pop()
// let allName = x1.join('.')
// let x2 = allName.split('_')
// let showName = `${x2[0]}.${postfix}`
return realName
}
// 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
// });
// };
setFileList = (nextEditData, isQiniu, isAli) => {
let defaultFileList = [];
if (nextEditData.length) {
defaultFileList = nextEditData.map((u, index) => {
let fileUrl =
isQiniu ? `/_file-server/${u.storageUrl}`
: isAli ? `/_file-ali-server/${u.storageUrl}`
: `${this.ApiRoot}/${u.storageUrl}`;
return {
uid: -index - 1,
name: this.dealName(u.storageUrl),
status: 'done',
storageUrl: u.storageUrl,
url: fileUrl,
size: u.size || -1
};
});
}
this.setState({
fileList: defaultFileList
});
};
componentWillMount () {
this.setState({
delPicIng: true
})
RouteRequest.get(RouteTable.getAliSts).then(async (result) => {
this.setState({
delPicIng: false,
stsRes: result
})
}, (err) => {
this.setState({
delPicIng: false
})
})
}
componentDidMount () {
const { value, defaultValue, isQiniu, isAli } = this.props;
if (defaultValue) {
this.setFileList(defaultValue, isQiniu, isAli)
}
}
UNSAFE_componentWillReceiveProps (np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData, isQiniu, isAli } = np;
// this.setFileList(nextEditData, isQiniu)
// const setFileList = () => {
// let defaultFileList = [];
// defaultFileList = nextEditData.map((u, index) => {
// let fileUrl = isQiniu ? `/_file-server/${u.storageUrl}` : `${this.ApiRoot}/${u.storageUrl}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.storageUrl),
// status: 'done',
// storageUrl: u.storageUrl,
// url: fileUrl,
// size: u.size || -1
// };
// });
// this.setState({
// fileList: defaultFileList
// });
// };
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else if (nextEditData.length != thisEditData.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
this.setFileList(nextEditData, isQiniu, isAli);
}
}
}
// else{
// this.setState({
// fileList:[],
// })
// }
}
render () {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf', 'pptx', 'png', 'jpg', 'svg', 'jpeg', 'rar', 'zip', 'jpeg', 'mp4'],
report: ['doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf'],
data: ['txt', 'xls', 'xlsx', 'csv'],
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,
isQiniu,
isAli,
} = this.props;
const { fileList, curPreviewPic, curPreviewVideo, delPicIng, removeFilesList, stsRes } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
// debugger
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action:
isQiniu ? `/_upload/attachments/${uploadType_}`
: isAli ? `/_upload/attachments/ali/${uploadType_}`
: `${this.ApiRoot}/attachments/${uploadType_}`,
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 });
}
},
customRequest: isQiniu ? undefined : async (params) => {
try {
const client = new OSS({
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: that.aliRegion,
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
accessKeyId: stsRes.AccessKeyId,
accessKeySecret: stsRes.AccessKeySecret,
// 从STS服务获取的安全令牌(SecurityToken)。
stsToken: stsRes.SecurityToken,
// 填写Bucket名称,例如examplebucket。
bucket: that.aliBucket,
});
let uploadRes = null
let uploadPath = `/${uploadType_}/${uuidv4()}/` + params.file.name
if (
// false &&
params.file.size < 1024 * 1024 * 1
) {
params.onProgress({ percent: 40 })
uploadRes = await client.put(
uploadPath,
params.file
);
} else {
uploadRes = await client.multipartUpload(uploadPath, params.file, {
progress: (p, _checkpoint) => {
// Object的上传进度。
// console.log(p);
// 分片上传的断点信息。
// console.log(_checkpoint);
params.onProgress({ percent: p * 100 })
},
// 设置并发上传的分片数量。
parallel: 4,
// 设置分片大小。默认值为1 MB,最小值为100 KB。
partSize: 1024 * 1024 * 3, // 3m
});
}
// console.log(uploadRes);
let { name: url, res } = uploadRes;
let size = params.file.size;
let nextFileList = fileList;
let url_ = url.startsWith('/') ? url.substring(1) : url
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: params.file.name,
status: 'done',
storageUrl: url_,
url: `/_file-ali-server/${url_}`,
size: size
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
params.onSuccess({
result: {
uploaded: url,
url: res.requestUrls[0]
},
})
} catch (error) {
console.error(error);
params.onError({})
}
},
onChange (info) {
console.log(111,info);
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { uploaded, url } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: that.dealName(uploaded),
status: 'done',
storageUrl: uploaded,
url:
isQiniu ? '/_file-server/' + uploaded :
isAli ? `/_file-ali-server/${uploaded}` :
url,
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);
}
});
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
if (curPreviewVideo == file.url) {
that.setState({
curPreviewVideo: ''
});
}
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 if (
UploadPath.video.some((img) => img == filePostfix)
&& isAli
) {
that.setState({
curPreviewVideo: that.aliAdmin + '/' + file.storageUrl
});
} else {
//message.warn('仅支持图片预览');
preview(file.storageUrl)
}
}
};
const preview = (url) => {
let link = isQiniu ? encodeURI(`${this.qnDomain}/${url}`) :
isAli ? encodeURI(`${this.aliAdmin}/${url}`) : ''
if (link)
if (url.indexOf("pdf") !== -1 || url.indexOf("csv") !== -1) {
window.open(link)
} else {
window.open(`https://view.officeapps.live.com/op/view.aspx?src=${link}`)
}
}
let fileList_ = fileList
// .map(f => {
// if (f.storageUrl) {
// let realName = f.storageUrl.split('/').pop()
// if (f.name != realName) {
// f.name = realName
// }
// }
// return f
// })
//下载文件
const handleDownload = (file) => {
saveAs(file)
};
const saveAs = (file) => {
let url = null
if (file.storageUrl.endsWith('mp4')) {
const client = new OSS({
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: that.aliRegion,
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
accessKeyId: stsRes.AccessKeyId,
accessKeySecret: stsRes.AccessKeySecret,
// 从STS服务获取的安全令牌(SecurityToken)。
stsToken: stsRes.SecurityToken,
// 填写Bucket名称,例如examplebucket。
bucket: that.aliBucket,
});
// 配置响应头实现通过URL访问时自动下载文件,并设置下载后的文件名。
const response = {
'content-disposition': `attachment; filename=${encodeURIComponent(file.name)}`
}
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
url = client.signatureUrl(file.storageUrl, { response });
}
const link = document.createElement('a');
link.href = url || file.url;
link.download = file.name;
link.style.display = 'none';
link.click();
}
//自定义下载
return (
<div>
<Spin spinning={delPicIng}>
<Upload {...uploadProps} fileList={fileList_} showUploadList={{ showDownloadIcon: true, showRemoveIcon: true, }} onDownload={handleDownload}
>
{
disabled ? (
''
) :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 85, height: 30, border: '1px solid #a29696' }}>
<IconPlus />
<div>添加附件</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<IconCloudUploadStroked />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>图片预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewPic: '' }) }}>
<IconCrossStroked style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic} />
</Card>
) : ''
}
{
curPreviewVideo ? (<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>视频预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewVideo: '' }) }}>
<IconCrossStroked style={{ fontSize: 20 }} />
</span>
</div>
<video controls style={{ width: '100%' }}>
<source src={curPreviewVideo} type="video/mp4"></source>
</video>
</Card>) : ''
}
</Spin>
</div >
);
}
}
function mapStateToProps (state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(Uploads);

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

@ -4,6 +4,7 @@ import ReminderBox from './reminderBox'
import Setup from './setup'
import { SkeletonScreen } from './skeletonScreen'
import OutHidden from './outHidden'
import Uploads from './Uploads/index'
export {
SimpleFileDownButton,
@ -11,4 +12,5 @@ export {
Setup,
SkeletonScreen,
OutHidden,
Uploads
};

1
web/client/src/index.less

@ -2,6 +2,7 @@
// @import '~@douyinfe/semi-ui/dist/css/semi.min.css';
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import '~nprogress/nprogress.css';
@import '~simplebar-react/dist/simplebar.min.css';
*,

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

@ -1,2 +1,8 @@
'use strict';
import * as means from './means'
export default {
...means,
}

75
web/client/src/sections/means/actions/means.js

@ -0,0 +1,75 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
//添加、编辑文件夹
export function addEditFile (data = {}) {
return (dispatch) => basicAction({
type: "post",
dispatch: dispatch,
actionType: "POST_ADDEDITFILE",
data,
url: `${ApiTable.addEditFile}`,
msg: { option: data.id ? '文件夹编辑' : '添加文件夹' },
});
}
//获取文件夹列表
export function fileList (query = {}) {
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_FILELIST",
query,
url: `${ApiTable.fileList}`,
msg: { option: '获取文件夹列表' },
});
}
//删除文件夹
export function delFile (id) {
return (dispatch) => basicAction({
type: "del",
dispatch: dispatch,
actionType: "DEL_DELFILE",
url: `${ApiTable.delFile.replace('{id}', id)}`,
msg: { option: '删除文件夹' },
});
}
//添加文件
export function addFile (data = {}) {
return (dispatch) => basicAction({
type: "post",
dispatch: dispatch,
actionType: "POST_FILE",
data,
url: `${ApiTable.file}`,
msg: { option: '添加文件' },
});
}
//获取文件列表
export function folderFileList (query = {}) {
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_FOLDERFILELIST",
query,
url: `${ApiTable.file}`,
msg: { option: '获取文件列表' },
});
}
//删除文件
export function delfolderFile (id) {
return (dispatch) => basicAction({
type: "del",
dispatch: dispatch,
actionType: "DEL_DELFILE",
url: `${ApiTable.delfolderFile.replace('{id}', id)}`,
msg: { option: '删除文件' },
});
}

86
web/client/src/sections/means/components/fileModal.jsx

@ -0,0 +1,86 @@
import React, { useState, useRef, useEffect } from "react";
import { connect } from "react-redux";
import { Modal, Form } from "@douyinfe/semi-ui";
import { IconAlertCircle } from '@douyinfe/semi-icons';
function FileModal (props) {
const { close, success, dispatch, actions, editData, pepProjectId, higherFile } = props;
const { means } = actions;
const form = useRef();//
return (
<>
<Modal
title={editData?.id ? '编辑文件夹' : '新建文件夹'}
okText="确定"
cancelText="取消"
visible={true}
onOk={() => {
form.current.validate().then((v) => {
dispatch(means.addEditFile({
id: editData?.id,
projectId: pepProjectId,
fileName: v.fileName,
higherFileId: editData?.higherFileId || v.higherFileId || null,
type: 1
})).then(v => {
if (v.success) {
close()
success()
}
})
})
}}
width={607}
onCancel={() => {
close()
}}
>
<div style={{ margin: "0px 25px" }}>
<Form
allowEmpty
labelPosition="left"
labelAlign="right"
labelWidth="100px"
getFormApi={(formApi) => (form.current = formApi)}
>
<Form.Input
field='fileName'
label='文件夹名称'
rules={[{ required: true, message: "请输入文件夹名称" }]}
initValue={editData?.fileName}
/>
{editData?.id ? "" : <Form.Select
field="higherFileId"
label='上级文件夹'
style={{ width: '100%' }}
>
{higherFile?.map((item, index) => (
<Form.Select.Option key={index} value={item.value}>
{item.name}
</Form.Select.Option>
))}
</Form.Select>}
</Form>
</div>
</Modal>
</>
);
}
function mapStateToProps (state) {
const { auth, global, members } = state;
return {
// loading: members.isRequesting,
user: auth.user,
actions: global.actions,
// members: members.data,
};
}
export default connect(mapStateToProps)(FileModal);

468
web/client/src/sections/means/containers/projectMeans.jsx

@ -1,33 +1,485 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState, useMemo } from 'react';
import { connect } from 'react-redux';
import { Input, Button, Tree, Modal, Table, Upload, Pagination, Popconfirm } from '@douyinfe/semi-ui';
import { IconDeleteStroked, IconEditStroked, IconUpload } from '@douyinfe/semi-icons';
import SimpleBar from 'simplebar-react';
import FileModal from '../components/fileModal';
import moment from 'moment';
import './style.less'
const Rest = (props) => {
const { dispatch, actions, user, loading, socket } = props
const { dispatch, actions, user, loading, clientHeight, overallProjectId } = props
const { install, means } = actions
const [pomsList, setPomsList] = useState([]); //
const [showPomsList, setShowPomsList] = useState([]); //
const [pepProjectId, setPepProjectId] = useState() //id
const [projectSearch, setProjectSearch] = useState() //
const [isFileModal, setIsFileModal] = useState(false) //
const [editData, setEditData] = useState({}) //
const [treeData, settreeData] = useState([]); //
const [higherFile, setHigherFile] = useState([]); //
const [fileId, setFileId] = useState(); //id
const [uploadModal, setUploadModal] = useState(false) //
const [uploadData, setUploadData] = useState({})
const [dataSource, setDataSource] = useState([]) //
const [query, setQuery] = useState({ limit: 10, page: 0 })
const [count, setCount] = useState(0)
useEffect(() => {
dispatch(install.getProjectPoms({ global: 1 })).then((res => {
if (res.success) {
let data = res.payload.data?.rows?.filter(v => v.pepProjectIsDelete !== 1)?.map(v => ({ pepProjectId: v.id, pepProjectName: v.pepProjectName || v.name }))
setPomsList(data)
setShowPomsList(data)
setPepProjectId(data[0]?.pepProjectId)
fileList(data[0]?.pepProjectId)
}
}))
}, [])
useEffect(() => {
let data
if (overallProjectId) {
data = pomsList?.filter(v => (v.pepProjectName?.indexOf(projectSearch) != -1 && v.pepProjectId == overallProjectId))
} else {
data = pomsList?.filter(v => v.pepProjectName?.indexOf(projectSearch) != -1)
}
setShowPomsList(data)
setPepProjectId(data[0]?.pepProjectId)
fileList(data[0]?.pepProjectId)
}, [projectSearch])
useEffect(() => {
setProjectSearch('')
let data
if (overallProjectId) {
data = pomsList?.filter(v => v.pepProjectId == overallProjectId)
} else {
data = pomsList
}
setShowPomsList(data)
setPepProjectId(data[0]?.pepProjectId)
fileList(data[0]?.pepProjectId)
}, [overallProjectId])
useEffect(() => {
if (fileId) {
filfolderFileListe()
}
}, [fileId])
const fileList = (id) => {
dispatch(means.fileList({ projectId: id })).then((res => {
if (res.success) {
let data = res.payload.data
let oneLevel = res.payload.data?.filter(f => !f.higherFileId) || []
settreeData(listErgodic(oneLevel, data))
setHigherFile(data?.map(d => ({ name: d.fileName, value: d.id })))
}
}))
}
const filfolderFileListe = (params) => {
let fileIds = []
const treeDataList = (data, value) => {
data?.map(c => {
if (c.key == fileId || value) {
fileIds.push(c.key)
if (c.children?.length) {
treeDataList(c.children, true)
}
} else if (c.children?.length) {
treeDataList(c.children)
}
})
}
console.log(treeData);
treeDataList(treeData)
let datas = params || query
console.log(fileIds);
dispatch(means.folderFileList({ fileId: JSON.stringify(fileIds), ...datas })).then((res => {
if (res.success) {
setDataSource(res.payload.data?.rows || [])
setCount(res.payload.data?.count)
}
}))
}
const listErgodic = (level, datas) => {
let data = []
level.map(v => {
let list = {
value: v.id,
key: v.id,
}
let childrenList = datas?.filter(c => c.higherFileId == list.value)
if (childrenList?.length) {
list.children = listErgodic(childrenList, datas)
}
let fileData
dispatch(means.folderFileList({ fileId: JSON.stringify([v.id]) })).then((res => {
if (res.success) {
fileData = res.payload.data?.rows?.length
}
}))
list.label = <div className='dd' title={v.fileName} style={{ width: '100%', display: 'flex', }}>
<div style={{ width: '100%', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{v.fileName}</div>
<IconEditStroked className='tip' style={{ color: '#827777c7' }} onClick={() => {
setIsFileModal(true)
setEditData({ id: v.id, fileName: v.fileName, higherFileId: v.higherFileId })
}} />
<Popconfirm
title={(fileData || list?.children?.length) ? '请删除该文件下的文件或文件夹' : "是否确认删除文件夹?"}
onConfirm={() => {
if (!fileData || !list?.children?.length) {
dispatch(means.delFile(v.id)).then((res => {
if (res.success) {
fileList(pepProjectId)
}
}))
}
}}
// onCancel={onCancel}
>
<IconDeleteStroked className='tip' style={{ margin: '0 8px', color: '#827777c7' }} />
</Popconfirm>
</div>
data.push(list)
})
return data
}
const column = [
{
title: '文件名',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
},
{
title: '文件大小',
dataIndex: 'size',
key: 'size',
render: (text) => {
let show = '--'
if (text < 1048576) {
show = (text / 1024).toFixed(1) + ' KB'
} else {
show = (text / 1048576).toFixed(1) + ' MB'
}
return show
}
},
{
title: '上传时间',
dataIndex: 'uploadTime',
key: 'uploadTime',
render: (text) => text ? moment(text).format("YYYY-MM-DD HH:mm:ss") : '--'
},
{
title: '操作',
key: 'operation',
dataIndex: 'operation',
render: (_, r) => {
return <div style={{ minWidth: 174 }}>
<Button theme='borderless' type='primary' style={{ marginRight: 8 }} ><a href={`/_file-server/${r.url + '?filename=' + encodeURIComponent(r.name)}`}>
下载
</a></Button>
<Button theme='borderless' type='primary' style={{ marginRight: 8 }} onClick={() => {
}}>
预览
</Button>
<Popconfirm
title="是否确认删除文件?"
// content=""
onConfirm={() => {
dispatch(means.delfolderFile(r.id)).then((res => {
if (res.success) {
filfolderFileListe({ limit: 10, page: 0 })
}
}))
}}
// onCancel={onCancel}
>
<Button theme='borderless' type='danger' >删除</Button>
</Popconfirm>
</div>
}
},
]
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sydney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
return (
<>
// <div>
<div style={{ width: '100%', height: clientHeight - 72, display: 'flex', }}>
<div style={{ width: 200, height: '100%', padding: '16px 10px', boxShadow: '0 0 4px 2px #0000000d' }}>
<Input placeholder='请输入项目名称' value={projectSearch} onChange={v => setProjectSearch(v)} />
<SimpleBar style={{ height: 'calc(100% - 24px', }} forceVisible="y" >
{showPomsList?.map(v => {
return <div key={'pepProjectId' + v.pepProjectId} title={v.pepProjectName} style={{ cursor: 'pointer', background: v.pepProjectId == pepProjectId ? 'rgb(15 126 251 / 16%)' : '', width: 180, height: 30, display: 'flex', alignItems: 'center' }}
onClick={() => {
setPepProjectId(v.pepProjectId)
fileList(v.pepProjectId)
}}>
<img src="/assets/images/icon/project-icon.png" style={{ width: 14, marginRight: 8 }} />
<div style={{ fontSize: 14, width: 152, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}> {v.pepProjectName}</div>
</div>
})}
</SimpleBar>
</div>
<div style={{ height: '100%', display: 'flex', flex: 1 }}>
{/* 文件夹———树 */}
<div style={{
minWidth: 200, maxWidth: 500, width: 300, height: '100%', boxShadow: '0 2px 8px 0 #c8c9cc80',
resize: 'horizontal', overflow: 'auto',
}}>
<Button theme='borderless' type='secondary' style={{ border: ' 1px solid #005ABD', borderRadius: 2, margin: '20px 0 0 10px' }} onClick={() => {
setIsFileModal(true)
}}> 新建文件夹 </Button>
{/* 添加文件弹窗 */}
{isFileModal ?
<FileModal
editData={editData}
higherFile={higherFile}
pepProjectId={pepProjectId}
close={() => {
setIsFileModal(false)
setEditData({})
}}
success={() => {
fileList(pepProjectId)
}}
/> : ""
}
<SimpleBar style={{ height: 'calc(100% - 70px' }} forceVisible='y' >
<Tree
treeData={treeData}
defaultExpandAll
// labelEllipsis={true}
// style={style}
onSelect={(selectedKey, selected, selectedNode) => {
setFileId(selectedKey)
}}
/>
</SimpleBar>
</div>
{/* 表格 */}
<div style={{
flex: 1, height: '100%',
// border: '1px solid rgb(24 22 22)'
}}>
<div style={{ margin: '20px 0 10px 10px', display: 'flex', alignItems: 'center' }}>
<Button theme='solid' type='primary' style={{ width: 80, background: '#005ABD', marginRight: 10 }} onClick={() => {
if (higherFile?.filter(c => c.value == fileId)?.length) {
setUploadModal(true)
}
}}> 上传 </Button>
<div style={{ display: 'flex' }}>当前文件夹{higherFile?.filter(c => c.value == fileId)[0]?.name || <div style={{ color: '#c31515' }}>请选择文件夹</div>}</div>
{uploadModal ?
<Modal
title={'上传文件'}
okText="确定"
cancelText="取消"
visible={true}
onOk={() => {
dispatch(means.addFile({ ...uploadData, fileId: fileId })).then(v => {
if (v.success) {
setUploadModal(false)
filfolderFileListe({ limit: 10, page: 0 })
}
})
}}
width={607}
onCancel={() => {
setUploadModal(false)
}}
>
<div style={{ display: 'flex' }}>
<div style={{ marginTop: 6 }}>文件</div>
<Upload
style={{ display: 'inline-block' }}
action="/_upload/attachments"
accept={'.txt, .doc, .docx, .xls, .xlsx, .pdf, .png, .jpg, .rar, .zip'}
limit={1}
maxSize={51200}
onSuccess={(responseBody, file) => {
console.log(11111, responseBody, file);
setUploadData({
name: file.name,
size: file.size,
url: responseBody?.uploaded,
uploadTime: moment().format("YYYY-MM-DD HH:mm:ss")
})
}}
>
<Button icon={<IconUpload />} theme="light">
文件上传
</Button>
</Upload>
</div>
</Modal> : ""
}
</div>
<SimpleBar style={{ height: 'calc(100% - 100px' }} forceVisible='y' >
<Table
style={{ width: 'calc(100% - 10px)', marginLeft: 10 }}
columns={column}
dataSource={dataSource}
pagination={false}
bordered={false}
empty={
<div>
<img src="/assets/images/install/watting.png" alt="" style={{ width: 'calc(100% + 16px)', position: "relative", top: -12, left: -8, }} />
<img src="/assets/images/problem/shield.png" style={{ width: 20 }} />暂无告警数据
</div>
</>
}
onRow={(record, index) => {
if (index % 1 === 0) {
return { style: { background: '#FAFCFF' } }
}
}}
// rowSelection={{
// // selectedRowKeys: selected || [],
// getCheckboxProps: record => ({
// // disabled: record.confirmTime ? true : false,
// // name: record.name,
// }),
// onSelect: (record, selected) => {
// // console.log(`select row: ${selected}`, record);
// },
// // onSelectAll: (selected, selectedRows) => {
// // console.log(`select all rows: ${selected}`, selectedRows);
// // },
// onChange: (selectedRowKeys, selectedRows) => {
// // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// // setSelected(selectedRows?.map(v => v.id))
// },
// }}
/>
</SimpleBar>
{count > 0 ? <div style={{ display: 'flex', width: '100%', justifyContent: 'flex-end', alignItems: 'center', marginTop: 12 }}>
<span style={{ lineHeight: "30px", fontSize: 13 }}>
{count}个文件
</span>
<Pagination
className="22"
total={count}
showSizeChanger
currentPage={(query?.page || 0) + 1}
pageSizeOpts={[10, 20, 50]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
filfolderFileListe({ limit: pageSize, page: currentPage - 1 })
}}
/>
</div> : ""}
{/* <div
style={{
display: "flex",
justifyContent: "space-between",
padding: "20px 20px",
}}
>
<div>
<div style={{ display: 'inline-block', lineHeight: '30px', fontSize: 13 }}>勾选<span style={{ fontWeight: 400, color: '#0F7EFB', margin: '0 6px' }}>{selected.length}</span>问题</div>
<Button onClick={() => {
if (checkAll) {
setSelected((route == 'useAbnormal' || route == 'videoAbnormal' ? tableData.slice(query.page * query.limit, (query.page + 1) * query.limit) || [] : tableData)?.map(v => {
if (['videoAbnormal', 'useAbnormal'].includes(route)) {
if (!v.confirmTime) {
return v.id
}
} else {
if (!(v.State > 2)) {
return v.id
}
}
})?.filter(v => v))
} else {
setSelected([])
}
setCheckAll(!checkAll)
}}
style={{ width: 93, height: 24, borderRadius: '1px', border: '1px solid #0F7EFB', color: '#0F7EFB', background: "#FFFFFF", fontWeight: 400, margin: '0 10px' }}>
{checkAll ? '全选' : "取消全选"}
</Button>
<Button type='primary' theme='solid' onClick={() => (setIfBulk(true), setConfirm(true))} style={{ width: 93, height: 24, borderRadius: '1px', border: '1px solid #0F7EFB', color: '#FFFFFF', fontWeight: 400, }}>批量确认</Button>
</div>
{count > 0 ? <div style={{ display: 'flex', }}>
<span style={{ lineHeight: "30px", fontSize: 13 }}>
{count}个问题
</span>
<Pagination
className="22"
total={count}
showSizeChanger
currentPage={(query?.page || 0) + 1}
pageSizeOpts={[10, 20, 30, 40]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
}}
/>
</div> : ""}
</div> */}
</div>
</div>
</div >
// </div>
)
}
function mapStateToProps (state) {
const { auth, global, members, webSocket } = state;
return {
// loading: members.isRequesting,
// user: auth.user,
// actions: global.actions,
// members: members.data,
user: auth.user,
actions: global.actions,
overallProjectId: global.pepProjectId,
// socket: webSocket.socket
clientHeight: global.clientHeight
};
}

9
web/client/src/sections/means/containers/style.less

@ -0,0 +1,9 @@
.tip{
display: none;
}
.dd:hover{
.tip{
display: inline-block;
}
}

2
web/client/src/sections/means/index.js

@ -6,7 +6,7 @@ import actions from './actions';
import { getNavItem } from './nav-item';
export default {
key: 'facility',
key: 'means',
name: '设备',
reducers: reducers,
routes: routes,

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

@ -69,7 +69,12 @@ export const ApiTable = {
getAlarmsHandleStatistics: '/alarms/handle/statistics', //查询BI分析数据-问题处理效率分析
getLatestDynamic: 'latest/dynamic', // 查询最新动态
//资料库
addEditFile: 'file/addEdit', //添加、编辑文件夹
fileList: 'file/list', //获取问文件夹列表
delFile: 'file/del/{id}', //删除文件夹
file: 'file',
delfolderFile: 'file/{id}', //删除文件
};
export const RouteTable = {
apiRoot: "/api/root",

5
web/package.json

@ -7,7 +7,7 @@
"test": "mocha",
"start-vite": "cross-env NODE_ENV=developmentVite npm run start-params",
"start": "cross-env NODE_ENV=development npm run start-params",
"start-params": "node server -p 5600 -u http://localhost:4600 --apiPomsUrl http://localhost:4600 --apiAnxinyunUrl http://10.8.30.112:4100 --apiEmisUrl http://10.8.30.112:14000 --qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5 --qnsk yewcieZLzKZuDfig0wLZ9if9jKp2P_1jd3CMJPSa --qnbkt dev-highways4good --qndmn http://rhvqdivo5.hn-bkt.clouddn.com --iotVcmpWeb https://mediaconsole.ngaiot.com --pomsMonitor https://monitor.anxinyun.cn --dcWeb https://fsiot-oamss.anxinyun.cn",
"start-params": "node server -p 5600 -u http://localhost:4600 --apiPomsUrl http://localhost:4600 --apiAnxinyunUrl http://10.8.30.112:4100 --apiEmisUrl http://10.8.30.112:14000 --qnak 5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu --qnsk w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5 --qnbkt anxinyun-test --qndmn http://test.resources.anxinyun.cn --iotVcmpWeb https://mediaconsole.ngaiot.com --pomsMonitor https://monitor.anxinyun.cn --dcWeb https://fsiot-oamss.anxinyun.cn",
"deploy": "export NODE_ENV=production&& npm run build && node server",
"build-dev": "cross-env NODE_ENV=development&&webpack --config webpack.config.js",
"build": "cross-env NODE_ENV=production&&webpack --config webpack.config.prod.js"
@ -54,7 +54,9 @@
"@semi-bot/semi-theme-fscamera": "^1.0.0",
"@vitejs/plugin-react": "^1.3.1",
"@vitejs/plugin-react-refresh": "^1.3.6",
"ali-oss": "^6.17.1",
"args": "^5.0.1",
"async-busboy": "^1.1.0",
"babel-polyfill": "^6.26.0",
"copy-to-clipboard": "^3.3.1",
"cross-env": "^7.0.3",
@ -70,6 +72,7 @@
"npm": "^7.20.6",
"perfect-scrollbar": "^1.5.5",
"screenfull": "5.2.0",
"simplebar-react": "^3.2.4",
"socket.io-client": "^1.7.4",
"socket.io-parser": "^3.4.1",
"superagent": "^6.1.0",

33
web/routes/attachment/index.js

@ -1,6 +1,6 @@
'use strict';
const request = require('superagent');
// const parse = require('async-busboy');
const parse = require('async-busboy');
// const path = require('path')
// const fs = require('fs');
@ -49,8 +49,39 @@ module.exports = {
await next();
}
}
let upload_ = async function (ctx, next) {
let fkey = null;
try {
// const { p } = ctx.params;
console.log(111,ctx);
const { files } = await parse(ctx.req);
// console.log(files);
const file = files[0];
// const extname = path.extname(file.filename).toLowerCase();
// if (!UploadPath[p]) {
// ctx.status = 400;
// ctx.body = JSON.stringify({ error: '附件存放的文件夹名称无效' });
// return;
// } else if (UploadPath[p].indexOf(extname) < 0) {
// ctx.status = 400;
// ctx.body = JSON.stringify({ error: '文件格式无效' });
// return;
// } else {
const fileInfo = await ctx.app.fs.attachment.upload(file);
fkey = fileInfo.key;
console.log(11,fileInfo);
ctx.body = { uploaded: fkey };
// }
} catch (err) {
ctx.status = 500;
ctx.fs.logger.error(err);
ctx.body = { err: 'upload error.' };
}
}
router.use(download);
router.get('/api/root', getApiRoot);
router.post('/_upload/attachments', upload_);
}
};

Loading…
Cancel
Save