Просмотр исходного кода

Add 完成oss文件管理模块

YueYunyun 1 год назад
Родитель
Сommit
7d6859ec73
23 измененных файлов с 949 добавлено и 44 удалено
  1. 12 5
      SERVER/VberAdminPlusV3/.script/sql/admin.sql
  2. 11 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/properties/OssProperties.java
  3. 2 2
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/resources/vm/vue/view-tree.vue.vm
  4. 2 2
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/resources/vm/vue/view.vue.vm
  5. 1 0
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysOssController.java
  6. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysProfileController.java
  7. 2 3
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysClientServiceImpl.java
  8. 9 0
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysOssConfigServiceImpl.java
  9. BIN
      SERVER/VberAdminPlusV3/vber/profile/upload/avatar/2024/04/29/339d/1db9/6eeb/0dab/fa6b/4ab6/49d7/126a/4b89/ebff/339d1db96eeb0dabfa6b4ab649d7126a4b89ebff
  10. BIN
      SERVER/VberAdminPlusV3/vber/profile/upload/jpg/2024/04/29/4d00/844c/1451/f961/9b06/a99b/27c3/ca8f/5c2b/2b14/4d00844c1451f9619b06a99b27c3ca8f5c2b2b14
  11. BIN
      SERVER/VberAdminPlusV3/vber/profile/upload/temp/24042918094066710777.temp
  12. 67 0
      UI/VAP_V3.VUE/src/api/system/_oss.ts
  13. 67 0
      UI/VAP_V3.VUE/src/api/system/_ossConfig.ts
  14. 8 1
      UI/VAP_V3.VUE/src/api/system/index.ts
  15. 24 1
      UI/VAP_V3.VUE/src/components/symbol/VbSymbol.vue
  16. 43 16
      UI/VAP_V3.VUE/src/components/upload/VbImagePreview.vue
  17. 6 3
      UI/VAP_V3.VUE/src/components/upload/VbUpload.vue
  18. 14 0
      UI/VAP_V3.VUE/src/core/utils/index.ts
  19. 7 2
      UI/VAP_V3.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue
  20. 5 5
      UI/VAP_V3.VUE/src/stores/_auth.ts
  21. 5 3
      UI/VAP_V3.VUE/src/views/account/profile/_avatar.vue
  22. 377 0
      UI/VAP_V3.VUE/src/views/system/oss/_config.vue
  23. 286 0
      UI/VAP_V3.VUE/src/views/system/oss/index.vue

+ 12 - 5
SERVER/VberAdminPlusV3/.script/sql/admin.sql

@@ -530,22 +530,29 @@ insert into sys_menu
 values ('1062', '文件上传', '108', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:upload', 'cloud-upload',
         'btn btn-light-info', 'handleUpload', 100, 1, sysdate(), null, null, '');
 insert into sys_menu
