Browse Source

feat: add upload component

xingyu 2 years ago
parent
commit
731e49d7b6

+ 11 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm

@@ -77,6 +77,17 @@ const crudSchemas = reactive<VxeCrudSchema>({
         component: 'InputNumber',
         value: 0
       },
+      #elseif($column.htmlType == "imageUpload")## 图片上传
+      form: {
+        component: 'UploadImg',
+        componentProps: {
+          limit: 1
+        }
+      },
+      #elseif($column.htmlType == "fileUpload")## 图片上传
+      form: {
+        component: 'UploadFile'
+      },
       #end
       #end
       #if ($column.listOperation)

+ 4 - 1
yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts

@@ -21,6 +21,7 @@ import {
 } from 'element-plus'
 import { InputPassword } from '@/components/InputPassword'
 import { Editor } from '@/components/Editor'
+import { UploadImg, UploadFile } from '@/components/UploadFile'
 import { ComponentName } from '@/types/components'
 
 const componentMap: Recordable<Component, ComponentName> = {
@@ -45,7 +46,9 @@ const componentMap: Recordable<Component, ComponentName> = {
   TreeSelect: ElTreeSelect,
   RadioButton: ElRadioGroup,
   InputPassword: InputPassword,
-  Editor: Editor
+  Editor: Editor,
+  UploadImg: UploadImg,
+  UploadFile: UploadFile
 }
 
 export { componentMap }

+ 2 - 1
yudao-ui-admin-vue3/src/components/UploadFile/index.ts

@@ -1,3 +1,4 @@
 import UploadImg from './src/UploadImg.vue'
+import UploadFile from './src/UploadFile.vue'
 
-export { UploadImg }
+export { UploadImg, UploadFile }

+ 167 - 0
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue

@@ -0,0 +1,167 @@
+<template>
+  <div class="upload-file">
+    <el-upload
+      ref="uploadRef"
+      :multiple="props.limit > 1"
+      name="file"
+      v-model="valueRef"
+      :file-list="fileList"
+      :show-file-list="false"
+      :action="updateUrl"
+      :headers="uploadHeaders"
+      :limit="props.limit"
+      :before-upload="beforeUpload"
+      :on-exceed="handleExceed"
+      :on-success="handleFileSuccess"
+      :on-error="excelUploadError"
+      :on-remove="handleRemove"
+      class="upload-file-uploader"
+    >
+      <Icon icon="ep:upload-filled" />
+    </el-upload>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { useMessage } from '@/hooks/web/useMessage'
+import { propTypes } from '@/utils/propTypes'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
+
+const message = useMessage() // 消息弹窗
+const emit = defineEmits(['update:modelValue'])
+
+const props = defineProps({
+  modelValue: propTypes.oneOfType([String, Object, Array]),
+  title: propTypes.string.def('文件上传'),
+  updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
+  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
+  fileSize: propTypes.number.def(5), // 大小限制(MB)
+  limit: propTypes.number.def(5), // 数量限制
+  isShowTip: propTypes.bool.def(false) // 是否显示提示
+})
+// ========== 上传相关 ==========
+const valueRef = ref(props.modelValue)
+const uploadRef = ref<UploadInstance>()
+const uploadList = ref<UploadUserFile[]>([])
+const fileList = ref<UploadUserFile[]>([])
+const uploadNumber = ref<number>(0)
+const uploadHeaders = ref({
+  Authorization: 'Bearer ' + getAccessToken(),
+  'tenant-id': getTenantId()
+})
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
+      const list = Array.isArray(props.modelValue)
+        ? props.modelValue
+        : Array.isArray(props.modelValue?.split(','))
+        ? props.modelValue?.split(',')
+        : Array.of(props.modelValue)
+      // 然后将数组转为对象数组
+      fileList.value = list.map((item) => {
+        if (typeof item === 'string') {
+          // edit by 芋道源码
+          item = { name: item, url: item }
+        }
+        return item
+      })
+    } else {
+      fileList.value = []
+      return []
+    }
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+// 文件上传之前判断
+const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
+  if (fileList.value.length >= props.limit) {
+    message.error(`上传文件数量不能超过${props.limit}个!`)
+    return false
+  }
+  let fileExtension = ''
+  if (file.name.lastIndexOf('.') > -1) {
+    fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
+  }
+  const isImg = props.fileType.some((type: string) => {
+    if (file.type.indexOf(type) > -1) return true
+    return !!(fileExtension && fileExtension.indexOf(type) > -1)
+  })
+  const isLimit = file.size < props.fileSize * 1024 * 1024
+  if (!isImg) {
+    message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
+    return false
+  }
+  if (!isLimit) {
+    message.error(`上传文件大小不能超过${props.fileSize}MB!`)
+    return false
+  }
+  message.success('正在上传文件,请稍候...')
+  uploadNumber.value++
+}
+// 处理上传的文件发生变化
+// const handleFileChange = (uploadFile: UploadFile): void => {
+//   uploadRef.value.data.path = uploadFile.name
+// }
+// 文件上传成功
+const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
+  message.success('上传成功')
+  uploadList.value.push({ name: res.data, url: res.data })
+  if (uploadList.value.length == uploadNumber.value) {
+    fileList.value = fileList.value.concat(uploadList.value)
+    uploadList.value = []
+    uploadNumber.value = 0
+    emit('update:modelValue', listToString(fileList.value))
+  }
+}
+// 文件数超出提示
+const handleExceed: UploadProps['onExceed'] = (): void => {
+  message.error(`上传文件数量不能超过${props.limit}个!`)
+}
+// 上传错误提示
+const excelUploadError: UploadProps['onError'] = (): void => {
+  message.error('导入数据失败,请您重新上传!')
+}
+// 删除上传文件
+const handleRemove = (file) => {
+  const findex = fileList.value.map((f) => f.name).indexOf(file.name)
+  if (findex > -1) {
+    fileList.value.splice(findex, 1)
+    emit('update:modelValue', listToString(fileList.value))
+  }
+}
+// 对象转成指定字符串分隔
+const listToString = (list: UploadUserFile[], separator?: string) => {
+  let strs = ''
+  separator = separator || ','
+  for (let i in list) {
+    strs += list[i].url + separator
+  }
+  return strs != '' ? strs.substr(0, strs.length - 1) : ''
+}
+</script>
+<style scoped lang="scss">
+.upload-file-uploader {
+  margin-bottom: 5px;
+}
+.upload-file-list .el-upload-list__item {
+  border: 1px solid #e4e7ed;
+  line-height: 2;
+  margin-bottom: 10px;
+  position: relative;
+}
+.upload-file-list .ele-upload-list__item-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: inherit;
+}
+.ele-upload-list__item-content-action .el-link {
+  margin-right: 10px;
+}
+</style>

