feat: restore source parity and harden agent runtime

This commit is contained in:
2026-06-22 11:17:26 +08:00
parent e33f08277b
commit 0793eb82d6
596 changed files with 168879 additions and 290 deletions
@@ -0,0 +1,386 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
内置文档模板
"""
import json
from typing import Dict, Any, List
# 请假申请单模板
LEAVE_APPLICATION_TEMPLATE = {
"version": "1.0",
"pageConfig": {
"size": "A4",
"orientation": "portrait",
"margin": {"top": 25, "right": 20, "bottom": 25, "left": 20}
},
"elements": [
{
"id": "title",
"type": "text",
"content": "请假申请单",
"position": {"x": 0, "y": 10, "width": 170},
"style": {"fontSize": 22, "fontWeight": "bold", "textAlign": "center"}
},
{
"id": "doc_no",
"type": "field",
"fieldName": "_instance.instance_no",
"label": "单据编号:",
"position": {"x": 120, "y": 25},
"style": {"fontSize": 10, "color": "#666"}
},
{
"id": "applicant_section",
"type": "container",
"position": {"x": 0, "y": 35, "width": 170},
"children": [
{
"id": "applicant_name",
"type": "field",
"fieldName": "applicant_name",
"label": "申请人:",
"position": {"x": 0, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "department",
"type": "field",
"fieldName": "department_name",
"label": "部门:",
"position": {"x": 60, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "apply_date",
"type": "field",
"fieldName": "apply_date",
"label": "申请日期:",
"format": "date",
"position": {"x": 120, "y": 0},
"style": {"fontSize": 12}
}
]
},
{
"id": "leave_info",
"type": "container",
"position": {"x": 0, "y": 50, "width": 170},
"style": {"border": "1px solid #333", "padding": "10"},
"children": [
{
"id": "leave_type",
"type": "field",
"fieldName": "leave_type_name",
"label": "请假类型:",
"position": {"x": 5, "y": 5},
"style": {"fontSize": 12}
},
{
"id": "start_time",
"type": "field",
"fieldName": "start_time",
"label": "开始时间:",
"format": "datetime",
"position": {"x": 5, "y": 15},
"style": {"fontSize": 12}
},
{
"id": "end_time",
"type": "field",
"fieldName": "end_time",
"label": "结束时间:",
"format": "datetime",
"position": {"x": 90, "y": 15},
"style": {"fontSize": 12}
},
{
"id": "duration",
"type": "field",
"fieldName": "duration",
"label": "请假时长:",
"position": {"x": 5, "y": 25},
"style": {"fontSize": 12}
},
{
"id": "reason_label",
"type": "text",
"content": "请假事由:",
"position": {"x": 5, "y": 40},
"style": {"fontSize": 12}
},
{
"id": "reason",
"type": "field",
"fieldName": "reason",
"position": {"x": 5, "y": 50, "width": 160, "height": 30},
"style": {"fontSize": 12}
}
]
},
{
"id": "approval_section",
"type": "text",
"content": "审批记录",
"position": {"x": 0, "y": 145},
"style": {"fontSize": 14, "fontWeight": "bold"}
},
{
"id": "approval_table",
"type": "table",
"dataSource": "_logs",
"columns": [
{"field": "node_name", "label": "审批节点", "width": "60"},
{"field": "action", "label": "操作", "width": "30"},
{"field": "comment", "label": "审批意见", "width": "60"},
{"field": "created_at", "label": "时间", "width": "40"}
],
"position": {"x": 0, "y": 155, "width": 170}
},
{
"id": "signature_section",
"type": "container",
"position": {"x": 0, "y": 220, "width": 170},
"children": [
{
"id": "applicant_sign_label",
"type": "text",
"content": "申请人签名:",
"position": {"x": 0, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "applicant_signature",
"type": "signature",
"fieldName": "applicant_signature",
"position": {"x": 30, "y": 0},
"size": {"width": 40, "height": 20}
},
{
"id": "approver_sign_label",
"type": "text",
"content": "审批人签名:",
"position": {"x": 100, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "approver_signature",
"type": "signature",
"fieldName": "approver_signature",
"position": {"x": 130, "y": 0},
"size": {"width": 40, "height": 20}
}
]
},
{
"id": "seal_placeholder",
"type": "seal",
"sealType": "company",
"position": {"x": 130, "y": 200},
"size": {"width": 40, "height": 40}
}
]
}
# 报销单模板
EXPENSE_REPORT_TEMPLATE = {
"version": "1.0",
"pageConfig": {
"size": "A4",
"orientation": "portrait",
"margin": {"top": 25, "right": 20, "bottom": 25, "left": 20}
},
"elements": [
{
"id": "title",
"type": "text",
"content": "费用报销单",
"position": {"x": 0, "y": 10, "width": 170},
"style": {"fontSize": 22, "fontWeight": "bold", "textAlign": "center"}
},
{
"id": "doc_no",
"type": "field",
"fieldName": "_instance.instance_no",
"label": "单据编号:",
"position": {"x": 120, "y": 25},
"style": {"fontSize": 10, "color": "#666"}
},
{
"id": "basic_info",
"type": "container",
"position": {"x": 0, "y": 35, "width": 170},
"children": [
{
"id": "applicant",
"type": "field",
"fieldName": "applicant_name",
"label": "报销人:",
"position": {"x": 0, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "department",
"type": "field",
"fieldName": "department_name",
"label": "部门:",
"position": {"x": 60, "y": 0},
"style": {"fontSize": 12}
},
{
"id": "apply_date",
"type": "field",
"fieldName": "apply_date",
"label": "申请日期:",
"format": "date",
"position": {"x": 120, "y": 0},
"style": {"fontSize": 12}
}
]
},
{
"id": "expense_table_label",
"type": "text",
"content": "报销明细",
"position": {"x": 0, "y": 50},
"style": {"fontSize": 14, "fontWeight": "bold"}
},
{
"id": "expense_table",
"type": "table",
"dataSource": "expense_items",
"columns": [
{"field": "expense_date", "label": "日期", "width": "30"},
{"field": "expense_type", "label": "费用类型", "width": "35"},
{"field": "description", "label": "说明", "width": "60"},
{"field": "amount", "label": "金额(元)", "width": "30"}
],
"position": {"x": 0, "y": 60, "width": 170}
},
{
"id": "total_section",
"type": "container",
"position": {"x": 0, "y": 130, "width": 170},
"children": [
{
"id": "total_label",
"type": "text",
"content": "合计金额:",
"position": {"x": 100, "y": 0},
"style": {"fontSize": 14, "fontWeight": "bold"}
},
{
"id": "total_amount",
"type": "field",
"fieldName": "total_amount",
"format": "money",
"position": {"x": 130, "y": 0},
"style": {"fontSize": 14, "fontWeight": "bold", "color": "#c00"}
}
]
},
{
"id": "approval_section",
"type": "text",
"content": "审批记录",
"position": {"x": 0, "y": 150},
"style": {"fontSize": 14, "fontWeight": "bold"}
},
{
"id": "approval_table",
"type": "table",
"dataSource": "_logs",
"columns": [
{"field": "node_name", "label": "审批节点", "width": "60"},
{"field": "action", "label": "操作", "width": "30"},
{"field": "comment", "label": "审批意见", "width": "60"},
{"field": "created_at", "label": "时间", "width": "40"}
],
"position": {"x": 0, "y": 160, "width": 170}
},
{
"id": "seal_placeholder",
"type": "seal",
"sealType": "finance",
"position": {"x": 130, "y": 220},
"size": {"width": 40, "height": 40}
}
]
}
# 通用模板
GENERAL_TEMPLATE = {
"version": "1.0",
"pageConfig": {
"size": "A4",
"orientation": "portrait",
"margin": {"top": 25, "right": 20, "bottom": 25, "left": 20}
},
"elements": [
{
"id": "title",
"type": "text",
"content": "{{_instance.title}}",
"position": {"x": 0, "y": 10, "width": 170},
"style": {"fontSize": 22, "fontWeight": "bold", "textAlign": "center"}
},
{
"id": "doc_no",
"type": "field",
"fieldName": "_instance.instance_no",
"label": "单据编号:",
"position": {"x": 120, "y": 25},
"style": {"fontSize": 10, "color": "#666"}
},
{
"id": "content_placeholder",
"type": "text",
"content": "(请在模板设计器中添加表单字段)",
"position": {"x": 0, "y": 50, "width": 170},
"style": {"fontSize": 12, "textAlign": "center", "color": "#999"}
}
]
}
# 内置模板列表
BUILTIN_TEMPLATES: List[Dict[str, Any]] = [
{
"code": "builtin_leave_application",
"name": "请假申请单",
"category": "leave",
"description": "标准请假申请单模板,包含请假信息、审批记录和签章位置",
"template_type": "designer",
"template_content": json.dumps(LEAVE_APPLICATION_TEMPLATE, ensure_ascii=False),
"is_builtin": True,
"status": "published",
},
{
"code": "builtin_expense_report",
"name": "费用报销单",
"category": "expense",
"description": "标准费用报销单模板,包含报销明细、合计金额和审批记录",
"template_type": "designer",
"template_content": json.dumps(EXPENSE_REPORT_TEMPLATE, ensure_ascii=False),
"is_builtin": True,
"status": "published",
},
{
"code": "builtin_general",
"name": "通用模板",
"category": "other",
"description": "通用文档模板,可根据需要自定义内容",
"template_type": "designer",
"template_content": json.dumps(GENERAL_TEMPLATE, ensure_ascii=False),
"is_builtin": True,
"status": "published",
},
]
def get_builtin_templates() -> List[Dict[str, Any]]:
"""获取内置模板列表"""
return BUILTIN_TEMPLATES
@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page {
size: {{ page_width }} {{ page_height }};
margin: {{ margin_top }}px {{ margin_right }}px {{ margin_bottom }}px {{ margin_left }}px;
{{ page_number_css|safe }}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: {{ font_family|safe }};
font-size: 12pt;
line-height: 1.6;
color: #333;
}
.document-body {
width: 100%;
position: relative;
}
.element {
margin-bottom: 5px;
}
.element-text {
white-space: pre-wrap;
}
.element-field {
display: inline-block;
}
.element-field .label {
font-weight: normal;
}
.element-field .value {
border-bottom: 1px solid #333;
min-width: 100px;
display: inline-block;
padding: 0 5px;
}
.element-table, .info-table, .detail-table, .info-row {
width: 100%;
border-collapse: collapse;
}
.element-table th,
.element-table td {
border: 1px solid #333;
padding: 8px;
text-align: left;
}
.element-table th {
background-color: #f5f5f5;
font-weight: bold;
}
.element-image {
max-width: 100%;
}
.element-signature {
max-width: 150px;
max-height: 60px;
}
.element-qrcode,
.element-barcode,
.element-image {
width: 100%;
}
.element-divider {
border-top: 1px solid #333;
margin: 10px 0;
}
h1 {
margin: 15px 0;
}
.doc-header {
margin-bottom: 10px;
}
.doc-footer {
margin-top: 30px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.approval-area {
margin: 20px 0;
}
{{ custom_css|safe }}
</style>
</head>
<body>
<div class="document-body">
{{ content }}
</div>
</body>
</html>
@@ -0,0 +1,28 @@
{% set label_font_size = labelFontSize|default(14) %}
{% set label_color = labelColor|default('#333333') %}
{% set label_font_weight = labelFontWeight|default('normal') %}
{% set label_position = labelPosition|default('bottom') %}
{% set align_map = {'left': 'flex-start', 'center': 'center', 'right': 'flex-end'} %}
{% set justify = align_map.get(textAlign|default('center'), 'center') %}
<div style="display: flex; justify-content: {{ justify }}; width: 100%; box-sizing: border-box; line-height: 0;">
<div style="display: inline-flex; flex-direction: {% if label_position == 'top' %}column-reverse{% elif label_position == 'left' %}row-reverse{% elif label_position == 'right' %}row{% else %}column{% endif %}; align-items: center; gap: 4px; line-height: normal;">
{% if barcode_image %}
<img src="{{ barcode_image }}" style="width: {{ width }}; height: {{ height }}; display: block;" />
{% else %}
<div style="width: {{ width }}; height: {{ height }}; display: inline-flex; border: 2px dashed #dcdfe6; background-color: #f5f7fa; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: #909399; font-size: 12px;">
<div style="display: flex; align-items: center; gap: 2px;">
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
<div style="width: 2px; height: 16px; background-color: #909399;"></div>
</div>
<span>{{ width }} × {{ height }}</span>
</div>
{% endif %}
{% if label %}<span style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ label }}</span>{% endif %}
</div>
</div>
@@ -0,0 +1 @@
{% for child in children %}{{ child|safe }}{% endfor %}
@@ -0,0 +1,33 @@
{% set header_font_size = header_font_size|default(14) %}
{% set header_color = header_color|default('#333333') %}
{% set header_font_weight = header_font_weight|default('bold') %}
{% set content_font_size = content_font_size|default(14) %}
{% set content_color = content_color|default('#333333') %}
<table class="detail-table" style="width: 100%; border-collapse: collapse; margin: 10px 0;">
{% if show_header %}
<thead><tr>
{% if show_index %}<th style="border: 1px solid #333; padding: 8px; font-size: {{ header_font_size }}px; color: {{ header_color }}; font-weight: {{ header_font_weight }};{% if header_bg_color %} background-color: {{ header_bg_color }};{% endif %} width: {{ index_width|default('40') }}px;">序号</th>{% endif %}
{% for col in columns %}
<th style="border: 1px solid #333; padding: 8px; font-size: {{ header_font_size }}px; color: {{ header_color }}; font-weight: {{ header_font_weight }};{% if header_bg_color %} background-color: {{ header_bg_color }};{% endif %}{% if col.width %} width: {{ col.width }};{% endif %} text-align: {{ col.align|default('left') }};">{{ col.label }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tbody>
{% for row in rows %}
<tr>
{% if show_index %}<td style="border: 1px solid #333; padding: 8px; font-size: {{ content_font_size }}px; color: {{ content_color }}; text-align: center;">{{ loop.index }}</td>{% endif %}
{% for col in columns %}
<td style="border: 1px solid #333; padding: 8px; font-size: {{ content_font_size }}px; color: {{ content_color }}; text-align: {{ col.align|default('left') }};">{{ row[col.field]|default('') }}</td>
{% endfor %}
</tr>
{% endfor %}
{% if show_summary and rows %}
<tr>
{% if show_index %}<td style="border: 1px solid #333; padding: 8px; font-size: {{ content_font_size }}px; color: {{ content_color }}; text-align: center; font-weight: bold;">合计</td>{% endif %}
{% for col in columns %}
<td style="border: 1px solid #333; padding: 8px; font-size: {{ content_font_size }}px; color: {{ content_color }}; text-align: {{ col.align|default('left') }}; font-weight: bold;">{{ summary_values[col.field]|default('') }}</td>
{% endfor %}
</tr>
{% endif %}
</tbody>
</table>
@@ -0,0 +1 @@
<hr style="border: none; border-top: {{ line_width }}px {{ line_style }} {{ line_color }}; margin: 10px 0;" />
@@ -0,0 +1,8 @@
{% set label_font_size = label_font_size|default(font_size|default(12)) %}
{% set label_color = label_color|default('#333333') %}
{% set label_font_weight = label_font_weight|default('normal') %}
{% set value_font_size = value_font_size|default(font_size|default(12)) %}
{% set value_color = value_color|default('#333333') %}
<div style="text-align: {{ text_align }}; margin: 10px 0;">
{% for field in fields %}{% if not loop.first %}&nbsp;&nbsp;&nbsp;&nbsp;{% endif %}<span style="font-size: {{ label_font_size }}pt; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ field.label }}</span><span style="font-size: {{ value_font_size }}pt; color: {{ value_color }};{% if show_underline %} border-bottom: 1px solid #333; padding-bottom: 2px; display: inline-block; min-width: 50px;{% endif %}">{{ field.value if field.value else '' }}</span>{% endfor %}
</div>
@@ -0,0 +1 @@
{% if label %}<span class="label">{{ label }}</span>{% endif %}<span class="value">{{ value }}</span>
@@ -0,0 +1,5 @@
{% set color = color|default('#666666') %}
<div class="doc-footer" style="text-align: {{ text_align }}; font-size: {{ font_size }}pt; color: {{ color }}; margin-top: 30px; padding-top: 10px; border-top: 1px solid #ddd;">
{% if content %}<div>{{ content }}</div>{% endif %}
{% if footer_parts %}<div style="margin-top: 5px;">{{ footer_parts|join(' | ') }}</div>{% endif %}
</div>
@@ -0,0 +1,13 @@
{% set header_font_size = header_font_size|default(14) %}
{% set header_color = header_color|default('#333333') %}
{% set header_font_weight = header_font_weight|default('bold') %}
<table class="doc-header" style="width: 100%; border: none;">
<tr>
{% if header_type in ['logo', 'logo-text'] and logo_src %}
<td style="width: 80px; border: none;"><img src="{{ logo_src }}" style="max-height: 50px;" /></td>
{% endif %}
{% if header_type in ['text', 'logo-text'] %}
<td style="text-align: {{ text_align }}; border: none; font-size: {{ header_font_size }}pt; font-weight: {{ header_font_weight }}; color: {{ header_color }};">{{ company_name }}</td>
{% endif %}
</tr>
</table>
@@ -0,0 +1,27 @@
{% set label_font_size = labelFontSize|default(14) %}
{% set label_color = labelColor|default('#333333') %}
{% set label_font_weight = labelFontWeight|default('normal') %}
{% set align_map = {'left': 'flex-start', 'center': 'center', 'right': 'flex-end'} %}
{% set justify = align_map.get(textAlign|default('center'), 'center') %}
{% set is_float = position_mode|default('inline') == 'float' %}
{% if is_float %}
<div style="position: absolute; left: {{ float_x|default(0) }}px; top: {{ float_y|default(0) }}px; z-index: {{ float_z_index|default(100) }};">
{% else %}
<div style="display: flex; justify-content: {{ justify }}; width: 100%; box-sizing: border-box;">
{% endif %}
<div style="display: inline-flex; flex-direction: {% if labelPosition == 'top' %}column-reverse{% elif labelPosition == 'left' %}row-reverse{% elif labelPosition == 'right' %}row{% else %}column{% endif %}; align-items: center; gap: 4px;">
{% if src %}
<img src="{{ src }}" style="width: {{ width }}; height: {{ height }}; display: block;" />
{% else %}
<div style="width: {{ width }}; height: {{ height }}; display: flex; border: 2px dashed #dcdfe6; background-color: #f5f7fa; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: #909399; font-size: 12px;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
<span>{{ width }} × {{ height }}</span>
</div>
{% endif %}
{% if label %}<span style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ label }}</span>{% endif %}
</div>
</div>
@@ -0,0 +1,12 @@
{% set label_font_size = label_font_size|default(font_size|default(12)) %}
{% set label_color = label_color|default('#333333') %}
{% set label_font_weight = label_font_weight|default('normal') %}
{% set value_font_size = value_font_size|default(font_size|default(12)) %}
{% set value_color = value_color|default('#333333') %}
<table class="info-row" style="width: 100%; border: none; margin: 8px 0;">
<tr>
{% for field in fields %}
<td style="width: {{ field.width|default('auto') }}; border: none; padding: 4px 0;">{% if show_label %}<span style="font-size: {{ label_font_size }}pt; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ field.label }}</span>{% endif %}<span style="font-size: {{ value_font_size }}pt; color: {{ value_color }};{% if show_underline %} border-bottom: 1px solid #333; padding-bottom: 2px; display: inline-block; min-width: 50px;{% endif %}">{{ field.value if field.value else '' }}</span></td>
{% endfor %}
</tr>
</table>
@@ -0,0 +1,14 @@
{% set label_font_size = label_font_size|default(14) %}
{% set label_color = label_color|default('#333333') %}
{% set label_font_weight = label_font_weight|default('normal') %}
{% set field_font_size = field_font_size|default(14) %}
{% set field_color = field_color|default('#333333') %}
<table class="info-table" style="width: 100%; border-collapse: collapse; margin: 10px 0;">
{% for row in rows %}
<tr>
{% for cell in row.cells %}
<td colspan="{{ cell.colspan|default(1) }}" rowspan="{{ cell.rowspan|default(1) }}" style="border: {{ border }}; padding: 8px; text-align: {{ cell.align|default('left') }};{% if cell.type == 'label' %} font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }};{% if label_width %} width: {{ label_width }}px;{% endif %}{% if label_bg_color %} background-color: {{ label_bg_color }};{% elif cell.backgroundColor %} background-color: {{ cell.backgroundColor }};{% endif %}{% else %} font-size: {{ field_font_size }}px; color: {{ field_color }};{% if cell.bold %} font-weight: bold;{% endif %}{% if cell.backgroundColor %} background-color: {{ cell.backgroundColor }};{% endif %}{% endif %}">{% if cell.type == 'field' and show_underline %}<span style="border-bottom: 1px solid #333; padding-bottom: 2px; display: inline-block; min-width: 50px;">{{ cell.content if cell.content else '' }}</span>{% else %}{{ cell.content }}{% endif %}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
@@ -0,0 +1,10 @@
{% set label_font_size = label_font_size|default(font_size|default(12)) %}
{% set label_color = label_color|default('#333333') %}
{% set label_font_weight = label_font_weight|default('normal') %}
{% set value_font_size = value_font_size|default(font_size|default(12)) %}
{% set value_color = value_color|default('#333333') %}
{% if show_label %}
<p style="margin: 5px 0;"><span style="font-size: {{ label_font_size }}pt; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ label }}</span><span style="font-size: {{ value_font_size }}pt; color: {{ value_color }};{% if show_underline %} border-bottom: 1px solid #333; padding-bottom: 2px; display: inline-block; min-width: 50px;{% endif %}">{{ value if value else '' }}</span></p>
{% else %}
<p style="margin: 5px 0;"><span style="font-size: {{ value_font_size }}pt; color: {{ value_color }};{% if show_underline %} border-bottom: 1px solid #333; padding-bottom: 2px; display: inline-block; min-width: 50px;{% endif %}">{{ value if value else '' }}</span></p>
{% endif %}
@@ -0,0 +1,13 @@
{% set color = color|default('#333333') %}
<div style="font-size: {{ font_size }}pt; line-height: {{ line_height }}; text-align: {{ text_align }}; color: {{ color }}; margin: 10px 0; white-space: pre-wrap; word-wrap: break-word;">
<style>
.paragraph-content p { margin: 0.5em 0; }
.paragraph-content p:first-child { margin-top: 0; }
.paragraph-content p:last-child { margin-bottom: 0; }
.paragraph-content br { display: block; content: ''; margin: 0.5em 0; }
.paragraph-content table { border-collapse: collapse; width: 100%; margin: 1em 0; }
.paragraph-content table td, .paragraph-content table th { border: 1px solid #ddd; padding: 8px; text-align: left; }
.paragraph-content table th { font-weight: 500; }
</style>
<div class="paragraph-content">{{ content|safe }}</div>
</div>
@@ -0,0 +1,27 @@
{% set label_font_size = labelFontSize|default(14) %}
{% set label_color = labelColor|default('#333333') %}
{% set label_font_weight = labelFontWeight|default('normal') %}
{% set label_position = labelPosition|default('bottom') %}
{% set align_map = {'left': 'flex-start', 'center': 'center', 'right': 'flex-end'} %}
{% set justify = align_map.get(textAlign|default('center'), 'center') %}
<div style="display: flex; justify-content: {{ justify }}; width: 100%; box-sizing: border-box; line-height: 0;">
<div style="display: inline-flex; flex-direction: {% if label_position == 'top' %}column-reverse{% elif label_position == 'left' %}row-reverse{% elif label_position == 'right' %}row{% else %}column{% endif %}; align-items: center; gap: 4px; line-height: normal;">
{% if qrcode_image %}
<img src="{{ qrcode_image }}" style="width: {{ width }}; height: {{ height }}; display: block;" />
{% else %}
<div style="width: {{ width }}; height: {{ height }}; display: inline-flex; border: 2px dashed #dcdfe6; background-color: #f5f7fa; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: #909399; font-size: 12px;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
<rect x="14" y="14" width="3" height="3"></rect>
<rect x="18" y="14" width="3" height="3"></rect>
<rect x="14" y="18" width="3" height="3"></rect>
<rect x="18" y="18" width="3" height="3"></rect>
</svg>
<span>{{ width }} × {{ height }}</span>
</div>
{% endif %}
{% if label %}<span style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }};">{{ label }}</span>{% endif %}
</div>
</div>
@@ -0,0 +1 @@
<div class="rich-text" style="margin: 10px 0;">{{ content|safe }}</div>
@@ -0,0 +1,7 @@
<table style="width: 100%; border: none; border-collapse: separate; border-spacing: {{ gap }}px 0;">
<tr>
{% for child in children %}
<td style="{% if child.width %}width: {{ child.width }};{% endif %} vertical-align: top; border: none; padding: 0;">{{ child.content|safe }}</td>
{% endfor %}
</tr>
</table>
@@ -0,0 +1,42 @@
{% set align_map = {'left': 'flex-start', 'center': 'center', 'right': 'flex-end'} %}
{% set justify = align_map.get(text_align, 'flex-end') %}
{% set flex_direction = 'column' if label_position in ['top', 'bottom'] else 'row' %}
{% set label_font_size = label_font_size|default(14) %}
{% set label_color = label_color|default('#333333') %}
{% set label_font_weight = label_font_weight|default('normal') %}
{% set is_float = position_mode|default('inline') == 'float' %}
{% set float_style = 'position: absolute; left: %spx; top: %spx; z-index: %s;'|format(float_x|default(0), float_y|default(0), float_z_index|default(100)) if is_float else 'display: flex; justify-content: %s; margin: 10px 0; width: 100%%; box-sizing: border-box;'|format(justify) %}
{% if seal_image_data %}
<div class="seal-container" style="{{ float_style }}">
<div style="display: flex; flex-direction: {{ flex_direction }}; align-items: center; gap: 8px;">
{% if label and label_position in ['top', 'left'] %}
<span class="seal-label" style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }}; white-space: nowrap;">{{ label }}</span>
{% endif %}
<img class="seal-image"
src="{{ seal_image_data }}"
data-seal-id="{{ seal_id }}"
data-seal-image-id="{{ seal_image_id }}"
style="width: {{ width }}px; height: {{ height }}px; object-fit: contain; flex-shrink: 0;"
alt="{{ seal_name }}" />
{% if label and label_position in ['bottom', 'right'] %}
<span class="seal-label" style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }}; white-space: nowrap;">{{ label }}</span>
{% endif %}
</div>
</div>
{% else %}
<div class="seal-placeholder" data-seal-type="{{ seal_type }}"
style="{{ float_style }}">
<div style="display: flex; flex-direction: {{ flex_direction }}; align-items: center; gap: 8px;">
{% if label and label_position in ['top', 'left'] %}
<span class="seal-label" style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }}; white-space: nowrap;">{{ label }}</span>
{% endif %}
<div style="width: {{ width }}px; height: {{ height }}px; border: 2px dashed #ccc; border-radius: 50%;
display: flex; align-items: center; justify-content: center; color: #999; flex-shrink: 0;">
签章
</div>
{% if label and label_position in ['bottom', 'right'] %}
<span class="seal-label" style="font-size: {{ label_font_size }}px; color: {{ label_color }}; font-weight: {{ label_font_weight }}; white-space: nowrap;">{{ label }}</span>
{% endif %}
</div>
</div>
{% endif %}
@@ -0,0 +1,5 @@
{% if src %}
<img class="element-signature" src="{{ src }}" />
{% else %}
<div class="signature-placeholder"></div>
{% endif %}
@@ -0,0 +1,52 @@
{% set table_font_size = table_font_size|default(14) %}
{% set border_mode = border_mode|default('all') %}
{% set border_color = border_color|default('#333') %}
<table class="smart-table" style="width: 100%; border-collapse: collapse; margin: 10px 0; table-layout: fixed; font-size: {{ table_font_size }}px;">
{% if col_widths %}
<colgroup>
{% for w in col_widths %}
<col style="width: {{ w }}%;" />
{% endfor %}
</colgroup>
{% endif %}
{% for row in rows %}
{% set row_idx = loop.index0 %}
{% set is_first_row = loop.first %}
{% set is_last_row = loop.last %}
<tr>
{% for cell in row.cells %}
{% set col_idx = loop.index0 %}
{% set is_first_col = loop.first %}
{% set is_last_col = loop.last %}
{% set solid_border = '1px solid ' ~ border_color %}
{% set no_border = 'none' %}
{% if border_mode == 'none' %}
{% set b_top = no_border %}{% set b_right = no_border %}{% set b_bottom = no_border %}{% set b_left = no_border %}
{% elif border_mode == 'outer' %}
{% set b_top = solid_border if is_first_row else no_border %}
{% set b_right = solid_border if is_last_col else no_border %}
{% set b_bottom = solid_border if is_last_row else no_border %}
{% set b_left = solid_border if is_first_col else no_border %}
{% elif border_mode == 'inner' %}
{% set b_top = no_border if is_first_row else solid_border %}
{% set b_right = no_border if is_last_col else solid_border %}
{% set b_bottom = no_border if is_last_row else solid_border %}
{% set b_left = no_border if is_first_col else solid_border %}
{% elif border_mode == 'horizontal' %}
{% set b_top = no_border if is_first_row else solid_border %}
{% set b_right = no_border %}
{% set b_bottom = no_border if is_last_row else solid_border %}
{% set b_left = no_border %}
{% elif border_mode == 'vertical' %}
{% set b_top = no_border %}
{% set b_right = no_border if is_last_col else solid_border %}
{% set b_bottom = no_border %}
{% set b_left = no_border if is_first_col else solid_border %}
{% else %}
{% set b_top = solid_border %}{% set b_right = solid_border %}{% set b_bottom = solid_border %}{% set b_left = solid_border %}
{% endif %}
<td colspan="{{ cell.colspan|default(1) }}" rowspan="{{ cell.rowspan|default(1) }}" style="border-top: {{ b_top }}; border-right: {{ b_right }}; border-bottom: {{ b_bottom }}; border-left: {{ b_left }}; padding: 6px 8px; text-align: {{ cell.align|default('left') }};{% if cell.bold %} font-weight: bold;{% endif %}{% if cell.fontSize %} font-size: {{ cell.fontSize }}px;{% endif %}{% if cell.color %} color: {{ cell.color }};{% endif %}{% if cell.backgroundColor %} background-color: {{ cell.backgroundColor }};{% endif %} vertical-align: top; overflow-wrap: break-word;">{{ cell.content }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
@@ -0,0 +1,4 @@
<div class="smart-text" style="margin: 8px 0; font-size: {{ font_size }}px; line-height: {{ line_height }}; text-align: {{ text_align }}; overflow-wrap: break-word;">{{ content|safe }}</div>
{% if table_html %}
<div class="smart-text-table" style="margin: 4px 0 8px 0;">{{ table_html|safe }}</div>
{% endif %}
@@ -0,0 +1 @@
<div style="height: {{ height }}px;"></div>
@@ -0,0 +1,16 @@
<table class="element-table">
<thead><tr>
{% for col in columns %}
<th style="width: {{ col.width|default('auto') }}">{{ col.label }}</th>
{% endfor %}
</tr></thead>
<tbody>
{% for row in rows %}
<tr>
{% for col in columns %}
<td>{{ row[col.field]|default('') }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
@@ -0,0 +1 @@
<span class="text-content">{{ content }}</span>
@@ -0,0 +1,2 @@
{% set color = color|default('#333333') %}
<h1 style="font-size: {{ font_size }}pt; font-weight: {{ font_weight }}; text-align: {{ text_align }}; color: {{ color }}; margin: 15px 0;">{{ content }}</h1>
@@ -0,0 +1,11 @@
<div class="wysiwyg-content" style="font-size: 14px; line-height: 1.6;">
{{ content }}
</div>
<style>
.wysiwyg-content h1 { font-size: 28px; font-weight: bold; margin: 16px 0 8px; line-height: 1.3; }
.wysiwyg-content h2 { font-size: 22px; font-weight: bold; margin: 14px 0 6px; line-height: 1.3; }
.wysiwyg-content h3 { font-size: 18px; font-weight: bold; margin: 12px 0 4px; line-height: 1.4; }
.wysiwyg-content p { margin: 4px 0; }
.wysiwyg-content ul, .wysiwyg-content ol { padding-left: 24px; margin: 4px 0; }
.wysiwyg-content img { max-width: 100%; height: auto; }
</style>