cardbuddy 0.11.0 → 0.12.1
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 +46 -5
- package/dist/cli.js +51 -1
- package/dist/config.js +51 -16
- package/dist/core.js +2 -2
- package/dist/init.js +425 -0
- package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
- package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +8 -0
- package/dist/vendored/mcp/client.mjs +537 -0
- package/dist/vendored/mcp/tools.mjs +268 -0
- package/dist/vendored/preview/assets/default-image.png +0 -0
- package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
- package/dist/{server-core → vendored/server/core}/browser.js +1 -1
- package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
- package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
- package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
- package/dist/{server-core → vendored/server/core}/index.js +5 -3
- package/dist/vendored/server/core/skillPaths.js +124 -0
- package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
- package/dist/vendored/skills/README.md +93 -0
- package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
- package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
- package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
- package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
- package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
- package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
- package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
- package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
- package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
- package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
- package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
- package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
- package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
- package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
- package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
- package/dist/vendored/skills/shared/README.md +55 -0
- package/dist/vendored/skills/shared/auth-model.md +79 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
- package/dist/vendored/templates/helloCard/data.json +10 -0
- package/dist/vendored/templates/helloCard/schema.json +238 -0
- package/dist/vendored/tools/link-skills.mjs +485 -0
- package/package.json +2 -1
- package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
- package/dist/vendored/App.vue +0 -2858
- package/dist/vendored/ansi.js +0 -185
- package/dist/vendored/assets/apple-touch-icon.png +0 -0
- package/dist/vendored/assets/default-image.png +0 -0
- package/dist/vendored/assets/favicon-16.png +0 -0
- package/dist/vendored/assets/favicon-32.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svgIcons/icons.svg +0 -87
- package/dist/vendored/assets/svgIcons/index.js +0 -39
- package/dist/vendored/components/AiSettings.vue +0 -392
- package/dist/vendored/components/AlertCardInstance.vue +0 -58
- package/dist/vendored/components/AlertCardLayer.vue +0 -59
- package/dist/vendored/components/ApiCallPanel.vue +0 -127
- package/dist/vendored/components/ApiPayloadTree.vue +0 -201
- package/dist/vendored/components/AppToast.vue +0 -37
- package/dist/vendored/components/CardGallery.vue +0 -291
- package/dist/vendored/components/CardPreview.vue +0 -72
- package/dist/vendored/components/CardSelect.vue +0 -206
- package/dist/vendored/components/ChatComposer.vue +0 -413
- package/dist/vendored/components/ChatMessage.vue +0 -383
- package/dist/vendored/components/ChatPanel.vue +0 -937
- package/dist/vendored/components/ComponentPalette.vue +0 -301
- package/dist/vendored/components/ConfirmDialog.vue +0 -54
- package/dist/vendored/components/DatePickerOverlay.vue +0 -345
- package/dist/vendored/components/DebugPanel.vue +0 -1734
- package/dist/vendored/components/DeviceOverlay.vue +0 -70
- package/dist/vendored/components/Dropdown.vue +0 -193
- package/dist/vendored/components/EventEditor.vue +0 -273
- package/dist/vendored/components/HoverGuides.vue +0 -122
- package/dist/vendored/components/JsonTree.vue +0 -547
- package/dist/vendored/components/LogValue.vue +0 -52
- package/dist/vendored/components/MarkdownBody.vue +0 -64
- package/dist/vendored/components/NewCardDialog.vue +0 -132
- package/dist/vendored/components/NodeInspector.vue +0 -295
- package/dist/vendored/components/NumberStepper.vue +0 -125
- package/dist/vendored/components/PickerOverlay.vue +0 -131
- package/dist/vendored/components/PropControl.vue +0 -245
- package/dist/vendored/components/PropTree.vue +0 -128
- package/dist/vendored/components/RemotePreview.vue +0 -173
- package/dist/vendored/components/SelectionOverlay.vue +0 -105
- package/dist/vendored/components/StructureTree.vue +0 -1002
- package/dist/vendored/components/Switch.vue +0 -38
- package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
- package/dist/vendored/components/setters/SetBgSize.vue +0 -90
- package/dist/vendored/components/setters/SetCode.vue +0 -198
- package/dist/vendored/components/setters/SetColor.vue +0 -244
- package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
- package/dist/vendored/components/setters/SetExpr.vue +0 -107
- package/dist/vendored/components/setters/SetGradient.vue +0 -125
- package/dist/vendored/components/setters/SetGrid9.vue +0 -68
- package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
- package/dist/vendored/components/setters/SetIfForm.vue +0 -61
- package/dist/vendored/components/setters/SetImage.vue +0 -43
- package/dist/vendored/components/setters/SetList.vue +0 -139
- package/dist/vendored/components/setters/SetNumber.vue +0 -118
- package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
- package/dist/vendored/components/setters/SetPadding.vue +0 -77
- package/dist/vendored/components/setters/SetSegment.vue +0 -160
- package/dist/vendored/components/setters/SetSelect.vue +0 -51
- package/dist/vendored/components/setters/SetSwitch.vue +0 -49
- package/dist/vendored/components/setters/SetText.vue +0 -52
- package/dist/vendored/components/setters/props.js +0 -23
- package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
- package/dist/vendored/fidelity/calibration.json +0 -29
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
- package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
- package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
- package/dist/vendored/index.html +0 -16
- package/dist/vendored/main.js +0 -37
- package/dist/vendored/runtime/ai-api.js +0 -165
- package/dist/vendored/runtime/alert-card.js +0 -125
- package/dist/vendored/runtime/app-toast.js +0 -33
- package/dist/vendored/runtime/clipboard-node.js +0 -78
- package/dist/vendored/runtime/clipboard.js +0 -29
- package/dist/vendored/runtime/event-model.js +0 -300
- package/dist/vendored/runtime/hoverGuide.js +0 -31
- package/dist/vendored/runtime/jsapi-mock.js +0 -256
- package/dist/vendored/runtime/markdown.js +0 -140
- package/dist/vendored/runtime/node-model.js +0 -217
- package/dist/vendored/skills/.manifest.json +0 -1101
- package/dist/vendored/standalone-main.js +0 -36
- package/dist/vendored/styles/ai-chat.css +0 -1850
- package/dist/vendored/styles/app.css +0 -3648
- package/dist/vendored/styles/card-gallery.css +0 -285
- package/dist/vendored/styles/card-select.css +0 -288
- package/dist/vendored/styles/components.css +0 -383
- package/dist/vendored/styles/dropdown.css +0 -324
- package/dist/vendored/styles/json-tree.css +0 -308
- package/dist/vendored/styles/node-inspector.css +0 -800
- package/dist/vendored/styles/setters.css +0 -1050
- package/dist/vendored/styles/standalone.css +0 -27
- package/dist/vendored/styles/structure-tree.css +0 -460
- /package/dist/vendored/{api.js → preview/api.js} +0 -0
- /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
- /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
- /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
- /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
- /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
- /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
- /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
- /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
- /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
- /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
- /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
- /package/dist/vendored/{components → preview/components}/base.js +0 -0
- /package/dist/vendored/{components → preview/components}/icons.js +0 -0
- /package/dist/vendored/{components → preview/components}/index.js +0 -0
- /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
- /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
- /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
- /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
- /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
- /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
- /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
- /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
- /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
- /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
- /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
- /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
- /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
- /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
- /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
|
@@ -1,1734 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
调试面板:三个标签页。
|
|
3
|
-
- 变量:schema.dictionary.inputParams 的增 / 删 / 改(变量名 · 中文名 · 类型 · 默认值 · 描述),
|
|
4
|
-
即卡片对外声明的入参;默认值同时是设计期 mock 基线,落到 rt.rootScope 供画布预览。
|
|
5
|
-
底部「配置变量 mock」进入二级视图,以 Raw JSON 直接编辑整份设计期数据。
|
|
6
|
-
- 组件结构:当前卡片的组件层级树(参考编辑器「结构」面板)
|
|
7
|
-
- 卡片来源:导入来源元数据
|
|
8
|
-
|
|
9
|
-
注:「接口调用」tab 已拆出为右侧常驻独立面板(ApiCallPanel.vue),
|
|
10
|
-
由 App 顶栏按钮控制展开/收起。
|
|
11
|
-
|
|
12
|
-
运行态(rt.state)已从标签页移出:底部状态栏新增「运行态」指示器,点击在面板内
|
|
13
|
-
弹出可拖拽高度的只读面板,默认收起。rt.state 是运行期交互态(Input 输入 /
|
|
14
|
-
RadioButton·CheckGroup 选择 / globalSet 等只写这里),不污染 rootScope、不触发保存
|
|
15
|
-
高亮,事件代码里 `this.data.xxx` 也读这里,调试变更永不落盘。
|
|
16
|
-
|
|
17
|
-
注:「变量绑定」已移至底部状态栏(类 VSCode 面板),点击状态栏指示器在
|
|
18
|
-
底部弹出面板查看,不再作为此处的标签页。
|
|
19
|
-
-->
|
|
20
|
-
<template>
|
|
21
|
-
<aside class="debug">
|
|
22
|
-
<div class="debug__tabs">
|
|
23
|
-
<button
|
|
24
|
-
v-for="t in tabs"
|
|
25
|
-
:key="t.key"
|
|
26
|
-
:class="['debug__tab', { 'debug__tab--active': activeTab === t.key }]"
|
|
27
|
-
@click="$emit('update:activeTab', t.key)"
|
|
28
|
-
>
|
|
29
|
-
{{ t.label }}
|
|
30
|
-
</button>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div class="debug__body">
|
|
34
|
-
<template v-if="activeTab === 'vars'">
|
|
35
|
-
<!-- 变量列表视图:管理 schema.dictionary.inputParams(变量属性的增 / 删 / 改) -->
|
|
36
|
-
<div v-if="varsMode === 'list'" class="vars">
|
|
37
|
-
<!-- 空态占位:与卡片来源 / API 调用面板同款语言 -->
|
|
38
|
-
<div v-if="!inputParams.length" class="empty-state">
|
|
39
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
40
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-settings" /></svg>
|
|
41
|
-
</span>
|
|
42
|
-
<p class="debug__empty">暂无变量</p>
|
|
43
|
-
<p class="empty-state__hint">变量与 schema.dictionary.inputParams 一一对应:绑定表达式以 $key 引用,mock 值供设计期预览</p>
|
|
44
|
-
</div>
|
|
45
|
-
<ul v-else class="vars__list">
|
|
46
|
-
<li v-for="(p, i) in inputParams" :key="p.key || i" class="vars__row" @dblclick="openVarDlg(i)">
|
|
47
|
-
<span class="vars__key">{{ p.key }}</span>
|
|
48
|
-
<span class="vars__type" :class="'vars__type--' + (p.type || 'string')">{{ p.type || 'string' }}</span>
|
|
49
|
-
<span v-if="hasMock(p)" class="vars__mock" title="已配置 mock(dictionary.mocks,设计期覆盖默认值)">m</span>
|
|
50
|
-
<span class="vars__name" :title="[p.name, p.desc].filter(Boolean).join(' · ')">{{ p.name || p.desc || '' }}</span>
|
|
51
|
-
<!-- 操作区双击不冒泡到行:连点复制/删除/编辑钮时不误开编辑弹窗 -->
|
|
52
|
-
<span class="vars__ops" @dblclick.stop>
|
|
53
|
-
<!-- 复制变量名:复制当前变量 key(绑定以 $key 引用),无变量名时禁用 -->
|
|
54
|
-
<button
|
|
55
|
-
class="vars__op"
|
|
56
|
-
type="button"
|
|
57
|
-
:disabled="!p.key"
|
|
58
|
-
:title="copiedVar === i ? '已复制 ✓' : p.key ? '复制变量名' : '暂无变量名'"
|
|
59
|
-
@click="copyVarName(p, i)"
|
|
60
|
-
>
|
|
61
|
-
<span v-if="copiedVar === i" class="debug__copy-ok">✓</span>
|
|
62
|
-
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-copy-sm" /></svg>
|
|
63
|
-
</button>
|
|
64
|
-
<button class="vars__op" type="button" title="编辑变量" @click="openVarDlg(i)">
|
|
65
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-gear" /></svg>
|
|
66
|
-
</button>
|
|
67
|
-
<button class="vars__op vars__op--del" type="button" title="删除变量" @click="askDeleteVar(i)">
|
|
68
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-trash" /></svg>
|
|
69
|
-
</button>
|
|
70
|
-
</span>
|
|
71
|
-
</li>
|
|
72
|
-
</ul>
|
|
73
|
-
<!-- 右下角悬浮操作组:与 mock 视图悬浮条同语言,不占固定行高 -->
|
|
74
|
-
<div class="vars__fabs">
|
|
75
|
-
<button class="debug__copy" type="button" title="新增变量(写入 dictionary.inputParams)" @click="openVarDlg(null)">+ 添加变量</button>
|
|
76
|
-
<button v-if="inputParams.length" class="debug__copy" type="button" title="以 Raw JSON 编辑全部变量的设计期 mock 数据" @click="varsMode = 'mock'">配置变量 mock</button>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
<!-- mock 二级视图:仅 Raw JSON(CodeJar + Prism 高亮);右下角操作按钮只在此出现 -->
|
|
80
|
-
<div v-else class="debug__scope-wrap debug__scope-wrap--raw">
|
|
81
|
-
<div class="vars__mockhead">
|
|
82
|
-
<button class="vars__back" type="button" title="返回变量列表" @click="leaveMock">
|
|
83
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6" /></svg>
|
|
84
|
-
变量
|
|
85
|
-
</button>
|
|
86
|
-
<span class="vars__mocktitle">变量 mock · 设计期覆盖(dictionary.mocks · Raw JSON)</span>
|
|
87
|
-
<span class="vars__mockhint">仅设计期生效 · 未列出的键回退默认值</span>
|
|
88
|
-
</div>
|
|
89
|
-
<div class="debug__scope-raw">
|
|
90
|
-
<!-- 报错提示条:与顶部提示条同语言(语义色软底 + 图标 + 关闭),钉在编辑器顶部不挤占编辑区 -->
|
|
91
|
-
<div v-if="rawError" class="raw-err-bar" role="alert">
|
|
92
|
-
<svg class="raw-err-bar__icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-alert-triangle" /></svg>
|
|
93
|
-
<span class="raw-err-bar__msg">{{ rawError }}</span>
|
|
94
|
-
<button class="raw-err-bar__close" type="button" title="关闭" @click="rawError = ''">✕</button>
|
|
95
|
-
</div>
|
|
96
|
-
<div
|
|
97
|
-
ref="rawEditor"
|
|
98
|
-
class="debug__raw-editor"
|
|
99
|
-
spellcheck="false"
|
|
100
|
-
></div>
|
|
101
|
-
</div>
|
|
102
|
-
<div class="debug__scope-foot">
|
|
103
|
-
<!-- 右下角悬浮组内的返回:与头部 chip 同一 leaveMock 语义,手不离编辑区即可退出二级视图 -->
|
|
104
|
-
<button class="debug__copy" type="button" title="返回变量列表" @click="leaveMock">
|
|
105
|
-
<svg class="debug__copy-icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6" /></svg>返回
|
|
106
|
-
</button>
|
|
107
|
-
<span class="debug__sep"></span>
|
|
108
|
-
<span v-if="copied" class="debug__copied">已复制 ✓</span>
|
|
109
|
-
<button class="debug__copy" type="button" @click="copyScope">
|
|
110
|
-
<svg class="debug__copy-icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-copy-sm" /></svg>复制
|
|
111
|
-
</button>
|
|
112
|
-
<span class="debug__sep"></span>
|
|
113
|
-
<button class="debug__copy debug__copy--primary" type="button" @click="saveRaw">
|
|
114
|
-
<svg class="debug__copy-icn" viewBox="0 0 16 16" aria-hidden="true"><use href="#icon-check" /></svg>应用
|
|
115
|
-
</button>
|
|
116
|
-
<button class="debug__copy debug__copy--warn" type="button" @click="$emit('reset-import')" title="丢弃内存编辑,从磁盘重新载入(导入优先于编辑)">
|
|
117
|
-
<svg class="debug__copy-icn" viewBox="0 0 16 16" aria-hidden="true"><use href="#icon-reset" /></svg>重置
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</template>
|
|
122
|
-
|
|
123
|
-
<template v-else-if="activeTab === 'structure'">
|
|
124
|
-
<StructureTree :preview="preview" :schema="schema" :card-code="cardId" :selected-node-id="selectedNodeId" :move-collapse-token="moveCollapseToken" :can-paste="canPaste" @edit-event="onEditEvent" @inspect="onInspect" @select-node="onSelectNode" @add-node="onAddNode" @move-node="onMoveNode" @hover-node="onHoverNode" @copy-selected="$emit('copy-selected')" @paste="$emit('paste')" @duplicate="$emit('duplicate')" @delete-selected="$emit('delete-selected')" />
|
|
125
|
-
</template>
|
|
126
|
-
|
|
127
|
-
<template v-else-if="activeTab === 'meta'">
|
|
128
|
-
<div class="debug__meta">
|
|
129
|
-
<!-- 空状态占位:与 API 调用面板同款(虚线徽章 + 线性图标 + 文案 + 弱提示) -->
|
|
130
|
-
<div v-if="!importMeta" class="empty-state">
|
|
131
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
132
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-cloud" /></svg>
|
|
133
|
-
</span>
|
|
134
|
-
<p class="debug__empty">暂无卡片来源</p>
|
|
135
|
-
<p class="empty-state__hint">该卡尚未经「同步远程」导入,无导入来源元数据;本地新建卡 / 未同步卡均如此</p>
|
|
136
|
-
</div>
|
|
137
|
-
<dl v-else class="debug__meta-list">
|
|
138
|
-
<div class="debug__meta-row">
|
|
139
|
-
<dt>导入接口</dt>
|
|
140
|
-
<dd><code>{{ importMeta.interface }}</code> <span class="debug__meta-muted">({{ importMeta.source }})</span></dd>
|
|
141
|
-
</div>
|
|
142
|
-
<div class="debug__meta-row">
|
|
143
|
-
<dt>远端基址</dt>
|
|
144
|
-
<dd><code>{{ importMeta.baseUrl }}</code></dd>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="debug__meta-row">
|
|
147
|
-
<dt>cardId</dt>
|
|
148
|
-
<dd><code>{{ importMeta.cardId }}</code></dd>
|
|
149
|
-
</div>
|
|
150
|
-
<div class="debug__meta-row">
|
|
151
|
-
<dt>cardCode</dt>
|
|
152
|
-
<dd><code>{{ importMeta.cardCode }}</code></dd>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="debug__meta-row" v-if="importMeta.cardName">
|
|
155
|
-
<dt>卡片标题</dt>
|
|
156
|
-
<dd>{{ importMeta.cardName }}</dd>
|
|
157
|
-
</div>
|
|
158
|
-
<div class="debug__meta-row">
|
|
159
|
-
<dt>应用 / 空间</dt>
|
|
160
|
-
<dd><code>{{ importMeta.appId }}</code> / <code>{{ importMeta.workspaceId }}</code></dd>
|
|
161
|
-
</div>
|
|
162
|
-
<div class="debug__meta-row">
|
|
163
|
-
<dt>同步时间</dt>
|
|
164
|
-
<dd>{{ formatTime(importMeta.syncedAt) }}</dd>
|
|
165
|
-
</div>
|
|
166
|
-
<div class="debug__meta-row">
|
|
167
|
-
<dt>落库位置</dt>
|
|
168
|
-
<dd><code>src/cards/{{ importMeta.cardCode }}/</code></dd>
|
|
169
|
-
</div>
|
|
170
|
-
<div class="debug__meta-row">
|
|
171
|
-
<dt>本地状态</dt>
|
|
172
|
-
<dd>
|
|
173
|
-
<span v-if="importMeta.dirty" class="debug__tag debug__tag--warn" title="本地已编辑,偏离导入镜像">已编辑(偏离导入镜像)</span>
|
|
174
|
-
<span v-else class="debug__tag debug__tag--ok">与导入镜像一致</span>
|
|
175
|
-
</dd>
|
|
176
|
-
</div>
|
|
177
|
-
<div class="debug__meta-row" v-if="importMeta.lastExport">
|
|
178
|
-
<dt>最近导出</dt>
|
|
179
|
-
<dd>
|
|
180
|
-
<span v-if="importMeta.lastExport.ok" class="debug__tag debug__tag--ok">读回校验通过 ✓</span>
|
|
181
|
-
<span v-else class="debug__tag debug__tag--err">读回校验未通过 ✗</span>
|
|
182
|
-
<span class="debug__meta-muted">({{ formatTime(importMeta.lastExport.at) }})</span>
|
|
183
|
-
<span v-if="importMeta.lastExport.readBackError" class="debug__meta-err"> · {{ importMeta.lastExport.readBackError }}</span>
|
|
184
|
-
</dd>
|
|
185
|
-
</div>
|
|
186
|
-
</dl>
|
|
187
|
-
<p class="debug__meta-hint">导入优先于编辑:在「变量」tab →「配置变量 mock」里点「重置」,可丢弃内存编辑、从磁盘重新载入最新导入。</p>
|
|
188
|
-
</div>
|
|
189
|
-
</template>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<!--
|
|
193
|
-
节点属性面板:组件结构 tab 双击节点弹出(可编辑写回)。
|
|
194
|
-
事件编辑已并入本面板的「事件」页签(meta 驱动),不再有独立弹层。
|
|
195
|
-
-->
|
|
196
|
-
<Transition name="modal">
|
|
197
|
-
<NodeInspector
|
|
198
|
-
v-if="inspectingComp"
|
|
199
|
-
:key="inspectKey"
|
|
200
|
-
:comp="inspectingComp"
|
|
201
|
-
:comp-names="compNames"
|
|
202
|
-
:rt="rt"
|
|
203
|
-
@apply="onApply"
|
|
204
|
-
@commit="onCommit"
|
|
205
|
-
@close="inspectingComp = null"
|
|
206
|
-
/>
|
|
207
|
-
</Transition>
|
|
208
|
-
|
|
209
|
-
<!--
|
|
210
|
-
事件编辑弹框:点击结构树「事件」徽标时单独弹出,与属性面板解耦。
|
|
211
|
-
只编辑 eventactions,不涉及属性/样式。
|
|
212
|
-
-->
|
|
213
|
-
<Transition name="ee-drawer">
|
|
214
|
-
<EventEditor
|
|
215
|
-
v-if="editingEventComp"
|
|
216
|
-
:key="'evt-' + eventEditorKey"
|
|
217
|
-
:comp="editingEventComp"
|
|
218
|
-
:comp-names="compNames"
|
|
219
|
-
:rt="rt"
|
|
220
|
-
@apply="onApply"
|
|
221
|
-
@commit="onCommit"
|
|
222
|
-
@close="editingEventComp = null"
|
|
223
|
-
/>
|
|
224
|
-
</Transition>
|
|
225
|
-
|
|
226
|
-
<!--
|
|
227
|
-
运行态面板:默认收起;点击底部状态栏「运行态」指示器弹出。面板高度由头部
|
|
228
|
-
拖拽调整(startResize,无专门拖拽节点,与预览区宽度调节同一套 mousedown 机制),
|
|
229
|
-
并持久化到 localStorage。展示 rt.state 实时 JSON(运行期交互态,只读),不污染绑定数据、
|
|
230
|
-
不触发保存高亮、调试变更永不落盘。
|
|
231
|
-
-->
|
|
232
|
-
<!--
|
|
233
|
-
底部弹出面板(运行态 / 事件 / 变量绑定):Teleport 到 App 的 #app-workside-panels,
|
|
234
|
-
宽度贯穿中右两栏、紧贴右缘(与共同底栏同一容器机制,贴近 VSCode 底部面板)。
|
|
235
|
-
defer:目标与 Teleport 同属本应用组件树,挂载时目标尚未插入 document,须等整个
|
|
236
|
-
应用挂载完成后再解析(Vue 3.5+)。chrome-off 截图模式下禁用 Teleport,回落本面板内。
|
|
237
|
-
-->
|
|
238
|
-
<Teleport defer to="#app-workside-panels" :disabled="chromeOff">
|
|
239
|
-
<section
|
|
240
|
-
class="statepanel"
|
|
241
|
-
ref="statePanel"
|
|
242
|
-
v-show="statePanelOpen"
|
|
243
|
-
>
|
|
244
|
-
<!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
|
|
245
|
-
<div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
|
|
246
|
-
<div class="statepanel__head">
|
|
247
|
-
<span class="statepanel__title">运行态 <code>rt.state</code></span>
|
|
248
|
-
<button class="statepanel__close" type="button" title="收起" @click="statePanelOpen = false">
|
|
249
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="5" x2="19" y2="19" /><line x1="19" y1="5" x2="5" y2="19" /></svg>
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
<div class="statepanel__tree">
|
|
253
|
-
<JsonTree :data="rt.state" :rt="rt" :editable="false" />
|
|
254
|
-
</div>
|
|
255
|
-
</section>
|
|
256
|
-
</Teleport>
|
|
257
|
-
|
|
258
|
-
<!--
|
|
259
|
-
底部状态栏:Teleport 到 App 的 #app-workside-bar,作为「调试面板 + 接口调用面板」
|
|
260
|
-
中右两栏共同的底栏(贴近 VSCode 布局)。defer:目标与 Teleport 同属本应用组件树,
|
|
261
|
-
挂载时目标尚未插入 document,须等整个应用挂载完成后再解析(Vue 3.5+)。
|
|
262
|
-
chrome-off 截图模式下禁用 Teleport,回落到本面板内渲染(随面板一起隐藏)。
|
|
263
|
-
左侧三枚指示器(运行态 / 变量绑定 / 事件)点击弹出或收起对应底部面板;右侧展示当前卡片与卡片总数。
|
|
264
|
-
-->
|
|
265
|
-
<Teleport defer to="#app-workside-bar" :disabled="chromeOff">
|
|
266
|
-
<section class="statusbar">
|
|
267
|
-
<button
|
|
268
|
-
class="statusbar__item statusbar__item--prob"
|
|
269
|
-
:class="{ 'is-open': statePanelOpen }"
|
|
270
|
-
type="button"
|
|
271
|
-
@click="toggleStatePanel"
|
|
272
|
-
:title="statePanelOpen ? '收起运行态面板' : '展开运行态面板(可拖拽调整高度)'"
|
|
273
|
-
>
|
|
274
|
-
<span class="statusbar__icon" :class="{ 'is-dirty': stateDirty }">◉</span>
|
|
275
|
-
<span>运行态</span>
|
|
276
|
-
</button>
|
|
277
|
-
<button
|
|
278
|
-
class="statusbar__item statusbar__item--prob"
|
|
279
|
-
:class="{ 'is-error': bindingErrors.length, 'is-ok': !bindingErrors.length }"
|
|
280
|
-
type="button"
|
|
281
|
-
@click="toggleErrorPanel('binding')"
|
|
282
|
-
:title="bindingErrors.length ? '查看绑定错误' : '无绑定错误'"
|
|
283
|
-
>
|
|
284
|
-
<span class="statusbar__icon">{{ bindingErrors.length ? '⚠' : '✓' }}</span>
|
|
285
|
-
<span>{{ bindingErrors.length ? bindingErrors.length + ' 个绑定错误' : '无绑定错误' }}</span>
|
|
286
|
-
</button>
|
|
287
|
-
<button
|
|
288
|
-
class="statusbar__item statusbar__item--prob"
|
|
289
|
-
:class="eventStatusClass"
|
|
290
|
-
type="button"
|
|
291
|
-
@click="toggleErrorPanel('event')"
|
|
292
|
-
:title="eventStatusTitle"
|
|
293
|
-
>
|
|
294
|
-
<span class="statusbar__icon">{{ eventCount ? (eventErrors.length ? '⚠' : '☰') : '✓' }}</span>
|
|
295
|
-
<span>{{ eventCount ? eventCount + ' 条事件记录' : '无事件记录' }}</span>
|
|
296
|
-
</button>
|
|
297
|
-
<!-- 本地开发服务连接状态:连接图标 + 状态文案,颜色与右上角「连接」按钮实时同步(复用 devSvcClass / devSvcTitle / devSvcLabel)。
|
|
298
|
-
可点击:弹出 / 收起底部「服务日志」面板(实时镜像所连 cardbuddy 终端输出)。
|
|
299
|
-
仅重连 / 断开 / 未配置等_warn、danger 态着语义色,连接成功用状态栏默认色;不设选中态,仅 hover 提示可点。 -->
|
|
300
|
-
<button
|
|
301
|
-
class="statusbar__item statusbar__conn"
|
|
302
|
-
:class="devSvcClass"
|
|
303
|
-
type="button"
|
|
304
|
-
:title="devSvcTitle + '(点击查看服务日志)'"
|
|
305
|
-
@click="toggleErrorPanel('svclog')"
|
|
306
|
-
>
|
|
307
|
-
<svg class="statusbar__conn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-link" /></svg>
|
|
308
|
-
<span>{{ devSvcLabel }}</span>
|
|
309
|
-
</button>
|
|
310
|
-
<span class="statusbar__spacer"></span>
|
|
311
|
-
<span class="statusbar__item statusbar__item--right">{{ cardId }}</span>
|
|
312
|
-
<span class="statusbar__item statusbar__item--right">{{ cardCount }} 张卡片</span>
|
|
313
|
-
</section>
|
|
314
|
-
</Teleport>
|
|
315
|
-
|
|
316
|
-
<Teleport defer to="#app-workside-panels" :disabled="chromeOff">
|
|
317
|
-
<section class="errpanel" ref="errPanel" v-show="errorPanel">
|
|
318
|
-
<!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
|
|
319
|
-
<div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
|
|
320
|
-
<div class="errpanel__head">
|
|
321
|
-
<span class="errpanel__title">{{ errorPanel === 'svclog' ? '服务日志' : errorPanel === 'event' ? '事件记录' : '绑定错误记录' }}</span>
|
|
322
|
-
<!-- 服务日志:标题后给一枚读数(行数 + 实时态),与事件面板 tab 同一位置语言 -->
|
|
323
|
-
<span v-if="errorPanel === 'svclog'" class="svclog__meta">
|
|
324
|
-
{{ svcLogs.length }} 行<span v-if="svcLogDropped"> · 已滚动淘汰 {{ svcLogDropped }}</span>
|
|
325
|
-
<i class="svclog__dot" :class="svcLogLive ? 'is-live' : 'is-off'"></i>{{ svcLogLive ? '实时' : '未连接' }}
|
|
326
|
-
</span>
|
|
327
|
-
<!-- 事件面板:日志 / 错误 tab 并入头部单行(一次只看一类);变量绑定面板无 tab -->
|
|
328
|
-
<div v-if="errorPanel === 'event'" class="errpanel__tabs">
|
|
329
|
-
<button
|
|
330
|
-
type="button"
|
|
331
|
-
class="errpanel__tab"
|
|
332
|
-
:class="{ 'errpanel__tab--active': eventTab === 'log' }"
|
|
333
|
-
@click="eventTab = 'log'"
|
|
334
|
-
>
|
|
335
|
-
日志<span class="errpanel__tab-count">{{ logs.length }}</span>
|
|
336
|
-
</button>
|
|
337
|
-
<button
|
|
338
|
-
type="button"
|
|
339
|
-
class="errpanel__tab"
|
|
340
|
-
:class="{ 'errpanel__tab--active': eventTab === 'err' }"
|
|
341
|
-
@click="eventTab = 'err'"
|
|
342
|
-
>
|
|
343
|
-
错误<span class="errpanel__tab-count" :class="{ 'is-hit': eventErrors.length }">{{ eventErrors.length }}</span>
|
|
344
|
-
</button>
|
|
345
|
-
</div>
|
|
346
|
-
<span class="errpanel__actions">
|
|
347
|
-
<button
|
|
348
|
-
v-if="errorPanel === 'event' && eventTab === 'log'"
|
|
349
|
-
class="errpanel__iconbtn errpanel__clear"
|
|
350
|
-
type="button"
|
|
351
|
-
:disabled="!logs.length"
|
|
352
|
-
title="清空日志(不影响错误)"
|
|
353
|
-
@click="clearEventLogs"
|
|
354
|
-
>
|
|
355
|
-
<!-- 垃圾桶图标:清空日志 -->
|
|
356
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
357
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-trash" /></svg>
|
|
358
|
-
</button>
|
|
359
|
-
<button
|
|
360
|
-
v-if="errorPanel === 'svclog'"
|
|
361
|
-
class="errpanel__iconbtn"
|
|
362
|
-
:class="{ 'is-done': svcCopied }"
|
|
363
|
-
type="button"
|
|
364
|
-
:disabled="!svcLogs.length"
|
|
365
|
-
:title="svcCopied ? '已复制' : '复制全部日志(纯文本,去 ANSI)'"
|
|
366
|
-
@click="copySvcLogs"
|
|
367
|
-
>
|
|
368
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></svg>
|
|
369
|
-
</button>
|
|
370
|
-
<button
|
|
371
|
-
v-if="errorPanel === 'svclog'"
|
|
372
|
-
class="errpanel__iconbtn svclog__follow"
|
|
373
|
-
:class="{ 'is-on': svcLogFollow }"
|
|
374
|
-
type="button"
|
|
375
|
-
:title="svcLogFollow ? '跟随最新输出(点击暂停)' : '已暂停跟随(点击回到最新)'"
|
|
376
|
-
@click="toggleSvcFollow"
|
|
377
|
-
>
|
|
378
|
-
<!-- 下箭头图标:跟随最新输出 -->
|
|
379
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19" /><polyline points="19 12 12 19 5 12" /></svg>
|
|
380
|
-
</button>
|
|
381
|
-
<button
|
|
382
|
-
v-if="errorPanel === 'svclog'"
|
|
383
|
-
class="errpanel__iconbtn errpanel__clear"
|
|
384
|
-
type="button"
|
|
385
|
-
:disabled="!svcLogs.length"
|
|
386
|
-
title="清空面板(不影响服务端缓冲)"
|
|
387
|
-
@click="clearSvcLogs"
|
|
388
|
-
>
|
|
389
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
390
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-trash" /></svg>
|
|
391
|
-
</button>
|
|
392
|
-
<button class="errpanel__iconbtn errpanel__close" type="button" title="关闭" @click="errorPanel = ''">
|
|
393
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="5" x2="19" y2="19" /><line x1="19" y1="5" x2="5" y2="19" /></svg>
|
|
394
|
-
</button>
|
|
395
|
-
</span>
|
|
396
|
-
</div>
|
|
397
|
-
<div
|
|
398
|
-
class="errpanel__body"
|
|
399
|
-
:class="{ 'errpanel__body--term': errorPanel === 'svclog' && svcLogs.length }"
|
|
400
|
-
ref="errBody"
|
|
401
|
-
@scroll.passive="onErrBodyScroll"
|
|
402
|
-
>
|
|
403
|
-
<!-- 服务日志面板:本地服务(cardbuddy)终端输出的实时镜像。
|
|
404
|
-
服务端按行镜像 stdout/stderr 并保留原始 ANSI,这里经 preview/ansi.js 还原配色,
|
|
405
|
-
因此面板固定深色终端底(chalk 调色板取自深色令牌,浅底不可读)。 -->
|
|
406
|
-
<template v-if="errorPanel === 'svclog'">
|
|
407
|
-
<div v-if="!svcLogs.length" class="empty-state">
|
|
408
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
409
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
410
|
-
<polyline points="4 17 10 11 4 5" />
|
|
411
|
-
<line x1="12" y1="19" x2="20" y2="19" />
|
|
412
|
-
</svg>
|
|
413
|
-
</span>
|
|
414
|
-
<p class="debug__empty">{{ svcEmptyText }}</p>
|
|
415
|
-
<p class="empty-state__hint">{{ svcEmptyHint }}</p>
|
|
416
|
-
</div>
|
|
417
|
-
<div v-else class="svclog">
|
|
418
|
-
<div
|
|
419
|
-
v-for="row in svcLogs"
|
|
420
|
-
:key="row.seq || row.t"
|
|
421
|
-
class="svclog__line"
|
|
422
|
-
:class="'svclog__line--' + row.level"
|
|
423
|
-
><span class="svclog__text" v-html="row.html"></span></div>
|
|
424
|
-
</div>
|
|
425
|
-
</template>
|
|
426
|
-
<!-- 事件面板:按当前 tab 只渲染日志或错误一类 -->
|
|
427
|
-
<template v-else-if="errorPanel === 'event'">
|
|
428
|
-
<template v-if="eventTab === 'log'">
|
|
429
|
-
<!-- 空状态占位:与 API 调用面板同款(虚线徽章 + 线性图标 + 文案 + 弱提示) -->
|
|
430
|
-
<div v-if="!logs.length" class="empty-state">
|
|
431
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
432
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
433
|
-
<line x1="21" y1="6" x2="3" y2="6" />
|
|
434
|
-
<line x1="21" y1="12" x2="3" y2="12" />
|
|
435
|
-
<line x1="21" y1="18" x2="3" y2="18" />
|
|
436
|
-
</svg>
|
|
437
|
-
</span>
|
|
438
|
-
<p class="debug__empty">暂无日志</p>
|
|
439
|
-
<p class="empty-state__hint">卡片事件内 console.* 主动打印的日志会实时显示在这里</p>
|
|
440
|
-
</div>
|
|
441
|
-
<ul v-else class="debug__list debug__list--log">
|
|
442
|
-
<li v-for="(l, i) in logs" :key="'l' + i" class="log__item" :class="'log--' + l.level">
|
|
443
|
-
<span class="log__time">{{ fmtTime(l.t) }}</span>
|
|
444
|
-
<span class="log__tag" :class="'log__tag--' + l.level">{{ l.level }}</span>
|
|
445
|
-
<div class="log__content">
|
|
446
|
-
<template v-if="l.args && l.args.length">
|
|
447
|
-
<LogValue v-for="(a, ai) in l.args" :key="ai" :value="a" />
|
|
448
|
-
</template>
|
|
449
|
-
<span v-else class="log__empty">—</span>
|
|
450
|
-
</div>
|
|
451
|
-
</li>
|
|
452
|
-
</ul>
|
|
453
|
-
</template>
|
|
454
|
-
<template v-else>
|
|
455
|
-
<div v-if="!eventErrors.length" class="empty-state">
|
|
456
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
457
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
458
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
|
459
|
-
<polyline points="22 4 12 14.01 9 11.01" />
|
|
460
|
-
</svg>
|
|
461
|
-
</span>
|
|
462
|
-
<p class="debug__empty">无事件错误</p>
|
|
463
|
-
<p class="empty-state__hint">事件动作(customAction / 监听器)执行报错时会记录在这里</p>
|
|
464
|
-
</div>
|
|
465
|
-
<ul v-else class="debug__list">
|
|
466
|
-
<li v-for="(e, i) in eventErrors" :key="'e' + i" class="debug__item debug__item--err">
|
|
467
|
-
<span class="err__time">{{ fmtTime(e.t) }}</span>
|
|
468
|
-
<span class="debug__kind">{{ e.kind }}</span>
|
|
469
|
-
<span class="err__msg">{{ e.msg }}</span>
|
|
470
|
-
</li>
|
|
471
|
-
</ul>
|
|
472
|
-
</template>
|
|
473
|
-
</template>
|
|
474
|
-
<!-- 变量绑定面板:仅错误列表 -->
|
|
475
|
-
<template v-else>
|
|
476
|
-
<div v-if="!panelErrors.length" class="empty-state">
|
|
477
|
-
<span class="empty-state__badge" aria-hidden="true">
|
|
478
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
479
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
|
480
|
-
<polyline points="22 4 12 14.01 9 11.01" />
|
|
481
|
-
</svg>
|
|
482
|
-
</span>
|
|
483
|
-
<p class="debug__empty">无绑定错误</p>
|
|
484
|
-
<p class="empty-state__hint">绑定 / 表达式(bind、vshow、render、refresh)求值失败时会记录在这里</p>
|
|
485
|
-
</div>
|
|
486
|
-
<ul v-else class="debug__list">
|
|
487
|
-
<li v-for="(e, i) in panelErrors" :key="i" class="debug__item debug__item--err">
|
|
488
|
-
<span class="err__time">{{ fmtTime(e.t) }}</span>
|
|
489
|
-
<span class="debug__kind">{{ e.kind }}</span>
|
|
490
|
-
<span class="err__msg">{{ e.msg }}</span>
|
|
491
|
-
</li>
|
|
492
|
-
</ul>
|
|
493
|
-
</template>
|
|
494
|
-
</div>
|
|
495
|
-
</section>
|
|
496
|
-
</Teleport>
|
|
497
|
-
|
|
498
|
-
<!-- 变量添加 / 编辑弹窗:与节点属性面板同构(ni-overlay / ni-panel / ni-head / ni-foot),
|
|
499
|
-
宽度 · 字号 · 控件高度 · 发丝线分隔全部沿用 node-inspector 的视觉语言;
|
|
500
|
-
默认值按类型分控件:数组 / 对象走 CodeJar 代码编辑区(高亮 + 缩进 + 大段粘贴,确定时自动格式化)、
|
|
501
|
-
字符串走多行文本域、数字走带 −/+ 的 NumberStepper、布尔走 SetSegment 分段单选。 -->
|
|
502
|
-
<transition name="modal">
|
|
503
|
-
<div v-if="varDlg" class="ni-overlay" @click.self="varDlg = null">
|
|
504
|
-
<div class="ni-panel modal-card vars-dlg" role="alertdialog" aria-modal="true" :aria-label="varDlg.index == null ? '添加变量' : '编辑变量'">
|
|
505
|
-
<div class="ni-head">
|
|
506
|
-
<span class="ni-title">{{ varDlg.index == null ? '添加变量' : '编辑变量' }}<em class="ni-cls">{{ varDlg.form.key || '未命名' }}</em></span>
|
|
507
|
-
<button class="ni-close" type="button" title="关闭" @click="varDlg = null"></button>
|
|
508
|
-
</div>
|
|
509
|
-
<div class="ni-body">
|
|
510
|
-
<div class="ni-scroll">
|
|
511
|
-
<div class="ni-field-row">
|
|
512
|
-
<label class="ni-field-label"><i>*</i>变量名</label>
|
|
513
|
-
<input class="ni-input" type="text" v-model="varDlg.form.key" placeholder="英文标识,绑定以 $key 引用" />
|
|
514
|
-
</div>
|
|
515
|
-
<div class="ni-field-row">
|
|
516
|
-
<label class="ni-field-label"><i>*</i>中文名</label>
|
|
517
|
-
<input class="ni-input" type="text" v-model="varDlg.form.name" placeholder="中文名" />
|
|
518
|
-
</div>
|
|
519
|
-
<div class="ni-field-row">
|
|
520
|
-
<label class="ni-field-label"><i>*</i>类型</label>
|
|
521
|
-
<!-- 类型下拉:触发器与选项都走「中文名 + 彩色类型标签」,标签直接复用变量列表的
|
|
522
|
-
.vars__type / .vars__type--<type>(同一套 --type-* 语义色),两处视觉完全一致 -->
|
|
523
|
-
<Dropdown class="vars-dlg__ctrl" block size="sm" :value="varDlg.form.type" :options="varTypeOptions" @change="onVarTypeChange">
|
|
524
|
-
<template #current="{ opt, text }">
|
|
525
|
-
<span v-if="opt" class="vars-type-pick">
|
|
526
|
-
<span class="vars-type-pick__name">{{ opt.value }}</span>
|
|
527
|
-
<span class="vars__type" :class="'vars__type--' + opt.key">{{ opt.key }}</span>
|
|
528
|
-
</span>
|
|
529
|
-
<template v-else>{{ text }}</template>
|
|
530
|
-
</template>
|
|
531
|
-
<template #option="{ opt }">
|
|
532
|
-
<span class="vars-type-pick">
|
|
533
|
-
<span class="vars-type-pick__name">{{ opt.value }}</span>
|
|
534
|
-
<span class="vars__type" :class="'vars__type--' + opt.key">{{ opt.key }}</span>
|
|
535
|
-
</span>
|
|
536
|
-
</template>
|
|
537
|
-
</Dropdown>
|
|
538
|
-
</div>
|
|
539
|
-
<!-- 默认值按类型分四种控件:数组 / 对象 = CodeJar 代码编辑区、字符串 = 多行文本域、
|
|
540
|
-
数字 = 带 −/+ 的 NumberStepper、布尔 = 复用 SetSegment 的 true / false 分段单选。四者统一以文本承载
|
|
541
|
-
(varDlg.form.defaultValue),确定时仍由 parseDefault 按类型解析。 -->
|
|
542
|
-
<div class="ni-field-row ni-field-row--top">
|
|
543
|
-
<label class="ni-field-label">默认值</label>
|
|
544
|
-
<div v-if="isComplexType(varDlg.form.type)" ref="dlgCode" class="vars-dlg__code" spellcheck="false"></div>
|
|
545
|
-
<textarea
|
|
546
|
-
v-else-if="varDlg.form.type === 'string'"
|
|
547
|
-
class="ni-input vars-dlg__str"
|
|
548
|
-
v-model="varDlg.form.defaultValue"
|
|
549
|
-
:placeholder="scalarPlaceholder('string')"
|
|
550
|
-
></textarea>
|
|
551
|
-
<NumberStepper
|
|
552
|
-
v-else-if="varDlg.form.type === 'number'"
|
|
553
|
-
class="vars-dlg__ctrl"
|
|
554
|
-
:value="varDlg.form.defaultValue"
|
|
555
|
-
:placeholder="scalarPlaceholder('number')"
|
|
556
|
-
@input="varDlg.form.defaultValue = $event"
|
|
557
|
-
/>
|
|
558
|
-
<SetSegment
|
|
559
|
-
v-else
|
|
560
|
-
class="vars-dlg__ctrl"
|
|
561
|
-
:value="varDlg.form.defaultValue"
|
|
562
|
-
:options="{ iParamList: varBoolOptions }"
|
|
563
|
-
@input="varDlg.form.defaultValue = $event"
|
|
564
|
-
/>
|
|
565
|
-
</div>
|
|
566
|
-
<div class="ni-field-row ni-field-row--top">
|
|
567
|
-
<label class="ni-field-label">描述</label>
|
|
568
|
-
<textarea class="ni-input vars-dlg__desc" v-model="varDlg.form.desc" placeholder="可选"></textarea>
|
|
569
|
-
</div>
|
|
570
|
-
<p v-if="varDlgError" class="vars-dlg__err">{{ varDlgError }}</p>
|
|
571
|
-
</div>
|
|
572
|
-
</div>
|
|
573
|
-
<div class="ni-foot">
|
|
574
|
-
<span class="ni-tip">默认值入 defaultValue;设计期覆盖见「配置变量 mock」</span>
|
|
575
|
-
<div class="ni-foot-btns">
|
|
576
|
-
<button class="ni-btn ni-btn--ghost" type="button" @click="varDlg = null">取消</button>
|
|
577
|
-
<button class="ni-btn" type="button" @click="confirmVarDlg">确定</button>
|
|
578
|
-
</div>
|
|
579
|
-
</div>
|
|
580
|
-
</div>
|
|
581
|
-
</div>
|
|
582
|
-
</transition>
|
|
583
|
-
<ConfirmDialog
|
|
584
|
-
:visible="delVarTarget != null"
|
|
585
|
-
title="删除变量"
|
|
586
|
-
:message="delVarMessage"
|
|
587
|
-
:severity="delVarSeverity"
|
|
588
|
-
:confirm-text="delVarConfirmText"
|
|
589
|
-
@confirm="confirmDeleteVar"
|
|
590
|
-
@cancel="delVarTarget = null"
|
|
591
|
-
/>
|
|
592
|
-
</aside>
|
|
593
|
-
</template>
|
|
594
|
-
|
|
595
|
-
<script>
|
|
596
|
-
import JsonTree from './JsonTree.vue'
|
|
597
|
-
import StructureTree from './StructureTree.vue'
|
|
598
|
-
import NodeInspector from './NodeInspector.vue'
|
|
599
|
-
import EventEditor from './EventEditor.vue'
|
|
600
|
-
import LogValue from './LogValue.vue'
|
|
601
|
-
import Dropdown from './Dropdown.vue'
|
|
602
|
-
import ConfirmDialog from './ConfirmDialog.vue'
|
|
603
|
-
import NumberStepper from './NumberStepper.vue'
|
|
604
|
-
import SetSegment from './setters/SetSegment.vue'
|
|
605
|
-
import { COMPONENT_META } from '@/runtime/toPreview.js'
|
|
606
|
-
import { copyText } from '@/runtime/clipboard.js'
|
|
607
|
-
// 结构树「粘贴」按钮的 disabled 态:剪贴板是否有可粘贴节点
|
|
608
|
-
import { nodeClipboard } from '@/runtime/clipboard-node.js'
|
|
609
|
-
// Raw 编辑器的代码高亮:复用项目既有的 CodeJar + Prism 方案(与 SetCode/SetExpr 一致),
|
|
610
|
-
// 仅切换语法为 json(Prism 自带 json grammar,无需额外安装)。
|
|
611
|
-
import { CodeJar } from 'codejar'
|
|
612
|
-
import Prism from 'prismjs'
|
|
613
|
-
import 'prismjs/components/prism-json'
|
|
614
|
-
import { apiFetch, subscribeServiceLog, fetchServiceLogs } from '@/api.js'
|
|
615
|
-
// mock 覆盖仓与派生真相:与 App / 服务端共用同一实现,避免判据漂移
|
|
616
|
-
import { buildDataFromSchema, readMocks, hasMockValue } from '../../server/core/buildDataFromSchema.js'
|
|
617
|
-
// 服务日志面板:终端 ANSI(SGR)→ HTML,1:1 还原 cardbuddy 的 chalk 配色
|
|
618
|
-
import { ansiToHtml, stripAnsi } from '@/ansi.js'
|
|
619
|
-
|
|
620
|
-
/** 服务日志在前端保留的最大行数(与服务端环形缓冲同量级,超出裁剪最旧的)。 */
|
|
621
|
-
const SVC_MAX_LINES = 800
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* 容错 JSON 解析(对象字面量友好):先做一遍「格式化容忍」清理,再交严格 JSON.parse ——
|
|
625
|
-
* 报错只针对对象结构本身非法(括号不配对 / 悬空冒号 / 未闭合字符串等),
|
|
626
|
-
* 手写习惯一律容忍并规范化:无引号键 / 单引号字符串 / 注释 / 尾逗号 /
|
|
627
|
-
* 分号当逗号 / 重复逗号 / undefined·NaN·Infinity 归 null。
|
|
628
|
-
* 字符串字面量原样搬运(含转义),不会误伤内容里的 // 、,} 等字符;
|
|
629
|
-
* 清理后的标准 JSON 由调用方(应用 / 确定)回灌编辑器完成「保存即格式化」。
|
|
630
|
-
* @param {string} text
|
|
631
|
-
* @returns {{ value: any } | { error: string }}
|
|
632
|
-
*/
|
|
633
|
-
function parseJsonLenient(text) {
|
|
634
|
-
const src = String(text == null ? '' : text).replace(/^\uFEFF/, '')
|
|
635
|
-
let out = ''
|
|
636
|
-
let i = 0
|
|
637
|
-
const n = src.length
|
|
638
|
-
const lastNonWs = () => {
|
|
639
|
-
let k = out.length - 1
|
|
640
|
-
while (k >= 0 && /\s/.test(out[k])) k -= 1
|
|
641
|
-
return k
|
|
642
|
-
}
|
|
643
|
-
// 逗号去重入栈: leading 逗号(紧跟 [ { )与连续逗号都不输出
|
|
644
|
-
const pushComma = () => {
|
|
645
|
-
const k = lastNonWs()
|
|
646
|
-
if (k >= 0 && out[k] !== ',' && out[k] !== '[' && out[k] !== '{') out += ','
|
|
647
|
-
}
|
|
648
|
-
const skipWs = (from) => {
|
|
649
|
-
let k = from
|
|
650
|
-
while (k < n && /\s/.test(src[k])) k += 1
|
|
651
|
-
return k
|
|
652
|
-
}
|
|
653
|
-
while (i < n) {
|
|
654
|
-
const ch = src[i]
|
|
655
|
-
// 双引号字符串:原样搬运(含转义)
|
|
656
|
-
if (ch === '"') {
|
|
657
|
-
let j = i + 1
|
|
658
|
-
while (j < n) {
|
|
659
|
-
if (src[j] === '\\') { j += 2; continue }
|
|
660
|
-
if (src[j] === '"') { j += 1; break }
|
|
661
|
-
j += 1
|
|
662
|
-
}
|
|
663
|
-
out += src.slice(i, j)
|
|
664
|
-
i = j
|
|
665
|
-
continue
|
|
666
|
-
}
|
|
667
|
-
// 单引号字符串:转双引号(内部双引号转义、\' 还原为裸引号)
|
|
668
|
-
if (ch === '\'') {
|
|
669
|
-
let j = i + 1
|
|
670
|
-
let body = ''
|
|
671
|
-
while (j < n) {
|
|
672
|
-
const c = src[j]
|
|
673
|
-
if (c === '\\' && j + 1 < n) {
|
|
674
|
-
const nx = src[j + 1]
|
|
675
|
-
if (nx === '\'') { body += '\''; j += 2; continue }
|
|
676
|
-
if (nx === '"') { body += '\\\"'; j += 2; continue }
|
|
677
|
-
body += c + nx
|
|
678
|
-
j += 2
|
|
679
|
-
continue
|
|
680
|
-
}
|
|
681
|
-
if (c === '\'') { j += 1; break }
|
|
682
|
-
if (c === '"') { body += '\\\"'; j += 1; continue }
|
|
683
|
-
body += c
|
|
684
|
-
j += 1
|
|
685
|
-
}
|
|
686
|
-
out += '"' + body + '"'
|
|
687
|
-
i = j
|
|
688
|
-
continue
|
|
689
|
-
}
|
|
690
|
-
// 注释:// 与 /* */
|
|
691
|
-
if (ch === '/' && src[i + 1] === '/') {
|
|
692
|
-
while (i < n && src[i] !== '\n') i += 1
|
|
693
|
-
continue
|
|
694
|
-
}
|
|
695
|
-
if (ch === '/' && src[i + 1] === '*') {
|
|
696
|
-
const close = src.indexOf('*/', i + 2)
|
|
697
|
-
i = close === -1 ? n : close + 2
|
|
698
|
-
continue
|
|
699
|
-
}
|
|
700
|
-
// 分号视同逗号;逗号去重
|
|
701
|
-
if (ch === ';' || ch === ',') {
|
|
702
|
-
pushComma()
|
|
703
|
-
i += 1
|
|
704
|
-
continue
|
|
705
|
-
}
|
|
706
|
-
// 标识符:后跟冒号 → 键,补双引号;否则原样(true/false/null 等关键字)
|
|
707
|
-
if (/[A-Za-z_$]/.test(ch)) {
|
|
708
|
-
let j = i
|
|
709
|
-
while (j < n && /[A-Za-z0-9_$]/.test(src[j])) j += 1
|
|
710
|
-
const token = src.slice(i, j)
|
|
711
|
-
if (src[skipWs(j)] === ':') out += '"' + token + '"'
|
|
712
|
-
else if (token === 'undefined' || token === 'NaN' || token === 'Infinity') out += 'null'
|
|
713
|
-
else out += token
|
|
714
|
-
i = j
|
|
715
|
-
continue
|
|
716
|
-
}
|
|
717
|
-
// 数字(含负号 / 指数):后跟冒号 → 键,补双引号;-Infinity 归 null
|
|
718
|
-
if (ch === '-' && src.startsWith('Infinity', i + 1)) {
|
|
719
|
-
out += 'null'
|
|
720
|
-
i += 9
|
|
721
|
-
continue
|
|
722
|
-
}
|
|
723
|
-
if (/[0-9]/.test(ch) || (ch === '-' && /[0-9]/.test(src[i + 1] || ''))) {
|
|
724
|
-
let j = i
|
|
725
|
-
while (j < n && /[0-9eE+\-.]/.test(src[j])) j += 1
|
|
726
|
-
const token = src.slice(i, j)
|
|
727
|
-
if (src[skipWs(j)] === ':') out += '"' + token + '"'
|
|
728
|
-
else out += token
|
|
729
|
-
i = j
|
|
730
|
-
continue
|
|
731
|
-
}
|
|
732
|
-
// 收括号:抹掉所有尾逗号
|
|
733
|
-
if (ch === '}' || ch === ']') {
|
|
734
|
-
let k = lastNonWs()
|
|
735
|
-
while (k >= 0 && out[k] === ',') {
|
|
736
|
-
out = out.slice(0, k) + out.slice(k + 1)
|
|
737
|
-
k = lastNonWs()
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
out += ch
|
|
741
|
-
i += 1
|
|
742
|
-
}
|
|
743
|
-
try {
|
|
744
|
-
return { value: JSON.parse(out.trim() || '{}') }
|
|
745
|
-
} catch (e) {
|
|
746
|
-
return { error: e.message }
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* 行级别判定:只认 logger.js 的行首标签(HH:MM:SS.mmm INFO/OK/WARN/ERR),
|
|
752
|
-
* 其余输出(console.* / 第三方 / vite)统一 info,避免把卡片内容里的「错误」字样误判。
|
|
753
|
-
*/
|
|
754
|
-
function svcLevelOf(plain) {
|
|
755
|
-
const m = /^\d{2}:\d{2}:\d{2}\.\d{3}\s+(INFO|OK\s|WARN|ERR)\b/.exec(plain)
|
|
756
|
-
if (m) {
|
|
757
|
-
const tag = m[1].trim()
|
|
758
|
-
return tag === 'ERR' ? 'error' : tag === 'WARN' ? 'warn' : tag === 'OK' ? 'ok' : 'info'
|
|
759
|
-
}
|
|
760
|
-
return 'info'
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/** 服务端日志行 { seq, t, text } → 面板行(ANSI 转 HTML 只做一次,避免每次重渲染重复解析)。 */
|
|
764
|
-
function toSvcRow(line) {
|
|
765
|
-
const text = String(line && line.text != null ? line.text : '')
|
|
766
|
-
const plain = stripAnsi(text)
|
|
767
|
-
return {
|
|
768
|
-
seq: Number(line && line.seq) || 0,
|
|
769
|
-
t: Number(line && line.t) || 0,
|
|
770
|
-
level: svcLevelOf(plain),
|
|
771
|
-
plain,
|
|
772
|
-
html: ansiToHtml(text),
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// copyText 已抽到 @/runtime/clipboard.js,供 DebugPanel 与 NodeInspector 共用
|
|
777
|
-
|
|
778
|
-
// 判断运行态是否与绑定数据基线发生偏离(用于状态栏「运行态」小点变黄)
|
|
779
|
-
function deepEqual(a, b) {
|
|
780
|
-
if (a === b) return true
|
|
781
|
-
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false
|
|
782
|
-
const ka = Object.keys(a)
|
|
783
|
-
const kb = Object.keys(b)
|
|
784
|
-
if (ka.length !== kb.length) return false
|
|
785
|
-
for (const k of ka) {
|
|
786
|
-
if (!Object.prototype.hasOwnProperty.call(b, k)) return false
|
|
787
|
-
if (!deepEqual(a[k], b[k])) return false
|
|
788
|
-
}
|
|
789
|
-
return true
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/** 深拷贝 JSON 安全值;变量默认值写入 schema / rootScope 前隔断引用 */
|
|
793
|
-
function cloneJson(v) {
|
|
794
|
-
return v === undefined ? undefined : JSON.parse(JSON.stringify(v))
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
export default {
|
|
798
|
-
name: 'DebugPanel',
|
|
799
|
-
components: { JsonTree, StructureTree, NodeInspector, EventEditor, LogValue, Dropdown, ConfirmDialog, NumberStepper, SetSegment },
|
|
800
|
-
props: {
|
|
801
|
-
rt: { type: Object, required: true },
|
|
802
|
-
preview: { type: Object, default: null },
|
|
803
|
-
schema: { type: Object, default: null },
|
|
804
|
-
cardId: { type: String, default: '' },
|
|
805
|
-
// 预览重载令牌:loadCard(含刷新/同步后自动重载/撤销重做)每次自增,
|
|
806
|
-
// 用于让「卡片来源」tab 在卡片被重新载入磁盘内容时也重拉导入来源元数据,
|
|
807
|
-
// 修复「同步后点刷新,来源信息不更新」的问题。
|
|
808
|
-
reloadToken: { type: Number, default: 0 },
|
|
809
|
-
cardCount: { type: Number, default: 0 },
|
|
810
|
-
selectedNodeId: { type: String, default: '' },
|
|
811
|
-
// 拖拽移动成功令牌:App 在节点拖拽移动成功后自增,透传给结构树,
|
|
812
|
-
// 触发「选中被拖节点 + 仅展开其祖先链、其余全部折叠」。
|
|
813
|
-
moveCollapseToken: { type: Number, default: 0 },
|
|
814
|
-
// 当前激活的调试面板 tab(变量/组件结构/卡片来源),由 App 统一管理,
|
|
815
|
-
// 便于画布 Ctrl/⌘ 点击定位在「组件结构」tab 激活时才生效。
|
|
816
|
-
activeTab: { type: String, default: 'vars' },
|
|
817
|
-
// 本地开发服务连接状态:复用 App 中顶栏「连接」按钮同款状态/配色(devSvcClass/devSvcTitle/devSvcLabel),
|
|
818
|
-
// 供底部状态栏展示「连接图标 + 状态文案」,与右上角按钮实时同步(颜色一致、状态一致)。
|
|
819
|
-
devSvcClass: { type: String, default: 'app__btn--warn' },
|
|
820
|
-
devSvcTitle: { type: String, default: '' },
|
|
821
|
-
devSvcLabel: { type: String, default: '' },
|
|
822
|
-
// 本地服务连接态('' | unconfigured | connecting | open | closed):
|
|
823
|
-
// 服务日志面板据此给出空态文案(未配置 / 连接中 / 已断开)。
|
|
824
|
-
devSvcConn: { type: String, default: '' },
|
|
825
|
-
// 纯净截图模式(?chrome=off):禁用状态栏 Teleport,使其留在本面板内随面板隐藏
|
|
826
|
-
chromeOff: { type: Boolean, default: false },
|
|
827
|
-
},
|
|
828
|
-
data() {
|
|
829
|
-
return {
|
|
830
|
-
copied: false,
|
|
831
|
-
copyTimer: null,
|
|
832
|
-
// 刚复制过默认值的变量行下标(图标变 ✓ 反馈),null=无
|
|
833
|
-
copiedVar: null,
|
|
834
|
-
varCopyTimer: null,
|
|
835
|
-
// 底部面板模式:''(收起) | 'binding'(变量绑定错误) | 'event'(事件日志 / 执行错误)
|
|
836
|
-
errorPanel: '',
|
|
837
|
-
// 事件面板内部 tab:'log'(主动日志) | 'err'(执行错误),打开面板时按是否有错误自动定位
|
|
838
|
-
eventTab: 'log',
|
|
839
|
-
// 服务日志面板(errorPanel === 'svclog'):本地服务终端输出的实时镜像
|
|
840
|
-
svcLogs: [],
|
|
841
|
-
// 已收到的最大 seq:断线重连后用 GET /__logs?since= 精确补齐缺口
|
|
842
|
-
svcLogSeq: 0,
|
|
843
|
-
// 清空面板时记录的水位线:此后取历史不会把已清掉的行再拉回来
|
|
844
|
-
svcLogFloor: 0,
|
|
845
|
-
svcLogLive: false,
|
|
846
|
-
// 自动跟随最新输出;用户向上翻阅即暂停,回到底部自动恢复
|
|
847
|
-
svcLogFollow: true,
|
|
848
|
-
svcLogDropped: 0,
|
|
849
|
-
svcLogLoading: false,
|
|
850
|
-
svcLogErr: '',
|
|
851
|
-
svcCopied: false,
|
|
852
|
-
// 运行态面板:默认收起;点击状态栏「运行态」指示器切换
|
|
853
|
-
statePanelOpen: false,
|
|
854
|
-
// 运行态 / 变量绑定 / 事件 三个底部面板互斥,共用同一高度变量(px);
|
|
855
|
-
// 可被顶部抓手拖拽调整,并持久化到 localStorage(cardbuddy:debug-panel-height)
|
|
856
|
-
panelH: this.loadPanelH(),
|
|
857
|
-
// 底部面板头部拖拽进行中:头部高亮与竖向分隔条 .is-dragging 同一语言
|
|
858
|
-
panelDragging: false,
|
|
859
|
-
// 双击结构树节点后弹出的属性面板目标节点
|
|
860
|
-
inspectingComp: null,
|
|
861
|
-
// 换节点时强制重建面板(内部含 CodeJar 这类托管 DOM 的控件)
|
|
862
|
-
inspectKey: 0,
|
|
863
|
-
// 事件编辑弹框目标节点(点击「事件」徽标时单独弹出,与属性面板解耦)
|
|
864
|
-
editingEventComp: null,
|
|
865
|
-
eventEditorKey: 0,
|
|
866
|
-
// Raw JSON 编辑状态(mock 二级视图的编辑器内容与解析错误)
|
|
867
|
-
rawText: '',
|
|
868
|
-
rawError: '',
|
|
869
|
-
// 变量 tab 二级视图:'list'(变量列表,管理 inputParams)| 'mock'(Raw JSON 编辑 mock 数据)
|
|
870
|
-
varsMode: 'list',
|
|
871
|
-
// 变量添加 / 编辑弹窗:null | { index: null|number, form: { key, name, type, defaultValue, desc } }
|
|
872
|
-
varDlg: null,
|
|
873
|
-
varDlgError: '',
|
|
874
|
-
// 待删除变量下标(ConfirmDialog 确认中)
|
|
875
|
-
delVarTarget: null,
|
|
876
|
-
// 类型选项:value 只留中文名,英文类型名由 Dropdown 的 current / option 插槽渲染成彩色标签
|
|
877
|
-
varTypeOptions: [
|
|
878
|
-
{ key: 'string', value: '字符串' },
|
|
879
|
-
{ key: 'number', value: '数字' },
|
|
880
|
-
{ key: 'boolean', value: '布尔' },
|
|
881
|
-
{ key: 'array', value: '数组' },
|
|
882
|
-
{ key: 'object', value: '对象' },
|
|
883
|
-
],
|
|
884
|
-
// 布尔默认值的分段单选项(交 SetSegment 渲染,即属性面板同款 .st-seg):
|
|
885
|
-
// 值仍以文本承载('true' / 'false'),与 parseDefault 的解析口径一致
|
|
886
|
-
varBoolOptions: [
|
|
887
|
-
{ key: 'true', value: 'true' },
|
|
888
|
-
{ key: 'false', value: 'false' },
|
|
889
|
-
],
|
|
890
|
-
// 导入来源元数据(Phase E):GET /__import-meta/:id,无侧车时为 null
|
|
891
|
-
importMeta: null,
|
|
892
|
-
tabs: [
|
|
893
|
-
{ key: 'vars', label: '变量' },
|
|
894
|
-
{ key: 'structure', label: '组件结构' },
|
|
895
|
-
{ key: 'meta', label: '卡片来源' },
|
|
896
|
-
],
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
computed: {
|
|
900
|
-
/** 设计期 mock 覆盖表:schema.dictionary.mocks(key → 值);m 徽标与 mock 编辑器的唯一数据源 */
|
|
901
|
-
mocks() {
|
|
902
|
-
return readMocks(this.schema) || {}
|
|
903
|
-
},
|
|
904
|
-
/** 变量定义列表:schema.dictionary.inputParams(变量 tab 的唯一数据源) */
|
|
905
|
-
inputParams() {
|
|
906
|
-
const dict = this.schema && this.schema.dictionary
|
|
907
|
-
return dict && Array.isArray(dict.inputParams) ? dict.inputParams : []
|
|
908
|
-
},
|
|
909
|
-
/** 删除确认文案:点名变量与影响面 */
|
|
910
|
-
/** 待删变量的引用统计(绑定表达式 / 事件脚本 / 其它文本),供删除确认分级 */
|
|
911
|
-
delVarRefs() {
|
|
912
|
-
const p = this.delVarTarget == null ? null : this.inputParams[this.delVarTarget]
|
|
913
|
-
return this.scanVarRefs(p && p.key)
|
|
914
|
-
},
|
|
915
|
-
/** 被引用 = danger(红·禁止图标,明确破坏面);未被引用 = warn(橙黄·三角,普通不可逆操作) */
|
|
916
|
-
delVarSeverity() {
|
|
917
|
-
const r = this.delVarRefs
|
|
918
|
-
return r.binding + r.event + r.other > 0 ? 'danger' : 'warn'
|
|
919
|
-
},
|
|
920
|
-
delVarConfirmText() {
|
|
921
|
-
return this.delVarSeverity === 'danger' ? '仍要删除' : '删除'
|
|
922
|
-
},
|
|
923
|
-
delVarMessage() {
|
|
924
|
-
const p = this.delVarTarget == null ? null : this.inputParams[this.delVarTarget]
|
|
925
|
-
if (!p) return ''
|
|
926
|
-
// 中文名与 key 相同时不重复展示括号
|
|
927
|
-
const nm = p.name && p.name !== p.key ? '(' + p.name + ')' : ''
|
|
928
|
-
const r = this.delVarRefs
|
|
929
|
-
const total = r.binding + r.event + r.other
|
|
930
|
-
if (total > 0) {
|
|
931
|
-
const parts = []
|
|
932
|
-
if (r.binding) parts.push(`绑定表达式 ${r.binding} 处`)
|
|
933
|
-
if (r.event) parts.push(`事件脚本 ${r.event} 处`)
|
|
934
|
-
if (r.other) parts.push(`其它文本 ${r.other} 处`)
|
|
935
|
-
return `变量「${p.key}」${nm}仍被${parts.join('、')}引用(共 ${total} 处)。删除后这些引用将取不到值,画布可能出现空白或执行报错,请先迁移再删除。`
|
|
936
|
-
}
|
|
937
|
-
return `变量「${p.key}」${nm}当前未被任何绑定表达式或事件脚本引用。删除后变量定义与 mock 值一并移除,不可恢复。`
|
|
938
|
-
},
|
|
939
|
-
/** 服务日志空态标题:按连接态区分「没配 / 没连上 / 连上了但还没输出」 */
|
|
940
|
-
svcEmptyText() {
|
|
941
|
-
if (this.devSvcConn === 'unconfigured') return '未配置本地开发服务'
|
|
942
|
-
if (this.devSvcConn === 'open') return '暂无服务日志'
|
|
943
|
-
return '本地服务未连接'
|
|
944
|
-
},
|
|
945
|
-
svcEmptyHint() {
|
|
946
|
-
if (this.svcLogErr) {
|
|
947
|
-
return '取历史失败:' + this.svcLogErr + '(本地服务需升级到支持 /__logs 的 cardbuddy 版本)'
|
|
948
|
-
}
|
|
949
|
-
if (this.devSvcConn === 'unconfigured') {
|
|
950
|
-
return '在顶栏「连接」中配置本地开发服务地址后,这里会实时镜像该服务终端的彩色输出'
|
|
951
|
-
}
|
|
952
|
-
if (this.devSvcConn === 'open') {
|
|
953
|
-
return 'cardbuddy 终端的输出(含配色)会实时镜像到这里;试试同步、保存、校验或截图'
|
|
954
|
-
}
|
|
955
|
-
return '连接恢复后自动继续;服务端保留最近 ' + SVC_MAX_LINES + ' 行历史,重连不丢'
|
|
956
|
-
},
|
|
957
|
-
// 依赖 revision,使 setData 后数据绑定实时刷新
|
|
958
|
-
scopeText() {
|
|
959
|
-
void this.rt.revision
|
|
960
|
-
try {
|
|
961
|
-
return JSON.stringify(this.rt.rootScope, null, 2)
|
|
962
|
-
} catch (e) {
|
|
963
|
-
return '(无法序列化)'
|
|
964
|
-
}
|
|
965
|
-
},
|
|
966
|
-
// 绑定类错误(bind / vshow / render / refresh),依赖 errorRev 保证实时刷新
|
|
967
|
-
// 同 apiCalls:必须 slice() 出新引用,否则 Vue 3.4+ 的 computed 值稳定性
|
|
968
|
-
// 会判定「值没变」而不通知渲染 effect,状态栏计数将停留在旧值。
|
|
969
|
-
bindingErrors() {
|
|
970
|
-
void this.rt.errorRev
|
|
971
|
-
return this.rt.errors.slice()
|
|
972
|
-
},
|
|
973
|
-
// 事件执行错误(customAction / listener),由运行时单独存放,跨渲染保留
|
|
974
|
-
eventErrors() {
|
|
975
|
-
void this.rt.errorRev
|
|
976
|
-
return this.rt.eventErrors.slice()
|
|
977
|
-
},
|
|
978
|
-
// 事件主动日志(console.*),依赖 logRev 保证实时刷新
|
|
979
|
-
logs() {
|
|
980
|
-
void this.rt.logRev
|
|
981
|
-
return this.rt.eventLogs.slice()
|
|
982
|
-
},
|
|
983
|
-
/** 事件面板合并计数器:事件执行错误 + 主动日志(状态栏单一「事件」指示器使用) */
|
|
984
|
-
eventCount() {
|
|
985
|
-
return this.eventErrors.length + this.logs.length
|
|
986
|
-
},
|
|
987
|
-
/** 合并指示器配色:有错误→红;仅日志→蓝;皆无→灰 */
|
|
988
|
-
eventStatusClass() {
|
|
989
|
-
if (this.eventErrors.length) return 'is-error'
|
|
990
|
-
if (this.logs.length) return 'is-log'
|
|
991
|
-
return 'is-ok'
|
|
992
|
-
},
|
|
993
|
-
eventStatusTitle() {
|
|
994
|
-
if (this.eventErrors.length) return '查看事件(含错误)'
|
|
995
|
-
if (this.logs.length) return '查看事件(含日志)'
|
|
996
|
-
return '无事件记录'
|
|
997
|
-
},
|
|
998
|
-
panelErrors() {
|
|
999
|
-
return this.errorPanel === 'event' ? this.eventErrors : this.bindingErrors
|
|
1000
|
-
},
|
|
1001
|
-
compNames() {
|
|
1002
|
-
const map = {}
|
|
1003
|
-
for (const key in COMPONENT_META) {
|
|
1004
|
-
map[key] = COMPONENT_META[key].name || key
|
|
1005
|
-
}
|
|
1006
|
-
map.Root = '根'
|
|
1007
|
-
return map
|
|
1008
|
-
},
|
|
1009
|
-
/** 剪贴板是否有可粘贴节点(实时反映 nodeClipboard.node 是否为空) */
|
|
1010
|
-
canPaste() {
|
|
1011
|
-
return !!nodeClipboard.node
|
|
1012
|
-
},
|
|
1013
|
-
/**
|
|
1014
|
-
* 运行态是否与「绑定数据」基线(rt.rootScope)发生偏离。
|
|
1015
|
-
* 一旦用户在调试中改动运行态(输入 / 选择 / globalSet 等),state 即与 rootScope
|
|
1016
|
-
* 不一致 → 状态栏「运行态」小点变黄,提示当前有未落库的临时调试值。
|
|
1017
|
-
* 读取 state/rootScope 的响应式键建立细粒度依赖;revision 覆盖换卡 / reseed。
|
|
1018
|
-
*/
|
|
1019
|
-
stateDirty() {
|
|
1020
|
-
void this.rt.revision
|
|
1021
|
-
return !deepEqual(this.rt.state, this.rt.rootScope)
|
|
1022
|
-
},
|
|
1023
|
-
},
|
|
1024
|
-
watch: {
|
|
1025
|
-
// 服务日志采用懒订阅:面板打开才建 SSE(浏览器对同源并发连接有限,不与
|
|
1026
|
-
// cards-changed / code-bridge 常驻连接抢位),关闭即退订,历史由服务端缓冲补。
|
|
1027
|
-
errorPanel(mode) {
|
|
1028
|
-
if (mode === 'svclog') this.startSvcLog()
|
|
1029
|
-
else this.stopSvcLog()
|
|
1030
|
-
},
|
|
1031
|
-
// 切换卡片时退出 Raw 模式,避免编辑内容与新卡片数据不一致;
|
|
1032
|
-
// 同时关闭属性面板,避免展示旧卡片节点;并拉取导入来源元数据。
|
|
1033
|
-
cardId: {
|
|
1034
|
-
immediate: true,
|
|
1035
|
-
handler() {
|
|
1036
|
-
this.rawError = ''
|
|
1037
|
-
this.destroyJar()
|
|
1038
|
-
// 换卡回到变量列表视图,并关掉可能开着的变量弹窗 / 删除确认
|
|
1039
|
-
this.varsMode = 'list'
|
|
1040
|
-
this.varDlg = null
|
|
1041
|
-
this.varDlgError = ''
|
|
1042
|
-
this.delVarTarget = null
|
|
1043
|
-
this.inspectingComp = null
|
|
1044
|
-
this.editingEventComp = null
|
|
1045
|
-
this.importMeta = null
|
|
1046
|
-
// 懒加载:仅当用户停留在「来源」tab 时才拉取(见 maybeFetchMeta),
|
|
1047
|
-
// 避免每次切换/加载卡片都向无侧车的本地卡打 404(问题 #7)。
|
|
1048
|
-
this.maybeFetchMeta()
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
// 预览被重新载入(刷新按钮 / 同步后自动重载 / 撤销重做)时:cardId 未变,
|
|
1052
|
-
// 但磁盘上的 .import-meta.json 可能已被「同步远程」更新,须同步重拉来源元数据。
|
|
1053
|
-
reloadToken() {
|
|
1054
|
-
this.maybeFetchMeta()
|
|
1055
|
-
},
|
|
1056
|
-
// 切到「来源」tab 时按需拉取导入来源元数据(懒加载,消除自动 404)。
|
|
1057
|
-
activeTab(n) {
|
|
1058
|
-
if (n === 'meta') this.fetchImportMeta()
|
|
1059
|
-
// 离开变量 tab:mock 编辑器随视图卸载,销毁 CodeJar 防泄漏;回到列表视图
|
|
1060
|
-
if (n !== 'vars') {
|
|
1061
|
-
this.varsMode = 'list'
|
|
1062
|
-
this.rawError = ''
|
|
1063
|
-
this.destroyJar()
|
|
1064
|
-
this.varDlg = null
|
|
1065
|
-
}
|
|
1066
|
-
},
|
|
1067
|
-
// 变量弹窗开合:打开且为数组 / 对象类型时挂载 CodeJar 代码编辑区;关闭即销毁
|
|
1068
|
-
varDlg(n) {
|
|
1069
|
-
if (!n) {
|
|
1070
|
-
this.destroyDlgJar()
|
|
1071
|
-
return
|
|
1072
|
-
}
|
|
1073
|
-
this.varDlgError = ''
|
|
1074
|
-
if (this.isComplexType(n.form.type)) this.$nextTick(() => this.initDlgJar())
|
|
1075
|
-
},
|
|
1076
|
-
// 类型切换:四种默认值控件互换,旧文本按新类型规整(能沿用则沿用,不能则给该类型的空态);
|
|
1077
|
-
// 容器类型还要等 DOM 换出后重新挂 CodeJar
|
|
1078
|
-
'varDlg.form.type'(n, o) {
|
|
1079
|
-
if (!this.varDlg || n === o) return
|
|
1080
|
-
this.varDlg.form.defaultValue = this.normalizeDefaultText(n, this.varDlg.form.defaultValue)
|
|
1081
|
-
if (this.isComplexType(n)) this.$nextTick(() => this.initDlgJar())
|
|
1082
|
-
else this.destroyDlgJar()
|
|
1083
|
-
},
|
|
1084
|
-
// 进入 mock 二级视图:序列化 rootScope 交 CodeJar 托管;退出即销毁实例
|
|
1085
|
-
varsMode(n) {
|
|
1086
|
-
if (n === 'mock') {
|
|
1087
|
-
this.seedRawFromMocks()
|
|
1088
|
-
this.$nextTick(() => this.initJar())
|
|
1089
|
-
} else {
|
|
1090
|
-
this.rawError = ''
|
|
1091
|
-
this.destroyJar()
|
|
1092
|
-
}
|
|
1093
|
-
},
|
|
1094
|
-
// schema 引用变化(撤销/重做、刷新重载会整体替换对象;就地编辑只 mutate 不换引用)
|
|
1095
|
-
// 时关闭已打开的节点属性/事件面板,避免面板持有已被替换的旧预览节点引用。
|
|
1096
|
-
schema() {
|
|
1097
|
-
this.inspectingComp = null
|
|
1098
|
-
this.editingEventComp = null
|
|
1099
|
-
// schema 整体被替换(重置 / 撤销重做 / 外部重载):mock 视图开着时同步重播种,
|
|
1100
|
-
// 否则 CodeJar 会停留在「应用」前的旧文本,重置按钮看起来像没生效
|
|
1101
|
-
if (this.varsMode === 'mock') this.seedRawFromMocks()
|
|
1102
|
-
},
|
|
1103
|
-
},
|
|
1104
|
-
mounted() {
|
|
1105
|
-
// 初始化两个底部面板高度为持久化值(默认 240px)。拖拽由面板头部(已有节点,
|
|
1106
|
-
// 非专门拖拽节点)经 startResize 处理,与预览区宽度调节同一套 mousedown 机制。
|
|
1107
|
-
const h = this.panelH
|
|
1108
|
-
if (this.$refs.statePanel) this.$refs.statePanel.style.height = h + 'px'
|
|
1109
|
-
if (this.$refs.errPanel) this.$refs.errPanel.style.height = h + 'px'
|
|
1110
|
-
},
|
|
1111
|
-
beforeUnmount() {
|
|
1112
|
-
// DebugPanel 一般常驻,但保险起见卸载时销毁 CodeJar、清理可能残留的拖拽监听
|
|
1113
|
-
this.destroyJar()
|
|
1114
|
-
this.destroyDlgJar()
|
|
1115
|
-
this.cancelResize()
|
|
1116
|
-
this.stopSvcLog()
|
|
1117
|
-
},
|
|
1118
|
-
methods: {
|
|
1119
|
-
// 三个底部面板互斥:运行态 / 变量绑定 / 事件 不会同时出现。
|
|
1120
|
-
// 点开运行态即关闭错误/事件面板。
|
|
1121
|
-
toggleStatePanel() {
|
|
1122
|
-
this.statePanelOpen = !this.statePanelOpen
|
|
1123
|
-
if (this.statePanelOpen) this.errorPanel = ''
|
|
1124
|
-
},
|
|
1125
|
-
toggleErrorPanel(kind) {
|
|
1126
|
-
// 再次点击同一指示器则收起;点击另一指示器则切换列表
|
|
1127
|
-
this.errorPanel = this.errorPanel === kind ? '' : kind
|
|
1128
|
-
// 与运行态面板互斥:点开错误/事件面板即关闭运行态
|
|
1129
|
-
if (this.errorPanel) {
|
|
1130
|
-
this.statePanelOpen = false
|
|
1131
|
-
// 事件面板展开时自动落到更值得关注的一侧:有执行错误看错误,否则看日志
|
|
1132
|
-
if (kind === 'event') this.eventTab = this.eventErrors.length ? 'err' : 'log'
|
|
1133
|
-
}
|
|
1134
|
-
},
|
|
1135
|
-
/**
|
|
1136
|
-
* 拖拽调整底部面板(运行态 / 变量绑定 / 事件)高度:挂在面板头部(已有 .statepanel__head /
|
|
1137
|
-
* .errpanel__head,无专门拖拽节点),与预览区宽度调节同一套 mousedown 机制。
|
|
1138
|
-
* 在头部按下后监听 document 的 mousemove / mouseup;向上拖(clientY 减小)增大面板高度,
|
|
1139
|
-
* 向下拖减小,符合底部停靠面板的直觉。高度夹在 [120px, 视窗 60%],并实时同步到两个面板
|
|
1140
|
-
* (三者互斥共用 panelH),鼠标松开才持久化到 localStorage(避免拖拽过程频繁写盘)。
|
|
1141
|
-
* 头部上的关闭/清空等按钮不触发拖拽,避免与点击冲突。
|
|
1142
|
-
*/
|
|
1143
|
-
startResize(e) {
|
|
1144
|
-
if (e.target.closest('button')) return
|
|
1145
|
-
this.panelDragging = true
|
|
1146
|
-
const startY = e.clientY
|
|
1147
|
-
const startH = this.panelH
|
|
1148
|
-
const minH = 120
|
|
1149
|
-
const maxH = Math.max(minH, Math.round(window.innerHeight * 0.6))
|
|
1150
|
-
const apply = (h) => {
|
|
1151
|
-
this.panelH = h
|
|
1152
|
-
if (this.$refs.statePanel) this.$refs.statePanel.style.height = h + 'px'
|
|
1153
|
-
if (this.$refs.errPanel) this.$refs.errPanel.style.height = h + 'px'
|
|
1154
|
-
}
|
|
1155
|
-
const onMove = (ev) => {
|
|
1156
|
-
const delta = startY - ev.clientY // 向上拖 = 正值 = 面板变高
|
|
1157
|
-
apply(Math.min(maxH, Math.max(minH, startH + delta)))
|
|
1158
|
-
}
|
|
1159
|
-
const onUp = () => {
|
|
1160
|
-
document.body.style.cursor = ''
|
|
1161
|
-
document.body.style.userSelect = ''
|
|
1162
|
-
document.removeEventListener('mousemove', onMove)
|
|
1163
|
-
document.removeEventListener('mouseup', onUp)
|
|
1164
|
-
this._resizeMove = this._resizeUp = null
|
|
1165
|
-
this.panelDragging = false
|
|
1166
|
-
this.savePanelH()
|
|
1167
|
-
}
|
|
1168
|
-
this._resizeMove = onMove
|
|
1169
|
-
this._resizeUp = onUp
|
|
1170
|
-
document.body.style.cursor = 'row-resize'
|
|
1171
|
-
document.body.style.userSelect = 'none'
|
|
1172
|
-
document.addEventListener('mousemove', onMove)
|
|
1173
|
-
document.addEventListener('mouseup', onUp)
|
|
1174
|
-
},
|
|
1175
|
-
/** 清理可能残留的拖拽监听(组件卸载或异常中断时调用) */
|
|
1176
|
-
cancelResize() {
|
|
1177
|
-
if (this._resizeMove) document.removeEventListener('mousemove', this._resizeMove)
|
|
1178
|
-
if (this._resizeUp) document.removeEventListener('mouseup', this._resizeUp)
|
|
1179
|
-
this._resizeMove = this._resizeUp = null
|
|
1180
|
-
this.panelDragging = false
|
|
1181
|
-
document.body.style.cursor = ''
|
|
1182
|
-
document.body.style.userSelect = ''
|
|
1183
|
-
},
|
|
1184
|
-
/** 读取持久化的底部面板高度(localStorage)。无记录 / 损坏 / 越界时回退 240px。 */
|
|
1185
|
-
loadPanelH() {
|
|
1186
|
-
try {
|
|
1187
|
-
const v = localStorage.getItem('cardbuddy:debug-panel-height')
|
|
1188
|
-
if (v != null) {
|
|
1189
|
-
const h = parseInt(v, 10)
|
|
1190
|
-
if (!Number.isNaN(h)) {
|
|
1191
|
-
const minH = 120
|
|
1192
|
-
const maxH = Math.max(minH, Math.round(window.innerHeight * 0.6))
|
|
1193
|
-
return Math.min(maxH, Math.max(minH, h))
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
} catch (e) {
|
|
1197
|
-
/* localStorage 不可用时忽略 */
|
|
1198
|
-
}
|
|
1199
|
-
return 240
|
|
1200
|
-
},
|
|
1201
|
-
/** 持久化底部面板高度到 localStorage(px)。 */
|
|
1202
|
-
savePanelH() {
|
|
1203
|
-
try {
|
|
1204
|
-
localStorage.setItem('cardbuddy:debug-panel-height', String(Math.round(this.panelH)))
|
|
1205
|
-
} catch (e) {
|
|
1206
|
-
/* localStorage 不可用时忽略 */
|
|
1207
|
-
}
|
|
1208
|
-
},
|
|
1209
|
-
/** 用 CodeJar 初始化原始 JSON 编辑器:contenteditable + Prism(json) 高亮 */
|
|
1210
|
-
initJar() {
|
|
1211
|
-
const el = this.$refs.rawEditor
|
|
1212
|
-
if (!el) return
|
|
1213
|
-
// 复用 CodeJar 时先销毁旧实例,避免重复绑定到同一 DOM
|
|
1214
|
-
this.destroyJar()
|
|
1215
|
-
const highlight = (editor) => {
|
|
1216
|
-
editor.innerHTML = Prism.highlight(editor.textContent, Prism.languages.json, 'json')
|
|
1217
|
-
}
|
|
1218
|
-
this._jar = CodeJar(el, highlight, {
|
|
1219
|
-
tab: ' ',
|
|
1220
|
-
indentOn: /[({[]$/,
|
|
1221
|
-
addClosing: true,
|
|
1222
|
-
history: true,
|
|
1223
|
-
})
|
|
1224
|
-
this._jar.updateCode(this.rawText || '')
|
|
1225
|
-
this._jar.onUpdate((code) => {
|
|
1226
|
-
this.rawText = code
|
|
1227
|
-
if (this.rawError) this.rawError = '' // 用户再次输入即清除旧错误
|
|
1228
|
-
})
|
|
1229
|
-
// 开发期暴露 Raw 编辑器句柄供 E2E 程序化写入(与 window.__rt 同源约定);生产构建不暴露。
|
|
1230
|
-
// 合成 input 事件驱动不了 CodeJar,E2E 经 updateCode 走真实 onUpdate → 「应用」路径。
|
|
1231
|
-
if (import.meta.env.DEV) window.__rawEditor = this._jar
|
|
1232
|
-
this.$nextTick(() => el.focus())
|
|
1233
|
-
},
|
|
1234
|
-
/** 销毁 CodeJar 实例(退出/保存/卸载/切卡时调用) */
|
|
1235
|
-
destroyJar() {
|
|
1236
|
-
if (this._jar && this._jar.destroy) {
|
|
1237
|
-
this._jar.destroy()
|
|
1238
|
-
}
|
|
1239
|
-
this._jar = null
|
|
1240
|
-
if (typeof window !== 'undefined' && window.__rawEditor) window.__rawEditor = null
|
|
1241
|
-
},
|
|
1242
|
-
/** 变量弹窗「默认值」代码编辑区(数组 / 对象):CodeJar + Prism(json),与 mock Raw 编辑器同方案 */
|
|
1243
|
-
initDlgJar() {
|
|
1244
|
-
const el = this.$refs.dlgCode
|
|
1245
|
-
if (!el) return
|
|
1246
|
-
this.destroyDlgJar()
|
|
1247
|
-
const highlight = (editor) => {
|
|
1248
|
-
editor.innerHTML = Prism.highlight(editor.textContent, Prism.languages.json, 'json')
|
|
1249
|
-
}
|
|
1250
|
-
this._dlgJar = CodeJar(el, highlight, {
|
|
1251
|
-
tab: ' ',
|
|
1252
|
-
indentOn: /[({[]$/,
|
|
1253
|
-
addClosing: true,
|
|
1254
|
-
history: true,
|
|
1255
|
-
})
|
|
1256
|
-
this._dlgJar.updateCode(this.varDlg ? this.varDlg.form.defaultValue : '')
|
|
1257
|
-
this._dlgJar.onUpdate((code) => {
|
|
1258
|
-
if (this.varDlg) this.varDlg.form.defaultValue = code
|
|
1259
|
-
if (this.varDlgError) this.varDlgError = ''
|
|
1260
|
-
})
|
|
1261
|
-
// 开发期暴露弹窗代码区句柄供 E2E 程序化写入(合成 input 事件驱动不了 CodeJar);生产不暴露
|
|
1262
|
-
if (import.meta.env.DEV) window.__dlgEditor = this._dlgJar
|
|
1263
|
-
},
|
|
1264
|
-
destroyDlgJar() {
|
|
1265
|
-
if (this._dlgJar && this._dlgJar.destroy) this._dlgJar.destroy()
|
|
1266
|
-
this._dlgJar = null
|
|
1267
|
-
if (typeof window !== 'undefined' && window.__dlgEditor) window.__dlgEditor = null
|
|
1268
|
-
},
|
|
1269
|
-
/** 标量 → 容器切换时的种子文本:旧文本形状匹配则格式化沿用,否则给空容器骨架 */
|
|
1270
|
-
complexSeed(type, text) {
|
|
1271
|
-
const empty = type === 'array' ? '[]' : '{}'
|
|
1272
|
-
const t = String(text == null ? '' : text).trim()
|
|
1273
|
-
if (!t) return empty
|
|
1274
|
-
try {
|
|
1275
|
-
const v = JSON.parse(t)
|
|
1276
|
-
const shapeOk = type === 'array' ? Array.isArray(v) : !!v && typeof v === 'object'
|
|
1277
|
-
return shapeOk ? JSON.stringify(v, null, 2) : empty
|
|
1278
|
-
} catch (e) {
|
|
1279
|
-
return empty
|
|
1280
|
-
}
|
|
1281
|
-
},
|
|
1282
|
-
/**
|
|
1283
|
-
* 默认值统一以文本承载,但四种类型「合法文本」的形态不同:切类型 / 打开弹窗时按目标类型规整,
|
|
1284
|
-
* 免得把上一类型的字面量塞进新控件(如布尔的 'true' 落进数字步进框)。
|
|
1285
|
-
* 字符串原样保留(含首尾空白与换行)——占位提示已明示「字符串原样保存」。
|
|
1286
|
-
*/
|
|
1287
|
-
normalizeDefaultText(type, text) {
|
|
1288
|
-
if (this.isComplexType(type)) return this.complexSeed(type, text)
|
|
1289
|
-
const t = String(text == null ? '' : text).trim()
|
|
1290
|
-
if (type === 'number') return t !== '' && Number.isFinite(Number(t)) ? String(Number(t)) : ''
|
|
1291
|
-
if (type === 'boolean') return /^(true|1)$/i.test(t) ? 'true' : 'false'
|
|
1292
|
-
return String(text == null ? '' : text)
|
|
1293
|
-
},
|
|
1294
|
-
/** 应用 Raw 编辑:校验 JSON 合法性与形状 → 写入 dictionary.mocks → 重算预览数据(默认值之上叠 mock);
|
|
1295
|
-
* 写入即按 2 空格标准格式回灌编辑器;留在 mock 视图继续编辑 */
|
|
1296
|
-
saveRaw() {
|
|
1297
|
-
// 先容错清理(注释 / 尾逗号)再严格校验:报错判定基于清理后的文本
|
|
1298
|
-
const parsed = parseJsonLenient(this.rawText)
|
|
1299
|
-
if (parsed.error) {
|
|
1300
|
-
this.rawError = 'JSON 格式错误:' + parsed.error
|
|
1301
|
-
return
|
|
1302
|
-
}
|
|
1303
|
-
if (parsed.value === null || typeof parsed.value !== 'object' || Array.isArray(parsed.value)) {
|
|
1304
|
-
this.rawError = '变量 mock 必须是 JSON 对象({ ... })'
|
|
1305
|
-
return
|
|
1306
|
-
}
|
|
1307
|
-
const schema = this.schema
|
|
1308
|
-
if (!schema) {
|
|
1309
|
-
this.rawError = '当前未载入卡片'
|
|
1310
|
-
return
|
|
1311
|
-
}
|
|
1312
|
-
if (!schema.dictionary || typeof schema.dictionary !== 'object') {
|
|
1313
|
-
schema.dictionary = { dictionaryList: [], inputParams: [] }
|
|
1314
|
-
}
|
|
1315
|
-
schema.dictionary.mocks = parsed.value
|
|
1316
|
-
// 画布数据 = defaultValue 之上按 key 叠加非空 mock;watcher 会把 rootScope 回写两仓
|
|
1317
|
-
this.rt.setRootScope(buildDataFromSchema(schema, { includeMocks: true }))
|
|
1318
|
-
const formatted = JSON.stringify(parsed.value, null, 2)
|
|
1319
|
-
this.rawText = formatted
|
|
1320
|
-
if (this._jar) this._jar.updateCode(formatted)
|
|
1321
|
-
// 落定一笔撤销记录(与节点属性「应用」同路径)
|
|
1322
|
-
this.$nextTick(() => this.onCommit())
|
|
1323
|
-
this.rawError = ''
|
|
1324
|
-
},
|
|
1325
|
-
async copyScope() {
|
|
1326
|
-
// 复制当前 mock 文档(编辑器所见即所得)
|
|
1327
|
-
const text = this.rawText || ''
|
|
1328
|
-
const ok = await copyText(text)
|
|
1329
|
-
if (ok) {
|
|
1330
|
-
this.copied = true
|
|
1331
|
-
if (this.copyTimer) clearTimeout(this.copyTimer)
|
|
1332
|
-
this.copyTimer = setTimeout(() => {
|
|
1333
|
-
this.copied = false
|
|
1334
|
-
}, 1500)
|
|
1335
|
-
}
|
|
1336
|
-
},
|
|
1337
|
-
/** 结构树「事件」徽标:打开属性面板并直接落到事件页签 */
|
|
1338
|
-
onEditEvent(comp) {
|
|
1339
|
-
this.editingEventComp = comp
|
|
1340
|
-
this.eventEditorKey++
|
|
1341
|
-
},
|
|
1342
|
-
/** 节点属性 / 事件面板「应用」(实时编辑):仅内存重建预览,不落盘、不记历史 */
|
|
1343
|
-
onApply() {
|
|
1344
|
-
this.$emit('schema-changed')
|
|
1345
|
-
},
|
|
1346
|
-
/** 节点属性 / 事件面板「应用」按钮:重建预览 + 提交一次历史(写入 undo/redo 栈) */
|
|
1347
|
-
onCommit() {
|
|
1348
|
-
this.$emit('schema-commit')
|
|
1349
|
-
},
|
|
1350
|
-
// —— 变量管理(schema.dictionary.inputParams)——
|
|
1351
|
-
/** 是否已配置 mock(dictionary.mocks 有数据;与 buildDataFromSchema 同判据)→ 列表行 m 徽标 */
|
|
1352
|
-
hasMock(param) {
|
|
1353
|
-
return hasMockValue(this.mocks, param.key)
|
|
1354
|
-
},
|
|
1355
|
-
/** 复制变量名(当前 key,绑定以 $key 引用);成功后图标短暂变 ✓(与接口调用面板同款反馈节奏) */
|
|
1356
|
-
async copyVarName(param, i) {
|
|
1357
|
-
const text = param && param.key ? String(param.key) : ''
|
|
1358
|
-
if (!text) return
|
|
1359
|
-
const ok = await copyText(text)
|
|
1360
|
-
if (ok) {
|
|
1361
|
-
this.copiedVar = i
|
|
1362
|
-
if (this.varCopyTimer) clearTimeout(this.varCopyTimer)
|
|
1363
|
-
this.varCopyTimer = setTimeout(() => {
|
|
1364
|
-
if (this.copiedVar === i) this.copiedVar = null
|
|
1365
|
-
}, 1500)
|
|
1366
|
-
}
|
|
1367
|
-
},
|
|
1368
|
-
isComplexType(type) {
|
|
1369
|
-
return type === 'array' || type === 'object'
|
|
1370
|
-
},
|
|
1371
|
-
// 布尔改用单选(无占位提示),故这里只剩数字与字符串两支
|
|
1372
|
-
scalarPlaceholder(type) {
|
|
1373
|
-
if (type === 'number') return '数字,如 0 / 99.5'
|
|
1374
|
-
return '字符串原样保存'
|
|
1375
|
-
},
|
|
1376
|
-
onVarTypeChange(type) {
|
|
1377
|
-
if (this.varDlg) this.varDlg.form.type = type
|
|
1378
|
-
},
|
|
1379
|
-
/** 打开添加(index=null)/ 编辑(index=下标)弹窗;默认值统一以文本承载,确定时按类型解析 */
|
|
1380
|
-
openVarDlg(index) {
|
|
1381
|
-
const param = index == null ? null : this.inputParams[index]
|
|
1382
|
-
const type = param ? param.type || 'string' : 'string'
|
|
1383
|
-
const raw = param ? param.defaultValue : undefined
|
|
1384
|
-
let text = ''
|
|
1385
|
-
if (raw !== undefined && raw !== null) {
|
|
1386
|
-
text = typeof raw === 'object' ? JSON.stringify(raw, null, 2) : String(raw)
|
|
1387
|
-
}
|
|
1388
|
-
// 按类型规整:布尔补出 'false' 让单选有落点,数字剔除非法字面量,容器格式化
|
|
1389
|
-
text = this.normalizeDefaultText(type, text)
|
|
1390
|
-
this.varDlgError = ''
|
|
1391
|
-
this.varDlg = {
|
|
1392
|
-
index,
|
|
1393
|
-
form: {
|
|
1394
|
-
key: param ? param.key || '' : '',
|
|
1395
|
-
name: param ? param.name || '' : '',
|
|
1396
|
-
type,
|
|
1397
|
-
defaultValue: text,
|
|
1398
|
-
desc: param ? param.desc || '' : '',
|
|
1399
|
-
},
|
|
1400
|
-
}
|
|
1401
|
-
},
|
|
1402
|
-
/** 默认值文本 → 类型化值;返回 { value } 或 { error } */
|
|
1403
|
-
parseDefault(type, text) {
|
|
1404
|
-
const t = String(text == null ? '' : text).trim()
|
|
1405
|
-
if (this.isComplexType(type)) {
|
|
1406
|
-
if (!t) return { value: type === 'array' ? [] : {} }
|
|
1407
|
-
// 与 mock 编辑器同套容错:先清理注释 / 尾逗号再严格校验
|
|
1408
|
-
const parsed = parseJsonLenient(t)
|
|
1409
|
-
if (parsed.error) return { error: '默认值不是合法 JSON:' + parsed.error }
|
|
1410
|
-
const v = parsed.value
|
|
1411
|
-
if (type === 'array' && !Array.isArray(v)) return { error: '默认值需为 JSON 数组' }
|
|
1412
|
-
if (type === 'object' && (typeof v !== 'object' || v === null || Array.isArray(v))) return { error: '默认值需为 JSON 对象' }
|
|
1413
|
-
return { value: v }
|
|
1414
|
-
}
|
|
1415
|
-
if (type === 'number') {
|
|
1416
|
-
if (!t) return { value: 0 }
|
|
1417
|
-
const n = Number(t)
|
|
1418
|
-
if (!Number.isFinite(n)) return { error: '默认值不是合法数字' }
|
|
1419
|
-
return { value: n }
|
|
1420
|
-
}
|
|
1421
|
-
if (type === 'boolean') {
|
|
1422
|
-
if (!t) return { value: false }
|
|
1423
|
-
if (t === 'true' || t === '1') return { value: true }
|
|
1424
|
-
if (t === 'false' || t === '0') return { value: false }
|
|
1425
|
-
return { error: '布尔默认值仅接受 true / false' }
|
|
1426
|
-
}
|
|
1427
|
-
return { value: String(text == null ? '' : text) }
|
|
1428
|
-
},
|
|
1429
|
-
/** 确定:写入 inputParams 并同步 rootScope(mock 基线),再记一笔历史 */
|
|
1430
|
-
confirmVarDlg() {
|
|
1431
|
-
const dlg = this.varDlg
|
|
1432
|
-
if (!dlg) return
|
|
1433
|
-
const form = dlg.form
|
|
1434
|
-
const key = String(form.key || '').trim()
|
|
1435
|
-
const name = String(form.name || '').trim()
|
|
1436
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
1437
|
-
this.varDlgError = '变量名需为字母 / 下划线开头的英文标识(绑定以 $key 引用)'
|
|
1438
|
-
return
|
|
1439
|
-
}
|
|
1440
|
-
if (this.inputParams.some((p, i) => p.key === key && i !== dlg.index)) {
|
|
1441
|
-
this.varDlgError = '已存在同名变量 ' + key
|
|
1442
|
-
return
|
|
1443
|
-
}
|
|
1444
|
-
if (!name) {
|
|
1445
|
-
this.varDlgError = '中文名不能为空'
|
|
1446
|
-
return
|
|
1447
|
-
}
|
|
1448
|
-
const parsed = this.parseDefault(form.type, form.defaultValue)
|
|
1449
|
-
if (parsed.error) {
|
|
1450
|
-
this.varDlgError = parsed.error
|
|
1451
|
-
return
|
|
1452
|
-
}
|
|
1453
|
-
// 容器类型:保存即按 2 空格标准 JSON 格式化回灌(编辑器与存档同形)
|
|
1454
|
-
if (this.isComplexType(form.type)) {
|
|
1455
|
-
const formatted = JSON.stringify(parsed.value, null, 2)
|
|
1456
|
-
form.defaultValue = formatted
|
|
1457
|
-
if (this._dlgJar) this._dlgJar.updateCode(formatted)
|
|
1458
|
-
}
|
|
1459
|
-
const schema = this.schema
|
|
1460
|
-
if (!schema) {
|
|
1461
|
-
this.varDlgError = '当前未载入卡片'
|
|
1462
|
-
return
|
|
1463
|
-
}
|
|
1464
|
-
if (!schema.dictionary || typeof schema.dictionary !== 'object') {
|
|
1465
|
-
schema.dictionary = { dictionaryList: [], inputParams: [] }
|
|
1466
|
-
}
|
|
1467
|
-
if (!Array.isArray(schema.dictionary.inputParams)) schema.dictionary.inputParams = []
|
|
1468
|
-
const params = schema.dictionary.inputParams
|
|
1469
|
-
const root = this.rt.rootScope
|
|
1470
|
-
const mocks = readMocks(schema) || {}
|
|
1471
|
-
const value = cloneJson(parsed.value)
|
|
1472
|
-
if (dlg.index == null) {
|
|
1473
|
-
params.push({ key, name, type: form.type, defaultValue: cloneJson(value), desc: String(form.desc || ''), rule: { type: '', options: {} }, children: [] })
|
|
1474
|
-
// 已有 mock 覆盖时画布继续用 mock,默认值只入 defaultValue 仓
|
|
1475
|
-
if (!hasMockValue(mocks, key)) root[key] = value
|
|
1476
|
-
} else {
|
|
1477
|
-
const param = params[dlg.index]
|
|
1478
|
-
if (!param) return
|
|
1479
|
-
const oldKey = param.key
|
|
1480
|
-
param.key = key
|
|
1481
|
-
param.name = name
|
|
1482
|
-
param.type = form.type
|
|
1483
|
-
param.desc = String(form.desc || '')
|
|
1484
|
-
param.defaultValue = cloneJson(value)
|
|
1485
|
-
if (oldKey !== key) {
|
|
1486
|
-
// 改名时 mock 覆盖跟随迁移,避免旧 key 的 mock 变成孤儿 / 新 key 丢覆盖
|
|
1487
|
-
if (Object.prototype.hasOwnProperty.call(mocks, oldKey)) {
|
|
1488
|
-
mocks[key] = mocks[oldKey]
|
|
1489
|
-
delete mocks[oldKey]
|
|
1490
|
-
}
|
|
1491
|
-
if (root && Object.prototype.hasOwnProperty.call(root, oldKey)) delete root[oldKey]
|
|
1492
|
-
}
|
|
1493
|
-
if (!hasMockValue(mocks, key)) root[key] = value
|
|
1494
|
-
}
|
|
1495
|
-
this.varDlg = null
|
|
1496
|
-
this.onCommit()
|
|
1497
|
-
},
|
|
1498
|
-
askDeleteVar(index) {
|
|
1499
|
-
this.delVarTarget = index
|
|
1500
|
-
},
|
|
1501
|
-
/** 删除:inputParams 移除 + rootScope 清键,再记一笔历史 */
|
|
1502
|
-
/**
|
|
1503
|
-
* 扫描变量引用面:遍历 schema(跳过 dictionary 两仓与 compiled 回声产物),
|
|
1504
|
-
* 按两种引用形态匹配——表达式里的 `$key`、事件脚本里的 `state.key` / `state['key']`;
|
|
1505
|
-
* 分类计数:绑定表达式(itemValue 为字符串)/ 事件脚本(code 键)/ 其它文本。
|
|
1506
|
-
* 仅供删除确认分级(被引用=danger / 未被引用=warn),不做写操作。
|
|
1507
|
-
*/
|
|
1508
|
-
scanVarRefs(key) {
|
|
1509
|
-
const refs = { binding: 0, event: 0, other: 0 }
|
|
1510
|
-
const schema = this.schema
|
|
1511
|
-
if (!key || !schema || typeof schema !== 'object') return refs
|
|
1512
|
-
const esc = String(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
1513
|
-
const boundary = '(?![A-Za-z0-9_$])'
|
|
1514
|
-
const dollarRe = new RegExp('\\$' + esc + boundary)
|
|
1515
|
-
const stateRe = new RegExp('state\\s*(?:\\.\\s*' + esc + boundary + '|\\[\\s*[\'"]' + esc + '[\'"]\\s*\\])')
|
|
1516
|
-
const hit = (s) => dollarRe.test(s) || stateRe.test(s)
|
|
1517
|
-
const walk = (v) => {
|
|
1518
|
-
if (v == null) return
|
|
1519
|
-
if (Array.isArray(v)) {
|
|
1520
|
-
for (const item of v) walk(item)
|
|
1521
|
-
return
|
|
1522
|
-
}
|
|
1523
|
-
if (typeof v !== 'object') return
|
|
1524
|
-
for (const [k, val] of Object.entries(v)) {
|
|
1525
|
-
if (k === 'dictionary' || k === 'compiled') continue
|
|
1526
|
-
if (typeof val === 'string') {
|
|
1527
|
-
if (!hit(val)) continue
|
|
1528
|
-
if (k === 'itemValue') refs.binding += 1
|
|
1529
|
-
else if (k === 'code') refs.event += 1
|
|
1530
|
-
else refs.other += 1
|
|
1531
|
-
} else {
|
|
1532
|
-
walk(val)
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
walk(schema)
|
|
1537
|
-
return refs
|
|
1538
|
-
},
|
|
1539
|
-
confirmDeleteVar() {
|
|
1540
|
-
const param = this.delVarTarget == null ? null : this.inputParams[this.delVarTarget]
|
|
1541
|
-
this.delVarTarget = null
|
|
1542
|
-
if (!param || !this.schema || !this.schema.dictionary) return
|
|
1543
|
-
const params = this.schema.dictionary.inputParams || []
|
|
1544
|
-
const idx = params.indexOf(param)
|
|
1545
|
-
if (idx >= 0) params.splice(idx, 1)
|
|
1546
|
-
// mock 覆盖一并清除,避免留下孤儿 key 与误报的 m 徽标
|
|
1547
|
-
const mocks = readMocks(this.schema) || {}
|
|
1548
|
-
if (Object.prototype.hasOwnProperty.call(mocks, param.key)) delete mocks[param.key]
|
|
1549
|
-
const root = this.rt.rootScope
|
|
1550
|
-
if (root && Object.prototype.hasOwnProperty.call(root, param.key)) delete root[param.key]
|
|
1551
|
-
this.onCommit()
|
|
1552
|
-
},
|
|
1553
|
-
/** 从 mock 二级视图回到变量列表(编辑器随视图卸载,watcher 负责销毁 CodeJar) */
|
|
1554
|
-
leaveMock() {
|
|
1555
|
-
this.varsMode = 'list'
|
|
1556
|
-
},
|
|
1557
|
-
/** 以当前 dictionary.mocks 播种 mock 编辑器文本(进入视图 / schema 被替换时共用) */
|
|
1558
|
-
seedRawFromMocks() {
|
|
1559
|
-
this.rawError = ''
|
|
1560
|
-
try {
|
|
1561
|
-
// mock 编辑器只编辑覆盖仓 dictionary.mocks(默认值在变量弹窗里改);空表即 {}
|
|
1562
|
-
this.rawText = JSON.stringify(this.mocks, null, 2)
|
|
1563
|
-
} catch (e) {
|
|
1564
|
-
this.rawText = ''
|
|
1565
|
-
this.rawError = '无法序列化当前数据:' + (e && e.message)
|
|
1566
|
-
return
|
|
1567
|
-
}
|
|
1568
|
-
if (this._jar) this._jar.updateCode(this.rawText)
|
|
1569
|
-
},
|
|
1570
|
-
/** 双击结构树节点:弹出属性面板 */
|
|
1571
|
-
onInspect(comp) {
|
|
1572
|
-
this.inspectingComp = comp
|
|
1573
|
-
this.inspectKey++
|
|
1574
|
-
},
|
|
1575
|
-
onSelectNode(id) {
|
|
1576
|
-
this.$emit('select-node', id)
|
|
1577
|
-
},
|
|
1578
|
-
/** 结构树某行「+」:透传到 App 打开组件选择面板 */
|
|
1579
|
-
onAddNode(comp) {
|
|
1580
|
-
this.$emit('add-node', comp)
|
|
1581
|
-
},
|
|
1582
|
-
onMoveNode(payload) {
|
|
1583
|
-
this.$emit('move-node', payload)
|
|
1584
|
-
},
|
|
1585
|
-
/** 结构树节点 hover:透传到 App,驱动画布参考线框 */
|
|
1586
|
-
onHoverNode(payload) {
|
|
1587
|
-
this.$emit('hover-node', payload)
|
|
1588
|
-
},
|
|
1589
|
-
/** 清空事件主动日志(事件面板「清空日志」按钮);错误不清除 */
|
|
1590
|
-
clearEventLogs() {
|
|
1591
|
-
this.rt.clearEventLogs()
|
|
1592
|
-
},
|
|
1593
|
-
/** 打开服务日志:先补历史(含上次清空后的水位线),再建立 SSE 实时订阅。 */
|
|
1594
|
-
async startSvcLog() {
|
|
1595
|
-
this.svcLogFollow = true
|
|
1596
|
-
await this.pullSvcLogHistory(0)
|
|
1597
|
-
if (this._svcUnsub) return
|
|
1598
|
-
this._svcUnsub = subscribeServiceLog(
|
|
1599
|
-
(payload) => this.appendSvcLogs(payload && payload.lines),
|
|
1600
|
-
(state) => {
|
|
1601
|
-
this.svcLogLive = state === 'open'
|
|
1602
|
-
// 断线重连成功后按 seq 补漏:缓冲仍在服务端,不会丢行也不会重复
|
|
1603
|
-
if (state === 'open' && this.svcLogSeq) this.pullSvcLogHistory(this.svcLogSeq)
|
|
1604
|
-
}
|
|
1605
|
-
)
|
|
1606
|
-
},
|
|
1607
|
-
/** 关闭服务日志:退订 SSE(面板数据保留,下次打开按水位线增量补齐)。 */
|
|
1608
|
-
stopSvcLog() {
|
|
1609
|
-
if (this._svcUnsub) {
|
|
1610
|
-
this._svcUnsub()
|
|
1611
|
-
this._svcUnsub = null
|
|
1612
|
-
}
|
|
1613
|
-
this.svcLogLive = false
|
|
1614
|
-
},
|
|
1615
|
-
/**
|
|
1616
|
-
* 拉取服务日志历史。
|
|
1617
|
-
* @param {number} since 0 = 全量(取最新 SVC_MAX_LINES 行);>0 = 只取 seq 大于它的行
|
|
1618
|
-
*/
|
|
1619
|
-
async pullSvcLogHistory(since) {
|
|
1620
|
-
if (this.svcLogLoading) return
|
|
1621
|
-
this.svcLogLoading = true
|
|
1622
|
-
const from = Math.max(Number(since) || 0, this.svcLogFloor)
|
|
1623
|
-
try {
|
|
1624
|
-
const snap = await fetchServiceLogs({ limit: SVC_MAX_LINES, since: from })
|
|
1625
|
-
this.svcLogErr = ''
|
|
1626
|
-
if (snap && snap.enabled === false) {
|
|
1627
|
-
this.svcLogErr = '服务端已关闭日志流(CARDBUDDY_STREAM_LOGS=0)'
|
|
1628
|
-
} else {
|
|
1629
|
-
this.svcLogDropped = (snap && snap.dropped) || 0
|
|
1630
|
-
if (snap && snap.seq) this.svcLogSeq = Math.max(this.svcLogSeq, snap.seq)
|
|
1631
|
-
const rows = ((snap && snap.lines) || []).map(toSvcRow)
|
|
1632
|
-
// from > 0 是断线补漏:走同一条去重/裁剪/跟随逻辑,避免与实时流重叠
|
|
1633
|
-
if (from) this.appendSvcLogs(rows)
|
|
1634
|
-
else this.svcLogs = rows
|
|
1635
|
-
this.scrollSvcToBottom(true)
|
|
1636
|
-
}
|
|
1637
|
-
} catch (e) {
|
|
1638
|
-
this.svcLogErr = (e && e.message) || String(e)
|
|
1639
|
-
} finally {
|
|
1640
|
-
this.svcLogLoading = false
|
|
1641
|
-
}
|
|
1642
|
-
},
|
|
1643
|
-
/** 追加实时行:按 seq 去重(历史与实时流可能重叠)、裁剪上限、按需跟随滚动。 */
|
|
1644
|
-
appendSvcLogs(lines) {
|
|
1645
|
-
const list = Array.isArray(lines) ? lines : []
|
|
1646
|
-
if (!list.length) return
|
|
1647
|
-
const rows = []
|
|
1648
|
-
for (const l of list) {
|
|
1649
|
-
// 既接受服务端原始行 { seq, t, text },也接受已转换好的面板行(含 html)
|
|
1650
|
-
const row = l && l.html != null ? l : toSvcRow(l)
|
|
1651
|
-
const seq = Number(row.seq) || 0
|
|
1652
|
-
if (seq && seq <= this.svcLogSeq) continue
|
|
1653
|
-
if (seq) this.svcLogSeq = seq
|
|
1654
|
-
rows.push(row)
|
|
1655
|
-
}
|
|
1656
|
-
if (!rows.length) return
|
|
1657
|
-
const next = this.svcLogs.concat(rows)
|
|
1658
|
-
this.svcLogs = next.length > SVC_MAX_LINES ? next.slice(next.length - SVC_MAX_LINES) : next
|
|
1659
|
-
if (this.svcLogFollow) this.scrollSvcToBottom()
|
|
1660
|
-
},
|
|
1661
|
-
clearSvcLogs() {
|
|
1662
|
-
this.svcLogFloor = this.svcLogSeq
|
|
1663
|
-
this.svcLogs = []
|
|
1664
|
-
this.svcLogDropped = 0
|
|
1665
|
-
},
|
|
1666
|
-
/** 复制全部日志为纯文本(去掉 ANSI),便于贴到工单 / IM 里排障。 */
|
|
1667
|
-
async copySvcLogs() {
|
|
1668
|
-
const ok = await copyText(this.svcLogs.map((r) => r.plain).join('\n'))
|
|
1669
|
-
if (!ok) return
|
|
1670
|
-
this.svcCopied = true
|
|
1671
|
-
if (this._svcCopiedTimer) clearTimeout(this._svcCopiedTimer)
|
|
1672
|
-
this._svcCopiedTimer = setTimeout(() => {
|
|
1673
|
-
this.svcCopied = false
|
|
1674
|
-
}, 1200)
|
|
1675
|
-
},
|
|
1676
|
-
toggleSvcFollow() {
|
|
1677
|
-
this.svcLogFollow = !this.svcLogFollow
|
|
1678
|
-
if (this.svcLogFollow) this.scrollSvcToBottom(true)
|
|
1679
|
-
},
|
|
1680
|
-
scrollSvcToBottom(force) {
|
|
1681
|
-
if (!force && !this.svcLogFollow) return
|
|
1682
|
-
this.$nextTick(() => {
|
|
1683
|
-
const el = this.$refs.errBody
|
|
1684
|
-
if (el) el.scrollTop = el.scrollHeight
|
|
1685
|
-
})
|
|
1686
|
-
},
|
|
1687
|
-
/** 面板体滚动:距底 24px 内视为「在底部」并恢复跟随,否则暂停(用户正在往上翻)。 */
|
|
1688
|
-
onErrBodyScroll() {
|
|
1689
|
-
if (this.errorPanel !== 'svclog') return
|
|
1690
|
-
const el = this.$refs.errBody
|
|
1691
|
-
if (!el) return
|
|
1692
|
-
this.svcLogFollow = el.scrollHeight - el.scrollTop - el.clientHeight < 24
|
|
1693
|
-
},
|
|
1694
|
-
/** 日志 / 错误行首时间戳:HH:MM:SS 等宽对齐,单行多带一份定位信息 */
|
|
1695
|
-
fmtTime(t) {
|
|
1696
|
-
if (!t) return '--:--:--'
|
|
1697
|
-
const d = new Date(t)
|
|
1698
|
-
const p = (n) => String(n).padStart(2, '0')
|
|
1699
|
-
return p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
|
|
1700
|
-
},
|
|
1701
|
-
/** 拉取当前卡片的导入来源元数据(Phase E 来源 tab) */
|
|
1702
|
-
maybeFetchMeta() {
|
|
1703
|
-
if (this.activeTab === 'meta') this.fetchImportMeta()
|
|
1704
|
-
},
|
|
1705
|
-
async fetchImportMeta() {
|
|
1706
|
-
const id = this.cardId
|
|
1707
|
-
if (!id) {
|
|
1708
|
-
this.importMeta = null
|
|
1709
|
-
return
|
|
1710
|
-
}
|
|
1711
|
-
try {
|
|
1712
|
-
const res = await apiFetch(`/__import-meta/${encodeURIComponent(id)}`)
|
|
1713
|
-
if (res.ok) {
|
|
1714
|
-
const r = await res.json()
|
|
1715
|
-
this.importMeta = r.meta || null
|
|
1716
|
-
} else {
|
|
1717
|
-
this.importMeta = null
|
|
1718
|
-
}
|
|
1719
|
-
} catch {
|
|
1720
|
-
this.importMeta = null
|
|
1721
|
-
}
|
|
1722
|
-
},
|
|
1723
|
-
/** ISO 时间转人读字符串(本地时区) */
|
|
1724
|
-
formatTime(iso) {
|
|
1725
|
-
if (!iso) return '-'
|
|
1726
|
-
try {
|
|
1727
|
-
return new Date(iso).toLocaleString('zh-CN', { hour12: false })
|
|
1728
|
-
} catch {
|
|
1729
|
-
return iso
|
|
1730
|
-
}
|
|
1731
|
-
},
|
|
1732
|
-
},
|
|
1733
|
-
}
|
|
1734
|
-
</script>
|