490 lines
13 KiB
Markdown
490 lines
13 KiB
Markdown
# 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?**
|