zlwu 1 år sedan
förälder
incheckning
4a9c50891f

+ 26 - 1
mobile/src/common/request.js

@@ -38,7 +38,7 @@ function handleUnauthorized(message = '登录已失效,请重新登录') {
     const pages = getCurrentPages();
     const currentPage = pages[pages.length - 1];
     const currentPath = '/' + currentPage.route;
-    
+
     // 如果是新生报名页面,不跳转到登录页,只是静默处理
     if (currentPath === '/pages/enrollment/index') {
         console.log('📋 新生报名页面401错误,使用兜底数据,不跳转登录页');
@@ -395,6 +395,31 @@ export const api = {
             url: '/auth/logout',
             method: 'POST',
         }),
+
+    // 修改密码
+    resetPassword: (data) =>
+        request({
+            url: '/system/user/profile/updatePwd',
+            method: 'PUT',
+            header: {
+                isEncrypt: 'true', // 修复:使用字符串 'true' 而不是布尔值 true
+                repeatSubmit: false,
+            },
+            data: {
+                oldPassword: data.oldPassword,
+                newPassword: data.newPassword,
+            },
+        }),
+
+    // 更新用户头像
+    // updateUserAvatar: (data) =>
+    //     request({
+    //         url: '/system/user/profile/updateAvatar',
+    //         method: 'PUT',
+    //         data: {
+    //             avatar: data.avatar,
+    //         },
+    //     }),
 };
 
 // 便捷请求方法封装

+ 8 - 0
mobile/src/pages.json

@@ -294,6 +294,14 @@
 				"navigationBarBackgroundColor": "#1890ff",
 				"navigationBarTextStyle": "white"
 			}
+		},
+		{
+			"path": "pages/user/resetPasswd",
+			"style": {
+				"navigationBarTitleText": "修改密码",
+				"navigationBarBackgroundColor": "#0051d9",
+				"navigationBarTextStyle": "white"
+			}
 		}
 	],
 	"globalStyle": {

+ 38 - 4
mobile/src/pages/index/index.vue

@@ -460,7 +460,7 @@
             </Card> -->
 
             <!-- 系统管理 -->
-            <!-- <Card v-if="hasSystemServices">
+            <Card>
                 <template #header>
                     <view class="section-header">
                         <u-icon name="setting" size="18" color="#f5222d" />
@@ -468,7 +468,27 @@
                     </view>
                 </template>
                 <GridLayout :columns="3" gap="16px">
-                    <ServiceItem
+                    <ServiceItem title="修改密码" @click="gotoResetPassword">
+                        <template #icon>
+                            <image
+                                src="/static/00118.png"
+                                mode="aspectFit"
+                                style="width: 24px; height: 24px"
+                            />
+                        </template>
+                    </ServiceItem>
+
+                    <!-- <ServiceItem title="修改头像" @click="gotoChangeAvatar">
+                        <template #icon>
+                            <image
+                                src="/static/password.png"
+                                mode="aspectFit"
+                                style="width: 24px; height: 24px"
+                            />
+                        </template>
+                    </ServiceItem> -->
+
+                    <!-- <ServiceItem
                         v-if="hasPermi(['system:mobileuser:list'])"
                         title="用户管理"
                         @click="handleServiceClick"
@@ -530,9 +550,9 @@
                         <template #icon>
                             <u-icon name="wifi" size="24" />
                         </template>
-                    </ServiceItem>
+                    </ServiceItem> -->
                 </GridLayout>
-            </Card> -->
+            </Card>
 
             <!-- 工作流 -->
             <Card v-if="hasWorkflowServices">
@@ -946,6 +966,20 @@ export default {
             });
         },
 
