3973abea8b
- 自建轻量 axios client(src/api/client.ts):Bearer 注入、401 清会话跳登录
(login 请求除外)、409 精确匹配"必须先修改初始密码"跳强制改密(区别于并发
冲突 409)、429/网络错误统一 ElMessage、ApiError 标准化、showErrorMessage
可opt-out——模板 utils/http 的 {code,msg,data} 信封与后端裸 HTTP 语义不符故弃用
- Pinia auth store(src/store/modules/auth.ts):token/role/tenant_slug/
must_change 落地并持久化(all-kefu-auth key),login/logout/changePassword/
会话过期/强制改密跳转(动态 import 断循环)
- 路由守卫:token 事实源判定、must_change 全站门(除改密页)、UserInfo 由登录
快照派生(后端无用户信息端点);user store 残留自愈
- 登录页实装(去除角色调试开关)、强制改密页实装;用户菜单/锁屏对接真实 logout
- 域 API 骨架:tenants/config/conversations/stats/settings(类型+端点常量)
- mask 哨兵助手:__SET__ ↔ 已设置 表单语义
- vitest 25 例(client 11/store 8/mask 6);登录页拖拽验证 reset 空引用修复
Co-Authored-By: Claude Code <noreply@anthropic.com>
123 lines
3.6 KiB
Vue
123 lines
3.6 KiB
Vue
<!-- 强制修改密码(首登引导 / 管理员重置后的 must_change 语义) -->
|
||
<template>
|
||
<div class="force-change-password flex-cc w-full h-screen">
|
||
<ElCard shadow="never" class="card">
|
||
<template #header>
|
||
<span class="card-title">首次登录请修改密码</span>
|
||
</template>
|
||
<ElForm ref="formRef" :model="formData" :rules="rules" label-position="top">
|
||
<ElFormItem prop="oldPassword" label="原密码">
|
||
<ElInput
|
||
v-model.trim="formData.oldPassword"
|
||
type="password"
|
||
placeholder="请输入原密码"
|
||
show-password
|
||
autocomplete="off"
|
||
/>
|
||
</ElFormItem>
|
||
<ElFormItem prop="newPassword" label="新密码">
|
||
<ElInput
|
||
v-model.trim="formData.newPassword"
|
||
type="password"
|
||
placeholder="请输入新密码(不少于 8 位)"
|
||
show-password
|
||
autocomplete="off"
|
||
/>
|
||
</ElFormItem>
|
||
<ElFormItem prop="confirmPassword" label="确认新密码">
|
||
<ElInput
|
||
v-model.trim="formData.confirmPassword"
|
||
type="password"
|
||
placeholder="请再次输入新密码"
|
||
show-password
|
||
autocomplete="off"
|
||
/>
|
||
</ElFormItem>
|
||
<ElButton type="primary" class="w-full" :loading="loading" @click="handleSubmit">
|
||
提交修改
|
||
</ElButton>
|
||
</ElForm>
|
||
<p class="tip">密码修改成功后方可进入后台;修改后当前会话保持有效,无需重新登录</p>
|
||
</ElCard>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||
import { useAuthStore } from '@/store/modules/auth'
|
||
|
||
defineOptions({ name: 'ForceChangePassword' })
|
||
|
||
const authStore = useAuthStore()
|
||
const router = useRouter()
|
||
|
||
const formRef = ref<FormInstance>()
|
||
const loading = ref(false)
|
||
|
||
const formData = reactive({
|
||
oldPassword: '',
|
||
newPassword: '',
|
||
confirmPassword: ''
|
||
})
|
||
|
||
const rules: FormRules = {
|
||
oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
|
||
newPassword: [
|
||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||
{ min: 8, message: '密码长度不少于 8 位', trigger: 'blur' }
|
||
],
|
||
confirmPassword: [
|
||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||
{
|
||
validator: (_rule, value, callback) => {
|
||
if (value !== formData.newPassword) {
|
||
callback(new Error('两次输入的密码不一致'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
]
|
||
}
|
||
|
||
// 提交改密(POST /api/platform/auth/change-password):
|
||
// 成功 → must_change 清零 → 进仪表盘;400 原密码不正确 / 401 会话过期
|
||
// 由 HTTP client 统一提示
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
const valid = await formRef.value.validate().catch(() => false)
|
||
if (!valid) return
|
||
|
||
loading.value = true
|
||
try {
|
||
await authStore.changePassword({
|
||
old_password: formData.oldPassword,
|
||
new_password: formData.newPassword
|
||
})
|
||
ElMessage.success('密码修改成功,即将进入后台')
|
||
await router.push('/')
|
||
} catch {
|
||
// 错误提示已由 HTTP client 统一弹出(如 400 原密码不正确)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.card {
|
||
width: 400px;
|
||
|
||
.card-title {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tip {
|
||
margin-top: 12px;
|
||
font-size: 12px;
|
||
color: var(--art-gray-500);
|
||
}
|
||
}
|
||
</style>
|