Commit bd6b203fa969d173574657940a50b649c778b0b4

Authored by vben
1 parent 404db2fb

fix(upload): repair file upload and delete invalidation

.gitignore
@@ -4,6 +4,8 @@ dist @@ -4,6 +4,8 @@ dist
4 .npmrc 4 .npmrc
5 .cache 5 .cache
6 6
  7 +test/upload-server/static
  8 +
7 .local 9 .local
8 # local env files 10 # local env files
9 .env.local 11 .env.local
CHANGELOG.zh_CN.md
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 - 修复菜单图标大小不一致 30 - 修复菜单图标大小不一致
31 - 修复顶部菜单宽度计算问题 31 - 修复顶部菜单宽度计算问题
32 - 修复表格 tabSetting 问题 32 - 修复表格 tabSetting 问题
  33 +- 修复文件上传删除失效
33 34
34 ## 2.0.0-rc.12 (2020-11-30) 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,6 +4,7 @@ import Icon from '/@/components/Icon/index';
4 import { DownOutlined } from '@ant-design/icons-vue'; 4 import { DownOutlined } from '@ant-design/icons-vue';
5 import { ActionItem } from '/@/components/Table'; 5 import { ActionItem } from '/@/components/Table';
6 import { Button } from '/@/components/Button'; 6 import { Button } from '/@/components/Button';
  7 +import { snowUuid } from '/@/utils/uuid';
