提交
This commit is contained in:
1 parent
375a2c8df7
commit
ba7b7b8497
9 files changed
+343
-57
No files matched your search
+7
-2
@@ -75,11 +75,16 @@ public class SysUserVo implements Serializable {
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 头像地址
|
||||
* 头像 OSS ID
|
||||
*/
|
||||
@Translation(type = TransConstant.OSS_ID_TO_URL)
|
||||
private Long avatar;
|
||||
|
||||
/**
|
||||
* 头像访问地址
|
||||
*/
|
||||
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "avatar")
|
||||
private String avatarUrl;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
|
||||
@@ -395,7 +395,8 @@ loginWithEncryption()
|
||||
"email": "admin@example.com",
|
||||
"phonenumber": "13800138000",
|
||||
"sex": "1",
|
||||
"avatar": "/profile/avatar/default.jpg",
|
||||
"avatar": "1938120323613728770",
|
||||
"avatarUrl": "http://example.com/ruoyi/2025/08/31/xxxx.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
|
||||
"status": "0",
|
||||
"loginIp": "127.0.0.1",
|
||||
"loginDate": "2025-01-26 10:30:00",
|
||||
@@ -409,6 +410,13 @@ loginWithEncryption()
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明补充**:
|
||||
|
||||
- `avatar`:始终返回 `sys_user.avatar` 中存储的 OSS ID(字符串),用于鉴权下载接口 `/resource/oss/download/{ossId}`。
|
||||
- `avatarUrl`:系统通过 `TransConstant.OSS_ID_TO_URL` 规则实时翻译得到的可直接访问地址(MinIO 预签名 URL),供无需链路改造的场景快速显示;有效期受 OSS 策略控制。
|
||||
|
||||
前端如需在带 Token 的受保护组件(如 `ImagePreview`)中展示,请优先使用 `avatar` 并通过统一的认证下载逻辑获取图片,避免直接暴露临时 URL。
|
||||
|
||||
**超级管理员权限说明**:
|
||||
|
||||
admin 用户具有系统最高权限`*:*:*`,可以访问所有功能模块和执行所有操作。
|
||||
@@ -663,7 +671,9 @@ methods: {
|
||||
---
|
||||
|
||||
**📝 维护**: 用户认证登录 API 文档
|
||||
**🔄 更新**: 2025-01-03
|
||||
**🔄 更新记录**:
|
||||
- 2025-11-28:`/system/user/getInfo` 增加 `avatarUrl` 字段,并明确 `avatar` 返回 OSS ID
|
||||
- 2025-01-03:文档初始化
|
||||
**📁 文件**: 用户认证登录 API.md
|
||||
|
||||
## 获取具有指定权限的用户列表
|
||||
@@ -693,7 +703,8 @@ methods: {
|
||||
deptId: string; // 部门ID
|
||||
deptName: string; // 部门名称
|
||||
};
|
||||
avatar: string; // 头像URL
|
||||
avatar: string; // 头像OSS ID
|
||||
avatarUrl?: string; // 头像访问URL(可选,预签名)
|
||||
}[];
|
||||
total: number; // 总记录数
|
||||
}
|
||||
@@ -713,7 +724,8 @@ methods: {
|
||||
"deptId": "1",
|
||||
"deptName": "后勤部"
|
||||
},
|
||||
"avatar": "http://example.com/avatar.jpg"
|
||||
"avatar": "1938120323613728770",
|
||||
"avatarUrl": "http://example.com/avatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
|
||||
@@ -65,6 +65,36 @@ ImagePreview 是一个图片预览组件,支持两种模式:
|
||||
</template>
|
||||
```
|
||||
|
||||
### 4. 顶部个人头像(自动识别OSS ID)
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const avatarIsOssId = computed(() => {
|
||||
const avatar = userStore.avatar;
|
||||
if (!avatar) return false;
|
||||
return !/^https?:\/\//i.test(avatar) && !avatar.startsWith('/');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ImagePreview
|
||||
v-if="userStore.avatar"
|
||||
class="user-avatar"
|
||||
:src="userStore.avatar"
|
||||
:width="24"
|
||||
:height="24"
|
||||
:is-oss-id="avatarIsOssId"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
> ✅ 用于首页 `Navbar` 头像区域时,`avatarIsOssId` 会自动识别当前值是否为 OSS ID,从而在带 Token 的 `/resource/oss/download/{ossId}` 接口与传统 URL 之间无缝切换,保证头像始终通过认证通道加载。
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心功能
|
||||
@@ -126,6 +156,7 @@ const urlCache = ref<Map<string, string>>(new Map());
|
||||
|
||||
## 更新记录
|
||||
|
||||
- **2025-11-28**:补充顶部栏个人头像场景示例,强调 OSS ID 自动识别策略,确保 `Navbar` 等公共区域也复用统一的图片安全加载方案。
|
||||
- **2025-08-02**:修复组件,支持OSS ID模式,添加缓存机制和加载状态
|
||||
- 修复了导入错误和computed未定义的问题
|
||||
- 添加了完整的错误处理和日志记录
|
||||
@@ -737,6 +737,13 @@ export default {
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 头像鉴权加载
|
||||
|
||||
- 统一从 `/system/user/getInfo` 获取 `avatar`(OSS ID)与 `avatarUrl`(预签名地址)。
|
||||
- 页面中优先识别 `avatar` 是否为 OSS ID,使用 `uni.request`(`responseType: 'arraybuffer'`)访问 `/resource/oss/download/{ossId}` 并转换为 `Blob URL/Base64`,确保 Token 认证生效。
|
||||
- 若历史账号仍存储直接 URL,则回退到 `avatar` 或 `avatarUrl` 原始值,最后兜底 `/static/111.png`。
|
||||
- 首页、详情页等通用区域必需复用该策略,避免直接暴露预签名链接导致过期或越权。
|
||||
|
||||
#### 列表优化
|
||||
|
||||
```vue
|
||||
|
||||
@@ -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,15 +840,220 @@ 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 '';
|
||||
},
|
||||
|
||||
// 点击用户头像
|
||||
handleUserClick() {
|
||||
// const info = userManager.getUserInfoString();
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface UserVO extends BaseEntity {
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
avatar: string;
|
||||
avatarUrl?: string;
|
||||
status: string;
|
||||
delFlag: string;
|
||||
loginIp: string;
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
<div class="avatar-container">
|
||||
<el-dropdown class="right-menu-item hover-effect" trigger="click" @command="handleCommand">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="userStore.avatar" class="user-avatar" />
|
||||
<ImagePreview v-if="userStore.avatar" class="user-avatar" :src="userStore.avatar" :width="24" :height="24" :is-oss-id="avatarIsOssId" />
|
||||
<img v-else :src="userStore.avatar" class="user-avatar" />
|
||||
<el-icon><caret-bottom /></el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
@@ -98,9 +99,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComponentInternalInstance, computed, getCurrentInstance, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Hamburger from '@/components/Hamburger/index.vue';
|
||||
import TopNav from '@/components/TopNav/index.vue';
|
||||
import SearchMenu from './TopBar/search.vue';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
@@ -129,6 +133,15 @@ const dynamic = ref(false);
|
||||
const tenantEnabled = ref(true);
|
||||
// 搜索菜单
|
||||
const searchMenuRef = ref<InstanceType<typeof SearchMenu>>();
|
||||
const avatarIsOssId = computed(() => {
|
||||
const avatar = userStore.avatar;
|
||||
if (!avatar) {
|
||||
return false;
|
||||
}
|
||||
const isHttp = /^https?:\/\//i.test(avatar);
|
||||
const isAbsolutePath = avatar.startsWith('/');
|
||||
return !isHttp && !isAbsolutePath;
|
||||
});
|
||||
|
||||
const openSearchMenu = () => {
|
||||
searchMenuRef.value?.openSearch();
|
||||
|
||||
@@ -13,6 +13,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
const userId = ref<string | number>('');
|
||||
const tenantId = ref<string>('');
|
||||
const avatar = ref('');
|
||||
const avatarUrl = ref('');
|
||||
const deptName = ref('');
|
||||
const phonenumber = ref('');
|
||||
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
|
||||
@@ -40,7 +41,9 @@ export const useUserStore = defineStore('user', () => {
|
||||
if (res) {
|
||||
const data = res.data;
|
||||
const user = data.user;
|
||||
const profile = user.avatar == '' || user.avatar == null ? defAva : user.avatar;
|
||||
const avatarValue = user.avatar === '' || user.avatar === null || user.avatar === undefined ? '' : String(user.avatar);
|
||||
const previewUrl = user.avatarUrl || '';
|
||||
const profile = avatarValue || defAva;
|
||||
|
||||
if (data.roles && data.roles.length > 0) {
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
@@ -52,6 +55,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
name.value = user.userName;
|
||||
nickname.value = user.nickName;
|
||||
avatar.value = profile;
|
||||
avatarUrl.value = previewUrl || (avatarValue ? '' : defAva);
|
||||
userId.value = user.userId;
|
||||
tenantId.value = user.tenantId;
|
||||
deptName.value = user.deptName;
|
||||
@@ -72,6 +76,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
|
||||
const setAvatar = (value: string) => {
|
||||
avatar.value = value;
|
||||
avatarUrl.value = value;
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -81,6 +86,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
name,
|
||||
nickname,
|
||||
avatar,
|
||||
avatarUrl,
|
||||
deptName,
|
||||
phonenumber,
|
||||
roles,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="user-info-head" @click="editCropper()">
|
||||
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
|
||||
<el-dialog v-model="open" :title="title" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
|
||||
<el-row>
|
||||
<el-row justify="center">
|
||||
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
|
||||
<vue-cropper
|
||||
v-if="visible"
|
||||
@@ -17,40 +17,23 @@
|
||||
@real-time="realTime"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
|
||||
<div class="avatar-upload-preview">
|
||||
<img :src="options.previews.url" :style="options.previews.img" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<br />
|
||||
<el-row>
|
||||
<el-col :lg="2" :md="2">
|
||||
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
|
||||
<el-button>
|
||||
选择
|
||||
<el-icon class="el-icon--right">
|
||||
<Upload />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
|
||||
<el-button icon="Plus" @click="changeScale(1)"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
|
||||
<el-button type="primary" @click="uploadImg()">提 交</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="avatar-actions">
|
||||
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
|
||||
<el-button>
|
||||
选择
|
||||
<el-icon class="el-icon--right">
|
||||
<Upload />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button icon="Plus" @click="changeScale(1)"></el-button>
|
||||
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
|
||||
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
|
||||
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
|
||||
<el-button type="primary" @click="uploadImg()">提 交</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -84,13 +67,13 @@ const title = ref('修改头像');
|
||||
const cropper = ref<any>({});
|
||||
//图片裁剪数据
|
||||
const options = reactive<Options>({
|
||||
img: userStore.avatar,
|
||||
img: userStore.avatarUrl || userStore.avatar,
|
||||
autoCrop: true,
|
||||
autoCropWidth: 200,
|
||||
autoCropHeight: 200,
|
||||
fixedBox: true,
|
||||
outputType: 'png',
|
||||
fileName: '',
|
||||
fileName: 'avatar.png',
|
||||
previews: {},
|
||||
visible: false
|
||||
});
|
||||
@@ -134,14 +117,20 @@ const beforeUpload = (file: UploadRawFile): any => {
|
||||
/** 上传图片 */
|
||||
const uploadImg = async () => {
|
||||
cropper.value.getCropBlob(async (data: any) => {
|
||||
const formData = new FormData();
|
||||
formData.append('avatarfile', data, options.fileName);
|
||||
const res = await uploadAvatar(formData);
|
||||
open.value = false;
|
||||
options.img = res.data.imgUrl;
|
||||
userStore.setAvatar(options.img);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
visible.value = false;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
const fileName = options.fileName || `avatar.${options.outputType || 'png'}`;
|
||||
formData.append('avatarfile', data, fileName);
|
||||
const res = await uploadAvatar(formData);
|
||||
await userStore.getInfo(); // 刷新用户信息以获取最新的oss_id
|
||||
options.img = res?.data?.imgUrl || userStore.avatarUrl || userStore.avatar;
|
||||
open.value = false;
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} catch (error) {
|
||||
proxy?.$modal.msgError('上传失败,请重试');
|
||||
} finally {
|
||||
visible.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 实时预览 */
|
||||
@@ -150,7 +139,7 @@ const realTime = (data: any) => {
|
||||
};
|
||||
/** 关闭窗口 */
|
||||
const closeDialog = () => {
|
||||
options.img = userStore.avatar;
|
||||
options.img = userStore.avatarUrl || userStore.avatar;
|
||||
options.visible = false;
|
||||
};
|
||||
</script>
|
||||
@@ -179,4 +168,12 @@ const closeDialog = () => {
|
||||
line-height: 110px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user