feat: 优化技能编辑页UI布局 - 新增概览卡片、信息卡片重构

重构技能编辑页面为四层结构:技能概览卡片(两行布局)→ 详细信息卡片 → 操作按钮区 → 版本管理

主要改进:
- 技能概览卡片:左侧80x80图标,右侧两行结构(技能名称 + 关键指标)
- 关键指标图标化:👥 订阅数、📦 当前版本、 评分
- 详细信息卡片:网格布局展示状态、分类、标签、技能描述、版本说明
- 操作按钮区:独立区域,视觉层次更清晰
- 响应式设计:移动端自适应布局
This commit is contained in:
2026-03-21 13:11:04 +08:00
parent fb9616a10f
commit 07b6d99054
7 changed files with 373 additions and 53 deletions

View File

@@ -813,6 +813,58 @@ pendingUnlistReviews = [{
#### 技能详情页SkillEditorPage
**优化后的页面结构(两行布局):**
```
┌─────────────────────────────────────────────────────────┐
│ 技能编辑页面优化 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 技能概览卡片 (两行结构) │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ [图标] ┌─────────────────────────────────────┐ │ │
│ │ │ 第一行:技能名称 │ │ │
│ │ │ 第二行:👥 156 📦 v1.2.0 ⭐ 4.7 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 详细信息卡片 (普通卡片) │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ 状态:已上架 │ │
│ │ 分类:信息查询 │ │
│ │ 标签:天气 查询 生活 │ │
│ │ 技能描述:根据城市名称查询当前天气和未来预报... │ │
│ │ 版本说明新增支持未来7天预报 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 操作按钮区 │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ [更新基本信息] [下架技能] [删除技能] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 技能包管理区 (保持不变) │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ [上传新版本] 按钮 │ │
│ │ 版本历史表格 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
```
**关键改进:**
1. **两行布局设计**:左侧技能图标,右侧两行信息结构
2. **图标化指标**:👥 订阅数、📦 当前版本、⭐ 评分,使用图标库图标
3. **信息分层**
- 第一行:技能名称
- 第二行:关键指标(带图标)
4. **详细信息卡片**:包含状态、分类、标签、技能描述和版本说明
5. **优化视觉层次**:四层结构:技能概览 → 详细信息 → 操作按钮 → 版本管理
6. **简化交互**:详细信息卡片为普通卡片,不可折叠
**版本历史表格:**
| 版本号 | 版本说明 | 状态 | 更新时间 | 操作 |

View File

@@ -12,4 +12,4 @@ context: |
- 不做安全防御性编程eval/dangerouslySetInnerHTML等按需使用
- README.md是项目的开发文档记录代码结构和关键开发模式优先读取获取上下文
- 涉及页面/路由/组件/功能模块变更或技术栈调整时同步更新README.md
- Git提交: 仅中文; 格式为"类型: 简短描述",类型可选: feat(新功能)/fix(修复)/refactor(重构)/docs(文档)/style(格式)/test(测试)/chore(构建/工具); 多行描述空行后加详细说明
- Git提交: 仅中文; 格式为"类型: 简短描述",类型可选: feat(新功能)/fix(修复)/refactor(重构)/docs(文档)/style(格式)/test(测试)/chore(构建/工具); 多行描述空行后加详细说明; 禁创建git操作task

View File

@@ -1,7 +1,7 @@
## ADDED Requirements
### Requirement: 基本信息编辑表单
UpdateSkillInfoPage SHALL 提供技能基本信息的编辑表单,预填当前数据。
UpdateSkillInfoPage SHALL 提供技能基本信息的编辑表单,预填当前数据。表单功能保持不变但技能详情页的UI布局已更新。
#### Scenario: 表单预填展示
- **WHEN** 用户从技能详情页点击"更新基本信息"进入 UpdateSkillInfoPage
@@ -13,11 +13,11 @@ UpdateSkillInfoPage SHALL 提供技能基本信息的编辑表单,预填当前
#### Scenario: 提交基本信息修改
- **WHEN** 用户填写完基本信息后点击"保存修改"按钮
- **THEN** 页面展示成功提示"保存成功",并返回技能详情页
- **THEN** 页面展示成功提示"保存成功",并返回技能详情页使用新的UI布局
#### Scenario: 取消编辑
- **WHEN** 用户在基本信息编辑页面点击"取消"按钮
- **THEN** 返回技能详情页,不保存任何修改
- **THEN** 返回技能详情页使用新的UI布局,不保存任何修改
### Requirement: 技能图标选择
UpdateSkillInfoPage 和 UploadSkillPage SHALL 提供技能图标的 emoji 选择器。
@@ -31,8 +31,12 @@ UpdateSkillInfoPage 和 UploadSkillPage SHALL 提供技能图标的 emoji 选择
- **THEN** 该图标高亮选中,之前的选中项取消高亮
### Requirement: 技能图标显示
技能详情页 SHALL 在头部区域展示技能图标
技能详情页 SHALL 在技能概览卡片中展示技能图标并采用新的UI布局设计
#### Scenario: 图标展示
- **WHEN** 用户打开技能详情页
- **THEN** 技能头部区域的图标位置显示该技能选择的 emoji 图标
- **THEN** 技能概览卡片的图标位置显示该技能选择的 emoji 图标图标尺寸为80x80像素圆角16像素
#### Scenario: 图标背景样式
- **WHEN** 用户查看技能概览卡片中的图标
- **THEN** 图标具有渐变背景(从#8B5CF6到#EC4899),白色文字,与新的设计系统一致

View File

@@ -0,0 +1,35 @@
## Purpose
技能信息折叠面板用于组织技能详细信息,解决信息重复显示问题,提供更好的信息组织和用户体验。
## ADDED Requirements
### Requirement: 技能信息折叠面板
技能编辑页面 SHALL 提供可折叠的信息面板,用于组织技能详细信息,解决信息重复显示问题。
#### Scenario: 折叠面板默认状态
- **WHEN** 用户打开技能编辑页面
- **THEN** 基本信息折叠面板默认展开,显示技能分类、标签、当前版本、版本说明和技能描述
#### Scenario: 折叠面板收起/展开交互
- **WHEN** 用户点击折叠面板的"收起"按钮
- **THEN** 面板内容隐藏,按钮文字变为"展开"
#### Scenario: 折叠面板展开交互
- **WHEN** 用户点击折叠面板的"展开"按钮
- **THEN** 面板内容显示,按钮文字变为"收起"
#### Scenario: 信息网格布局
- **WHEN** 用户查看展开的折叠面板
- **THEN** 信息以网格布局展示,包含分类、标签、当前版本、版本说明和技能描述字段
#### Scenario: 标签显示格式
- **WHEN** 用户查看折叠面板中的标签字段
- **THEN** 标签以圆角标签样式显示,多个标签之间用空格分隔
#### Scenario: 版本说明显示
- **WHEN** 用户查看折叠面板中的版本说明字段
- **THEN** 显示当前版本的说明文字
#### Scenario: 技能描述显示
- **WHEN** 用户查看折叠面板中的技能描述字段
- **THEN** 显示完整的技能描述文字,支持多行显示

View File

@@ -0,0 +1,35 @@
## Purpose
技能概览卡片用于在技能编辑页面顶部集中展示技能核心信息,提供更好的信息组织和视觉体验。
## ADDED Requirements
### Requirement: 技能概览卡片
技能编辑页面 SHALL 在页面顶部显示技能概览卡片,集中展示技能核心信息。
#### Scenario: 卡片布局结构
- **WHEN** 用户打开技能编辑页面
- **THEN** 页面顶部显示技能概览卡片,包含技能图标、名称、状态标签和关键指标
#### Scenario: 技能图标显示
- **WHEN** 用户查看技能概览卡片
- **THEN** 卡片左侧显示技能图标图标尺寸为80x80像素圆角16像素
#### Scenario: 技能名称显示
- **WHEN** 用户查看技能概览卡片
- **THEN** 卡片右侧顶部显示技能名称字体大小为24px字体加粗
#### Scenario: 状态标签显示
- **WHEN** 用户查看技能概览卡片
- **THEN** 技能名称下方显示状态标签,标签样式与现有状态标签系统保持一致
#### Scenario: 关键指标显示
- **WHEN** 用户查看技能概览卡片
- **THEN** 状态标签右侧显示关键指标,包括订阅数和评分,指标之间用分隔符分隔
#### Scenario: 卡片视觉样式
- **WHEN** 用户查看技能概览卡片
- **THEN** 卡片具有白色背景、圆角12像素、轻微阴影效果与页面其他卡片样式一致
#### Scenario: 响应式布局
- **WHEN** 用户在较小屏幕设备上查看技能概览卡片
- **THEN** 卡片内容自动调整布局,确保信息清晰可读

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { FiChevronLeft, FiUpload } from 'react-icons/fi';
import { FiChevronLeft, FiUpload, FiUsers, FiPackage, FiStar } from 'react-icons/fi';
import { api } from '../../services/api.js';
import Modal from '../../components/common/Modal.jsx';
import Toast from '../../components/common/Toast.jsx';
@@ -43,70 +43,93 @@ function SkillEditorPage({ skillId, onBack, onUploadNewVersion, onUpdateInfo })
setToast({ visible: true, type: 'success', message: '已删除' });
};
const currentVersion = skill.versions && skill.versions.length > 0
? skill.versions.find(v => v.status === 'approved') || skill.versions[0]
: null;
return (
<>
<div className="dev-back-btn" onClick={onBack}>
<FiChevronLeft /> 返回我的技能
</div>
<div className="card">
{/* 1. 技能概览卡片(两行结构) */}
<div className="skill-overview-card">
<div className="skill-icon">{skill.icon || skill.name.charAt(0)}</div>
<div className="skill-header">
{/* 第一行:技能名称 */}
<h2 className="skill-name">{skill.name}</h2>
{/* 第二行:关键指标(带图标) */}
<div className="skill-metrics-row">
<div className="metric-item">
<FiUsers className="metric-icon" />
<span className="metric-value">{skill.installs || 0}</span>
</div>
<div className="metric-item">
<FiPackage className="metric-icon" />
<span className="metric-value">{skill.version || 'v1.0.0'}</span>
</div>
<div className="metric-item">
<FiStar className="metric-icon" />
<span className="metric-value">{skill.rating || 0}</span>
</div>
</div>
</div>
</div>
{/* 2. 详细信息卡片(普通卡片,不可折叠) */}
<div className="info-card">
<div className="card-header">
<div className="card-title">配置信息</div>
<h3>详细信息</h3>
</div>
<div className="card-body">
<div className="dev-detail-header">
<div className="dev-detail-icon">{skill.icon || skill.name.charAt(0)}</div>
<div className="dev-detail-main">
<h2 style={{ marginBottom: '8px' }}>{skill.name}</h2>
<div style={{ color: '#64748B', marginBottom: '12px' }}>{skill.category}</div>
<div className="dev-detail-tags">
<div className="info-grid">
<div className="info-item">
<label>状态</label>
<span className={`status ${skillStatusMap[skill.status]?.className || 'status-stopped'}`}>
{skillStatusMap[skill.status]?.text || skill.status}
</span>
</div>
<div className="info-item">
<label>分类</label>
<span>{skill.category}</span>
</div>
<div className="info-item full-width">
<label>标签</label>
<div className="tags">
{skill.tags.map(tag => (
<span key={tag} className="dev-detail-tag">{tag}</span>
))}
</div>
<div className="dev-detail-stats">
<span>当前版本: {skill.version}</span>
</div>
</div>
</div>
<div className="dev-detail-section">
<h3>基本信息</h3>
<div className="dev-info-row">
<span className="dev-info-label">技能名称</span>
<span className="dev-info-value">{skill.name}</span>
<div className="info-item full-width">
<label>技能描述</label>
<p>{skill.desc}</p>
</div>
<div className="dev-info-row">
<span className="dev-info-label">技能描述</span>
<span className="dev-info-value">{skill.desc}</span>
<div className="info-item full-width">
<label>版本说明</label>
<span>{currentVersion?.desc || '暂无版本说明'}</span>
</div>
<div className="dev-info-row">
<span className="dev-info-label">技能分类</span>
<span className="dev-info-value">{skill.category}</span>
</div>
<div className="dev-info-row">
<span className="dev-info-label">技能标签</span>
<span className="dev-info-value">
{skill.tags.map(tag => (
<span key={tag} className="dev-detail-tag" style={{ marginRight: '6px' }}>{tag}</span>
))}
</span>
</div>
<div style={{ display: 'flex', gap: '12px', marginBottom: '24px' }}>
<button className="btn btn-primary" onClick={() => onUpdateInfo && onUpdateInfo(skill.id)}>更新基本信息</button>
{skill.status === 'published' && (
<button className="btn btn-danger" onClick={handleTogglePublish}>下架技能</button>
)}
<button
className="btn btn-danger"
onClick={() => setDeleteSkillModal(true)}
disabled={skill.status === 'published'}
title={skill.status === 'published' ? '已上架的技能需要先下架才能删除' : ''}
>
删除技能
</button>
</div>
</div>
</div>
</div>
{/* 3. 操作按钮区 */}
<div className="action-buttons">
<button className="btn btn-primary" onClick={() => onUpdateInfo && onUpdateInfo(skill.id)}>更新基本信息</button>
{skill.status === 'published' && (
<button className="btn btn-danger" onClick={handleTogglePublish}>下架技能</button>
)}
<button
className="btn btn-danger"
onClick={() => setDeleteSkillModal(true)}
disabled={skill.status === 'published'}
title={skill.status === 'published' ? '已上架的技能需要先下架才能删除' : ''}
>
删除技能
</button>
</div>
<div className="card" style={{ marginTop: '24px' }}>
<div className="card-header">
<div className="card-title">技能包管理</div>

View File

@@ -132,3 +132,174 @@
background: #FEF2F2;
border-radius: 4px;
}
/* ============ 技能编辑页面优化样式 ============ */
/* 技能概览卡片(两行结构) */
.skill-overview-card {
display: flex;
gap: 20px;
padding: 24px;
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
align-items: center;
.skill-icon {
width: 80px;
height: 80px;
border-radius: 16px;
background: linear-gradient(135deg, #8B5CF6 0%, #EC4899 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 36px;
flex-shrink: 0;
}
.skill-header {
flex: 1;
/* 第一行:技能名称 */
.skill-name {
margin: 0 0 12px 0;
font-size: 24px;
font-weight: 700;
color: #1E293B;
}
/* 第二行:关键指标 */
.skill-metrics-row {
display: flex;
align-items: center;
gap: 24px;
.metric-item {
display: flex;
align-items: center;
gap: 6px;
.metric-icon {
width: 16px;
height: 16px;
opacity: 0.7;
color: #64748B;
}
.metric-value {
font-size: 14px;
font-weight: 500;
color: #1E293B;
}
}
}
}
}
/* 详细信息卡片(普通卡片) */
.info-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
overflow: hidden;
.card-header {
padding: 16px 24px;
border-bottom: 1px solid #E2E8F0;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #1E293B;
}
}
.card-body {
padding: 24px;
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
.info-item {
&.full-width {
grid-column: 1 / -1;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
color: #64748B;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
span, p {
font-size: 14px;
color: #1E293B;
line-height: 1.5;
margin: 0;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
}
}
}
}
/* 操作按钮区 */
.action-buttons {
display: flex;
gap: 12px;
padding: 16px;
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.skill-overview-card {
flex-direction: column;
align-items: flex-start;
gap: 16px;
.skill-icon {
width: 60px;
height: 60px;
font-size: 28px;
}
.skill-header {
.skill-name {
font-size: 20px;
}
.skill-metrics-row {
flex-wrap: wrap;
gap: 16px;
}
}
}
.info-grid {
grid-template-columns: 1fr !important;
}
.action-buttons {
flex-direction: column;
}
}