From e79d599da1d04d03283d926024dc9c390152086d Mon Sep 17 00:00:00 2001 From: luo Date: Mon, 28 Sep 2026 11:17:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E5=85=A8=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design.md | 33 ++++ .../proposal.md | 21 +++ .../specs/admin-authentication/spec.md | 33 ++++ .../add-self-service-password-change/tasks.md | 13 ++ src/api/modules/auth.ts | 5 +- .../art-header-bar/ChangePasswordDialog.vue | 151 ++++++++++++++++++ .../core/layouts/art-header-bar/index.vue | 17 ++ .../core/layouts/art-header-bar/style.scss | 17 ++ src/locales/langs/en.json | 14 ++ src/locales/langs/zh.json | 14 ++ src/types/api/auth.ts | 5 +- src/types/components.d.ts | 1 + 12 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 openspec/changes/add-self-service-password-change/design.md create mode 100644 openspec/changes/add-self-service-password-change/proposal.md create mode 100644 openspec/changes/add-self-service-password-change/specs/admin-authentication/spec.md create mode 100644 openspec/changes/add-self-service-password-change/tasks.md create mode 100644 src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue diff --git a/openspec/changes/add-self-service-password-change/design.md b/openspec/changes/add-self-service-password-change/design.md new file mode 100644 index 0000000..a90d4a0 --- /dev/null +++ b/openspec/changes/add-self-service-password-change/design.md @@ -0,0 +1,33 @@ +## Context + +头像菜单位于 `src/components/core/layouts/art-header-bar/index.vue`,当前“个人中心”通过路由跳转,“锁定屏幕”通过事件打开。`AuthService.changePassword` 已封装 `PUT /api/auth/password`,但当前 TypeScript 参数 `ChangePasswordParams` 使用 `oldPassword`、`newPassword`、`confirmPassword`,与 OpenAPI 的 `old_password`、`new_password` 不一致。HTTP 层只序列化 JSON,不会将 camelCase 自动转换成 snake_case。 + +## Goals / Non-Goals + +- Goals: 提供头像菜单入口和简单的自助修改密码流程;遵循给定 OpenAPI 请求契约;不增加权限编码或权限判断。 +- Non-Goals: 修改后端接口、增加路由、改变锁屏行为、增加权限管理配置。 + +## Decisions + +- Decision: 使用头像菜单中的对话框承载表单,不为此功能新增页面路由。入口依次位于个人中心、修改密码、锁定屏幕。 +- Decision: 表单收集当前密码、新密码和确认密码;确认密码仅用于客户端一致性校验,提交时仅构造 `{ old_password, new_password }`。 +- Decision: 将 API DTO 类型调整为精确表达 OpenAPI 的 snake_case 字段,避免依赖 HTTP 层不存在的字段转换。 +- Decision: 所有已登录用户均可见并可操作该入口,不使用 RBAC、按钮权限或菜单权限配置进行隐藏/拦截;API 请求仍携带现有 Bearer Token。 +- Decision: 接口成功时清空敏感表单数据、提示成功,再调用 `userStore.clearLocalState()` 清除旧 token 并跳转登录页;接口失败时展示错误反馈,不记录密码值。 + +## Risks / Trade-offs + +- 当前类型与接口字段不一致,直接复用旧 camelCase 参数会发送错误字段;以 DTO 对齐及请求验证覆盖此风险。 +- 密码策略细节未在给定 OpenAPI 中定义;本次不自行新增密码复杂度规则,沿用后端校验结果并反馈错误。 + +## Migration Plan + +无需数据迁移或后端变更。前端发布后,已登录用户可从头像菜单使用入口;回滚时可移除菜单项及对话框实现。 + +## Open Questions + +- None. + +## Session Handling + +After a successful password change, clear the current client authentication state with `userStore.clearLocalState()` and redirect to the login route. Do not attempt the logout API after changing the password because the old token may already be invalid. diff --git a/openspec/changes/add-self-service-password-change/proposal.md b/openspec/changes/add-self-service-password-change/proposal.md new file mode 100644 index 0000000..60b1987 --- /dev/null +++ b/openspec/changes/add-self-service-password-change/proposal.md @@ -0,0 +1,21 @@ +# Change: 添加自助修改密码功能 + +## Why + +当前头像菜单提供个人中心和锁定屏幕入口,但用户无法从该处修改自己的登录密码。认证服务已提供 `PUT /api/auth/password`,需要补齐可发现的前端入口和修改流程。 + +## What Changes + +- 在头像菜单的“个人中心”和“锁定屏幕”之间增加“修改密码”入口。 +- 点击后打开修改密码表单,提交当前密码和新密码至 `PUT /api/auth/password`。 +- 按 OpenAPI 使用 `old_password`、`new_password` 请求字段;确认密码(如表单提供)只在前端校验,不发送给接口。 +- 修改密码入口面向所有已登录用户,不新增权限编码、路由权限或按钮权限。 +- 修改成功后清理本地认证状态并跳转登录页,要求用户使用新密码重新登录。 +- 为新增文案提供中英文国际化。 + +## Impact + +- Affected specs: `admin-authentication` +- Affected code: `src/components/core/layouts/art-header-bar/index.vue`、`src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue`、`src/components/core/layouts/art-header-bar/style.scss`、`src/api/modules/auth.ts`、`src/types/api/auth.ts`、`src/locales/langs/zh.json`、`src/locales/langs/en.json` +- API: 已有 `PUT /api/auth/password`;请求体按 OpenAPI 发送 `old_password` 和 `new_password`,使用当前登录 Bearer Token。 +- Authorization: 不新增前端权限配置;接口仍按认证 API 的 Bearer 认证要求调用。 diff --git a/openspec/changes/add-self-service-password-change/specs/admin-authentication/spec.md b/openspec/changes/add-self-service-password-change/specs/admin-authentication/spec.md new file mode 100644 index 0000000..9d93f36 --- /dev/null +++ b/openspec/changes/add-self-service-password-change/specs/admin-authentication/spec.md @@ -0,0 +1,33 @@ +## ADDED Requirements + +### Requirement: Self-Service Password Change Entry + +The authenticated admin interface SHALL provide a password-change entry in the avatar menu between “个人中心” and “锁定屏幕”. The entry SHALL be available to every authenticated user without a frontend route, button, or menu permission check. + +#### Scenario: User opens password-change form from avatar menu + +- **GIVEN** the user is authenticated in the admin interface +- **WHEN** the user opens the avatar menu +- **THEN** the menu shows “个人中心”, “修改密码”, and “锁定屏幕” in that order +- **AND** selecting “修改密码” opens the password-change form +- **AND** the entry is not hidden or disabled based on a permission code + +### Requirement: Change Current User Password + +The password-change form SHALL collect the current password and new password, and SHALL submit them through `PUT /api/auth/password` using the current Bearer token. The request body MUST contain exactly the API fields `old_password` and `new_password`; any confirmation value used for client-side validation MUST NOT be sent. Password values MUST NOT be written to application logs. + +#### Scenario: Password change succeeds + +- **GIVEN** the user has entered a current password and matching new-password values +- **WHEN** the user submits the form and the API returns success +- **THEN** the frontend sends `old_password` and `new_password` to `PUT /api/auth/password` +- **AND** the frontend displays success feedback and clears the password form values +- **AND** the frontend clears the local authentication state and redirects the user to the login page +- **AND** the user must authenticate again with the new password + +#### Scenario: Password change is rejected + +- **GIVEN** the user submits the password-change form +- **WHEN** the API rejects the request +- **THEN** the frontend displays error feedback and does not report success +- **AND** the frontend does not log any password value diff --git a/openspec/changes/add-self-service-password-change/tasks.md b/openspec/changes/add-self-service-password-change/tasks.md new file mode 100644 index 0000000..2e9e457 --- /dev/null +++ b/openspec/changes/add-self-service-password-change/tasks.md @@ -0,0 +1,13 @@ +## 1. 头像菜单与修改密码表单 + +- [x] 1.1 在头像菜单“个人中心”和“锁定屏幕”之间添加“修改密码”入口,不设置权限判断。 +- [x] 1.2 实现修改密码表单,收集当前密码、新密码及确认密码,并在前端校验必填项与两次新密码一致。 +- [x] 1.3 调用认证 API;请求仅包含 `old_password` 和 `new_password`,成功后清空表单并提示结果,失败时显示错误反馈。 +- [x] 1.4 添加中英文菜单、表单、校验及结果提示文案。 + +## 2. API 契约与验证 + +- [x] 2.1 对齐 `ChangePasswordParams` 与 `AuthService.changePassword` 的请求 DTO,确保网络请求字段为 OpenAPI 定义的 snake_case,且不发送确认密码。 +- [x] 2.2 验证菜单顺序、表单校验、成功与失败反馈,以及入口不依赖任何权限编码。 +- [x] 2.3 运行相关类型检查和构建,处理本次改动引入的问题。 +- [x] 2.4 密码修改成功后清理本地认证状态并跳转登录页。 diff --git a/src/api/modules/auth.ts b/src/api/modules/auth.ts index 17111f9..d16980d 100644 --- a/src/api/modules/auth.ts +++ b/src/api/modules/auth.ts @@ -6,7 +6,6 @@ import { BaseService } from '../BaseService' import type { LoginParams, LoginData, - UserInfo, UserInfoResponse, RefreshTokenParams, RefreshTokenData, @@ -51,7 +50,9 @@ export class AuthService extends BaseService { * @param params 修改密码参数 */ static changePassword(params: ChangePasswordParams): Promise { - return this.put('/api/auth/password', params) + return this.put('/api/auth/password', params, { + requestOptions: { errorMessageMode: 'none' } + }) } /** diff --git a/src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue b/src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue new file mode 100644 index 0000000..d08ac05 --- /dev/null +++ b/src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue @@ -0,0 +1,151 @@ + + + diff --git a/src/components/core/layouts/art-header-bar/index.vue b/src/components/core/layouts/art-header-bar/index.vue index c51350b..96af9cd 100644 --- a/src/components/core/layouts/art-header-bar/index.vue +++ b/src/components/core/layouts/art-header-bar/index.vue @@ -158,6 +158,12 @@ {{ $t('topBar.user.userCenter') }} +
  • + +
  • {{ $t('topBar.user.lockScreen') }} @@ -175,12 +181,15 @@ +