NewLife/NewLife.QuickVue

修改图片上传组件,优化架构
zk 编写于 2024-01-02 15:27:59
共计: 修改9个文件,增加158行、删除68行。
修改 +2 -3
修改 +7 -7
修改 +1 -1
增加 +13 -0
修改 +115 -53
修改 +1 -1
增加 +16 -0
修改 +2 -2
修改 +1 -1
修改 +2 -3
diff --git a/.env.development b/.env.development
index c78524c..61b2c1d 100644
--- a/.env.development
+++ b/.env.development
@@ -2,8 +2,7 @@
 ENV = development
 
 # 本地环境接口地址
-# VITE_API_URL = https://cube3.newlifex.com
-VITE_API_URL = /base-api
+VITE_API_URL = https://cube3.newlifex.com
 
 # 图片前缀地址
-VITE_IMG_BASE_URL = https://rengleme.oss-cn-beijing.aliyuncs.com/
\ No newline at end of file
+VITE_IMG_BASE_URL = http://120.78.152.40:54439
\ No newline at end of file
修改 +7 -7
diff --git a/package-lock.json b/package-lock.json
index 76033f6..1c974ed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,7 @@
 				"echarts": "^5.4.1",
 				"echarts-gl": "^2.0.9",
 				"echarts-wordcloud": "^2.1.0",
-				"element-plus": "^2.2.32",
+				"element-plus": "^2.3.4",
 				"js-cookie": "^3.0.1",
 				"js-table2excel": "^1.0.3",
 				"jsplumb": "^2.15.6",
