提交 bd6b203f 编写于 作者: V vben

fix(upload): repair file upload and delete invalidation

上级 404db2fb
......@@ -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>
......
<template>
<span>
<span class="thumb">
<img v-if="fileUrl" :src="fileUrl" />
<span v-else>{{ fileType }}</span>
</span>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { defineComponent } from 'vue';
import { propTypes } from '/@/utils/propTypes';
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: '',
},
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>
......@@ -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),
......
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++;
......
# Upload Server
Simple file upload service for testing file upload components.
## Usage
```js
cs ./test/upload-server
yarn install
node app.js
```
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');
});
{
"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"
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册