Browse Source

fix(upload): repair file upload and delete invalidation

vben 4 years ago
parent
commit
bd6b203fa9

+ 2 - 0
.gitignore

@@ -4,6 +4,8 @@ dist
 .npmrc
 .cache
 
+test/upload-server/static
+
 .local
 # local env files
 .env.local

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -30,6 +30,7 @@
 - 修复菜单图标大小不一致
 - 修复顶部菜单宽度计算问题
 - 修复表格 tabSetting 问题
+- 修复文件上传删除失效
 
 ## 2.0.0-rc.12 (2020-11-30)
 

+ 12 - 11
src/components/Table/src/components/TableAction.tsx

@@ -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>
                             );

+ 18 - 9
src/components/Upload/src/FileList.tsx

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

+ 27 - 0
src/components/Upload/src/ThumbUrl.vue

@@ -0,0 +1,27 @@
+<template>
+  <span class="thumb">
+    <img v-if="fileUrl" :src="fileUrl" />
+  </span>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { propTypes } from '/@/utils/propTypes';
+
+  export default defineComponent({
+    props: {
+      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>

+ 0 - 26
src/components/Upload/src/ThumnUrl.vue

@@ -1,26 +0,0 @@
-<template>
-  <span>
-    <img v-if="fileUrl" :src="fileUrl" />
-    <span v-else>{{ fileType }}</span>
-  </span>
-</template>
-<script lang="ts">
-  import { defineComponent, PropType } from 'vue';
-
-  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: '',
-      },
-    },
-  });
-</script>

+ 5 - 14
src/components/Upload/src/UploadModal.vue

@@ -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),

+ 6 - 8
src/components/Upload/src/data.tsx

@@ -1,11 +1,11 @@
 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} />;
       },
     },
     {

+ 0 - 0
src/components/Upload/src/utils.ts → src/components/Upload/src/helper.ts


+ 1 - 1
src/locales/lang/zh_CN/component/upload.ts

@@ -18,7 +18,7 @@ export default {
   maxSizeMultiple: '只能上传不超过{0}MB的文件!',
   maxNumber: '最多只能上传{0}个文件',
 
-  legend: '图',
+  legend: '略缩图',
   fileName: '文件名',
   fileSize: '文件大小',
   fileStatue: '状态',

+ 1 - 1
src/utils/uuid.ts

@@ -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++;

+ 15 - 0
test/upload-server/README.md

@@ -0,0 +1,15 @@
+# Upload Server
+
+Simple file upload service for testing file upload components.
+
+## Usage
+
+```js
+
+cs ./test/upload-server
+
+yarn install
+
+node app.js
+
+```

+ 101 - 0
test/upload-server/app.js

@@ -0,0 +1,101 @@
+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');
+});

+ 13 - 0
test/upload-server/package.json

@@ -0,0 +1,13 @@
+{
+  "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"
+  }
+}