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

feat(functional-report): ERP配置持久化与菜单改造

- 新增 ErpConfig 数据库模型(erp_configs 单行表,5个配置字段)
- config 端点改为 DB 读写:GET 优先读 DB(空字段回退环境变量),PUT upsert 到 DB
- 功能测试报告改为父菜单(el-sub-menu),含"生成报告"和"ERP 配置"两个子项
- 路由改造:/reports/functional → 父路由 + generate/erp-config 子路由
- 新增容器组件 index.vue 和 ERP 配置页面 ErpConfig.vue(5字段表单,自动填充默认值)
- 更新 HANDOFF_功能测试报告.md 文档
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 226dc687
# HANDOFF — 功能测试报告模块会话交接文档
> **生成时间**: 2026-08-12
> **生成时间**: 2026-08-13
> **当前分支**: `platform-auto-test`
> **最近提交**: `09346c8e` feat(element-mapping): 元素映射表扩展至v1.2 新增登录页+会议预约子页面共220元素
> **状态**: ✅ 功能测试报告模块全部完成(后端服务 + API + 前端页面 + ERP 上传)
> **最近提交**: `f3f5d771` feat(frontend): 菜单重构为自动化测试父菜单,性能/功能报告独立,修复性能任务编辑详情加载
> **状态**: ✅ 功能测试报告模块全部完成(后端服务 + API + 前端页面 + ERP 上传 + ERP 配置持久化
---
......@@ -23,7 +23,7 @@
## 一、本次任务(已完成)
### 任务:报告中心-功能测试报告模块
### 任务:报告中心-功能测试报告模块(含 ERP 配置管理)
**参考工具**`E:\GithubData\ubains-module-test\develop\AuxiliaryTool\FunctionalTestReportGeneration\`
......@@ -106,8 +106,8 @@ GET /api/functional-report/download/{session_id} → 下载报告文件
GET /api/functional-report/preview/{session_id} → 预览 Markdown 报告(HTML)
GET /api/functional-report/info/{session_id} → 获取会话报告信息
POST /api/functional-report/upload-to-erp → 上传报告到 ERP
GET /api/functional-report/config → 获取 ERP 配置
PUT /api/functional-report/config → 更新 ERP 配置(内存级
GET /api/functional-report/config → 获取 ERP 配置(DB 优先,回退环境变量)
PUT /api/functional-report/config → 更新 ERP 配置(持久化到数据库
```
### 上传接口
......@@ -232,6 +232,16 @@ ERP 配置通过环境变量管理,默认值在 `backend/app/config.py` 的 `S
ERP 请求参数:超时 30s,最多重试 3 次(间隔 5s),4xx 错误不重试。
### ERP 配置持久化(v2 — 2026-08-13 新增)
| 内容 | 说明 |
|------|------|
| 数据库模型 | `backend/app/models/erp_config.py` — 单行表 `erp_configs`(id=1),5 个配置字段 |
| 配置端点改造 | `GET /config` 优先读 DB,DB 空字段回退到环境变量;`PUT /config` upsert 到 DB |
| 前端配置页面 | `ErpConfig.vue` — 5 字段表单(API Key 密码框/Base URL/测试单 ID/Type ID/图片 URL 前缀),加载自动填充默认值 |
| 路由改造 | 功能测试报告改为父菜单(`/reports/functional`),含 `generate`(生成报告)和 `erp-config`(ERP 配置)两个子页面 |
| 持久化验证 | ✅ 写入 DB 后重启后端,GET 仍返回已保存值 |
---
## 六、当前数据状态
......@@ -241,10 +251,12 @@ ERP 请求参数:超时 30s,最多重试 3 次(间隔 5s),4xx 错误
| 后端功能模块 | ✅ 6 个文件 | 从参考工具移植,修复 2 个 bug |
| 后端服务类 | ✅ 2 个 | FunctionalReportService + ErpService |
| 后端 API 路由 | ✅ 7 个端点 | 全部注册到 main.py |
| 后端 ERP 配置模型 | ✅ 新增 | `ErpConfig`(erp_configs 表),config 端点持久化到 DB |
| 前端 API 封装 | ✅ 8 个方法 | 在 functionalReport.ts 中 |
| 前端页面 | ✅ 完成 | FunctionalReport.vue(上传→生成→预览→ERP 上传) |
| 前端路由 | ✅ 新增 | `/reports/functional` 指向新页面 |
| 前端构建 | ✅ 通过 | FunctionalReport 10.26 kB,无报错 |
| 前端页面 | ✅ 完成 | FunctionalReport.vue(生成报告)+ ErpConfig.vue(ERP 配置) |
| 前端路由 | ✅ 改造 | `/reports/functional` 父路由 + 2 个子路由(generate/erp-config) |
| 前端菜单 | ✅ 改造 | 功能测试报告改为父菜单,含"生成报告"/"ERP 配置"子项 |
| 前端构建 | ✅ 通过 | vue-tsc 类型检查 + Vite 构建均通过 |
| 后端导入 | ✅ 通过 | 所有模块可正常 import |
---
......@@ -256,8 +268,7 @@ ERP 请求参数:超时 30s,最多重试 3 次(间隔 5s),4xx 错误
| 优先级 | 待办 | 说明 |
|--------|------|------|
| **P0** | 后端启动验证 | 启动后端服务,用示例 Excel 测试完整流程(上传→生成→下载) |
| **P0** | 前端联调验证 | 启动前端服务,访问 `/reports/functional`,确认上传/生成/下载/预览正常 |
| **P1** | ERP 配置管理持久化 | 当前 `PUT /config` 仅内存级修改,需持久化到数据库(可复用系统配置表) |
| **P0** | 前端联调验证 | 启动前端服务,访问 `/reports/functional`,确认上传/生成/下载/预览正常,ERP 配置页加载/保存正常 |
| **P1** | 系统设置页 ERP 配置 | 在 Settings.vue 中增加 ERP 配置项(API Key/URL/默认测试单 ID) |
| **P2** | 前端页面加载状态细化 | 上传/生成过程中的 loading 状态、进度提示等 |
| **P2** | 报告历史管理 | 支持查看历史报告列表、删除、重新生成 |
......@@ -272,6 +283,8 @@ ERP 请求参数:超时 30s,最多重试 3 次(间隔 5s),4xx 错误
| F3 | mammoth 转换 Word 图片丢失 | 图片提取为 base64 未替换为 ERP URL | `word_to_html_with_images` 中逐张上传并替换 src |
| F4 | 路由 `/reports/functional` 被 `:type?` 捕获 | Vue Router 静态路由写在动态路由之后 | 将 `/reports/functional` 放在 `/reports/:type?` 之前 |
| F5 | 前端 `baseURL` 双重前缀 404 | axios baseURL 设为 `/api` 后路径又写 `/api/...` | baseURL 留空,路径写完整 `/api/...` |
| F6 | `updateErpConfig`/`getErpConfig` 导入报 TS 错误 | `functionalReport.ts` 导出的是对象 `functionalReportApi`,非命名导出 | 用 `functionalReportApi.getErpConfig()` 调用 |
| F7 | PUT config 后 api_key 返回空字符串 | DB 记录只更新了非空字段,空字段未回退环境变量 | GET/PUT 返回时空字段一律回退到 `settings` 默认值 |
---
......@@ -288,7 +301,8 @@ cd frontend
npm run dev
# 访问
# 前端: http://localhost:3000/reports/functional
# 前端: http://localhost:3000/reports/functional/generate(生成报告)
# 前端: http://localhost:3000/reports/functional/erp-config(ERP 配置)
# API 文档: http://localhost:8001/docs(搜 functional-report 标签)
# 健康检查: http://localhost:8001/health
......@@ -301,7 +315,7 @@ npm run dev
## 九、文件完整清单
### 后端新增(14 个文件)
### 后端新增(15 个文件)
```
backend/app/services/functional/__init__.py
......@@ -316,27 +330,32 @@ backend/app/services/functional_report_service.py
backend/app/services/erp_service.py
backend/app/schemas/functional_report.py
backend/app/routers/functional_report.py
backend/app/models/erp_config.py — ERP 配置数据库模型
```
### 后端修改(3 个文件)
### 后端修改(4 个文件)
```
backend/app/config.py — 新增 ERP 配置项
backend/app/main.py — 注册 functional_report 路由
backend/requirements.txt — 新增 python-docx/mammoth/Pillow/matplotlib
backend/app/models/__init__.py — 注册 ErpConfig 模型
```
### 前端新增(2 个文件)
### 前端新增(4 个文件)
```
frontend/src/api/functionalReport.ts
frontend/src/views/functional-report/FunctionalReport.vue
frontend/src/views/functional-report/index.vue — 父容器组件
frontend/src/views/functional-report/ErpConfig.vue — ERP 配置页面
```
### 前端修改(1 个文件)
### 前端修改(2 个文件)
```
frontend/src/router/index.ts — 新增 /reports/functional 路由
frontend/src/router/index.ts — 改为父路由 + 子路由结构
frontend/src/App.vue — 菜单改为 el-sub-menu 子菜单结构
```
### 文档(2 个文件)
......
......@@ -22,6 +22,7 @@ from app.models.deploy_script import DeployScript
from app.models.deploy_execution import DeployExecution
from app.models.deploy_log import DeployLog
from app.models.performance import PerformanceTask, PerformanceSnapshot
from app.models.erp_config import ErpConfig
__all__ = [
"Module",
......@@ -41,4 +42,5 @@ __all__ = [
"DeployLog",
"PerformanceTask",
"PerformanceSnapshot",
"ErpConfig",
]
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:erp_config.py
模块描述:功能测试报告 ERP 配置数据库模型定义
作者:czj
创建日期:2026-08-13
最后修改:2026-08-13
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, DateTime, Integer
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class ErpConfig(Base):
"""
功能测试报告 ERP 配置数据库模型
存储 ERP 系统连接配置,支持持久化到数据库。
使用固定 ID 为 1 的单行记录。
Attributes:
id (int): 主键,固定为 1
api_key (str): ERP API 密钥
base_url (str): ERP 基础 URL
developtesting_id (int): 默认测试单 ID
type_id (int): 报告类型 ID
image_url_prefix (str): 图片 URL 前缀
updated_at (datetime): 更新时间
"""
__tablename__ = "erp_configs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False, default=1, comment="配置ID(固定为1)")
api_key: Mapped[str] = mapped_column(String(255), default="", comment="ERP API密钥")
base_url: Mapped[str] = mapped_column(String(500), default="", comment="ERP基础URL")
developtesting_id: Mapped[int] = mapped_column(Integer, default=397, comment="默认测试单ID")
type_id: Mapped[int] = mapped_column(Integer, default=2, comment="报告类型ID")
image_url_prefix: Mapped[str] = mapped_column(String(500), default="", comment="图片URL前缀")
updated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
)
def __repr__(self) -> str:
return f"<ErpConfig(id={self.id}, base_url={self.base_url})>"
def to_dict(self) -> dict:
return {
"api_key": self.api_key or "",
"base_url": self.base_url or "",
"developtesting_id": self.developtesting_id or 0,
"type_id": self.type_id or 2,
"image_url_prefix": self.image_url_prefix or "",
}
\ No newline at end of file
......@@ -15,9 +15,11 @@ import os
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, HTTPException, Query, UploadFile, File, Form
from fastapi.responses import FileResponse, HTMLResponse
from fastapi import APIRouter, HTTPException, Query, UploadFile, File, Form, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.schemas.functional_report import (
UploadResponse,
GenerateRequest,
......@@ -33,6 +35,7 @@ from app.schemas.functional_report import (
)
from app.services.functional_report_service import FunctionalReportService
from app.services.erp_service import ErpService
from app.models.erp_config import ErpConfig
from app.config import settings
logger = logging.getLogger(__name__)
......@@ -514,10 +517,27 @@ def _resolve_report_path(session_id: str, report_filename: str) -> Optional[str]
@router.get("/config", response_model=ErpConfigResponse, summary="获取ERP配置")
async def get_erp_config():
async def get_erp_config(db: AsyncSession = Depends(get_db)):
"""
获取当前ERP配置
优先从数据库读取,若数据库无记录则回退到环境变量默认值。
"""
# 从数据库读取
result = await db.execute(select(ErpConfig).where(ErpConfig.id == 1))
config = result.scalar_one_or_none()
if config:
# 数据库有记录,但空字段回退到环境变量默认值(保证配置自动填充)
return ErpConfigResponse(
api_key=config.api_key or settings.ERP_API_KEY or "",
base_url=config.base_url or settings.ERP_BASE_URL or "",
developtesting_id=config.developtesting_id or settings.ERP_DEVELOPTESTING_ID or 0,
type_id=config.type_id or settings.ERP_TYPE_ID or 2,
image_url_prefix=config.image_url_prefix or settings.ERP_IMAGE_URL_PREFIX or "",
)
# 数据库无记录,回退到环境变量
return ErpConfigResponse(
api_key=settings.ERP_API_KEY or "",
base_url=settings.ERP_BASE_URL or "",
......@@ -528,29 +548,54 @@ async def get_erp_config():
@router.put("/config", response_model=ErpConfigResponse, summary="更新ERP配置")
async def update_erp_config(request: ErpConfigUpdateRequest):
async def update_erp_config(request: ErpConfigUpdateRequest, db: AsyncSession = Depends(get_db)):
"""
更新ERP配置(仅更新当前会话,不会持久化到环境变量
更新ERP配置(持久化到数据库
如需持久化,请设置环境变量或在系统配置页面修改
更新后的配置会持久化到数据库,服务重启后依然有效
"""
# 从数据库读取或创建新记录
result = await db.execute(select(ErpConfig).where(ErpConfig.id == 1))
config = result.scalar_one_or_none()
if not config:
# 新记录:先用环境变量默认值填充,再用请求值覆盖
config = ErpConfig(
id=1,
api_key=settings.ERP_API_KEY or "",
base_url=settings.ERP_BASE_URL or "",
developtesting_id=settings.ERP_DEVELOPTESTING_ID or 0,
type_id=settings.ERP_TYPE_ID or 2,
image_url_prefix=settings.ERP_IMAGE_URL_PREFIX or "",
)
db.add(config)
# 更新字段
if request.api_key is not None:
config.api_key = request.api_key
settings.ERP_API_KEY = request.api_key
if request.base_url is not None:
config.base_url = request.base_url
settings.ERP_BASE_URL = request.base_url
if request.developtesting_id is not None:
config.developtesting_id = request.developtesting_id
settings.ERP_DEVELOPTESTING_ID = request.developtesting_id
if request.type_id is not None:
config.type_id = request.type_id
settings.ERP_TYPE_ID = request.type_id
if request.image_url_prefix is not None:
config.image_url_prefix = request.image_url_prefix
settings.ERP_IMAGE_URL_PREFIX = request.image_url_prefix
logger.info("ERP配置已更新(当前会话)")
await db.commit()
await db.refresh(config)
logger.info("ERP配置已更新(持久化到数据库)")
return ErpConfigResponse(
api_key=settings.ERP_API_KEY or "",
base_url=settings.ERP_BASE_URL or "",
developtesting_id=settings.ERP_DEVELOPTESTING_ID or 0,
type_id=settings.ERP_TYPE_ID or 2,
image_url_prefix=settings.ERP_IMAGE_URL_PREFIX or "",
api_key=config.api_key or settings.ERP_API_KEY or "",
base_url=config.base_url or settings.ERP_BASE_URL or "",
developtesting_id=config.developtesting_id or settings.ERP_DEVELOPTESTING_ID or 0,
type_id=config.type_id or settings.ERP_TYPE_ID or 2,
image_url_prefix=config.image_url_prefix or settings.ERP_IMAGE_URL_PREFIX or "",
)
\ No newline at end of file
......@@ -89,11 +89,15 @@
<el-menu-item index="/performance/report">报告查看</el-menu-item>
</el-sub-menu>
<!-- 功能测试报告(独立一级菜单) -->
<el-menu-item index="/reports/functional">
<el-icon><DocumentChecked /></el-icon>
<span>功能测试报告</span>
</el-menu-item>
<!-- 功能测试报告(父菜单,含生成报告和ERP配置两个子页面) -->
<el-sub-menu index="/reports/functional">
<template #title>
<el-icon><DocumentChecked /></el-icon>
<span>功能测试报告</span>
</template>
<el-menu-item index="/reports/functional/generate">生成报告</el-menu-item>
<el-menu-item index="/reports/functional/erp-config">ERP 配置</el-menu-item>
</el-sub-menu>
<!-- 自动化部署测试(父菜单) -->
<el-sub-menu index="/deploy">
......@@ -255,6 +259,12 @@ const PERFORMANCE_LABELS: Record<string, string> = {
report: '报告查看',
}
/** 功能测试报告子页面标签映射 */
const FUNCTIONAL_LABELS: Record<string, string> = {
generate: '生成报告',
'erp-config': 'ERP 配置',
}
/** 当前激活菜单项(无 type 时回退到默认子项以保持高亮) */
const currentRoute = computed(() => {
const seg = route.path.split('/').filter(Boolean)
......@@ -289,14 +299,21 @@ const pageTitle = computed(() => {
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]
else if (root === '/device-sim') typeLabel = DEVICE_SIM_LABELS[type]
else if (root === '/system') typeLabel = SYSTEM_LABELS[type]
else if (root === '/deploy') typeLabel = DEPLOY_LABELS[type]
else if (root === '/performance') typeLabel = PERFORMANCE_LABELS[type]
// 功能测试报告子页面:/reports/functional/generate → "功能测试报告 - 生成报告"
if (root === '/reports' && type === 'functional' && seg[2]) {
return `功能测试报告 - ${FUNCTIONAL_LABELS[seg[2]] || ''}`
}
const typeLabel = (() => {
if (root === '/modules') return MODULE_TYPE_LABELS[type]
if (root === '/reports') return REPORT_TYPE_LABELS[type]
if (root === '/cases' || root === '/execution') return CASE_TYPE_LABELS[type]
if (root === '/device-sim') return DEVICE_SIM_LABELS[type]
if (root === '/system') return SYSTEM_LABELS[type]
if (root === '/deploy') return DEPLOY_LABELS[type]
if (root === '/performance') return PERFORMANCE_LABELS[type]
return ''
})()
return typeLabel ? `${base} - ${typeLabel}` : base
})
......
......@@ -47,12 +47,27 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/Reports.vue'),
meta: { title: '报告中心' }
},
// 功能测试报告(独立页面,覆盖 reports/functional
// 功能测试报告(父菜单,含生成报告和ERP配置两个子页面
{
path: '/reports/functional',
name: 'FunctionalReport',
component: () => import('@/views/functional-report/FunctionalReport.vue'),
meta: { title: '功能测试报告' }
component: () => import('@/views/functional-report/index.vue'),
redirect: '/reports/functional/generate',
meta: { title: '功能测试报告' },
children: [
{
path: 'generate',
name: 'FunctionalReportGenerate',
component: () => import('@/views/functional-report/FunctionalReport.vue'),
meta: { title: '生成报告' }
},
{
path: 'erp-config',
name: 'ErpConfig',
component: () => import('@/views/functional-report/ErpConfig.vue'),
meta: { title: 'ERP配置' }
}
]
},
// 设备模拟模块
{
......
<!--
组件名称:ErpConfig.vue
组件描述:功能测试报告 ERP 配置管理页面
@author czj
@date 2026-08-13
-->
<template>
<div class="erp-config-page">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>ERP 配置</span>
</div>
</template>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="140px"
label-position="right"
style="max-width: 600px"
>
<el-form-item label="API Key" prop="api_key">
<el-input
v-model="form.api_key"
type="password"
show-password
placeholder="请输入 ERP API 密钥"
clearable
/>
</el-form-item>
<el-form-item label="Base URL" prop="base_url">
<el-input
v-model="form.base_url"
placeholder="请输入 ERP 基础 URL"
clearable
/>
</el-form-item>
<el-form-item label="测试单 ID" prop="developtesting_id">
<el-input-number
v-model="form.developtesting_id"
:min="0"
:max="999999"
:step="1"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="Type ID" prop="type_id">
<el-input-number
v-model="form.type_id"
:min="0"
:max="999"
:step="1"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="图片 URL 前缀" prop="image_url_prefix">
<el-input
v-model="form.image_url_prefix"
placeholder="请输入图片 URL 前缀"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="handleSave">
{{ saving ? '保存中...' : '保存配置' }}
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
/**
* ERP 配置管理页面
*
* 功能:
* 1. 加载时自动获取当前 ERP 配置(从数据库 / 环境变量回退)
* 2. 修改后保存到数据库,重启后持久化
*/
import { reactive, ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { functionalReportApi } from '@/api/functionalReport'
// ==================== 类型定义 ====================
interface ErpConfigForm {
api_key: string
base_url: string
developtesting_id: number
type_id: number
image_url_prefix: string
}
// ==================== 响应式数据 ====================
const formRef = ref()
const saving = ref(false)
const form = reactive<ErpConfigForm>({
api_key: '',
base_url: '',
developtesting_id: 397,
type_id: 2,
image_url_prefix: '',
})
/** 原始数据快照,用于重置 */
let originalSnapshot = ''
// ==================== 表单校验规则 ====================
const rules = {
api_key: [{ required: true, message: '请输入 ERP API 密钥', trigger: 'blur' }],
base_url: [{ required: true, message: '请输入 ERP 基础 URL', trigger: 'blur' }],
}
// ==================== 方法 ====================
/** 加载配置 */
async function loadConfig() {
try {
const res = await functionalReportApi.getErpConfig()
Object.assign(form, res)
originalSnapshot = JSON.stringify(res)
} catch (err: any) {
ElMessage.error('加载 ERP 配置失败:' + (err?.message || '未知错误'))
}
}
/** 保存配置 */
async function handleSave() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
saving.value = true
try {
await functionalReportApi.updateErpConfig({
api_key: form.api_key,
base_url: form.base_url,
developtesting_id: form.developtesting_id,
type_id: form.type_id,
image_url_prefix: form.image_url_prefix,
})
ElMessage.success('ERP 配置已保存')
originalSnapshot = JSON.stringify({ ...form })
} catch (err: any) {
ElMessage.error('保存 ERP 配置失败:' + (err?.message || '未知错误'))
} finally {
saving.value = false
}
}
/** 重置表单到加载时的值 */
function handleReset() {
const snapshot = JSON.parse(originalSnapshot)
Object.assign(form, snapshot)
formRef.value?.clearValidate()
}
// ==================== 生命周期 ====================
onMounted(() => {
loadConfig()
})
</script>
<style scoped>
.erp-config-page {
max-width: 800px;
}
.card-header {
font-size: 16px;
font-weight: 600;
}
</style>
\ No newline at end of file
<!--
组件名称:FunctionalReportIndex.vue
组件描述:功能测试报告模块父级容器,渲染子路由
@author czj
@date 2026-08-13
-->
<template>
<div class="functional-report-page">
<div class="page-header">
<h2>功能测试报告</h2>
</div>
<router-view />
</div>
</template>
<script setup lang="ts">
/**
* 功能测试报告模块容器组件
* 两个子页面通过路由切换:
* 1. /reports/functional/generate — 生成报告
* 2. /reports/functional/erp-config — ERP 配置
*/
</script>
<style scoped>
.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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论