|
|
@@ -0,0 +1,932 @@
|
|
|
+<template>
|
|
|
+ <view class="page-container">
|
|
|
+ <!-- 页面头部 -->
|
|
|
+ <u-navbar :title="pageTitle" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
|
|
+ <view slot="left" @click="goBack">
|
|
|
+ <u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
|
|
+ </view>
|
|
|
+ </u-navbar>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <u-loading-page :loading="loading" loading-text="加载中..."></u-loading-page>
|
|
|
+
|
|
|
+ <!-- 主要内容 -->
|
|
|
+ <view v-if="!loading" class="main-content">
|
|
|
+ <view class="avatar-container">
|
|
|
+ <!-- 当前头像预览 -->
|
|
|
+ <view class="current-avatar-section">
|
|
|
+ <text class="section-title">当前头像</text>
|
|
|
+ <view class="avatar-preview">
|
|
|
+ <image
|
|
|
+ :src="currentAvatar"
|
|
|
+ mode="aspectFill"
|
|
|
+ class="avatar-image"
|
|
|
+ @error="handleAvatarError"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 新头像预览区域 -->
|
|
|
+ <view class="new-avatar-section">
|
|
|
+ <text class="section-title">新头像预览</text>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view v-if="avatarLoading || wechatImageLoading" class="avatar-loading">
|
|
|
+ <u-loading-icon mode="circle" size="32"></u-loading-icon>
|
|
|
+ <text class="loading-text">
|
|
|
+ {{ wechatImageLoading ? '正在处理图片...' : '加载中...' }}
|
|
|
+ </text>
|
|
|
+ <text v-if="uploadProgress > 0" class="progress-text">
|
|
|
+ 上传进度: {{ uploadProgress }}%
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 已有新头像 - 点击可预览或重新选择 -->
|
|
|
+ <view v-else-if="newAvatar" class="avatar-wrapper" @click="handleAvatarClick">
|
|
|
+ <image
|
|
|
+ :src="newAvatar"
|
|
|
+ mode="aspectFill"
|
|
|
+ class="avatar-image"
|
|
|
+ @error="handleAvatarError"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 头像操作遮罩 -->
|
|
|
+ <view class="avatar-overlay">
|
|
|
+ <view class="overlay-content">
|
|
|
+ <u-icon
|
|
|
+ name="camera-fill"
|
|
|
+ color="rgba(255,255,255,0.9)"
|
|
|
+ size="24"
|
|
|
+ ></u-icon>
|
|
|
+ <text class="overlay-text">点击更换头像</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 头像角标 -->
|
|
|
+ <view class="avatar-badge">
|
|
|
+ <u-icon name="checkmark-circle-fill" color="#52c41a" size="20"></u-icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空状态 - 点击选择头像 -->
|
|
|
+ <view v-else class="avatar-placeholder" @click="handlePhotoUpload">
|
|
|
+ <text class="placeholder-text">选择新头像</text>
|
|
|
+ <text class="placeholder-note">点击选择图片</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 快捷操作按钮 -->
|
|
|
+ <view class="avatar-quick-actions">
|
|
|
+ <view class="placeholder-tips">
|
|
|
+ <text class="tip-item">
|
|
|
+ • 建议尺寸:200x200像素 • 支持格式:JPG、PNG • 文件大小:不超过10MB
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 提交按钮 -->
|
|
|
+ <view v-if="newAvatar" class="submit-section">
|
|
|
+ <button
|
|
|
+ class="submit-btn"
|
|
|
+ :class="{ 'btn-loading': isSubmitting }"
|
|
|
+ :disabled="isSubmitting"
|
|
|
+ @click="handleSubmit"
|
|
|
+ >
|
|
|
+ <u-icon v-if="isSubmitting" name="loading" size="16" color="#ffffff" />
|
|
|
+ <text class="btn-text">{{ isSubmitting ? '上传中...' : '确认修改' }}</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 微信环境提示 -->
|
|
|
+ <view v-if="!isWechatBrowser" class="wechat-tip">
|
|
|
+ <u-icon name="info-circle" size="16" color="#fa8c16" />
|
|
|
+ <text class="tip-text">建议在微信中使用以获得最佳体验</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { api, request } from '@/common/request';
|
|
|
+import { userManager } from '@/utils/user';
|
|
|
+import { REQUEST_CONFIG } from '@/common/config';
|
|
|
+import weChatSDK from '@/utils/wechat.js';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ChangeAvatar',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ avatarLoading: false,
|
|
|
+ currentAvatar: '',
|
|
|
+ newAvatar: '',
|
|
|
+ isSubmitting: false,
|
|
|
+ pageTitle: '修改头像',
|
|
|
+
|
|
|
+ // 微信SDK相关
|
|
|
+ wechatSDK: weChatSDK,
|
|
|
+ isWechatBrowser: false,
|
|
|
+ wechatInitializing: false,
|
|
|
+ wechatImageLoading: false,
|
|
|
+ uploadProgress: 0,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ onLoad() {
|
|
|
+ this.loadCurrentAvatar();
|
|
|
+ this.checkWechatEnvironment();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 返回上一页
|
|
|
+ goBack() {
|
|
|
+ uni.navigateBack();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载当前头像
|
|
|
+ loadCurrentAvatar() {
|
|
|
+ const userInfo = userManager.getCurrentUser();
|
|
|
+ this.currentAvatar = userInfo?.avatar || '/static/111.png';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查微信环境并自动初始化SDK
|
|
|
+ async checkWechatEnvironment() {
|
|
|
+ this.isWechatBrowser = this.wechatSDK.isWeChatBrowser();
|
|
|
+
|
|
|
+ if (this.isWechatBrowser) {
|
|
|
+ console.log('检测到微信环境,自动初始化微信SDK...');
|
|
|
+ await this.initWechatSDK();
|
|
|
+ } else {
|
|
|
+ console.log('非微信环境,使用标准图片选择功能');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化微信SDK
|
|
|
+ async initWechatSDK() {
|
|
|
+ if (this.wechatInitializing) return;
|
|
|
+
|
|
|
+ this.wechatInitializing = true;
|
|
|
+ console.log('开始初始化微信SDK...');
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 检查微信环境
|
|
|
+ if (!this.isWechatBrowser) {
|
|
|
+ throw new Error('当前不在微信浏览器环境中');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用微信SDK工具类初始化
|
|
|
+ const configData = await this.wechatSDK.init({
|
|
|
+ debug: false,
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('✅ 微信SDK初始化成功!', configData);
|
|
|
+
|
|
|
+ // 验证图片相关API可用性
|
|
|
+ try {
|
|
|
+ const checkResult = await this.wechatSDK.checkJsApi([
|
|
|
+ 'chooseImage',
|
|
|
+ 'previewImage',
|
|
|
+ 'uploadImage',
|
|
|
+ 'downloadImage',
|
|
|
+ ]);
|
|
|
+ console.log('图片API可用性检查完成:', checkResult);
|
|
|
+ } catch (checkError) {
|
|
|
+ console.warn('API可用性检查失败:', checkError.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('微信SDK初始化失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '微信SDK初始化失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.wechatInitializing = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // === 头像处理相关方法 ===
|
|
|
+
|
|
|
+ // 头像上传处理(主要入口)
|
|
|
+ handlePhotoUpload() {
|
|
|
+ if (this.isWechatBrowser) {
|
|
|
+ if (this.wechatSDK.isInitialized) {
|
|
|
+ this.chooseWechatImage();
|
|
|
+ } else {
|
|
|
+ this.showWechatInitDialog();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.chooseImage();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 头像点击处理(已有新头像时)
|
|
|
+ handleAvatarClick() {
|
|
|
+ uni.showActionSheet({
|
|
|
+ itemList: ['预览头像', '更换头像', '删除头像'],
|
|
|
+ success: (res) => {
|
|
|
+ switch (res.tapIndex) {
|
|
|
+ case 0:
|
|
|
+ this.handleAvatarPreview();
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ this.handleAvatarReselect();
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ this.handleAvatarDelete();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 预览头像
|
|
|
+ handleAvatarPreview() {
|
|
|
+ if (this.newAvatar) {
|
|
|
+ uni.previewImage({
|
|
|
+ urls: [this.newAvatar],
|
|
|
+ current: this.newAvatar,
|
|
|
+ success: () => {
|
|
|
+ console.log('头像预览打开成功');
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('预览头像失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '预览失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重新选择头像
|
|
|
+ handleAvatarReselect() {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认操作',
|
|
|
+ content: '确定要重新选择头像吗?当前头像将被替换。',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ this.handlePhotoUpload();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除头像
|
|
|
+ handleAvatarDelete() {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认删除',
|
|
|
+ content: '确定要删除当前头像吗?删除后无法恢复。',
|
|
|
+ confirmColor: '#ff5722',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ this.clearAvatarData();
|
|
|
+ uni.showToast({
|
|
|
+ title: '头像已删除',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清空头像数据
|
|
|
+ clearAvatarData() {
|
|
|
+ this.newAvatar = '';
|
|
|
+ this.uploadProgress = 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示微信初始化对话框
|
|
|
+ showWechatInitDialog() {
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '微信SDK未初始化,是否立即初始化?',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ this.initWechatSDK();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示非微信环境提示
|
|
|
+ showNonWechatTip() {
|
|
|
+ uni.showModal({
|
|
|
+ title: '环境提示',
|
|
|
+ content:
|
|
|
+ '建议在微信中使用此功能以获得最佳体验。\n\n在微信中打开此页面可以:\n• 直接选择相册图片\n• 拍照上传\n• 图片预览和编辑',
|
|
|
+ showCancel: false,
|
|
|
+ confirmText: '我知道了',
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择微信图片
|
|
|
+ async chooseWechatImage() {
|
|
|
+ if (!this.wechatSDK.isInitialized) {
|
|
|
+ console.log('请先初始化微信SDK');
|
|
|
+ await this.initWechatSDK();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.wechatImageLoading = true;
|
|
|
+ this.uploadProgress = 0;
|
|
|
+ console.log('正在打开微信图片选择器...');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await this.wechatSDK.chooseImage({
|
|
|
+ count: 1, // 头像只选择一张
|
|
|
+ sizeType: ['original', 'compressed'],
|
|
|
+ sourceType: ['album', 'camera'],
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('✅ 微信图片选择成功,共', res.localIds.length, '张');
|
|
|
+
|
|
|
+ // 将选择的图片设置为头像预览
|
|
|
+ if (res.localIds.length > 0) {
|
|
|
+ this.newAvatar = res.localIds[0];
|
|
|
+ console.log('📷 已设置头像预览');
|
|
|
+
|
|
|
+ // 显示上传提示
|
|
|
+ uni.showToast({
|
|
|
+ title: '正在上传头像...',
|
|
|
+ icon: 'loading',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 自动上传图片到微信服务器并保存到OSS
|
|
|
+ await this.uploadWechatImageToOSS(res.localIds[0]);
|
|
|
+
|
|
|
+ // 上传成功提示
|
|
|
+ uni.showToast({
|
|
|
+ title: '头像上传成功!',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 1500,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('选择微信图片失败:', error);
|
|
|
+
|
|
|
+ // 显示错误提示
|
|
|
+ uni.showModal({
|
|
|
+ title: '选择图片失败',
|
|
|
+ content: error.message || '请检查微信环境或网络连接',
|
|
|
+ showCancel: false,
|
|
|
+ confirmText: '我知道了',
|
|
|
+ });
|
|
|
+
|
|
|
+ // 重置头像预览
|
|
|
+ this.newAvatar = '';
|
|
|
+ } finally {
|
|
|
+ this.wechatImageLoading = false;
|
|
|
+ this.uploadProgress = 0;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传微信图片到OSS
|
|
|
+ async uploadWechatImageToOSS(localId) {
|
|
|
+ try {
|
|
|
+ console.log('📤 开始上传微信图片,localId:', localId);
|
|
|
+
|
|
|
+ // 步骤1:上传到微信服务器(20%进度)
|
|
|
+ this.uploadProgress = 10;
|
|
|
+ console.log('📤 正在上传到微信服务器...');
|
|
|
+
|
|
|
+ const wxUploadRes = await new Promise((resolve, reject) => {
|
|
|
+ wx.uploadImage({
|
|
|
+ localId: localId,
|
|
|
+ isShowProgressTips: 0, // 关闭微信自带的进度提示
|
|
|
+ success: resolve,
|
|
|
+ fail: reject,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ const mediaId = wxUploadRes.serverId;
|
|
|
+ this.uploadProgress = 30;
|
|
|
+ console.log('✅ 微信服务器上传成功,mediaId:', mediaId);
|
|
|
+
|
|
|
+ // 步骤2:保存到OSS(80%进度)
|
|
|
+ this.uploadProgress = 50;
|
|
|
+ console.log('💾 正在保存到云端存储...');
|
|
|
+
|
|
|
+ const ossResponse = await request({
|
|
|
+ url: '/system/wechat/downloadToOss',
|
|
|
+ method: 'GET',
|
|
|
+ params: {
|
|
|
+ mediaId: mediaId,
|
|
|
+ },
|
|
|
+ timeout: 30000, // 30秒超时
|
|
|
+ });
|
|
|
+
|
|
|
+ if (ossResponse.code === 200) {
|
|
|
+ const ossResult = ossResponse.data;
|
|
|
+ this.uploadProgress = 90;
|
|
|
+
|
|
|
+ console.log('🎉 头像保存成功!', ossResult);
|
|
|
+
|
|
|
+ // 更新新头像URL
|
|
|
+ this.newAvatar = ossResult.url;
|
|
|
+
|
|
|
+ // 上传进度完成
|
|
|
+ this.uploadProgress = 100;
|
|
|
+
|
|
|
+ return ossResult;
|
|
|
+ } else {
|
|
|
+ throw new Error(ossResponse.msg || '保存头像到云端存储失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('上传微信图片失败:', error);
|
|
|
+
|
|
|
+ // 上传失败时重置进度
|
|
|
+ this.uploadProgress = 0;
|
|
|
+
|
|
|
+ // 显示详细错误信息
|
|
|
+ let errorMsg = '头像上传失败,请重试';
|
|
|
+ if (error.message.includes('timeout')) {
|
|
|
+ errorMsg = '上传超时,请检查网络连接后重试';
|
|
|
+ } else if (error.message.includes('mediaId')) {
|
|
|
+ errorMsg = '微信服务器上传失败,请重试';
|
|
|
+ } else if (error.message.includes('OSS')) {
|
|
|
+ errorMsg = '保存到云端存储失败,请重试';
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: errorMsg,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择图片(非微信环境)
|
|
|
+ chooseImage() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ sizeType: ['compressed'],
|
|
|
+ sourceType: ['album'],
|
|
|
+ success: (res) => {
|
|
|
+ this.handleImageSelected(res.tempFilePaths[0]);
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('选择图片失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '选择图片失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 拍照
|
|
|
+ takePhoto() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ sizeType: ['compressed'],
|
|
|
+ sourceType: ['camera'],
|
|
|
+ success: (res) => {
|
|
|
+ this.handleImageSelected(res.tempFilePaths[0]);
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('拍照失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '拍照失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理选中的图片
|
|
|
+ handleImageSelected(filePath) {
|
|
|
+ // 检查文件大小
|
|
|
+ uni.getFileInfo({
|
|
|
+ filePath: filePath,
|
|
|
+ success: (res) => {
|
|
|
+ const fileSizeInMB = res.size / (1024 * 1024);
|
|
|
+ if (fileSizeInMB > 10) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '图片大小不能超过10MB',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 压缩图片
|
|
|
+ this.compressImage(filePath);
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('获取文件信息失败:', error);
|
|
|
+ // 直接使用原图
|
|
|
+ this.newAvatar = filePath;
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 压缩图片
|
|
|
+ compressImage(filePath) {
|
|
|
+ // 检查是否支持压缩图片API
|
|
|
+ if (typeof uni.compressImage === 'function' && uni.compressImage) {
|
|
|
+ try {
|
|
|
+ uni.compressImage({
|
|
|
+ src: filePath,
|
|
|
+ quality: 80,
|
|
|
+ success: (res) => {
|
|
|
+ console.log('图片压缩成功:', res.tempFilePath);
|
|
|
+ this.newAvatar = res.tempFilePath;
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.warn('压缩图片失败,使用原图:', error);
|
|
|
+ // 使用原图
|
|
|
+ this.newAvatar = filePath;
|
|
|
+ },
|
|
|
+ complete: () => {
|
|
|
+ // 确保有值
|
|
|
+ if (!this.newAvatar) {
|
|
|
+ this.newAvatar = filePath;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('compressImage API调用异常,使用原图:', error);
|
|
|
+ this.newAvatar = filePath;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('当前环境不支持图片压缩API,使用原图');
|
|
|
+ // 不支持压缩时直接使用原图
|
|
|
+ this.newAvatar = filePath;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理头像加载错误
|
|
|
+ handleAvatarError() {
|
|
|
+ this.currentAvatar = '/static/111.png';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 提交头像
|
|
|
+ async handleSubmit() {
|
|
|
+ if (!this.newAvatar) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请先选择头像',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isSubmitting = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ let avatarUrl = this.newAvatar;
|
|
|
+
|
|
|
+ // 如果不是微信上传的图片,需要先上传到服务器
|
|
|
+ if (!this.newAvatar.startsWith('http')) {
|
|
|
+ const uploadResult = await this.uploadAvatar(this.newAvatar);
|
|
|
+ avatarUrl = uploadResult.url;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新用户信息
|
|
|
+ await api.updateUserAvatar({
|
|
|
+ avatar: avatarUrl,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新本地用户信息
|
|
|
+ userManager.updateUserInfo({
|
|
|
+ avatar: avatarUrl,
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '头像修改成功',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 延迟返回上一页
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.navigateBack();
|
|
|
+ }, 2000);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('修改头像失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: error.message || '修改头像失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.isSubmitting = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传头像到服务器(非微信环境使用)
|
|
|
+ uploadAvatar(filePath) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const token = uni.getStorageSync('access_token');
|
|
|
+ const clientid = uni.getStorageSync('clientid') || 'mobile';
|
|
|
+
|
|
|
+ // 构建完整URL
|
|
|
+ let fullUrl;
|
|
|
+ if (REQUEST_CONFIG.baseURL.startsWith('/')) {
|
|
|
+ // 开发环境,baseURL是'/api',需要使用完整的服务器URL
|
|
|
+ fullUrl = `http://localhost:8080${REQUEST_CONFIG.baseURL}/system/user/profile/avatar`;
|
|
|
+ } else {
|
|
|
+ // 生产环境,直接拼接
|
|
|
+ fullUrl = REQUEST_CONFIG.baseURL + '/system/user/profile/avatar';
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.uploadFile({
|
|
|
+ url: fullUrl,
|
|
|
+ filePath: filePath,
|
|
|
+ name: 'avatarfile',
|
|
|
+ header: {
|
|
|
+ authorization: token ? `Bearer ${token}` : '',
|
|
|
+ clientid: clientid,
|
|
|
+ },
|
|
|
+ success: (res) => {
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(res.data);
|
|
|
+ if (data.code === 200) {
|
|
|
+ resolve(data.data);
|
|
|
+ } else {
|
|
|
+ reject(new Error(data.msg || '上传失败'));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ reject(new Error('解析响应失败'));
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ reject(new Error('上传失败'));
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #f5f6fa;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.main-content {
|
|
|
+ flex: 1;
|
|
|
+ padding: 20rpx;
|
|
|
+ padding-bottom: 40rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-container {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.current-avatar-section,
|
|
|
+.new-avatar-section {
|
|
|
+ padding: 30rpx;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-loading {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 200rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ border: 2rpx dashed #d9d9d9;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #fafafa;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ margin-top: 16rpx;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-text {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #1890ff;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-wrapper {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+ cursor: pointer;
|
|
|
+ margin: 0 auto;
|
|
|
+ display: block;
|
|
|
+
|
|
|
+ &:hover .avatar-overlay {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-image {
|
|
|
+ width: 200rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 3rpx solid #ffffff;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
|
+ display: block;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-overlay {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 200rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ opacity: 0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.overlay-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.overlay-text {
|
|
|
+ color: rgba(255, 255, 255, 0.9);
|
|
|
+ font-size: 20rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-badge {
|
|
|
+ position: absolute;
|
|
|
+ top: -6rpx;
|
|
|
+ right: calc(50% - 100rpx + 6rpx);
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-placeholder {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 200rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ border: 2rpx dashed #d9d9d9;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #fafafa;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #8c939d;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-note {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #c0c4cc;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-tips {
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background: rgba(24, 144, 255, 0.05);
|
|
|
+ border-radius: 8rpx;
|
|
|
+ border: 1px solid rgba(24, 144, 255, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.tip-item {
|
|
|
+ display: block;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-quick-actions {
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20rpx;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.submit-section {
|
|
|
+ padding: 30rpx;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.submit-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 88rpx;
|
|
|
+ background: linear-gradient(135deg, #52c41a, #389e0d);
|
|
|
+ border: none;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: scale(0.98);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ background: #cccccc;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.btn-loading {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-text {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-tip {
|
|
|
+ margin: 20rpx 30rpx;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background-color: #fffbe6;
|
|
|
+ border: 1px solid #ffe58f;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #faad14;
|
|
|
+}
|
|
|
+
|
|
|
+.tip-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #faad14;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 375px) {
|
|
|
+ .main-content {
|
|
|
+ padding: 16rpx 12rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-container {
|
|
|
+ padding: 20rpx 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-image {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-wrapper {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-overlay {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-placeholder {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|