提交代码

This commit is contained in:
zlwu committed 2025-08-25 22:50:10 +08:00
1 parent 1f9b52e4e4
commit 4a9c50891f
11 files changed
+1695 -221

No files matched your search

+26 -1
View File
@@ -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
View File
@@ -294,6 +294,14 @@
"navigationBarBackgroundColor": "#1890ff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/user/resetPasswd",
"style": {
"navigationBarTitleText": "修改密码",
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
+38 -4
View File
@@ -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);
File diff suppressed because it is too large. Load diff
+932
View File
@@ -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
View File
@@ -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>
@@ -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"
File renamed without changes.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="3182" height="3197">
<g transform="matrix(1.3333334 0 0 1.3333334 0 0)">
<image x="0" y="0" xlink:href="data:image/png;base64,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 truncated
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+1 -1
View File
@@ -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,