feat: 统一三端账号管理页面

- 新增共享账号管理组件 src/components/account/AccountPage.jsx
- 管理台新增账号管理入口(修复 SidebarUser onClick)
- 开发台使用共享组件替换占位符页面
- 扩展 api.user 支持 updateProfile 和 changePassword
- 新增 account-management 规格文件
- 更新 page-navigation 规格文件
This commit is contained in:
2026-03-26 19:33:58 +08:00
parent bc4537b3bc
commit ce9ebe5784
10 changed files with 147 additions and 36 deletions

View File

@@ -0,0 +1,93 @@
# Capability: 账号管理
提供账号管理功能,允许用户查看和编辑个人信息、修改密码。
## Purpose
账号管理页面允许当前登录用户管理自己的个人信息,包括查看账号信息、编辑基本信息、修改密码。三端(工作台、管理台、开发台)共享同一个账号管理组件。
## Requirements
### Requirement: 账号信息展示
系统 SHALL 在账号管理页面展示当前登录用户的账号信息,包括头像、用户名、姓名、邮箱、手机号、所属部门。
#### Scenario: 展示用户头像
- **WHEN** 用户进入账号管理页面
- **THEN** 系统显示用户头像(首字母)
- **AND** 头像使用渐变背景色
#### Scenario: 展示基本信息
- **WHEN** 用户进入账号管理页面
- **THEN** 系统显示用户名、姓名、邮箱、手机号、所属部门
- **AND** 用户名字段为只读状态
- **AND** 所属部门字段为只读状态
### Requirement: 编辑基本信息
系统 SHALL 允许用户编辑姓名、邮箱、手机号等可修改的基本信息。
#### Scenario: 编辑姓名
- **WHEN** 用户修改姓名字段
- **THEN** 输入框可编辑
- **AND** 点击保存按钮后显示保存成功提示
#### Scenario: 编辑邮箱
- **WHEN** 用户修改邮箱字段
- **THEN** 输入框可编辑
- **AND** 点击保存按钮后显示保存成功提示
#### Scenario: 编辑手机号
- **WHEN** 用户修改手机号字段
- **THEN** 输入框可编辑
- **AND** 点击保存按钮后显示保存成功提示
#### Scenario: 保存成功提示
- **WHEN** 用户点击"保存修改"按钮
- **THEN** 系统显示 Toast 提示"保存成功"
- **AND** Toast 在 3 秒后自动消失
### Requirement: 修改密码
系统 SHALL 允许用户通过输入当前密码和新密码来修改登录密码。
#### Scenario: 显示修改密码表单
- **WHEN** 用户进入账号管理页面
- **THEN** 系统显示修改密码卡片
- **AND** 包含当前密码、新密码、确认密码三个输入框
#### Scenario: 密码验证 - 当前密码为空
- **WHEN** 用户未输入当前密码
- **AND** 用户点击"更新密码"按钮
- **THEN** 系统显示错误提示"请输入当前密码"
#### Scenario: 密码验证 - 新密码为空
- **WHEN** 用户未输入新密码
- **AND** 用户点击"更新密码"按钮
- **THEN** 系统显示错误提示"请输入新密码"
#### Scenario: 密码验证 - 确认密码为空
- **WHEN** 用户未输入确认密码
- **AND** 用户点击"更新密码"按钮
- **THEN** 系统显示错误提示"请再次输入新密码"
#### Scenario: 密码验证 - 两次密码不一致
- **WHEN** 用户输入的新密码与确认密码不一致
- **AND** 用户点击"更新密码"按钮
- **THEN** 系统显示错误提示"两次输入的密码不一致"
### Requirement: 三端统一入口
系统 SHALL 在工作台、管理台、开发台的侧边栏用户信息区域提供账号管理入口。
#### Scenario: 工作台入口
- **WHEN** 用户在工作台点击侧边栏用户信息区域
- **THEN** 系统导航到账号管理页面
#### Scenario: 管理台入口
- **WHEN** 用户在管理台点击侧边栏用户信息区域
- **THEN** 系统导航到账号管理页面
#### Scenario: 开发台入口
- **WHEN** 用户在开发台点击侧边栏用户信息区域
- **THEN** 系统导航到账号管理页面

