后端模块:
yudao-module-qcreport接口前缀:/admin-api/qc-report/ai-import
本轮范围:**上传一份已有的检验报告文件 → AI 读懂 → 产出模板草稿**。
接口不落库,草稿只是「一串组件 + 各自的属性」;人工在设计器确认后点既有的「保存」才会写成模板版本。
| 页面 | 说明 | 本轮状态 |
|---|---|---|
| 模板设计器(既有) | 顶栏「AI 导入」按钮 → 弹窗选文件 → 识别 → 预览 → 「生成到画布」 | 已接入 |
| 模板列表页 | 本轮**不做**「AI 新建模板」入口,见「注意事项」 | 不做 |
templateId 进入(/qc/report-template/designer?id=5)。templateId 是后续「绑附件」与「日志留痕」的主键。blobId。blobId 后**立即**调 system 模块既有的附件绑定接口,把文件归属到当前模板application='file'、recordType='qc_report_template'、recordId=templateId)。application 受后端枚举限制,**只能是 file / image / avatar**,传其它值会 500。draft 接口,带上 blobIds、templateId、schemaVersion、catalog、可选的 hint。summary、warnings。带入关系要点
templateId 是主线,从模板列表带入设计器、再带入 AI 导入与附件绑定。catalog(积木清单)**每次请求都从当前前端注册表现算一遍再传**,不要在应用里缓存成常量:blobIds 的顺序即文档页序,多页文件按这个顺序逐页识别、最后合并去重。| 方法 | 路径 | 说明 | 权限码 |
|---|---|---|---|
| POST | /qc-report/ai-import/draft |
识别文件生成模板草稿。**同步返回、不落库、不建版本行** | qc-report:template:ai-import |
⚠ 本轮**未**往
system_menu插入qc-report:template:ai-import权限行(与出件接口同一口径)。
当前admin是超管、直接放行,联调不受影响;**前端页面正式落地时需一并补菜单与按钮权限**,
否则非超管角色调本接口会 403。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
blobIds |
Long 数组 | 是 | 上传文件对应的 blobId。**顺序即文档页序**;最多 3 个(该上限由后端 yudao.qcreport.ai-import.max-files 决定,超限报 1_070_104_005),单个文件最大 20MB |
templateId |
Long | 是 | 目标模板编号,用于日志留痕与附件归属 |
schemaVersion |
String | 是 | 固定传 1.1。与设计器同源,两端不一致时后端直接报错 |
catalog |
Object 数组 | 是 | 可用组件积木清单,见「catalog 的构造方式」;最多 40 项 |
hint |
String | 否 | 用户补充说明,会拼进提示词,例如「这是 IQC 来料检验报告」 |
catalog 一项的结构
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
String | 是 | 组件类型,如 QualityTable。**大小写敏感**,模型会原样抄进结果 |
label |
String | 否 | 组件显示名,供模型理解语义 |
category |
String | 否 | 组件分类,如 basic / data / result |
hint |
String | 是 | 该组件的**选型说明**:用来放什么、不要用来放什么。模型选型以它为准,见「选型指引必须随清单带出」 |
fields |
Object 数组 | 否 | 该组件允许填写的属性 |
hint标为**是**不是因为后端强制(后端收到空值不报错),而是因为**漏带它 = 模型退回瞎猜**:
组件之间大量可互相替代(报告抬头可写Heading/Text/ReportHeader),
模型只看中文label时会挑字段最简单、最通用的那个。实测漏带前的表现就是
「页眉页脚位置用了文本组件」。
catalog[].fields[] 一项的结构
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
key |
String | 是 | 属性 key,如 itemsPath |
label |
String | 否 | 字段显示名 |
type |
String | 否 | 字段类型,如 text / number / boolean / enum |
required |
Boolean | 否 | 是否必填;**只置真不置假** |
bindable |
Boolean | 否 | 是否支持数据绑定;**只置真不置假** |
enumOptions |
Object 数组 | 否 | 枚举可选值,元素形如 { label: '横向', value: 'landscape' }。**仅在 type=enum 时有意义** |
| 字段 | 类型 | 说明 |
|---|---|---|
components |
Object 数组 | 识别出的组件草稿,按报告从上到下的顺序;已做过合并去重与非法项过滤 |
components[].type |
String | 组件类型,**保证是 catalog 里声明过的 type** |
components[].props |
Object | 组件属性。清单里未声明的 key 已被后端丢弃 |
page |
Object | AI 猜测的纸张配置,**可能为空**(为空表示沿用当前模板的纸张) |
page.size |
String | A3 / A4 / A5 / Letter |
page.orientation |
String | portrait / landscape |
page.margin |
Object | top / right / bottom / left,单位 mm |
summary |
String | AI 对这份文档的一句话说明,展示在预览弹窗顶部 |
warnings |
String 数组 | 软提示:被跳过的页、无法表达的区块、被丢弃的未知组件、**原件带页眉/页脚已被标记并入识别内容**等 |
durationMs |
Long | 识别总耗时(毫秒),含多次模型调用 |
响应示例
{
"code": 0,
"data": {
"components": [
{ "type": "ReportHeader", "props": { "title": "来料检验报告" } },
{ "type": "QualityTable", "props": { "itemsPath": "inspectionItems" } },
{ "type": "Result", "props": { "conclusion": "{{report.conclusion}}" } }
],
"page": { "size": "A4", "orientation": "portrait" },
"summary": "这是一份 IQC 来料检验报告,含表头、检验项目表格与结论栏。",
"warnings": ["第 2 页的判定规则说明无法用可用组件表达,已丢弃"],
"durationMs": 12400
}
}
⚠ 响应里没有
rawText字段,这是有意的:完全解析不出结果时接口直接抛错
(1_070_104_012),响应体是错误结构而不是本响应;塞一个恒为空的字段只会让人误以为
「解析失败时前端能拿到模型原文」。模型原文写进了服务端日志。
catalog 的构造方式catalog 不是手写的常量表,而是**从设计器实际使用的组件注册表现算出来的派生视图**。
| 要点 | 说明 |
|---|---|
| 来源 | 设计器实际使用的那个组件注册表(src/components/quality),它是组件清单的**唯一真相来源** |
| 何时算 | 每次发起识别请求前现算一遍 |
| 必须先去注册 | 注册表**只有显式注册过才有内容**。设计器初始化时已做过一次,弹窗里再补一次是幂等的兜底 |
| 剥掉什么 | 剥离不可序列化的实现成员(图标、构建函数、校验函数、默认值),只留「模型需要知道的」:类型、显示名、分类、**选型说明**、字段 |
| 为什么不在后端硬编码 | 后端抄一份就是第二个真相来源,且是最坏的:前端加组件/改必填字段时前端立刻生效、后端副本静默过期,模型随即会编出注册表里根本不存在的 type |
| 边界为什么没被削弱 | 清单进提示词后的唯一出口是「被模型抄成 JSON 字符串」。真正把关的是**前端装配器对着活注册表查**:未注册的 type 会被跳过,**永远产不出任意 HTML** |
| 前置校验 | 注册表为空时构造函数**直接抛错**,不要发一个空清单出去——后端会判为入参非法,报出来的错和真实原因隔了好几层 |
hint 是**组件选型的唯一真相来源**,和组件定义同源:改注册表里某个组件的 hint,下次请求自动生效,
后端不需要(也不应该)同步任何一份副本。
| 要点 | 说明 |
|---|---|
| 写在哪 | 每个组件定义的一条 aiHint 文案,随清单序列化带出 |
| 写什么 | 「用来放什么」+「**不要**用来放什么」。后者才是关键——模型误用的根因是「不知道某个通用组件不该用在这里」 |
| 怎么用 | 后端只负责把它原样序列化进提示词,一条抽取规则要求模型「选型时以 hint 为准,清单里存在专门组件就不要退而用通用组件凑数」 |
| 不写会怎样 | 模型只看到组件中文名,会挑字段最简单的那个。实测症状就是:报告抬头与落款全落到了通用文本组件上 |
| 和设计器界面的关系 | hint 不进设计器界面,它是给模型看的说明,不是组件的展示名或属性提示 |
草稿**不能直接塞进画布**。它要先经前端的装配器编译成画布数据,再交给设计器载入(与「打开一份已保存的模板」走完全同一条路)。
装配器(assembleQualityCanvas)入参 / 出参
| 方向 | 名字 | 类型 | 说明 |
|---|---|---|---|
| 入 | components |
草稿组件数组 | 即响应里的 components;可为空 |
| 出 | grapes |
Object | 画布数据,喂给设计器的载入方法 |
| 出 | skipped |
Object 数组 | 被跳过的组件:{ type, label, reason }。**reason 是给人看的中文,直接展示** |
| 出 | problems |
String 数组 | 装进去了但有问题:必填缺失、收到结构化数据而回落默认值等 |
逐项行为(与既有「从画布收集语义」互为逆运算)
| 情况 | 行为 |
|---|---|
type 取不到注册表里的定义 |
进 skipped,**继续处理下一个**,不整批失败 |
type 为空 / 缺失 |
同上,进 skipped |
| AI 漏了某些字段 | 用组件声明的默认值补齐(不会出现「组件缺属性」) |
| AI 编了清单外的属性 key | 静默丢弃(属性序列化只输出声明过的 key) |
| 某个字段收到对象 / 数组 | 记一条 problems,该字段按默认值处理(防止 [object Object] 被写进画布) |
| 必填字段为空 | 记一条 problems,**组件仍然装进画布**(用户可以在设计器里补) |
| 一个组件都装不进去 | 不动画布,只报错。载入动作会清空撤销栈与已有内容,白白搭进去一版画布却什么都换不来 |
载入动作的两个副作用(必须让用户知情)
装配产物不能直接落库。 出参
grapes里每个节点的components仍是**未解析的 HTML 字符串**——它必须先进画布(GrapesJS 会把它拆成节点树)才能保存。绕过设计器直接把这串写进schema_json,后端渲染器不会解析它,出件时组件会渲成空 div。同理,若要程序化地把组件放进画布,**要把节点对象交给画布,不要自己拼 HTML 串再解析**:HTML 解析器会把属性名转小写(
data-qc-itemsPath→data-qc-itemspath),属性面板与渲染都会绑不上。设计器内部走的就是「直接传对象」这条路径。
原件的检验表**列**常常比组件默认列多(「检测方法」「标准要求」「结论」等),样品信息块的字段也常与默认六项不同。为此新增三个可填属性,AI 会把原件的列与字段写进去。**这三个属性都是纯文本字符串,不是数组**——属性值在画布上是标量,数组存不进去。
| 属性 | 挂在哪个组件 | 作用 |
|---|---|---|
columns |
分组检验项表 | 覆盖该表的列:列标题 + 每列绑定的数据 |
headerSpans |
分组检验项表 | 表头分上下两层时,描述**上面那一层** |
fields |
样品信息 | 覆盖样品信息块展示的字段 |
columns 的写法一行字符串,列与列之间用 | 分隔,每列写作「列标题=绑定表达式」,**在第一个 = 处切分**(所以列标题里不能出现 =)。
# = 该列要**纵向合并跨住整组**(放组名的那一列),一表只有一列该加。columns 整表就按它来,「显示序号 / 显示检测要求 / 显示实测值 / 显示单位 / 显示判定」这些开关全部失效(它们只管默认列)。可用的绑定路径是白名单,写白名单外的路径会在校验时报错:{{index}} 行序号、{{item.itemName}} 检验项目名、{{item.group.childName}} 子项名、{{item.checkMethod}} 检测方法、{{item.requirement}} 检测要求、{{item.standardValue}} 标准值、{{item.actualValue}} 实测值、{{item.unit}} 单位、{{item.resultText}} 判定结论、{{item.remark}} 备注。
给分组表写 columns 时必须保留「子项」列(子项={{item.group.childName}}),且它不加 #。分组表的默认列本来就有这一列,AI 把列写全时最容易漏掉它——一漏,子项名整列从报告上消失,而子项往往才是标准真正考核的对象。实测:不写明这条时 3/3 轮都把子项列丢掉(只写出 4 列),写明后 5/5 轮保留、且与原件的 6 列逐列对齐。
合并**不在前端算,也画布上看不见**,全部由数据下发、出件时才生效:
| 环节 | 谁做 | 做什么 |
|---|---|---|
| 标出合并列 | 模板 | columns 里给该列标题加 #,组件据此只在这一格写 rowspan / hidden 两个绑定 |
| 算出合并几行 | 后端 | 组的第一行下发 span = 本单该组的实际行数、hidden = 空串;组内其余行下发 span = 1、hidden = hidden |
| 渲成视觉合并 | 前端 / Java 渲染器 | 两边都在「求值结果为空串」时不输出该属性。于是组头行的 hidden 消失、那一格带着 rowspan 显示;其余行的 hidden="hidden" 让整格 display:none,把位置让给上方的合并格 |
三条由此推出的口径:
span = 1 下发),独立录入项同理——本来就只有一行,无所谓跨行。hidden 只能靠「属性在不在」表达,所以这个值必须是字符串:hidden="false" 照样隐藏,只有空串能表示「不输出该属性 = 这一格可见」。前端不要把它当布尔值处理。自定列**不带任何宽度声明**(默认列带:序号 48px、检测要求 30%、实测值 26%……),所以整张表的列宽只能由各列的**最小内容宽度**倒推。而报告里的值是 {{item.standardValue}} 这种不含空格的长串,最小内容宽度就是整串长度——几列加起来轻易超过 A4 正文宽度,浏览器只能把表格撑出纸张,最末一列被挤成二十几个像素的竖条。
修法:**只在自定列时**给单元格加 overflow-wrap:anywhere。它会参与最小内容宽度计算(break-word 不会),列宽因此不再被长串绑架;出件时值本来就短(「烘箱干燥法」「13.2」),折行极少触发。
| 场景(A4 纵向,正文宽 672px) | 修前 | 修后 |
|---|---|---|
| 自定 6 列,值为占位符 | 整表 810px、溢出 138px、末列 29px | 整表 672px、不溢出、末列 97px |
| 自定 6 列,值为真实数据 | 本就 672px | 672px,列宽分布不变 |
| 默认列(分组表 7 列) | 807px、溢出 135px | 保持原样 |
最后一行是刻意为之:默认列的宽度声明已经定住版面,再叠一条折行反而会让没写宽度的「检验项目」「子项」被挤到 30 余像素、行高从 59px 涨到 283px(实测)。因此这条只挂在自定列上,**没有 columns 的存量模板产物逐字不变**(探针以 sha256 守着)。
自定列的属性是随本轮一起进的,存量模板与已发布版本里一条都没有(全库实测
data-qc-columns/data-qc-headerSpans/data-qc-fields命中数均为 0),因此这条改动对既有报告零影响。
headerSpans 的写法同样是 | 分隔的一行字符串,每段写作「标题^跨越列数」,**在最后一个 ^ 处切分**;不写跨越列数时按 1 算。
columns 各列的标题自动拼出,不要重复写——headerSpans 只描述上面那一层。结论^1)。不点明时模型会图省事把它的列数并进「检验结果」(写成 检验结果^6),表头就把结论列画到检验结果底下。columns 里这一列的列标题**一字不差**。两者相同时报告会把它合出一格跨两行(rowspan),下层不再重复出这一格;不一致时会被当成另一层的新分组标题,「结论」二字在表头上印两遍。结论^1、列名也叫「结论」,两层各印一次,用户看到的就是「多了一个结论」。补上「两边一字不差 ⇒ 合成一格跨两行」这条判据后,直连 5/5 轮合成为一格。columns 里那一列改名成「判定」、headerSpans 却仍写着原件上的「结论」,两边对不上又退回多印一个表头。提示词里因此补了一句「不要自作主张把它改名成近义词」并写明后果,复跑 5/5 轮不再出现。fields 的写法语法与 columns 完全相同(字段名=绑定表达式,| 分隔),但**不允许 # 合并标记**(那是分组表标组名的)。每行放几组仍由「每行字段数」属性控制。
绑定表达式只能写报告上下文里确实存在的字段(与 columns 同受「不许自造键」约束);原件里有、上下文里没有的栏目(如「产品数量」「土豆品种」「生产日期」「有效日期」)会被丢弃,并在 summary 里说明。
字段数不是「每行字段数」的整数倍时(**原件常见**,如原件 5 项、每行放 2 组),末行由最后一个值格横向跨掉余下的列,右侧不会留出只有边框的空格。这是渲染期的固定行为,**不需要在 fields 里做任何标记**,也不会因此增减字段格数。
设计器画布**不求解绑定**——{{item.group.childName}} 这类占位符原样留着。由此有一处必须单独处理:分组检验项表的组名格写成 hidden="{{item.group.hidden}}",在浏览器眼里「有 hidden 属性」就成立,于是画布里这一格被 display:none,该行后面的格整体左移一格,末列只剩表头、被挤成竖排细条——画布上看到的排版与出件结果对不上。
设计器为此只在**画布文档内**注入一条引导样式,把「值还停在占位符」的 hidden 还原成可见格。出件时该值已解成空串或 hidden,选择器不再命中,报告渲染一个字都不受影响;这条样式只加在画布 iframe 里,不写进 Schema、不随模板保存。
占位符带来的第二处差异是**宽度**:占位符比真值长得多,撑不撑得破纸张在两侧表现不同。这一处不走画布样式,而是把 overflow-wrap:anywhere 写进自定列的单元格样式里——两侧用同一套规则,所见即所得(详见上一节)。
只有**保存 / 发布前**的属性校验会检查它们(列定义语法、跨列数合计、# 用在样品信息上等),设计过程中的中间态不触发。校验信息走既有的 problems 通道(**非阻断**),组件仍会装进画布,用户可以在设计器里改。
已知边界:AI 导入路径会跑这套校验;设计器里**手工编辑**后点保存**目前不跑**(
validateQualityProps只在装配器里被调用)。也就是说手工填错的列定义不会在保存时被拦住,只会在出件时以「绑定取不到值」的形式暴露。这是既有缺口,本轮未改。
| 字段 | 展示位置 | 说明 |
|---|---|---|
summary |
弹窗顶部 | 成功色提示条。为空时不展示 |
warnings |
弹窗中部 | 警告色提示条,逐条 · 列出。**必须展示**——它承载「文件里有哪些内容没被识别到」 |
components |
弹窗中部列表 | 每行:序号 + 类型显示名 + 类型原始值 + 已识别的属性(字段显示名=值)。类型取不到定义时额外打「未注册,将被跳过」标记 |
components 条数 |
列表上方 | 「识别出 N 个组件」;有未注册项时追加「M 个组件设计器不认识」标记 |
durationMs |
列表上方 | 「耗时 N 秒」 |
page |
不进弹窗 | 并入设计器的纸张设置后再载入画布,见「业务规则」 |
skipped / problems |
载入后弹窗 | 「有 N 处需要留意」,逐条列出 |
| 场景 | 规则 |
|---|---|
| 只作草稿,不落库 | 接口不建模板、不建版本行、不写画布。**唯一的落库点是设计器既有的「保存」**,所以既有的模板校验(含判定规则)自动生效 |
| 多页逐页识别后去重合并 | 扫描件/照片按页各出一次草稿,再按「类型 + 属性」完全相同去重合并。页眉、标题、表头在多页重复出现时只留第一份 |
.doc / .xls 旧格式不支持 |
直接报 1_070_104_003,提示另存为 .docx / .xlsx。.docx / .xlsx / .pdf / 图片 / .txt / .csv 支持 |
| 「空白」与「损坏」分开报 | 扩展名合法但内容为空 → 1_070_104_005;扩展名合法但文件打不开(损坏、带密码) → 1_070_104_015。两者给用户的动作完全不同,不能合成一条 |
| PDF 走哪条通道由「有没有文本层」决定 | 电子版 PDF(有文本层)走文本通道,整份 1 次调用;扫描版 PDF(无文本层)逐页转图后按图识别 |
| 表格只识别结构 | 表格的**具体数据行不会写进模板**,QualityTable 的数据源一律是 inspectionItems;真实数据在出件时由业务单据提供。**这一点必须向用户说明**,否则用户会以为文件里的数据也导进来了 |
| 原件列/字段与默认不同时是覆盖,不是换组件 | 检验表列比默认多(检测方法 / 标准要求 / 结论…)→ 写 columns;表头分上下两层 → 写 headerSpans;样品信息字段与默认六项不同 → 写 fields。**一律不换组件**——换成别的组件只会把多出来的内容丢掉。三者的语法、白名单与校验见上文「AI 可填的模板结构属性」 |
| 绑定表达式原样保留 | 文件里出现 {{report.reportNo}} 这类占位符时,AI 会原样保留,不会替换成它看到的字面值 |
| AI 不得自造绑定键 | 提示词明确禁止模型发明新的 {{...}} 键,并且**把可用的键全量列了出来**:报告级字段取自后端 ReportFields(报告号、样品名、规格、批号、检验员、检验日期、结论、合格率、计数等 23 个,反射生成、不手写),检验项字段取自表格组件的绑定路径。**只禁编造、不给可选集合时模型只能靠猜**——实测 5/5 轮都编出了 {{report.productionDate}} / {{report.expiryDate}} 这类不存在的键,报告上留白并触发「绑定取不到值」告警 |
.docx 的 Word 页眉/页脚会被标记并入内容 |
Word 文档的**页眉与页脚**(每页重复的公司抬头、厂址电话等)原本不在正文流里,现已被读取并以 [页眉] / [页脚] 标记拼进识别内容(页眉在正文前、页脚在正文后),同时往 warnings 里加一条提示。**扫描件/照片不受影响**:它们的页眉页脚本来就是页面像素的一部分 |
| 表格末行的落款签署行属于落款 | 检验员 / 审核人 / 批准人 / 日期这类签署行**常排在检验表格的最后一行**。它不算「具体数据行」,应被取出来与原件页脚合成放进 ReportFooter(ReportFooter 与 QualityTable 两条选型说明里各自写明了这条边界) |
| 签署行的人名与日期留空 | 签署行**保留栏目名、值留空**(形如「检验员:____ 审核人:____ 日期:____」),打印出来由人手填。人名与日期是每一份报告各自的数据,写死原件上的那几个人会让所有报告印成同一个检验员 |
| 落款允许多行,换行要真的折行 | 签署行与原件页脚合成一段**多行**文本交给 ReportFooter,几行内容**必须折行显示**,不能糊成一片。属性值里保留的是原始换行;**折行发生在渲染产物这一侧**——组件生成的画布 content 里是 <br>,渲染引擎按原样输出。Text 组件同样支持多行 |
| 判定规则与数据绑定不由 AI 生成 | AI 只出组件的静态属性;判定规则、数据绑定一律保留默认值,由人工在设计器里配置 |
| 纸张的合并规则 | AI 猜出的纸张并入当前设计器的纸张设置(**按字段合并**,AI 没猜到的字段沿用用户当前设置),再随载入动作一起生效。**不同步的话,保存时会被模板保存接口按旧值写回** |
| AI 猜不到纸张时不覆盖 | page 为空时**必须传当前设计器的纸张设置**,不能传空。传空会让载入动作退回默认 A4,把用户已经调好的纸张悄悄改掉 |
| 组件数上限 | 识别结果最多 200 个组件,超出截断并在 warnings 里提示补录 |
| 任何单个组件坏掉都不影响整体 | 未知类型、结构化属性、必填缺失都只进 warnings / problems,其余组件正常返回。**只有「一个组件都没识别出来」才整体报错** |
| 功能可被运维关掉 | 部署环境不能出网时会关掉(yudao.qcreport.ai-import.enabled=false),此时接口报 1_070_104_000。前端应原样提示,不要当成系统异常 |
这条口径容易混淆,前端联调时务必对齐:
画布上的 ReportHeader / ReportFooter |
服务端出件时的页眉页脚 | |
|---|---|---|
| 位置 | 正文流里的顶部抬头块 / 底部落款块,随正文只出现一次 | 打印时**每页重复**的版式区域 |
| 由谁生成 | AI 从文件里识别出来、装进画布,用户可编辑 | 服务端 PDF 出件配置(PdfPrintOptions)加的,目前只有页码 |
| 本轮改动 | .docx 的 Word 页眉被读进来后,模型会把它挪进正文顶部的 ReportHeader |
未改动 |
也就是说:把 Word 页眉认成 ReportHeader,是**把它搬进正文顶部**,**不是**还原每页重复的打印版式。
真正每页重复的那一层仍由服务端 PDF 出件配置控制。**不要向前端承诺「导入后会得到每页重复的页眉」**。
catalog:每次请求现算。理由见「catalog 的构造方式」。catalog 每项必须带 hint:选型指引是它可以被模型看见的唯一出口。注册表里新增组件时,aiHint 要一并写全——漏一条,那个组件就基本不会被模型选中。blobIds 顺序有意义:多页文件按数组顺序理解页序,前端要保序,不要用无序集合承载。skipped / problems 逐条展示:不要只报条数,用户要照着改。templateId 进入,AI 导入只在既有模板的设计器内。想做「上传文件直接造新模板」需先建模板壳,是另一条流程。| 错误码 | 报文 | 触发场景 |
|---|---|---|
1_070_104_000 |
AI 导入功能未启用(yudao.qcreport.ai-import.enabled 当前为 false),请联系管理员开启后再试 |
运维把开关关成了 false |
1_070_104_001 |
上传的文件(blobId=X)不存在或已被清理,请重新上传后再试 | blobId 查不到。文件已被清理或编号传错 |
1_070_104_002 |
不支持的文件「X」(扩展名:Y)。仅支持 PDF、Word(.docx)、Excel(.xlsx)、图片(png/jpg/jpeg/bmp/gif/webp) 与文本(txt/csv),请转换格式后重新导入 | 扩展名不在白名单内 |
1_070_104_003 |
不支持的文件「X」(旧版 Office 格式.Y)。请用 Office 打开后「另存为」.docx 或 .xlsx 再重新导入 | 上传了 .doc / .xls |
1_070_104_004 |
文件「X」大小 YMB,超过单文件上限 ZMB,请压缩或拆分后重新导入 | 超过 maxFileSizeMb(默认 20MB) |
1_070_104_005 |
文件「X」未解析出任何可用内容(共 N 页)。若是扫描件,请确认分辨率不低于 200 DPI、文字无严重倾斜或遮挡;推荐改用电子版 PDF 或 Word/Excel | 空文件;传真件等编码不受支持的图片;**内容为纯空白的 .docx / .xlsx / .txt**。.docx / .xlsx 打不开(损坏、带密码)走 1_070_104_015,不会落到这里 |
1_070_104_006 |
一次最多导入 N 个文件,本次提交了 M 个,请分批导入 | 超过 maxFiles(默认 3) |
1_070_104_007 |
文件「X」共 N 页,超过单次识别上限 M 页,请拆分后分批导入 | 超过 maxPagesPerFile(默认 5)。**不静默截断** |
1_070_104_008 |
组件清单参数不合法(原因),请刷新页面后重试 | catalog 为空、含没有 type 的项、超 40 项、序列化超 32KB |
1_070_104_009 |
组件清单的 Schema 版本为「X」,服务端只接受「Y」,请刷新页面后重试 | schemaVersion 与设计器不一致(通常是前端已更新而后端未发版) |
1_070_104_010 |
AI 识别调用失败(已耗时 Nms):原因。请到「AI 大模型」中确认对话模型已启用且密钥有效,或稍后重试 | 模型服务异常、网络不通、密钥无效 |
1_070_104_011 |
AI 识别超时(本次已等 N 秒)。文件页数或数量较多时耗时更长,请减少文件数量,或改用电子版 PDF / Word / Excel | 模型排队或文件过大。**接口侧超时默认 60 秒**(后端 yudao.ai.timeout,与前端那条 axios 的 180 秒是两道独立的闸) |
1_070_104_012 |
AI 返回的内容无法解析为模板草稿(已收到 N 个字符)。若文件内容很长,请拆分后分批导入;若持续失败,请改用手工设计 | 模型输出不是预期 JSON(被截断、夹带解释文字等)。原始输出已写进服务端日志 |
1_070_104_013 |
AI 未能从文件「X」中识别出任何可用组件。请确认该文件是检验报告;若确实是,请改用手工设计模板 | 一个组件都没识别出来。**这是唯一一个「整体失败」的语义** |
1_070_104_014 |
本次提交的文件合计 N 页,超过单次识别上限 M 页(单文件上限 K 页),请减少文件数量或分批导入 | 每个文件都没超、加起来超 maxPagesPerRequest(默认 8) |
1_070_104_015 |
文件「X」无法作为 .Y 打开,内容已损坏或受密码保护。请先用 Office 打开该文件,确认能正常显示后「另存为」.docx / .xlsx 再重新导入;若文件有打开密码,请先解除密码保护 | .docx / .xlsx 解析抛异常(POI 打不开)。**「空白」与「损坏」是两回事,报文分开**;此前损坏文件误报 1_070_104_005(让用户去查分辨率),已纠正 |
注:
templateId不做存在性校验,它只用于日志留痕与附件归属。前端务必先用真实存在的模板 id 进设计器。