/**
|
* 质量组件 → 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);
|
}
|