@@ -2166,9 +2166,9 @@
 			"dev": true
 		},
 		"node_modules/element-plus": {
-			"version": "2.2.32",
-			"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.32.tgz",
-			"integrity": "sha512-DTJMhYOy6MApbmh6z/95hPTK5WrBiNHGzV4IN+uEkup1WoimQ+Qyt8RxKdTe/X1LWEJ8YgWv/Cl8P4ocrt5z5g==",
+			"version": "2.3.4",
+			"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.4.tgz",
+			"integrity": "sha512-SQr0J9z7N4z48WYk/l9NE2tizl8Q7j2OhqlpTc42k4pGncry3+rVX6dsmcsglFynn6vt3NzYxWJqmLFyDKQq+g==",
 			"dependencies": {
 				"@ctrl/tinycolor": "^3.4.1",
 				"@element-plus/icons-vue": "^2.0.6",
@@ -6033,9 +6033,9 @@
 			"dev": true
 		},
 		"element-plus": {
-			"version": "2.2.32",
-			"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.32.tgz",
-			"integrity": "sha512-DTJMhYOy6MApbmh6z/95hPTK5WrBiNHGzV4IN+uEkup1WoimQ+Qyt8RxKdTe/X1LWEJ8YgWv/Cl8P4ocrt5z5g==",
+			"version": "2.3.4",
+			"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.4.tgz",
+			"integrity": "sha512-SQr0J9z7N4z48WYk/l9NE2tizl8Q7j2OhqlpTc42k4pGncry3+rVX6dsmcsglFynn6vt3NzYxWJqmLFyDKQq+g==",
 			"requires": {
 				"@ctrl/tinycolor": "^3.4.1",
 				"@element-plus/icons-vue": "^2.0.6",
修改 +1 -1
diff --git a/package.json b/package.json
index 8951143..8b08b16 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
 		"echarts": "^5.4.1",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.1.0",
-		"element-plus": "^2.2.32",
+		"element-plus": "^2.3.4",
 		"js-cookie": "^3.0.1",
 		"js-table2excel": "^1.0.3",
 		"jsplumb": "^2.15.6",
增加 +13 -0
diff --git a/src/components/upload/enum.ts b/src/components/upload/enum.ts
new file mode 100644
index 0000000..af86bb0
--- /dev/null
+++ b/src/components/upload/enum.ts
@@ -0,0 +1,13 @@
+export enum AcceptEnum {
+  IMAGE = 'image/*',
+  VIDEO = 'video/*',
+  AUDIO = 'audio/*',
+  DOC = '.doc',
+  DOCX = '.docx',
+  PPT = '.ppt',
+  PNG = 'image/png',
+  JPG = 'image/jpg',
+  JPEG = 'image/jpeg',
+  GIF = 'image/gif',
+  APK = 'application/vnd.android.package-archive',
+}
修改 +115 -53
diff --git a/src/components/upload/index.vue b/src/components/upload/index.vue
index bbef816..31354bf 100644
--- a/src/components/upload/index.vue
+++ b/src/components/upload/index.vue
@@ -1,82 +1,152 @@
 <template>
   <el-upload
-    class="avatar-uploader"
+    class="upload-component"
+    v-model:file-list="fileList"
     :action="action"
     :show-file-list="false"
-    :on-success="handleAvatarSuccess"
-    :before-upload="beforeAvatarUpload"
+    :disabled="limit !==1 && fileList.length >= limit"
+    :accept="accept.toString()"
+    :on-success="handleSuccess"
+    :on-exceed="onExceed"
   >
-    <img v-if="imageUrl" :src="baseUrl + imageUrl" class="avatar" />
-    <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+    <draggable v-model="fileList" :item-key="(val: UploadUserFile) => getItemSrc(val)" class="flex flex-wrap" :animation="200">
+      <template #item="{element, index}">
+        <div class="relative mr-2 flex group" @click.stop :key="getItemSrc(element)" v-if="fileList.length <= limit || index >= limit">
+          <el-image
+            fit="cover"
+            class="w-28 h-28 rounded mb-2"
+            :preview-src-list="fileList.map(v => getItemSrc(v))"
+            :initial-index="index"
+            :src="getItemSrc(element)">
+            <template #error>
+              <div class="bg-gray-200 w-full h-full flex justify-center items-center font-bold text-green-500 text-xl flex-col border border-green-500" v-if="element.status === 'success'">
+                <el-icon class="!text-4xl !text-green-500"><CircleCheck /></el-icon>
+                {{ getItemSrc(element).split('.').reverse()[0] }}
+              </div>
+            </template>
+          </el-image>
+          <div class="absolute inset-0 flex justify-center items-center p-4" v-if="element.status === 'ready' || element.status === 'uploading'">
+            <el-progress :text-inside="true" :stroke-width="20" :percentage="element.percentage" class="w-full" status="success"/>
+          </div>
+          <div class="!absolute right-0 top-0 bg-black/80 w-6 h-6 flex justify-center items-center rounded-tr opacity-0 group-hover:opacity-100 duration-75" @click="fileList.splice(index, 1)">
+            <el-icon class="!text-white !text-xl"><Close /></el-icon>
+          </div>
+        </div>
+      </template>
+      <template #footer>
+        <div class="avatar-uploader w-28 h-28 flex justify-center items-center flex-col" key="btn" :class="{'opacity-50': limit !== 1 && fileList.length >= limit}">
+          <template v-if="limit === 1 && fileList.length === 1">
+            <el-icon class="!text-3xl !text-gray-500"><Switch /></el-icon>
+            <div class="text-gray-500 text-sm mt-2">更换</div>
+          </template>
+          <template v-else-if="limit !== 1 && fileList.length >= limit">
+            <Icon class="text-gray-500 text-3xl" icon="system-uicons:no-sign"></Icon>
+            <div class="text-gray-500 text-sm mt-2">(上传数量已满)</div>
+          </template>
+          <template v-else>
+            <el-icon class="!text-3xl !text-gray-500"><Plus /></el-icon>
+            <div class="text-gray-500 text-sm mt-2" v-if="limit > 1">({{fileList.length + ' / ' + limit }})</div>
+          </template>
+        </div>
+      </template>
+    </draggable>
   </el-upload>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue'
-import { ElMessage } from 'element-plus'
-import { Plus } from '@element-plus/icons-vue'
-import type { UploadProps } from 'element-plus'
+
+import { ref } from 'vue'
+import { Plus, Close, CircleCheck, Switch } from '@element-plus/icons-vue'
+import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
+import { AcceptEnum } from './enum';
+import { watch } from 'vue';
+import draggable from 'vuedraggable'
 
 interface Props {
-  modelValue?: string;
+  modelValue?: string | Array<string>;
   maxSize?: number;
   limit?: number;
   url?: string;
   resultKey?: string;
+  accept?: Array<AcceptEnum | string>;
+  modelType?: 'string' | 'array';
 }
 interface Emits {
-  (e: 'update:modelValue', val: string): void
+  (e: 'update:modelValue', val: string | Array<string>): void
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  limit: 1
+  limit: 1,
+  resultKey: 'filePath',
+  modelType: 'string',
+  accept: () => [],
 })
 const emits = defineEmits<Emits>()
 
-const action = import.meta.env.VITE_API_URL + props.url
+const fileList = ref<UploadUserFile[]>([]);
+const action = (import.meta.env.DEV ? '/base-api' : import.meta.env.VITE_API_URL) + props.url
 const baseUrl = import.meta.env.VITE_IMG_BASE_URL
+const onExceed = (_: any, uploadFiles: UploadUserFile[]) => {
+  uploadFiles.splice(0, 1)
+}
+
+watch(fileList, () => {
+  let srcList = getSrcList();
+  if (props.modelValue?.toString() !== srcList.toString()) {
+    emits('update:modelValue', props.modelType === 'string' ? srcList.toString() : srcList)
+  }
+}, {
+  deep: true,
+})
 
-const imageUrl = computed({
-  get () {
-    return props.modelValue
-  },
-  set (val) {
-    emits('update:modelValue', val || '')
+watch(() => props.modelValue, (val) => {
+  if (val?.toString() !== getSrcList().toString()) {
+    if (!val || !val.length) {
+      fileList.value = []
+    } else {
+      fileList.value = (typeof val === 'string' ? val.split(',') : val).map(url => ({
+        name: url,
+        url,
+        status: "success",
+      }))
+    }
   }
 })
-// const imageUrl = ref('')
 
-const handleAvatarSuccess: UploadProps['onSuccess'] = (
-  response,
-  uploadFile
-) => {
-  // console.log('response', response)
-  // imageUrl.value = URL.createObjectURL(uploadFile.raw!)
-  imageUrl.value = response.data.shortPath
+const getSrcList = () => {
+  return fileList.value.filter(item => item.status == "success").map(item => item.url!)
 }
 
-const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
-  if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/jpg' && rawFile.type !== 'image/png') {
-    ElMessage.error('图片格式不支持!')
-    return false  
-  } else if (rawFile.size / 1024 / 1024 > 2) {
-    ElMessage.error('Avatar picture size can not exceed 2MB!')
-    return false
-  }
-  return true
+const getItemSrc = (item: UploadUserFile & { rawUrl?: string }) => {
+  let url = ""
+  if (item.rawUrl)
+    url = item.rawUrl;
+  else if (item.raw && item.raw.type.indexOf('image') === 0) {
+    item.rawUrl = URL.createObjectURL(item.raw)
+    url = item.rawUrl
+  } else if (item?.url)
+    url = baseUrl + item.url
+  else if (item?.response)
+    url = baseUrl + (item.response as any).data[props.resultKey]
+  return url
 }
-</script>
 
-<style scoped>
-.avatar-uploader .avatar {
-  width: 178px;
-  height: 178px;
-  display: block;
+const handleSuccess: UploadProps['onSuccess'] = (_, uploadFile: UploadFile) => {
+  uploadFile.url = (uploadFile.response as any).data[props.resultKey]
+  if (props.limit === 1 && fileList.value.length > 1) {
+    fileList.value.splice(0, fileList.value.length - 1)
+  }
 }
-</style>
+</script>
 
 <style>
-.avatar-uploader .el-upload {
+.upload-component .el-upload {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: flex-start;
+  justify-content: flex-start;
+}
+.avatar-uploader {
   border: 1px dashed var(--el-border-color);
   border-radius: 6px;
   cursor: pointer;
@@ -85,15 +155,7 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
   transition: var(--el-transition-duration-fast);
 }
 
-.avatar-uploader .el-upload:hover {
+.avatar-uploader:hover {
   border-color: var(--el-color-primary);
 }
-
-.el-icon.avatar-uploader-icon {
-  font-size: 28px;
-  color: #8c939d;
-  width: 178px;
-  height: 178px;
-  text-align: center;
-}
 </style>
\ No newline at end of file
修改 +1 -1
diff --git a/src/utils/request.ts b/src/utils/request.ts
index b30d80d..d79c830 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -6,7 +6,7 @@ import { ApiResult } from '../model/api/common';
 
 // 配置新建一个 axios 实例
 const service: AxiosInstance = axios.create({
-	baseURL: import.meta.env.VITE_API_URL,
+	baseURL: import.meta.env.DEV ? '/base-api' : import.meta.env.VITE_API_URL,
 	timeout: 50000,
 	headers: { 'Content-Type': 'application/json' },
 	paramsSerializer: {
增加 +16 -0
diff --git a/src/views/home/index1.vue b/src/views/home/index1.vue
new file mode 100644
index 0000000..5e5a1e2
--- /dev/null
+++ b/src/views/home/index1.vue
@@ -0,0 +1,16 @@
+<template>
+	<div class="w-full h-full p-3">
+		<div class="w-full h-full bg-white flex flex-col justify-center items-center">
+			<p class="text-4xl font-bold mb-10">欢迎使用</p>
+			<img src="../../assets/login-icon-two.svg" class="w-2/5" alt="">
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts" name="home">
+
+</script>
+
+<style scoped lang="scss">
+
+</style>
修改 +2 -2
diff --git a/src/views/modules/admin/user/index.vue b/src/views/modules/admin/user/index.vue
index ee475e5..4d707aa 100644
--- a/src/views/modules/admin/user/index.vue
+++ b/src/views/modules/admin/user/index.vue
@@ -41,9 +41,9 @@ const { columns, forms } = usePage({
 			prop: 'avatar',
 			component: 'upload',
 			props: {
-				url: '/Projects/Work/UploadFileService',
+				url: '/api/Upload/UploadFiles',
 				data: {
-					directory: 'works'
+					proName: 'avatar'
 				}
 			}
 		}
修改 +1 -1
diff --git a/vite.config.ts b/vite.config.ts
index 7cacb8d..500e35b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -29,7 +29,7 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
 			hmr: true,
 			proxy: {
 				'/base-api': {
-					target: 'https://cube3.newlifex.com',
+					target: env.VITE_API_URL,
 					ws: true,
 					changeOrigin: true,
 					rewrite: (path) => path.replace(/^\/base-api/, ''),