weaver-e10-builder 1.5.10 → 1.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -189
- package/assets/image-style-observation.schema.json +1355 -0
- package/assets/layout-spec.schema.json +52 -0
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +159 -8
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/designer-setters.d.ts.map +1 -1
- package/dist/commands/designer-setters.js +26 -4
- package/dist/commands/designer-setters.js.map +1 -1
- package/dist/commands/form-association-manage.d.ts.map +1 -1
- package/dist/commands/form-association-manage.js +3 -0
- package/dist/commands/form-association-manage.js.map +1 -1
- package/dist/commands/form-button-actions-cmd.d.ts +3 -0
- package/dist/commands/form-button-actions-cmd.d.ts.map +1 -0
- package/dist/commands/form-button-actions-cmd.js +309 -0
- package/dist/commands/form-button-actions-cmd.js.map +1 -0
- package/dist/commands/form-button-permission-cmd.d.ts +3 -0
- package/dist/commands/form-button-permission-cmd.d.ts.map +1 -0
- package/dist/commands/form-button-permission-cmd.js +132 -0
- package/dist/commands/form-button-permission-cmd.js.map +1 -0
- package/dist/commands/form-button-props-cmd.d.ts +3 -0
- package/dist/commands/form-button-props-cmd.d.ts.map +1 -0
- package/dist/commands/form-button-props-cmd.js +236 -0
- package/dist/commands/form-button-props-cmd.js.map +1 -0
- package/dist/commands/form-button.d.ts.map +1 -1
- package/dist/commands/form-button.js +82 -55
- package/dist/commands/form-button.js.map +1 -1
- package/dist/commands/form-field-linkage.d.ts +55 -0
- package/dist/commands/form-field-linkage.d.ts.map +1 -1
- package/dist/commands/form-field-linkage.js +1026 -12
- package/dist/commands/form-field-linkage.js.map +1 -1
- package/dist/commands/form-formula-linkage.d.ts +36 -0
- package/dist/commands/form-formula-linkage.d.ts.map +1 -1
- package/dist/commands/form-formula-linkage.js +256 -65
- package/dist/commands/form-formula-linkage.js.map +1 -1
- package/dist/commands/form-layout-restore.d.ts +3 -0
- package/dist/commands/form-layout-restore.d.ts.map +1 -0
- package/dist/commands/form-layout-restore.js +217 -0
- package/dist/commands/form-layout-restore.js.map +1 -0
- package/dist/commands/form-layout.d.ts.map +1 -1
- package/dist/commands/form-layout.js +11 -1
- package/dist/commands/form-layout.js.map +1 -1
- package/dist/commands/form-list-permission.d.ts.map +1 -1
- package/dist/commands/form-list-permission.js +29 -3
- package/dist/commands/form-list-permission.js.map +1 -1
- package/dist/commands/form-list-search-group.d.ts.map +1 -1
- package/dist/commands/form-list-search-group.js +66 -3
- package/dist/commands/form-list-search-group.js.map +1 -1
- package/dist/commands/form-list-utils.d.ts +14 -0
- package/dist/commands/form-list-utils.d.ts.map +1 -1
- package/dist/commands/form-list-utils.js +65 -4
- package/dist/commands/form-list-utils.js.map +1 -1
- package/dist/commands/form-message.d.ts.map +1 -1
- package/dist/commands/form-message.js +16 -4
- package/dist/commands/form-message.js.map +1 -1
- package/dist/commands/form-obj-meta.d.ts +12 -0
- package/dist/commands/form-obj-meta.d.ts.map +1 -0
- package/dist/commands/form-obj-meta.js +116 -0
- package/dist/commands/form-obj-meta.js.map +1 -0
- package/dist/commands/form-permission.d.ts +20 -0
- package/dist/commands/form-permission.d.ts.map +1 -1
- package/dist/commands/form-permission.js +69 -11
- package/dist/commands/form-permission.js.map +1 -1
- package/dist/commands/form-validate.d.ts.map +1 -1
- package/dist/commands/form-validate.js +167 -35
- package/dist/commands/form-validate.js.map +1 -1
- package/dist/commands/form.d.ts.map +1 -1
- package/dist/commands/form.js +26 -2
- package/dist/commands/form.js.map +1 -1
- package/dist/commands/menu-permission.d.ts.map +1 -1
- package/dist/commands/menu-permission.js +6 -7
- package/dist/commands/menu-permission.js.map +1 -1
- package/dist/commands/menu.d.ts.map +1 -1
- package/dist/commands/menu.js +68 -43
- package/dist/commands/menu.js.map +1 -1
- package/dist/commands/wf-form.d.ts.map +1 -1
- package/dist/commands/wf-form.js +269 -47
- package/dist/commands/wf-form.js.map +1 -1
- package/dist/commands/workflow-function-management.d.ts +5 -0
- package/dist/commands/workflow-function-management.d.ts.map +1 -0
- package/dist/commands/workflow-function-management.js +91 -0
- package/dist/commands/workflow-function-management.js.map +1 -0
- package/dist/commands/workflow-node-assign-flow.d.ts.map +1 -1
- package/dist/commands/workflow-node-assign-flow.js +1 -0
- package/dist/commands/workflow-node-assign-flow.js.map +1 -1
- package/dist/commands/workflow-node-sign.d.ts +0 -12
- package/dist/commands/workflow-node-sign.d.ts.map +1 -1
- package/dist/commands/workflow-node-sign.js +31 -102
- package/dist/commands/workflow-node-sign.js.map +1 -1
- package/dist/commands/workflow-opinion-settings.d.ts +5 -0
- package/dist/commands/workflow-opinion-settings.d.ts.map +1 -0
- package/dist/commands/workflow-opinion-settings.js +51 -0
- package/dist/commands/workflow-opinion-settings.js.map +1 -0
- package/dist/commands/workflow-subprocess.d.ts.map +1 -1
- package/dist/commands/workflow-subprocess.js +370 -20
- package/dist/commands/workflow-subprocess.js.map +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +35 -61
- package/dist/commands/workflow.js.map +1 -1
- package/dist/core/client.d.ts +2 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +4 -2
- package/dist/core/client.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/services/actionflow/calendar_mapper.d.ts.map +1 -1
- package/dist/services/actionflow/calendar_mapper.js +64 -18
- package/dist/services/actionflow/calendar_mapper.js.map +1 -1
- package/dist/services/actionflow/component_registry.d.ts.map +1 -1
- package/dist/services/actionflow/component_registry.js +6 -0
- package/dist/services/actionflow/component_registry.js.map +1 -1
- package/dist/services/actionflow/components/document_actions.d.ts.map +1 -1
- package/dist/services/actionflow/components/document_actions.js +113 -4
- package/dist/services/actionflow/components/document_actions.js.map +1 -1
- package/dist/services/actionflow/components/eb_get_data_by_id.d.ts.map +1 -1
- package/dist/services/actionflow/components/eb_get_data_by_id.js +18 -1
- package/dist/services/actionflow/components/eb_get_data_by_id.js.map +1 -1
- package/dist/services/actionflow/components/index.d.ts.map +1 -1
- package/dist/services/actionflow/components/index.js +2 -1
- package/dist/services/actionflow/components/index.js.map +1 -1
- package/dist/services/actionflow/components/meeting_create.d.ts.map +1 -1
- package/dist/services/actionflow/components/meeting_create.js +5 -1
- package/dist/services/actionflow/components/meeting_create.js.map +1 -1
- package/dist/services/actionflow/components/schedule_create.d.ts.map +1 -1
- package/dist/services/actionflow/components/schedule_create.js +22 -6
- package/dist/services/actionflow/components/schedule_create.js.map +1 -1
- package/dist/services/actionflow/components/workflow_actions.d.ts +1 -0
- package/dist/services/actionflow/components/workflow_actions.d.ts.map +1 -1
- package/dist/services/actionflow/components/workflow_actions.js +44 -7
- package/dist/services/actionflow/components/workflow_actions.js.map +1 -1
- package/dist/services/actionflow/document_source.d.ts +5 -0
- package/dist/services/actionflow/document_source.d.ts.map +1 -1
- package/dist/services/actionflow/document_source.js +16 -0
- package/dist/services/actionflow/document_source.js.map +1 -1
- package/dist/services/actionflow/formula_service.d.ts +7 -0
- package/dist/services/actionflow/formula_service.d.ts.map +1 -1
- package/dist/services/actionflow/formula_service.js +44 -8
- package/dist/services/actionflow/formula_service.js.map +1 -1
- package/dist/services/actionflow/preflight_service.d.ts +5 -0
- package/dist/services/actionflow/preflight_service.d.ts.map +1 -1
- package/dist/services/actionflow/preflight_service.js +127 -2
- package/dist/services/actionflow/preflight_service.js.map +1 -1
- package/dist/services/actionflow/save_workflow_draft.d.ts +7 -0
- package/dist/services/actionflow/save_workflow_draft.d.ts.map +1 -1
- package/dist/services/actionflow/save_workflow_draft.js +1 -1
- package/dist/services/actionflow/save_workflow_draft.js.map +1 -1
- package/dist/services/actionflow/workflow_compiler.d.ts.map +1 -1
- package/dist/services/actionflow/workflow_compiler.js +26 -0
- package/dist/services/actionflow/workflow_compiler.js.map +1 -1
- package/dist/services/app-publish.d.ts +25 -0
- package/dist/services/app-publish.d.ts.map +1 -1
- package/dist/services/app-publish.js +38 -0
- package/dist/services/app-publish.js.map +1 -1
- package/dist/services/app-range.d.ts +66 -0
- package/dist/services/app-range.d.ts.map +1 -0
- package/dist/services/app-range.js +137 -0
- package/dist/services/app-range.js.map +1 -0
- package/dist/services/form-association.d.ts +13 -0
- package/dist/services/form-association.d.ts.map +1 -1
- package/dist/services/form-association.js +42 -0
- package/dist/services/form-association.js.map +1 -1
- package/dist/services/form-button-action.d.ts +82 -0
- package/dist/services/form-button-action.d.ts.map +1 -1
- package/dist/services/form-button-action.js +194 -2
- package/dist/services/form-button-action.js.map +1 -1
- package/dist/services/form-button-condition.d.ts +45 -0
- package/dist/services/form-button-condition.d.ts.map +1 -0
- package/dist/services/form-button-condition.js +106 -0
- package/dist/services/form-button-condition.js.map +1 -0
- package/dist/services/form-button-context.d.ts +41 -0
- package/dist/services/form-button-context.d.ts.map +1 -0
- package/dist/services/form-button-context.js +54 -0
- package/dist/services/form-button-context.js.map +1 -0
- package/dist/services/form-button-logicflow.d.ts +30 -0
- package/dist/services/form-button-logicflow.d.ts.map +1 -0
- package/dist/services/form-button-logicflow.js +59 -0
- package/dist/services/form-button-logicflow.js.map +1 -0
- package/dist/services/form-button-org.d.ts +30 -0
- package/dist/services/form-button-org.d.ts.map +1 -0
- package/dist/services/form-button-org.js +80 -0
- package/dist/services/form-button-org.js.map +1 -0
- package/dist/services/form-button-permission.d.ts +65 -0
- package/dist/services/form-button-permission.d.ts.map +1 -0
- package/dist/services/form-button-permission.js +113 -0
- package/dist/services/form-button-permission.js.map +1 -0
- package/dist/services/form-button-position.d.ts +4 -18
- package/dist/services/form-button-position.d.ts.map +1 -1
- package/dist/services/form-button-position.js +4 -36
- package/dist/services/form-button-position.js.map +1 -1
- package/dist/services/form-button-props.d.ts +55 -0
- package/dist/services/form-button-props.d.ts.map +1 -0
- package/dist/services/form-button-props.js +90 -0
- package/dist/services/form-button-props.js.map +1 -0
- package/dist/services/form-button-save.d.ts +49 -0
- package/dist/services/form-button-save.d.ts.map +1 -0
- package/dist/services/form-button-save.js +54 -0
- package/dist/services/form-button-save.js.map +1 -0
- package/dist/services/form-formula-linkage.d.ts +120 -20
- package/dist/services/form-formula-linkage.d.ts.map +1 -1
- package/dist/services/form-formula-linkage.js +253 -39
- package/dist/services/form-formula-linkage.js.map +1 -1
- package/dist/services/form-layout-io.d.ts +18 -0
- package/dist/services/form-layout-io.d.ts.map +1 -0
- package/dist/services/form-layout-io.js +89 -0
- package/dist/services/form-layout-io.js.map +1 -0
- package/dist/services/form-layout-source/artifact.d.ts +8 -0
- package/dist/services/form-layout-source/artifact.d.ts.map +1 -0
- package/dist/services/form-layout-source/artifact.js +52 -0
- package/dist/services/form-layout-source/artifact.js.map +1 -0
- package/dist/services/form-layout-source/binding.d.ts +10 -0
- package/dist/services/form-layout-source/binding.d.ts.map +1 -0
- package/dist/services/form-layout-source/binding.js +43 -0
- package/dist/services/form-layout-source/binding.js.map +1 -0
- package/dist/services/form-layout-source/compile.d.ts +7 -0
- package/dist/services/form-layout-source/compile.d.ts.map +1 -0
- package/dist/services/form-layout-source/compile.js +262 -0
- package/dist/services/form-layout-source/compile.js.map +1 -0
- package/dist/services/form-layout-source/creation.d.ts +18 -0
- package/dist/services/form-layout-source/creation.d.ts.map +1 -0
- package/dist/services/form-layout-source/creation.js +42 -0
- package/dist/services/form-layout-source/creation.js.map +1 -0
- package/dist/services/form-layout-source/excel-extractor.d.ts +7 -0
- package/dist/services/form-layout-source/excel-extractor.d.ts.map +1 -0
- package/dist/services/form-layout-source/excel-extractor.js +342 -0
- package/dist/services/form-layout-source/excel-extractor.js.map +1 -0
- package/dist/services/form-layout-source/image-border.d.ts +9 -0
- package/dist/services/form-layout-source/image-border.d.ts.map +1 -0
- package/dist/services/form-layout-source/image-border.js +128 -0
- package/dist/services/form-layout-source/image-border.js.map +1 -0
- package/dist/services/form-layout-source/image-observation.d.ts +14 -0
- package/dist/services/form-layout-source/image-observation.d.ts.map +1 -0
- package/dist/services/form-layout-source/image-observation.js +90 -0
- package/dist/services/form-layout-source/image-observation.js.map +1 -0
- package/dist/services/form-layout-source/image-review.d.ts +32 -0
- package/dist/services/form-layout-source/image-review.d.ts.map +1 -0
- package/dist/services/form-layout-source/image-review.js +266 -0
- package/dist/services/form-layout-source/image-review.js.map +1 -0
- package/dist/services/form-layout-source/image-style.d.ts +16 -0
- package/dist/services/form-layout-source/image-style.d.ts.map +1 -0
- package/dist/services/form-layout-source/image-style.js +539 -0
- package/dist/services/form-layout-source/image-style.js.map +1 -0
- package/dist/services/form-layout-source/json.d.ts +3 -0
- package/dist/services/form-layout-source/json.d.ts.map +1 -0
- package/dist/services/form-layout-source/json.js +33 -0
- package/dist/services/form-layout-source/json.js.map +1 -0
- package/dist/services/form-layout-source/presentation.d.ts +23 -0
- package/dist/services/form-layout-source/presentation.d.ts.map +1 -0
- package/dist/services/form-layout-source/presentation.js +174 -0
- package/dist/services/form-layout-source/presentation.js.map +1 -0
- package/dist/services/form-layout-source/preview.d.ts +4 -0
- package/dist/services/form-layout-source/preview.d.ts.map +1 -0
- package/dist/services/form-layout-source/preview.js +49 -0
- package/dist/services/form-layout-source/preview.js.map +1 -0
- package/dist/services/form-layout-source/schema.d.ts +20 -0
- package/dist/services/form-layout-source/schema.d.ts.map +1 -0
- package/dist/services/form-layout-source/schema.js +149 -0
- package/dist/services/form-layout-source/schema.js.map +1 -0
- package/dist/services/form-layout-source/source-grid.d.ts +8 -0
- package/dist/services/form-layout-source/source-grid.d.ts.map +1 -0
- package/dist/services/form-layout-source/source-grid.js +61 -0
- package/dist/services/form-layout-source/source-grid.js.map +1 -0
- package/dist/services/form-layout-source/types.d.ts +242 -0
- package/dist/services/form-layout-source/types.d.ts.map +1 -0
- package/dist/services/form-layout-source/types.js +2 -0
- package/dist/services/form-layout-source/types.js.map +1 -0
- package/dist/services/form-layout-source/verify.d.ts +14 -0
- package/dist/services/form-layout-source/verify.d.ts.map +1 -0
- package/dist/services/form-layout-source/verify.js +226 -0
- package/dist/services/form-layout-source/verify.js.map +1 -0
- package/dist/services/form-list-search-group.d.ts +17 -2
- package/dist/services/form-list-search-group.d.ts.map +1 -1
- package/dist/services/form-list-search-group.js +29 -12
- package/dist/services/form-list-search-group.js.map +1 -1
- package/dist/services/form-list-search.d.ts.map +1 -1
- package/dist/services/form-list-search.js +4 -0
- package/dist/services/form-list-search.js.map +1 -1
- package/dist/services/form-list-stats.d.ts.map +1 -1
- package/dist/services/form-list-stats.js +9 -2
- package/dist/services/form-list-stats.js.map +1 -1
- package/dist/services/form-message.d.ts +26 -7
- package/dist/services/form-message.d.ts.map +1 -1
- package/dist/services/form-message.js +52 -5
- package/dist/services/form-message.js.map +1 -1
- package/dist/services/form-obj-meta.d.ts +72 -0
- package/dist/services/form-obj-meta.d.ts.map +1 -0
- package/dist/services/form-obj-meta.js +103 -0
- package/dist/services/form-obj-meta.js.map +1 -0
- package/dist/services/form-permission-archive.d.ts +9 -1
- package/dist/services/form-permission-archive.d.ts.map +1 -1
- package/dist/services/form-permission-archive.js +38 -22
- package/dist/services/form-permission-archive.js.map +1 -1
- package/dist/services/form-view-populate.d.ts.map +1 -1
- package/dist/services/form-view-populate.js +7 -0
- package/dist/services/form-view-populate.js.map +1 -1
- package/dist/services/icon-path.d.ts +70 -0
- package/dist/services/icon-path.d.ts.map +1 -0
- package/dist/services/icon-path.js +80 -0
- package/dist/services/icon-path.js.map +1 -0
- package/dist/services/matrix-condition.d.ts +161 -0
- package/dist/services/matrix-condition.d.ts.map +1 -0
- package/dist/services/matrix-condition.js +403 -0
- package/dist/services/matrix-condition.js.map +1 -0
- package/dist/services/menu-catalog.d.ts +33 -0
- package/dist/services/menu-catalog.d.ts.map +1 -1
- package/dist/services/menu-catalog.js +56 -0
- package/dist/services/menu-catalog.js.map +1 -1
- package/dist/services/permission-entity.d.ts +55 -0
- package/dist/services/permission-entity.d.ts.map +1 -0
- package/dist/services/permission-entity.js +79 -0
- package/dist/services/permission-entity.js.map +1 -0
- package/dist/services/validate-help.d.ts +25 -0
- package/dist/services/validate-help.d.ts.map +1 -0
- package/dist/services/validate-help.js +29 -0
- package/dist/services/validate-help.js.map +1 -0
- package/dist/services/workflow-assign-flow.d.ts.map +1 -1
- package/dist/services/workflow-assign-flow.js.map +1 -1
- package/dist/services/workflow-function-management.d.ts +123 -0
- package/dist/services/workflow-function-management.d.ts.map +1 -0
- package/dist/services/workflow-function-management.js +425 -0
- package/dist/services/workflow-function-management.js.map +1 -0
- package/dist/services/workflow-matrix-rule.d.ts +34 -0
- package/dist/services/workflow-matrix-rule.d.ts.map +1 -0
- package/dist/services/workflow-matrix-rule.js +94 -0
- package/dist/services/workflow-matrix-rule.js.map +1 -0
- package/dist/services/workflow-opinion-settings.d.ts +67 -0
- package/dist/services/workflow-opinion-settings.d.ts.map +1 -0
- package/dist/services/workflow-opinion-settings.js +328 -0
- package/dist/services/workflow-opinion-settings.js.map +1 -0
- package/package.json +3 -1
- package/skill-docs/SKILL.md +15 -6
- package/skill-docs/_catalog.md +4 -0
- package/skill-docs/actionflows/references/components/calendar/MeetingCreate.md +2 -0
- package/skill-docs/actionflows/references/components/document/DocumentSave.md +17 -17
- package/skill-docs/actionflows/references/components/workflow/ApproveCompletelyDelete.md +29 -0
- package/skill-docs/actionflows/references/components/workflow/ApproveDelete.md +2 -2
- package/skill-docs/assets/schemas/image-style-observation.schema.json +1355 -0
- package/skill-docs/references/common/guide-plan-template.md +3 -0
- package/skill-docs/references/common/ref-cli-commands.md +77 -26
- package/skill-docs/references/ebuilder-designer/guide-designer-components.md +9 -0
- package/skill-docs/references/form/guide-create-form.md +2 -0
- package/skill-docs/references/form/guide-field-linkage.md +140 -55
- package/skill-docs/references/form/guide-form-archive-permission.md +55 -2
- package/skill-docs/references/form/guide-form-association.md +13 -0
- package/skill-docs/references/form/guide-form-button.md +87 -0
- package/skill-docs/references/form/guide-form-layout-planning.md +6 -0
- package/skill-docs/references/form/guide-form-layout-source.md +366 -0
- package/skill-docs/references/form/guide-form-message.md +19 -2
- package/skill-docs/references/form/guide-form-view.md +24 -4
- package/skill-docs/references/form/guide-formula-linkage.md +86 -12
- package/skill-docs/references/workflow/guide-function-management.md +154 -0
- package/skill-docs/references/workflow/guide-opinion-settings.md +126 -0
- package/skill-docs/references/workflow/guide-workflow.md +22 -6
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { assert, clone, safeTree, stable } from './schema.js';
|
|
2
|
+
import { extent } from './source-grid.js';
|
|
3
|
+
const KEYS = ['color', 'backgroundColor', 'fontFamily', 'fontSize', 'fontUnit', 'fontWeight', 'fontStyle', 'textAlign', 'verticalAlign', 'wordWrap', 'underline', 'height', 'width', 'border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight', 'align', 'bold'];
|
|
4
|
+
const objectKeys = (v, keys, label) => assert(v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).every(k => keys.includes(k)), `${label} 包含未支持属性`);
|
|
5
|
+
function nativeFont(value) {
|
|
6
|
+
if (!value)
|
|
7
|
+
return undefined;
|
|
8
|
+
const match = /^(.*?)\s*(\d+(?:\.\d+)?)(px|pt)\s+(.+)$/.exec(value);
|
|
9
|
+
assert(match, '图片 font 需要字号单位和字体家族,例如 bold 18pt SimSun');
|
|
10
|
+
const tokens = match[1].trim().split(/\s+/).filter(Boolean);
|
|
11
|
+
assert(tokens.every(t => ['italic', 'normal', 'bold'].includes(t) || /^[1-9]00$/.test(t)), '图片 font 前缀不支持');
|
|
12
|
+
const weight = tokens.find(t => t === 'bold' || /^[1-9]00$/.test(t));
|
|
13
|
+
return normalizeObservedStyle({ fontFamily: match[4], fontSize: Number(match[2]), fontUnit: match[3], fontWeight: weight === 'bold' ? 'bold' : weight ? Number(weight) : 400, fontStyle: tokens.includes('italic') ? 'italic' : 'normal' });
|
|
14
|
+
}
|
|
15
|
+
export function imageColor(value) {
|
|
16
|
+
const named = { red: '#FF0000', white: '#FFFFFF', black: '#000000', blue: '#0000FF', gray: '#808080', grey: '#808080', green: '#008000' };
|
|
17
|
+
assert(typeof value === 'string', '图片颜色必须是字符串');
|
|
18
|
+
const v = value.trim().toLowerCase();
|
|
19
|
+
if (named[v])
|
|
20
|
+
return named[v];
|
|
21
|
+
if (/^#[0-9a-f]{3}$/.test(v))
|
|
22
|
+
return '#' + v.slice(1).split('').map(c => c + c).join('').toUpperCase();
|
|
23
|
+
if (/^#[0-9a-f]{6}$/.test(v))
|
|
24
|
+
return v.toUpperCase();
|
|
25
|
+
const rgb = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/.exec(v);
|
|
26
|
+
assert(rgb && rgb.slice(1).every(n => Number(n) <= 255), `图片颜色无法规范化 ${value}`);
|
|
27
|
+
return '#' + rgb.slice(1).map(n => Number(n).toString(16).padStart(2, '0')).join('').toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
export function normalizeObservedStyle(input) {
|
|
30
|
+
safeTree(input);
|
|
31
|
+
objectKeys(input, KEYS, '图片样式');
|
|
32
|
+
const s = clone(input);
|
|
33
|
+
if (s.align !== undefined) {
|
|
34
|
+
assert(s.textAlign === undefined || s.textAlign === s.align, 'align 与 textAlign 冲突');
|
|
35
|
+
s.textAlign = s.align;
|
|
36
|
+
delete s.align;
|
|
37
|
+
}
|
|
38
|
+
if (s.bold !== undefined) {
|
|
39
|
+
assert(typeof s.bold === 'boolean', 'bold 必须是布尔值');
|
|
40
|
+
const weight = s.bold ? 700 : 400;
|
|
41
|
+
assert(s.fontWeight === undefined || s.fontWeight === weight || s.fontWeight === (s.bold ? 'bold' : 'normal'), 'bold 与 fontWeight 冲突');
|
|
42
|
+
s.fontWeight = weight;
|
|
43
|
+
delete s.bold;
|
|
44
|
+
}
|
|
45
|
+
for (const key of ['color', 'backgroundColor'])
|
|
46
|
+
if (s[key] !== undefined)
|
|
47
|
+
s[key] = imageColor(s[key]);
|
|
48
|
+
if (s.fontFamily !== undefined)
|
|
49
|
+
assert(typeof s.fontFamily === 'string' && s.fontFamily.length > 0 && s.fontFamily.length <= 100 && !/[;{}<>\r\n()]|url|expression/i.test(s.fontFamily), '字体名称无效');
|
|
50
|
+
if (s.fontSize !== undefined)
|
|
51
|
+
assert(Number.isFinite(s.fontSize) && s.fontSize >= 1 && s.fontSize <= (s.fontUnit === 'pt' ? 150 : 200), '字号无效');
|
|
52
|
+
assert(s.fontUnit === undefined || ['px', 'pt'].includes(s.fontUnit) && s.fontSize !== undefined, 'fontUnit 需要字号及 px/pt 单位');
|
|
53
|
+
if (s.fontWeight !== undefined)
|
|
54
|
+
assert(['normal', 'bold'].includes(s.fontWeight) || Number.isInteger(s.fontWeight) && s.fontWeight >= 100 && s.fontWeight <= 900 && s.fontWeight % 100 === 0, '字体粗细无效');
|
|
55
|
+
for (const [key, allowed] of [['fontStyle', ['normal', 'italic']], ['textAlign', ['left', 'center', 'right']], ['verticalAlign', ['top', 'middle', 'bottom']]])
|
|
56
|
+
assert(s[key] === undefined || allowed.includes(s[key]), `${key} 无效`);
|
|
57
|
+
for (const key of ['wordWrap', 'underline'])
|
|
58
|
+
assert(s[key] === undefined || typeof s[key] === 'boolean', `${key} 必须是布尔值`);
|
|
59
|
+
for (const key of ['height', 'width'])
|
|
60
|
+
if (s[key] !== undefined)
|
|
61
|
+
assert(Number.isFinite(s[key]) && s[key] > 0 && s[key] <= 2000, `${key} 无效`);
|
|
62
|
+
for (const key of ['border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight'])
|
|
63
|
+
if (s[key] !== undefined) {
|
|
64
|
+
const b = s[key];
|
|
65
|
+
objectKeys(b, ['color', 'width', 'line'], '图片边框');
|
|
66
|
+
b.color = imageColor(b.color);
|
|
67
|
+
assert([1, 2, 3].includes(b.width) && ['solid', 'dashed', 'dotted', 'double'].includes(b.line), '边框宽度或线型无效');
|
|
68
|
+
assert(b.line === 'solid' || b.width <= 2, '非实线边框最多支持宽度 2,不能静默降为细线');
|
|
69
|
+
}
|
|
70
|
+
return s;
|
|
71
|
+
}
|
|
72
|
+
function validateImageGeometry(geometry, source) {
|
|
73
|
+
objectKeys(geometry, ['imageWidth', 'imageHeight', 'sheets'], '图片几何证据');
|
|
74
|
+
assert([geometry.imageWidth, geometry.imageHeight].every(n => Number.isInteger(n) && n > 0 && n <= 100000), '图片像素尺寸无效');
|
|
75
|
+
assert(Array.isArray(geometry.sheets) && geometry.sheets.length > 0 && geometry.sheets.length <= source.sheets.length, '图片几何 Sheet 数量无效');
|
|
76
|
+
const names = new Set();
|
|
77
|
+
for (const grid of geometry.sheets) {
|
|
78
|
+
objectKeys(grid, ['sheet', 'xLines', 'yLines', 'regions'], '图片几何网格');
|
|
79
|
+
const sheet = source.sheets.find(s => s.name === grid.sheet);
|
|
80
|
+
assert(sheet && !names.has(grid.sheet), '图片几何 Sheet 缺失或重复');
|
|
81
|
+
names.add(grid.sheet);
|
|
82
|
+
for (const [lines, count, limit] of [[grid.xLines, sheet.columnCount, geometry.imageWidth], [grid.yLines, sheet.rowCount, geometry.imageHeight]]) {
|
|
83
|
+
assert(Array.isArray(lines) && lines.length === count + 1 && lines.every((n, i) => Number.isFinite(n) && n >= 0 && n <= limit && (!i || n > lines[i - 1] && n - lines[i - 1] <= 10000)), '图片边界坐标必须严格递增、匹配行列数且位于原图内');
|
|
84
|
+
}
|
|
85
|
+
assert(sheet.rowCount * sheet.columnCount <= 100000 && Array.isArray(grid.regions) && grid.regions.length <= 100000, '图片几何区域数量超限');
|
|
86
|
+
const occupied = new Set();
|
|
87
|
+
for (const region of grid.regions) {
|
|
88
|
+
objectKeys(region, ['row', 'col', 'rowCount', 'colCount', 'style'], '图片可见区域');
|
|
89
|
+
assert([region.row, region.col, region.rowCount, region.colCount].every(Number.isInteger) && region.row >= 0 && region.col >= 0 && region.rowCount > 0 && region.colCount > 0 && region.row + region.rowCount <= sheet.rowCount && region.col + region.colCount <= sheet.columnCount, '图片可见区域越界');
|
|
90
|
+
if (region.style) {
|
|
91
|
+
const style = normalizeObservedStyle(region.style);
|
|
92
|
+
assert(style.width === undefined && style.height === undefined, '图片区域尺寸由像素边界决定');
|
|
93
|
+
}
|
|
94
|
+
for (let r = region.row; r < region.row + region.rowCount; r++)
|
|
95
|
+
for (let c = region.col; c < region.col + region.colCount; c++) {
|
|
96
|
+
const key = `${r},${c}`;
|
|
97
|
+
assert(!occupied.has(key), '图片可见区域重叠');
|
|
98
|
+
occupied.add(key);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
assert(occupied.size === sheet.rowCount * sheet.columnCount, '图片区域分割不完整:空白区域也必须描述,不能遗漏合并和值区域');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** 像素边界是几何来源,不从字段数或默认两字段模板推定网格。 */
|
|
105
|
+
export function geometrySheet(source, selected, mergedAnchorBorders = false, preserveBorders = false) {
|
|
106
|
+
const sheet = clone(selected), geometry = source.imageStyles?.geometry;
|
|
107
|
+
if (!geometry)
|
|
108
|
+
return sheet;
|
|
109
|
+
validateImageGeometry(geometry, source);
|
|
110
|
+
const grid = geometry.sheets.find(s => s.sheet === sheet.name);
|
|
111
|
+
if (!grid)
|
|
112
|
+
return sheet;
|
|
113
|
+
assert(sheet.rows.every(r => r.visible) && sheet.columns.every(c => c.visible), '图片像素网格不接受隐藏行列');
|
|
114
|
+
sheet.columns = grid.xLines.slice(1).map((x, i) => ({ size: x - grid.xLines[i], visible: true }));
|
|
115
|
+
sheet.rows = grid.yLines.slice(1).map((y, i) => ({ size: y - grid.yLines[i], visible: true }));
|
|
116
|
+
sheet.spans = grid.regions.filter(r => r.rowCount > 1 || r.colCount > 1).map(({ row, col, rowCount, colCount }) => ({ row, col, rowCount, colCount }));
|
|
117
|
+
const cells = new Map(sheet.cells.map(c => [`${Number(c.row)},${Number(c.col)}`, c]));
|
|
118
|
+
const get = (r, c) => { const key = `${r},${c}`; if (!cells.has(key)) {
|
|
119
|
+
const cell = { row: String(r), col: String(c), style: {} };
|
|
120
|
+
sheet.cells.push(cell);
|
|
121
|
+
cells.set(key, cell);
|
|
122
|
+
} return cells.get(key); };
|
|
123
|
+
for (const region of grid.regions) {
|
|
124
|
+
const native = observedNative(region.style || {}, get(region.row, region.col).style || {});
|
|
125
|
+
if (preserveBorders) {
|
|
126
|
+
const anchor = get(region.row, region.col), anchorStyle = clone(anchor.style || {});
|
|
127
|
+
const perimeter = [];
|
|
128
|
+
for (let r = region.row; r < region.row + region.rowCount; r++)
|
|
129
|
+
for (let c = region.col; c < region.col + region.colCount; c++) {
|
|
130
|
+
const cell = get(r, c);
|
|
131
|
+
assert(cell === anchor || (!cell.formula && (cell.value === undefined || cell.value === '')), '图片合并区域覆盖了非锚点文字或公式,必须修正观察和映射');
|
|
132
|
+
for (const [key, edge] of [['borderTop', r === region.row], ['borderBottom', r === region.row + region.rowCount - 1], ['borderLeft', c === region.col], ['borderRight', c === region.col + region.colCount - 1]]) {
|
|
133
|
+
const border = native[key] || cell.style?.[key] || (key === 'borderRight' || key === 'borderBottom' ? anchorStyle[key] : undefined);
|
|
134
|
+
if (edge && border)
|
|
135
|
+
perimeter.push({ row: r, col: c, key, border: clone(border) });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (let r = region.row; r < region.row + region.rowCount; r++)
|
|
139
|
+
for (let c = region.col; c < region.col + region.colCount; c++) {
|
|
140
|
+
const cell = get(r, c);
|
|
141
|
+
cell.style ||= {};
|
|
142
|
+
for (const key of ['borderTop', 'borderBottom', 'borderLeft', 'borderRight'])
|
|
143
|
+
delete cell.style[key];
|
|
144
|
+
}
|
|
145
|
+
const { borderTop, borderBottom, borderLeft, borderRight, ...text } = native;
|
|
146
|
+
Object.assign(anchor.style, text);
|
|
147
|
+
for (const edge of perimeter)
|
|
148
|
+
get(edge.row, edge.col).style[edge.key] = edge.border;
|
|
149
|
+
if (mergedAnchorBorders)
|
|
150
|
+
for (const [key, count] of [['borderBottom', region.colCount], ['borderRight', region.rowCount]]) {
|
|
151
|
+
const edges = perimeter.filter(e => e.key === key);
|
|
152
|
+
if (edges.length === count && edges.every(e => stable(e.border) === stable(edges[0].border)))
|
|
153
|
+
anchor.style[key] = clone(edges[0].border);
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
for (let r = region.row; r < region.row + region.rowCount; r++)
|
|
158
|
+
for (let c = region.col; c < region.col + region.colCount; c++) {
|
|
159
|
+
const cell = get(r, c);
|
|
160
|
+
cell.style ||= {};
|
|
161
|
+
const anchor = r === region.row && c === region.col;
|
|
162
|
+
assert(anchor || (!cell.formula && (cell.value === undefined || cell.value === '')), '图片合并区域覆盖了非锚点文字或公式,必须修正观察和映射');
|
|
163
|
+
for (const key of ['borderTop', 'borderBottom', 'borderLeft', 'borderRight'])
|
|
164
|
+
delete cell.style[key];
|
|
165
|
+
if (anchor) {
|
|
166
|
+
const { borderTop, borderBottom, borderLeft, borderRight, ...text } = native;
|
|
167
|
+
Object.assign(cell.style, text);
|
|
168
|
+
if (mergedAnchorBorders && region.rowCount > 1 && borderBottom)
|
|
169
|
+
cell.style.borderBottom = clone(borderBottom);
|
|
170
|
+
if (mergedAnchorBorders && region.colCount > 1 && borderRight)
|
|
171
|
+
cell.style.borderRight = clone(borderRight);
|
|
172
|
+
}
|
|
173
|
+
for (const [key, edge] of [['borderTop', r === region.row], ['borderBottom', r === region.row + region.rowCount - 1], ['borderLeft', c === region.col], ['borderRight', c === region.col + region.colCount - 1]])
|
|
174
|
+
if (edge && native[key])
|
|
175
|
+
cell.style[key] = clone(native[key]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return sheet;
|
|
179
|
+
}
|
|
180
|
+
export function validateImageStyles(profile, source) {
|
|
181
|
+
safeTree(profile);
|
|
182
|
+
objectKeys(profile, ['schemaVersion', 'componentStyles', 'geometry', 'page', 'cells', 'fields', 'issues', 'notes', 'tableBorders'], '图片样式观察');
|
|
183
|
+
assert(source.sourceType === 'image' && profile.schemaVersion === 'e10.image-styles/v1', '样式观察只用于图片,版本必须为 e10.image-styles/v1');
|
|
184
|
+
if (profile.geometry)
|
|
185
|
+
validateImageGeometry(profile.geometry, source);
|
|
186
|
+
assert(profile.tableBorders === undefined || Array.isArray(profile.tableBorders) && profile.tableBorders.length <= 1000, '表格外框观察数量超限');
|
|
187
|
+
for (const table of profile.tableBorders || []) {
|
|
188
|
+
objectKeys(table, ['sheet', 'row', 'col', 'rowCount', 'colCount', 'top', 'right', 'bottom', 'left'], '表格外框观察');
|
|
189
|
+
const sheet = source.sheets.find(s => s.name === table.sheet);
|
|
190
|
+
assert(sheet && [table.row, table.col, table.rowCount, table.colCount].every(Number.isInteger) && table.row >= 0 && table.col >= 0 && table.rowCount > 0 && table.colCount > 0 && table.row + table.rowCount <= sheet.rowCount && table.col + table.colCount <= sheet.columnCount, '表格外框必须明确正文范围且位于源网格内');
|
|
191
|
+
for (const side of ['top', 'right', 'bottom', 'left']) {
|
|
192
|
+
assert(table[side] !== undefined, '表格外框必须逐侧记录 top/right/bottom/left,未知用 unknown,无边框用 none');
|
|
193
|
+
if (table[side] !== 'none' && table[side] !== 'unknown')
|
|
194
|
+
normalizeObservedStyle({ border: table[side] });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
assert(profile.componentStyles === undefined || ['merge', 'replace', 'workbookOnly'].includes(profile.componentStyles), '组件样式策略无效');
|
|
198
|
+
assert(Array.isArray(profile.issues) && profile.issues.length <= 1000 && profile.issues.every(s => typeof s === 'string' && s.length > 0), '图片样式 issues 无效');
|
|
199
|
+
assert(profile.notes === undefined || Array.isArray(profile.notes) && profile.notes.length <= 1000 && profile.notes.every(s => typeof s === 'string' && s.length > 0), '图片样式 notes 无效');
|
|
200
|
+
if (profile.page) {
|
|
201
|
+
objectKeys(profile.page, ['backgroundColor'], '页面样式');
|
|
202
|
+
imageColor(profile.page.backgroundColor);
|
|
203
|
+
}
|
|
204
|
+
assert(profile.cells === undefined || Array.isArray(profile.cells) && profile.cells.length <= 100000, '图片单元格样式数量超限');
|
|
205
|
+
const positions = new Set();
|
|
206
|
+
for (const c of profile.cells || []) {
|
|
207
|
+
objectKeys(c, ['sheet', 'row', 'col', 'style'], '图片单元格样式');
|
|
208
|
+
const sheet = source.sheets.find(s => s.name === c.sheet);
|
|
209
|
+
assert(sheet && Number.isInteger(c.row) && Number.isInteger(c.col) && c.row >= 0 && c.row < sheet.rowCount && c.col >= 0 && c.col < sheet.columnCount, '图片样式位置超出网格');
|
|
210
|
+
extent(sheet, { row: c.row, col: c.col });
|
|
211
|
+
const key = `${c.sheet}:${c.row},${c.col}`;
|
|
212
|
+
assert(!positions.has(key), '重复图片样式位置');
|
|
213
|
+
positions.add(key);
|
|
214
|
+
const style = normalizeObservedStyle(c.style);
|
|
215
|
+
assert(style.height === undefined && style.width === undefined, '网格高度和宽度应通过 rows/columns 描述');
|
|
216
|
+
}
|
|
217
|
+
assert(profile.fields === undefined || Array.isArray(profile.fields) && profile.fields.length <= 1000, '图片字段样式数量超限');
|
|
218
|
+
for (const f of profile.fields || []) {
|
|
219
|
+
objectKeys(f, ['fieldId', 'fieldName', 'subFormId', 'displayTitle', 'inputTarget', 'title', 'content', 'input', 'textArea'], '图片字段样式');
|
|
220
|
+
assert(typeof f.fieldId === 'string' && /^[1-9]\d*$/.test(f.fieldId) || typeof f.fieldName === 'string' && f.fieldName.length > 0, '图片字段样式需要精确字段身份');
|
|
221
|
+
assert(f.subFormId === undefined || typeof f.subFormId === 'string' && /^[1-9]\d*$/.test(f.subFormId), '图片字段样式明细身份无效');
|
|
222
|
+
assert(f.title || f.content || f.input || f.textArea || f.displayTitle, '图片字段样式不能为空');
|
|
223
|
+
if (f.displayTitle !== undefined)
|
|
224
|
+
assert(typeof f.fieldId === 'string' && typeof f.displayTitle === 'string' && f.displayTitle.trim() === f.displayTitle && f.displayTitle.length > 0 && f.displayTitle.length <= 200 && !/[<>\r\n]/.test(f.displayTitle), '展示标题必须绑定真实 fieldId,且为纯文本');
|
|
225
|
+
assert(f.inputTarget === undefined || !!f.input && ['component', 'cell'].includes(f.inputTarget), 'inputTarget 必须有 input 样式且为 component/cell');
|
|
226
|
+
if (f.inputTarget === 'cell')
|
|
227
|
+
assert(f.input.height === undefined && f.input.width === undefined, '单元格投影不接受输入框宽高;网格尺寸用 geometry 或 rows/columns');
|
|
228
|
+
assert(profile.componentStyles !== 'workbookOnly' || !f.input || f.inputTarget === 'cell', 'workbookOnly 下 input 必须显式选择 cell;不能静默丢弃控件样式');
|
|
229
|
+
if (f.textArea) {
|
|
230
|
+
objectKeys(f.textArea, ['rows', 'fitCell', 'autoHeight'], '多行文本展示');
|
|
231
|
+
assert(Number.isInteger(f.textArea.rows) && f.textArea.rows > 0 && f.textArea.rows <= 50, '多行文本 rows 必须为 1~50');
|
|
232
|
+
for (const key of ['fitCell', 'autoHeight'])
|
|
233
|
+
assert(f.textArea[key] === undefined || typeof f.textArea[key] === 'boolean', `多行文本 ${key} 必须为布尔值`);
|
|
234
|
+
}
|
|
235
|
+
for (const style of [f.title, f.content, f.input])
|
|
236
|
+
if (style)
|
|
237
|
+
normalizeObservedStyle(style);
|
|
238
|
+
for (const style of [f.title, f.content])
|
|
239
|
+
if (style)
|
|
240
|
+
assert(style.height === undefined && style.width === undefined, '标题/内容区域宽高使用 rows/columns;输入框尺寸使用 input');
|
|
241
|
+
if (f.input)
|
|
242
|
+
assert(!Object.keys(f.input).some(k => ['color', 'fontFamily', 'fontSize', 'fontUnit', 'fontWeight', 'fontStyle', 'textAlign', 'align', 'bold', 'verticalAlign', 'wordWrap', 'underline', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight'].includes(k)), 'input 仅描述文本框外观和统一 border;文字样式用 content,分边边框用单元格样式');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export function observedCss(input) {
|
|
246
|
+
const s = normalizeObservedStyle(input), css = {};
|
|
247
|
+
for (const [from, to] of [['color', 'color'], ['backgroundColor', 'background-color'], ['fontFamily', 'font-family'], ['fontStyle', 'font-style'], ['textAlign', 'text-align']])
|
|
248
|
+
if (s[from] !== undefined)
|
|
249
|
+
css[to] = s[from];
|
|
250
|
+
if (s.fontSize !== undefined)
|
|
251
|
+
css['font-size'] = `${Number((s.fontSize * (s.fontUnit === 'pt' ? 96 / 72 : 1)).toFixed(1))}px`;
|
|
252
|
+
if (s.fontWeight !== undefined)
|
|
253
|
+
css['font-weight'] = String(s.fontWeight === 'bold' ? 700 : s.fontWeight === 'normal' ? 400 : s.fontWeight);
|
|
254
|
+
for (const key of ['height', 'width'])
|
|
255
|
+
if (s[key] !== undefined)
|
|
256
|
+
css[key] = `${s[key]}px`;
|
|
257
|
+
if (s.border)
|
|
258
|
+
css.border = `${s.border.width}px ${s.border.line} ${s.border.color}`;
|
|
259
|
+
// 网格对齐/换行由 SpreadJS 承载;不向原生组件传未登记的 CSS 属性。
|
|
260
|
+
return css;
|
|
261
|
+
}
|
|
262
|
+
export function observedNative(input, existing = {}) {
|
|
263
|
+
const s = normalizeObservedStyle(input), native = {};
|
|
264
|
+
if (s.color !== undefined)
|
|
265
|
+
native.foreColor = s.color;
|
|
266
|
+
if (s.backgroundColor !== undefined)
|
|
267
|
+
native.backColor = s.backgroundColor;
|
|
268
|
+
const oldFont = nativeFont(existing.font);
|
|
269
|
+
const hasFont = [s.fontSize, s.fontFamily, s.fontWeight, s.fontStyle].some(v => v !== undefined);
|
|
270
|
+
const size = s.fontSize !== undefined ? Number((s.fontSize * (s.fontUnit === 'pt' ? 96 / 72 : 1)).toFixed(1)) : oldFont ? Number((oldFont.fontSize * (oldFont.fontUnit === 'pt' ? 96 / 72 : 1)).toFixed(1)) : undefined;
|
|
271
|
+
if (hasFont && size !== undefined) {
|
|
272
|
+
const weight = s.fontWeight ?? oldFont?.fontWeight ?? 400, fontStyle = s.fontStyle ?? oldFont?.fontStyle;
|
|
273
|
+
native.font = `${fontStyle === 'italic' ? 'italic ' : ''}${weight === 'bold' ? 700 : weight === 'normal' ? 400 : weight} ${size}px ${s.fontFamily || oldFont?.fontFamily || 'sans-serif'}`;
|
|
274
|
+
}
|
|
275
|
+
if (s.textAlign !== undefined)
|
|
276
|
+
native.hAlign = ['left', 'center', 'right'].indexOf(s.textAlign);
|
|
277
|
+
if (s.verticalAlign !== undefined)
|
|
278
|
+
native.vAlign = ['top', 'middle', 'bottom'].indexOf(s.verticalAlign);
|
|
279
|
+
if (s.wordWrap !== undefined)
|
|
280
|
+
native.wordWrap = s.wordWrap;
|
|
281
|
+
if (s.underline !== undefined)
|
|
282
|
+
native.textDecoration = s.underline ? 1 : 0;
|
|
283
|
+
for (const key of ['borderTop', 'borderBottom', 'borderLeft', 'borderRight']) {
|
|
284
|
+
const b = s[key] || s.border;
|
|
285
|
+
if (!b)
|
|
286
|
+
continue;
|
|
287
|
+
native[key] = { color: b.color, style: b.line === 'solid' ? ({ 1: 1, 2: 2, 3: 5 }[b.width]) : b.line === 'dashed' && b.width === 2 ? 8 : ({ dashed: 3, dotted: 4, double: 6 }[b.line]) };
|
|
288
|
+
}
|
|
289
|
+
return native;
|
|
290
|
+
}
|
|
291
|
+
/** 图像单元格键别名规范化,仅在 extract 图片入口使用,Excel 与旧产物不变。 */
|
|
292
|
+
export function normalizeImageCells(source) {
|
|
293
|
+
const copy = clone(source);
|
|
294
|
+
safeTree(copy);
|
|
295
|
+
copy.imageStyles ??= { schemaVersion: 'e10.image-styles/v1', issues: [] };
|
|
296
|
+
const initialRules = copy.imageStyles.cells || (copy.imageStyles.cells = []);
|
|
297
|
+
assert(Array.isArray(initialRules), '图片单元格样式必须为数组');
|
|
298
|
+
const ruleIndex = new Map(initialRules.map(r => [`${r.sheet}:${r.row},${r.col}`, r]));
|
|
299
|
+
for (const sheet of copy.sheets || [])
|
|
300
|
+
for (const cell of sheet.cells || []) {
|
|
301
|
+
const style = cell.style || {}, semantic = {};
|
|
302
|
+
for (const key of KEYS)
|
|
303
|
+
if (style[key] !== undefined && !['wordWrap', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight', 'underline'].includes(key)) {
|
|
304
|
+
semantic[key] = style[key];
|
|
305
|
+
delete style[key];
|
|
306
|
+
}
|
|
307
|
+
if (semantic.bold !== undefined || semantic.fontWeight !== undefined || semantic.fontStyle !== undefined || semantic.fontFamily !== undefined) {
|
|
308
|
+
const match = nativeFont(style.font);
|
|
309
|
+
semantic.fontSize ??= match?.fontSize;
|
|
310
|
+
semantic.fontFamily ??= match?.fontFamily;
|
|
311
|
+
if (semantic.fontSize !== undefined)
|
|
312
|
+
semantic.fontUnit ??= match?.fontUnit;
|
|
313
|
+
semantic.fontStyle ??= match?.fontStyle;
|
|
314
|
+
}
|
|
315
|
+
assert(semantic.height === undefined && semantic.width === undefined, '单元格宽高应通过 rows/columns 描述');
|
|
316
|
+
if (Object.keys(semantic).length) {
|
|
317
|
+
Object.assign(style, observedNative(semantic, style));
|
|
318
|
+
const rules = initialRules, key = `${sheet.name}:${Number(cell.row)},${Number(cell.col)}`;
|
|
319
|
+
const hit = ruleIndex.get(key);
|
|
320
|
+
if (hit)
|
|
321
|
+
hit.style = { ...semantic, ...hit.style };
|
|
322
|
+
else {
|
|
323
|
+
const added = { sheet: sheet.name, row: Number(cell.row), col: Number(cell.col), style: semantic };
|
|
324
|
+
rules.push(added);
|
|
325
|
+
ruleIndex.set(key, added);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const key of ['foreColor', 'backColor'])
|
|
329
|
+
if (style[key] !== undefined)
|
|
330
|
+
style[key] = imageColor(style[key]);
|
|
331
|
+
cell.style = style;
|
|
332
|
+
}
|
|
333
|
+
return copy;
|
|
334
|
+
}
|
|
335
|
+
export function prepareImageStyleSheet(source, selected, mergedAnchorBorders = false, preserveBorders = false) {
|
|
336
|
+
const sheet = geometrySheet(source, selected, mergedAnchorBorders, preserveBorders);
|
|
337
|
+
const cellIndex = new Map(sheet.cells.map(c => [`${Number(c.row)},${Number(c.col)}`, c]));
|
|
338
|
+
if (source.imageStyles)
|
|
339
|
+
for (const cell of sheet.cells) {
|
|
340
|
+
for (const key of ['foreColor', 'backColor'])
|
|
341
|
+
if (cell.style?.[key] !== undefined)
|
|
342
|
+
cell.style[key] = imageColor(cell.style[key]);
|
|
343
|
+
if (cell.style?.font)
|
|
344
|
+
cell.style.font = observedNative(nativeFont(cell.style.font)).font;
|
|
345
|
+
}
|
|
346
|
+
for (const rule of source.imageStyles?.cells || []) {
|
|
347
|
+
assert(rule.sheet === selected.name, '图片样式包含未选择的 Sheet,需要分别编译');
|
|
348
|
+
const key = `${rule.row},${rule.col}`;
|
|
349
|
+
const cell = cellIndex.get(key) || { row: String(rule.row), col: String(rule.col), style: {} };
|
|
350
|
+
cell.style = { ...(cell.style || {}), ...observedNative(rule.style, cell.style) };
|
|
351
|
+
if (!cellIndex.has(key)) {
|
|
352
|
+
sheet.cells.push(cell);
|
|
353
|
+
cellIndex.set(key, cell);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return sheet;
|
|
357
|
+
}
|
|
358
|
+
function nativeTextCss(style, effectiveText = false) {
|
|
359
|
+
const css = {};
|
|
360
|
+
if (style.foreColor !== undefined)
|
|
361
|
+
css.color = imageColor(style.foreColor);
|
|
362
|
+
if (effectiveText && style.backColor !== undefined)
|
|
363
|
+
css['background-color'] = imageColor(style.backColor);
|
|
364
|
+
const font = nativeFont(style.font);
|
|
365
|
+
if (font)
|
|
366
|
+
Object.assign(css, observedCss(font));
|
|
367
|
+
if (style.hAlign !== undefined && style.hAlign <= 2)
|
|
368
|
+
css['text-align'] = ['left', 'center', 'right'][style.hAlign];
|
|
369
|
+
if (effectiveText && style.vAlign !== undefined && style.vAlign <= 2)
|
|
370
|
+
css['align-items'] = ['flex-start', 'center', 'flex-end'][style.vAlign];
|
|
371
|
+
return css;
|
|
372
|
+
}
|
|
373
|
+
export function projectImageField(source, binding, title, content, projection, used, sheetName, effectiveDisplay = false, effectiveText = false) {
|
|
374
|
+
if (!source.imageStyles)
|
|
375
|
+
return;
|
|
376
|
+
const f = binding.field, sub = String(f.subFormId || '');
|
|
377
|
+
const rules = (source.imageStyles.fields || []).filter(r => (r.fieldId ? r.fieldId === f.fieldId : r.fieldName === String(f.title || f.fieldName)) && (r.subFormId ? r.subFormId === sub : ['', '0', '-1'].includes(sub)));
|
|
378
|
+
assert(rules.length <= 1, `重复图片字段样式 ${f.fieldId}`);
|
|
379
|
+
const rule = rules[0];
|
|
380
|
+
if (rule) {
|
|
381
|
+
if (rule.fieldName)
|
|
382
|
+
assert(rule.fieldName === String(f.title || f.fieldName), '图片样式字段名与 ID 不一致');
|
|
383
|
+
used.add(rule);
|
|
384
|
+
}
|
|
385
|
+
if (rule?.displayTitle !== undefined) {
|
|
386
|
+
assert(binding.value.config, '字段缺少原生展示配置');
|
|
387
|
+
assert(binding.value.config.displayName === undefined || binding.value.config.displayName && typeof binding.value.config.displayName === 'object' && !Array.isArray(binding.value.config.displayName), '原生展示标题形状无效');
|
|
388
|
+
const properties = { enableDisplayName: true, displayName: { ...(binding.value.config.displayName || {}), nameAlias: rule.displayTitle } };
|
|
389
|
+
Object.assign(binding.value.config, properties);
|
|
390
|
+
projection.entries.push({ layer: 'componentConfig', componentId: String(binding.value.id), source: `field:${f.fieldId}.displayTitle`, properties: clone(properties) });
|
|
391
|
+
}
|
|
392
|
+
if (rule?.textArea) {
|
|
393
|
+
assert(binding.value.type === 'TextArea' && binding.value.config?.richEditor !== true, 'textArea 展示只支持普通 TextArea,不转换字段或富文本');
|
|
394
|
+
const properties = effectiveDisplay && rule.textArea.fitCell === true ? {} : { height: rule.textArea.rows };
|
|
395
|
+
if (effectiveDisplay && rule.textArea.fitCell === true)
|
|
396
|
+
(projection.notes ||= []).push(`字段 ${f.fieldId} 使用 fullHeight 撑满单元格;rows=${rule.textArea.rows} 不作为独立显示高度写入`);
|
|
397
|
+
if (rule.textArea.fitCell !== undefined)
|
|
398
|
+
properties.fullHeight = rule.textArea.fitCell;
|
|
399
|
+
if (rule.textArea.autoHeight !== undefined)
|
|
400
|
+
properties.disableAutoHeight = !rule.textArea.autoHeight;
|
|
401
|
+
Object.assign(binding.value.config, properties);
|
|
402
|
+
projection.entries.push({ layer: 'componentConfig', componentId: String(binding.value.id), source: `field:${f.fieldId}.textArea`, properties });
|
|
403
|
+
}
|
|
404
|
+
for (const [cell, style] of [[title, rule?.title], [content, rule?.content]])
|
|
405
|
+
if (style)
|
|
406
|
+
cell.style = { ...(cell.style || {}), ...observedNative(style, cell.style) };
|
|
407
|
+
if (rule?.input && rule.inputTarget === 'cell')
|
|
408
|
+
content.style = { ...(content.style || {}), ...observedNative(rule.input, content.style) };
|
|
409
|
+
if (source.imageStyles.componentStyles === 'replace' || source.imageStyles.componentStyles === 'workbookOnly') {
|
|
410
|
+
delete binding.title.config.styles;
|
|
411
|
+
delete binding.value.config.styles;
|
|
412
|
+
}
|
|
413
|
+
if (source.imageStyles.componentStyles === 'workbookOnly')
|
|
414
|
+
return;
|
|
415
|
+
const cellCss = (cell) => { const r = source.imageStyles?.cells?.find(r => r.row === Number(cell.row) && r.col === Number(cell.col)); return r ? observedCss(r.style) : {}; };
|
|
416
|
+
const originalCss = (cell) => {
|
|
417
|
+
const candidates = source.sheets.filter(s => s.name === sheetName).flatMap(s => s.cells).filter(c => Number(c.row) === Number(cell.row) && Number(c.col) === Number(cell.col));
|
|
418
|
+
return candidates.length === 1 ? nativeTextCss(candidates[0].style || {}) : {};
|
|
419
|
+
};
|
|
420
|
+
// CSS 只投影实际观察属性;SpreadJS 字体缺省值不能冒充图片已识别的字体属性。
|
|
421
|
+
const groups = { title: { ...originalCss(title), ...cellCss(title), ...(rule?.title ? observedCss(rule.title) : {}) }, content: { ...originalCss(content), ...cellCss(content), ...(rule?.content ? observedCss(rule.content) : {}) } };
|
|
422
|
+
if (effectiveText) {
|
|
423
|
+
Object.assign(groups.title, nativeTextCss(title.style || {}, true));
|
|
424
|
+
Object.assign(groups.content, nativeTextCss(content.style || {}, true));
|
|
425
|
+
}
|
|
426
|
+
if (rule?.input && rule.inputTarget !== 'cell')
|
|
427
|
+
groups.inputStyle = observedCss(rule.input);
|
|
428
|
+
for (const key of Object.keys(groups))
|
|
429
|
+
if (!Object.keys(groups[key]).length)
|
|
430
|
+
delete groups[key];
|
|
431
|
+
if (!Object.keys(groups).length)
|
|
432
|
+
return;
|
|
433
|
+
const comp = binding.value, categories = Array.isArray(comp.applyStyle) ? comp.applyStyle.map(String) : String(comp.applyStyle || '').split(',').filter(Boolean);
|
|
434
|
+
if (categories.length !== 1 || !comp.styleCategoryCompKey || !comp.config) {
|
|
435
|
+
projection.issues.push(`字段 ${f.fieldId} 缺少唯一原生样式分类,组件样式未投影`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const auditedCategories = ['InputStyleConfig', 'TextAreaStyleConfig', 'UploadStyleConfig', 'OptionStyleConfig', 'InputOptionStyleConfig', 'ImageOptionStyleConfig', 'DateStyleConfig', 'DateInputStyleConfig', 'RelateStyleConfig', 'OtherStyleConfig1', 'OtherStyleConfig2', 'BaseFieldStyleConfig'];
|
|
439
|
+
if (!auditedCategories.includes(comp.styleCategoryCompKey) || comp.styleCategoryPackage && comp.styleCategoryPackage !== '@weapp/formbuilder') {
|
|
440
|
+
projection.issues.push(`字段 ${f.fieldId} 的样式分类 ${comp.styleCategoryCompKey} 尚未核对原生渲染契约`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (groups.inputStyle && !['InputStyleConfig', 'DateInputStyleConfig', 'TextAreaStyleConfig', 'InputOptionStyleConfig'].includes(comp.styleCategoryCompKey)) {
|
|
444
|
+
projection.issues.push(`字段 ${f.fieldId} 的原生分类不支持 inputStyle`);
|
|
445
|
+
delete groups.inputStyle;
|
|
446
|
+
}
|
|
447
|
+
if (groups.inputStyle?.height && comp.styleCategoryCompKey === 'TextAreaStyleConfig') {
|
|
448
|
+
projection.issues.push(`字段 ${f.fieldId} 的 TextArea 高度需专门原生契约,未写 inputStyle.height`);
|
|
449
|
+
delete groups.inputStyle.height;
|
|
450
|
+
}
|
|
451
|
+
assert(comp.config.styles === undefined || Array.isArray(comp.config.styles), '原生组件 styles 形状无效');
|
|
452
|
+
const styles = comp.config.styles || (comp.config.styles = []), category = categories[0];
|
|
453
|
+
const matches = styles.filter((s) => String(s.category) === category);
|
|
454
|
+
assert(matches.length <= 1, '原生样式分类重复');
|
|
455
|
+
const row = matches[0] || { category, useCustom: true, customStyle: {} };
|
|
456
|
+
row.useCustom = true;
|
|
457
|
+
row.customStyle ||= {};
|
|
458
|
+
if (Array.isArray(comp.applyStyle))
|
|
459
|
+
row.category = comp.applyStyle[0];
|
|
460
|
+
for (const [group, properties] of Object.entries(groups)) {
|
|
461
|
+
row.customStyle[group] = { ...(row.customStyle[group] || {}), ...properties };
|
|
462
|
+
projection.entries.push({ layer: 'component', componentId: String(comp.id), category, group, source: `field:${f.fieldId}.${group}`, properties: clone(properties) });
|
|
463
|
+
}
|
|
464
|
+
if (!matches.length)
|
|
465
|
+
styles.push(row);
|
|
466
|
+
}
|
|
467
|
+
export function projectImagePage(source, layout, projection) {
|
|
468
|
+
if (!source.imageStyles?.page)
|
|
469
|
+
return;
|
|
470
|
+
const parse = (v, label) => {
|
|
471
|
+
const value = typeof v === 'string' ? (v.trim() ? JSON.parse(v) : {}) : clone(v || {});
|
|
472
|
+
assert(value && typeof value === 'object' && !Array.isArray(value), `${label} 必须为 JSON 对象`);
|
|
473
|
+
safeTree(value);
|
|
474
|
+
return value;
|
|
475
|
+
};
|
|
476
|
+
const style = parse(layout.formStyle, 'formStyle'), config = parse(style.styleConfig, 'styleConfig');
|
|
477
|
+
config.formLayout ||= {};
|
|
478
|
+
config.formLayout.background ||= {};
|
|
479
|
+
assert(typeof config.formLayout === 'object' && !Array.isArray(config.formLayout) && typeof config.formLayout.background === 'object' && !Array.isArray(config.formLayout.background), 'formLayout/background 必须为 JSON 对象');
|
|
480
|
+
const properties = { 'background-color': imageColor(source.imageStyles.page.backgroundColor) };
|
|
481
|
+
Object.assign(config.formLayout.background, properties);
|
|
482
|
+
// 原生 FormStyle.styleConfig 是 Java String;内部修改用对象,保存必须编码回 JSON 字符串。
|
|
483
|
+
style.styleConfig = JSON.stringify(config);
|
|
484
|
+
layout.formStyle = style;
|
|
485
|
+
projection.entries.push({ layer: 'page', source: 'page.backgroundColor', group: 'background', properties });
|
|
486
|
+
}
|
|
487
|
+
export function canonicalImageFormStyle(value) {
|
|
488
|
+
if (value === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
const style = typeof value === 'string' ? JSON.parse(value) : clone(value);
|
|
491
|
+
if (style && typeof style.styleConfig === 'string')
|
|
492
|
+
style.styleConfig = JSON.parse(style.styleConfig);
|
|
493
|
+
safeTree(style);
|
|
494
|
+
return style;
|
|
495
|
+
}
|
|
496
|
+
export function imageStyleReport(projection, pages, formStyle) {
|
|
497
|
+
const scope = { verificationScope: 'OBSERVATION_TO_NATIVE_READBACK', sourceImageCompared: false };
|
|
498
|
+
if (!projection)
|
|
499
|
+
return { ...scope, projectionVerified: false, status: 'NOT_OBSERVED', comparedProperties: 0, matchedProperties: 0, differences: [], issues: [] };
|
|
500
|
+
let comparedProperties = 0, matchedProperties = 0;
|
|
501
|
+
const differences = [];
|
|
502
|
+
const parse = (v) => { try {
|
|
503
|
+
return typeof v === 'string' ? JSON.parse(v) : v || {};
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return {};
|
|
507
|
+
} };
|
|
508
|
+
const comps = [];
|
|
509
|
+
const flatten = (list) => { for (const c of list) {
|
|
510
|
+
comps.push(c);
|
|
511
|
+
flatten(c.children || []);
|
|
512
|
+
flatten(c.config?.children || []);
|
|
513
|
+
} };
|
|
514
|
+
pages?.forEach(p => flatten(p.comps || []));
|
|
515
|
+
for (const entry of projection.entries) {
|
|
516
|
+
let actual = {};
|
|
517
|
+
if (pages) {
|
|
518
|
+
if (entry.layer === 'page')
|
|
519
|
+
actual = parse(parse(formStyle).styleConfig).formLayout?.[entry.group] || {};
|
|
520
|
+
else if (entry.layer === 'componentConfig')
|
|
521
|
+
actual = comps.find(c => String(c.id) === entry.componentId)?.config || {};
|
|
522
|
+
else if (entry.layer === 'component')
|
|
523
|
+
actual = comps.find(c => String(c.id) === entry.componentId)?.config?.styles?.find((s) => String(s.category) === entry.category)?.customStyle?.[entry.group] || {};
|
|
524
|
+
else
|
|
525
|
+
actual = pages.flatMap(p => p.config?.workbook?.[entry.sheet]?.cells || []).find(c => Number(c.row) === entry.row && Number(c.col) === entry.col)?.style || {};
|
|
526
|
+
}
|
|
527
|
+
for (const [property, expected] of Object.entries(entry.properties)) {
|
|
528
|
+
comparedProperties++;
|
|
529
|
+
if (!pages)
|
|
530
|
+
continue;
|
|
531
|
+
if (stable(expected) === stable(actual[property]))
|
|
532
|
+
matchedProperties++;
|
|
533
|
+
else if (differences.length < 100)
|
|
534
|
+
differences.push({ source: entry.source, layer: entry.layer, property, expected, actual: actual[property] ?? null });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return { ...scope, projectionVerified: !!pages && !projection.issues.length && !differences.length && comparedProperties > 0, status: projection.issues.length ? 'INCOMPLETE' : !pages ? 'COMPILED' : differences.length ? 'MISMATCH' : comparedProperties > 0 ? 'VERIFIED' : 'INCOMPLETE', comparedProperties, matchedProperties, differences, issues: clone(projection.issues), ...(projection.notes ? { notes: clone(projection.notes) } : {}) };
|
|
538
|
+
}
|
|
539
|
+
//# sourceMappingURL=image-style.js.map
|