+ 38 - 32
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImg.vue

@@ -1,24 +1,27 @@
 <template>
-  <el-upload
-    ref="uploadRef"
-    :multiple="limit > 1"
-    name="file"
-    list-type="picture-card"
-    v-model:file-list="fileList"
-    :show-file-list="true"
-    :action="updateUrl"
-    :headers="uploadHeaders"
-    :limit="limit"
-    :before-upload="beforeUpload"
-    :on-exceed="handleExceed"
-    :on-success="handleFileSuccess"
-    :on-error="excelUploadError"
-    :on-remove="handleRemove"
-    :on-preview="handlePictureCardPreview"
-    :class="{ hide: fileList.length >= limit }"
-  >
-    <Icon icon="ep:upload-filled" />
-  </el-upload>
+  <div class="component-upload-image">
+    <el-upload
+      ref="uploadRef"
+      :multiple="props.limit > 1"
+      name="file"
+      v-model="valueRef"
+      list-type="picture-card"
+      :file-list="fileList"
+      :show-file-list="true"
+      :action="updateUrl"
+      :headers="uploadHeaders"
+      :limit="props.limit"
+      :before-upload="beforeUpload"
+      :on-exceed="handleExceed"
+      :on-success="handleFileSuccess"
+      :on-error="excelUploadError"
+      :on-remove="handleRemove"
+      :on-preview="handlePictureCardPreview"
+      :class="{ hide: fileList.length >= props.limit }"
+    >
+      <Icon icon="ep:upload-filled" />
+    </el-upload>
+  </div>
   <!-- 文件列表 -->
   <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
     <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
@@ -32,10 +35,10 @@ import { getAccessToken, getTenantId } from '@/utils/auth'
 import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
 
 const message = useMessage() // 消息弹窗
