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

@@ -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;
}
}