|
|
@@ -681,7 +681,8 @@ import Card from '@/components/Card.vue';
|
|
|
import ServiceItem from '@/components/ServiceItem.vue';
|
|
|
import GridLayout from '@/components/GridLayout.vue';
|
|
|
import { userManager, userState } from '@/utils/user';
|
|
|
-import { api } from '@/common/request';
|
|
|
+import { api, request } from '@/common/request';
|
|
|
+import { REQUEST_CONFIG } from '@/common/config';
|
|
|
import { hasPermi, permissionMixin } from '@/utils/permission';
|
|
|
import RouterManager from '@/utils/router';
|
|
|
|
|
|
@@ -698,8 +699,10 @@ export default {
|
|
|
userInfo: {
|
|
|
name: '用户',
|
|
|
avatar: '',
|
|
|
+ avatarOssId: '',
|
|
|
deptName: '',
|
|
|
},
|
|
|
+ avatarBlobUrl: '',
|
|
|
// 轮播图片数据
|
|
|
bannerList: [
|
|
|
{
|
|
|
@@ -817,6 +820,12 @@ export default {
|
|
|
this.checkLoginStatus();
|
|
|
this.loadUserInfo();
|
|
|
},
|
|
|
+ onUnload() {
|
|
|
+ this.cleanupAvatarBlob();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.cleanupAvatarBlob();
|
|
|
+ },
|
|
|
methods: {
|
|
|
// 全局权限校验方法
|
|
|
hasPermi(permissions) {
|
|
|
@@ -831,13 +840,218 @@ export default {
|
|
|
},
|
|
|
|
|
|
// 加载用户信息
|
|
|
- loadUserInfo() {
|
|
|
- const displayUser = userManager.formatUserForDisplay();
|
|
|
- if (displayUser) {
|
|
|
+ async loadUserInfo() {
|
|
|
+ try {
|
|
|
+ const displayUser = userManager.formatUserForDisplay();
|
|
|
+ if (!displayUser) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
this.userInfo.name = displayUser.name;
|
|
|
- this.userInfo.avatar = displayUser.avatar;
|
|
|
this.userInfo.deptName = displayUser.deptName;
|
|
|
+ this.userInfo.avatar = '';
|
|
|
+ this.userInfo.avatarOssId = '';
|
|
|
+
|
|
|
+ if (!displayUser.avatar) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const resolvedAvatar = await this.resolveAvatarValue(displayUser.avatar);
|
|
|
+ if (resolvedAvatar) {
|
|
|
+ const displayableUrl = await this.getDisplayableAvatarUrl(
|
|
|
+ resolvedAvatar.url,
|
|
|
+ resolvedAvatar.ossId,
|
|
|
+ );
|
|
|
+ this.userInfo.avatar = displayableUrl || resolvedAvatar.url;
|
|
|
+ this.userInfo.avatarOssId = resolvedAvatar.ossId || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载用户信息失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 统一处理头像字段,兼容URL与OSS ID
|
|
|
+ async resolveAvatarValue(rawAvatar) {
|
|
|
+ const avatarValue = (rawAvatar || '').toString().trim();
|
|
|
+ if (!avatarValue) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 直接可用的URL
|
|
|
+ if (this.isDirectUrl(avatarValue)) {
|
|
|
+ return { url: avatarValue, ossId: '' };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 后端仅返回了相对路径(例如/resource/oss/preview/xxx 或 /api/resource/...)
|
|
|
+ if (avatarValue.startsWith('/resource/') || avatarValue.startsWith('/api/resource/')) {
|
|
|
+ return {
|
|
|
+ url: this.normalizeResourceUrl(avatarValue),
|
|
|
+ ossId: '',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其余场景按OSS ID处理
|
|
|
+ const ossId = avatarValue.split(',')[0].trim();
|
|
|
+ if (!ossId) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const urlFromOss = await this.fetchAvatarFromOss(ossId);
|
|
|
+ return urlFromOss
|
|
|
+ ? {
|
|
|
+ url: urlFromOss,
|
|
|
+ ossId,
|
|
|
+ }
|
|
|
+ : null;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 判断是否为绝对可用的URL
|
|
|
+ isDirectUrl(value) {
|
|
|
+ const lower = value.toLowerCase();
|
|
|
+ return (
|
|
|
+ this.isAbsoluteExternalUrl(lower) ||
|
|
|
+ lower.startsWith('data:') ||
|
|
|
+ lower.startsWith('blob:') ||
|
|
|
+ value.startsWith('/static/')
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ isAbsoluteExternalUrl(value) {
|
|
|
+ const lower = value.toLowerCase();
|
|
|
+ return lower.startsWith('http://') || lower.startsWith('https://');
|
|
|
+ },
|
|
|
+
|
|
|
+ // 将后端返回的/resource路径转换成可直接展示的URL
|
|
|
+ normalizeResourceUrl(url) {
|
|
|
+ if (!url) return '';
|
|
|
+ if (url.startsWith('/api/')) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+ return `${REQUEST_CONFIG.serverUrl}${url}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 确保头像URL可以直接展示(必要时转换为blob URL)
|
|
|
+ async getDisplayableAvatarUrl(url, ossId) {
|
|
|
+ if (!url) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 静态资源或可匿名访问的URL直接返回
|
|
|
+ if (this.isAbsoluteExternalUrl(url) || url.startsWith('/static/')) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优先尝试把现有URL转换为blob,确保请求可以带上Token
|
|
|
+ const blobUrl = await this.fetchImageBlobUrl(url);
|
|
|
+ if (blobUrl) {
|
|
|
+ this.updateAvatarBlobUrl(blobUrl);
|
|
|
+ return blobUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 兜底:通过下载接口重新获取
|
|
|
+ if (ossId) {
|
|
|
+ const downloadPath = `/resource/oss/download/${encodeURIComponent(ossId)}`;
|
|
|
+ const downloadBlobUrl = await this.fetchImageBlobUrl(downloadPath);
|
|
|
+ if (downloadBlobUrl) {
|
|
|
+ this.updateAvatarBlobUrl(downloadBlobUrl);
|
|
|
+ return downloadBlobUrl;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return url;
|
|
|
+ },
|
|
|
+
|
|
|
+ updateAvatarBlobUrl(blobUrl) {
|
|
|
+ if (!blobUrl) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.cleanupAvatarBlob();
|
|
|
+ this.avatarBlobUrl = blobUrl;
|
|
|
+ },
|
|
|
+
|
|
|
+ cleanupAvatarBlob() {
|
|
|
+ if (this.avatarBlobUrl && this.avatarBlobUrl.startsWith('blob:')) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(this.avatarBlobUrl);
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('释放头像blob URL失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.avatarBlobUrl = '';
|
|
|
+ },
|
|
|
+
|
|
|
+ async fetchImageBlobUrl(imageUrl) {
|
|
|
+ try {
|
|
|
+ if (!imageUrl) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.isAbsoluteExternalUrl(imageUrl) || imageUrl.startsWith('/static/')) {
|
|
|
+ return imageUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ let requestUrl = imageUrl;
|
|
|
+
|
|
|
+ const serverPrefix = REQUEST_CONFIG.serverUrl || '';
|
|
|
+ if (
|
|
|
+ serverPrefix &&
|
|
|
+ requestUrl.startsWith(serverPrefix) &&
|
|
|
+ serverPrefix.startsWith('/')
|
|
|
+ ) {
|
|
|
+ requestUrl = requestUrl.slice(serverPrefix.length);
|
|
|
+ if (!requestUrl.startsWith('/')) {
|
|
|
+ requestUrl = `/${requestUrl}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (requestUrl.startsWith('/api/')) {
|
|
|
+ requestUrl = requestUrl.replace('/api', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!requestUrl.startsWith('/')) {
|
|
|
+ requestUrl = `/${requestUrl}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const arrayBuffer = await request({
|
|
|
+ url: requestUrl,
|
|
|
+ method: 'GET',
|
|
|
+ responseType: 'arraybuffer',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!arrayBuffer) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ const blob = new Blob([arrayBuffer], { type: 'image/*' });
|
|
|
+ return URL.createObjectURL(blob);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取头像blob失败:', error);
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 通过OSS ID获取文件访问URL
|
|
|
+ async fetchAvatarFromOss(ossId) {
|
|
|
+ try {
|
|
|
+ const response = await request({
|
|
|
+ url: `/resource/oss/listByIds/${encodeURIComponent(ossId)}`,
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (
|
|
|
+ response.code === 200 &&
|
|
|
+ Array.isArray(response.data) &&
|
|
|
+ response.data.length > 0
|
|
|
+ ) {
|
|
|
+ const fileInfo = response.data[0];
|
|
|
+ if (fileInfo?.url) {
|
|
|
+ return this.normalizeResourceUrl(fileInfo.url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('根据OSS ID获取头像失败:', error);
|
|
|
}
|
|
|
+ return '';
|
|
|
},
|
|
|
|
|
|
// 点击用户头像
|