提交 ce2c75de authored 作者: 陈泽健's avatar 陈泽健

feat: 功能测试报告新增模板配置功能(多模板管理)

- 新增 ReportTemplate 模型(name/is_default/config JSON字段)
- 新增模板 CRUD API(/api/functional-report/templates)
- 新增前端 TemplateConfig.vue 模板配置管理页面
- 新增模板选择下拉框(FunctionalReport.vue 生成报告时可选模板)
- 改造 word_generator/chart_generator/markdown_generator 支持 template_config 参数
- 修复 MySQL 下 config 字段 dict→JSON 序列化问题(json.dumps)
- 注册路由到 main.py,导入模型到 models/__init__.py
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 7c1c0488
......@@ -27,7 +27,7 @@ from fastapi.responses import FileResponse
from app.config import settings
from app.database import init_db
from app.routers import modules, cases, executions, recorder, stats, reports, cleanup, batch, dependencies, security, ai_locator, device_sim, system, element_locator, login_template, smart_locate, performance, performance_output, api_preset, deploy, functional_report
from app.routers import modules, cases, executions, recorder, stats, reports, cleanup, batch, dependencies, security, ai_locator, device_sim, system, element_locator, login_template, smart_locate, performance, performance_output, api_preset, deploy, functional_report, report_template
# 配置日志
logging.basicConfig(
......@@ -244,6 +244,13 @@ app.include_router(
tags=["功能测试报告"]
)
# 报告模板配置路由(自身 prefix=/templates,注册在 /api/functional-report 下)
app.include_router(
report_template.router,
prefix="/api/functional-report",
tags=["功能测试报告"]
)
# ==================== 根路径 ====================
......
......@@ -25,6 +25,7 @@ from app.models.performance import PerformanceTask, PerformanceSnapshot
from app.models.performance_output import PerformanceTaskOutput
from app.models.api_preset import ApiPreset
from app.models.erp_config import ErpConfig
from app.models.report_template import ReportTemplate
__all__ = [
"Module",
......@@ -47,4 +48,5 @@ __all__ = [
"PerformanceTaskOutput",
"ApiPreset",
"ErpConfig",
"ReportTemplate",
]
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:report_template.py
模块描述:功能测试报告模板配置数据库模型定义
作者:czj
创建日期:2026-08-13
最后修改:2026-08-13
"""
import json
from datetime import datetime
from typing import Optional
from sqlalchemy import String, DateTime, Integer, Text, Boolean
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class ReportTemplate(Base):
"""
功能测试报告模板配置数据库模型
存储报告模板配置,包括章节开关、Word样式、图表样式、报告默认值。
支持多模板管理,其中一个模板可标记为默认。
Attributes:
id (int): 主键,自增
name (str): 模板名称
is_default (bool): 是否默认模板(全局唯一为 True)
config (str): 完整配置 JSON 字符串
created_at (datetime): 创建时间
updated_at (datetime): 更新时间
"""
__tablename__ = "report_templates"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="主键")
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="模板名称")
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, comment="是否默认模板")
config: Mapped[str] = mapped_column(Text, nullable=False, default="{}", comment="完整配置JSON字符串")
created_at: Mapped[Optional[datetime]] = mapped_column(
DateTime, default=datetime.now, comment="创建时间"
)
updated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间"
)
def __repr__(self) -> str:
return f"<ReportTemplate(id={self.id}, name='{self.name}', is_default={self.is_default})>"
def to_dict(self) -> dict:
"""转为完整字典,config 反序列化为 dict"""
from app.services.functional.config import DATE_FORMAT
return {
"id": self.id,
"name": self.name,
"is_default": self.is_default,
"config": json.loads(self.config) if self.config else {},
"created_at": self.created_at.strftime(DATE_FORMAT) if self.created_at else None,
"updated_at": self.updated_at.strftime(DATE_FORMAT) if self.updated_at else None,
}
def to_list_item(self) -> dict:
"""转为列表项,不含 config 详情"""
from app.services.functional.config import DATE_FORMAT
return {
"id": self.id,
"name": self.name,
"is_default": self.is_default,
"updated_at": self.updated_at.strftime(DATE_FORMAT) if self.updated_at else None,
}
\ No newline at end of file
......@@ -153,12 +153,13 @@ async def generate_report(request: GenerateRequest):
service = get_report_service()
try:
result = service.analyze_and_generate(
result = await service.analyze_and_generate(
testcase_path=testcase_path,
buglist_path=buglist_path,
project_name=request.project_name,
report_format=request.report_format,
session_id=request.session_id,
template_id=request.template_id,
)
# 缓存分析结果到会话目录
analysis_cache = {
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:report_template.py
模块描述:功能测试报告模板配置 API 路由,提供模板的增删改查、设置默认等接口
作者:czj
创建日期:2026-08-13
最后修改:2026-08-13
"""
import json
import logging
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models import ReportTemplate
from app.schemas.report_template import (
ReportTemplateCreateRequest,
ReportTemplateUpdateRequest,
ReportTemplateListItem,
ReportTemplateResponse,
)
from app.services.functional.config import get_default_config, merge_template_config
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/templates",
tags=["报告模板配置"],
)
async def _list_default_template(db: AsyncSession) -> ReportTemplate:
"""查找当前默认模板(不存在返回 None)"""
result = await db.execute(
select(ReportTemplate).where(ReportTemplate.is_default == True) # noqa: E712
)
return result.scalar_one_or_none()
async def _clear_default_flag(db: AsyncSession) -> None:
"""清除所有模板的默认标记"""
default = await _list_default_template(db)
if default:
default.is_default = False
async def _ensure_one_default(db: AsyncSession, current: ReportTemplate) -> None:
"""确保至少保留一个默认模板:如果没有任何默认模板,将当前模板设为默认"""
result = await db.execute(select(ReportTemplate))
total = len(result.scalars().all())
if total == 1 and not current.is_default:
current.is_default = True
def _validate_config(config: dict) -> dict:
"""校验模板配置:合并到默认配置,保证结构完整,移除无效键"""
merged = merge_template_config(get_default_config(), config)
return merged
# ==================== 模板列表 ====================
@router.get("", response_model=list[ReportTemplateListItem], summary="获取模板列表")
async def list_templates(db: AsyncSession = Depends(get_db)):
"""
获取所有报告模板列表(不含 config 详情)
Returns:
List[ReportTemplateListItem]: 模板列表
"""
result = await db.execute(select(ReportTemplate).order_by(ReportTemplate.updated_at.desc()))
templates = result.scalars().all()
return [t.to_list_item() for t in templates]
# ==================== 模板详情 ====================
@router.get("/{template_id}", response_model=ReportTemplateResponse, summary="获取模板详情")
async def get_template(template_id: int, db: AsyncSession = Depends(get_db)):
"""
获取单个报告模板的完整配置
Args:
template_id: 模板ID
Returns:
ReportTemplateResponse: 模板完整信息(含config)
"""
template = await db.get(ReportTemplate, template_id)
if not template:
raise HTTPException(status_code=404, detail=f"模板不存在: {template_id}")
return template.to_dict()
# ==================== 创建模板 ====================
@router.post("", response_model=ReportTemplateResponse, summary="创建模板")
async def create_template(
request: ReportTemplateCreateRequest,
db: AsyncSession = Depends(get_db),
):
"""
创建新的报告模板
- config 会与默认配置合并,保证结构完整
- 如果请求 is_default=True,则清除其他模板的默认标记
- 当系统中还没有任何模板时,自动将新模板设为默认
"""
# 检查名称是否重复
result = await db.execute(select(ReportTemplate).where(ReportTemplate.name == request.name))
if result.scalar_one_or_none():
raise HTTPException(status_code=400, detail=f"模板名称已存在: {request.name}")
# 合并配置,保证结构完整
config = _validate_config(request.config)
# 当系统还没有模板时,强制设为默认
count_result = await db.execute(select(ReportTemplate.id))
existing_count = len(count_result.scalars().all())
is_default = request.is_default or existing_count == 0
# 若设为默认,先清除其他默认标记
if is_default:
await _clear_default_flag(db)
template = ReportTemplate(
name=request.name,
is_default=is_default,
config=json.dumps(config, ensure_ascii=False),
)
db.add(template)
await db.commit()
await db.refresh(template)
logger.info(f"创建报告模板: {template.name} (id={template.id}, is_default={template.is_default})")
return template.to_dict()
# ==================== 更新模板 ====================
@router.put("/{template_id}", response_model=ReportTemplateResponse, summary="更新模板")
async def update_template(
template_id: int,
request: ReportTemplateUpdateRequest,
db: AsyncSession = Depends(get_db),
):
"""
更新报告模板
- 可单独更新 name / config / is_default
- 更新后的 config 会与默认配置合并
- 若请求 is_default=True,则清除其他模板的默认标记
"""
template = await db.get(ReportTemplate, template_id)
if not template:
raise HTTPException(status_code=404, detail=f"模板不存在: {template_id}")
# 名称重复校验(排除自身)
if request.name is not None and request.name != template.name:
result = await db.execute(
select(ReportTemplate).where(ReportTemplate.name == request.name)
)
duplicate = result.scalar_one_or_none()
if duplicate and duplicate.id != template_id:
raise HTTPException(status_code=400, detail=f"模板名称已存在: {request.name}")
if request.name is not None:
template.name = request.name
if request.config is not None:
template.config = json.dumps(_validate_config(request.config), ensure_ascii=False)
if request.is_default:
await _clear_default_flag(db)
template.is_default = True
await db.commit()
# 无论如何保留一个默认模板
await _ensure_one_default(db, template)
await db.commit()
await db.refresh(template)
logger.info(f"更新报告模板: {template.name} (id={template.id})")
return template.to_dict()
# ==================== 设为默认 ====================
@router.put("/{template_id}/default", response_model=ReportTemplateResponse, summary="设为默认模板")
async def set_default_template(template_id: int, db: AsyncSession = Depends(get_db)):
"""
将指定模板设为默认模板(全局唯一)
"""
template = await db.get(ReportTemplate, template_id)
if not template:
raise HTTPException(status_code=404, detail=f"模板不存在: {template_id}")
if not template.is_default:
await _clear_default_flag(db)
template.is_default = True
await db.commit()
await db.refresh(template)
logger.info(f"设为默认模板: {template.name} (id={template.id})")
return template.to_dict()
# ==================== 删除模板 ====================
@router.delete("/{template_id}", summary="删除模板")
async def delete_template(template_id: int, db: AsyncSession = Depends(get_db)):
"""
删除报告模板
- 不允许删除最后一个模板(至少保留一个)
"""
template = await db.get(ReportTemplate, template_id)
if not template:
raise HTTPException(status_code=404, detail=f"模板不存在: {template_id}")
# 检查剩余模板数量
result = await db.execute(select(ReportTemplate.id))
total = len(result.scalars().all())
if total <= 1:
raise HTTPException(status_code=400, detail="至少需要保留一个模板,不能删除最后一个")
was_default = template.is_default
await db.delete(template)
await db.commit()
logger.info(f"删除报告模板: {template.name} (id={template_id})")
# 如果删除的是默认模板,把剩余的第一个设为默认
if was_default:
remaining = await db.execute(select(ReportTemplate).order_by(ReportTemplate.updated_at.desc()))
first = remaining.scalars().first()
if first:
first.is_default = True
await db.commit()
logger.info(f"删除默认模板后,自动将 {first.name} (id={first.id}) 设为默认")
return {"success": True, "message": "模板已删除"}
\ No newline at end of file
......@@ -26,6 +26,7 @@ class GenerateRequest(BaseModel):
session_id: str = Field(..., description="会话ID")
project_name: str = Field("", description="项目名称")
report_format: str = Field("docx", description="报告格式: docx/md/both")
template_id: Optional[int] = Field(None, description="报告模板ID(None 时使用默认模板或系统默认配置)")
# ERP上传选项(可选,勾选后在生成报告后自动上传)
upload_to_erp: bool = Field(False, description="是否上传到ERP测试单")
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:report_template.py
模块描述:功能测试报告模板配置 Pydantic 数据模型
作者:czj
创建日期:2026-08-13
最后修改:2026-08-13
"""
from typing import Dict, Optional
from pydantic import BaseModel, Field
class ReportTemplateCreateRequest(BaseModel):
"""创建报告模板请求"""
name: str = Field(..., min_length=1, max_length=100, description="模板名称")
is_default: bool = Field(False, description="是否设为默认模板")
config: Dict = Field(default_factory=dict, description="模板完整配置(章节开关/样式/默认值)")
class ReportTemplateUpdateRequest(BaseModel):
"""更新报告模板请求"""
name: Optional[str] = Field(None, min_length=1, max_length=100, description="模板名称")
is_default: Optional[bool] = Field(None, description="是否设为默认模板")
config: Optional[Dict] = Field(None, description="模板完整配置")
class ReportTemplateListItem(BaseModel):
"""报告模板列表项(不含 config 详情)"""
id: int = Field(..., description="模板ID")
name: str = Field(..., description="模板名称")
is_default: bool = Field(False, description="是否默认模板")
updated_at: Optional[str] = Field(None, description="更新时间")
class ReportTemplateResponse(BaseModel):
"""报告模板完整响应"""
id: int = Field(..., description="模板ID")
name: str = Field(..., description="模板名称")
is_default: bool = Field(False, description="是否默认模板")
config: Dict = Field(default_factory=dict, description="模板完整配置")
created_at: Optional[str] = Field(None, description="创建时间")
updated_at: Optional[str] = Field(None, description="更新时间")
\ No newline at end of file
......@@ -316,4 +316,84 @@ def get_report_number(project_name: str, date: str = None) -> str:
return REPORT_NUMBER_TEMPLATE.format(
project_name=project_name,
date=date
)
\ No newline at end of file
)
# ================================
# 模板配置工具函数
# ================================
def get_default_config() -> dict:
"""
获取默认模板配置字典
将当前硬编码常量组织为模板配置结构,供 ReportTemplate 模型和生成器使用。
当用户未创建任何模板时,生成器使用此默认配置。
Returns:
dict: 完整的默认配置字典,包含 chapters/defaults/word_style/chart_style
"""
return {
"chapters": {
"report_basic_info": True,
"execution_summary": True,
"test_scope": True,
"case_details": True,
"bug_list": True,
"chart_analysis": True,
"execution_records": True,
"attachments": True,
"risk_assessment": True,
"appendix": True,
},
"defaults": {
"test_lead": DEFAULT_TEST_LEAD,
"tester": DEFAULT_TESTER,
"test_env": DEFAULT_TEST_ENV,
"system_version": DEFAULT_SYSTEM_VERSION,
"server_address": DEFAULT_SERVER_ADDRESS,
"deployment_env": DEFAULT_DEPLOYMENT_ENV,
},
"word_style": {
"font_name": WORD_FONT_NAME,
"font_size": WORD_FONT_SIZE,
"title_level_1_size": WORD_TITLE_LEVEL_1_SIZE,
"title_level_2_size": WORD_TITLE_LEVEL_2_SIZE,
"title_level_3_size": WORD_TITLE_LEVEL_3_SIZE,
"title_level_4_size": WORD_TITLE_LEVEL_4_SIZE,
"paragraph_first_line_indent": WORD_PARAGRAPH_FIRST_LINE_INDENT,
"title_color": "#000000",
},
"chart_style": {
"dpi": CHART_DPI,
"figure_width": CHART_FIGURE_SIZE[0],
"figure_height": CHART_FIGURE_SIZE[1],
"chinese_font": CHINESE_FONT,
"colors": dict(CHART_COLORS),
},
}
def merge_template_config(base_config: dict, template_config: dict) -> dict:
"""
将模板配置与默认配置深合并,缺失字段用默认值填充
Args:
base_config: 默认配置(get_default_config() 返回值)
template_config: 用户模板配置(可能只包含部分字段)
Returns:
dict: 合并后的完整配置
"""
def deep_merge(base, override):
result = dict(base)
for k, v in (override or {}).items():
if isinstance(v, dict) and isinstance(result.get(k), dict):
result[k] = deep_merge(result[k], v)
else:
result[k] = v
return result
return deep_merge(base_config, template_config or {})
\ No newline at end of file
......@@ -9,12 +9,18 @@
最后修改:2026-08-12
"""
import json
import logging
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from sqlalchemy import select
from app.database import async_session_maker
from app.models import ReportTemplate
from .functional.excel_reader import read_test_cases, read_bug_list, TestCase, Bug
from .functional.data_analyzer import (
analyze_test_cases,
......@@ -78,13 +84,54 @@ class FunctionalReportService:
logger.info(f"文件已保存: {file_path}")
return str(file_path)
def analyze_and_generate(
async def _load_template_config(self, template_id: Optional[int] = None) -> Optional[Dict]:
"""
从数据库加载报告模板配置
模板配置会与系统默认配置合并,保证结构完整(缺失键回退到默认值)。
无可用模板(指定ID不存在或系统中没有任何模板)时返回 None,
生成器将回退到系统硬编码默认值。
Args:
template_id: 模板ID(None 时使用默认模板,无默认模板则返回 None)
Returns:
Optional[Dict]: 合并后的模板配置字典,无可用模板时返回 None
"""
try:
from .functional.config import get_default_config, merge_template_config
async with async_session_maker() as session:
if template_id is not None:
template = await session.get(ReportTemplate, template_id)
if template is None:
logger.warning(f"指定模板不存在: {template_id},使用系统默认配置")
return None
else:
result = await session.execute(
select(ReportTemplate).where(ReportTemplate.is_default == True) # noqa: E712
)
template = result.scalar_one_or_none()
if template is None:
logger.info("系统中没有报告模板,使用系统默认配置")
return None
template_config = json.loads(template.config) if template.config else {}
merged = merge_template_config(get_default_config(), template_config)
logger.info(f"使用报告模板: {template.name} (id={template.id}, is_default={template.is_default})")
return merged
except Exception as e:
logger.error(f"加载报告模板失败,使用系统默认配置: {e}")
return None
async def analyze_and_generate(
self,
testcase_path: str,
buglist_path: str,
project_name: str,
report_format: str = "docx",
session_id: str = "",
template_id: Optional[int] = None,
) -> Dict:
"""
执行完整分析并生成报告
......@@ -95,6 +142,7 @@ class FunctionalReportService:
project_name: 项目名称
report_format: 报告格式(docx/md/both)
session_id: 会话ID(沿用上传的session,保证报告与上传文件在同一目录)
template_id: 报告模板ID(可选,None 时使用默认模板,无模板时使用系统硬编码默认值)
Returns:
{
......@@ -107,6 +155,9 @@ class FunctionalReportService:
"reports": list[str],
}
"""
# 0. 加载报告模板配置(如指定模板ID或存在默认模板)
template_config = await self._load_template_config(template_id)
# 1. 读取Excel数据
logger.info("步骤1: 读取测试数据...")
test_cases = read_test_cases(testcase_path)
......@@ -133,7 +184,7 @@ class FunctionalReportService:
session_id = session_id or self._generate_session_id()
charts_dir = self._get_charts_dir(session_id)
logger.info("步骤3: 生成统计图表...")
chart_paths = generate_all_charts(case_analysis, bug_analysis, str(charts_dir))
chart_paths = generate_all_charts(case_analysis, bug_analysis, str(charts_dir), template_config)
logger.info(f" [OK] 生成了 {len(chart_paths)} 个图表")
# 4. 生成报告
......@@ -156,6 +207,7 @@ class FunctionalReportService:
project_name=project_name,
cases=test_cases,
bugs=bugs,
template_config=template_config,
)
else:
report_path = generate_markdown_report(
......@@ -168,6 +220,7 @@ class FunctionalReportService:
project_name=project_name,
cases=test_cases,
bugs=bugs,
template_config=template_config,
)
generated_reports.append(report_path)
logger.info(f" [OK] {fmt.upper()}报告已生成: {report_path}")
......
......@@ -44,6 +44,7 @@ export const functionalReportApi = {
sessionId: string,
projectName: string,
reportFormat: string = 'docx',
templateId?: number,
erpOptions?: {
upload_to_erp?: boolean
upload_to_project?: boolean
......@@ -71,6 +72,7 @@ export const functionalReportApi = {
session_id: sessionId,
project_name: projectName,
report_format: reportFormat,
template_id: templateId,
...(erpOptions || {})
})
return response as any
......
/**
* 功能测试报告模板配置 API 接口
*
* @module api/reportTemplate
* @author czj
* @date 2026-08-13
*/
import request from '@/utils/request'
/** 报告章节开关键集合 */
export type ReportChapterKey = 'report_basic_info' | 'execution_summary' | 'test_scope' | 'case_details' | 'bug_list' | 'chart_analysis' | 'execution_records' | 'attachments' | 'risk_assessment' | 'appendix'
/** 报告章节开关 */
export type ReportChapters = Record<ReportChapterKey, boolean>
/** 模板配置结构(与后端 config.py 的 get_default_config() 对齐) */
export interface ReportTemplateConfig {
/** 章节开关 */
chapters: ReportChapters
/** 报告默认值 */
defaults: {
test_lead: string
tester: string
test_env: string
system_version: string
server_address: string
deployment_env: string
}
/** Word 样式 */
word_style: {
font_name: string
font_size: number
title_level_1_size: number
title_level_2_size: number
title_level_3_size: number
title_level_4_size: number
paragraph_first_line_indent: number
title_color: string
}
/** 图表样式 */
chart_style: {
dpi: number
figure_width: number
figure_height: number
chinese_font: string
colors: {
pass: string
fail: string
unverified: string
undeveloped: string
bug_1: string
bug_2: string
bug_3: string
bug_4: string
}
}
}
/** 模板列表项(不含 config 详情) */
export interface ReportTemplateListItem {
id: number
name: string
is_default: boolean
updated_at?: string
}
/** 模板完整响应 */
export interface ReportTemplateResponse {
id: number
name: string
is_default: boolean
config: ReportTemplateConfig
created_at?: string
updated_at?: string
}
/** 创建模板请求 */
export interface ReportTemplateCreateRequest {
name: string
is_default?: boolean
config: ReportTemplateConfig
}
/** 更新模板请求 */
export interface ReportTemplateUpdateRequest {
name?: string
is_default?: boolean
config?: ReportTemplateConfig
}
/**
* 报告模板 API 服务
*/
export const reportTemplateApi = {
/**
* 获取模板列表
*/
async list(): Promise<ReportTemplateListItem[]> {
const response = await request.get('/api/functional-report/templates')
return response as any
},
/**
* 获取单个模板详情
* @param id 模板 ID
*/
async get(id: number): Promise<ReportTemplateResponse> {
const response = await request.get(`/api/functional-report/templates/${id}`)
return response as any
},
/**
* 创建模板
* @param data 创建请求
*/
async create(data: ReportTemplateCreateRequest): Promise<ReportTemplateResponse> {
const response = await request.post('/api/functional-report/templates', data)
return response as any
},
/**
* 更新模板
* @param id 模板 ID
* @param data 更新请求
*/
async update(id: number, data: ReportTemplateUpdateRequest): Promise<ReportTemplateResponse> {
const response = await request.put(`/api/functional-report/templates/${id}`, data)
return response as any
},
/**
* 删除模板
* @param id 模板 ID
*/
async delete(id: number): Promise<void> {
const response = await request.delete(`/api/functional-report/templates/${id}`)
return response as any
},
/**
* 设为默认模板
* @param id 模板 ID
*/
async setDefault(id: number): Promise<ReportTemplateResponse> {
const response = await request.put(`/api/functional-report/templates/${id}/default`)
return response as any
}
}
\ No newline at end of file
......@@ -91,6 +91,13 @@
<el-input v-model="projectName" placeholder="请输入项目名称" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="报告模板">
<el-select v-model="selectedTemplateId" placeholder="使用默认模板" clearable style="width: 100%">
<el-option v-for="tpl in templateList" :key="tpl.id" :label="tpl.name" :value="tpl.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="报告格式">
<el-radio-group v-model="reportFormat">
......@@ -311,7 +318,7 @@
* 3. 下载、预览报告
*/
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import {
Refresh,
......@@ -324,6 +331,7 @@ import {
CircleClose
} from '@element-plus/icons-vue'
import { functionalReportApi } from '@/api/functionalReport'
import { reportTemplateApi } from '@/api/reportTemplate'
// ==================== 响应式数据 ====================
......@@ -339,6 +347,10 @@ const reportFormat = ref('docx')
const generating = ref(false)
const generateResult = ref<any>(null)
// 报告模板
const selectedTemplateId = ref<number | undefined>(undefined)
const templateList = ref<any[]>([])
// ERP 上传选项
const erpEnabled = ref(false)
const projectEnabled = ref(false)
......@@ -376,6 +388,19 @@ const chartLabels: Record<string, string> = {
// ==================== 方法定义 ====================
/** 加载报告模板列表 */
const loadTemplates = async () => {
try {
templateList.value = await reportTemplateApi.list()
} catch (error: any) {
console.warn('加载报告模板失败:', error.message)
}
}
onMounted(() => {
loadTemplates()
})
/** 上传文件 */
const handleUpload = async () => {
if (!testcaseFile.value || !buglistFile.value) {
......@@ -420,6 +445,7 @@ const handleGenerate = async () => {
sessionId.value,
projectName.value,
reportFormat.value,
selectedTemplateId.value,
{
upload_to_erp: erpEnabled.value,
upload_to_project: projectEnabled.value,
......@@ -471,6 +497,7 @@ const resetAll = () => {
sessionId.value = ''
projectName.value = ''
reportFormat.value = 'docx'
selectedTemplateId.value = undefined
generateResult.value = null
previewVisible.value = false
erpEnabled.value = false
......
此差异已折叠。
......@@ -8,9 +8,7 @@
<template>
<div class="functional-report-page">
<div class="page-header">
<h2>功能测试报告</h2>
</div>
<!-- 子页面自带标题(生成报告 / ERP配置),父容器不再重复渲染标题 -->
<router-view />
</div>
</template>
......@@ -18,9 +16,10 @@
<script setup lang="ts">
/**
* 功能测试报告模块容器组件
* 个子页面通过路由切换:
* 个子页面通过路由切换:
* 1. /reports/functional/generate — 生成报告
* 2. /reports/functional/erp-config — ERP 配置
* 2. /reports/functional/template-config — 模板配置
* 3. /reports/functional/erp-config — ERP 配置
*/
</script>
......@@ -28,18 +27,4 @@
.functional-report-page {
padding: 0;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.page-header h2 {
font-size: 20px;
font-weight: 600;
color: #303133;
margin: 0;
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论