mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-02-03 02:54:40 +08:00
fix(upload): repair file upload and delete invalidation
This commit is contained in:
parent
404db2fb49
commit
bd6b203fa9
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,8 @@ dist
|
||||
.npmrc
|
||||
.cache
|
||||
|
||||
test/upload-server/static
|
||||
|
||||
.local
|
||||
# local env files
|
||||
.env.local
|
||||
|
@ -30,6 +30,7 @@
|
||||
- 修复菜单图标大小不一致
|
||||
- 修复顶部菜单宽度计算问题
|
||||
- 修复表格 tabSetting 问题
|
||||
- 修复文件上传删除失效
|
||||
|
||||
## 2.0.0-rc.12 (2020-11-30)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import Icon from '/@/components/Icon/index';
|
||||
import { DownOutlined } from '@ant-design/icons-vue';
|
||||
import { ActionItem } from '/@/components/Table';
|
||||
import { Button } from '/@/components/Button';
|
||||
import { snowUuid } from '/@/utils/uuid';
|
||||
const prefixCls = 'basic-table-action';
|
||||
export default defineComponent({
|
||||
name: 'TableAction',
|
||||
@ -23,7 +24,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function renderButton(action: ActionItem, index: number) {
|
||||
function renderButton(action: ActionItem) {
|
||||
const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action;
|
||||
const button = (
|
||||
<Button
|
||||
@ -32,7 +33,7 @@ export default defineComponent({
|
||||
disabled={disabled}
|
||||
color={color}
|
||||
{...actionProps}
|
||||
key={`${index}-${label}`}
|
||||
key={`${snowUuid()}`}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
@ -45,10 +46,10 @@ export default defineComponent({
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderPopConfirm(action: ActionItem, index: number) {
|
||||
function renderPopConfirm(action: ActionItem) {
|
||||
const { popConfirm = null } = action;
|
||||
if (!popConfirm) {
|
||||
return renderButton(action, index);
|
||||
return renderButton(action);
|
||||
}
|
||||
const {
|
||||
title,
|
||||
@ -60,7 +61,7 @@ export default defineComponent({
|
||||
} = popConfirm;
|
||||
return (
|
||||
<Popconfirm
|
||||
key={`p-${index}-${title}`}
|
||||
key={`${snowUuid()}`}
|
||||
title={title}
|
||||
onConfirm={confirm}
|
||||
onCancel={cancel}
|
||||
@ -68,7 +69,7 @@ export default defineComponent({
|
||||
cancelText={cancelText}
|
||||
icon={icon}
|
||||
>
|
||||
{() => renderButton(action, index)}
|
||||
{() => renderButton(action)}
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
@ -92,8 +93,8 @@ export default defineComponent({
|
||||
return (
|
||||
<div class={prefixCls}>
|
||||
{actions &&
|
||||
actions.map((action, index) => {
|
||||
return renderPopConfirm(action, index);
|
||||
actions.map((action) => {
|
||||
return renderPopConfirm(action);
|
||||
})}
|
||||
{dropDownActions && dropDownActions.length && (
|
||||
<Dropdown overlayClassName="basic-tale-action-dropdown">
|
||||
@ -104,13 +105,13 @@ export default defineComponent({
|
||||
<Menu>
|
||||
{{
|
||||
default: () => {
|
||||
return dropDownActions.map((action, index) => {
|
||||
return dropDownActions.map((action) => {
|
||||
const { disabled = false } = action;
|
||||
action.ghost = true;
|
||||
return (
|
||||
<Menu.Item key={`${index}`} disabled={disabled}>
|
||||
<Menu.Item key={`${snowUuid()}`} disabled={disabled}>
|
||||
{() => {
|
||||
return renderPopConfirm(action, index);
|
||||
return renderPopConfirm(action);
|
||||
}}
|
||||
</Menu.Item>
|
||||
);
|
||||
|
@ -10,13 +10,14 @@ export default defineComponent({
|
||||
return () => {
|
||||
const { columns, actionColumn, dataSource } = props;
|
||||
|
||||
const columnList = [...columns, actionColumn];
|
||||
return (
|
||||
<table class="file-table">
|
||||
<colgroup>
|
||||
{[...columns, actionColumn].map((item) => {
|
||||
const { width = 0 } = item;
|
||||
{columnList.map((item) => {
|
||||
const { width = 0, dataIndex } = item;
|
||||
return width ? (
|
||||
<col style={'width:' + width + 'px;min-width:' + width + 'px;'} />
|
||||
<col style={'width:' + width + 'px;min-width:' + width + 'px;'} key={dataIndex} />
|
||||
) : (
|
||||
<col />
|
||||
);
|
||||
@ -24,9 +25,13 @@ export default defineComponent({
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="file-table-tr">
|
||||
{[...columns, actionColumn].map((item) => {
|
||||
const { title = '', align = 'center' } = item;
|
||||
return <th class={['file-table-th', align]}>{title}</th>;
|
||||
{columnList.map((item) => {
|
||||
const { title = '', align = 'center', dataIndex } = item;
|
||||
return (
|
||||
<th class={['file-table-th', align]} key={dataIndex}>
|
||||
{title}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
@ -34,16 +39,20 @@ export default defineComponent({
|
||||
{dataSource.map((record = {}) => {
|
||||
return (
|
||||
<tr class="file-table-tr">
|
||||
{[...columns, actionColumn].map((item) => {
|
||||
{columnList.map((item) => {
|
||||
const { dataIndex = '', customRender, align = 'center' } = item;
|
||||
if (customRender && isFunction(customRender)) {
|
||||
return (
|
||||
<td class={['file-table-td', align]}>
|
||||
<td class={['file-table-td', align]} key={dataIndex}>
|
||||
{customRender({ text: record[dataIndex], record })}
|
||||
</td>
|
||||
);
|
||||
} else {
|
||||
return <td class={['file-table-td', align]}>{record[dataIndex]}</td>;
|
||||
return (
|
||||
<td class={['file-table-td', align]} key={dataIndex}>
|
||||
{record[dataIndex]}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</tr>
|
||||
|
27
src/components/Upload/src/ThumbUrl.vue
Normal file
27
src/components/Upload/src/ThumbUrl.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<span class="thumb">
|
||||
<img v-if="fileUrl" :src="fileUrl" />
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
fileUrl: propTypes.string.def(''),
|
||||
fileName: propTypes.string.def(''),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.thumb {
|
||||
img {
|
||||
position: static;
|
||||
display: block;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<span>
|
||||
<img v-if="fileUrl" :src="fileUrl" />
|
||||
<span v-else>{{ fileType }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
fileUrl: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
fileType: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
fileName: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -23,8 +23,10 @@
|
||||
{{ getUploadBtnText }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div class="upload-modal-toolbar">
|
||||
<Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text"></Alert>
|
||||
<Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text" />
|
||||
|
||||
<Upload
|
||||
:accept="getStringAccept"
|
||||
:multiple="multiple"
|
||||
@ -50,7 +52,7 @@
|
||||
import { basicProps } from './props';
|
||||
import { createTableColumns, createActionColumn } from './data';
|
||||
// utils
|
||||
import { checkFileType, checkImgType, getBase64WithFile } from './utils';
|
||||
import { checkFileType, checkImgType, getBase64WithFile } from './helper';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { createImgPreview } from '/@/components/Preview/index';
|
||||
import { uploadApi } from '/@/api/sys/upload';
|
||||
@ -63,9 +65,9 @@
|
||||
components: { BasicModal, Upload, Alert, FileList },
|
||||
props: basicProps,
|
||||
setup(props, { emit }) {
|
||||
// 是否正在上传
|
||||
const { t } = useI18n();
|
||||
|
||||
// 是否正在上传
|
||||
const isUploadingRef = ref(false);
|
||||
const fileListRef = ref<FileItem[]>([]);
|
||||
const state = reactive<{ fileList: FileItem[] }>({
|
||||
@ -116,7 +118,6 @@
|
||||
const { size, name } = file;
|
||||
const { maxSize } = props;
|
||||
const accept = unref(getAccept);
|
||||
|
||||
// 设置最大值,则判断
|
||||
if (maxSize && file.size / 1024 / 1024 >= maxSize) {
|
||||
createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
|
||||
@ -175,7 +176,6 @@
|
||||
}
|
||||
try {
|
||||
item.status = UploadResultStatus.UPLOADING;
|
||||
|
||||
const { data } = await uploadApi(
|
||||
{
|
||||
...(props.uploadParams || {}),
|
||||
@ -266,15 +266,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// const [registerTable] = useTable({
|
||||
// columns: createTableColumns(),
|
||||
// actionColumn: createActionColumn(handleRemove, handlePreview),
|
||||
// pagination: false,
|
||||
// inset: true,
|
||||
// scroll: {
|
||||
// y: 3000,
|
||||
// },
|
||||
// });
|
||||
return {
|
||||
columns: createTableColumns(),
|
||||
actionColumn: createActionColumn(handleRemove, handlePreview),
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type { BasicColumn, ActionItem } from '/@/components/Table';
|
||||
|
||||
import { FileItem, PreviewFileItem, UploadResultStatus } from './types';
|
||||
import { checkImgType, isImgTypeByName } from './utils';
|
||||
import { checkImgType, isImgTypeByName } from './helper';
|
||||
import { Progress, Tag } from 'ant-design-vue';
|
||||
|
||||
import TableAction from '/@/components/Table/src/components/TableAction';
|
||||
|
||||
import ThumbUrl from './ThumbUrl.vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -17,8 +17,8 @@ export function createTableColumns(): BasicColumn[] {
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { thumbUrl, type } = (record as FileItem) || {};
|
||||
return <span>{thumbUrl ? <img style={{ maxWidth: '100%' }} src={thumbUrl} /> : type}</span>;
|
||||
const { thumbUrl } = (record as FileItem) || {};
|
||||
return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -108,10 +108,8 @@ export function createPreviewColumns(): BasicColumn[] {
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { url, type } = (record as PreviewFileItem) || {};
|
||||
return (
|
||||
<span>{isImgTypeByName(url) ? <img src={url} style={{ width: '50px' }} /> : type}</span>
|
||||
);
|
||||
const { url } = (record as PreviewFileItem) || {};
|
||||
return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ export default {
|
||||
maxSizeMultiple: '只能上传不超过{0}MB的文件!',
|
||||
maxNumber: '最多只能上传{0}个文件',
|
||||
|
||||
legend: '图例',
|
||||
legend: '略缩图',
|
||||
fileName: '文件名',
|
||||
fileSize: '文件大小',
|
||||
fileStatue: '状态',
|
||||
|
@ -19,7 +19,7 @@ export function buildUUID(): string {
|
||||
}
|
||||
|
||||
let unique = 0;
|
||||
export function snowUuid(prefix: string): string {
|
||||
export function snowUuid(prefix = ''): string {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
unique++;
|
||||
|
15
test/upload-server/README.md
Normal file
15
test/upload-server/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Upload Server
|
||||
|
||||
Simple file upload service for testing file upload components.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
|
||||
cs ./test/upload-server
|
||||
|
||||
yarn install
|
||||
|
||||
node app.js
|
||||
|
||||
```
|
101
test/upload-server/app.js
Normal file
101
test/upload-server/app.js
Normal file
@ -0,0 +1,101 @@
|
||||
const Koa = require('koa');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const router = require('koa-router')();
|
||||
const koaBody = require('koa-body');
|
||||
const static = require('koa-static');
|
||||
const cors = require('koa2-cors');
|
||||
const app = new Koa();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.use(
|
||||
koaBody({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
maxFieldsSize: 20 * 1024 * 1024,
|
||||
multipart: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const uploadUrl = 'http://localhost:3001/static/upload';
|
||||
|
||||
router.get('/', (ctx) => {
|
||||
ctx.type = 'html';
|
||||
const pathUrl = path.join(__dirname, '/static/upload.html');
|
||||
ctx.body = fs.createReadStream(pathUrl);
|
||||
});
|
||||
|
||||
const uploadFilePublic = function (ctx, files, flag) {
|
||||
const filePath = path.join(__dirname, '/static/upload/');
|
||||
let fileReader, fileResource, writeStream;
|
||||
|
||||
const fileFunc = function (file) {
|
||||
fileReader = fs.createReadStream(file.path);
|
||||
fileResource = filePath + `/${file.name}`;
|
||||
|
||||
writeStream = fs.createWriteStream(fileResource);
|
||||
fileReader.pipe(writeStream);
|
||||
};
|
||||
const returnFunc = function (flag) {
|
||||
console.log(flag);
|
||||
console.log(files);
|
||||
if (flag) {
|
||||
let url = '';
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
url += uploadUrl + `/${files[i].name},`;
|
||||
}
|
||||
url = url.replace(/,$/gi, '');
|
||||
ctx.body = {
|
||||
url: url,
|
||||
code: 0,
|
||||
message: '上传成功',
|
||||
};
|
||||
} else {
|
||||
ctx.body = {
|
||||
url: uploadUrl + `/${files.name}`,
|
||||
code: 0,
|
||||
message: '上传成功',
|
||||
};
|
||||
}
|
||||
};
|
||||
if (flag) {
|
||||
// 多个文件上传
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const f1 = files[i];
|
||||
fileFunc(f1);
|
||||
}
|
||||
} else {
|
||||
fileFunc(files);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
fs.mkdir(filePath, (err) => {
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
} else {
|
||||
returnFunc(flag);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
returnFunc(flag);
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/upload', (ctx) => {
|
||||
let files = ctx.request.files.file;
|
||||
if (files.length === undefined) {
|
||||
uploadFilePublic(ctx, files, false);
|
||||
} else {
|
||||
uploadFilePublic(ctx, files, true);
|
||||
}
|
||||
});
|
||||
|
||||
app.use(static(path.join(__dirname)));
|
||||
|
||||
app.use(router.routes()).use(router.allowedMethods());
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log('server is listen in 3001');
|
||||
});
|
13
test/upload-server/package.json
Normal file
13
test/upload-server/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"koa": "^2.13.0",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-router": "^10.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa2-cors": "^2.0.6"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user