Prechádzať zdrojové kódy

新增文件上传组件

tongshangming 2 rokov pred
rodič
commit
f3e215e727

+ 1 - 0
src/components.d.ts

@@ -26,6 +26,7 @@ declare module '@vue/runtime-core' {
     ElDict: typeof import('./components/form/ElDict.vue')['default']
     ElEditor: typeof import('./components/form/ElEditor.vue')['default']
     ElEmployees: typeof import('./components/form/ElEmployees.vue')['default']
+    ElFileUpload: typeof import('./components/form/ElFileUpload.vue')['default']
     ElFormCard: typeof import('./components/form/ElFormCard.vue')['default']
     ElFormGroup: typeof import('./components/form/ElFormGroup.vue')['default']
     ElFormLayout: typeof import('./components/form/ElFormLayout.vue')['default']

+ 26 - 9
src/components/core/form/BasicForm.vue

@@ -17,8 +17,17 @@ const formData = computed(() => {
       if (['upload', 'image-upload'].includes(item.type) && !Array.isArray(props.formData[item.name])) {
         props.formData[item.name] = props.formData[item.name]
           .split(',')
-          .filter((item: any) => item)
-          .map((item: any) => ({ url: item.props?.oss ?? config.oss ? config.ossHost + item : item }))
+          .filter((item: string) => item)
+          .map((item: string) => ({ url: config.oss ? config.ossHost + item : item }))
+      } else if (item.type === 'file-upload' && typeof props.formData[item.name] === 'string') {
+        try {
+          props.formData[item.name] = JSON.parse(props.formData[item.name] || '[]').map((item: any) => ({
+            url: config.oss ? config.ossHost + item.url : item.url,
+            name: item.name
+          }))
+        } catch (error) {
+          props.formData[item.name] = []
+        }
       }
     })
     return props.formData
@@ -30,7 +39,16 @@ const formData = computed(() => {
         if (!item.notFormItem || !notFormItem.includes(item.type)) {
           // 将上传的值改成数组
           if (['upload', 'image-upload'].includes(item.type) && !Array.isArray(item.value)) {
-            item.value = item.value?.split(',').filter((item: any) => item) || []
+            item.value = item.value?.split(',').filter((item: string) => item) || []
+          } else if (item.type === 'file-upload' && typeof item.value === 'string') {
+            try {
+              item.value = JSON.parse(item.value || '[]').map((item: any) => ({
+                url: config.oss ? config.ossHost + item.url : item.url,
+                name: item.name
+              }))
+            } catch (error) {
+              item.value = []
+            }
           }
           // 避免修改当前表单项value重置其他表单项的value
           res[item.name] = res[item.name] !== undefined && item.value !== undefined ? res[item.name] : item.value
@@ -43,18 +61,17 @@ const formData = computed(() => {
   }
 })
 
+const formItems = computed(() => {
+  return props.formConfig.formItems.filter(item => !item.hidden)
+})
+
 // 构造表单容器插槽
 buildContainerSlots(props.formConfig.formItems)
 </script>
 
 <template>
   <el-row :gutter="20">
-    <el-col
-      :span="item.span || formConfig.span || 12"
-      v-for="(item, index) in formConfig.formItems"
-      :key="index"
-      v-show="!item.hidden"
-    >
+    <el-col :span="item.span || formConfig.span || 12" v-for="(item, index) in formItems" :key="index">
       <component
         :is="'el-' + item.type"
         v-if="containerTypes.includes(item.type)"

+ 1 - 7
src/components/core/form/FormComp.vue

@@ -62,7 +62,7 @@ const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
   }
   return true
 }
-// 图片上传成功
+// 上传成功
 const handleUploadSuccess: UploadProps['onSuccess'] = (response, uploadFile: UploadFile) => {
   if (response.success || response.code === 200) {
     // 附加oss路径
@@ -121,12 +121,6 @@ if (props.item.type === 'upload') {
     v-bind="{ ...item.props, ...uploadAttrs }"
     v-on="item.events || {}"
   >
-    <template #[slot.name]="slotProps" v-for="slot in item.slots" :key="slot.alias">
-      <slot :name="slot.alias" v-bind="slotProps">
-        <el-icon v-if="item.props['list-type'] === 'picture-card'"><Plus /></el-icon>
-        <el-button type="primary" v-else>上传</el-button>
-      </slot>
-    </template>
   </el-upload>
 
   <div v-else-if="item.type === 'form-html'" v-html="item.props?.html"></div>

+ 3 - 0
src/components/core/form/ProForm.vue

@@ -45,6 +45,9 @@ const submit = async () => {
           } else {
             data[item.name] = data[item.name].join(',')
           }
+        } else if (item.type === 'file-upload') {
+          console.log(item.extra)
+          data[item.name] = JSON.stringify(item.extra)
         }
       })
       if (data.id) {

+ 88 - 0
src/components/form/ElFileUpload.vue

@@ -0,0 +1,88 @@
+<script lang="ts" setup>
+import { useUserStore } from '@/stores/user'
+import { ACCESS_TOKEN } from '@/utils/constants'
+import { ElMessage } from 'element-plus'
+import { isAbsolutePath, ossUpload } from '@/utils/utils'
+import config from '@/config/defaultSetting'
+import type { UploadProps, UploadFile } from 'element-plus'
+import type { BasicFormItem } from '@/types/form'
+
+interface Props {
+  item: BasicFormItem
+  modelValue: any
+  fileSize?: number | string
+  uploadApi?: string
+  oss?: boolean
+}
+const props = withDefaults(defineProps<Props>(), {
+  uploadApi: config.uploadApi,
+  fileSize: 100,
+  oss: config.oss
+})
+const emits = defineEmits(['update:modelValue'])
+
+const modelValue = computed({
+  get: () => props.modelValue,
+  set: value => emits('update:modelValue', value)
+})
+
+const user = useUserStore()
+const baseApi = import.meta.env.VITE_BASE_API
+const headers = reactive({
+  [ACCESS_TOKEN]: user.token
+})
+
+// 在上传之前对文件进行验证
+const fileSize = Number(props.fileSize) || 100
+const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
+  if (rawFile.size / 1024 / 1024 > fileSize) {
+    ElMessage.error(`文件大小不能超过${fileSize}MB!`)
+    return false
+  }
+  return true
+}
+
+// 上传成功
+const handleUploadSuccess: UploadProps['onSuccess'] = (response, uploadFile: UploadFile) => {
+  if (response.success || response.code === 200) {
+    // 附加oss路径
+    modelValue.value.forEach((item: any) => {
+      if (item.uid === uploadFile.uid) {
+        item.extra = {
+          url: props.oss ? response.url : config.uploadSuccessCb(response),
+          name: uploadFile.name
+        }
+      }
+    })
+  } else {
+    ElMessage.error(response.msg)
+  }
+}
+
+// 接口需要的属性
+props.item.extra = computed(() => modelValue.value.map((item: any) => item.extra))
+
+// 动态属性
+const attrs: any = props.oss ? { 'http-request': ossUpload } : {}
+if (!props.item.props?.onSuccess) {
+  attrs['on-success'] = handleUploadSuccess
+}
+</script>
+
+<template>
+  <el-upload
+    v-model:file-list="modelValue"
+    :action="isAbsolutePath(props.uploadApi) ? props.uploadApi : baseApi + props.uploadApi"
+    :headers="headers"
+    :before-upload="beforeUpload"
+    v-bind="{ ...attrs, ...$attrs }"
+  >
+    <template #[slot.name]="slotProps" v-for="slot in item.slots" :key="slot.alias">
+      <slot :name="slot.alias" v-bind="slotProps">
+        <el-button type="primary">上传</el-button>
+      </slot>
+    </template>
+  </el-upload>
+</template>
+
+<style lang="scss" scoped></style>

+ 6 - 8
src/components/form/ElFormLayout.vue

@@ -7,18 +7,16 @@ interface Props {
   formConfig: BasicForm
   formData: any
 }
-defineProps<Props>()
+const props = defineProps<Props>()
+
+const children = computed(() => {
+  return props.formItem.children?.filter(item => !item.hidden)
+})
 </script>
 
 <template>
   <el-row :gutter="20">
-    <el-col
-      :span="item.span"
-      v-bind="item.props"
-      v-for="(item, index) in formItem.children"
-      :key="index"
-      v-show="!item.hidden"
-    >
+    <el-col :span="item.span" v-bind="item.props" v-for="(item, index) in children" :key="index">
       <component
         :is="'el-' + item.type"
         v-if="containerTypes.includes(item.type)"

+ 1 - 1
src/components/form/ElImageUpload.vue

@@ -83,7 +83,7 @@ const closeViewer = () => {
 // 接口需要的属性
 props.item.extra = computed(() => modelValue.value.map((item: any) => item.extra))
 
-//
+// 动态属性
 const attrs: any = props.oss ? { 'http-request': ossUpload } : {}
 if (!props.item.props?.onSuccess) {
   attrs['on-success'] = handleUploadSuccess

+ 3 - 9
src/views/form/Basic.vue

@@ -243,7 +243,7 @@ const formConfig = reactive<BasicForm>({
     },
     {
       label: '图片',
-      value: '',
+      value: [],
       name: 'image',
       type: 'image-upload'
     },
@@ -259,15 +259,9 @@ const formConfig = reactive<BasicForm>({
     },
     {
       label: '文件',
-      value: [],
+      value: '',
       name: 'file',
-      type: 'upload',
-      props: {
-        multiple: true,
-        'on-success': (res: any) => {
-          console.log(res)
-        }
-      },
+      type: 'file-upload',
       slots: [
         {
           name: 'default',

+ 40 - 6
src/views/system/User.vue

@@ -20,6 +20,33 @@ const CRUD: ICRUD = {
     return saveUser(data)
   },
   getList(data: any) {
+    return Promise.resolve({
+      list: [
+        {
+          id: 1,
+          name: 'admin',
+          loginName: 'admin',
+          roleList: [
+            {
+              id: 1,
+              name: '管理员'
+            }
+          ],
+          file: ''
+        },
+        {
+          id: 2,
+          name: 'test',
+          loginName: 'test',
+          roleList: [
+            {
+              id: 1,
+              name: '管理员'
+            }
+          ]
+        }
+      ]
+    })
     return getUserList(data)
   },
   delete(data: any) {
@@ -44,6 +71,18 @@ const duplicate = (rule: any, value: any, callback: any) => {
 }
 const formConfig = reactive<BasicForm>({
   formItems: [
+    {
+      label: '文件',
+      value: '',
+      name: 'file',
+      type: 'file-upload',
+      slots: [
+        {
+          name: 'default',
+          alias: 'file'
+        }
+      ]
+    },
     {
       label: '用户名',
       value: '',
@@ -128,12 +167,7 @@ const handleEdit = (row: any) => {
 </script>
 
 <template>
-  <pro-table
-    :crud="CRUD"
-    :formConfig="formConfig"
-    @click-create="handleCreate"
-    @click-edit="handleEdit"
-  >
+  <pro-table :crud="CRUD" :formConfig="formConfig" @click-create="handleCreate" @click-edit="handleEdit">
     <vxe-column field="name" title="用户名"></vxe-column>
     <vxe-column field="loginName" title="登录名"></vxe-column>
     <vxe-column field="phone" title="手机号"></vxe-column>