View File

@@ -63,3 +63,18 @@
- **WHEN** 开发者查找返回按钮样式定义
- **THEN** 样式定义位于 `src/styles/components/_index.scss`
- **AND** 不位于任何页面级样式文件
### Requirement: 管理台账号管理页面导航
管理台 SHALL 在页面配置中包含账号管理页面配置。
#### Scenario: 管理台页面配置
- **WHEN** 用户查看管理台页面配置
- **THEN** 系统包含 account 页面配置
- **AND** account 页面标题为"账号管理"
- **AND** account 页面图标为 FiUser
#### Scenario: 管理台侧边栏用户点击
- **WHEN** 用户在管理台侧边栏点击用户信息区域
- **THEN** 系统导航到账号管理页面
- **AND** 页面标题显示为"账号管理"

View File

@@ -1,7 +1,9 @@
import { useState } from 'react';
import Toast from '../../components/common/Toast.jsx';
import { useUserContext } from '../../contexts/UserContext.jsx';
import Toast from '../common/Toast.jsx';
function AccountPage() {
const { user } = useUserContext();
const [profileToast, setProfileToast] = useState(null);
const [passwordErrors, setPasswordErrors] = useState({});
const [passwordForm, setPasswordForm] = useState({
@@ -43,7 +45,6 @@ function AccountPage() {
<div className="card-title">账号信息</div>
</div>
<div className="card-body">
{/* 头像区域 */}
<div style={{ textAlign: 'center', marginBottom: '24px', paddingBottom: '24px', borderBottom: '1px solid var(--color-border-2)' }}>
<div style={{
width: '100px',
@@ -56,10 +57,9 @@ function AccountPage() {
color: '#fff',
fontSize: '36px',
margin: '0 auto 12px'
}}></div>
}}>{user.avatar}</div>
<button className="btn btn-sm">更换头像</button>
</div>
{/* 表单区域 */}
<div className="form-row">
<div className="form-col">
<div className="form-group">
@@ -70,7 +70,7 @@ function AccountPage() {
<div className="form-col">
<div className="form-group">
<label className="form-label">姓名</label>
<input type="text" className="form-control" defaultValue="张三" />
<input type="text" className="form-control" defaultValue={user.name} />
</div>
</div>
</div>
@@ -90,7 +90,7 @@ function AccountPage() {
</div>
<div className="form-group">
<label className="form-label">所属部门</label>
<input type="text" className="form-control" defaultValue="AI 产品部" readOnly style={{ background: '#F8FAFC' }} />
<input type="text" className="form-control" defaultValue={user.role} readOnly style={{ background: '#F8FAFC' }} />
</div>
<button className="btn btn-primary" onClick={handleProfileSave}>保存修改</button>
</div>
@@ -152,4 +152,4 @@ function AccountPage() {
);
}
export default AccountPage;
export default AccountPage;

View File

@@ -0,0 +1,2 @@
export { default as AccountPage } from './AccountPage.jsx';
export { default } from './AccountPage.jsx';

View File

@@ -34,6 +34,7 @@ export const ADMIN_PAGES = {
addUser: { title: '新增用户', icon: null },
addProject: { title: '新增项目', icon: null },
addModelConfig: { title: '新增配置', icon: null },
account: { title: '账号管理', icon: 'FiUser' },
};
/**
@@ -45,7 +46,7 @@ export const DEVELOPER_PAGES = {
uploadSkill: { title: '创建技能', icon: 'FiPlus' },
newVersion: { title: '上传新版本', icon: null },
devDocs: { title: '开发文档', icon: 'FiTerminal' },
devAccount: { title: '开发者设置', icon: 'FiSettings' },
devAccount: { title: '账号管理', icon: 'FiSettings' },
skillEditor: { title: '技能详情', icon: null },
};

View File

@@ -20,6 +20,7 @@ import ConsoleReviewListPage from './console/ConsoleReviewListPage.jsx';
import ConsoleReviewDetailPage from './console/ConsoleReviewDetailPage.jsx';
import ModelConfigsPage from './admin/ModelConfigsPage.jsx';
import AddModelConfigPage from './admin/AddModelConfigPage.jsx';
import AccountPage from '../components/account/AccountPage.jsx';
function AdminPage() {
const location = useLocation();
@@ -106,6 +107,8 @@ function AdminPage() {
onBack={() => navigateTo('modelConfigs')}
editData={editData}
/>;
case 'account':
return <AccountPage />;
default:
return <div>Page not found</div>;
}
@@ -194,7 +197,7 @@ function AdminPage() {
/>
</nav>
<SidebarUser
onClick={() => {}}
onClick={() => navigateTo('account')}
wrapperClassName="admin-sidebar-user"
infoClassName="admin-sidebar-user-info"
nameClassName="admin-sidebar-user-name"

View File

@@ -18,7 +18,7 @@ import SkillConfigPage from './console/SkillConfigPage.jsx';
import LogsPage from './console/LogsPage.jsx';
import TasksPage from './console/TasksPage.jsx';
import TaskDetailPage from './console/TaskDetailPage.jsx';
import AccountPage from './console/AccountPage.jsx';
import AccountPage from '../components/account/AccountPage.jsx';
import ProjectsPage from './console/ProjectsPage.jsx';
import MemberConfigPage from './console/MemberConfigPage.jsx';
import AddMemberPage from './console/AddMemberPage.jsx';

View File

@@ -15,7 +15,7 @@ import MySkillsPage from './developer/MySkillsPage.jsx';
import UploadSkillPage from './developer/UploadSkillPage.jsx';
import NewVersionPage from './developer/NewVersionPage.jsx';
import DevDocsPage from './developer/DevDocsPage.jsx';
import DevAccountPage from './developer/DevAccountPage.jsx';
import AccountPage from '../components/account/AccountPage.jsx';
import SkillEditorPage from './developer/SkillEditorPage.jsx';
import UpdateSkillInfoPage from './developer/UpdateSkillInfoPage.jsx';
import UploadVersionPage from './developer/UploadVersionPage.jsx';
@@ -104,7 +104,7 @@ function DeveloperPage() {
case 'devDocs':
return <DevDocsPage />;
case 'devAccount':
return <DevAccountPage />;
return <AccountPage />;
case 'skillEditor':
return <SkillEditorPage
skillId={currentSkillId}

View File

@@ -1,24 +0,0 @@
function DevAccountPage() {
return (
<div className="card">
<div className="card-header">
<div className="card-title">开发者设置</div>
</div>
<div className="card-body">
<div style={{ display: 'flex', gap: '20px', alignItems: 'flex-start' }}>
<div className="user-avatar" style={{ width: '72px', height: '72px', fontSize: '32px' }}></div>
<div>
<h2 style={{ marginBottom: '8px' }}>张三</h2>
<div style={{ color: '#64748B', marginBottom: '12px' }}>开发者</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button className="btn btn-primary">编辑资料</button>
<button className="btn">API 密钥管理</button>
</div>
</div>
</div>
</div>
</div>
);
}
export default DevAccountPage;

View File

@@ -25,6 +25,27 @@ export const user = {
avatar: '张',
role: 'AI 产品部',
}),
/**
* 更新用户资料
* @param {Object} profile - 用户资料
* @returns {Object} 更新后的用户信息
*/
updateProfile: (profile) => {
console.log('updateProfile:', profile);
return { success: true };
},
/**
* 修改密码
* @param {string} currentPassword - 当前密码
* @param {string} newPassword - 新密码
* @returns {Object} 修改结果
*/
changePassword: (currentPassword, newPassword) => {
console.log('changePassword:', { currentPassword, newPassword });
return { success: true };
},
};
/**