11 小时以前 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
140
import type { QualityComponentDefinition, QualityProps } from '../core/types';
 
import { parseColumnSpec } from '../core/column-spec';
import { cssStyle, escapeHtml } from '../core/html';
import { QUALITY_ICONS } from '../core/icons';
import { QUALITY_CATEGORY } from '../core/types';
 
/** 样品信息默认展示的字段,值全部来自报告上下文 */
const SAMPLE_FIELDS: { expr: string; label: string }[] = [
  { label: '样品编号', expr: '{{report.sampleNo}}' },
  { label: '产品名称', expr: '{{report.productName}}' },
  { label: '规格型号', expr: '{{report.spec}}' },
  { label: '批次号', expr: '{{report.batchNo}}' },
  { label: '检验日期', expr: '{{report.inspectDate}}' },
  { label: '检验员', expr: '{{report.inspector}}' },
];
 
const CELL_BORDER = '1px solid #d9d9d9';
 
function textOf(value: unknown): string {
  return String(value ?? '').trim();
}
 
/**
 * 出表用的字段:填了 fields 就整块照它来,否则用默认字段。
 * <p>
 * 与分组检验项表共用同一套 `字段名=绑定表达式` 语法(见 {@link parseColumnSpec}),
 * 免得同一个「键值对列表」长出两种写法。合并标记 `#` 在这里没有意义,解析出来也只是被丢掉,
 * 所以 validate 里明确拦下,不让用户以为自己写对了。
 */
function effectiveFields(properties: QualityProps): { expr: string; label: string }[] {
  const custom = parseColumnSpec(textOf(properties.fields));
  return custom.items.length > 0
    ? custom.items.map((field) => ({ label: field.label, expr: field.expr }))
    : SAMPLE_FIELDS;
}
 
function buildRows(
  pairsPerRow: number,
  showBorder: boolean,
  fields: { expr: string; label: string }[],
): string {
  const perRow = Math.max(pairsPerRow, 1);
  const border = showBorder ? CELL_BORDER : 'none';
  const rows: string[] = [];
  for (let index = 0; index < fields.length; index += perRow) {
    const rowFields = fields.slice(index, index + perRow);
    // 末行凑不满一组时,让最后一个值格横向吃掉余下的列。照原样铺满格子的话,
    // 右侧会秃出几个只有边框的空格(字段数不是「每行组数」整数倍时必然出现)。
    const tailColspan = rowFields.length < perRow ? (perRow - rowFields.length) * 2 + 1 : 1;
    const cells = rowFields
      .map((field, position) => {
        const span = position === rowFields.length - 1 && tailColspan > 1 ? ` colspan="${tailColspan}"` : '';
        return (
          `<td style="${cssStyle({
            border,
            padding: '5px 8px',
            background: '#fafafa',
            'font-weight': 600,
            width: '90px',
          })}">${escapeHtml(field.label)}</td>` +
          `<td${span} style="${cssStyle({ border, padding: '5px 8px' })}">${escapeHtml(field.expr)}</td>`
        );
      })
      .join('');
    rows.push(`<tr>${cells}</tr>`);
  }
  return rows.join('');
}
 
export const sampleInfoDefinition: QualityComponentDefinition = {
  type: 'SampleInfo',
  name: 'sampleInfo',
  label: '样品信息',
  category: QUALITY_CATEGORY.HEADER,
  icon: QUALITY_ICONS.sample,
  aiHint: '样品与物料的基本信息块:产品名称、规格、批号、数量、生产日期、有效日期等键值对。',
  defaults: { title: '样品信息', pairsPerRow: 2, showBorder: true },
  propertySchema: [
    { key: 'title', label: '区块标题', type: 'string', defaultValue: '样品信息' },
    {
      key: 'pairsPerRow',
      label: '每行字段数',
      type: 'number',
      defaultValue: 2,
      tip: '一行放几组「字段名 + 值」',
    },
    { key: 'showBorder', label: '显示边框', type: 'boolean', defaultValue: true },
    {
      key: 'fields',
      label: '字段定义(覆盖默认字段)',
      type: 'text',
      tip:
        '留空则用默认的六项。格式:字段名=绑定表达式,多项用 | 分隔,' +
        '如 样品编号={{report.sampleNo}}|产品名称={{report.productName}}|生产日期={{report.produceDate}}。' +
        '每行放几组仍由上面的「每行字段数」控制;这里不支持 # 合并标记',
    },
  ],
  dataSchema: SAMPLE_FIELDS.map((field) => ({
    // 表达式形如 {{report.sampleNo}},去掉两层花括号即绑定路径
    key: field.expr.slice(2, -2),
    label: field.label,
    type: 'string' as const,
    bindable: true,
  })),
  buildContent: (properties) => {
    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 table = `<table style="${cssStyle({
      width: '100%',
      'border-collapse': 'collapse',
      'font-size': '12px',
    })}">${buildRows(
      Number(properties.pairsPerRow ?? 2),
      properties.showBorder !== false,
      effectiveFields(properties),
    )}</table>`;
    return {
      tagName: 'div',
      components: heading + table,
      style: { margin: '0 0 10px' },
    };
  },
  validate: (properties) => {
    const parsed = parseColumnSpec(textOf(properties.fields));
    const errors = [...parsed.errors];
    if (parsed.items.some((field) => field.merge)) {
      errors.push(
        '样品信息的「字段定义」不支持 # 合并标记(那是分组检验项表标组名用的),请去掉字段名前的 #',
      );
    }
    return errors;
  },
};