-const emit = defineEmits(['input'])
+const emit = defineEmits(['update:modelValue'])
 
 const props = defineProps({
-  imgs: propTypes.oneOfType([String, Object, Array]),
+  modelValue: propTypes.oneOfType([String, Object, Array]),
   title: propTypes.string.def('图片上传'),
   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
   fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // 文件类型, 例如['png', 'jpg', 'jpeg']
@@ -44,6 +47,7 @@ const props = defineProps({
   isShowTip: propTypes.bool.def(false) // 是否显示提示
 })
 // ========== 上传相关 ==========
+const valueRef = ref(props.modelValue)
 const uploadRef = ref<UploadInstance>()
 const uploadList = ref<UploadUserFile[]>([])
 const fileList = ref<UploadUserFile[]>([])
@@ -55,15 +59,15 @@ const uploadHeaders = ref({
   'tenant-id': getTenantId()
 })
 watch(
-  () => props.imgs,
+  () => props.modelValue,
   (val) => {
     if (val) {
       // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
-      const list = Array.isArray(props.imgs)
-        ? props.imgs
-        : Array.isArray(props.imgs?.split(','))
-        ? props.imgs?.split(',')
-        : Array.of(props.imgs)
+      const list = Array.isArray(props.modelValue)
+        ? props.modelValue
+        : Array.isArray(props.modelValue?.split(','))
+        ? props.modelValue?.split(',')
+        : Array.of(props.modelValue)
       // 然后将数组转为对象数组
       fileList.value = list.map((item) => {
         if (typeof item === 'string') {
@@ -84,6 +88,10 @@ watch(
 )
 // 文件上传之前判断
 const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
+  if (fileList.value.length >= props.limit) {
+    message.error(`上传文件数量不能超过${props.limit}个!`)
+    return false
+  }
   let fileExtension = ''
   if (file.name.lastIndexOf('.') > -1) {
     fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
@@ -111,14 +119,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
 // 文件上传成功
 const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
   message.success('上传成功')
-  console.info(uploadList.value)
-  console.info(fileList.value)
   uploadList.value.push({ name: res.data, url: res.data })
   if (uploadList.value.length == uploadNumber.value) {
     fileList.value = fileList.value.concat(uploadList.value)
     uploadList.value = []
     uploadNumber.value = 0
-    emit('input', listToString(fileList.value))
+    emit('update:modelValue', listToString(fileList.value))
   }
 }
 // 文件数超出提示
@@ -134,7 +140,7 @@ const handleRemove = (file) => {
   const findex = fileList.value.map((f) => f.name).indexOf(file.name)
   if (findex > -1) {
     fileList.value.splice(findex, 1)
-    emit('input', listToString(fileList.value))
+    emit('update:modelValue', listToString(fileList.value))
   }
 }
 // 对象转成指定字符串分隔

+ 2 - 0
yudao-ui-admin-vue3/src/types/components.d.ts

@@ -21,6 +21,8 @@ export type ComponentName =
   | 'TreeSelect'
   | 'InputPassword'
   | 'Editor'
+  | 'UploadImg'
+  | 'UploadFile'
 
 export type ColProps = {
   span?: number

+ 6 - 0
yudao-ui-admin-vue3/src/views/system/oauth2/client/client.data.ts

@@ -41,6 +41,12 @@ const crudSchemas = reactive<VxeCrudSchema>({
         cellRender: {
           name: 'XImg'
         }
+      },
+      form: {
+        component: 'UploadImg',
+        componentProps: {
+          limit: 1
+        }
       }
     },
     {

+ 4 - 9
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue

@@ -61,16 +61,12 @@
       v-if="['create', 'update'].includes(actionType)"
       :schema="allSchemas.formSchema"
       :rules="rules"
-    >
-      <template #logo="form">
-        <UploadImg :imgs="form['logo']" :limit="1" />
-      </template>
-    </Form>
+    />
     <!-- 表单:详情 -->
     <Descriptions
       v-if="actionType === 'detail'"
       :schema="allSchemas.detailSchema"
-      :data="detailRef"
+      :data="detailData"
     >
       <template #accessTokenValiditySeconds="{ row }">
         {{ row.accessTokenValiditySeconds + '秒' }}
@@ -142,7 +138,6 @@ import { useMessage } from '@/hooks/web/useMessage'
 import { useVxeGrid } from '@/hooks/web/useVxeGrid'
 import { VxeGridInstance } from 'vxe-table'
 import { FormExpose } from '@/components/Form'
-import { UploadImg } from '@/components/UploadFile'
 // 业务相关的 import
 import * as ClientApi from '@/api/system/oauth2/client'
 import { rules, allSchemas } from './client.data'
@@ -163,7 +158,7 @@ const dialogTitle = ref('edit') // 弹出层标题
 const actionType = ref('') // 操作按钮的类型
 const actionLoading = ref(false) // 按钮 Loading
 const formRef = ref<FormExpose>() // 表单 Ref
-const detailRef = ref() // 详情 Ref
+const detailData = ref() // 详情 Ref
 // 设置标题
 const setDialogTile = (type: string) => {
   dialogTitle.value = t('action.' + type)
@@ -188,7 +183,7 @@ const handleUpdate = async (rowId: number) => {
 const handleDetail = async (rowId: number) => {
   setDialogTile('detail')
   const res = await ClientApi.getOAuth2ClientApi(rowId)
-  detailRef.value = res
+  detailData.value = res
 }
 
 // 删除操作