fix: 问题

This commit is contained in:
2026-02-22 21:08:07 +08:00
parent df4b956a32
commit 227dd4f78d
2 changed files with 404 additions and 305 deletions

134
.claude/commands/plan.md Normal file
View File

@@ -0,0 +1,134 @@
## 计划模式
启动实施前的计划制定模式,制定详细的实施策略。通过在代码实施前制定结构化计划,支持高效开发。
### 使用方法
```bash
# 请求 Claude 进入 Plan Mode
"制定 [实施内容] 的实施计划"
```
### 基本示例
```bash
# 新功能的实施计划
"制定用户认证功能的实施计划"
# 系统设计计划
"制定微服务拆分的实施计划"
# 重构计划
"制定遗留代码的重构计划"
```
### 与 Claude 的协作
```bash
# 复杂功能实施
"制定聊天功能的实施计划。包括 WebSocket、实时通知、历史管理"
# 数据库设计
"制定电商网站的数据库设计计划。包括商品、订单、用户管理"
# API 设计
"制定 GraphQL API 的实施计划。包括认证、缓存、速率限制"
# 基础设施设计
"制定 Docker 化的实施计划。包括开发环境、生产环境、CI/CD"
```
### Plan Mode 的特点
**自动启动**
- 检测到实施任务时自动启动 Plan Mode
- 可通过"制定实施计划"等关键词明确启动
**结构化规格书**
- 需求定义 (用户故事·验收标准)
- 设计书 (架构·数据设计·UI 设计)
- 实施计划 (任务分解·进度跟踪·质量保证)
- 风险分析与对策
**审批流程**
- 通过 `exit_plan_mode` 工具提交计划
- **重要**: 无论工具返回值如何,必须等待用户的明确批准
- 禁止未经批准就开始实施
- 可以修改·调整计划
- 仅在批准后才开始使用 TodoWrite 进行任务管理
### 详细示例
```bash
# 复杂系统实施
"制定在线支付系统的实施计划。包括 Stripe 集成、安全性、错误处理"
# 前端实施
"制定 React 仪表板的实施计划。包括状态管理、组件设计、测试"
# 后端实施
"制定 RESTful API 的实施计划。包括认证、验证、日志记录"
# DevOps 实施
"制定 CI/CD 管道的实施计划。包括测试自动化、部署、监控"
```
### 3 阶段工作流程
#### 阶段 1: Requirements(需求定义)
- **用户故事**: 明确功能的目的和价值
- **验收标准**: 定义完成条件和质量标准
- **约束·前提条件**: 整理技术·时间约束
- **优先级排序**: Must-have/Nice-to-have 分类
#### 阶段 2: Design(设计)
- **架构设计**: 系统构成和技术选型
- **数据设计**: 模式、API 规格、数据流
- **UI/UX 设计**: 界面构成和操作流程
- **风险分析**: 潜在问题和对策
#### 阶段 3: Implementation(实施)
- **任务分解**: 细分为可实施的单位
- **进度跟踪**: 通过 TodoWrite 进行状态管理
- **质量保证**: 测试策略和验证方法
- **审批流程**: 通过 exit_plan_mode 提交计划并等待明确批准
### 注意事项
**适用范围**
- Plan Mode 最适合复杂的实施任务
- 简单修改或小规模变更使用常规实施形式
- 推荐用于 3 步以上的工作或新功能开发
**技术约束**
- 不要信任 `exit_plan_mode` 工具的返回值
- 审批流程通过用户的明确意思表示判断
- 与 CLI 的 plan mode 是不同的功能
**执行注意事项**
- 严禁在批准前开始实施
- 提交计划后必须等待用户响应
- 出错时提供替代方案
### 执行示例
```bash
# 使用示例
"制定用户管理系统的实施计划"
# 预期行为
# 1. Plan Mode 自动启动
# 2. 需求分析和技术选型
# 3. 实施步骤的结构化
# 4. 通过 exit_plan_mode 提交计划
# 5. 批准后开始实施
```

View File

