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 | 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
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
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