feat(profile): implement independent email update dialog to prevent accidental password resets during partial updates
This commit is contained in:
@ -414,3 +414,55 @@ def change_my_password():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Change Password Failed: {str(e)}")
|
current_app.logger.error(f"Change Password Failed: {str(e)}")
|
||||||
return jsonify({'msg': f'密码修改失败: {str(e)}'}), 500
|
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
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
import { InfoFilled, SwitchButton, UserFilled, Lock, User, ArrowDown } from '@element-plus/icons-vue'
|
import { InfoFilled, SwitchButton, UserFilled, Lock, User, ArrowDown } from '@element-plus/icons-vue'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { getMyProfile, changeMyPassword } from '@/api/auth'
|
import { getMyProfile, changeMyPassword, updateMyEmail } from '@/api/auth'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -34,13 +34,15 @@ interface ProfileData {
|
|||||||
username: string
|
username: string
|
||||||
display_name: string
|
display_name: string
|
||||||
department: string
|
department: string
|
||||||
|
email: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileForm = ref<ProfileData>({
|
const profileForm = ref<ProfileData>({
|
||||||
id: 0,
|
id: 0,
|
||||||
username: '',
|
username: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
department: ''
|
department: '',
|
||||||
|
email: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const passwordForm = ref({
|
const passwordForm = ref({
|
||||||
@ -50,6 +52,62 @@ const passwordForm = ref({
|
|||||||
|
|
||||||
const passwordFormRef = ref()
|
const passwordFormRef = ref()
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// 绑定/修改邮箱
|
||||||
|
// ================================================================
|
||||||
|
const emailDialogVisible = ref(false)
|
||||||
|
const emailLoading = ref(false)
|
||||||
|
const emailFormRef = ref()
|
||||||
|
|
||||||
|
interface EmailForm {
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailForm = ref<EmailForm>({
|
||||||
|
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 () => {
|
const openProfileDialog = async () => {
|
||||||
profileDialogVisible.value = true
|
profileDialogVisible.value = true
|
||||||
@ -210,6 +268,12 @@ const handleLogout = () => {
|
|||||||
<!-- 【严格脱敏】系统角色字段已移除,不在此展示 -->
|
<!-- 【严格脱敏】系统角色字段已移除,不在此展示 -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="margin: 16px 0; text-align: center;">
|
||||||
|
<el-button type="primary" plain @click="openEmailDialog">
|
||||||
|
绑定/修改邮箱
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-divider>
|
<el-divider>
|
||||||
<el-icon><Lock /></el-icon> 修改密码
|
<el-icon><Lock /></el-icon> 修改密码
|
||||||
</el-divider>
|
</el-divider>
|
||||||
@ -260,6 +324,19 @@ const handleLogout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 绑定/修改邮箱弹窗 -->
|
||||||
|
<el-dialog v-model="emailDialogVisible" title="绑定/修改邮箱" width="400px" @close="resetEmailForm">
|
||||||
|
<el-form :model="emailForm" :rules="emailRules" ref="emailFormRef" label-width="80px">
|
||||||
|
<el-form-item label="新邮箱" prop="email">
|
||||||
|
<el-input v-model="emailForm.email" placeholder="请输入有效邮箱地址" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="emailDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="emailLoading" @click="submitEmailUpdate">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -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[]) {
|
export function batchCreateUser(data: any[]) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
Reference in New Issue
Block a user