Commit bd6b203fa969d173574657940a50b649c778b0b4

Authored by vben
1 parent 404db2fb

fix(upload): repair file upload and delete invalidation

.gitignore
... ... @@ -4,6 +4,8 @@ dist
4 4 .npmrc
5 5 .cache
6 6  
  7 +test/upload-server/static
  8 +
7 9 .local
8 10 # local env files
9 11 .env.local
... ...
CHANGELOG.zh_CN.md
... ... @@ -30,6 +30,7 @@
30 30 - 修复菜单图标大小不一致
31 31 - 修复顶部菜单宽度计算问题
32 32 - 修复表格 tabSetting 问题
  33 +- 修复文件上传删除失效
33 34  
34 35 ## 2.0.0-rc.12 (2020-11-30)
35 36  
... ...
src/components/Table/src/components/TableAction.tsx
... ... @@ -4,6 +4,7 @@ import Icon from '/@/components/Icon/index';
4 4 import { DownOutlined } from '@ant-design/icons-vue';
5 5 import { ActionItem } from '/@/components/Table';
6 6 import { Button } from '/@/components/Button';
  7 +import { snowUuid } from '/@/utils/uuid';
7 8 const prefixCls = 'basic-table-action';
8 9 export default defineComponent({
9 10 name: 'TableAction',
... ... @@ -23,7 +24,7 @@ export default defineComponent({
23 24 },
24 25 },
25 26 setup(props) {
26   - function renderButton(action: ActionItem, index: number) {
  27 + function renderButton(action: ActionItem) {
27 28 const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action;
28 29 const button = (
29 30 <Button
... ... @@ -32,7 +33,7 @@ export default defineComponent({
32 33 disabled={disabled}
33 34 color={color}
34 35 {...actionProps}
35   - key={`${index}-${label}`}
  36 + key={`${snowUuid()}`}
36 37 >
37 38 {() => (
38 39 <>
... ... @@ -45,10 +46,10 @@ export default defineComponent({
45 46 return button;
46 47 }
47 48  
48   - function renderPopConfirm(action: ActionItem, index: number) {
  49 + function renderPopConfirm(action: ActionItem) {
49 50 const { popConfirm = null } = action;
50 51 if (!popConfirm) {
51   - return renderButton(action, index);
  52 + return renderButton(action);
52 53 }
53 54 const {
54 55 title,
... ... @@ -60,7 +61,7 @@ export default defineComponent({
60 61 } = popConfirm;
61 62 return (
62 63 <Popconfirm
63   - key={`p-${index}-${title}`}
  64 + key={`${snowUuid()}`}
64 65 title={title}
65 66 onConfirm={confirm}
66 67 onCancel={cancel}
... ... @@ -68,7 +69,7 @@ export default defineComponent({
68 69 cancelText={cancelText}
69 70 icon={icon}
70 71 >
71   - {() => renderButton(action, index)}
  72 + {() => renderButton(action)}
72 73 </Popconfirm>
73 74 );
74 75 }
... ... @@ -92,8 +93,8 @@ export default defineComponent({
92 93 return (
93 94 <div class={prefixCls}>
94 95 {actions &&
95   - actions.map((action, index) => {
96   - return renderPopConfirm(action, index);
  96 + actions.map((action) => {
  97 + return renderPopConfirm(action);
97 98 })}
98 99 {dropDownActions && dropDownActions.length && (
99 100 <Dropdown overlayClassName="basic-tale-action-dropdown">
... ... @@ -104,13 +105,13 @@ export default defineComponent({
104 105 <Menu>
105 106 {{
106 107 default: () => {
107   - return dropDownActions.map((action, index) => {
  108 + return dropDownActions.map((action) => {
108 109 const { disabled = false } = action;
109 110 action.ghost = true;
110 111 return (
111   - <Menu.Item key={`${index}`} disabled={disabled}>
  112 + <Menu.Item key={`${snowUuid()}`} disabled={disabled}>
112 113 {() => {
113   - return renderPopConfirm(action, index);
  114 + return renderPopConfirm(action);
114 115 }}
115 116 </Menu.Item>
116 117 );
... ...
src/components/Upload/src/FileList.tsx
... ... @@ -10,13 +10,14 @@ export default defineComponent({
10 10 return () => {
11 11 const { columns, actionColumn, dataSource } = props;
12 12  
  13 + const columnList = [...columns, actionColumn];
13 14 return (
14 15 <table class="file-table">
15 16 <colgroup>
16   - {[...columns, actionColumn].map((item) => {
17   - const { width = 0 } = item;
  17 + {columnList.map((item) => {
  18 + const { width = 0, dataIndex } = item;
18 19 return width ? (
19   - <col style={'width:' + width + 'px;min-width:' + width + 'px;'} />
  20 + <col style={'width:' + width + 'px;min-width:' + width + 'px;'} key={dataIndex} />
20 21 ) : (
21 22 <col />
22 23 );
... ... @@ -24,9 +25,13 @@ export default defineComponent({
24 25 </colgroup>
25 26 <thead>
26 27 <tr class="file-table-tr">
27   - {[...columns, actionColumn].map((item) => {
28   - const { title = '', align = 'center' } = item;
29   - return <th class={['file-table-th', align]}>{title}</th>;
  28 + {columnList.map((item) => {
  29 + const { title = '', align = 'center', dataIndex } = item;
  30 + return (
  31 + <th class={['file-table-th', align]} key={dataIndex}>
  32 + {title}
  33 + </th>
  34 + );
30 35 })}
31 36 </tr>
32 37 </thead>
... ... @@ -34,16 +39,20 @@ export default defineComponent({
34 39 {dataSource.map((record = {}) => {
35 40 return (
36 41 <tr class="file-table-tr">
37   - {[...columns, actionColumn].map((item) => {
  42 + {columnList.map((item) => {
38 43 const { dataIndex = '', customRender, align = 'center' } = item;
39 44 if (customRender && isFunction(customRender)) {
40 45 return (
41   - <td class={['file-table-td', align]}>
  46 + <td class={['file-table-td', align]} key={dataIndex}>
42 47 {customRender({ text: record[dataIndex], record })}
43 48 </td>
44 49 );
45 50 } else {
46   - return <td class={['file-table-td', align]}>{record[dataIndex]}</td>;
  51 + return (
  52 + <td class={['file-table-td', align]} key={dataIndex}>
  53 + {record[dataIndex]}
  54 + </td>
  55 + );
47 56 }
48 57 })}
49 58 </tr>
... ...
src/components/Upload/src/ThumbUrl.vue 0 → 100644
  1 +<template>
  2 + <span class="thumb">
  3 + <img v-if="fileUrl" :src="fileUrl" />
  4 + </span>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent } from 'vue';
  8 + import { propTypes } from '/@/utils/propTypes';
  9 +
  10 + export default defineComponent({
  11 + props: {
  12 + fileUrl: propTypes.string.def(''),
  13 + fileName: propTypes.string.def(''),
  14 + },
  15 + });
  16 +</script>
  17 +<style lang="less" scoped>
  18 + .thumb {
  19 + img {
  20 + position: static;
  21 + display: block;
  22 + width: 104px;
  23 + height: 104px;
  24 + object-fit: cover;
  25 + }
  26 + }
  27 +</style>
... ...
src/components/Upload/src/ThumnUrl.vue deleted 100644 → 0
1   -<template>
2   - <span>
3   - <img v-if="fileUrl" :src="fileUrl" />
4   - <span v-else>{{ fileType }}</span>
5   - </span>
6   -</template>
7   -<script lang="ts">
8   - import { defineComponent, PropType } from 'vue';
9   -
10   - export default defineComponent({
11   - props: {
12   - fileUrl: {
13   - type: String as PropType<string>,
14   - default: '',
15   - },
16   - fileType: {
17   - type: String as PropType<string>,
18   - default: '',
19   - },
20   - fileName: {
21   - type: String as PropType<string>,
22   - default: '',
23   - },
24   - },
25   - });
26   -</script>
src/components/Upload/src/UploadModal.vue
... ... @@ -23,8 +23,10 @@
23 23 {{ getUploadBtnText }}
24 24 </a-button>
25 25 </template>
  26 +
26 27 <div class="upload-modal-toolbar">
27   - <Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text"></Alert>
  28 + <Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text" />
  29 +
28 30 <Upload
29 31 :accept="getStringAccept"
30 32 :multiple="multiple"
... ... @@ -50,7 +52,7 @@
50 52 import { basicProps } from './props';
51 53 import { createTableColumns, createActionColumn } from './data';
52 54 // utils
53   - import { checkFileType, checkImgType, getBase64WithFile } from './utils';
  55 + import { checkFileType, checkImgType, getBase64WithFile } from './helper';
54 56 import { buildUUID } from '/@/utils/uuid';
55 57 import { createImgPreview } from '/@/components/Preview/index';
56 58 import { uploadApi } from '/@/api/sys/upload';
... ... @@ -63,9 +65,9 @@
63 65 components: { BasicModal, Upload, Alert, FileList },
64 66 props: basicProps,
65 67 setup(props, { emit }) {
66   - // 是否正在上传
67 68 const { t } = useI18n();
68 69  
  70 + // 是否正在上传
69 71 const isUploadingRef = ref(false);
70 72 const fileListRef = ref<FileItem[]>([]);
71 73 const state = reactive<{ fileList: FileItem[] }>({
... ... @@ -116,7 +118,6 @@
116 118 const { size, name } = file;
117 119 const { maxSize } = props;
118 120 const accept = unref(getAccept);
119   -
120 121 // 设置最大值,则判断
121 122 if (maxSize && file.size / 1024 / 1024 >= maxSize) {
122 123 createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
... ... @@ -175,7 +176,6 @@
175 176 }
176 177 try {
177 178 item.status = UploadResultStatus.UPLOADING;
178   -
179 179 const { data } = await uploadApi(
180 180 {
181 181 ...(props.uploadParams || {}),
... ... @@ -266,15 +266,6 @@
266 266 }
267 267 }
268 268  
269   - // const [registerTable] = useTable({
270   - // columns: createTableColumns(),
271   - // actionColumn: createActionColumn(handleRemove, handlePreview),
272   - // pagination: false,
273   - // inset: true,
274   - // scroll: {
275   - // y: 3000,
276   - // },
277   - // });
278 269 return {
279 270 columns: createTableColumns(),
280 271 actionColumn: createActionColumn(handleRemove, handlePreview),
... ...
src/components/Upload/src/data.tsx
1 1 import type { BasicColumn, ActionItem } from '/@/components/Table';
2 2  
3 3 import { FileItem, PreviewFileItem, UploadResultStatus } from './types';
4   -import { checkImgType, isImgTypeByName } from './utils';
  4 +import { checkImgType, isImgTypeByName } from './helper';
5 5 import { Progress, Tag } from 'ant-design-vue';
6 6  
7 7 import TableAction from '/@/components/Table/src/components/TableAction';
8   -
  8 +import ThumbUrl from './ThumbUrl.vue';
9 9 import { useI18n } from '/@/hooks/web/useI18n';
10 10 const { t } = useI18n();
11 11  
... ... @@ -17,8 +17,8 @@ export function createTableColumns(): BasicColumn[] {
17 17 title: t('component.upload.legend'),
18 18 width: 100,
19 19 customRender: ({ record }) => {
20   - const { thumbUrl, type } = (record as FileItem) || {};
21   - return <span>{thumbUrl ? <img style={{ maxWidth: '100%' }} src={thumbUrl} /> : type}</span>;
  20 + const { thumbUrl } = (record as FileItem) || {};
  21 + return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
22 22 },
23 23 },
24 24 {
... ... @@ -108,10 +108,8 @@ export function createPreviewColumns(): BasicColumn[] {
108 108 title: t('component.upload.legend'),
109 109 width: 100,
110 110 customRender: ({ record }) => {
111   - const { url, type } = (record as PreviewFileItem) || {};
112   - return (
113   - <span>{isImgTypeByName(url) ? <img src={url} style={{ width: '50px' }} /> : type}</span>
114   - );
  111 + const { url } = (record as PreviewFileItem) || {};
  112 + return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
115 113 },
116 114 },
117 115 {
... ...
src/components/Upload/src/utils.ts renamed to src/components/Upload/src/helper.ts
src/locales/lang/zh_CN/component/upload.ts
... ... @@ -18,7 +18,7 @@ export default {
18 18 maxSizeMultiple: '只能上传不超过{0}MB的文件!',
19 19 maxNumber: '最多只能上传{0}个文件',
20 20  
21   - legend: '图例',
  21 + legend: '略缩图',
22 22 fileName: '文件名',
23 23 fileSize: '文件大小',
24 24 fileStatue: '状态',
... ...
src/utils/uuid.ts
... ... @@ -19,7 +19,7 @@ export function buildUUID(): string {
19 19 }
20 20  
21 21 let unique = 0;
22   -export function snowUuid(prefix: string): string {
  22 +export function snowUuid(prefix = ''): string {
23 23 const time = Date.now();
24 24 const random = Math.floor(Math.random() * 1000000000);
25 25 unique++;
... ...
test/upload-server/README.md 0 → 100644
  1 +# Upload Server
  2 +
  3 +Simple file upload service for testing file upload components.
  4 +
  5 +## Usage
  6 +
  7 +```js
  8 +
  9 +cs ./test/upload-server
  10 +
  11 +yarn install
  12 +
  13 +node app.js
  14 +
  15 +```
... ...
test/upload-server/app.js 0 → 100644
  1 +const Koa = require('koa');
  2 +const fs = require('fs');
  3 +const path = require('path');
  4 +const router = require('koa-router')();
  5 +const koaBody = require('koa-body');
  6 +const static = require('koa-static');
  7 +const cors = require('koa2-cors');
  8 +const app = new Koa();
  9 +
  10 +app.use(cors());
  11 +
  12 +app.use(
  13 + koaBody({
  14 + multipart: true,
  15 + formidable: {
  16 + maxFieldsSize: 20 * 1024 * 1024,
  17 + multipart: true,
  18 + },
  19 + })
  20 +);
  21 +
  22 +const uploadUrl = 'http://localhost:3001/static/upload';
  23 +
  24 +router.get('/', (ctx) => {
  25 + ctx.type = 'html';
  26 + const pathUrl = path.join(__dirname, '/static/upload.html');
  27 + ctx.body = fs.createReadStream(pathUrl);
  28 +});
  29 +
  30 +const uploadFilePublic = function (ctx, files, flag) {
  31 + const filePath = path.join(__dirname, '/static/upload/');
  32 + let fileReader, fileResource, writeStream;
  33 +
  34 + const fileFunc = function (file) {
  35 + fileReader = fs.createReadStream(file.path);
  36 + fileResource = filePath + `/${file.name}`;
  37 +
  38 + writeStream = fs.createWriteStream(fileResource);
  39 + fileReader.pipe(writeStream);
  40 + };
  41 + const returnFunc = function (flag) {
  42 + console.log(flag);
  43 + console.log(files);
  44 + if (flag) {
  45 + let url = '';
  46 + for (let i = 0; i < files.length; i++) {
  47 + url += uploadUrl + `/${files[i].name},`;
  48 + }
  49 + url = url.replace(/,$/gi, '');
  50 + ctx.body = {
  51 + url: url,
  52 + code: 0,
  53 + message: '上传成功',
  54 + };
  55 + } else {
  56 + ctx.body = {
  57 + url: uploadUrl + `/${files.name}`,
  58 + code: 0,
  59 + message: '上传成功',
  60 + };
  61 + }
  62 + };
  63 + if (flag) {
  64 + // 多个文件上传
  65 + for (let i = 0; i < files.length; i++) {
  66 + const f1 = files[i];
  67 + fileFunc(f1);
  68 + }
  69 + } else {
  70 + fileFunc(files);
  71 + }
  72 +
  73 + if (!fs.existsSync(filePath)) {
  74 + fs.mkdir(filePath, (err) => {
  75 + if (err) {
  76 + throw new Error(err);
  77 + } else {
  78 + returnFunc(flag);
  79 + }
  80 + });
  81 + } else {
  82 + returnFunc(flag);
  83 + }
  84 +};
  85 +
  86 +router.post('/upload', (ctx) => {
  87 + let files = ctx.request.files.file;
  88 + if (files.length === undefined) {
  89 + uploadFilePublic(ctx, files, false);
  90 + } else {
  91 + uploadFilePublic(ctx, files, true);
  92 + }
  93 +});
  94 +
  95 +app.use(static(path.join(__dirname)));
  96 +
  97 +app.use(router.routes()).use(router.allowedMethods());
  98 +
  99 +app.listen(3001, () => {
  100 + console.log('server is listen in 3001');
  101 +});
... ...
test/upload-server/package.json 0 → 100644
  1 +{
  2 + "name": "server",
  3 + "version": "1.0.0",
  4 + "main": "index.js",
  5 + "license": "MIT",
  6 + "dependencies": {
  7 + "koa": "^2.13.0",
  8 + "koa-body": "^4.2.0",
  9 + "koa-router": "^10.0.0",
  10 + "koa-static": "^5.0.0",
  11 + "koa2-cors": "^2.0.6"
  12 + }
  13 +}
... ...