7 const prefixCls = 'basic-table-action'; 8 const prefixCls = 'basic-table-action';
8 export default defineComponent({ 9 export default defineComponent({
9 name: 'TableAction', 10 name: 'TableAction',
@@ -23,7 +24,7 @@ export default defineComponent({ @@ -23,7 +24,7 @@ export default defineComponent({
23 }, 24 },
24 }, 25 },
25 setup(props) { 26 setup(props) {
26 - function renderButton(action: ActionItem, index: number) { 27 + function renderButton(action: ActionItem) {
27 const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action; 28 const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action;
28 const button = ( 29 const button = (
29 <Button 30 <Button
@@ -32,7 +33,7 @@ export default defineComponent({ @@ -32,7 +33,7 @@ export default defineComponent({
32 disabled={disabled} 33 disabled={disabled}
33 color={color} 34 color={color}
34 {...actionProps} 35 {...actionProps}
35 - key={`${index}-${label}`} 36 + key={`${snowUuid()}`}
36 > 37 >
37 {() => ( 38 {() => (
38 <> 39 <>
@@ -45,10 +46,10 @@ export default defineComponent({ @@ -45,10 +46,10 @@ export default defineComponent({
45 return button; 46 return button;
46 } 47 }
47 48
48 - function renderPopConfirm(action: ActionItem, index: number) { 49 + function renderPopConfirm(action: ActionItem) {
49 const { popConfirm = null } = action; 50 const { popConfirm = null } = action;
50 if (!popConfirm) { 51 if (!popConfirm) {
51 - return renderButton(action, index); 52 + return renderButton(action);
52 } 53 }
53 const { 54 const {
54 title, 55 title,
@@ -60,7 +61,7 @@ export default defineComponent({ @@ -60,7 +61,7 @@ export default defineComponent({
60 } = popConfirm; 61 } = popConfirm;
61 return ( 62 return (
62 <Popconfirm 63 <Popconfirm
63 - key={`p-${index}-${title}`} 64 + key={`${snowUuid()}`}
64 title={title} 65 title={title}
65 onConfirm={confirm} 66 onConfirm={confirm}
66 onCancel={cancel} 67 onCancel={cancel}
@@ -68,7 +69,7 @@ export default defineComponent({ @@ -68,7 +69,7 @@ export default defineComponent({
68 cancelText={cancelText} 69 cancelText={cancelText}
69 icon={icon} 70 icon={icon}
70 > 71 >
71 - {() => renderButton(action, index)} 72 + {() => renderButton(action)}
72 </Popconfirm> 73 </Popconfirm>
73 ); 74 );
74 } 75 }
@@ -92,8 +93,8 @@ export default defineComponent({ @@ -92,8 +93,8 @@ export default defineComponent({
92 return ( 93 return (
93 <div class={prefixCls}> 94 <div class={prefixCls}>
94 {actions && 95 {actions &&
95 - actions.map((action, index) => {  
96 - return renderPopConfirm(action, index); 96 + actions.map((action) => {
  97 + return renderPopConfirm(action);
97 })} 98 })}
98 {dropDownActions && dropDownActions.length && ( 99 {dropDownActions && dropDownActions.length && (
99 <Dropdown overlayClassName="basic-tale-action-dropdown"> 100 <Dropdown overlayClassName="basic-tale-action-dropdown">
@@ -104,13 +105,13 @@ export default defineComponent({ @@ -104,13 +105,13 @@ export default defineComponent({
104 <Menu> 105 <Menu>
105 {{ 106 {{
106 default: () => { 107 default: () => {
107 - return dropDownActions.map((action, index) => { 108 + return dropDownActions.map((action) => {
108 const { disabled = false } = action; 109 const { disabled = false } = action;
109 action.ghost = true; 110 action.ghost = true;
110 return ( 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 </Menu.Item> 116 </Menu.Item>
116 ); 117 );
src/components/Upload/src/FileList.tsx
@@ -10,13 +10,14 @@ export default defineComponent({ @@ -10,13 +10,14 @@ export default defineComponent({
10 return () => { 10 return () => {
11 const { columns, actionColumn, dataSource } = props; 11 const { columns, actionColumn, dataSource } = props;
12 12
  13 + const columnList = [...columns, actionColumn];
13 return ( 14 return (
14 <table class="file-table"> 15 <table class="file-table">
15 <colgroup> 16 <colgroup>
16 - {[...columns, actionColumn].map((item) => {  
17 - const { width = 0 } = item; 17 + {columnList.map((item) => {
  18 + const { width = 0, dataIndex } = item;
18 return width ? ( 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 <col /> 22 <col />
22 ); 23 );
@@ -24,9 +25,13 @@ export default defineComponent({ @@ -24,9 +25,13 @@ export default defineComponent({
24 </colgroup> 25 </colgroup>
25 <thead> 26 <thead>
26 <tr class="file-table-tr"> 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 </tr> 36 </tr>
32 </thead> 37 </thead>
@@ -34,16 +39,20 @@ export default defineComponent({ @@ -34,16 +39,20 @@ export default defineComponent({
34 {dataSource.map((record = {}) => { 39 {dataSource.map((record = {}) => {
35 return ( 40 return (
36 <tr class="file-table-tr"> 41 <tr class="file-table-tr">
37 - {[...columns, actionColumn].map((item) => { 42 + {columnList.map((item) => {
38 const { dataIndex = '', customRender, align = 'center' } = item; 43 const { dataIndex = '', customRender, align = 'center' } = item;
39 if (customRender && isFunction(customRender)) { 44 if (customRender && isFunction(customRender)) {
40 return ( 45 return (
41 - <td class={['file-table-td', align]}> 46 + <td class={['file-table-td', align]} key={dataIndex}>
42 {customRender({ text: record[dataIndex], record })} 47 {customRender({ text: record[dataIndex], record })}
43 </td> 48 </td>
44 ); 49 );
45 } else { 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 </tr> 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,8 +23,10 @@
23 {{ getUploadBtnText }} 23 {{ getUploadBtnText }}
24 </a-button> 24 </a-button>
25 </template> 25 </template>
  26 +
26 <div class="upload-modal-toolbar"> 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 <Upload 30 <Upload
29 :accept="getStringAccept" 31 :accept="getStringAccept"
30 :multiple="multiple" 32 :multiple="multiple"
@@ -50,7 +52,7 @@ @@ -50,7 +52,7 @@
50 import { basicProps } from './props'; 52 import { basicProps } from './props';
51 import { createTableColumns, createActionColumn } from './data'; 53 import { createTableColumns, createActionColumn } from './data';
52 // utils 54 // utils
53 - import { checkFileType, checkImgType, getBase64WithFile } from './utils'; 55 + import { checkFileType, checkImgType, getBase64WithFile } from './helper';
54 import { buildUUID } from '/@/utils/uuid'; 56 import { buildUUID } from '/@/utils/uuid';
55 import { createImgPreview } from '/@/components/Preview/index'; 57 import { createImgPreview } from '/@/components/Preview/index';
56 import { uploadApi } from '/@/api/sys/upload'; 58 import { uploadApi } from '/@/api/sys/upload';
@@ -63,9 +65,9 @@ @@ -63,9 +65,9 @@
63 components: { BasicModal, Upload, Alert, FileList }, 65 components: { BasicModal, Upload, Alert, FileList },
64 props: basicProps, 66 props: basicProps,
65 setup(props, { emit }) { 67 setup(props, { emit }) {
66 - // 是否正在上传  
67 const { t } = useI18n(); 68 const { t } = useI18n();
68 69
  70 + // 是否正在上传
69 const isUploadingRef = ref(false); 71 const isUploadingRef = ref(false);
70 const fileListRef = ref<FileItem[]>([]); 72 const fileListRef = ref<FileItem[]>([]);
71 const state = reactive<{ fileList: FileItem[] }>({ 73 const state = reactive<{ fileList: FileItem[] }>({
@@ -116,7 +118,6 @@ @@ -116,7 +118,6 @@
116 const { size, name } = file; 118 const { size, name } = file;
117 const { maxSize } = props; 119 const { maxSize } = props;
118 const accept = unref(getAccept); 120 const accept = unref(getAccept);
119 -  
120 // 设置最大值,则判断 121 // 设置最大值,则判断
121 if (maxSize && file.size / 1024 / 1024 >= maxSize) { 122 if (maxSize && file.size / 1024 / 1024 >= maxSize) {
122 createMessage.error(t('component.upload.maxSizeMultiple', [maxSize])); 123 createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
@@ -175,7 +176,6 @@ @@ -175,7 +176,6 @@
175 } 176 }
176 try { 177 try {
177 item.status = UploadResultStatus.UPLOADING; 178 item.status = UploadResultStatus.UPLOADING;
178 -  
179 const { data } = await uploadApi( 179 const { data } = await uploadApi(
180 { 180 {
181 ...(props.uploadParams || {}), 181 ...(props.uploadParams || {}),
@@ -266,15 +266,6 @@ @@ -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 return { 269 return {
279 columns: createTableColumns(), 270 columns: createTableColumns(),
280 actionColumn: createActionColumn(handleRemove, handlePreview), 271 actionColumn: createActionColumn(handleRemove, handlePreview),
src/components/Upload/src/data.tsx
1 import type { BasicColumn, ActionItem } from '/@/components/Table'; 1 import type { BasicColumn, ActionItem } from '/@/components/Table';
2 2
3 import { FileItem, PreviewFileItem, UploadResultStatus } from './types'; 3 import { FileItem, PreviewFileItem, UploadResultStatus } from './types';
4 -import { checkImgType, isImgTypeByName } from './utils'; 4 +import { checkImgType, isImgTypeByName } from './helper';
5 import { Progress, Tag } from 'ant-design-vue'; 5 import { Progress, Tag } from 'ant-design-vue';
6 6
7 import TableAction from '/@/components/Table/src/components/TableAction'; 7 import TableAction from '/@/components/Table/src/components/TableAction';
8 - 8 +import ThumbUrl from './ThumbUrl.vue';
9 import { useI18n } from '/@/hooks/web/useI18n'; 9 import { useI18n } from '/@/hooks/web/useI18n';
10 const { t } = useI18n(); 10 const { t } = useI18n();
11 11
@@ -17,8 +17,8 @@ export function createTableColumns(): BasicColumn[] { @@ -17,8 +17,8 @@ export function createTableColumns(): BasicColumn[] {
17 title: t('component.upload.legend'), 17 title: t('component.upload.legend'),
18 width: 100, 18 width: 100,
19 customRender: ({ record }) => { 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,10 +108,8 @@ export function createPreviewColumns(): BasicColumn[] {
108 title: t('component.upload.legend'), 108 title: t('component.upload.legend'),
109 width: 100, 109 width: 100,
110 customRender: ({ record }) => { 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,7 +18,7 @@ export default {
18 maxSizeMultiple: '只能上传不超过{0}MB的文件!', 18 maxSizeMultiple: '只能上传不超过{0}MB的文件!',
19 maxNumber: '最多只能上传{0}个文件', 19 maxNumber: '最多只能上传{0}个文件',
20 20
21 - legend: '图例', 21 + legend: '略缩图',
22 fileName: '文件名', 22 fileName: '文件名',
23 fileSize: '文件大小', 23 fileSize: '文件大小',
24 fileStatue: '状态', 24 fileStatue: '状态',
src/utils/uuid.ts
@@ -19,7 +19,7 @@ export function buildUUID(): string { @@ -19,7 +19,7 @@ export function buildUUID(): string {
19 } 19 }
20 20
21 let unique = 0; 21 let unique = 0;
22 -export function snowUuid(prefix: string): string { 22 +export function snowUuid(prefix = ''): string {
23 const time = Date.now(); 23 const time = Date.now();
24 const random = Math.floor(Math.random() * 1000000000); 24 const random = Math.floor(Math.random() * 1000000000);
25 unique++; 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 +}