Переглянути джерело

feat(feature):upload->preview & upload->resultfield (#3707)

* feat(components->upload->resultField): upload和imageupload组件中添加resultField与demo

* feat(components->upload->preview): upload组件中添加自定义预览组件的方法与demo
Electrolux 1 рік тому
батько
коміт
b28a46edcb

+ 2 - 0
src/components/Upload/src/BasicUpload.vue

@@ -34,6 +34,8 @@
       @register="registerPreviewModal"
       @list-change="handlePreviewChange"
       @delete="handlePreviewDelete"
+      v-bind:preview-columns="props.previewColumns"
+      v-bind:before-preview-data="props.beforePreviewData"
     />
   </div>
 </template>

+ 4 - 1
src/components/Upload/src/components/FileList.vue

@@ -4,6 +4,7 @@
   import { useSortable } from '@/hooks/web/useSortable';
   import { useModalContext } from '@/components/Modal/src/hooks/useModalContext';
   import { defineComponent, CSSProperties, watch, nextTick, ref, onMounted } from 'vue';
+  import { FileBasicColumn } from '../types/typing';
 
   export default defineComponent({
     name: 'FileList',
@@ -51,7 +52,9 @@
 
       return () => {
         const { columns, actionColumn, dataSource } = props;
-        const columnList = [...columns, actionColumn];
+        let columnList: FileBasicColumn[];
+        columnList = (actionColumn ? [...columns, actionColumn] : [...columns]) as FileBasicColumn[];
+        
         return (
           // x scrollbar
           <div class="overflow-x-auto">

+ 11 - 3
src/components/Upload/src/components/ImageUpload.vue

@@ -37,12 +37,12 @@
   import { uploadContainerProps } from '../props';
   import { isImgTypeByName } from '../helper';
   import { UploadResultStatus } from '@/components/Upload/src/types/typing';
-
+  import { get,omit } from 'lodash-es';
   defineOptions({ name: 'ImageUpload' });
 
   const emit = defineEmits(['change', 'update:value', 'delete']);
   const props = defineProps({
-    ...uploadContainerProps,
+    ...omit(uploadContainerProps,["previewColumns","beforePreviewData"]),
   });
   const { t } = useI18n();
   const { createMessage } = useMessage();
@@ -170,7 +170,12 @@
         name: props.name,
         filename: props.filename,
       });
-      info.onSuccess!(res.data);
+      if(props.resultField){
+        info.onSuccess!(res);
+      }else{
+        // 不传入 resultField 的情况
+        info.onSuccess!(res.data);
+      }
       const value = getValue();
       isInnerOperate.value = true;
       emit('update:value', value);
@@ -185,6 +190,9 @@
     const list = (fileList.value || [])
       .filter((item) => item?.status === UploadResultStatus.DONE)
       .map((item: any) => {
+        if(props.resultField){
+          return get(item?.response, props.resultField)
+        }
         return item?.url || item?.response?.url;
       });
     return props.multiple ? list : list.length > 0 ? list[0] : '';

+ 10 - 1
src/components/Upload/src/components/UploadModal.vue

@@ -66,6 +66,7 @@
   import { warn } from '@/utils/log';
   import FileList from './FileList.vue';
   import { useI18n } from '@/hooks/web/useI18n';
+  import { get } from 'lodash-es';
 
   const props = defineProps({
     ...basicProps,
@@ -190,6 +191,14 @@
       const { data } = ret;
       item.status = UploadResultStatus.SUCCESS;
       item.response = data;
+      if(props.resultField){
+        // 适配预览组件而进行封装
+        item.response = {
+          code:0,
+          message:"upload Success!",
+          url:get(ret, props.resultField)
+        }
+      }
       return {
         success: true,
         error: null,
@@ -241,7 +250,7 @@
       return createMessage.warning(t('component.upload.saveWarn'));
     }
     const fileList: string[] = [];
-
+    
     for (const item of fileListRef.value) {
       const { status, response } = item;
       if (status === UploadResultStatus.SUCCESS && response) {

+ 29 - 4
src/components/Upload/src/components/UploadPreviewModal.vue

@@ -15,30 +15,55 @@
   import FileList from './FileList.vue';
   import { BasicModal, useModalInner } from '@/components/Modal';
   import { previewProps } from '../props';
-  import { PreviewFileItem } from '../types/typing';
+  import { FileBasicColumn, PreviewFileItem } from '../types/typing';
   import { downloadByUrl } from '@/utils/file/download';
   import { createPreviewColumns, createPreviewActionColumn } from './data';
   import { useI18n } from '@/hooks/web/useI18n';
   import { isArray } from '@/utils/is';
+  import { BasicColumn } from '@/components/Table';
 
   const props = defineProps(previewProps);
 
   const emit = defineEmits(['list-change', 'register', 'delete']);
 
-  const columns = createPreviewColumns() as any[];
-  const actionColumn = createPreviewActionColumn({ handleRemove, handleDownload }) as any;
+  let columns : BasicColumn[] | FileBasicColumn[] = createPreviewColumns();
+  let actionColumn :any;
 
   const [register] = useModalInner();
   const { t } = useI18n();
 
-  const fileListRef = ref<PreviewFileItem[]>([]);
+  const fileListRef = ref<PreviewFileItem[] | Array<any>>([]);
+    watch(
+    () => props.previewColumns,
+    () => {
+      if (props.previewColumns.length) {
+        columns = props.previewColumns;
+        actionColumn = null
+      }else{
+        columns=createPreviewColumns();
+        actionColumn = createPreviewActionColumn({ handleRemove, handleDownload })
+      };
+      
+    },
+    { immediate: true },
+  );
+
   watch(
     () => props.value,
     (value) => {
       if (!isArray(value)) value = [];
+      if(props.beforePreviewData){
+        value = props.beforePreviewData(value)  as any
+        fileListRef.value = value
+        return 
+      }
       fileListRef.value = value
         .filter((item) => !!item)
         .map((item) => {
+          if(typeof item!="string"){
+            console.error("return value should be string")
+            return
+          }
           return {
             url: item,
             type: item.split('.').pop() || '',

+ 21 - 2
src/components/Upload/src/props.ts

@@ -4,6 +4,8 @@ import { FileBasicColumn } from './types/typing';
 import type { Options } from 'sortablejs';
 
 import { Merge } from '@/utils/types';
+import { propTypes } from '@/utils/propTypes';
+import { BasicColumn } from '@/components/Table';
 
 type SortableOptions = Merge<
   Omit<Options, 'onEnd'>,
@@ -13,6 +15,19 @@ type SortableOptions = Merge<
   }
 >;
 
+export const previewType = {
+  previewColumns:{
+    type: Array as (PropType<BasicColumn[] | FileBasicColumn[]>),
+    default: [],
+    required: false,
+  },
+  beforePreviewData:{
+    type: Function as PropType<(arg:string[])=>Recordable<any>>,
+    default: null,
+    required: false,
+  },
+}
+
 type ListType = 'text' | 'picture' | 'picture-card';
 
 export const basicProps = {
@@ -69,11 +84,13 @@ export const basicProps = {
     type: Object as PropType<SortableOptions>,
     default: () => ({}),
   },
+  // support xxx.xxx.xx
+  resultField: propTypes.string.def(''),
 };
 
 export const uploadContainerProps = {
   value: {
-    type: Array as PropType<string[]>,
+    type: Array as (PropType<string[]>),
     default: () => [],
   },
   ...basicProps,
@@ -85,6 +102,7 @@ export const uploadContainerProps = {
     type: Boolean as PropType<boolean>,
     default: false,
   },
+  ...previewType
 };
 
 export const previewProps = {
@@ -92,11 +110,12 @@ export const previewProps = {
     type: Array as PropType<string[]>,
     default: () => [],
   },
+  ...previewType
 };
 
 export const fileListProps = {
   columns: {
-    type: Array as PropType<FileBasicColumn[]>,
+    type: Array as (PropType<BasicColumn[] | FileBasicColumn[]> ),
     default: null,
   },
   actionColumn: {

+ 1 - 1
src/components/Upload/src/types/typing.ts

@@ -16,7 +16,7 @@ export interface FileItem {
   percent: number;
   file: File;
   status?: UploadResultStatus;
-  response?: UploadApiResult;
+  response?: UploadApiResult | Recordable<any>;
   uuid: string;
 }
 

+ 178 - 11
src/views/demo/comp/upload/index.vue

@@ -12,7 +12,14 @@
 
     <Alert message="嵌入表单,加入表单校验" />
 
-    <BasicForm @register="register" class="my-5" />
+    <BasicForm @register="registerValiate" class="my-5" />
+
+    <Alert message="嵌入表单,加入resultFiled自定义返回值" />
+    <BasicForm @register="registerCustom" class="my-5" />
+
+    <Alert message="嵌入表单,自定义预览内容" />
+    <BasicForm @register="registerPreview" class="my-5" />
+    
   </PageWrapper>
 </template>
 <script lang="ts" setup>
@@ -20,33 +27,193 @@
   import { useMessage } from '@/hooks/web/useMessage';
   import { BasicForm, FormSchema, useForm } from '@/components/Form';
   import { PageWrapper } from '@/components/Page';
-  import { Alert } from 'ant-design-vue';
+  import { Alert,Button } from 'ant-design-vue';
   import { uploadApi } from '@/api/sys/upload';
+  import { createVNode } from "vue"
 
-  const schemas: FormSchema[] = [
+  const schemasValiate: FormSchema[] = [
     {
       field: 'field1',
       component: 'Upload',
       label: '字段1',
+      rules: [{ required: true, message: '请选择上传文件' }],
+      componentProps: {
+        api: uploadApi,
+      },
+    },{
+      field: 'field2',
+      component: "ImageUpload",
+      label: '字段2(ImageUpload)',
       colProps: {
         span: 8,
       },
-      rules: [{ required: true, message: '请选择上传文件' }],
       componentProps: {
         api: uploadApi,
+      }
+    },
+  ];
+  const schemasCustom: FormSchema[] = [
+    {
+      field: 'field3',
+      component: 'Upload',
+      label: '字段3',
+      componentProps: {
+        resultField:"data3.url",
+        api: (file,progress)=>{
+          return new Promise((resolve)=>{
+            uploadApi(file,progress).then((uploadApiResponse)=>{
+              resolve({
+                code:200,
+                data3:{
+                  url:uploadApiResponse.data.url
+                }
+              })
+            })
+          })
+        },
+      },
+    },
+    {
+      field: 'field4',
+      component: "ImageUpload",
+      label: '字段4(ImageUpload)',
+      colProps: {
+        span: 8,
+      },
+      componentProps: {
+        resultField:"data4.url",
+        api: (file,progress)=>{
+          return new Promise((resolve)=>{
+            uploadApi(file,progress).then((uploadApiResponse)=>{
+              resolve({
+                code:200,
+                data4:{
+                  url:uploadApiResponse.data.url
+                }
+              })
+            })
+          })
+        },
       },
     },
   ];
+  const schemasPreview: FormSchema[] = [
+    {
+      field: 'field5',
+      component: 'Upload',
+      label: '字段5',
+      componentProps: {
+        previewColumns:[{
+          title:"url5",
+          dataIndex:"url5"
+        },{
+          title:"type5",
+          dataIndex:"type5"
+        },{
+          title:"name5",
+          dataIndex:"name5"
+        },
+        {
+          title:"operation",
+          dataIndex:"",
+          customRender: ({ record })=>{
+            return createVNode(Button,{
+              onclick:()=>{
+                console.log(record)
+                createMessage.success(`请到控制台查看该行输出结果`);
+              }
+            },"点我")
+          }
+        },
+      ],
+        beforePreviewData:(arg)=>{
+          let data = arg.filter((item) => !!item).map((item) => {
+            if(typeof item !== "string"){
+              console.error("return value should be string")
+              return
+            }
+            return {
+              url5: item,
+              type5: item.split('.').pop() || '',
+              name5: item.split('/').pop() || '',
+            };
+          })
+          return data
+        },
+        resultField:"data5.url",
+        api: (file,progress)=>{
+          return new Promise((resolve)=>{
+            uploadApi(file,progress).then((uploadApiResponse)=>{
+              resolve({
+                code:200,
+                data5:{
+                  url:uploadApiResponse.data.url
+                }
+              })
+            })
+          })
+        },
+      },
+    },
+  ];
+  
+
+
   const { createMessage } = useMessage();
-  const [register] = useForm({
-    labelWidth: 120,
-    schemas,
+
+  function handleChange(list: string[]) {
+    createMessage.success(`已上传文件${JSON.stringify(list)}`);
+  }
+  const [registerValiate,{getFieldsValue:getFieldsValueValiate,validate}] = useForm({
+    labelWidth: 160,
+    schemas:schemasValiate,
     actionColOptions: {
-      span: 16,
+      span: 18,
     },
+    submitFunc:()=>{
+      return new Promise((resolve)=>{
+        validate().then((e)=>{
+          resolve()
+          console.log(getFieldsValueValiate())
+          createMessage.success(`请到控制台查看结果`);
+        }).catch(()=>{
+          createMessage.error(`请输入必填项`);
+        })
+      })
+    }
   });
 
-  function handleChange(list: string[]) {
-    createMessage.info(`已上传文件${JSON.stringify(list)}`);
-  }
+  // resultFields 字段示例
+  const [registerCustom,{getFieldsValue:getFieldsValueCustom}] = useForm({
+    labelWidth: 160,
+    schemas:schemasCustom,
+    actionColOptions: {
+      span: 18,
+    },
+    submitFunc:()=>{
+      return new Promise((resolve)=>{
+        console.log(getFieldsValueCustom())
+        resolve()
+        createMessage.success(`请到控制台查看结果`);
+      })
+    }
+  });
+
+  // registerPreview
+  const [registerPreview,{getFieldsValue:getFieldsValuePreview}] = useForm({
+    labelWidth: 160,
+    schemas:schemasPreview,
+    actionColOptions: {
+      span: 18,
+    },
+    submitFunc:()=>{
+      return new Promise((resolve)=>{
+        console.log(getFieldsValuePreview())
+        resolve()
+        createMessage.success(`请到控制台查看结果`);
+      })
+    }
+  });
+
+
 </script>