From 772f3f45f4d1ac95d12e5ce1a2f9d35e414c4661 Mon Sep 17 00:00:00 2001 From: DXC Date: Fri, 17 Apr 2026 12:48:30 +0800 Subject: [PATCH] feat(profile): implement independent email update dialog to prevent accidental password resets during partial updates --- inventory-backend/app/api/v1/auth.py | 52 ++++++++++++++++++ inventory-web/src/App.vue | 81 +++++++++++++++++++++++++++- inventory-web/src/api/auth.ts | 9 ++++ 3 files changed, 140 insertions(+), 2 deletions(-) diff --git a/inventory-backend/app/api/v1/auth.py b/inventory-backend/app/api/v1/auth.py index 6441a9f..4a3b512 100644 --- a/inventory-backend/app/api/v1/auth.py +++ b/inventory-backend/app/api/v1/auth.py @@ -414,3 +414,55 @@ def change_my_password(): except Exception as e: current_app.logger.error(f"Change Password Failed: {str(e)}") return jsonify({'msg': f'密码修改失败: {str(e)}'}), 500 + + +# ============================================================================== +# 自我更新邮箱 +# ============================================================================== +@auth_bp.route('/me/email', methods=['PUT']) +@jwt_required() +def update_my_email(): + """ + 自我更新邮箱接口 + - 仅更新 email 字段,与密码修改完全隔离 + - 防止后端意外清空用户密码 + """ + try: + from app.models.system import SysUser + + user_id = get_jwt_identity() + + # 超级管理员(user_id=0)不允许修改邮箱 + if user_id == 0: + return jsonify({'msg': '超级管理员邮箱由系统管理员管理'}), 400 + + data = request.get_json() + if not data: + return jsonify({'msg': '无效的请求数据'}), 400 + + email = data.get('email') + if not email: + return jsonify({'msg': '邮箱不能为空'}), 400 + + # 简单的邮箱格式校验 + import re + if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email): + return jsonify({'msg': '邮箱格式不正确'}), 400 + + user = SysUser.query.get(user_id) + if not user: + return jsonify({'msg': '用户不存在'}), 404 + + # 检查邮箱是否已被其他用户使用 + existing = SysUser.query.filter(SysUser.email == email, SysUser.id != user_id).first() + if existing: + return jsonify({'msg': '该邮箱已被其他用户使用'}), 400 + + user.email = email + db.session.commit() + + return jsonify({'msg': '邮箱更新成功'}), 200 + + except Exception as e: + current_app.logger.error(f"Update Email Failed: {str(e)}") + return jsonify({'msg': f'邮箱更新失败: {str(e)}'}), 500 diff --git a/inventory-web/src/App.vue b/inventory-web/src/App.vue index 7fcf8ab..f533c88 100644 --- a/inventory-web/src/App.vue +++ b/inventory-web/src/App.vue @@ -4,7 +4,7 @@ import { useRouter, useRoute } from 'vue-router' import { ElMessageBox, ElMessage } from 'element-plus' import { InfoFilled, SwitchButton, UserFilled, Lock, User, ArrowDown } from '@element-plus/icons-vue' import { useUserStore } from '@/stores/user' -import { getMyProfile, changeMyPassword } from '@/api/auth' +import { getMyProfile, changeMyPassword, updateMyEmail } from '@/api/auth' const router = useRouter() const route = useRoute() @@ -34,13 +34,15 @@ interface ProfileData { username: string display_name: string department: string + email: string } const profileForm = ref({ id: 0, username: '', display_name: '', - department: '' + department: '', + email: '' }) const passwordForm = ref({ @@ -50,6 +52,62 @@ const passwordForm = ref({ const passwordFormRef = ref() +// ================================================================ +// 绑定/修改邮箱 +// ================================================================ +const emailDialogVisible = ref(false) +const emailLoading = ref(false) +const emailFormRef = ref() + +interface EmailForm { + email: string +} + +const emailForm = ref({ + email: '' +}) + +const emailRules = { + email: [ + { required: true, message: '请输入邮箱地址', trigger: 'blur' }, + { type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] } + ] +} + +// 打开邮箱弹窗 +const openEmailDialog = () => { + emailForm.value.email = profileForm.value.email || '' + emailDialogVisible.value = true +} + +// 提交邮箱修改 +const submitEmailUpdate = async () => { + const formRef = emailFormRef.value + if (!formRef) return + + await formRef.validate(async (valid: boolean) => { + if (valid) { + emailLoading.value = true + try { + await updateMyEmail({ email: emailForm.value.email }) + ElMessage.success('邮箱绑定成功') + emailDialogVisible.value = false + // 刷新个人资料 + openProfileDialog() + } catch (e: any) { + ElMessage.error(e?.response?.data?.msg || e?.message || '绑定失败') + } finally { + emailLoading.value = false + } + } + }) +} + +// 重置表单 +const resetEmailForm = () => { + emailFormRef.value?.resetFields() +} + // 打开个人中心弹窗 const openProfileDialog = async () => { profileDialogVisible.value = true @@ -210,6 +268,12 @@ const handleLogout = () => { +
+ + 绑定/修改邮箱 + +
+ 修改密码 @@ -260,6 +324,19 @@ const handleLogout = () => { + + + + + + + + + + diff --git a/inventory-web/src/api/auth.ts b/inventory-web/src/api/auth.ts index 02ca295..d03a6c7 100644 --- a/inventory-web/src/api/auth.ts +++ b/inventory-web/src/api/auth.ts @@ -68,6 +68,15 @@ export function changeMyPassword(data: { new_password: string; confirm_password: }) } +// 【新增】自我更新邮箱(与密码修改完全隔离) +export function updateMyEmail(data: { email: string }) { + return request({ + url: '/v1/auth/me/email', + method: 'put', + data + }) +} + // 【新增】批量创建用户 export function batchCreateUser(data: any[]) { return request({