@@ -1,29 +1,14 @@
<template>
<FullWidthLayout :show-padding="false" class="agents-layout">
<div class="agents-container">
<!-- 筛选器区域 -->
<aside class="agents-sidebar">
<div class="sidebar-content">
<h3 class="sidebar-title">领域分类</h3>
<nav class="category-nav">
<button
v-for="category in categories"
:key="category.id"
class="category-btn"
:class="{ 'category-btn--active': activeCategory === category.id }"
@click="handleCategoryChange(category.id)"
>
<span>{{ category.name }}</span>
<span class="category-count">{{ category.count }}</span>
</button>
</nav>
</div>
</aside>
<!-- 头部区域搜索 + 分类 -->
<div class="agents-header">
<div class="header-content">
<!-- <h1 class="header-title">AI Agent Store</h1>
<p class="header-subtitle">Discover the perfect AI assistant for your workflow</p> -->
<!-- 主内容区域 -->
<main class="agents-main">
<!-- 搜索栏 -->
<div class="search-bar">
<div class="search-section">
<div class="search-input-wrapper">
<SearchOutlined class="search-icon" />
<input
@@ -35,18 +20,35 @@
/>
<CloseOutlined v-if="searchKeyword" class="search-clear" @click="searchKeyword = ''; handleSearch()" />
</div>
<div class="sort-info">
排序<span class="sort-active">热度 <i class="icon-arrow-down"></i></span>
</div>
<!-- 分类筛选 -->
<div class="category-section">
<nav class="category-nav">
<button
v-for="category in categories"
:key="category.id"
class="category-btn"
:class="{ 'category-btn--active': activeCategory === category.id }"
@click="handleCategoryChange(category.id)"
>
<span>{{ category.name }}</span>
<span class="category-count" v-if="category.count > 0">{{ category.count }}</span>
</button>
</nav>
</div>
</div>
</div>
<!-- 主内容区域 -->
<main class="agents-main">
<!-- 智能体卡片网格 -->
<div class="agents-content">
<a-spin :spinning="loading" tip="加载中...">
<template v-if="agentList.length > 0">
<template v-if="filteredAgentList.length > 0">
<div class="agents-grid">
<div
v-for="agent in agentList"
v-for="agent in filteredAgentList"
:key="agent.id"
class="agent-card"
@click="handleAgentClick(agent)"
@@ -80,19 +82,6 @@
</template>
<a-empty v-else description="暂无智能体" />
</a-spin>
<!-- 分页 -->
<div v-if="agentList.length > 0" class="pagination-wrapper">
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:show-size-changer="true"
:show-quick-jumper="true"
:show-total="(total) => `${total} 个智能体`"
@change="handlePageChange"
/>
</div>
</div>
</main>
</div>
@@ -107,7 +96,7 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import {
SearchOutlined,
RobotOutlined,
@@ -198,28 +187,32 @@ const agentList = ref([
}
])
// 分页
const pagination = reactive({
current: 1,
pageSize: 12,
total: 0
// 计算属性:过滤后的列表
const filteredAgentList = computed(() => {
let list = agentList.value
if (activeCategory.value !== 'all') {
list = list.filter(a => a.categoryId === activeCategory.value)
}
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
list = list.filter(a =>
a.name.toLowerCase().includes(keyword) ||
a.description.toLowerCase().includes(keyword)
)
}
return list
})
// 方法
const handleCategoryChange = (categoryId) => {
activeCategory.value = categoryId
pagination.current = 1
loadAgentList()
}
const handleSearch = () => {
pagination.current = 1
loadAgentList()
}
const handlePageChange = (page) => {
pagination.current = page
loadAgentList()
// 搜索逻辑通过 computed 自动处理这里可以留作扩展如埋点、API请求等
}
const handleAgentClick = (agent) => {
@@ -244,29 +237,6 @@ const formatNumber = (num) => {
return (num / 1000).toFixed(1) + 'w'
}
const loadAgentList = async () => {
loading.value = true
try {
// TODO: 调用 API 获取数据
// 模拟筛选
let filteredList = agentList.value
if (activeCategory.value !== 'all') {
filteredList = agentList.value.filter(a => a.categoryId === activeCategory.value)
}
if (searchKeyword.value) {
filteredList = filteredList.filter(a =>
a.name.includes(searchKeyword.value) || a.description.includes(searchKeyword.value)
)
}
pagination.total = filteredList.length
} catch (error) {
console.error('加载智能体列表失败:', error)
message.error('加载失败,请重试')
} finally {
loading.value = false
}
}
// 初始化
onMounted(() => {
// 更新分类数量
@@ -277,7 +247,6 @@ onMounted(() => {
cat.count = agentList.value.filter(a => a.categoryId === cat.id).length
}
})
pagination.total = agentList.value.length
})
</script>
@@ -288,135 +257,78 @@ onMounted(() => {
.agents-container {
display: flex;
flex-direction: column;
height: 100%;
background: #F8FAFC;
}
// 侧边栏
.agents-sidebar {
width: 260px;
background: #fff;
border-right: 1px solid #E2E8F0;
flex-shrink: 0;
padding: 20px;
}
.sidebar-content {
position: sticky;
top: 0;
}
.sidebar-title {
font-size: 12px;
font-weight: 700;
color: #94A3B8;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 12px 0;
}
.category-nav {
display: flex;
flex-direction: column;
gap: 4px;
}
.category-btn {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-radius: 8px;
font-size: 14px;
color: #64748B;
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
width: 100%;
&:hover {
background: #F1F5F9;
color: #334155;
}
&--active {
background: #EFF6FF;
color: #1D4ED8;
font-weight: 600;
}
}
.category-count {
font-size: 10px;
background: #E2E8F0;
color: #64748B;
padding: 2px 6px;
border-radius: 12px;
font-weight: 500;
.category-btn--active & {
background: #BFDBFE;
color: #1E40AF;
}
}
// 主内容区域
.agents-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
// 头部区域
.agents-header {
background: #F8FAFC;
padding: 64px 0 48px;
display: flex;
flex-direction: column;
align-items: center;
border-bottom: 1px solid #F1F5F9;
}
.search-bar {
padding: 16px 24px;
.header-content {
width: 100%;
max-width: 800px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 24px;
flex-shrink: 0;
background: #fff;
border-bottom: 1px solid #F1F5F9;
margin: 0 12px;
@media (max-width: 768px) {
flex-direction: column;
align-items: stretch;
padding: 16px;
gap: 12px;
}
align-items: center;
text-align: center;
padding: 0 24px;
}
.header-title {
font-size: 48px;
font-weight: 800;
color: #1E293B;
margin: 0 0 16px 0;
letter-spacing: -0.02em;
}
.header-subtitle {
font-size: 18px;
color: #64748B;
margin: 0 0 40px 0;
max-width: 600px;
line-height: 1.6;
}
.search-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 32px;
}
.search-input-wrapper {
position: relative;
width: 380px;
max-width: 100%;
@media (max-width: 768px) {
width: 100%;
}
max-width: 600px;
}
.search-icon {
position: absolute;
left: 14px;
left: 20px;
top: 50%;
transform: translateY(-50%);
color: #94A3B8;
font-size: 14px;
font-size: 20px;
z-index: 1;
pointer-events: none;
}
.search-clear {
position: absolute;
right: 12px;
right: 18px;
top: 50%;
transform: translateY(-50%);
color: #94A3B8;
font-size: 12px;
font-size: 16px;
cursor: pointer;
z-index: 2;
transition: all 0.2s ease;
@@ -428,23 +340,25 @@ onMounted(() => {
.search-input {
width: 100%;
height: 42px;
padding: 0 40px 0 42px;
height: 56px;
padding: 0 52px 0 56px;
border: 1px solid #E2E8F0;
border-radius: 10px;
font-size: 14px;
border-radius: 28px;
font-size: 16px;
color: #1E293B;
background: #fff;
background: #F8FAFC;
outline: none;
transition: all 0.2s ease;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
&::placeholder {
color: #94A3B8;
}
&:focus {
border-color: #3B82F6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
border-color: #94A3B8;
background: #fff;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
}
&:hover {
@@ -452,39 +366,121 @@ onMounted(() => {
}
}
.sort-info {
font-size: 12px;
color: #94A3B8;
// 分类筛选
.category-section {
width: 100%;
padding: 0 20px;
display: flex;
justify-content: center;
/* Mobile optimization: Keep horizontal scroll on small screens if desired,
but user asked for PC fix. Let's make it responsive. */
@media (max-width: 640px) {
display: block;
overflow-x: auto;
white-space: nowrap;
justify-content: flex-start;
mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent);
/* Hide scrollbar */
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
}
.sort-active {
font-weight: 600;
color: #1E293B;
.category-nav {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
max-width: 1000px;
margin: 0 auto;
padding: 4px 20px;
@media (max-width: 640px) {
display: inline-flex;
flex-wrap: nowrap;
width: max-content;
justify-content: flex-start;
margin: 0;
}
}
.category-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
color: #64748B;
background: #F1F5F9;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
&:hover {
background: #E2E8F0;
color: #334155;
}
&--active {
background: #1E293B;
color: #fff;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: #334155;
color: #fff;
}
}
}
.icon-arrow-down {
display: inline-block;
font-size: 10px;
margin-left: 2px;
.category-count {
font-size: 12px;
background: rgba(255, 255, 255, 0.5);
color: #64748B;
padding: 0 6px;
height: 18px;
line-height: 18px;
border-radius: 9px;
font-weight: 600;
transition: all 0.2s ease;
.category-btn--active & {
background: rgba(255, 255, 255, 0.2);
color: #fff;
}
}
// 主内容区域
.agents-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
// 智能体内容区域
.agents-content {
flex: 1;
padding: 24px;
padding: 40px 24px;
overflow-y: auto;
@media (max-width: 768px) {
padding: 16px;
}
}
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
@media (max-width: 640px) {
grid-template-columns: 1fr;
@@ -494,17 +490,22 @@ onMounted(() => {
// 智能体卡片
.agent-card {
background: #fff;
border: 1px solid #E2E8F0;
border-radius: 12px;
padding: 20px;
border: 1px solid #F1F5F9;
border-radius: 16px;
padding: 24px;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.02);
&:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
border-color: #93C5FD;
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
border-color: #E2E8F0;
}
}
@@ -512,20 +513,24 @@ onMounted(() => {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
margin-bottom: 20px;
}
.agent-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
width: 56px;
height: 56px;
border-radius: 12px;
overflow: hidden;
border: 2px solid #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 1px solid #F1F5F9;
background: #F8FAFC;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
.agent-card:hover & {
transform: scale(1.05);
}
img {
width: 100%;
@@ -535,140 +540,100 @@ onMounted(() => {
}
.avatar-placeholder {
font-size: 24px;
color: rgba(255, 255, 255, 0.8);
font-size: 28px;
color: #94A3B8;
}
.agent-tag {
font-size: 10px;
padding: 4px 8px;
border-radius: 12px;
font-weight: 600;
border: 1px solid;
font-size: 12px;
padding: 4px 10px;
border-radius: 6px;
font-weight: 500;
&--blue {
background: #EFF6FF;
color: #1D4ED8;
border-color: #BFDBFE;
color: #3B82F6;
}
&--green {
background: #F0FDF4;
color: #16A34A;
border-color: #BBF7D0;
color: #22C55E;
}
&--purple {
background: #FAF5FF;
color: #9333EA;
border-color: #E9D5FF;
color: #A855F7;
}
&--pink {
background: #FDF2F8;
color: #DB2777;
border-color: #FBCFE8;
color: #EC4899;
}
&--amber {
background: #FFFBEB;
color: #D97706;
border-color: #FDE68A;
color: #F59E0B;
}
}
.card-title {
font-size: 16px;
font-size: 18px;
font-weight: 600;
color: #1E293B;
margin: 0 0 6px 0;
margin: 0 0 8px 0;
line-height: 1.4;
}
.card-description {
font-size: 12px;
font-size: 14px;
color: #64748B;
margin: 0 0 16px 0;
margin: 0 0 24px 0;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
line-height: 1.6;
flex: 1;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 16px;
border-top: 1px solid #F1F5F9;
margin-top: auto;
}
.usage-count {
font-size: 10px;
font-size: 12px;
color: #94A3B8;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
}
.chat-btn {
background: #F1F5F9;
color: #64748B;
color: #475569;
border: none;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
padding: 8px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
.agent-card:hover & {
background: #3B82F6;
background: #1E293B;
color: #fff;
}
}
// 分页
.pagination-wrapper {
padding-top: 24px;
display: flex;
justify-content: center;
:deep(.ant-pagination) {
.ant-pagination-item {
border-radius: 6px;
border-color: #E2E8F0;
a {
color: #64748B;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
&:hover {
border-color: #3B82F6;
a {
color: #3B82F6;
}
}
&-active {
background: #3B82F6;
border-color: #3B82F6;
a {
color: #fff;
}
}
}
.ant-pagination-prev,
.ant-pagination-next {
.ant-pagination-item-link {
border-radius: 6px;
border-color: #E2E8F0;
&:hover {
border-color: #3B82F6;
color: #3B82F6;
}
}
}
background: #334155 !important;
}
}
</style>