前端项目初始化提交

This commit is contained in:
2026-06-03 13:16:30 +08:00
commit 0910ba9cbe
163 changed files with 110032 additions and 0 deletions

View File

@@ -0,0 +1,489 @@
# 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?**