refactor: 统一二级页面返回按钮样式
- 新增 page-back-btn 统一样式类,替换 dev-back-btn 和 console-back-btn - 所有二级页面返回按钮移至页面左上角 - 表单页面补充底部取消按钮 - 新增 page-navigation spec 文档 - 补充工作台 mySkills 和 skillConfig 页面标题配置
This commit is contained in:
65
openspec/specs/page-navigation/spec.md
Normal file
65
openspec/specs/page-navigation/spec.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Capability: 页面导航返回按钮
|
||||
|
||||
提供统一的二级页面返回按钮样式规范,确保用户界面一致性。
|
||||
|
||||
### Requirement: 二级页面返回按钮样式统一
|
||||
|
||||
所有二级页面必须使用统一的 `page-back-btn` 样式类名作为返回按钮。
|
||||
|
||||
#### Scenario: 返回按钮位于页面左上角
|
||||
- **WHEN** 用户访问任意二级页面
|
||||
- **THEN** 返回按钮显示在页面内容区左上角
|
||||
- **AND** 返回按钮使用 `page-back-btn` 类名
|
||||
|
||||
#### Scenario: 返回按钮样式一致性
|
||||
- **WHEN** 用户查看返回按钮
|
||||
- **THEN** 按钮显示为蓝色主题色文字
|
||||
- **AND** 按钮带有左箭头图标
|
||||
- **AND** 文字为粗体
|
||||
- **AND** 与上级页面名称关联(如"返回技能市场")
|
||||
|
||||
### Requirement: 表单页面按钮组合完整
|
||||
|
||||
表单类二级页面必须同时具有左上角返回按钮和底部取消按钮。
|
||||
|
||||
#### Scenario: 表单页面包含返回和取消按钮
|
||||
- **WHEN** 用户访问表单类二级页面(如新增、编辑页面)
|
||||
- **THEN** 页面左上角显示返回按钮
|
||||
- **AND** 页面底部显示"取消"和"确定/保存"按钮组合
|
||||
|
||||
#### Scenario: 点击返回按钮返回上级
|
||||
- **WHEN** 用户点击左上角返回按钮
|
||||
- **THEN** 页面返回至上级页面
|
||||
- **AND** 不触发任何保存操作
|
||||
|
||||
#### Scenario: 点击取消按钮返回上级
|
||||
- **WHEN** 用户点击底部取消按钮
|
||||
- **THEN** 页面返回至上级页面
|
||||
- **AND** 不触发任何保存操作
|
||||
|
||||
### Requirement: 详情页面仅保留返回按钮
|
||||
|
||||
只读详情类二级页面仅需左上角返回按钮,无需底部取消按钮。
|
||||
|
||||
#### Scenario: 详情页返回按钮
|
||||
- **WHEN** 用户访问详情类二级页面(如任务详情、审核详情)
|
||||
- **THEN** 页面左上角显示返回按钮
|
||||
- **AND** 页面底部不显示取消按钮
|
||||
|
||||
### Requirement: 废弃旧样式类名
|
||||
|
||||
`dev-back-btn` 和 `console-back-btn` 样式类名不再使用,全部替换为 `page-back-btn`。
|
||||
|
||||
#### Scenario: 样式类名替换
|
||||
- **WHEN** 代码中使用返回按钮
|
||||
- **THEN** 必须使用 `page-back-btn` 类名
|
||||
- **AND** 不再使用 `dev-back-btn` 或 `console-back-btn`
|
||||
|
||||
### Requirement: 样式定义位置
|
||||
|
||||
`page-back-btn` 样式定义在组件样式层,而非页面样式层。
|
||||
|
||||
#### Scenario: 样式文件位置
|
||||
- **WHEN** 开发者查找返回按钮样式定义
|
||||
- **THEN** 样式定义位于 `src/styles/components/_index.scss`
|
||||
- **AND** 不位于任何页面级样式文件
|
||||
@@ -7,6 +7,8 @@ export const CONSOLE_PAGES = {
|
||||
chat: { title: '智能助手', icon: 'FiMessageSquare' },
|
||||
skills: { title: '技能市场', icon: 'FaPuzzlePiece' },
|
||||
skillDetail: { title: '技能详情', icon: null },
|
||||
mySkills: { title: '我的技能', icon: 'FiBox' },
|
||||
skillConfig: { title: '技能配置', icon: null },
|
||||
logs: { title: '日志查询', icon: 'FiList' },
|
||||
scheduledTasks: { title: '定时任务', icon: 'FiClock' },
|
||||
taskDetail: { title: '任务详情', icon: null },
|
||||
|
||||
@@ -21,10 +21,15 @@ function AddDepartmentPage({ onBack, editData }) {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑部门' : '新增部门'}</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回部门列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑部门' : '新增部门'}</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="form-group">
|
||||
<label className="form-label required">部门名称</label>
|
||||
@@ -52,7 +57,8 @@ function AddDepartmentPage({ onBack, editData }) {
|
||||
<button className="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,10 +106,15 @@ function AddModelConfigPage({ onBack, editData }) {
|
||||
const configTypeList = getConfigTypeList();
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑配置' : '新增配置'}</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回配置列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑配置' : '新增配置'}</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{/* 基础信息 */}
|
||||
<div className="form-group">
|
||||
@@ -214,7 +219,8 @@ function AddModelConfigPage({ onBack, editData }) {
|
||||
<button className="btn btn-primary" onClick={handleSave}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,15 @@ function AddProjectPage({ onBack, editData }) {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑项目' : '新增项目'}</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回项目列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑项目' : '新增项目'}</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="form-group">
|
||||
<label className="form-label required">项目名称</label>
|
||||
@@ -52,7 +57,8 @@ function AddProjectPage({ onBack, editData }) {
|
||||
<button className="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,15 @@ function AddUserPage({ onBack, editData }) {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑用户' : '新增用户'}</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回用户列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">{isEdit ? '编辑用户' : '新增用户'}</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="form-group">
|
||||
<label className="form-label required">姓名</label>
|
||||
@@ -66,7 +71,8 @@ function AddUserPage({ onBack, editData }) {
|
||||
<button className="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,29 +30,35 @@ function AddMemberPage({ onBack }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div className="card-title">增加成员</div>
|
||||
<button className="btn btn-primary btn-sm" onClick={onBack}>返回列表</button>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回成员列表</span>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ListSelector
|
||||
data={availableMembers}
|
||||
selectedIds={selectedMembers}
|
||||
onChange={setSelectedMembers}
|
||||
searchPlaceholder="搜索成员姓名、部门、邮箱..."
|
||||
columns={memberColumns}
|
||||
multiSelect={true}
|
||||
selectedLabel={selectedLabel}
|
||||
onClearSelected={() => setSelectedMembers([])}
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '16px' }}>
|
||||
<button className="btn btn-primary" onClick={handleAdd} disabled={selectedMembers.length === 0}>
|
||||
添加选中成员 ({selectedMembers.length})
|
||||
</button>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">增加成员</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ListSelector
|
||||
data={availableMembers}
|
||||
selectedIds={selectedMembers}
|
||||
onChange={setSelectedMembers}
|
||||
searchPlaceholder="搜索成员姓名、部门、邮箱..."
|
||||
columns={memberColumns}
|
||||
multiSelect={true}
|
||||
selectedLabel={selectedLabel}
|
||||
onClearSelected={() => setSelectedMembers([])}
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '16px' }}>
|
||||
<button className="btn btn-secondary" onClick={onBack}>取消</button>
|
||||
<button className="btn btn-primary" onClick={handleAdd} disabled={selectedMembers.length === 0}>
|
||||
添加选中成员 ({selectedMembers.length})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { FiChevronLeft, FiFile } from 'react-icons/fi';
|
||||
import { FiFile } from 'react-icons/fi';
|
||||
import { pendingVersionReviews, pendingUnlistReviews, skillFiles } from '../../data/skills.js';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
|
||||
@@ -34,8 +34,9 @@ function ConsoleReviewDetailPage({ type, reviewId, onBack }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="console-back-btn" onClick={onBack}>
|
||||
<FiChevronLeft /> 返回审核列表
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回审核列表</span>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
function MemberConfigPage({ onBack }) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回成员列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">成员配置</div>
|
||||
<button className="btn btn-primary" onClick={onBack}>返回列表</button>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>成员配置页面内容</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>成员配置页面内容</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FiChevronLeft, FiPlus, FiX, FiUsers, FiStar, FiPackage } from 'react-icons/fi';
|
||||
import { FiPlus, FiX, FiUsers, FiStar, FiPackage } from 'react-icons/fi';
|
||||
import { skills, userSubscriptions } from '../../data/skills.js';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
|
||||
@@ -94,8 +94,9 @@ function SkillConfigPage({ subscriptionId, onBack }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dev-back-btn" onClick={onBack} style={{ marginBottom: '16px' }}>
|
||||
<FiChevronLeft /> 返回我的技能
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回我的技能</span>
|
||||
</div>
|
||||
|
||||
{/* 技能基本信息卡片 */}
|
||||
@@ -205,7 +206,10 @@ function SkillConfigPage({ subscriptionId, onBack }) {
|
||||
暂无配置项,点击右上角"新增配置"添加
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginTop: '16px', textAlign: 'right' }}>
|
||||
<div style={{ marginTop: '16px', textAlign: 'right', display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
|
||||
<button className="btn btn-secondary" onClick={onBack}>
|
||||
取消
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={handleSave}>
|
||||
保存
|
||||
</button>
|
||||
|
||||
@@ -5,24 +5,33 @@ function TaskDetailPage({ taskId, onBack }) {
|
||||
|
||||
if (!task) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">任务详情</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回任务列表</span>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>任务不存在</p>
|
||||
<button className="btn btn-primary btn-sm" onClick={onBack} style={{ marginTop: '16px' }}>返回列表</button>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">任务详情</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>任务不存在</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div className="card-title">任务详情</div>
|
||||
<button className="btn btn-primary btn-sm" onClick={onBack}>返回列表</button>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回任务列表</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">任务详情</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px', marginBottom: '24px' }}>
|
||||
<div>
|
||||
@@ -82,7 +91,8 @@ function TaskDetailPage({ taskId, onBack }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { FiUpload, FiChevronLeft } from 'react-icons/fi';
|
||||
import { FiUpload } from 'react-icons/fi';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
|
||||
function NewVersionPage({ skillName, onBack }) {
|
||||
@@ -14,8 +14,9 @@ function NewVersionPage({ skillName, onBack }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dev-back-btn" onClick={onBack}>
|
||||
<FiChevronLeft /> 返回技能详情
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回技能详情</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { FiChevronLeft, FiUpload, FiUsers, FiPackage, FiStar, FiRotateCcw } from 'react-icons/fi';
|
||||
import { FiUpload, FiUsers, FiPackage, FiStar, FiRotateCcw } from 'react-icons/fi';
|
||||
import { api } from '../../services/api.js';
|
||||
import Modal from '../../components/common/Modal.jsx';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
@@ -50,8 +50,9 @@ function SkillEditorPage({ skillId, onBack, onUploadNewVersion, onUpdateInfo })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dev-back-btn" onClick={onBack}>
|
||||
<FiChevronLeft /> 返回我的技能
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回我的技能</span>
|
||||
</div>
|
||||
|
||||
{/* 1. 开发者内部信息概览卡片 */}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FiChevronLeft } from 'react-icons/fi';
|
||||
import { useState } from 'react';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
|
||||
@@ -16,8 +15,9 @@ function UpdateSkillInfoPage({ skill, onBack }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dev-back-btn" onClick={onBack}>
|
||||
<FiChevronLeft /> 返回技能详情
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回技能详情</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -9,10 +9,15 @@ function UploadSkillPage({ onBack }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">创建技能</div>
|
||||
<>
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回技能管理</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="card-title">创建技能</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div style={{ marginBottom: '20px', padding: '12px', background: '#EFF6FF', borderRadius: '8px', color: '#1E40AF', fontSize: '14px' }}>
|
||||
<strong>提示:</strong>此处填写的信息仅供开发者自己管理使用,不会在技能商店展示。商店展示信息需要在上传版本时填写。
|
||||
@@ -30,13 +35,14 @@ function UploadSkillPage({ onBack }) {
|
||||
<button className="btn btn-primary" onClick={handleCreate}>创建技能</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Toast
|
||||
visible={showToast}
|
||||
type="success"
|
||||
message="创建成功"
|
||||
onClose={() => setShowToast(false)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FiUpload, FiChevronLeft, FiX } from 'react-icons/fi';
|
||||
import { FiUpload, FiX } from 'react-icons/fi';
|
||||
import { useState } from 'react';
|
||||
import { api } from '../../services/api.js';
|
||||
import Toast from '../../components/common/Toast.jsx';
|
||||
@@ -44,8 +44,9 @@ function UploadVersionPage({ skill, onBack }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dev-back-btn" onClick={onBack}>
|
||||
<FiChevronLeft /> 返回技能详情
|
||||
<div className="page-back-btn" onClick={onBack}>
|
||||
<span>←</span>
|
||||
<span>返回技能详情</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -16,3 +16,13 @@
|
||||
@forward 'password-input';
|
||||
@forward 'search-bar';
|
||||
@forward 'stat-card';
|
||||
|
||||
.page-back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
|
||||
@use '../tokens' as *;
|
||||
|
||||
// 开发台返回按钮
|
||||
.dev-back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
// 开发台详情
|
||||
.dev-detail-header {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user