feat: 支持工作流文件节点多格式文档导出

- 补齐 md/html/pdf/docx 导出与统一渲染服务

- 收口文件生成节点配置与格式校验

- 修复 PDF 中文字体与 Markdown 渲染链路
This commit is contained in:
2026-04-19 15:23:23 +08:00
parent 51198ff492
commit a5aab86de2
33 changed files with 2144 additions and 102 deletions

View File

@@ -4,10 +4,9 @@
import {Button, Chosen, Heading, Input, Select, Textarea} from '../base';
import RefParameterList from '../core/RefParameterList.svelte';
import {getCurrentNodeId} from '#components/utils/NodeUtils';
import {useAddParameter} from '../utils/useAddParameter.svelte';
import {fillParameterId, useAddParameter} from '../utils/useAddParameter.svelte';
import {getOptions} from '../utils/NodeUtils';
import OutputDefList from '../core/OutputDefList.svelte';
import {fillParameterId} from '../utils/useAddParameter.svelte.js';
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
const { data, ...rest }: {
@@ -23,15 +22,31 @@
const editorParameters = $derived.by(() => {
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
});
const currentNodeData = $derived.by(() => {
return (currentNode?.current?.data as Record<string, any>) || (data as Record<string, any>) || {};
});
const updateNodeData = (data: Record<string, any>) => {
updateNodeDataInner(currentNodeId, data);
};
const updateNodeDataByEvent = (name: string, event: Event) => {
updateNodeData({
[name]: (event.target as any)?.value
});
const resolveFormValue = (form: any) => {
return form.resolveValue?.(currentNodeData) ?? currentNodeData?.[form.name] ?? form.defaultValue;
};
const resolveFormOptions = (form: any) => {
return form.resolveOptions?.(currentNodeData) ?? form.options ?? [];
};
const updateFormValue = (form: any, nextValue: any) => {
const patch = form.onValueChange?.(nextValue, currentNodeData) ?? {
[form.name]: nextValue
};
updateNodeData(patch);
};
const updateNodeDataByEvent = (form: any, event: Event) => {
updateFormValue(form, (event.target as any)?.value);
};
const node = {
@@ -118,21 +133,21 @@
mode="input"
placeholder={form.placeholder}
style="width: 100%"
value={data[form.name] || form.defaultValue}
value={resolveFormValue(form)}
parameters={editorParameters}
{...form.attrs}
oninput={(e)=>{
updateNodeDataByEvent(form.name,e)
oninput={(e:any)=>{
updateNodeDataByEvent(form,e)
}}
/>
{:else}
<Input
placeholder={form.placeholder}
style="width: 100%"
value={data[form.name] || form.defaultValue}
value={resolveFormValue(form)}
{...form.attrs}
onchange={(e)=>{
updateNodeDataByEvent(form.name,e)
updateNodeDataByEvent(form,e)
}}
/>
{/if}
@@ -146,11 +161,11 @@
rows={3}
placeholder={form.placeholder}
style="width: 100%"
value={data[form.name] || form.defaultValue}
value={resolveFormValue(form)}
parameters={editorParameters}
{...form.attrs}
oninput={(e)=>{
updateNodeDataByEvent(form.name,e)
oninput={(e:any)=>{
updateNodeDataByEvent(form,e)
}}
/>
{:else}
@@ -158,10 +173,10 @@
rows={3}
placeholder={form.placeholder}
style="width: 100%"
value={data[form.name] || form.defaultValue}
value={resolveFormValue(form)}
{...form.attrs}
onchange={(e)=>{
updateNodeDataByEvent(form.name,e)
updateNodeDataByEvent(form,e)
}}
/>
{/if}
@@ -176,19 +191,16 @@
type="range"
{...form.attrs}
value={data[form.name] ?? form.defaultValue}
oninput={(e) => updateNodeData({ [form.name]: parseFloat(e.target.value) })}
oninput={(e:any) => updateNodeData({ [form.name]: parseFloat(e.target.value) })}
/>
</div>
</div>
{:else if form.type === 'select'}
<div class="setting-title">{form.label}</div>
<div class="setting-item">
<Select items={form.options||[]} style="width: 100%" placeholder={form.placeholder} onSelect={(item)=>{
const newValue = item.value;
updateNodeData({
[form.name]: newValue
})
}} value={data[form.name] ? [data[form.name]] : [form.defaultValue]} />
<Select items={resolveFormOptions(form)} style="width: 100%" placeholder={form.placeholder} onSelect={(item)=>{
updateFormValue(form, item.value)
}} value={[resolveFormValue(form)]} />
</div>
{:else if form.type === 'chosen'}
<div class="setting-title">{form.label}</div>

View File

@@ -46,6 +46,12 @@ export type CustomNodeForm = {
defaultValue?: string | number | boolean;
attrs?: Record<string, any>;
options?: SelectItem[];
resolveValue?: (data: Record<string, any>) => string | number | boolean | undefined;
resolveOptions?: (data: Record<string, any>) => SelectItem[];
onValueChange?: (
value: string | number | boolean | undefined,
data: Record<string, any>,
) => Record<string, any> | void;
templateSupport?: boolean;
chosen?: {
labelDataKey: string;
@@ -66,6 +72,7 @@ export type CustomNode = {
icon?: string;
sortNo?: number;
group?: 'base' | 'tools';
renderFirst?: boolean;
rootClass?: string;
rootStyle?: string;
parameters?: Parameter[];