Files
aue/docs/superpowers/plans/2026-05-14-generatepanel-layout-refactoring.md
2026-06-03 13:16:30 +08:00

490 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GeneratePanel 布局重构实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 重构GeneratePanel组件为顶底主从式布局解决内容截断问题并优化用户体验
**Architecture:** 采用Flexbox垂直布局配置面板flex-shrink: 0固定在顶部结果展示区flex: 1自适应填充剩余空间并独立滚动。表单内部使用CSS Grid双列布局提高信息密度。
**Tech Stack:** Vue 3 (Composition API), CSS Grid, Flexbox, Scoped Styles
---
## 文件结构
### 需要修改的文件:
- **Modify:** `src/components/exam/GeneratePanel.vue` - 主要重构目标
- Template部分重新组织DOM结构分离配置区和结果区
- Script部分无需改动保持现有逻辑
- Style部分完全重写布局样式
---
### Task 1: 重构GeneratePanel容器布局 - 实现顶底主从式结构
**Files:**
- Modify: `src/components/exam/GeneratePanel.vue` (Template + Style)
- [ ] **Step 1: 重新组织Template结构**
将现有的两个ContentCard包裹在语义化的容器中
```vue
<template>
<div class="generate-panel">
<!-- 页面头部和统计保持不变 -->
<PageHeader
title="AI智能出题系统"
description="基于AI技术自动生成高质量试题"
/>
<StatsRow :stats="examTypeStats" />
<!-- 新增配置面板容器 -->
<div class="config-panel">
<ContentCard title="智能出题配置">
<template #header>
<h2 class="card-title">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
智能出题配置
</h2>
</template>
<!-- 表单内容将在Task 2中优化 -->
<div class="form-content">
<!-- 现有表单代码保持不变 -->
<div class="form-item">...</div>
<div class="form-item">...</div>
<div class="form-item">...</div>
<div class="action-bar">...</div>
</div>
</ContentCard>
</div>
<!-- 新增结果展示区容器 -->
<div class="result-panel">
<ContentCard title="待审核题目列表">
<div v-if="isGenerating" class="loading-state">...</div>
<div v-else-if="generatedQuestions.length === 0" class="empty-state">...</div>
<div v-else class="question-list">
<!-- 现有题目列表代码保持不变 -->
</div>
</ContentCard>
</div>
</div>
</template>
```
- [ ] **Step 2: 重写容器级CSS样式**
```css
.generate-panel {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 16px;
overflow: hidden; /* 外层不滚动 */
}
/* 配置面板 - 固定高度,不压缩 */
.config-panel {
flex-shrink: 0;
margin-bottom: 12px;
}
/* 结果展示区 - 自适应填充剩余空间 */
.result-panel {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.result-panel :deep(.content-card) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.result-panel :deep(.card-body) {
flex: 1;
overflow-y: auto; /* 内容独立滚动 */
}
```
- [ ] **Step 3: 验证基础布局结构**
刷新浏览器,确认:
- ✅ 配置面板显示在顶部且完整可见
- ✅ 结果展示区占据下方所有剩余空间
- ✅ 整体无滚动条(除了结果区的内部滚动)
---
### Task 2: 优化配置表单为双列Grid布局 - 提高信息密度
**Files:**
- Modify: `src/components/exam/GeneratePanel.vue` (Template中的.form-content部分)
- [ ] **Step 1: 将表单项重组为Grid布局**
替换`.form-content`内部的HTML结构
```vue
<div class="form-content">
<!-- 文件选择器 - 跨两列 -->
<div class="form-item form-item--full">
<label class="form-label">选择制度文件 <span class="required-mark">*</span></label>
<FileSelector
v-model="selectedDoc"
@change="onDocSelected"
@collection-change="onCollectionChange"
placeholder="先选择知识库,再选择制度文件..."
/>
<div v-if="selectedCollectionName" class="collection-info">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
</svg>
<span>当前知识库: <strong>{{ selectedCollectionName }}</strong></span>
</div>
<div v-if="selectedDocData" class="selected-file-info">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<span>{{ selectedDocData.title || selectedDocData.rawFileName }}</span>
<span>v{{ selectedDocData.version }}</span>
</div>
</div>
<!-- 难度等级 + 总计题数 - 并排显示 -->
<div class="form-item">
<label class="form-label">难度等级</label>
<select v-model="difficulty" class="form-select">
<option :value="1">简单</option>
<option :value="2">中等</option>
<option :value="3">困难</option>
</select>
</div>
<div class="form-item form-item--total">
<label class="form-label">总计题数</label>
<div class="total-display">
<strong>{{ totalQuestionCount }}</strong>
</div>
</div>
<!-- 题型与数量配置 - 跨两列 -->
<div class="form-item form-item--full">
<label class="form-label">题型与数量配置</label>
<div class="type-count-grid">
<div class="type-count-item" v-for="qt in questionTypeOptions" :key="qt.value">
<label class="checkbox-label" :class="{ active: questionTypeCounts[qt.value] > 0 }">
<input type="checkbox" :checked="questionTypeCounts[qt.value] > 0" @change="toggleQuestionType(qt.value)" />
{{ qt.label }}
</label>
<input
type="number"
v-model.number="questionTypeCounts[qt.value]"
min="0"
max="50"
class="form-input-sm"
:disabled="questionTypeCounts[qt.value] <= 0"
placeholder="0"
/>
</div>
</div>
</div>
<!-- 操作按钮 - 跨两列右对齐 -->
<div class="action-bar">
<button class="btn btn-primary" @click="handleGenerateQuestions" :disabled="isGenerating">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"></path>
<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"></path>
</svg>
{{ isGenerating ? '生成中...' : '开始生成题目' }}
</button>
<button class="btn btn-secondary" @click="generatedQuestions = []">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
清空结果
</button>
</div>
</div>
```
- [ ] **Step 2: 编写Grid布局CSS**
```css
.form-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 16px;
align-items: start;
}
/* 跨列元素 */
.form-item--full {
grid-column: 1 / -1;
}
/* 难度等级和总计的特殊样式 */
.form-item--total .total-display {
padding: 6px 12px;
background: #F8F9FA;
border: 1px solid #DEE2E6;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
color: #212529;
text-align: center;
}
/* 按钮栏右对齐 */
.action-bar {
justify-content: flex-end;
padding-top: 12px;
border-top: 1px solid #E9ECEF;
margin-top: 4px;
}
/* 题型网格优化为3列 */
.type-count-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 6px;
}
```
- [ ] **Step 3: 验证双列布局效果**
确认:
- ✅ 文件选择器独占一行跨2列
- ✅ 难度等级和总计题数左右并排
- ✅ 题型配置以3列网格显示
- ✅ 操作按钮右对齐且跨2列
---
### Task 3: 实现结果展示区独立滚动 - 自适应高度填充
**Files:**
- Modify: `src/components/exam/GeneratePanel.vue` (Style部分)
- [ ] **Step 1: 优化空状态和加载状态样式**
```css
.loading-state,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 16px;
color: #6C757D;
min-height: 150px;
}
.loading-icon,
.empty-icon {
font-size: 36px;
margin-bottom: 10px;
}
.empty-state p {
font-size: 14px;
font-weight: 500;
color: #495057;
margin-bottom: 4px;
}
.empty-hint {
font-size: 13px;
color: #6C757D;
margin-top: 6px;
max-width: 400px;
text-align: center;
line-height: 1.5;
}
```
- [ ] **Step 2: 优化题目列表样式**
```css
.question-list {
display: flex;
flex-direction: column;
gap: 10px;
padding: 4px;
}
.question-item {
padding: 14px;
background: #FFFFFF;
border: 1px solid #E9ECEF;
border-radius: 8px;
transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.question-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}
```
- [ ] **Step 3: 测试滚动行为**
生成一些测试数据或使用loading状态验证
- ✅ 结果区可以独立滚动
- ✅ 配置面板保持固定不动
- ✅ 无整体页面滚动条
- ✅ 滚动条样式美观(可选优化)
---
### Task 4: 响应式适配与细节优化 - 移动端降级处理
**Files:**
- Modify: `src/components/exam/GeneratePanel.vue` (Style部分添加媒体查询)
- [ ] **Step 1: 添加移动端响应式规则**
```css
/* 平板设备 (≤1024px) */
@media (max-width: 1024px) {
.type-count-grid {
grid-template-columns: repeat(2, 1fr);
}
.form-content {
gap: 10px 12px;
}
}
/* 移动设备 (≤768px) */
@media (max-width: 768px) {
.generate-panel {
padding: 12px;
}
/* 降级为单列布局 */
.form-content {
grid-template-columns: 1fr;
gap: 10px;
}
.form-item--full,
.action-bar {
grid-column: 1;
}
/* 题型降为2列 */
.type-count-grid {
grid-template-columns: repeat(2, 1fr);
}
/* 按钮全宽 */
.action-bar {
flex-direction: column;
}
.action-bar .btn {
width: 100%;
}
/* 减少间距 */
.config-panel {
margin-bottom: 8px;
}
/* 缩小字体和内边距 */
.form-label {
font-size: 12px;
}
.type-count-item {
padding: 6px 8px;
}
}
/* 小屏手机 (≤480px) */
@media (max-width: 480px) {
.type-count-grid {
grid-template-columns: 1fr;
}
.stats-row {
font-size: 12px;
}
}
```
- [ ] **Step 2: 测试不同屏幕尺寸**
使用浏览器开发者工具测试:
- ✅ 桌面端 (1920x1080): 双列布局完美呈现
- ✅ 笔记本 (1366x768): 正常显示
- ✅ 平板 (768x1024): 降级为单列,按钮堆叠
- ✅ 手机 (375x667): 单列紧凑布局
- [ ] **Step 3: 最终视觉检查**
确认所有细节:
- ✅ 无内容溢出或被截断
- ✅ 所有交互元素可点击
- ✅ 样式符合项目白色体系规范
- ✅ 过渡动画流畅自然
- ✅ 空状态、加载状态、正常状态均正确显示
---
## 验收标准
完成所有任务后GeneratePanel应满足以下要求
1. **布局正确性**
- 配置面板始终可见,不被遮挡
- 结果区自适应填充剩余空间
- 仅结果区支持独立滚动
2. **表单可用性**
- 双列Grid布局信息密度合理
- 所有表单项完整显示
- 操作按钮易于触达
3. **响应式表现**
- 桌面端:双列布局
- 移动端:自动降级为单列
- 无横向滚动条
4. **视觉一致性**
- 符合项目白色体系设计规范
- 与其他模块风格统一
- 间距、字号、圆角等细节一致
---
## 执行选项
**Plan complete and saved to this document. Two execution options:**
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
**Which approach?**