feat(auth): 用户自助修改密码
- PATCH /auth/me/password(校验原密码 + tokenVersion 吊销其它会话) - 顶栏用户下拉新增「修改密码」 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
23da579b26
commit
b03e9ef62b
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -12,6 +12,7 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
ChangePasswordForm: typeof import('./src/components/change-password-form.vue')['default']
|
||||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
@ -70,6 +71,7 @@ declare module 'vue' {
|
|||||||
|
|
||||||
// For TSX support
|
// For TSX support
|
||||||
declare global {
|
declare global {
|
||||||
|
const ChangePasswordForm: typeof import('./src/components/change-password-form.vue')['default']
|
||||||
const ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
const ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||||
const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||||
const ElButton: typeof import('element-plus/es')['ElButton']
|
const ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
|||||||
@ -21,3 +21,8 @@ export function register(data: {
|
|||||||
}): Promise<{ id: string; username: string }> {
|
}): Promise<{ id: string; username: string }> {
|
||||||
return http.post<{ id: string; username: string }>('/auth/register', data)
|
return http.post<{ id: string; username: string }>('/auth/register', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 修改本人密码。 */
|
||||||
|
export function changePassword(oldPassword: string, newPassword: string): Promise<unknown> {
|
||||||
|
return http.patch('/auth/me/password', { oldPassword, newPassword })
|
||||||
|
}
|
||||||
|
|||||||
48
src/components/change-password-form.vue
Normal file
48
src/components/change-password-form.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="model" :rules="rules" label-width="80px">
|
||||||
|
<el-form-item label="原密码" prop="oldPassword">
|
||||||
|
<el-input v-model="model.oldPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input v-model="model.newPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
|
<el-input v-model="model.confirmPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ChangePasswordForm' })
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const model = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||||
|
|
||||||
|
const rules: FormRules = {
|
||||||
|
oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{ min: 6, message: '密码长度不能少于 6 位', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (_rule, value, callback) => {
|
||||||
|
if (value !== model.newPassword) callback(new Error('两次输入的密码不一致'))
|
||||||
|
else callback()
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit(): Promise<{ oldPassword: string; newPassword: string }> {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
return { oldPassword: model.oldPassword, newPassword: model.newPassword }
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ submit })
|
||||||
|
</script>
|
||||||
@ -56,6 +56,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item command="change-password">修改密码</el-dropdown-item>
|
||||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
@ -143,6 +144,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'toggle-fullscreen'): void
|
(e: 'toggle-fullscreen'): void
|
||||||
(e: 'open-settings'): void
|
(e: 'open-settings'): void
|
||||||
(e: 'navigate', path: string): void
|
(e: 'navigate', path: string): void
|
||||||
|
(e: 'change-password'): void
|
||||||
(e: 'logout'): void
|
(e: 'logout'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@ -152,6 +154,7 @@ function onNavSelect(index: string): void {
|
|||||||
|
|
||||||
function onUserCommand(command: string): void {
|
function onUserCommand(command: string): void {
|
||||||
if (command === 'logout') emit('logout')
|
if (command === 'logout') emit('logout')
|
||||||
|
else if (command === 'change-password') emit('change-password')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
@toggle-fullscreen="toggleFullscreen"
|
@toggle-fullscreen="toggleFullscreen"
|
||||||
@open-settings="settingsOpen = true"
|
@open-settings="settingsOpen = true"
|
||||||
@navigate="handleNavigate"
|
@navigate="handleNavigate"
|
||||||
|
@change-password="handleChangePassword"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -69,6 +70,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import { useAppStore } from '@/stores/modules/app'
|
import { useAppStore } from '@/stores/modules/app'
|
||||||
import { HOME_PATH, useTabsStore } from '@/stores/modules/tabs'
|
import { HOME_PATH, useTabsStore } from '@/stores/modules/tabs'
|
||||||
import { useAuthStore } from '@/stores/modules/auth'
|
import { useAuthStore } from '@/stores/modules/auth'
|
||||||
@ -80,6 +82,9 @@ import type { LayoutSettings } from '@/config/layout'
|
|||||||
import type { ResourceNode } from '@/types/resource'
|
import type { ResourceNode } from '@/types/resource'
|
||||||
import type { BreadcrumbItem } from '@/components/ty-breadcrumb.vue'
|
import type { BreadcrumbItem } from '@/components/ty-breadcrumb.vue'
|
||||||
import { applyTheme } from '@/utils/theme'
|
import { applyTheme } from '@/utils/theme'
|
||||||
|
import { showDialog } from '@/utils/dialog'
|
||||||
|
import { changePassword } from '@/api/auth'
|
||||||
|
import ChangePasswordForm from '@/components/change-password-form.vue'
|
||||||
|
|
||||||
const app = useAppStore()
|
const app = useAppStore()
|
||||||
const tabsStore = useTabsStore()
|
const tabsStore = useTabsStore()
|
||||||
@ -231,6 +236,18 @@ async function handleLogout(): Promise<void> {
|
|||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleChangePassword(): Promise<void> {
|
||||||
|
const result = await showDialog({
|
||||||
|
component: ChangePasswordForm,
|
||||||
|
title: '修改密码',
|
||||||
|
width: '420px',
|
||||||
|
})
|
||||||
|
if (result == null) return
|
||||||
|
await changePassword(result.oldPassword, result.newPassword)
|
||||||
|
ElMessage.success('密码已修改,请重新登录')
|
||||||
|
await handleLogout()
|
||||||
|
}
|
||||||
|
|
||||||
/** 把菜单索引(path 或 code)解析为可跳转的叶子路径。 */
|
/** 把菜单索引(path 或 code)解析为可跳转的叶子路径。 */
|
||||||
function resolveTarget(index: string): string | null {
|
function resolveTarget(index: string): string | null {
|
||||||
const node = findMenu(index)
|
const node = findMenu(index)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user