+        // 跳转到修改密码页面
+        gotoResetPassword() {
+            uni.navigateTo({
+                url: '/pages/user/resetPasswd',
+            });
+        },
+
+        // 跳转到修改头像页面
+        gotoChangeAvatar() {
+            uni.navigateTo({
+                url: '/pages/user/changeaAvatar',
+            });
+        },
+
         // 图片加载成功处理
         handleImageLoad(event) {
             console.log('✅ 轮播图片加载成功:', event.detail);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 227 - 177
mobile/src/pages/internship/index.vue


+ 932 - 0
mobile/src/pages/user/changeaAvatar.vue

@@ -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>

+ 418 - 0
mobile/src/pages/user/resetPasswd.vue

@@ -0,0 +1,418 @@
+<template>
+    <view class="page-container">
+        <!-- 页面头部 -->
+        <!-- <view class="page-header">
+            <view class="header-content">
+                <view class="back-btn" @click="goBack">
+                    <u-icon name="arrow-left" size="20" color="#ffffff" />
+                </view>
+                <text class="page-title">修改密码</text>
+                <view class="placeholder"></view>
+            </view>
+        </view> -->
+
+        <!-- 主要内容 -->
+        <view class="main-content">
+            <view class="form-container">
+                <!-- 旧密码 -->
+                <view class="form-item">
+                    <view class="input-label">
+                        <u-icon name="lock" size="16" color="#666666" />
+                        <text class="label-text">旧密码</text>
+                    </view>
+                    <view class="input-wrapper">
+                        <input
+                            v-model="formData.oldPassword"
+                            type="password"
+                            placeholder="请输入旧密码"
+                            class="input-field"
+                            :class="{ 'input-error': errors.oldPassword }"
+                            @blur="validateOldPassword"
+                        />
+                        <view v-if="errors.oldPassword" class="error-message">
+                            {{ errors.oldPassword }}
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 新密码 -->
+                <view class="form-item">
+                    <view class="input-label">
+                        <u-icon name="lock-open" size="16" color="#666666" />
+                        <text class="label-text">新密码</text>
+                    </view>
+                    <view class="input-wrapper">
+                        <input
+                            v-model="formData.newPassword"
+                            type="password"
+                            placeholder="请输入新密码"
+                            class="input-field"
+                            :class="{ 'input-error': errors.newPassword }"
+                            @blur="validateNewPassword"
+                        />
+                        <view v-if="errors.newPassword" class="error-message">
+                            {{ errors.newPassword }}
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 确认新密码 -->
+                <view class="form-item">
+                    <view class="input-label">
+                        <u-icon name="checkmark-circle" size="16" color="#666666" />
+                        <text class="label-text">确认新密码</text>
+                    </view>
+                    <view class="input-wrapper">
+                        <input
+                            v-model="formData.confirmPassword"
+                            type="password"
+                            placeholder="请再次输入新密码"
+                            class="input-field"
+                            :class="{ 'input-error': errors.confirmPassword }"
+                            @blur="validateConfirmPassword"
+                        />
+                        <view v-if="errors.confirmPassword" class="error-message">
+                            {{ errors.confirmPassword }}
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 提交按钮 -->
+                <view class="submit-section">
+                    <button
+                        class="submit-btn"
+                        :class="{ 'btn-loading': isSubmitting }"
+                        :disabled="isSubmitting || !isFormValid"
+                        @click="handleSubmit"
+                    >
+                        <u-icon v-if="isSubmitting" name="loading" size="16" color="#ffffff" />
+                        <text class="btn-text">{{ isSubmitting ? '提交中...' : '确认修改' }}</text>
+                    </button>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { api } from '@/common/request';
+
+export default {
+    name: 'ResetPassword',
+    data() {
+        return {
+            formData: {
+                oldPassword: '',
+                newPassword: '',
+                confirmPassword: '',
+            },
+            errors: {
+                oldPassword: '',
+                newPassword: '',
+                confirmPassword: '',
+            },
+            isSubmitting: false,
+        };
+    },
+    computed: {
+        isFormValid() {
+            return (
+                this.formData.oldPassword.trim() !== '' &&
+                this.formData.newPassword.trim() !== '' &&
+                this.formData.confirmPassword.trim() !== '' &&
+                !this.errors.oldPassword &&
+                !this.errors.newPassword &&
+                !this.errors.confirmPassword
+            );
+        },
+    },
+    methods: {
+        // 返回上一页
+        goBack() {
+            uni.navigateBack();
+        },
+
+        // 验证旧密码
+        validateOldPassword() {
+            if (!this.formData.oldPassword.trim()) {
+                this.errors.oldPassword = '请输入旧密码';
+                return false;
+            }
+            this.errors.oldPassword = '';
+            return true;
+        },
+
+        // 验证新密码
+        validateNewPassword() {
+            const password = this.formData.newPassword.trim();
+
+            if (!password) {
+                this.errors.newPassword = '请输入新密码';
+                return false;
+            }
+
+            if (password.length < 6) {
+                this.errors.newPassword = '密码长度不能少于6位';
+                return false;
+            }
+
+            if (password.length > 20) {
+                this.errors.newPassword = '密码长度不能超过20位';
+                return false;
+            }
+
+            // 检查是否包含数字和字母
+            const hasNumber = /\d/.test(password);
+            const hasLetter = /[a-zA-Z]/.test(password);
+
+            if (!hasNumber || !hasLetter) {
+                this.errors.newPassword = '密码必须包含数字和字母';
+                return false;
+            }
+
+            this.errors.newPassword = '';
+            return true;
+        },
+
+        // 验证确认密码
+        validateConfirmPassword() {
+            if (!this.formData.confirmPassword.trim()) {
+                this.errors.confirmPassword = '请确认新密码';
+                return false;
+            }
+
+            if (this.formData.newPassword !== this.formData.confirmPassword) {
+                this.errors.confirmPassword = '两次输入的密码不一致';
+                return false;
+            }
+
+            this.errors.confirmPassword = '';
+            return true;
+        },
+
+        // 提交表单
+        async handleSubmit() {
+            // 验证所有字段
+            const isOldValid = this.validateOldPassword();
+            const isNewValid = this.validateNewPassword();
+            const isConfirmValid = this.validateConfirmPassword();
+
+            if (!isOldValid || !isNewValid || !isConfirmValid) {
+                return;
+            }
+
+            if (!this.isFormValid) {
+                uni.showToast({
+                    title: '请完善表单信息',
+                    icon: 'none',
+                    duration: 2000,
+                });
+                return;
+            }
+
+            this.isSubmitting = true;
+
+            try {
+                // 调用修改密码API
+                await api.resetPassword({
+                    oldPassword: this.formData.oldPassword,
+                    newPassword: this.formData.newPassword,
+                });
+
+                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;
+            }
+        },
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    display: flex;
+    flex-direction: column;
+}
+
+/* 页面头部 */
+.page-header {
+    background: linear-gradient(135deg, #007aff, #0051d9);
+    padding: calc(var(--status-bar-height) + 12px) 16px 16px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.header-content {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.back-btn {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(255, 255, 255, 0.2);
+    transition: all 0.3s ease;
+
+    &:active {
+        background: rgba(255, 255, 255, 0.3);
+        transform: scale(0.95);
+    }
+}
+
+.page-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #ffffff;
+    flex: 1;
+    text-align: center;
+}
+
+.placeholder {
+    width: 40px;
+}
+
+/* 主要内容 */
+.main-content {
+    flex: 1;
+    padding: 20px 16px;
+}
+
+.form-container {
+    background-color: #ffffff;
+    border-radius: 12px;
+    padding: 24px 20px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.form-item {
+    margin-bottom: 24px;
+}
+
+.input-label {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 8px;
+}
+
+.label-text {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+}
+
+.input-wrapper {
+    position: relative;
+}
+
+.input-field {
+    width: 100%;
+    height: 48px;
+    padding: 0 16px;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    font-size: 16px;
+    background-color: #ffffff;
+    transition: all 0.3s ease;
+    box-sizing: border-box;
+
+    &:focus {
+        border-color: #007aff;
+        box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+    }
+
+    &.input-error {
+        border-color: #ff4d4f;
+        box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1);
+    }
+
+    &::placeholder {
+        color: #999999;
+    }
+}
+
+.error-message {
+    font-size: 14px;
+    color: #ff4d4f;
+    margin-top: 4px;
+    padding-left: 4px;
+}
+
+.submit-section {
+    margin-top: 32px;
+}
+
+.submit-btn {
+    width: 100%;
+    height: 48px;
+    background: linear-gradient(135deg, #007aff, #0051d9);
+    border: none;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    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: 16px;
+    font-weight: 600;
+    color: #ffffff;
+}
+
+/* 响应式设计 */
+@media (max-width: 375px) {
+    .main-content {
+        padding: 16px 12px;
+    }
+
+    .form-container {
+        padding: 20px 16px;
+    }
+
+    .input-field {
+        height: 44px;
+        font-size: 15px;
+    }
+
+    .submit-btn {
+        height: 44px;
+    }
+}
+</style>

+ 2 - 2
mobile/src/pages/xg/xsjcxx/xsws/stuMsgDialog.vue

@@ -78,9 +78,9 @@
                 </view>
 
                 <!-- 快捷操作按钮 -->
-                 <view class="photo-quick-actions">
+                <view class="photo-quick-actions">
                     <view class="placeholder-tips">
-                            <text class="tip-item">• 建议尺寸:2寸证件照 • 支持格式:JPG、PNG • 文件大小:不超过10MB</text>
+                        <text class="tip-item">• 建议尺寸:2寸证件照 • 支持格式:JPG、PNG • 文件大小:不超过10MB</text>
                     </view>
                     <!-- <u-button
                         size="small"

+ 0 - 0
mobile/src/static/logo-2.png → mobile/src/static/logo-22.png


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
mobile/src/static/logo.svg


BIN
mobile/src/static/password.png


+ 1 - 1
mobile/src/utils/user.js

@@ -107,7 +107,7 @@ export const userManager = {
 
         return {
             name: this.getDisplayName(),
-            avatar: user.avatar || '/static/logo-2.png',
+            avatar: user.avatar || '/static/logo.svg',
             deptName: user.deptName || '未设置部门',
             status: user.status,
             loginDate: user.loginDate,

Vissa filer visades inte eftersom för många filer har ändrats