feat: 统一三端账号管理页面
- 新增共享账号管理组件 src/components/account/AccountPage.jsx - 管理台新增账号管理入口(修复 SidebarUser onClick) - 开发台使用共享组件替换占位符页面 - 扩展 api.user 支持 updateProfile 和 changePassword - 新增 account-management 规格文件 - 更新 page-navigation 规格文件
This commit is contained in:
93
openspec/specs/account-management/spec.md
Normal file
93
openspec/specs/account-management/spec.md
Normal 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** 系统导航到账号管理页面
|
||||
@@ -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** 页面标题显示为"账号管理"
|
||||
|
||||
@@ -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;
|
||||
2
src/components/account/index.js
Normal file
2
src/components/account/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as AccountPage } from './AccountPage.jsx';
|
||||
export { default } from './AccountPage.jsx';
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user