Selaa lähdekoodia

图片oss上传

tongshangming 2 vuotta sitten
vanhempi
commit
5c20ee2148

+ 2 - 2
.env.development

@@ -1,4 +1,4 @@
 NODE_ENV = 'development'
 
-VITE_BASE_API = /apiSys
-VITE_BASE_PATH = http://10.8.8.100:10028
+VITE_BASE_API = /mbwb/api
+VITE_BASE_PATH = http://10.0.0.198:30701

+ 5 - 0
src/api/oss.ts

@@ -0,0 +1,5 @@
+import request from '@/utils/request'
+
+export function ossPolicy() {
+  return request.post('v1/oss/policy')
+}

+ 7 - 1
src/components/core/ProTable.vue

@@ -43,11 +43,16 @@ const slots = useSlots()
 
 // ============== 查询部分开始 ===============
 const query = ref<any>({})
+const defaultQuery = ref<any>({})
 const searchList = ref<any>([])
 // 构造搜索列表
 const buildSearchList = (item: BasicFormItem) => {
   if (item.search) {
     searchList.value.push(Object.assign({}, item, { props: { ...item.props, disabled: false } }))
+    if (item.value) {
+      query.value[item.name] = item.value
+      defaultQuery.value[item.name] = item.value
+    }
   }
   item.children && item.children.forEach(buildSearchList)
 }
@@ -67,7 +72,7 @@ const handleQuery = () => {
   getTableData()
 }
 const handleReset = () => {
-  query.value = {}
+  query.value = { ...defaultQuery.value }
   handleQuery()
 }
 // ============== 查询部分结束 ===============
@@ -143,6 +148,7 @@ const handleCreate = () => {
     router.push(formRoute.value)
   } else {
     formData.value = {}
+    props.formConfig.disabled = false
     dialogVisible.value = true
   }
 }

+ 12 - 2
src/components/core/form/BasicForm.vue

@@ -2,6 +2,7 @@
 import type { BasicForm, BasicFormItem, FormSlot } from '@/types/form'
 import { containerTypes, notFormItem } from '@/utils/constants'
 import { buildContainerSlots } from '@/utils/utils'