-values ('1063', '文件下载', '108', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:download', 'cloud-download',
+values ('1063', '文件下载', '108', '3', '#', '', '', 1, 0, 'F', '1', '0', 'system:oss:download', 'cloud-download',
         'btn btn-light-info', 'handleDownload', 100, 1, sysdate(), null, null, '');
 insert into sys_menu
 values ('1064', '文件删除', '108', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:remove', 'dash-square',
         'btn btn-light-danger', 'handleDelete@0', 100, 1, sysdate(), null, null, '');
 insert into sys_menu
-values ('1065', '配置列表', '108', '5', '#', '', '', 1, 0, 'F', '1', '0', 'system:ossConfig', 'config', '', '', 100, 1,
+values ('1065', '文件服务器配置', '108', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig',
+        'gear-wide-connected',
+        'btn btn-light-success', 'handleConfig',
+        100, 1,
         sysdate(), null, null, '');
 insert into sys_menu
-values ('1066', '配置添加', '108', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', 'plus-square',
+values ('1066', '配置查询', '1065', '1', '#', '', '', 1, 0, 'F', '1', '0', 'system:ossConfig:query', 'eye', '', '', 100,
+        1,
+        sysdate(), null, null, '');
+insert into sys_menu
+values ('1067', '配置添加', '1065', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', 'plus-square',
         'btn btn-light-primary', 'handleCreate', 100, 1, sysdate(), null, null, '');
 insert into sys_menu
-values ('1067', '配置编辑', '108', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', 'pencil-square',
+values ('1068', '配置编辑', '1065', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', 'pencil-square',
         'btn btn-light-success', 'handleUpdate@1', 100, 1, sysdate(), null, null, '');
 insert into sys_menu
-values ('1068', '配置删除', '108', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', 'dash-square',
+values ('1069', '配置删除', '1065', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', 'dash-square',
         'btn btn-light-danger', 'handleDelete@0', 100, 1, sysdate(), null, null, '');
 -- 租户管理相关按钮
 insert into sys_menu

+ 11 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/properties/OssProperties.java

@@ -60,4 +60,15 @@ public class OssProperties {
      */
     private String accessPolicy;
 
+    public boolean equals(OssProperties properties) {
+        return properties.getAccessKey().equals(accessKey) &&
+                properties.getSecretKey().equals(secretKey) &&
+                properties.getBucketName().equals(bucketName) &&
+                properties.getRegion().equals(region) &&
+                properties.getPrefix().equals(prefix) &&
+                properties.getDomain().equals(domain) &&
+                properties.getIsHttps().equals(isHttps) &&
+                (endpoint.isEmpty() || properties.getEndpoint().isEmpty() || properties.getEndpoint().equals(endpoint)) &&
+                properties.getAccessPolicy().equals(accessPolicy);
+    }
 }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/resources/vm/vue/view-tree.vue.vm

@@ -39,7 +39,7 @@
                 #set($comment=$column.columnComment)
             #end
             #if($column.pk)
-              {field: "$field", name: "$comment", width: 100, isSort: true, visible: false},
+              {field: "$field", name: "$comment", width: 100, isSort: true, visible: false, tooltip: true},
             #elseif($column.list)
                 #if(($field.indexOf("Time")>0&&$field.indexOf("Time")==${field.length()} - 4)
                 ||($field.indexOf("Date")>0&&$field.indexOf("Date")==${field.length()} - 4))
@@ -49,7 +49,7 @@
                 #elseif($column.dictType)
                   {field: "$field", name: "$comment", visible: true, isSort: $sort, width: 100},
                 #else
-                  {field: "$field", name: "$comment", visible: true, isSort: $sort, width: "auto"},
+                  {field: "$field", name: "$comment", visible: true, isSort: $sort, width: "auto", tooltip: true},
                 #end
             #end
         #end

+ 2 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/resources/vm/vue/view.vue.vm

@@ -38,7 +38,7 @@
                 #set($comment=$column.columnComment)
             #end
             #if($column.pk)
-              {field: "$field", name: "$comment", width: 100, isSort: true, visible: false},
+              {field: "$field", name: "$comment", width: 100, isSort: true, visible: false, tooltip: true},
             #elseif($column.list)
                 #if(($field.indexOf("Time")>0&&$field.indexOf("Time")==${field.length()} - 4)
                 ||($field.indexOf("Date")>0&&$field.indexOf("Date")==${field.length()} - 4))
@@ -48,7 +48,7 @@
                 #elseif($column.dictType)
                   {field: "$field", name: "$comment", visible: true, isSort: $sort, width: 100},
                 #else
-                  {field: "$field", name: "$comment", visible: true, isSort: $sort, width: "auto"},
+                  {field: "$field", name: "$comment", visible: true, isSort: $sort, width: "auto", tooltip: true},
                 #end
             #end
         #end

+ 1 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysOssController.java

@@ -108,6 +108,7 @@ public class SysOssController extends BaseController {
     @GetMapping("/preview/{objectId}")
     public void preview(@PathVariable String objectId, HttpServletResponse response) throws IOException {
         SysOssVo oss = ossService.getByObjectId(objectId);
+
         ossService.download(oss.getOssId(), response);
         R.ok();
     }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysProfileController.java

@@ -107,7 +107,7 @@ public class SysProfileController extends BaseController {
      */
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    public R<AvatarVo> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
+    public R<AvatarVo> avatar(@RequestPart("avatar") MultipartFile avatarfile) {
         if (!avatarfile.isEmpty()) {
             String extension = FileUtil.extName(avatarfile.getOriginalFilename());
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {

+ 2 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysClientServiceImpl.java

@@ -94,9 +94,7 @@ public class SysClientServiceImpl implements ISysClientService {
         validEntityBeforeSave(add);
         add.setGrantType(String.join(",", bo.getGrantTypeList()));
         // 生成clientid
-        String clientKey = bo.getClientKey();
-        String clientSecret = bo.getClientSecret();
-        add.setClientId(SecureUtil.md5(clientKey + clientSecret));
+        add.setClientId(SecureUtil.md5(bo.getClientKey() + bo.getClientSecret()));
         boolean flag = baseMapper.insert(add) > 0;
         if (flag) {
             bo.setId(add.getId());
@@ -112,6 +110,7 @@ public class SysClientServiceImpl implements ISysClientService {
         SysClient update = MapstructUtils.convert(bo, SysClient.class);
         validEntityBeforeSave(update);
         update.setGrantType(String.join(",", bo.getGrantTypeList()));
+        update.setClientId(SecureUtil.md5(bo.getClientKey() + bo.getClientSecret()));
         return baseMapper.updateById(update) > 0;
     }
 

+ 9 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysOssConfigServiceImpl.java

@@ -82,6 +82,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Boolean insertByBo(SysOssConfigBo bo) {
         SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
         validEntityBeforeSave(config);
@@ -90,11 +91,15 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
             // 从数据库查询完整的数据做缓存
             config = baseMapper.selectById(config.getOssConfigId());
             CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+            if ("0".equals(bo.getStatus()) && "1".equals(config.getStatus())) {
+                flag = updateOssConfigStatus(bo) > 0;
+            }
         }
         return flag;
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Boolean updateByBo(SysOssConfigBo bo) {
         SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
         validEntityBeforeSave(config);
@@ -109,7 +114,11 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
             // 从数据库查询完整的数据做缓存
             config = baseMapper.selectById(config.getOssConfigId());
             CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+            if ("0".equals(bo.getStatus()) && "1".equals(config.getStatus())) {
+                flag = updateOssConfigStatus(bo) > 0;
+            }
         }
+
         return flag;
     }
 

BIN
SERVER/VberAdminPlusV3/vber/profile/upload/avatar/2024/04/29/339d/1db9/6eeb/0dab/fa6b/4ab6/49d7/126a/4b89/ebff/339d1db96eeb0dabfa6b4ab649d7126a4b89ebff


BIN
SERVER/VberAdminPlusV3/vber/profile/upload/jpg/2024/04/29/4d00/844c/1451/f961/9b06/a99b/27c3/ca8f/5c2b/2b14/4d00844c1451f9619b06a99b27c3ca8f5c2b2b14


BIN
SERVER/VberAdminPlusV3/vber/profile/upload/temp/24042918094066710777.temp


+ 67 - 0
UI/VAP_V3.VUE/src/api/system/_oss.ts

@@ -0,0 +1,67 @@
+import Rs from "@/core/services/RequestService"
+
+class ossApi {
+	tableUrl = "/resource/oss/list"
+	exportUrl = "/resource/oss/export"
+
+	// 查询OSS对象存储列表
+	list = (query: any) => {
+		return Rs.get({
+			url: "/resource/oss/list",
+			params: query,
+			loading: false
+		})
+	}
+
+	// 查询OSS对象存储详细
+	get = (ossId: string) => {
+		return Rs.get({
+			url: "/resource/oss/" + ossId,
+			loading: false
+		})
+	}
+
+	// 新增或修改OSS对象存储
+	addOrUpdate = (data: any) => {
+		return new Promise((resolve) => {
+			if (data.ossId) {
+				this.update(data).then((res: any) => {
+					message.msgSuccess("修改成功")
+					resolve(res)
+				})
+			} else {
+				this.add(data).then((res: any) => {
+					message.msgSuccess("新增成功")
+					resolve(res)
+				})
+			}
+		})
+	}
+
+	// 新增OSS对象存储
+	add = (data: any) => {
+		return Rs.post({
+			url: "/resource/oss",
+			data: data,
+			successAlert: false
+		})
+	}
+
+	// 修改OSS对象存储
+	update = (data: any) => {
+		return Rs.put({
+			url: "/resource/oss",
+			data: data,
+			successAlert: false
+		})
+	}
+
+	// 删除OSS对象存储
+	del = (ossId: string | string[]) => {
+		return Rs.del({
+			url: "/resource/oss/" + ossId
+		})
+	}
+}
+
+export default ossApi

+ 67 - 0
UI/VAP_V3.VUE/src/api/system/_ossConfig.ts

@@ -0,0 +1,67 @@
+import Rs from "@/core/services/RequestService"
+
+class ossConfigApi {
+	tableUrl = "/resource/oss/config/list"
+	exportUrl = "/resource/oss/config/export"
+
+	// 查询对象存储配置列表
+	list = (query: any) => {
+		return Rs.get({
+			url: "/resource/oss/config/list",
+			params: query,
+			loading: false
+		})
+	}
+
+	// 查询对象存储配置详细
+	get = (ossConfigId: string) => {
+		return Rs.get({
+			url: "/resource/oss/config/" + ossConfigId,
+			loading: false
+		})
+	}
+
+	// 新增或修改对象存储配置
+	addOrUpdate = (data: any) => {
+		return new Promise((resolve) => {
+			if (data.ossConfigId) {
+				this.update(data).then((res: any) => {
+					message.msgSuccess("修改成功")
+					resolve(res)
+				})
+			} else {
+				this.add(data).then((res: any) => {
+					message.msgSuccess("新增成功")
+					resolve(res)
+				})
+			}
+		})
+	}
+
+	// 新增对象存储配置
+	add = (data: any) => {
+		return Rs.post({
+			url: "/resource/oss/config",
+			data: data,
+			successAlert: false
+		})
+	}
+
+	// 修改对象存储配置
+	update = (data: any) => {
+		return Rs.put({
+			url: "/resource/oss/config",
+			data: data,
+			successAlert: false
+		})
+	}
+
+	// 删除对象存储配置
+	del = (ossConfigId: string | string[]) => {
+		return Rs.del({
+			url: "/resource/oss/config/" + ossConfigId
+		})
+	}
+}
+
+export default ossConfigApi

+ 8 - 1
UI/VAP_V3.VUE/src/api/system/index.ts

@@ -10,6 +10,9 @@ import Auth from "./_auth"
 import LoginLog from "./_loginLog"
 import operLog from "./_operLog"
 import client from "./_client"
+import oss from "./_oss"
+import ossConfig from "./_ossConfig"
+
 export interface ISystemApi {
 	menuApi: Menu
 	userApi: User
@@ -23,6 +26,8 @@ export interface ISystemApi {
 	loginLogApi: LoginLog
 	operLogApi: operLog
 	clientApi: client
+	ossApi: oss
+	ossConfigApi: ossConfig
 }
 
 export const apis: ISystemApi = {
@@ -37,7 +42,9 @@ export const apis: ISystemApi = {
 	authApi: new Auth(),
 	loginLogApi: new LoginLog(),
 	operLogApi: new operLog(),
-	clientApi: new client()
+	clientApi: new client(),
+	ossConfigApi: new ossConfig(),
+	ossApi: new oss()
 }
 
 export default apis

+ 24 - 1
UI/VAP_V3.VUE/src/components/symbol/VbSymbol.vue

@@ -1,8 +1,10 @@
 <script setup lang="ts">
+import Rs from "@@/services/RequestService"
 const props = withDefaults(
 	defineProps<{
 		text?: string
 		src?: string
+		url?: string
 		size?: number
 		shape?: "" | "circle" | "square"
 		isRatio?: boolean
@@ -38,11 +40,32 @@ const _style = computed(() => {
 	}
 	return style
 })
+const srcUrl = ref<string>("")
+function init() {
+	if (props.url && props.src) {
+		Rs.get({
+			url: props.url + props.src,
+			loading: false,
+			responseType: "blob"
+		}).then((res: any) => {
+			if (res.type === "application/json") {
+				return
+			}
+			const data = new Blob([res])
+			const url = URL.createObjectURL(data)
+			srcUrl.value = url
+		})
+	} else if (props.src) {
+		srcUrl.value = props.src
+	}
+}
+
+onMounted(init)
 </script>
 
 <template>
 	<div class="symbol" :class="_class">
-		<img v-if="src" :src="src" :alt="alt" :style="_style" />
+		<img v-if="srcUrl" :src="srcUrl" :alt="alt" :style="_style" />
 		<template v-else-if="$slots.default">
 			<slot></slot>
 		</template>

+ 43 - 16
UI/VAP_V3.VUE/src/components/upload/VbImagePreview.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import Rs from "@@/services/RequestService"
 const props = withDefaults(
 	defineProps<{
 		src: string
@@ -7,6 +8,9 @@ const props = withDefaults(
 		width?: string | number
 		height?: string | number
 		imageStyle?: any
+		url?: string
+		load?: (e: any) => void
+		error?: (e: any) => void
 	}>(),
 	{
 		width: 75,
@@ -19,24 +23,15 @@ const props = withDefaults(
 		}
 	}
 )
-function formmateUrl(url: string) {
+function formatUrl(url: string) {
 	if (isExternal(url)) {
 		return url
 	}
-	return import.meta.env.VITE_APP_BASE_API + (props.prefixSrc ? props.prefixSrc : "") + url
+	url = import.meta.env.VITE_APP_BASE_API + (props.prefixSrc ? props.prefixSrc : "") + url
+	console.log("------", url)
+	return url
 }
-const srcList = computed(() => {
-	if (!props.src) {
-		return []
-	}
-	const src_list = props.src.split(",")
-	const srcList: any = []
-	src_list.forEach((v) => {
-		const url = formmateUrl(v)
-		return srcList.push(url)
-	})
-	return srcList
-})
+const srcList = ref<any>([])
 
 const imgStyle = computed(() => {
 	const style = typeof props.imageStyle == "object" ? props.imageStyle : {}
@@ -49,12 +44,42 @@ const imgStyle = computed(() => {
 	return style
 })
 
-function init() {
+function load() {
+	srcList.value = []
 	if (!props.src) {
-		throw Error("VbImageView组件src属性不能为空!")
+		return
 	}
+	const src_list = props.src.split(",")
+	src_list.forEach((v) => {
+		if (props.url) {
+			Rs.get({
+				url: props.url + v,
+				loading: false,
+				responseType: "blob"
+			}).then((res: any) => {
+				if (res.type === "application/json") {
+					return
+				}
+				const data = new Blob([res])
+				const url = URL.createObjectURL(data)
+				srcList.value.push(url)
+			})
+		} else {
+			const url = formatUrl(v)
+			srcList.value.push(url)
+		}
+	})
 }
 
+function init() {
+	load()
+}
+function onLoad(e: any) {
+	props.load && props.load(e)
+}
+function onError(e: any) {
+	props.error && props.error(e)
+}
 onMounted(init)
 </script>
 <template>
@@ -66,6 +91,8 @@ onMounted(init)
 		:initial-index="i"
 		:style="imgStyle"
 		:preview-src-list="srcList"
+		@load="onLoad"
+		@error="onError"
 		append-to-body="true">
 		<template #error>
 			<div class="image-slot">

+ 6 - 3
UI/VAP_V3.VUE/src/components/upload/VbUpload.vue

@@ -12,7 +12,7 @@ const props = withDefaults(
 	}>(),
 	{
 		uploadType: "image",
-		prefixUrl: "/oss/preview/",
+		prefixUrl: "/resource/oss/",
 		limit: 5,
 		fileSize: 5,
 		fileType: () => ["png", "jpg", "jpeg"],
@@ -31,9 +31,12 @@ const showViewer = ref(false)
 const viewerInitialIndex = ref(0)
 const baseUrl = import.meta.env.VITE_APP_BASE_API
 const uploadImgUrl = ref(
-	import.meta.env.VITE_APP_BASE_API + (props.uploadUrl ? props.uploadUrl : "/oss/upload/common")
+	import.meta.env.VITE_APP_BASE_API + (props.uploadUrl ? props.uploadUrl : "/resource/oss/upload")
 ) // 上传的图片服务器地址
-const headers = ref({ Authorization: "Bearer " + getToken() })
+const headers = ref({
+	Authorization: "Bearer " + getToken(),
+	ClientId: import.meta.env.VITE_APP_CLIENT_ID
+})
 const fileList = ref<any[]>([])
 const isImage = computed(() => {
 	return props.uploadType == "image"

+ 14 - 0
UI/VAP_V3.VUE/src/core/utils/index.ts

@@ -695,3 +695,17 @@ export function isNumberStr(str: string) {
 export const getAssetPath = (path: string): string => {
 	return import.meta.env.BASE_URL + path
 }
+
+function blob2Base64(blob: any) {
+	return new Promise((resolve, reject) => {
+		const reader = new FileReader()
+		reader.addEventListener("load", () => {
+			const base64 = reader.result?.toString() || ""
+			resolve(base64)
+		})
+		reader.addEventListener("error", () => {
+			reject(new Error("message"))
+		})
+		reader.readAsDataURL(blob)
+	})
+}

+ 7 - 2
UI/VAP_V3.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue

@@ -27,7 +27,12 @@ function signOut() {
 			data-vb-menu-trigger="click"
 			data-vb-menu-attach="parent"
 			data-vb-menu-placement="bottom-end">
-			<VbSymbol :src="user.avatar" :text="user.userName" :size="40" alt="user" />
+			<VbSymbol
+				url="/resource/oss"
+				:src="user.avatar"
+				:text="user.userName"
+				:size="40"
+				alt="user" />
 		</div>
 		<div
 			class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary py-4 fs-6 w-275px"
@@ -35,7 +40,7 @@ function signOut() {
 			<div class="menu-item px-3">
 				<div class="menu-content d-flex align-items-center px-3">
 					<div class="symbol symbol-50px me-5">
-						<VbSymbol :src="user.avatar" :text="user.userName" alt="user" />
+						<VbSymbol url="/resource/oss" :src="user.avatar" :text="user.userName" alt="user" />
 					</div>
 
 					<div class="d-flex flex-column">

+ 5 - 5
UI/VAP_V3.VUE/src/stores/_auth.ts

@@ -39,11 +39,11 @@ export const useAuthStore = defineStore("auth", () => {
 	}
 
 	function changeAvatar(avatar: any) {
-		user.value.avatar =
-			avatar == "" || avatar == null
-				? // ? getAssetPath("media/avatars/300-1.jpg")
-				  undefined
-				: import.meta.env.VITE_APP_BASE_API + avatar
+		user.value.avatar = avatar
+		// avatar == "" || avatar == null
+		// 	? // ? getAssetPath("media/avatars/300-1.jpg")
+		// 	  undefined
+		// 	: import.meta.env.VITE_APP_BASE_API + "/resource/oss" + avatar
 	}
 	const purgeAuth = () => {
 		isAuthenticated.value = false

+ 5 - 3
UI/VAP_V3.VUE/src/views/account/profile/_avatar.vue

@@ -16,6 +16,7 @@ const options = reactive<any>({
 	autoCropHeight: 200, // 默认生成截图框高度
 	fixedBox: true, // 固定截图框大小 不允许改变
 	outputType: "png", // 默认生成截图为PNG格式
+	fileName: "",
 	previews: {
 		div: {
 			width: "200px",
@@ -45,14 +46,14 @@ function changeScale(num: number) {
 }
 /** 上传预处理 */
 function beforeUpload(file: any) {
-	console.log("beforeUpload", file)
-	if (file.type.indexOf("image/") == -1) {
+	if (!file.type.includes("image/")) {
 		message.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
 	} else {
 		const reader = new FileReader()
 		reader.readAsDataURL(file)
 		reader.onload = () => {
 			options.img = reader.result
+			options.fileName = file.name
 		}
 	}
 }
@@ -60,7 +61,7 @@ function beforeUpload(file: any) {
 function uploadImg() {
 	cropperRef.value.getCropBlob((data: any) => {
 		const formData = new FormData()
-		formData.append("avatarfile", data)
+		formData.append("avatar", data, options.fileName)
 		apis.system.userApi.uploadAvatar(formData).then((res: any) => {
 			appStore.authStore.changeAvatar(res.imgUrl)
 			options.img = appStore.authStore.user.avatar
@@ -93,6 +94,7 @@ const previewDivStyle = computed(() => {
 			<VbSymbol
 				class="position-relative cursor-pointer"
 				:size="100"
+				url="/resource/oss"
 				:src="user.avatar"
 				:text="user.userName"
 				@click="avatarModalRef.show()"></VbSymbol>

+ 377 - 0
UI/VAP_V3.VUE/src/views/system/oss/_config.vue

@@ -0,0 +1,377 @@
+<script setup lang="ts" name="OssConfig">
+import apis from "@a"
+
+const statusOptions = computed(() => {
+	return [
+		{ label: "是", value: "0", type: "success" },
+		{ label: "否", value: "1", type: "danger" }
+	]
+})
+const accessPolicyOptions = computed(() => {
+	return [
+		{ label: "PRIVATE", value: "0" },
+		{ label: "PUBLIC", value: "1" },
+		{ label: "CUSTOM", value: "2" }
+	]
+})
+
+const tableRef = ref()
+const modalRef = ref()
+const opts = reactive({
+	columns: [
+		{ field: "ossConfigId", name: "主建", width: 100, isSort: true, visible: false },
+		{
+			field: "configKey",
+			name: "配置 key",
+			visible: true,
+			isSort: true,
+			width: 150,
+			tooltip: true
+		},
+		{
+			field: "accessKey",
+			name: "access Key",
+			visible: true,
+			isSort: false,
+			width: "auto",
+			tooltip: true
+		},
+		{
+			field: "secretKey",
+			name: "秘钥",
+			visible: true,
+			isSort: false,
+			width: "auto",
+			tooltip: true
+		},
+		{
+			field: "bucketName",
+			name: "桶名称",
+			visible: true,
+			isSort: false,
+			width: 180,
+			tooltip: true
+		},
+		{
+			field: "accessPolicy",
+			name: "桶权限类型",
+			visible: true,
+			isSort: true,
+			width: 100,
+			tooltip: true
+		},
+		{ field: "prefix", name: "前缀", visible: true, isSort: false, width: 100, tooltip: true },
+		{
+			field: "isHttps",
+			name: "是否https",
+			visible: true,
+			isSort: true,
+			width: 100,
+			tooltip: true
+		},
+		{ field: "status", name: "是否默认", visible: true, isSort: false, width: 100, tooltip: true },
+		{ field: "actions", name: `操作`, width: 150 }
+	],
+	queryParams: {
+		configKey: undefined,
+		bucketName: undefined
+	},
+	searchFormItems: [
+		{
+			field: "configKey",
+			label: "配置key",
+			class: "w-100",
+			required: false,
+			placeholder: "请输入配置key",
+			component: "I",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			},
+			span: 5
+		},
+		{
+			field: "bucketName",
+			label: "桶名称",
+			class: "w-100",
+			required: false,
+			placeholder: "请输入桶名称",
+			component: "I",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			},
+			span: 5
+		}
+	] as any,
+	permission: "system:ossConfig",
+	handleBtns: [],
+	handleFuns: {
+		handleUpdate: () => {
+			const row = tableRef.value.getSelected()
+			handleUpdate(row)
+		},
+		handleDelete: () => {
+			const rows = tableRef.value.getSelecteds()
+			handleDelete(rows)
+		}
+	},
+	customBtns: [],
+	tableListFun: apis.system.ossConfigApi.list,
+	getEntityFun: apis.system.ossConfigApi.get,
+	deleteEntityFun: apis.system.ossConfigApi.del,
+	exportUrl: apis.system.ossConfigApi.exportUrl,
+	exportName: "OssConfig",
+	modalTitle: "文件服务配置",
+	formItems: [
+		{
+			field: "configKey",
+			label: "配置key",
+			class: "w-100",
+			required: true,
+			placeholder: "请输入配置key",
+			component: "I"
+		},
+		{
+			field: "accessKey",
+			label: "AccessKey",
+			class: "w-100",
+			placeholder: "请输入accessKey",
+			component: "I"
+		},
+		{
+			field: "secretKey",
+			label: "秘钥",
+			class: "w-100",
+			placeholder: "请输入秘钥",
+			component: "I"
+		},
+		{
+			field: "bucketName",
+			label: "桶名称",
+			class: "w-100",
+			placeholder: "请输入桶名称",
+			component: "I"
+		},
+		{
+			field: "prefix",
+			label: "前缀",
+			class: "w-100",
+			placeholder: "请输入前缀",
+			component: "I"
+		},
+		{
+			field: "endpoint",
+			label: "访问站点",
+			class: "w-100",
+			placeholder: "请输入访问站点",
+			component: "I"
+		},
+		{
+			field: "domain",
+			label: "自定义域名",
+			class: "w-100",
+			placeholder: "请输入自定义域名",
+			component: "I"
+		},
+		{
+			field: "region",
+			label: "域",
+			class: "w-100",
+			placeholder: "请输入域",
+			component: "I"
+		},
+		{
+			field: "isHttps",
+			label: "是否https",
+			class: "w-100",
+			placeholder: "请输入是否https",
+			component: "Dict",
+			props: {
+				dictType: "sys_yes_no",
+				type: "radio"
+			}
+		},
+		{
+			field: "accessPolicy",
+			label: "桶权限类型",
+			class: "w-100",
+			placeholder: "请选择桶权限类型",
+			component: "VS",
+			props: {
+				data: () => accessPolicyOptions.value,
+				type: "radio"
+			}
+		},
+		{
+			field: "status",
+			label: "是否默认",
+			class: "w-100",
+			component: "VS",
+			data: () => statusOptions.value,
+			props: {
+				type: "radio",
+				valueIsNumber: false
+			}
+		},
+		{
+			field: "ext1",
+			label: "扩展字段",
+			class: "w-100",
+			placeholder: "请输入扩展字段",
+			component: "I"
+		},
+		{
+			field: "remark",
+			label: "备注",
+			class: "w-100",
+			placeholder: "请输入备注",
+			component: "I",
+			props: {
+				type: "textarea",
+				rows: 5
+			}
+		}
+	] as any,
+	resetForm: () => {
+		form.value = emptyFormData.value
+	},
+	labelWidth: "80px",
+	emptyFormData: {
+		ossConfigId: undefined,
+		configKey: undefined,
+		accessKey: undefined,
+		secretKey: undefined,
+		bucketName: undefined,
+		prefix: undefined,
+		endpoint: undefined,
+		domain: undefined,
+		isHttps: "N",
+		region: undefined,
+		accessPolicy: "1",
+		status: "1",
+		ext1: undefined,
+		remark: undefined
+	}
+})
+const { queryParams, emptyFormData } = toRefs(opts)
+const form = ref<any>(emptyFormData.value)
+
+/** 搜索按钮操作 */
+function handleQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	addDateRange(query, query.dateRangeCreateTime)
+	addDateRange(query, query.dateRangeUpdateTime, "UpdateTime")
+	tableRef.value?.query(query)
+}
+
+/** 重置按钮操作 */
+function resetQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	query.dateRangeCreateTime = [] as any
+	addDateRange(query, query.dateRangeCreateTime)
+	query.dateRangeUpdateTime = [] as any
+	addDateRange(query, query.dateRangeUpdateTime, "UpdateTime")
+	//
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row: any) {
+	tableRef.value.defaultHandleFuns.handleUpdate("", row)
+}
+
+/** 删除按钮操作 */
+function handleDelete(rows: any[]) {
+	tableRef.value.defaultHandleFuns.handleDelete("", rows)
+}
+
+/** 提交按钮 */
+function submitForm() {
+	apis.system.ossConfigApi.addOrUpdate(form.value).then(() => {
+		handleQuery()
+	})
+}
+
+function query() {
+	handleQuery()
+}
+defineExpose({ query })
+</script>
+<template>
+	<div class="app-container">
+		<VbDataTable
+			ref="tableRef"
+			keyField="ossConfigId"
+			:columns="opts.columns"
+			:handle-perm="opts.permission"
+			:handle-btns="opts.handleBtns"
+			:handle-funs="opts.handleFuns"
+			:search-form-items="opts.searchFormItems"
+			:custom-btns="opts.customBtns"
+			:remote-fun="opts.tableListFun"
+			:get-entity-fun="opts.getEntityFun"
+			:delete-entity-fun="opts.deleteEntityFun"
+			:export-url="opts.exportUrl"
+			:export-name="opts.exportName"
+			:modal="modalRef"
+			:reset-form-fun="opts.resetForm"
+			v-model:form-data="form"
+			v-model:query-params="queryParams"
+			:check-multiple="true"
+			:reset-search-form-fun="resetQuery"
+			:custom-search-fun="handleQuery"
+			:init-search="false"
+			table-box-height="50vh">
+			<template #status="{ row }">
+				<VbTag :data="statusOptions" :value-is-number="false" :value="row.status"></VbTag>
+			</template>
+			<template #isHttps="{ row }">
+				<DictTag type="sys_yes_no" :value-is-number="false" :value="row.isHttps"></DictTag>
+			</template>
+			<template #accessPolicy="{ row }">
+				<VbTag
+					:data="accessPolicyOptions"
+					:value-is-number="false"
+					:value="row.accessPolicy"></VbTag>
+			</template>
+			<template #actions="{ row }">
+				<vb-tooltip content="修改" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleUpdate(row)"
+						v-hasPermission="'system:ossConfig:edit'">
+						<template #icon>
+							<VbIcon icon-name="notepad-edit" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+				<vb-tooltip content="删除" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleDelete([row])"
+						v-hasPermission="'system:ossConfig:remove'">
+						<template #icon>
+							<VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+			</template>
+		</VbDataTable>
+		<VbModal
+			v-model:modal="modalRef"
+			:title="opts.modalTitle"
+			:form-data="form"
+			:form-items="opts.formItems"
+			:label-width="opts.labelWidth"
+			append-to-body
+			@confirm="submitForm"></VbModal>
+	</div>
+</template>

+ 286 - 0
UI/VAP_V3.VUE/src/views/system/oss/index.vue

@@ -0,0 +1,286 @@
+<script setup lang="ts" name="Oss">
+import apis from "@a"
+import Rs from "@@/services/RequestService"
+import Config from "./_config.vue"
+
+const serviceOptions = computed(() => {
+	return [
+		{ label: "请选择", value: "" },
+		{ label: "", value: "" }
+	]
+})
+
+const tableRef = ref()
+const modalRef = ref()
+const opts = reactive({
+	columns: [
+		{ field: "ossId", name: "主键", width: 100, isSort: true, visible: false },
+		{ field: "originalName", name: "原名", visible: true, isSort: false, width: "auto" },
+		{ field: "fileSuffix", name: "文件后缀名", visible: true, isSort: false, width: 100 },
+		{ field: "url", name: "文件展示", visible: true, isSort: false, width: "auto" },
+		{ field: "service", name: "服务商", visible: true, isSort: false, width: 200 },
+		{ field: "createTime", name: "上传时间", visible: true, isSort: true, width: 185 },
+		{ field: "actions", name: `操作`, width: 150 }
+	],
+	queryParams: {
+		fileName: undefined,
+		originalName: undefined,
+		service: undefined
+	},
+	searchFormItems: [
+		{
+			field: "fileName",
+			label: "文件名",
+			class: "w-100",
+			required: false,
+			placeholder: "请输入文件名",
+			component: "I",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			}
+		},
+		{
+			field: "originalName",
+			label: "原名",
+			class: "w-100",
+			required: false,
+			placeholder: "请输入原名",
+			component: "I",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			}
+		},
+		{
+			field: "service",
+			label: "服务商",
+			class: "w-100",
+			required: false,
+			component: "VS",
+			placeholder: "请选择服务商",
+			data: () => serviceOptions.value,
+			props: {
+				valueIsNumber: false,
+				type: "select"
+			}
+		}
+	] as any,
+	permission: "system:oss",
+	handleBtns: [],
+	handleFuns: {
+		handleUpload,
+		handleDelete,
+		handleConfig
+	},
+	customBtns: [],
+	tableListFun: apis.system.ossApi.list,
+	getEntityFun: apis.system.ossApi.get,
+	deleteEntityFun: apis.system.ossApi.del,
+	exportUrl: apis.system.ossApi.exportUrl,
+	exportName: "Oss",
+	modalTitle: "文件上传",
+	formItems: [
+		{
+			field: "uploadType",
+			label: "上传类型",
+			class: "w-100",
+			required: true,
+			placeholder: "请输入文件名",
+			component: "VS",
+			props: {
+				type: "radio",
+				data: [
+					{ label: "图片", value: "image" },
+					{ label: "文件", value: "file" }
+				]
+			}
+		},
+		{
+			show: () => {
+				return form.value.uploadType == "file"
+			},
+			field: "s",
+			label: "上传文件",
+			class: "w-100",
+			component: "VU",
+			props: {
+				uploadType: "file",
+				uploadUrl: "",
+				limit: 1,
+				fileType: ["doc", "xls", "ppt", "txt", "pdf"]
+			}
+		},
+		{
+			show: () => {
+				return form.value.uploadType == "image"
+			},
+			field: "s",
+			label: "上传图片",
+			class: "w-100",
+			component: "VU",
+			props: {
+				prefixUrl: "/resource/oss/",
+				uploadType: "image"
+			}
+		}
+	] as any,
+	resetForm: () => {
+		form.value = emptyFormData.value
+	},
+	labelWidth: "80px",
+	emptyFormData: {
+		uploadType: "image"
+	}
+})
+const { queryParams, emptyFormData } = toRefs(opts)
+const form = ref<any>(emptyFormData.value)
+
+/** 搜索按钮操作 */
+function handleQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	addDateRange(query, query.dateRangeCreateTime)
+	addDateRange(query, query.dateRangeUpdateTime, "UpdateTime")
+	tableRef.value?.query(query)
+}
+
+/** 重置按钮操作 */
+function resetQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	query.dateRangeCreateTime = [] as any
+	addDateRange(query, query.dateRangeCreateTime)
+	query.dateRangeUpdateTime = [] as any
+	addDateRange(query, query.dateRangeUpdateTime, "UpdateTime")
+	//
+}
+
+/** 上传按钮操作 */
+function handleUpload() {
+	modalRef.value.show()
+}
+/** 下载按钮操作 */
+function handleDownload(row: any) {
+	Rs.download("/resource/oss/download/" + row.ossId, {}, row.originalName)
+}
+
+/** 删除按钮操作 */
+function handleDelete(rows: any[]) {
+	message.confirm("确认删除文件吗?删除后可能会影响部分页面的显示!", "删除文件").then(() => {
+		const ids = rows.map((v: any) => v.ossId)
+		apis.system.ossApi.del(ids).then(() => {
+			handleQuery()
+		})
+	})
+}
+
+function checkFileType(fileName: any) {
+	const arr = fileName.split(".")
+	const ext = arr[arr.length - 1]
+	if (["png", "jpg", "jpeg"].some((item) => item === ext)) {
+		return true
+	}
+	return false
+}
+
+const configModalRef = ref()
+const configViewRef = ref()
+
+function handleConfig() {
+	configModalRef.value?.show()
+	configViewRef.value?.query()
+}
+</script>
+<template>
+	<div class="app-container">
+		<VbDataTable
+			ref="tableRef"
+			keyField="ossId"
+			:columns="opts.columns"
+			:handle-perm="opts.permission"
+			:handle-btns="opts.handleBtns"
+			:handle-funs="opts.handleFuns"
+			:search-form-items="opts.searchFormItems"
+			:custom-btns="opts.customBtns"
+			:remote-fun="opts.tableListFun"
+			:get-entity-fun="opts.getEntityFun"
+			:delete-entity-fun="opts.deleteEntityFun"
+			:export-url="opts.exportUrl"
+			:export-name="opts.exportName"
+			:modal="modalRef"
+			:reset-form-fun="opts.resetForm"
+			v-model:form-data="form"
+			v-model:query-params="queryParams"
+			:check-multiple="true"
+			:reset-search-form-fun="resetQuery"
+			:custom-search-fun="handleQuery">
+			<template #url="{ row }">
+				<template v-if="checkFileType(row.originalName)">
+					<VbImagePreview
+						v-if="row.service == 'local'"
+						url="/resource/oss"
+						:src="row.url"
+						:image-style="{ marginTop: '6px', marginBottom: '-10px' }"></VbImagePreview>
+					<VbImagePreview
+						v-else
+						:src="row.url"
+						:image-style="{ marginTop: '6px', marginBottom: '-10px' }"></VbImagePreview>
+				</template>
+				<span v-else>{{ row.url }}</span>
+			</template>
+			<template #service="{ row }">
+				<VbTag :data="serviceOptions" :value-is-number="false" :value="row.service"></VbTag>
+			</template>
+			<template #actions="{ row }">
+				<vb-tooltip content="下载" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleDownload(row)"
+						v-hasPermission="'system:oss:download'">
+						<template #icon>
+							<VbIcon icon-name="file-down" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+				<vb-tooltip content="删除" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleDelete([row])"
+						v-hasPermission="'system:oss:remove'">
+						<template #icon>
+							<VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+			</template>
+		</VbDataTable>
+		<VbModal
+			v-model:modal="modalRef"
+			:title="opts.modalTitle"
+			:form-data="form"
+			:form-items="opts.formItems"
+			:label-width="opts.labelWidth"
+			:close-btn="false"
+			confirm-btn-text="关闭"
+			append-to-body
+			@confirm="handleQuery"></VbModal>
+
+		<VbModal
+			v-model:modal="configModalRef"
+			title="文件服务器配置"
+			modal-dialog-style="width:1600px;max-width:1600px;"
+			modal-body-class="pt-0"
+			append-to-body>
+			<template #body>
+				<Config ref="configViewRef"></Config>
+			</template>
+		</VbModal>
+	</div>
+</template>