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
/**
 * 质量组件 → GrapesJS 产物
 * <p>
 * 协议(QualityComponentDefinition)与 GrapesJS 的桥接只在这一层:
 * 组件块、画布内容、属性面板(traits)都由 definition 推导,组件实现里不出现 GrapesJS 概念。
 */
import type { BlockProperties, ComponentAdd, TraitProperties } from 'grapesjs';
 
import type {
  QualityComponentContent,
  QualityComponentDefinition,
  QualityFieldSchema,
  QualityProps,
} from './types';
 
import { QUALITY_TYPE_ATTR, propAttrName } from './attrs';
import { listQualityComponents } from './registry';
import { propsToAttributes } from './serializer';
import { BINDING_TRAIT_TYPE } from './trait-binding';
import { resolveQualityProps } from './validator';
 
/** 落到画布上的组件定义:协议内容 + 属性面板字段 */
export interface QualityCanvasComponent extends QualityComponentContent {
  traits?: TraitProperties[];
}
 
/** definition + 属性 → 画布内容,attributes 里带上类型标记与全部业务属性 */
export function buildQualityContent(
  definition: QualityComponentDefinition,
  props?: QualityProps,
): QualityCanvasComponent {
  const resolved = resolveQualityProps(definition, props);
  const content = definition.buildContent(resolved);
  return {
    ...content,
    attributes: {
      ...content.attributes,
      [QUALITY_TYPE_ATTR]: definition.type,
      ...propsToAttributes(definition, resolved),
    },
    traits: buildQualityTraits(definition),
  };
}
 
/**
 * 属性面板字段。
 * <p>
 * 默认 changeProp 为 false,trait 值直接写回组件的 data-qc-<key> 属性,
 * 与 serializer 的读取路径保持一致。
 */
export function buildQualityTraits(
  definition: QualityComponentDefinition,
): TraitProperties[] {
  return definition.propertySchema.map((field) => traitFromField(definition, field));
}
 
function traitFromField(
  definition: QualityComponentDefinition,
  field: QualityFieldSchema,
): TraitProperties {
  const base: TraitProperties = {
    name: propAttrName(field.key),
    label: field.label,
    placeholder: field.placeholder,
  };
  // 可绑定字段用自定义控件:既能手工输入,也能从数据字段里挑,挑中的写成 {{路径}}
  if (field.bindable && (field.type === 'string' || field.type === 'text')) {
    return { ...base, type: BINDING_TRAIT_TYPE };
  }
  switch (field.type) {
    case 'boolean': {
      return {
        ...base,
        type: 'checkbox',
        // 显式声明真/假字面量,DOM 属性只能是字符串,靠它才能稳定往返
        valueTrue: 'true',
        valueFalse: 'false',
        default: String(field.defaultValue ?? false),
      };
    }
    case 'enum': {
      return {
        ...base,
        type: 'select',
        options: (field.options ?? []).map((option) => ({
          id: String(option.value),
          name: option.label,
        })),
        default: field.defaultValue === undefined ? undefined : String(field.defaultValue),
      };
    }
    case 'number': {
      return {
        ...base,
        type: 'number',
        default: field.defaultValue === undefined ? undefined : String(field.defaultValue),
      };
    }
    case 'text': {
      return { ...base, type: 'textarea' };
    }
    default: {
      return { ...base, type: 'text' };
    }
  }
}
 
/** definition → 左侧面板的一个组件块 */
export function buildQualityBlock(definition: QualityComponentDefinition): BlockProperties {
  return {
    id: `quality-${definition.type}`,
    label: definition.label,
    category: definition.category,
    media: definition.icon,
    content: buildQualityContent(definition) as unknown as BlockProperties['content'],
    // 点击组件块追加到纸张末尾。GrapesJS 默认追加进「当前选中的组件」内部,
    // 在报告这种扁平结构里会把新组件塞进标题、表格单元格等元素,还会顶掉它们原有的文本。
    // 需要嵌套时仍可用拖拽精确落点。
    onClick: (block, editor) => {
      const content = block.get('content') as ComponentAdd;
      editor.getWrapper()?.append(content);
    },
  };
}
 
/** 注册表里的全部组件 → GrapesJS 组件块列表 */
export function buildQualityBlocks(): BlockProperties[] {
  return listQualityComponents().map(buildQualityBlock);
}