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

feat(menu): 菜单分类架构与步骤编辑功能

- 后端: Module/TestCase/Execution 新增 module_type/case_type 字段,database _ensure_columns 自动补齐旧库列;modules/cases/executions/reports 路由与 service 层支持按类型筛选,创建用例/执行时透传/填充 case_type

- 前端: router 改 /:type? 可选参数,App.vue 模块/用例/执行/报告改父子菜单;Modules/Cases/Execution/Reports 读取路由 type 筛选;新建 CaseStepEditor UI自动化步骤编辑器(12动作+8断言动态表单,增删/上下移/自动重排)

- 平台更名为"测试管理平台"(App.vue / index.html)

- 修复构建: CreateCaseRequest 类型对齐 snake_case、清理未使用导入、补装 terser
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 12b134b7
此差异已折叠。
...@@ -89,7 +89,8 @@ async def init_db() -> None: ...@@ -89,7 +89,8 @@ async def init_db() -> None:
""" """
初始化数据库 初始化数据库
创建所有表结构。在应用启动时调用。 创建所有表结构,并对已存在的表补齐新增字段(SQLite 兼容)。
在应用启动时调用。
Example: Example:
>>> import asyncio >>> import asyncio
...@@ -97,9 +98,48 @@ async def init_db() -> None: ...@@ -97,9 +98,48 @@ async def init_db() -> None:
""" """
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
# SQLite 对已存在的表不会自动加列,需手动补齐
await _ensure_columns(conn)
logger.info("数据库表结构初始化完成") logger.info("数据库表结构初始化完成")
async def _ensure_columns(conn) -> None:
"""
补齐已存在表的新增字段(SQLite ALTER TABLE ADD COLUMN)
SQLite 的 create_all 只创建不存在的表,已存在的表不会加新列。
本方法检查并补充新增字段,保证旧库平滑升级。
Args:
conn: 异步数据库连接(AsyncConnection),内部通过 run_sync 执行同步检查
"""
from sqlalchemy import text, inspect
# 需要补齐的字段:[(表名, 列名, 列定义SQL)]
columns_to_add = [
("modules", "module_type", "VARCHAR(20) DEFAULT 'standard'"),
("test_cases", "case_type", "VARCHAR(20) DEFAULT 'ui'"),
("executions", "case_type", "VARCHAR(20) DEFAULT 'ui'"),
]
def _do_ensure(sync_conn) -> None:
"""同步执行:检查并补齐字段(在 run_sync 内调用,可使用 inspect)"""
inspector = inspect(sync_conn)
for table, column, col_def in columns_to_add:
try:
cols = [c["name"] for c in inspector.get_columns(table)]
if column not in cols:
sync_conn.execute(
text(f"ALTER TABLE {table} ADD COLUMN {column} {col_def}")
)
logger.info(f"补齐字段: {table}.{column}")
except Exception as e:
logger.warning(f"补齐字段失败 {table}.{column}: {e}")
# AsyncConnection 不支持直接 inspect,必须通过 run_sync 在同步上下文中执行
await conn.run_sync(_do_ensure)
async def drop_db() -> None: async def drop_db() -> None:
""" """
删除所有数据库表 删除所有数据库表
......
...@@ -82,6 +82,11 @@ class Execution(Base): ...@@ -82,6 +82,11 @@ class Execution(Base):
) )
environment: Mapped[str] = mapped_column(String(50), default="default", comment="执行环境") environment: Mapped[str] = mapped_column(String(50), default="default", comment="执行环境")
config: Mapped[dict] = mapped_column(JSON, default=dict, comment="执行配置") config: Mapped[dict] = mapped_column(JSON, default=dict, comment="执行配置")
case_type: Mapped[str] = mapped_column(
String(20),
default="ui",
comment="用例类型: ui/api/security/deploy(用于按类型筛选执行记录)"
)
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime, DateTime,
default=datetime.utcnow, default=datetime.utcnow,
...@@ -122,6 +127,7 @@ class Execution(Base): ...@@ -122,6 +127,7 @@ class Execution(Base):
"end_time": self.end_time.isoformat() if self.end_time else None, "end_time": self.end_time.isoformat() if self.end_time else None,
"environment": self.environment, "environment": self.environment,
"config": self.config or {}, "config": self.config or {},
"case_type": self.case_type,
"created_at": self.created_at.isoformat() if self.created_at else None, "created_at": self.created_at.isoformat() if self.created_at else None,
} }
......
...@@ -46,6 +46,11 @@ class Module(Base): ...@@ -46,6 +46,11 @@ class Module(Base):
description: Mapped[str] = mapped_column(Text, default="", comment="模块描述") description: Mapped[str] = mapped_column(Text, default="", comment="模块描述")
icon: Mapped[str] = mapped_column(String(50), default="folder", comment="模块图标") icon: Mapped[str] = mapped_column(String(50), default="folder", comment="模块图标")
order: Mapped[int] = mapped_column(Integer, default=0, comment="排序序号") order: Mapped[int] = mapped_column(Integer, default=0, comment="排序序号")
module_type: Mapped[str] = mapped_column(
String(20),
default="standard",
comment="模块类型: standard(标准模块)/custom(项目定制模块)"
)
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime, DateTime,
default=datetime.utcnow, default=datetime.utcnow,
...@@ -82,6 +87,7 @@ class Module(Base): ...@@ -82,6 +87,7 @@ class Module(Base):
"description": self.description, "description": self.description,
"icon": self.icon, "icon": self.icon,
"order": self.order, "order": self.order,
"module_type": self.module_type,
"created_at": self.created_at.isoformat() if self.created_at else None, "created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None,
} }
...@@ -72,6 +72,11 @@ class TestCase(Base): ...@@ -72,6 +72,11 @@ class TestCase(Base):
tags: Mapped[list] = mapped_column(JSON, default=list, comment="标签列表") tags: Mapped[list] = mapped_column(JSON, default=list, comment="标签列表")
steps: Mapped[list] = mapped_column(JSON, default=list, comment="测试步骤") steps: Mapped[list] = mapped_column(JSON, default=list, comment="测试步骤")
config: Mapped[dict] = mapped_column(JSON, default=dict, comment="执行配置") config: Mapped[dict] = mapped_column(JSON, default=dict, comment="执行配置")
case_type: Mapped[str] = mapped_column(
String(20),
default="ui",
comment="用例类型: ui/api/security/deploy/performance/functional"
)
# 新增字段:依赖管理和参数化支持 # 新增字段:依赖管理和参数化支持
depends_on: Mapped[list] = mapped_column(JSON, default=list, comment="依赖用例ID列表") depends_on: Mapped[list] = mapped_column(JSON, default=list, comment="依赖用例ID列表")
...@@ -135,6 +140,7 @@ class TestCase(Base): ...@@ -135,6 +140,7 @@ class TestCase(Base):
"tags": self.tags or [], "tags": self.tags or [],
"steps": self.steps or [], "steps": self.steps or [],
"config": self.config or {}, "config": self.config or {},
"case_type": self.case_type,
"depends_on": self.depends_on or [], "depends_on": self.depends_on or [],
"parameters": self.parameters or {}, "parameters": self.parameters or {},
"condition": self.condition or {}, "condition": self.condition or {},
......
...@@ -30,6 +30,32 @@ logger = logging.getLogger(__name__) ...@@ -30,6 +30,32 @@ logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
def _case_to_response(c) -> TestCaseResponse:
"""
统一构造用例响应对象(包含 case_type 等所有字段)
Args:
c: TestCase ORM 对象
Returns:
TestCaseResponse: 用例响应
"""
return TestCaseResponse(
id=c.id,
module_id=c.module_id,
name=c.name,
description=c.description,
status=c.status,
priority=c.priority,
tags=c.tags or [],
steps=c.steps or [],
config=c.config or {},
case_type=getattr(c, "case_type", None) or "ui",
created_at=c.created_at,
updated_at=c.updated_at,
)
def get_case_service(db: AsyncSession = Depends(get_db)) -> CaseService: def get_case_service(db: AsyncSession = Depends(get_db)) -> CaseService:
""" """
获取用例服务实例(依赖注入) 获取用例服务实例(依赖注入)
...@@ -49,6 +75,7 @@ async def list_cases( ...@@ -49,6 +75,7 @@ async def list_cases(
keyword: Optional[str] = Query(None, description="关键词搜索"), keyword: Optional[str] = Query(None, description="关键词搜索"),
status: Optional[str] = Query(None, description="状态筛选"), status: Optional[str] = Query(None, description="状态筛选"),
priority: Optional[str] = Query(None, description="优先级筛选"), priority: Optional[str] = Query(None, description="优先级筛选"),
case_type: Optional[str] = Query(None, description="用例类型筛选: ui/api/security/deploy"),
skip: int = Query(0, ge=0, description="跳过记录数"), skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"), limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
service: CaseService = Depends(get_case_service), service: CaseService = Depends(get_case_service),
...@@ -56,13 +83,14 @@ async def list_cases( ...@@ -56,13 +83,14 @@ async def list_cases(
""" """
获取用例列表 获取用例列表
支持分页查询和按模块/关键词/状态/优先级筛选。 支持分页查询和按模块/关键词/状态/优先级/类型筛选。
Args: Args:
module_id (Optional[str]): 模块ID筛选 module_id (Optional[str]): 模块ID筛选
keyword (Optional[str]): 模糊搜索关键词 keyword (Optional[str]): 模糊搜索关键词
status (Optional[str]): 状态过滤 status (Optional[str]): 状态过滤
priority (Optional[str]): 优先级过滤 priority (Optional[str]): 优先级过滤
case_type (Optional[str]): 用例类型过滤 ui/api/security/deploy
skip (int): 跳过的记录数 skip (int): 跳过的记录数
limit (int): 返回记录数上限 limit (int): 返回记录数上限
service (CaseService): 用例服务实例 service (CaseService): 用例服务实例
...@@ -76,26 +104,12 @@ async def list_cases( ...@@ -76,26 +104,12 @@ async def list_cases(
keyword=keyword, keyword=keyword,
status=status, status=status,
priority=priority, priority=priority,
case_type=case_type,
skip=skip, skip=skip,
limit=limit, limit=limit,
) )
items = [ items = [_case_to_response(c) for c in cases]
TestCaseResponse(
id=c.id,
module_id=c.module_id,
name=c.name,
description=c.description,
status=c.status,
priority=c.priority,
tags=c.tags or [],
steps=c.steps or [],
config=c.config or {},
created_at=c.created_at,
updated_at=c.updated_at,
)
for c in cases
]
return TestCaseListResponse(total=total, items=items) return TestCaseListResponse(total=total, items=items)
...@@ -127,19 +141,7 @@ async def get_case( ...@@ -127,19 +141,7 @@ async def get_case(
if not case: if not case:
raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}") raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}")
return TestCaseResponse( return _case_to_response(case)
id=case.id,
module_id=case.module_id,
name=case.name,
description=case.description,
status=case.status,
priority=case.priority,
tags=case.tags or [],
steps=case.steps or [],
config=case.config or {},
created_at=case.created_at,
updated_at=case.updated_at,
)
except HTTPException: except HTTPException:
raise raise
...@@ -168,19 +170,7 @@ async def create_case( ...@@ -168,19 +170,7 @@ async def create_case(
""" """
try: try:
case = await service.create(data) case = await service.create(data)
return TestCaseResponse( return _case_to_response(case)
id=case.id,
module_id=case.module_id,
name=case.name,
description=case.description,
status=case.status,
priority=case.priority,
tags=case.tags or [],
steps=case.steps or [],
config=case.config or {},
created_at=case.created_at,
updated_at=case.updated_at,
)
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
...@@ -216,19 +206,7 @@ async def update_case( ...@@ -216,19 +206,7 @@ async def update_case(
if not case: if not case:
raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}") raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}")
return TestCaseResponse( return _case_to_response(case)
id=case.id,
module_id=case.module_id,
name=case.name,
description=case.description,
status=case.status,
priority=case.priority,
tags=case.tags or [],
steps=case.steps or [],
config=case.config or {},
created_at=case.created_at,
updated_at=case.updated_at,
)
except HTTPException: except HTTPException:
raise raise
...@@ -289,19 +267,7 @@ async def copy_case( ...@@ -289,19 +267,7 @@ async def copy_case(
if not new_case: if not new_case:
raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}") raise HTTPException(status_code=404, detail=f"用例不存在: {case_id}")
return TestCaseResponse( return _case_to_response(new_case)
id=new_case.id,
module_id=new_case.module_id,
name=new_case.name,
description=new_case.description,
status=new_case.status,
priority=new_case.priority,
tags=new_case.tags or [],
steps=new_case.steps or [],
config=new_case.config or {},
created_at=new_case.created_at,
updated_at=new_case.updated_at,
)
except HTTPException: except HTTPException:
raise raise
......
...@@ -85,6 +85,7 @@ async def list_executions( ...@@ -85,6 +85,7 @@ async def list_executions(
skip: int = Query(0, ge=0, description="跳过记录数"), skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(50, ge=1, le=200, description="返回记录数上限"), limit: int = Query(50, ge=1, le=200, description="返回记录数上限"),
status: Optional[str] = Query(None, description="状态筛选"), status: Optional[str] = Query(None, description="状态筛选"),
case_type: Optional[str] = Query(None, description="用例类型筛选: ui/api/security/deploy/performance/functional"),
service: ExecutionService = Depends(get_execution_service), service: ExecutionService = Depends(get_execution_service),
): ):
""" """
...@@ -94,6 +95,7 @@ async def list_executions( ...@@ -94,6 +95,7 @@ async def list_executions(
skip (int): 跳过记录数 skip (int): 跳过记录数
limit (int): 返回记录数上限 limit (int): 返回记录数上限
status (Optional[str]): 状态筛选 status (Optional[str]): 状态筛选
case_type (Optional[str]): 用例类型筛选
service (ExecutionService): 执行服务实例 service (ExecutionService): 执行服务实例
Returns: Returns:
...@@ -101,7 +103,7 @@ async def list_executions( ...@@ -101,7 +103,7 @@ async def list_executions(
""" """
try: try:
executions, total = await service.list_executions( executions, total = await service.list_executions(
skip=skip, limit=limit, status=status skip=skip, limit=limit, status=status, case_type=case_type
) )
return { return {
"total": total, "total": total,
......
...@@ -47,23 +47,25 @@ def get_module_service(db: AsyncSession = Depends(get_db)) -> ModuleService: ...@@ -47,23 +47,25 @@ def get_module_service(db: AsyncSession = Depends(get_db)) -> ModuleService:
async def list_modules( async def list_modules(
skip: int = Query(0, ge=0, description="跳过的记录数"), skip: int = Query(0, ge=0, description="跳过的记录数"),
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"), limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
module_type: Optional[str] = Query(None, description="模块类型筛选: standard/custom"),
service: ModuleService = Depends(get_module_service) service: ModuleService = Depends(get_module_service)
): ):
""" """
获取模块列表 获取模块列表
支持分页查询,按排序号升序返回。 支持分页查询,按排序号升序返回,可按模块类型筛选
Args: Args:
skip (int): 跳过的记录数 skip (int): 跳过的记录数
limit (int): 返回记录数上限 limit (int): 返回记录数上限
module_type (Optional[str]): 模块类型筛选 standard/custom
service (ModuleService): 模块服务实例 service (ModuleService): 模块服务实例
Returns: Returns:
ModuleListResponse: 模块列表响应 ModuleListResponse: 模块列表响应
""" """
try: try:
modules, total = await service.list(skip=skip, limit=limit) modules, total = await service.list(skip=skip, limit=limit, module_type=module_type)
# 转换为响应格式 # 转换为响应格式
items = [ items = [
...@@ -73,6 +75,7 @@ async def list_modules( ...@@ -73,6 +75,7 @@ async def list_modules(
description=m.description, description=m.description,
icon=m.icon, icon=m.icon,
order=m.order, order=m.order,
module_type=getattr(m, 'module_type', 'standard') or 'standard',
created_at=m.created_at, created_at=m.created_at,
updated_at=m.updated_at, updated_at=m.updated_at,
case_count=getattr(m, 'case_count', 0) case_count=getattr(m, 'case_count', 0)
...@@ -116,6 +119,7 @@ async def get_module( ...@@ -116,6 +119,7 @@ async def get_module(
description=module.description, description=module.description,
icon=module.icon, icon=module.icon,
order=module.order, order=module.order,
module_type=getattr(module, 'module_type', 'standard') or 'standard',
created_at=module.created_at, created_at=module.created_at,
updated_at=module.updated_at, updated_at=module.updated_at,
case_count=getattr(module, 'case_count', 0) case_count=getattr(module, 'case_count', 0)
...@@ -154,6 +158,7 @@ async def create_module( ...@@ -154,6 +158,7 @@ async def create_module(
description=module.description, description=module.description,
icon=module.icon, icon=module.icon,
order=module.order, order=module.order,
module_type=getattr(module, 'module_type', 'standard') or 'standard',
created_at=module.created_at, created_at=module.created_at,
updated_at=module.updated_at, updated_at=module.updated_at,
case_count=0 case_count=0
...@@ -199,6 +204,7 @@ async def update_module( ...@@ -199,6 +204,7 @@ async def update_module(
description=module.description, description=module.description,
icon=module.icon, icon=module.icon,
order=module.order, order=module.order,
module_type=getattr(module, 'module_type', 'standard') or 'standard',
created_at=module.created_at, created_at=module.created_at,
updated_at=module.updated_at, updated_at=module.updated_at,
case_count=getattr(module, 'case_count', 0) case_count=getattr(module, 'case_count', 0)
......
...@@ -130,6 +130,7 @@ async def download_report( ...@@ -130,6 +130,7 @@ async def download_report(
async def list_reports( async def list_reports(
skip: int = Query(0, ge=0, description="跳过记录数"), skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(50, ge=1, le=200, description="返回记录数上限"), limit: int = Query(50, ge=1, le=200, description="返回记录数上限"),
case_type: Optional[str] = Query(None, description="用例类型筛选: ui/api/security/deploy/performance/functional"),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
""" """
...@@ -138,6 +139,7 @@ async def list_reports( ...@@ -138,6 +139,7 @@ async def list_reports(
Args: Args:
skip (int): 跳过记录数 skip (int): 跳过记录数
limit (int): 返回记录数上限 limit (int): 返回记录数上限
case_type (Optional[str]): 用例类型筛选(按关联执行的 case_type)
db (AsyncSession): 数据库会话 db (AsyncSession): 数据库会话
Returns: Returns:
...@@ -145,9 +147,14 @@ async def list_reports( ...@@ -145,9 +147,14 @@ async def list_reports(
""" """
from sqlalchemy import func from sqlalchemy import func
# 构建筛选条件:已完成 + 可选按用例类型
conditions = [Execution.status == "completed"]
if case_type:
conditions.append(Execution.case_type == case_type)
query = ( query = (
select(Execution) select(Execution)
.where(Execution.status == "completed") .where(*conditions)
.order_by(desc(Execution.created_at)) .order_by(desc(Execution.created_at))
.offset(skip) .offset(skip)
.limit(limit) .limit(limit)
...@@ -155,9 +162,7 @@ async def list_reports( ...@@ -155,9 +162,7 @@ async def list_reports(
result = await db.execute(query) result = await db.execute(query)
items = result.scalars().all() items = result.scalars().all()
count_query = select(func.count(Execution.id)).where( count_query = select(func.count(Execution.id)).where(*conditions)
Execution.status == "completed"
)
result = await db.execute(count_query) result = await db.execute(count_query)
total = result.scalar() or 0 total = result.scalar() or 0
......
...@@ -30,6 +30,7 @@ class ModuleBase(BaseModel): ...@@ -30,6 +30,7 @@ class ModuleBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="模块名称") name: str = Field(..., min_length=1, max_length=100, description="模块名称")
description: str = Field(default="", max_length=500, description="模块描述") description: str = Field(default="", max_length=500, description="模块描述")
icon: str = Field(default="folder", max_length=50, description="模块图标") icon: str = Field(default="folder", max_length=50, description="模块图标")
module_type: str = Field(default="standard", description="模块类型: standard/custom")
class ModuleCreate(ModuleBase): class ModuleCreate(ModuleBase):
...@@ -62,6 +63,7 @@ class ModuleUpdate(BaseModel): ...@@ -62,6 +63,7 @@ class ModuleUpdate(BaseModel):
description: Optional[str] = Field(None, max_length=500, description="模块描述") description: Optional[str] = Field(None, max_length=500, description="模块描述")
icon: Optional[str] = Field(None, max_length=50, description="模块图标") icon: Optional[str] = Field(None, max_length=50, description="模块图标")
order: Optional[int] = Field(None, ge=0, description="排序序号") order: Optional[int] = Field(None, ge=0, description="排序序号")
module_type: Optional[str] = Field(None, description="模块类型: standard/custom")
class ModuleResponse(ModuleBase): class ModuleResponse(ModuleBase):
......
...@@ -67,6 +67,7 @@ class TestCaseBase(BaseModel): ...@@ -67,6 +67,7 @@ class TestCaseBase(BaseModel):
default_factory=lambda: {"timeout": 30000, "retry": 0, "screenshot": True}, default_factory=lambda: {"timeout": 30000, "retry": 0, "screenshot": True},
description="执行配置" description="执行配置"
) )
case_type: str = Field(default="ui", description="用例类型: ui/api/security/deploy")
class TestCaseCreate(TestCaseBase): class TestCaseCreate(TestCaseBase):
...@@ -107,6 +108,7 @@ class TestCaseUpdate(BaseModel): ...@@ -107,6 +108,7 @@ class TestCaseUpdate(BaseModel):
tags: Optional[List[str]] = Field(None, description="标签列表") tags: Optional[List[str]] = Field(None, description="标签列表")
steps: Optional[List[StepDefinition]] = Field(None, description="测试步骤") steps: Optional[List[StepDefinition]] = Field(None, description="测试步骤")
config: Optional[Dict[str, Any]] = Field(None, description="执行配置") config: Optional[Dict[str, Any]] = Field(None, description="执行配置")
case_type: Optional[str] = Field(None, description="用例类型: ui/api/security/deploy")
class TestCaseResponse(TestCaseBase): class TestCaseResponse(TestCaseBase):
...@@ -178,4 +180,5 @@ class TestCaseImport(BaseModel): ...@@ -178,4 +180,5 @@ class TestCaseImport(BaseModel):
config: Dict[str, Any] = Field( config: Dict[str, Any] = Field(
default_factory=lambda: {"timeout": 30000, "retry": 0, "screenshot": True}, default_factory=lambda: {"timeout": 30000, "retry": 0, "screenshot": True},
description="执行配置" description="执行配置"
) )
\ No newline at end of file case_type: str = Field(default="ui", description="用例类型: ui/api/security/deploy")
\ No newline at end of file
...@@ -68,19 +68,21 @@ class CaseService: ...@@ -68,19 +68,21 @@ class CaseService:
keyword: Optional[str] = None, keyword: Optional[str] = None,
status: Optional[str] = None, status: Optional[str] = None,
priority: Optional[str] = None, priority: Optional[str] = None,
case_type: Optional[str] = None,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
) -> Tuple[List[TestCase], int]: ) -> Tuple[List[TestCase], int]:
""" """
获取用例列表(分页、筛选) 获取用例列表(分页、筛选)
支持按模块、关键词、状态、优先级筛选。 支持按模块、关键词、状态、优先级、类型筛选。
Args: Args:
module_id (Optional[str]): 模块ID筛选 module_id (Optional[str]): 模块ID筛选
keyword (Optional[str]): 关键词搜索(匹配名称和描述) keyword (Optional[str]): 关键词搜索(匹配名称和描述)
status (Optional[str]): 状态筛选 (active/disabled) status (Optional[str]): 状态筛选 (active/disabled)
priority (Optional[str]): 优先级筛选 (high/medium/low) priority (Optional[str]): 优先级筛选 (high/medium/low)
case_type (Optional[str]): 用例类型筛选 (ui/api/security/deploy/performance/functional)
skip (int): 跳过的记录数,默认0 skip (int): 跳过的记录数,默认0
limit (int): 返回记录数上限,默认100 limit (int): 返回记录数上限,默认100
...@@ -91,6 +93,7 @@ class CaseService: ...@@ -91,6 +93,7 @@ class CaseService:
>>> cases, total = await service.list( >>> cases, total = await service.list(
... module_id="module_001", ... module_id="module_001",
... keyword="登录", ... keyword="登录",
... case_type="ui",
... skip=0, limit=20 ... skip=0, limit=20
... ) ... )
""" """
...@@ -107,6 +110,8 @@ class CaseService: ...@@ -107,6 +110,8 @@ class CaseService:
conditions.append(TestCase.status == status) conditions.append(TestCase.status == status)
if priority: if priority:
conditions.append(TestCase.priority == priority) conditions.append(TestCase.priority == priority)
if case_type:
conditions.append(TestCase.case_type == case_type)
# 查询总数 # 查询总数
count_query = select(func.count(TestCase.id)) count_query = select(func.count(TestCase.id))
...@@ -193,6 +198,7 @@ class CaseService: ...@@ -193,6 +198,7 @@ class CaseService:
tags=data.tags or [], tags=data.tags or [],
steps=[s.model_dump() for s in data.steps] if data.steps else [], steps=[s.model_dump() for s in data.steps] if data.steps else [],
config=data.config or {"timeout": 30000, "retry": 0, "screenshot": True}, config=data.config or {"timeout": 30000, "retry": 0, "screenshot": True},
case_type=data.case_type or "ui",
) )
self.db.add(test_case) self.db.add(test_case)
...@@ -289,6 +295,7 @@ class CaseService: ...@@ -289,6 +295,7 @@ class CaseService:
tags=source.tags.copy() if source.tags else [], tags=source.tags.copy() if source.tags else [],
steps=source.steps.copy() if source.steps else [], steps=source.steps.copy() if source.steps else [],
config=source.config.copy() if source.config else {}, config=source.config.copy() if source.config else {},
case_type=source.case_type or "ui",
) )
self.db.add(new_case) self.db.add(new_case)
...@@ -350,6 +357,7 @@ class CaseService: ...@@ -350,6 +357,7 @@ class CaseService:
tags=case_data.tags or [], tags=case_data.tags or [],
steps=[s.model_dump() for s in case_data.steps] if case_data.steps else [], steps=[s.model_dump() for s in case_data.steps] if case_data.steps else [],
config=case_data.config or {"timeout": 30000, "retry": 0, "screenshot": True}, config=case_data.config or {"timeout": 30000, "retry": 0, "screenshot": True},
case_type=case_data.case_type or "ui",
) )
self.db.add(test_case) self.db.add(test_case)
await self.db.flush() await self.db.flush()
...@@ -402,6 +410,7 @@ class CaseService: ...@@ -402,6 +410,7 @@ class CaseService:
"tags": c.tags or [], "tags": c.tags or [],
"steps": c.steps or [], "steps": c.steps or [],
"config": c.config or {}, "config": c.config or {},
"case_type": getattr(c, "case_type", None) or "ui",
} }
for c in cases for c in cases
] ]
\ No newline at end of file
...@@ -69,6 +69,7 @@ class ExecutionService: ...@@ -69,6 +69,7 @@ class ExecutionService:
skip: int = 0, skip: int = 0,
limit: int = 50, limit: int = 50,
status: Optional[str] = None, status: Optional[str] = None,
case_type: Optional[str] = None,
) -> Tuple[List[Execution], int]: ) -> Tuple[List[Execution], int]:
""" """
获取执行记录列表 获取执行记录列表
...@@ -77,6 +78,7 @@ class ExecutionService: ...@@ -77,6 +78,7 @@ class ExecutionService:
skip (int): 跳过记录数 skip (int): 跳过记录数
limit (int): 返回记录数上限 limit (int): 返回记录数上限
status (Optional[str]): 状态筛选 status (Optional[str]): 状态筛选
case_type (Optional[str]): 用例类型筛选 (ui/api/security/deploy/performance/functional)
Returns: Returns:
Tuple[List[Execution], int]: (执行记录列表, 总数) Tuple[List[Execution], int]: (执行记录列表, 总数)
...@@ -84,6 +86,8 @@ class ExecutionService: ...@@ -84,6 +86,8 @@ class ExecutionService:
conditions = [] conditions = []
if status: if status:
conditions.append(Execution.status == status) conditions.append(Execution.status == status)
if case_type:
conditions.append(Execution.case_type == case_type)
count_query = select(func.count(Execution.id)) count_query = select(func.count(Execution.id))
if conditions: if conditions:
...@@ -145,6 +149,12 @@ class ExecutionService: ...@@ -145,6 +149,12 @@ class ExecutionService:
date_str = datetime.now().strftime("%Y%m%d-%H%M%S") date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
name = f"测试执行-{date_str}" name = f"测试执行-{date_str}"
# 先查询用例,取首个用例的 case_type 作为执行记录的类型(用于按类型筛选)
cases_query = select(TestCase).where(TestCase.id.in_(case_ids))
cases_result = await self.db.execute(cases_query)
cases = list(cases_result.scalars().all())
first_case_type = cases[0].case_type if cases else "ui"
execution = Execution( execution = Execution(
id=generate_id("exec"), id=generate_id("exec"),
name=name, name=name,
...@@ -154,6 +164,7 @@ class ExecutionService: ...@@ -154,6 +164,7 @@ class ExecutionService:
status="pending", status="pending",
environment=environment, environment=environment,
config=config or {}, config=config or {},
case_type=first_case_type,
) )
self.db.add(execution) self.db.add(execution)
...@@ -161,10 +172,6 @@ class ExecutionService: ...@@ -161,10 +172,6 @@ class ExecutionService:
await self.db.refresh(execution) await self.db.refresh(execution)
# 创建用例结果记录 # 创建用例结果记录
cases_query = select(TestCase).where(TestCase.id.in_(case_ids))
cases_result = await self.db.execute(cases_query)
cases = list(cases_result.scalars().all())
for case in cases: for case in cases:
case_result = CaseResult( case_result = CaseResult(
id=generate_id("result"), id=generate_id("result"),
...@@ -176,7 +183,7 @@ class ExecutionService: ...@@ -176,7 +183,7 @@ class ExecutionService:
self.db.add(case_result) self.db.add(case_result)
await self.db.flush() await self.db.flush()
logger.info(f"执行记录创建成功: id={execution.id}, name={name}, cases={len(case_ids)}") logger.info(f"执行记录创建成功: id={execution.id}, name={name}, cases={len(case_ids)}, case_type={first_case_type}")
return execution return execution
async def run_execution( async def run_execution(
......
...@@ -59,7 +59,8 @@ class ModuleService: ...@@ -59,7 +59,8 @@ class ModuleService:
self, self,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
order_by: str = "order" order_by: str = "order",
module_type: Optional[str] = None,
) -> Tuple[List[Module], int]: ) -> Tuple[List[Module], int]:
""" """
获取模块列表(分页) 获取模块列表(分页)
...@@ -70,16 +71,24 @@ class ModuleService: ...@@ -70,16 +71,24 @@ class ModuleService:
skip (int): 跳过的记录数,默认0 skip (int): 跳过的记录数,默认0
limit (int): 返回记录数上限,默认100 limit (int): 返回记录数上限,默认100
order_by (str): 排序字段,默认 "order" order_by (str): 排序字段,默认 "order"
module_type (Optional[str]): 模块类型筛选 (standard/custom)
Returns: Returns:
Tuple[List[Module], int]: (模块列表, 总数) Tuple[List[Module], int]: (模块列表, 总数)
Example: Example:
>>> modules, total = await service.list(skip=0, limit=20) >>> modules, total = await service.list(skip=0, limit=20, module_type="standard")
>>> print(f"共 {total} 个模块") >>> print(f"共 {total} 个模块")
""" """
# 构建查询条件
conditions = []
if module_type:
conditions.append(Module.module_type == module_type)
# 查询总数 # 查询总数
count_query = select(func.count(Module.id)) count_query = select(func.count(Module.id))
if conditions:
count_query = count_query.where(*conditions)
total_result = await self.db.execute(count_query) total_result = await self.db.execute(count_query)
total = total_result.scalar() or 0 total = total_result.scalar() or 0
...@@ -90,6 +99,8 @@ class ModuleService: ...@@ -90,6 +99,8 @@ class ModuleService:
.offset(skip) .offset(skip)
.limit(limit) .limit(limit)
) )
if conditions:
query = query.where(*conditions)
result = await self.db.execute(query) result = await self.db.execute(query)
modules = list(result.scalars().all()) modules = list(result.scalars().all())
...@@ -170,7 +181,8 @@ class ModuleService: ...@@ -170,7 +181,8 @@ class ModuleService:
name=data.name.strip(), name=data.name.strip(),
description=data.description or "", description=data.description or "",
icon=data.icon or "folder", icon=data.icon or "folder",
order=max_order + 1 order=max_order + 1,
module_type=data.module_type or "standard",
) )
self.db.add(module) self.db.add(module)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>平台自动化测试系统</title> <title>测试管理平台</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
此差异已折叠。
...@@ -10,19 +10,20 @@ ...@@ -10,19 +10,20 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix" "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix"
}, },
"dependencies": { "dependencies": {
"vue": "^3.4.21", "@element-plus/icons-vue": "^2.3.1",
"vue-router": "^4.3.0",
"pinia": "^2.1.7",
"axios": "^1.6.7", "axios": "^1.6.7",
"echarts": "^5.5.0",
"element-plus": "^2.5.6", "element-plus": "^2.5.6",
"@element-plus/icons-vue": "^2.3.1", "pinia": "^2.1.7",
"echarts": "^5.5.0" "vue": "^3.4.21",
"vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"sass": "^1.72.0",
"terser": "^5.49.0",
"typescript": "^5.4.2", "typescript": "^5.4.2",
"vite": "^5.1.6", "vite": "^5.1.6",
"vue-tsc": "^2.0.6", "vue-tsc": "^2.0.6"
"sass": "^1.72.0"
} }
} }
\ No newline at end of file
...@@ -13,38 +13,76 @@ ...@@ -13,38 +13,76 @@
<el-aside width="220px" class="app-aside"> <el-aside width="220px" class="app-aside">
<div class="logo"> <div class="logo">
<el-icon :size="24"><Monitor /></el-icon> <el-icon :size="24"><Monitor /></el-icon>
<span>自动化测试平台</span> <span>测试管理平台</span>
</div> </div>
<el-menu <el-menu
:default-active="currentRoute" :default-active="currentRoute"
class="app-menu" class="app-menu"
router router
unique-opened
> >
<el-menu-item index="/"> <el-menu-item index="/">
<el-icon><DataBoard /></el-icon> <el-icon><DataBoard /></el-icon>
<span>总览</span> <span>总览</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="/modules">
<el-icon><Folder /></el-icon> <!-- 模块管理(父菜单) -->
<span>模块管理</span> <el-sub-menu index="/modules">
</el-menu-item> <template #title>
<el-menu-item index="/cases"> <el-icon><Folder /></el-icon>
<el-icon><Document /></el-icon> <span>模块管理</span>
<span>用例管理</span> </template>
</el-menu-item> <el-menu-item index="/modules/standard">标准模块</el-menu-item>
<el-menu-item index="/modules/custom">项目定制模块</el-menu-item>
</el-sub-menu>
<!-- 用例管理(父菜单) -->
<el-sub-menu index="/cases">
<template #title>
<el-icon><Document /></el-icon>
<span>用例管理</span>
</template>
<el-menu-item index="/cases/ui">UI自动化</el-menu-item>
<el-menu-item index="/cases/api">接口自动化</el-menu-item>
<el-menu-item index="/cases/security">安全自动化测试</el-menu-item>
<el-menu-item index="/cases/deploy">自动化部署测试</el-menu-item>
<el-menu-item index="/cases/performance">性能测试</el-menu-item>
<el-menu-item index="/cases/functional">功能测试</el-menu-item>
</el-sub-menu>
<el-menu-item index="/recorder"> <el-menu-item index="/recorder">
<el-icon><VideoCamera /></el-icon> <el-icon><VideoCamera /></el-icon>
<span>用例录制</span> <span>用例录制</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="/execution">
<el-icon><VideoPlay /></el-icon> <!-- 执行中心(父菜单,6 种类型) -->
<span>执行中心</span> <el-sub-menu index="/execution">
</el-menu-item> <template #title>
<el-menu-item index="/reports"> <el-icon><VideoPlay /></el-icon>
<el-icon><DataLine /></el-icon> <span>执行中心</span>
<span>报告中心</span> </template>
</el-menu-item> <el-menu-item index="/execution/ui">UI自动化</el-menu-item>
<el-menu-item index="/execution/api">接口自动化</el-menu-item>
<el-menu-item index="/execution/security">安全自动化测试</el-menu-item>
<el-menu-item index="/execution/deploy">自动化部署测试</el-menu-item>
<el-menu-item index="/execution/performance">性能测试</el-menu-item>
<el-menu-item index="/execution/functional">功能测试</el-menu-item>
</el-sub-menu>
<!-- 报告中心(父菜单,5 种类型,无部署) -->
<el-sub-menu index="/reports">
<template #title>
<el-icon><DataLine /></el-icon>
<span>报告中心</span>
</template>
<el-menu-item index="/reports/ui">UI自动化测试</el-menu-item>
<el-menu-item index="/reports/api">接口测试</el-menu-item>
<el-menu-item index="/reports/security">安全测试</el-menu-item>
<el-menu-item index="/reports/performance">性能测试</el-menu-item>
<el-menu-item index="/reports/functional">功能测试</el-menu-item>
</el-sub-menu>
<el-menu-item index="/settings"> <el-menu-item index="/settings">
<el-icon><Setting /></el-icon> <el-icon><Setting /></el-icon>
<span>系统配置</span> <span>系统配置</span>
...@@ -110,12 +148,49 @@ const currentEnv = ref('test') ...@@ -110,12 +148,49 @@ const currentEnv = ref('test')
const route = useRoute() const route = useRoute()
// ==================== 计算属性 ==================== // ==================== 计算属性 ====================
/** 当前路由 */
const currentRoute = computed(() => route.path) /** 分类标签映射:用例管理/执行中心 */
const CASE_TYPE_LABELS: Record<string, string> = {
ui: 'UI自动化',
api: '接口自动化',
security: '安全自动化测试',
deploy: '自动化部署测试',
performance: '性能测试',
functional: '功能测试',
}
/** 分类标签映射:报告中心(不含部署) */
const REPORT_TYPE_LABELS: Record<string, string> = {
ui: 'UI自动化测试',
api: '接口测试',
security: '安全测试',
performance: '性能测试',
functional: '功能测试',
}
/** 模块类型标签映射 */
const MODULE_TYPE_LABELS: Record<string, string> = {
standard: '标准模块',
custom: '项目定制模块',
}
/** 当前激活菜单项(无 type 时回退到默认子项以保持高亮) */
const currentRoute = computed(() => {
const seg = route.path.split('/').filter(Boolean)
const root = '/' + (seg[0] || '')
// 无 type 参数时回退到默认子项,保证菜单高亮
if (seg.length === 1 && ['/modules', '/cases', '/execution', '/reports'].includes(root)) {
const defaultType = root === '/modules' ? 'standard' : 'ui'
return `${root}/${defaultType}`
}
return route.path
})
/** 页面标题 */ /** 页面标题 */
const pageTitle = computed(() => { const pageTitle = computed(() => {
const titleMap: Record<string, string> = { const seg = route.path.split('/').filter(Boolean)
const root = '/' + (seg[0] || '')
const baseMap: Record<string, string> = {
'/': '测试总览', '/': '测试总览',
'/modules': '模块管理', '/modules': '模块管理',
'/cases': '用例管理', '/cases': '用例管理',
...@@ -124,7 +199,16 @@ const pageTitle = computed(() => { ...@@ -124,7 +199,16 @@ const pageTitle = computed(() => {
'/reports': '报告中心', '/reports': '报告中心',
'/settings': '系统配置' '/settings': '系统配置'
} }
return titleMap[route.path] || '自动化测试平台' const base = baseMap[root] || '测试管理平台'
const type = seg[1]
if (!type) return base
let typeLabel = ''
if (root === '/modules') typeLabel = MODULE_TYPE_LABELS[type]
else if (root === '/reports') typeLabel = REPORT_TYPE_LABELS[type]
else if (root === '/cases' || root === '/execution') typeLabel = CASE_TYPE_LABELS[type]
return typeLabel ? `${base} - ${typeLabel}` : base
}) })
</script> </script>
...@@ -170,6 +254,7 @@ html, body, #app { ...@@ -170,6 +254,7 @@ html, body, #app {
border-right: none; border-right: none;
background: transparent; background: transparent;
// 顶级菜单项
.el-menu-item { .el-menu-item {
color: #bfcbd9; color: #bfcbd9;
...@@ -179,6 +264,38 @@ html, body, #app { ...@@ -179,6 +264,38 @@ html, body, #app {
color: #409eff; color: #409eff;
} }
} }
// 父菜单标题(el-sub-menu)
.el-sub-menu {
.el-sub-menu__title {
color: #bfcbd9;
&:hover {
background: #263445;
}
.el-sub-menu__icon-arrow {
color: #bfcbd9;
}
}
// 嵌套子菜单容器背景(比侧边栏略深,体现层级)
.el-menu {
background: #1f2d3d;
}
// 子菜单项
.el-menu-item {
color: #bfcbd9;
background: transparent;
&:hover,
&.is-active {
background: #263445;
color: #409eff;
}
}
}
} }
} }
......
...@@ -21,6 +21,7 @@ export const caseApi = { ...@@ -21,6 +21,7 @@ export const caseApi = {
keyword?: string keyword?: string
status?: string status?: string
priority?: string priority?: string
case_type?: string
skip?: number skip?: number
limit?: number limit?: number
}): Promise<CaseListResponse> { }): Promise<CaseListResponse> {
......
...@@ -19,6 +19,7 @@ export const executionApi = { ...@@ -19,6 +19,7 @@ export const executionApi = {
skip?: number skip?: number
limit?: number limit?: number
status?: string status?: string
case_type?: string
}): Promise<{ total: number; items: any[] }> { }): Promise<{ total: number; items: any[] }> {
const response = await request.get('/api/executions', { params }) const response = await request.get('/api/executions', { params })
return response.data return response.data
......
...@@ -15,10 +15,11 @@ import type { Module, ModuleListResponse, CreateModuleRequest, UpdateModuleReque ...@@ -15,10 +15,11 @@ import type { Module, ModuleListResponse, CreateModuleRequest, UpdateModuleReque
export const moduleApi = { export const moduleApi = {
/** /**
* 获取模块列表 * 获取模块列表
* @param module_type 模块类型筛选 standard/custom
*/ */
async list(skip = 0, limit = 100): Promise<ModuleListResponse> { async list(skip = 0, limit = 100, module_type?: string): Promise<ModuleListResponse> {
const response = await request.get('/api/modules', { const response = await request.get('/api/modules', {
params: { skip, limit } params: { skip, limit, module_type }
}) })
return response.data return response.data
}, },
......
...@@ -15,7 +15,7 @@ export const reportApi = { ...@@ -15,7 +15,7 @@ export const reportApi = {
/** /**
* 获取报告列表(已完成的执行记录) * 获取报告列表(已完成的执行记录)
*/ */
async list(params?: { skip?: number; limit?: number }): Promise<{ total: number; items: any[] }> { async list(params?: { skip?: number; limit?: number; case_type?: string }): Promise<{ total: number; items: any[] }> {
const response = await request.get('/api/reports/list', { params }) const response = await request.get('/api/reports/list', { params })
return response.data return response.data
}, },
......
此差异已折叠。
...@@ -18,13 +18,13 @@ const routes: RouteRecordRaw[] = [ ...@@ -18,13 +18,13 @@ const routes: RouteRecordRaw[] = [
meta: { title: '测试总览' } meta: { title: '测试总览' }
}, },
{ {
path: '/modules', path: '/modules/:type?',
name: 'Modules', name: 'Modules',
component: () => import('@/views/Modules.vue'), component: () => import('@/views/Modules.vue'),
meta: { title: '模块管理' } meta: { title: '模块管理' }
}, },
{ {
path: '/cases', path: '/cases/:type?',
name: 'Cases', name: 'Cases',
component: () => import('@/views/Cases.vue'), component: () => import('@/views/Cases.vue'),
meta: { title: '用例管理' } meta: { title: '用例管理' }
...@@ -36,13 +36,13 @@ const routes: RouteRecordRaw[] = [ ...@@ -36,13 +36,13 @@ const routes: RouteRecordRaw[] = [
meta: { title: '用例录制' } meta: { title: '用例录制' }
}, },
{ {
path: '/execution', path: '/execution/:type?',
name: 'Execution', name: 'Execution',
component: () => import('@/views/Execution.vue'), component: () => import('@/views/Execution.vue'),
meta: { title: '执行中心' } meta: { title: '执行中心' }
}, },
{ {
path: '/reports', path: '/reports/:type?',
name: 'Reports', name: 'Reports',
component: () => import('@/views/Reports.vue'), component: () => import('@/views/Reports.vue'),
meta: { title: '报告中心' } meta: { title: '报告中心' }
...@@ -70,7 +70,7 @@ router.beforeEach((to, _from, next) => { ...@@ -70,7 +70,7 @@ router.beforeEach((to, _from, next) => {
// 设置页面标题 // 设置页面标题
const title = to.meta?.title as string const title = to.meta?.title as string
if (title) { if (title) {
document.title = `${title} - 平台自动化测试系统` document.title = `${title} - 测试管理平台`
} }
next() next()
}) })
......
...@@ -50,6 +50,8 @@ export interface TestCase { ...@@ -50,6 +50,8 @@ export interface TestCase {
steps: StepDefinition[] steps: StepDefinition[]
/** 执行配置 */ /** 执行配置 */
config: Record<string, any> config: Record<string, any>
/** 用例类型 ui/api/security/deploy/performance/functional */
caseType?: string
/** 创建时间 */ /** 创建时间 */
createdAt: string createdAt: string
/** 更新时间 */ /** 更新时间 */
...@@ -58,10 +60,12 @@ export interface TestCase { ...@@ -58,10 +60,12 @@ export interface TestCase {
/** 创建用例请求参数 */ /** 创建用例请求参数 */
export interface CreateCaseRequest { export interface CreateCaseRequest {
/** 所属模块ID */ /** 所属模块ID(后端 snake_case) */
moduleId: string module_id: string
/** 用例名称 */ /** 用例名称 */
name: string name: string
/** 状态: active/disabled(create 时后端默认 active,update 时生效) */
status?: string
/** 用例描述 */ /** 用例描述 */
description?: string description?: string
/** 优先级 */ /** 优先级 */
...@@ -72,6 +76,8 @@ export interface CreateCaseRequest { ...@@ -72,6 +76,8 @@ export interface CreateCaseRequest {
steps?: StepDefinition[] steps?: StepDefinition[]
/** 配置 */ /** 配置 */
config?: Record<string, any> config?: Record<string, any>
/** 用例类型 ui/api/security/deploy/performance/functional */
case_type?: string
} }
/** 用例列表响应 */ /** 用例列表响应 */
......
...@@ -18,6 +18,8 @@ export interface Module { ...@@ -18,6 +18,8 @@ export interface Module {
icon: string icon: string
/** 排序序号 */ /** 排序序号 */
order: number order: number
/** 模块类型 standard/custom */
moduleType?: string
/** 用例数量(扩展字段) */ /** 用例数量(扩展字段) */
caseCount?: number caseCount?: number
/** 通过率(扩展字段) */ /** 通过率(扩展字段) */
...@@ -36,6 +38,8 @@ export interface CreateModuleRequest { ...@@ -36,6 +38,8 @@ export interface CreateModuleRequest {
description?: string description?: string
/** 模块图标 */ /** 模块图标 */
icon?: string icon?: string
/** 模块类型 standard/custom */
module_type?: string
} }
/** 更新模块请求参数 */ /** 更新模块请求参数 */
...@@ -44,6 +48,7 @@ export interface UpdateModuleRequest { ...@@ -44,6 +48,7 @@ export interface UpdateModuleRequest {
description?: string description?: string
icon?: string icon?: string
order?: number order?: number
module_type?: string
} }
/** 模块列表响应 */ /** 模块列表响应 */
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* @date 2026-07-09 * @date 2026-07-09
*/ */
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import axios, { AxiosInstance, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
/** /**
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
...@@ -262,17 +262,12 @@ import { ElMessage } from 'element-plus' ...@@ -262,17 +262,12 @@ import { ElMessage } from 'element-plus'
import { import {
VideoCamera, VideoCamera,
VideoPause, VideoPause,
VideoPlay,
Plus, Plus,
Top,
Bottom,
Delete,
FolderChecked, FolderChecked,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import type { Module } from '@/types/module' import type { Module } from '@/types/module'
import { moduleApi } from '@/api/modules' import { moduleApi } from '@/api/modules'
import { recorderApi } from '@/api/recorder' import { recorderApi } from '@/api/recorder'
import { caseApi } from '@/api/cases'
const router = useRouter() const router = useRouter()
......
此差异已折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论