Files
ai-kefu-admin/admin/src/views/auth/force-change-password/index.vue
T
浅念 3973abea8b feat: API层与认证流(client拦截/守卫/登录改密实装/域API骨架/vitest)
- 自建轻量 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>
2026-10-13 16:28:11 +08:00

123 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 强制修改密码(首登引导 / 管理员重置后的 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>