Commit bd6b203fa969d173574657940a50b649c778b0b4
1 parent
404db2fb
fix(upload): repair file upload and delete invalidation
Showing
14 changed files
with
202 additions
and
70 deletions
.gitignore
CHANGELOG.zh_CN.md
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
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