+import config from '@/config/defaultSetting'
 
 interface Props {
   formConfig: BasicForm
@@ -11,6 +12,15 @@ const props = defineProps<Props>()
 
 const formData = computed(() => {
   if (props.formData.id) {
+    // 将上传的值改成数组
+    props.formConfig.formItems.forEach(item => {
+      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 }))
+      }
+    })
     return props.formData
   } else {
     // 构造表单值
@@ -19,8 +29,8 @@ const formData = computed(() => {
       formItems.forEach(item => {
         if (!item.notFormItem || !notFormItem.includes(item.type)) {
           // 将上传的值改成数组
-          if (item.type === 'upload' && !Array.isArray(item.value)) {
-            item.value = item.value.split(',')
+          if (['upload', 'image-upload'].includes(item.type) && !Array.isArray(item.value)) {
+            item.value = item.value?.split(',').filter((item: any) => item) || []
           }
           // 避免修改当前表单项value重置其他表单项的value
           res[item.name] = res[item.name] !== undefined && item.value !== undefined ? res[item.name] : item.value

+ 33 - 12
src/components/core/form/FormComp.vue

@@ -1,13 +1,12 @@
 <!-- eslint-disable vue/no-mutating-props -->
 <script setup lang="ts">
 // @ts-nocheck
-
 import type { BasicFormItem } from '@/types/form'
-import type { UploadProps } from 'element-plus'
+import type { UploadProps, UploadFile } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import { useUserStore } from '@/stores/user'
 import { ACCESS_TOKEN } from '@/utils/constants'
-import { isAbsolutePath } from '@/utils/utils'
+import { isAbsolutePath, ossUpload } from '@/utils/utils'
 import config from '@/config/defaultSetting'
 
 interface Props {
@@ -49,22 +48,43 @@ const headers = reactive({
 
 let uploadApi = baseApi + config.uploadApi
 const uploadProps = props.item.props
+const oss = uploadProps?.oss ?? config.oss
+
 if (uploadProps?.action) {
   uploadApi = isAbsolutePath(uploadProps.action) ? uploadProps.action : baseApi + uploadProps.action
 }
 
-const fileSize = Number(uploadProps?.fileSize) || 10
-const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
+const fileSize = Number(uploadProps?.fileSize) || 50
+const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
   if (rawFile.size / 1024 / 1024 > fileSize) {
-    ElMessage.error(`图片大小不能超过${fileSize}MB!`)
+    ElMessage.error(`文件大小不能超过${fileSize}MB!`)
     return false
   }
   return true
 }
-// 图片上传
-// const handleUploadSuccess: UploadProps['onSuccess'] = response => {
-//   modelValue.value = config.uploadSuccessCb(response)
-// }
+// 图片上传成功
+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 = oss ? response.url : config.uploadSuccessCb(response)
+      }
+    })
+  } else {
+    ElMessage.error(response.msg)
+  }
+}
+
+const uploadAttrs: any = oss ? { 'http-request': ossUpload } : {}
+if (!uploadProps?.onSuccess) {
+  uploadAttrs['on-success'] = handleUploadSuccess
+}
+
+// 上传接口需要的属性
+if (props.item.type === 'upload') {
+  props.item.extra = computed(() => modelValue.value.map((item: any) => item.extra))
+}
 </script>
 
 <template>
@@ -96,9 +116,9 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
     v-else-if="item.type === 'upload'"
     v-model:file-list="modelValue"
     :action="uploadApi"
-    :before-upload="beforeAvatarUpload"
+    :before-upload="beforeUpload"
     :headers="headers"
-    v-bind="item.props"
+    v-bind="{ ...item.props, ...uploadAttrs }"
     v-on="item.events || {}"
   >
     <template #[slot.name]="slotProps" v-for="slot in item.slots" :key="slot.alias">
@@ -114,6 +134,7 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
   <component
     v-else
     :is="'el-' + item.type"
+    :item="item"
     v-model="modelValue"
     v-bind="item.props"
     :placeholder="item.placeholder || placeholder(item)"

+ 6 - 2
src/components/core/form/ProForm.vue

@@ -39,8 +39,12 @@ const submit = async () => {
       const data = { ...formInitData.value }
       // 将上传的值改成逗号隔开的字符串
       props.formConfig.formItems.forEach(item => {
-        if (item.type === 'upload') {
-          data[item.name] = data[item.name].join(',')
+        if (item.type === 'upload' || item.type === 'image-upload') {
+          if (item.extra) {
+            data[item.name] = item.extra.join(',')
+          } else {
+            data[item.name] = data[item.name].join(',')
+          }
         }
       })
       if (data.id) {

+ 73 - 20
src/components/form/ElImageUpload.vue

@@ -2,22 +2,28 @@
 import { useUserStore } from '@/stores/user'
 import { ACCESS_TOKEN } from '@/utils/constants'
 import { ElMessage } from 'element-plus'
-import type { UploadProps } from 'element-plus'
-import { isAbsolutePath } from '@/utils/utils'
+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
   size?: number | string
   iconSize?: number | string
   fileSize?: number | string
   uploadApi?: string
+  oss?: boolean
+  limit?: number
 }
 const props = withDefaults(defineProps<Props>(), {
   size: '148px',
   iconSize: '28px',
   uploadApi: config.uploadApi,
-  fileSize: 10
+  fileSize: 10,
+  oss: config.oss,
+  limit: 1
 })
 const emits = defineEmits(['update:modelValue'])
 
@@ -32,8 +38,12 @@ const headers = reactive({
   [ACCESS_TOKEN]: user.token
 })
 
+const limit = Number(props.limit) || 1
+
+const uploadRef = ref()
+// 在上传之前对文件进行验证
 const fileSize = Number(props.fileSize) || 10
-const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
+const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
   if (rawFile.size / 1024 / 1024 > fileSize) {
     ElMessage.error(`图片大小不能超过${fileSize}MB!`)
     return false
@@ -41,28 +51,73 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
   return true
 }
 
-// 图片上传
-const handleUploadSuccess: UploadProps['onSuccess'] = response => {
+// 图片上传成功
+const handleUploadSuccess: UploadProps['onSuccess'] = (response, uploadFile: UploadFile) => {
   if (response.success || response.code === 200) {
-    modelValue.value = config.uploadSuccessCb(response)
+    // 附加oss路径
+    modelValue.value.forEach((item: any) => {
+      if (item.uid === uploadFile.uid) {
+        item.extra = props.oss ? response.url : config.uploadSuccessCb(response)
+      }
+    })
   } else {
     ElMessage.error(response.msg)
   }
 }
+// 删除图片
+const handleRemove = (file: UploadFile) => {
+  uploadRef.value.handleRemove(file)
+}
+// 图片预览
+const showViewer = ref(false)
+const previewIndex = ref(0)
+const previewList = computed(() => modelValue.value.map((item: any) => item.url))
+const handlePreview = (file: UploadFile) => {
+  previewIndex.value = modelValue.value.findIndex((item: any) => item.url === file.url)
+  showViewer.value = true
+}
+const closeViewer = () => {
+  showViewer.value = false
+}
+
+// 接口需要的属性
+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
-    :on-success="handleUploadSuccess"
-    :before-upload="beforeAvatarUpload"
+    ref="uploadRef"
+    v-model:file-list="modelValue"
+    :class="{ 'is-disabled': modelValue.length >= limit }"
     :action="isAbsolutePath(props.uploadApi) ? props.uploadApi : baseApi + props.uploadApi"
     :headers="headers"
-    :show-file-list="false"
+    :before-upload="beforeUpload"
+    :limit="limit"
+    list-type="picture-card"
     accept="image/*"
+    v-bind="{ ...attrs, ...$attrs }"
   >
-    <img v-if="modelValue" :src="modelValue" class="avatar" />
-    <el-icon v-else class="avatar-uploader-icon" :style="{ fontSize: iconSize }"><Plus /></el-icon>
+    <el-icon class="avatar-uploader-icon" :style="{ fontSize: iconSize }"><Plus /></el-icon>
+    <template #file="{ file }">
+      <el-image class="el-upload-list__item-thumbnail" :src="file.url" fit="cover" />
+      <span class="el-upload-list__item-actions">
+        <span class="el-upload-list__item-preview" @click="handlePreview(file)">
+          <el-icon><zoom-in /></el-icon>
+        </span>
+
+        <span class="el-upload-list__item-delete" @click="handleRemove(file)">
+          <el-icon><Delete /></el-icon>
+        </span>
+      </span>
+    </template>
   </el-upload>
+  <el-image-viewer v-if="showViewer" :url-list="previewList" :initial-index="previewIndex" @close="closeViewer" />
 </template>
 
 <style lang="scss" scoped>
@@ -79,14 +134,12 @@ const handleUploadSuccess: UploadProps['onSuccess'] = response => {
   &:hover {
     border-color: var(--el-color-primary);
   }
-
-  img {
-    width: 100%;
-    height: 100%;
-    object-fit: contain;
-  }
 }
-.el-icon.avatar-uploader-icon {
-  color: #8c939d;
+:deep(.el-upload-list--picture-card .el-upload-list__item) {
+  width: v-bind(size);
+  height: v-bind(size);
+}
+.is-disabled :deep(.el-upload--picture-card) {
+  display: none;
 }
 </style>

+ 2 - 0
src/config/defaultSetting.ts

@@ -8,5 +8,7 @@ export default {
   uploadApi: '/file/upload',
   showTabs: true, // 显示标签页
   keepAliveTabs: true, // 标签页持久化
+  oss: true,
+  ossHost: 'https://fskj-res.oss-cn-zhangjiakou.aliyuncs.com/',
   uploadSuccessCb: (res: any) => import.meta.env.VITE_BASE_PATH + res.data
 }

+ 3 - 1
src/stores/user.ts

@@ -6,7 +6,9 @@ export const useUserStore = defineStore({
   state: () => ({
     user: {},
     flag: false,
-    token: localStorage.getItem('token') || 't'
+    token:
+      localStorage.getItem('token') ||
+      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiI1NjQ0MzBmMDNhYWU0NmExODE2YmU1YmI4NDExY2RmNiIsImV4cCI6MTcwMDUzMDE0MiwiaWF0IjoxNzAwNDQzNzQyfQ.zPp2URyuC6Wv0CquFKz8Pwq5eyittxRSFcUQx0g0Pdo'
   }),
   actions: {
     async getUserInfo() {

+ 1 - 0
src/types/form.ts

@@ -24,6 +24,7 @@ export type BasicFormItem = {
   slots?: Array<FormSlot>
   notFormItem?: boolean
   hidden?: boolean
+  extra?: any
 }
 
 export type AdvancedFormItem = {

+ 1 - 1
src/utils/constants.ts

@@ -80,7 +80,7 @@ export const themeNavList = [
   }
 ]
 
-export const ACCESS_TOKEN = 'access_token'
+export const ACCESS_TOKEN = 'Authorization'
 
 export const containerTypes = [
   'form-tabs',

+ 2 - 2
src/utils/request.ts

@@ -37,7 +37,7 @@ const errorHandler = (error: any) => {
 
 request.interceptors.request.use(config => {
   const userStore = useUserStore()
-  const token = userStore.token
+  const token = 'Bearer ' + userStore.token
   config.headers = config.headers || {}
 
   if (token) {
@@ -50,7 +50,7 @@ request.interceptors.request.use(config => {
 request.interceptors.response.use(res => {
   const data = res.data
   if (data.success || data.code === 200) {
-    return data.data
+    return data
   } else {
     if (data.code === 201 && data.msg === '登录超时,请登录') {
       const userStore = useUserStore()

+ 45 - 0
src/utils/utils.ts

@@ -2,6 +2,7 @@ import dayjs from 'dayjs'
 import type { BasicFormItem, FormSlot } from '@/types/form'
 import { useFormDesignerStore } from '@/stores/designer'
 import { containerTypes } from './constants'
+import { ossPolicy } from '@/api/oss'
 
 export const formatDate = (date: any, format = 'YYYY-MM-DD HH:mm') => {
   return dayjs(date).format(format)
@@ -101,3 +102,47 @@ export const buildContainerSlots = (formItems: Array<BasicFormItem>) => {
     }
   })
 }
+
+// oss上传
+export const ossUpload = (param: any) => {
+  const { file } = param
+
+  return ossPolicy()
+    .then(async (res: any) => {
+      const id = file.uid
+      const type = file.type.split('/')[1]
+      const ossKey = res.dir + id + '.' + type
+
+      const formData = new FormData()
+      formData.append('OSSAccessKeyId', res.accessKeyId)
+      formData.append('policy', res.policy)
+      formData.append('Signature', res.signature)
+      formData.append('success_action_status', '200')
+      formData.append('key', ossKey)
+      formData.append('name', id + '.' + type)
+      formData.append('file', file)
+
+      const data = await fetch(res.host, {
+        method: 'post',
+        body: formData
+      })
+      if (data.status == 200) {
+        return {
+          success: true,
+          file: file,
+          url: ossKey
+        }
+      } else {
+        return {
+          success: false,
+          msg: '上传失败'
+        }
+      }
+    })
+    .catch((err: any) => {
+      return {
+        success: false,
+        msg: err.message
+      }
+    })
+}

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

@@ -47,9 +47,9 @@ const formConfig = reactive<BasicForm>({
       name: 'phone',
       type: 'input',
       rules: [
-        {
-          validator: validator.mobile
-        }
+        // {
+        //   validator: validator.mobile
+        // }
       ]
     },
     {
@@ -247,6 +247,16 @@ const formConfig = reactive<BasicForm>({
       name: 'image',
       type: 'image-upload'
     },
+    {
+      label: '文件',
+      value: [],
+      name: 'picture',
+      type: 'image-upload',
+      props: {
+        limit: 9,
+        multiple: true
+      }
+    },
     {
       label: '文件',
       value: [],
@@ -331,6 +341,7 @@ const handleInputConfirm = () => {
         <template #file>
           <el-button type="primary">上传文件</el-button>
         </template>
+
         <template #password>
           <el-input type="password" name="password" v-model="formData.password" placeholder="请输入密码"></el-input>
           <el-text type="info">密码需包含大写字母、小写字母、数字</el-text>