13 小时以前 35722562e9e13f0504acc15b740d042ecb810199
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type { QualityComponentDefinition } from '../core/types';
 
import { QUALITY_REPEAT_ATTR, QUALITY_REPEAT_ROW_ATTR } from '../core/attrs';
import { cssStyle, escapeHtml } from '../core/html';
import { QUALITY_ICONS } from '../core/icons';
import { QUALITY_CATEGORY } from '../core/types';
 
/** 检验项数组在报告上下文中的默认路径 */
const DEFAULT_ITEMS_PATH = 'inspectionItems';
 
/** 循环变量名,行内占位符写作 {{item.xxx}} */
const LOOP_VAR = 'item';
 
/** 渲染期由重复容器注入的行号变量,写作 {{index}},从 1 开始 */
const INDEX_VAR = 'index';
 
const CELL_BORDER = '1px solid #d9d9d9';
 
interface ColumnSpec {
  /** 表头文字 */
  label: string;
  /** 行内取值表达式 */
  expr: string;
  /** 对应的显示开关属性名 */
  toggle: string;
  width?: string;
}
 
const COLUMNS: ColumnSpec[] = [
  { label: '序号', expr: `{{${INDEX_VAR}}}`, toggle: 'showIndex', width: '48px' },
  { label: '检验项目', expr: `{{${LOOP_VAR}.itemName}}`, toggle: '' },
  { label: '标准值', expr: `{{${LOOP_VAR}.standardValue}}`, toggle: 'showStandard' },
  { label: '实测值', expr: `{{${LOOP_VAR}.actualValue}}`, toggle: 'showActual', width: '40%' },
  { label: '单位', expr: `{{${LOOP_VAR}.unit}}`, toggle: 'showUnit', width: '60px' },
  { label: '判定', expr: `{{${LOOP_VAR}.resultText}}`, toggle: 'showResult', width: '72px' },
];
 
export const qualityTableDefinition: QualityComponentDefinition = {
  type: 'QualityTable',
  name: 'qualityTable',
  label: '检验项目表格',
  category: QUALITY_CATEGORY.INSPECTION,
  icon: QUALITY_ICONS.qualityTable,
  aiHint:
    '多行检验数据的表格,检验项平铺一层、没有父子关系(序号/检验项目/标准值/实测值/单位/判定)。' +
    '一个检验项目下挂若干子项(原件里表现为该列纵向合并、后续行写着「↑同上」)时不要用我,' +
    '改用分组检验项表。' +
    '报告末尾的落款签署行(检验员/审核人/日期)不要放进我这里,那属于 ReportFooter。',
  defaults: {
    title: '检验项目',
    itemsPath: DEFAULT_ITEMS_PATH,
    showIndex: true,
    showStandard: true,
    showActual: true,
    showUnit: true,
    showResult: true,
  },
  propertySchema: [
    { key: 'title', label: '表格标题', type: 'string', defaultValue: '检验项目' },
    {
      key: 'itemsPath',
      label: '检验项数据源',
      type: 'string',
      defaultValue: DEFAULT_ITEMS_PATH,
      required: true,
      bindable: true,
      // 渲染期按这个路径取数组逐行生成,必须是裸路径,不能写成 {{...}}
      bindAs: 'path',
      tip: '报告上下文中检验项目数组的路径,渲染时按它逐行生成',
    },
    { key: 'showIndex', label: '显示序号', type: 'boolean', defaultValue: true },
    { key: 'showStandard', label: '显示标准值', type: 'boolean', defaultValue: true },
    { key: 'showActual', label: '显示实测值', type: 'boolean', defaultValue: true },
    { key: 'showUnit', label: '显示单位', type: 'boolean', defaultValue: true },
    { key: 'showResult', label: '显示判定', type: 'boolean', defaultValue: true },
  ],
  dataSchema: [
    { key: DEFAULT_ITEMS_PATH, label: '检验项目列表', type: 'string', bindable: true },
    { key: INDEX_VAR, label: '行序号', type: 'number', bindable: true },
    { key: `${LOOP_VAR}.itemName`, label: '检验项目名称', type: 'string', bindable: true },
    { key: `${LOOP_VAR}.standardValue`, label: '标准值', type: 'string', bindable: true },
    { key: `${LOOP_VAR}.actualValue`, label: '实测值', type: 'string', bindable: true },
    { key: `${LOOP_VAR}.unit`, label: '单位', type: 'string', bindable: true },
    { key: `${LOOP_VAR}.resultText`, label: '判定结果', type: 'string', bindable: true },
  ],
  buildContent: (properties) => {
    const visible = COLUMNS.filter(
      (column) => column.toggle === '' || properties[column.toggle] !== false,
    );
    const head = visible
      .map(
        (column) =>
          `<th style="${cssStyle({
            border: CELL_BORDER,
            padding: '6px 8px',
            background: '#fafafa',
            'font-weight': 600,
            width: column.width,
          })}">${column.label}</th>`,
      )
      .join('');
    const row = visible
      .map(
        (column) =>
          `<td style="${cssStyle({ border: CELL_BORDER, padding: '6px 8px' })}">${column.expr}</td>`,
      )
      .join('');
    const rawTitle = String(properties.title ?? '').trim();
    const heading = rawTitle
      ? `<div style="${cssStyle({ 'font-size': '13px', 'font-weight': 600, margin: '0 0 4px' })}">${escapeHtml(rawTitle)}</div>`
      : '';
    const itemsPath = String(properties.itemsPath ?? DEFAULT_ITEMS_PATH).trim() || DEFAULT_ITEMS_PATH;
    const table =
      `<table style="${cssStyle({ width: '100%', 'border-collapse': 'collapse', 'font-size': '12px' })}">` +
      `<thead><tr>${head}</tr></thead>` +
      // 渲染期按 data-qc-repeat 指向的数组复制 data-qc-repeat-row 所在的行
      `<tbody ${QUALITY_REPEAT_ATTR}="${escapeHtml(itemsPath)}">` +
      `<tr ${QUALITY_REPEAT_ROW_ATTR}="${LOOP_VAR}">${row}</tr>` +
      `</tbody></table>`;
    return {
      tagName: 'div',
      components: heading + table,
      style: { margin: '0 0 10px' },
    };
  },
  validate: (properties) => {
    const path = String(properties.itemsPath ?? '').trim();
    const errors: string[] = [];
    if (!path) {
      errors.push('检验项目表格的「检验项数据源」不能为空,渲染时无法确定生成哪些行');
    }
    if (properties.showStandard === false && properties.showResult === true) {
      errors.push(
        '检验项目表格隐藏了「标准值」却保留「判定」列,判定依据将不可见,请一并隐藏判定或恢复标准值',
      );
    }
    return errors;
  },
};