cardbuddy 0.6.0
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 +211 -0
- package/dist/cli.js +76 -0
- package/dist/config.js +63 -0
- package/dist/core.js +2 -0
- package/dist/preview-editor/assets/apple-touch-icon-BQcES9QK.png +0 -0
- package/dist/preview-editor/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/preview-editor/assets/favicon-16-Cg6Nrosk.png +0 -0
- package/dist/preview-editor/assets/favicon-32-B_1744sp.png +0 -0
- package/dist/preview-editor/assets/index-BYB0hngG.js +72 -0
- package/dist/preview-editor/assets/index-CpBZEQDy.css +1 -0
- package/dist/preview-editor/assets/site-icon-CFMif8z4.png +0 -0
- package/dist/preview-editor/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/preview-editor/index.html +17 -0
- package/dist/server-core/_vendor/finmall-env.mjs +96 -0
- package/dist/server-core/assetProxy.js +154 -0
- package/dist/server-core/bbox.js +153 -0
- package/dist/server-core/browser.js +108 -0
- package/dist/server-core/buildDataFromSchema.js +50 -0
- package/dist/server-core/cardFiles.js +188 -0
- package/dist/server-core/cardHistory.js +156 -0
- package/dist/server-core/cardImage.js +85 -0
- package/dist/server-core/cardImportMeta.js +139 -0
- package/dist/server-core/cardListCache.js +68 -0
- package/dist/server-core/cardOpenFolder.js +135 -0
- package/dist/server-core/cardSave.js +127 -0
- package/dist/server-core/cardSidecar.js +72 -0
- package/dist/server-core/cardSync.js +259 -0
- package/dist/server-core/cardValidate.js +134 -0
- package/dist/server-core/index.js +82 -0
- package/dist/server-core/logger.js +241 -0
- package/dist/server-core/nodeModels.js +52 -0
- package/dist/server-core/renderStatus.js +207 -0
- package/dist/server-core/screenshot.js +123 -0
- package/dist/server-core/sliceStatic.js +44 -0
- package/dist/standalone.js +447 -0
- package/dist/vendored/App.vue +2330 -0
- package/dist/vendored/api.js +142 -0
- 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/logo.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svg-symbols.svg +118 -0
- package/dist/vendored/assets/svgIcons/icons.svg +54 -0
- package/dist/vendored/assets/svgIcons/index.js +39 -0
- package/dist/vendored/components/AbsoluteLayout.vue +42 -0
- package/dist/vendored/components/AlertCardInstance.vue +58 -0
- package/dist/vendored/components/AlertCardLayer.vue +59 -0
- package/dist/vendored/components/ApiPayloadTree.vue +168 -0
- package/dist/vendored/components/AppToast.vue +37 -0
- package/dist/vendored/components/Button.vue +37 -0
- package/dist/vendored/components/Canvas.vue +65 -0
- package/dist/vendored/components/CardGallery.vue +243 -0
- package/dist/vendored/components/CardPreview.vue +72 -0
- package/dist/vendored/components/CardSelect.vue +200 -0
- package/dist/vendored/components/CheckGroup.vue +131 -0
- package/dist/vendored/components/Column.vue +25 -0
- package/dist/vendored/components/ComponentPalette.vue +294 -0
- package/dist/vendored/components/ConfirmDialog.vue +39 -0
- package/dist/vendored/components/CustomPlaceholder.vue +33 -0
- package/dist/vendored/components/DatePickerOverlay.vue +345 -0
- package/dist/vendored/components/DebugPanel.vue +806 -0
- package/dist/vendored/components/DeviceOverlay.vue +70 -0
- package/dist/vendored/components/Divider.vue +22 -0
- package/dist/vendored/components/Dropdown.vue +191 -0
- package/dist/vendored/components/EventEditor.vue +247 -0
- package/dist/vendored/components/GridLayout.vue +67 -0
- package/dist/vendored/components/HoverGuides.vue +122 -0
- package/dist/vendored/components/Icon.vue +34 -0
- package/dist/vendored/components/Image.vue +34 -0
- package/dist/vendored/components/Input.vue +90 -0
- package/dist/vendored/components/JsonTree.vue +516 -0
- package/dist/vendored/components/ListLayout.vue +68 -0
- package/dist/vendored/components/LogValue.vue +51 -0
- package/dist/vendored/components/Map.vue +110 -0
- package/dist/vendored/components/MultiColumn.vue +49 -0
- package/dist/vendored/components/MultiText.vue +58 -0
- package/dist/vendored/components/NodeInspector.vue +295 -0
- package/dist/vendored/components/PickerOverlay.vue +131 -0
- package/dist/vendored/components/ProgressSlider.vue +167 -0
- package/dist/vendored/components/PropControl.vue +241 -0
- package/dist/vendored/components/PropTree.vue +128 -0
- package/dist/vendored/components/RadioButton.vue +74 -0
- package/dist/vendored/components/RemotePreview.vue +171 -0
- package/dist/vendored/components/RenderTree.vue +437 -0
- package/dist/vendored/components/Root.vue +30 -0
- package/dist/vendored/components/Row.vue +25 -0
- package/dist/vendored/components/SelectionOverlay.vue +105 -0
- package/dist/vendored/components/StructureTree.vue +949 -0
- package/dist/vendored/components/SvgCode.vue +19 -0
- package/dist/vendored/components/SvgSymbols.vue +30 -0
- package/dist/vendored/components/Switch.vue +38 -0
- package/dist/vendored/components/Text.vue +32 -0
- package/dist/vendored/components/Video.vue +70 -0
- package/dist/vendored/components/ViewContainer.vue +52 -0
- package/dist/vendored/components/base.js +12 -0
- package/dist/vendored/components/icons.js +47 -0
- package/dist/vendored/components/index.js +58 -0
- package/dist/vendored/components/setters/SetBgPosition.vue +64 -0
- package/dist/vendored/components/setters/SetBgSize.vue +90 -0
- package/dist/vendored/components/setters/SetCode.vue +90 -0
- package/dist/vendored/components/setters/SetColor.vue +244 -0
- package/dist/vendored/components/setters/SetCustomSelect.vue +58 -0
- package/dist/vendored/components/setters/SetExpr.vue +107 -0
- package/dist/vendored/components/setters/SetGradient.vue +125 -0
- package/dist/vendored/components/setters/SetGrid9.vue +68 -0
- package/dist/vendored/components/setters/SetIconSelect.vue +67 -0
- package/dist/vendored/components/setters/SetIfForm.vue +61 -0
- package/dist/vendored/components/setters/SetImage.vue +43 -0
- package/dist/vendored/components/setters/SetList.vue +139 -0
- package/dist/vendored/components/setters/SetNumber.vue +118 -0
- package/dist/vendored/components/setters/SetNumberStyle.vue +113 -0
- package/dist/vendored/components/setters/SetPadding.vue +77 -0
- package/dist/vendored/components/setters/SetSegment.vue +160 -0
- package/dist/vendored/components/setters/SetSelect.vue +51 -0
- package/dist/vendored/components/setters/SetSwitch.vue +49 -0
- package/dist/vendored/components/setters/SetText.vue +52 -0
- package/dist/vendored/components/setters/props.js +23 -0
- package/dist/vendored/fidelity/baseline-messageOverview.json +87 -0
- package/dist/vendored/fidelity/calibration.json +29 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +114 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +272 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.html +185 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.json +336 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +262 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +502 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +201 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +417 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +125 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +338 -0
- package/dist/vendored/index.html +16 -0
- package/dist/vendored/main.js +36 -0
- package/dist/vendored/meta/action-meta.json +141 -0
- package/dist/vendored/meta/component-meta.json +2714 -0
- package/dist/vendored/meta/directive-meta.json +40 -0
- package/dist/vendored/meta/style-meta.json +1086 -0
- package/dist/vendored/runtime/StyleParser.js +179 -0
- package/dist/vendored/runtime/adapter.js +166 -0
- package/dist/vendored/runtime/alert-card.js +125 -0
- package/dist/vendored/runtime/app-toast.js +33 -0
- package/dist/vendored/runtime/assets.js +16 -0
- package/dist/vendored/runtime/canvas-context.js +141 -0
- package/dist/vendored/runtime/card-runtime.js +289 -0
- package/dist/vendored/runtime/clipboard-node.js +83 -0
- package/dist/vendored/runtime/clipboard.js +29 -0
- package/dist/vendored/runtime/evaluate.js +152 -0
- package/dist/vendored/runtime/event-model.js +300 -0
- package/dist/vendored/runtime/hoverGuide.js +31 -0
- package/dist/vendored/runtime/jsapi-mock.js +256 -0
- package/dist/vendored/runtime/node-model.js +217 -0
- package/dist/vendored/runtime/property.js +364 -0
- package/dist/vendored/runtime/selectionOverlay.js +53 -0
- package/dist/vendored/runtime/toPreview.js +648 -0
- package/dist/vendored/standalone-main.js +36 -0
- package/dist/vendored/standalone.html +12 -0
- package/dist/vendored/styles/app.css +2565 -0
- package/dist/vendored/styles/card-gallery.css +234 -0
- package/dist/vendored/styles/card-select.css +281 -0
- package/dist/vendored/styles/components.css +383 -0
- package/dist/vendored/styles/dropdown.css +337 -0
- package/dist/vendored/styles/json-tree.css +308 -0
- package/dist/vendored/styles/node-inspector.css +1003 -0
- package/dist/vendored/styles/setters.css +962 -0
- package/dist/vendored/styles/standalone.css +27 -0
- package/dist/vendored/styles/structure-tree.css +320 -0
- package/package.json +42 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
地图(官方 module 1983 / 506)。
|
|
3
|
+
在线端用 Leaflet 渲染瓦片;预览端不内嵌 Leaflet(体积大且与本地资源代理不兼容),
|
|
4
|
+
改用「ce_map_view 占位 + 背景网格 + 标记点」还原在线结构(ce_map > ce_map_view + ce_map_placeholder),
|
|
5
|
+
视觉表现与在线一致:一个地图容器 + 中心标记点。
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<div class="ce_map" :style="rootStyle">
|
|
9
|
+
<div class="ce_map_view">
|
|
10
|
+
<svg class="ce_map_grid" viewBox="0 0 100 100" preserveAspectRatio="none">
|
|
11
|
+
<defs>
|
|
12
|
+
<pattern id="ceMapGrid" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
13
|
+
<path d="M10 0 L0 0 0 10" fill="none" stroke="#dfe3ea" stroke-width="0.4" />
|
|
14
|
+
</pattern>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect width="100" height="100" fill="#eef1f6" />
|
|
17
|
+
<rect width="100" height="100" fill="url(#ceMapGrid)" />
|
|
18
|
+
<!-- 模拟几条道路 -->
|
|
19
|
+
<path d="M0 60 Q40 50 100 65" fill="none" stroke="#cdd4df" stroke-width="2.2" />
|
|
20
|
+
<path d="M30 0 L45 100" fill="none" stroke="#cdd4df" stroke-width="2.2" />
|
|
21
|
+
</svg>
|
|
22
|
+
<div class="ce_map_center">
|
|
23
|
+
<div class="ce_map_pin">
|
|
24
|
+
<svg viewBox="0 0 24 24" width="26" height="26">
|
|
25
|
+
<path
|
|
26
|
+
d="M12 2C7.6 2 4 5.6 4 10c0 5.4 7.1 11.6 7.4 11.9.6.6 1.6.6 2.2 0C13.9 21.6 21 15.4 21 10c0-4.4-3.6-8-9-8z"
|
|
27
|
+
fill="#ff4d4f"
|
|
28
|
+
/>
|
|
29
|
+
<circle cx="12" cy="10" r="3" fill="#fff" />
|
|
30
|
+
</svg>
|
|
31
|
+
</div>
|
|
32
|
+
<div v-if="markerName" class="ce_map_markname">{{ doI18n(markerName) }}</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="ce_map_placeholder" />
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import base from './base.js'
|
|
41
|
+
import StyleParser from '../runtime/StyleParser.js'
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
name: 'CeMap',
|
|
45
|
+
mixins: [base],
|
|
46
|
+
props: {
|
|
47
|
+
styleProps: { type: Object, default: () => ({}) },
|
|
48
|
+
centerLat: { type: [String, Number], default: '' },
|
|
49
|
+
centerLng: { type: [String, Number], default: '' },
|
|
50
|
+
zoom: { type: [String, Number], default: '' },
|
|
51
|
+
markerLat: { type: [String, Number], default: '' },
|
|
52
|
+
markerLng: { type: [String, Number], default: '' },
|
|
53
|
+
markerName: { type: String, default: '' },
|
|
54
|
+
},
|
|
55
|
+
emits: ['click'],
|
|
56
|
+
computed: {
|
|
57
|
+
// 沿用官方统一做法:用 StyleParser 解析样式面板设置(rect.height / border /
|
|
58
|
+
// borderRadius 等),让高度、边框等样式真正生效;地图占位仍需裁掉溢出的网格与标记点。
|
|
59
|
+
rootStyle() {
|
|
60
|
+
const out = new StyleParser(this, this.styleProps).parse()
|
|
61
|
+
out.overflow = 'hidden'
|
|
62
|
+
return out
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.ce_map_view {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: 100%;
|
|
72
|
+
min-height: 160px;
|
|
73
|
+
position: relative;
|
|
74
|
+
background-repeat: no-repeat;
|
|
75
|
+
background-position: center center;
|
|
76
|
+
background-size: 100% 100%;
|
|
77
|
+
}
|
|
78
|
+
.ce_map_grid {
|
|
79
|
+
position: absolute;
|
|
80
|
+
inset: 0;
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
}
|
|
84
|
+
.ce_map_center {
|
|
85
|
+
position: absolute;
|
|
86
|
+
left: 50%;
|
|
87
|
+
top: 50%;
|
|
88
|
+
transform: translate(-50%, -100%);
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
align-items: center;
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
.ce_map_pin {
|
|
95
|
+
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25));
|
|
96
|
+
}
|
|
97
|
+
.ce_map_markname {
|
|
98
|
+
margin-top: 2px;
|
|
99
|
+
padding: 2px 6px;
|
|
100
|
+
font-size: 12px;
|
|
101
|
+
line-height: 1.3;
|
|
102
|
+
color: #333;
|
|
103
|
+
background: rgba(255, 255, 255, 0.9);
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
max-width: 160px;
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
text-overflow: ellipsis;
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
多列布局容器(官方组件 MultiColumn)。
|
|
3
|
+
|
|
4
|
+
预览运行时此前未注册该组件,导致画布显示「未知组件」占位(问题 #4)。
|
|
5
|
+
本组件对齐 Row/Column 的容器范式:用 StyleParser 解析样式(rect / flex / padding …),
|
|
6
|
+
渲染默认插槽内容;当 `column` 属性 > 1 时切换为 CSS Grid 多列布局(列数 = column)。
|
|
7
|
+
|
|
8
|
+
说明:当前测试卡的 MultiColumn 实例(comp_3)为「空容器」(无插槽子节点、无 column 属性),
|
|
9
|
+
此处仍提供完整的多列渲染能力,便于后续接入真实带子节点的多列布局卡片。
|
|
10
|
+
-->
|
|
11
|
+
<template>
|
|
12
|
+
<div ref="mc" class="ce_multicolumn" :style="styleObj" @click="$emit('click', $event)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import StyleParser from '../runtime/StyleParser.js'
|
|
19
|
+
import base from './base.js'
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
name: 'CeMultiColumn',
|
|
23
|
+
emits: ['click'],
|
|
24
|
+
mixins: [base],
|
|
25
|
+
props: {
|
|
26
|
+
styleProps: { type: Object, default: () => ({}) },
|
|
27
|
+
column: { type: [Number, String], default: 2 },
|
|
28
|
+
},
|
|
29
|
+
computed: {
|
|
30
|
+
styleObj() {
|
|
31
|
+
const s = new StyleParser(this, this.styleProps).parse()
|
|
32
|
+
const cols = parseInt(this.column, 10)
|
|
33
|
+
if (cols && cols > 1) {
|
|
34
|
+
// 多列:用 grid 把子节点按 column 列均分;flex gap 同时充当 grid 行列间距。
|
|
35
|
+
s.display = 'grid'
|
|
36
|
+
s.gridTemplateColumns = `repeat(${cols}, 1fr)`
|
|
37
|
+
}
|
|
38
|
+
return s
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
.ce_multicolumn {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
min-height: 24px;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
官方 module 1981 / 504。
|
|
3
|
+
一行里多段不同字体的文本;点击时用 event.__do__ 做冒泡去重,
|
|
4
|
+
子段先命中,父段再收到同一个 event 就不重复 emit 了。
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<span class="ce_multitext" :style="styleObj" @click="doClick($event)">
|
|
8
|
+
<span class="ce_multitext_item">
|
|
9
|
+
<span
|
|
10
|
+
v-for="(item, index) in textList"
|
|
11
|
+
:key="index"
|
|
12
|
+
:style="parseFont(item)"
|
|
13
|
+
@click="doClick($event, item, index)"
|
|
14
|
+
>{{ doI18n(item.value) }}</span
|
|
15
|
+
>
|
|
16
|
+
</span>
|
|
17
|
+
</span>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import StyleParser from '../runtime/StyleParser.js'
|
|
22
|
+
import base from './base.js'
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
name: 'CeMultiText',
|
|
26
|
+
emits: ['click'],
|
|
27
|
+
mixins: [base],
|
|
28
|
+
props: {
|
|
29
|
+
styleProps: { type: Object, default: () => ({}) },
|
|
30
|
+
textList: { type: Array, default: () => [] },
|
|
31
|
+
},
|
|
32
|
+
computed: {
|
|
33
|
+
styleObj() {
|
|
34
|
+
const parser = new StyleParser(this, this.styleProps)
|
|
35
|
+
const out = parser.parse()
|
|
36
|
+
parser.parseFont(this.styleProps.font || {}, out)
|
|
37
|
+
return out
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
methods: {
|
|
41
|
+
doClick(e, item, index) {
|
|
42
|
+
if (e.__do__) return
|
|
43
|
+
e.__do__ = true
|
|
44
|
+
this.$emit('click', {
|
|
45
|
+
data: { index, tag: item ? item.tag : '', text: item ? item.value : '' },
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
parseFont(item) {
|
|
49
|
+
const out = {}
|
|
50
|
+
new StyleParser(this, {}).parseFont(item || {}, out)
|
|
51
|
+
if (item && item.backgroundColor) {
|
|
52
|
+
out.backgroundColor = this.adapter.doColor(item.backgroundColor)
|
|
53
|
+
}
|
|
54
|
+
return out
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
节点属性面板(meta 驱动,可编辑写回)。
|
|
3
|
+
|
|
4
|
+
数据源:预览树节点 comp._schemaNode(与 currentSchema 同引用)。构建 NodeEditModel 后:
|
|
5
|
+
- 组件属性 / 指令:表单树 == 实例树,直接 mutate Property.itemValue,整树导出(applyToSchema);
|
|
6
|
+
- 样式:表单树(styleForm, 完整 meta) 与实例树(style, 精简) 分离,改动经 applyStyleChange 定点并入;
|
|
7
|
+
- 编辑即时(防抖)重建预览(emit apply),「应用」把改动暂存进内存工作副本(currentSchema),落盘由顶栏「保存到本地」负责;不再 emit write-back。
|
|
8
|
+
|
|
9
|
+
面板布局对齐线上编辑器右侧「属性 / 事件」:
|
|
10
|
+
属性 tab:基本信息 + 组件属性 + 样式 + 指令(分组可折叠,叶子用类型化控件);
|
|
11
|
+
事件 tab:事件动作列表,每条 = 触发事件 + 动作类型 + 动作参数。
|
|
12
|
+
参数由 meta/action-meta.json 声明(Property meta 形态),
|
|
13
|
+
因此和属性页签共用 Property / PropTree / PropControl 同一套渲染与回写机制,
|
|
14
|
+
customAction 的脚本走 itemType=Code 的 SetCode 设置器(CodeJar + Prism)。
|
|
15
|
+
导出纪律见 runtime/event-model.js 文件头:必填/原有/改过的参数才写回 schema。
|
|
16
|
+
-->
|
|
17
|
+
<template>
|
|
18
|
+
<div class="ni-overlay">
|
|
19
|
+
<div class="ni-panel">
|
|
20
|
+
<div class="ni-head">
|
|
21
|
+
<span class="ni-title">
|
|
22
|
+
节点属性 · {{ compName }}
|
|
23
|
+
<em class="ni-cls">{{ schemaNode && schemaNode.className }}</em>
|
|
24
|
+
</span>
|
|
25
|
+
<button class="ni-close" type="button" @click="onClose"></button>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div v-if="!model" class="ni-body">
|
|
29
|
+
<p class="ni-empty">无法定位该节点的原始 schema(_schemaNode 缺失)</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div v-else class="ni-body">
|
|
33
|
+
<!-- ============ 属性 ============ -->
|
|
34
|
+
<div class="ni-scroll">
|
|
35
|
+
<!-- 基本信息 -->
|
|
36
|
+
<section class="ni-sec">
|
|
37
|
+
<div class="ni-sec-head" @click="toggleSec('basic')">
|
|
38
|
+
<span class="ni-caret" :class="{ 'is-open': secOpen.basic }">▸</span>基本信息
|
|
39
|
+
</div>
|
|
40
|
+
<div v-show="secOpen.basic" class="ni-sec-body">
|
|
41
|
+
<div class="ni-field-row">
|
|
42
|
+
<label class="ni-field-label">组件类型</label>
|
|
43
|
+
<span class="ni-static">{{ compName }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="ni-field-row" v-if="hasField('componentId')">
|
|
46
|
+
<label class="ni-field-label">实例 ID</label>
|
|
47
|
+
<input class="ni-input" :value="schemaNode.componentId" @input="onMetaInput('componentId', $event.target.value)" />
|
|
48
|
+
</div>
|
|
49
|
+
<div class="ni-field-row" v-if="hasField('name')">
|
|
50
|
+
<label class="ni-field-label">节点名</label>
|
|
51
|
+
<input class="ni-input" :value="schemaNode.name" @input="onMetaInput('name', $event.target.value)" />
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<!-- 组件属性 -->
|
|
57
|
+
<section class="ni-sec" v-show="model.properties.length">
|
|
58
|
+
<div class="ni-sec-head" @click="toggleSec('props')">
|
|
59
|
+
<span class="ni-caret" :class="{ 'is-open': secOpen.props }">▸</span>
|
|
60
|
+
组件属性
|
|
61
|
+
</div>
|
|
62
|
+
<div v-show="secOpen.props" class="ni-sec-body">
|
|
63
|
+
<prop-tree
|
|
64
|
+
:properties="model.properties"
|
|
65
|
+
mode="property"
|
|
66
|
+
:plain="true"
|
|
67
|
+
@leaf-change="onPropChange"
|
|
68
|
+
@leaf-sync="onStyleSync"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<!-- 样式 -->
|
|
74
|
+
<section class="ni-sec">
|
|
75
|
+
<div class="ni-sec-head" @click="toggleSec('style')">
|
|
76
|
+
<span class="ni-caret" :class="{ 'is-open': secOpen.style }">▸</span>
|
|
77
|
+
样式
|
|
78
|
+
</div>
|
|
79
|
+
<div v-show="secOpen.style" class="ni-sec-body">
|
|
80
|
+
<prop-tree
|
|
81
|
+
:properties="model.styleForm"
|
|
82
|
+
mode="style"
|
|
83
|
+
:watches="styleWatches"
|
|
84
|
+
:plain="true"
|
|
85
|
+
@leaf-change="onStyleChange"
|
|
86
|
+
@leaf-sync="onStyleSync"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<!-- 指令 -->
|
|
92
|
+
<section class="ni-sec" v-if="model.hasDirectives">
|
|
93
|
+
<div class="ni-sec-head" @click="toggleSec('directive')">
|
|
94
|
+
<span class="ni-caret" :class="{ 'is-open': secOpen.directive }">▸</span>
|
|
95
|
+
指令
|
|
96
|
+
</div>
|
|
97
|
+
<div v-show="secOpen.directive" class="ni-sec-body">
|
|
98
|
+
<p v-if="!model.directives.length" class="ni-empty">无指令</p>
|
|
99
|
+
<prop-tree
|
|
100
|
+
v-else
|
|
101
|
+
:properties="model.directives"
|
|
102
|
+
mode="directive"
|
|
103
|
+
:b-exchange="false"
|
|
104
|
+
:plain="true"
|
|
105
|
+
@leaf-change="onPropChange"
|
|
106
|
+
@leaf-sync="onStyleSync"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="ni-foot">
|
|
115
|
+
<span class="ni-tip">编辑即时预览(不记历史);「取消」丢弃改动并还原,「应用」写入历史并关闭,落盘由顶栏「保存到本地」负责</span>
|
|
116
|
+
<div class="ni-foot-btns">
|
|
117
|
+
<button class="ni-btn ni-btn--ghost" type="button" @click="onCancel">取消</button>
|
|
118
|
+
<button class="ni-btn" type="button" @click="onApplyWrite">应用</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<script>
|
|
126
|
+
import { reactive } from 'vue'
|
|
127
|
+
import { NodeEditModel } from '@/runtime/node-model.js'
|
|
128
|
+
|
|
129
|
+
export default {
|
|
130
|
+
name: 'NodeInspector',
|
|
131
|
+
props: {
|
|
132
|
+
comp: { type: Object, required: true },
|
|
133
|
+
compNames: { type: Object, default: () => ({}) },
|
|
134
|
+
rt: { type: Object, required: true },
|
|
135
|
+
},
|
|
136
|
+
emits: ['apply', 'commit', 'close'],
|
|
137
|
+
data() {
|
|
138
|
+
return {
|
|
139
|
+
model: null,
|
|
140
|
+
secOpen: { basic: true, props: true, style: true, directive: true },
|
|
141
|
+
_t: null,
|
|
142
|
+
_snapshot: null,
|
|
143
|
+
_dirty: false,
|
|
144
|
+
_written: false,
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
computed: {
|
|
148
|
+
schemaNode() {
|
|
149
|
+
return (this.comp && this.comp._schemaNode) || null
|
|
150
|
+
},
|
|
151
|
+
compName() {
|
|
152
|
+
const cn = this.schemaNode && this.schemaNode.className
|
|
153
|
+
if (!cn) return '节点'
|
|
154
|
+
return (this.compNames && this.compNames[cn]) || cn
|
|
155
|
+
},
|
|
156
|
+
/** 样式编辑联动快照:把所有样式字段 itemKey→itemValue 摊平,供 itemWatch 使用 */
|
|
157
|
+
styleWatches() {
|
|
158
|
+
const w = {}
|
|
159
|
+
const walk = (props) => {
|
|
160
|
+
(props || []).forEach((p) => {
|
|
161
|
+
if (p.itemKey != null) w[p.itemKey] = p.itemValue
|
|
162
|
+
if (p.itemChildren && p.itemChildren.properties) walk(p.itemChildren.properties)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
if (this.model) walk(this.model.styleForm)
|
|
166
|
+
return w
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
watch: {
|
|
170
|
+
comp: {
|
|
171
|
+
immediate: true,
|
|
172
|
+
handler() {
|
|
173
|
+
this.buildModel()
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
methods: {
|
|
178
|
+
buildModel() {
|
|
179
|
+
const sn = this.schemaNode
|
|
180
|
+
this.model = sn ? reactive(new NodeEditModel(sn)) : null
|
|
181
|
+
this._snapshot = sn ? this.snapshotNode(sn) : null
|
|
182
|
+
this._dirty = false
|
|
183
|
+
this._written = false
|
|
184
|
+
},
|
|
185
|
+
snapshotNode(node) {
|
|
186
|
+
const has = (k) => Object.prototype.hasOwnProperty.call(node, k)
|
|
187
|
+
const clone = (v) => v === undefined ? undefined : JSON.parse(JSON.stringify(v))
|
|
188
|
+
return {
|
|
189
|
+
hasProperties: has('properties'), properties: clone(node.properties),
|
|
190
|
+
hasStyle: has('style'), style: clone(node.style),
|
|
191
|
+
hasDirectives: has('directives'), directives: clone(node.directives),
|
|
192
|
+
hasComponentId: has('componentId'), componentId: clone(node.componentId),
|
|
193
|
+
hasName: has('name'), name: clone(node.name),
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
restoreSnapshot() {
|
|
197
|
+
const sn = this.schemaNode
|
|
198
|
+
if (!sn || !this._snapshot) return
|
|
199
|
+
const s = this._snapshot
|
|
200
|
+
const setOrDel = (had, key, val) => { if (had) sn[key] = JSON.parse(JSON.stringify(val)); else delete sn[key] }
|
|
201
|
+
setOrDel(s.hasProperties, 'properties', s.properties)
|
|
202
|
+
setOrDel(s.hasStyle, 'style', s.style)
|
|
203
|
+
setOrDel(s.hasDirectives, 'directives', s.directives)
|
|
204
|
+
setOrDel(s.hasComponentId, 'componentId', s.componentId)
|
|
205
|
+
setOrDel(s.hasName, 'name', s.name)
|
|
206
|
+
},
|
|
207
|
+
toggleSec(k) {
|
|
208
|
+
this.secOpen[k] = !this.secOpen[k]
|
|
209
|
+
this.secOpen = Object.assign({}, this.secOpen)
|
|
210
|
+
},
|
|
211
|
+
hasField(name) {
|
|
212
|
+
return this.schemaNode && Object.prototype.hasOwnProperty.call(this.schemaNode, name)
|
|
213
|
+
},
|
|
214
|
+
onMetaInput(field, v) {
|
|
215
|
+
if (!this.schemaNode) return
|
|
216
|
+
this.schemaNode[field] = v
|
|
217
|
+
this._dirty = true
|
|
218
|
+
this.scheduleApply()
|
|
219
|
+
},
|
|
220
|
+
/** 组件属性 / 指令:整树导出即可 */
|
|
221
|
+
onPropChange() {
|
|
222
|
+
this.model.refreshVisible()
|
|
223
|
+
this._dirty = true
|
|
224
|
+
this.scheduleApply()
|
|
225
|
+
},
|
|
226
|
+
/** 样式:定点并入实例树 */
|
|
227
|
+
onStyleChange(chain) {
|
|
228
|
+
if (!chain || !chain.length) return
|
|
229
|
+
this.model.applyStyleChange(chain[0], chain.slice(1))
|
|
230
|
+
this.model.refreshVisible()
|
|
231
|
+
this._dirty = true
|
|
232
|
+
this.scheduleApply()
|
|
233
|
+
},
|
|
234
|
+
/** 常用样式(CustomSelect)批量写入:sync map 形如 { 'style.font.color': '#333' } */
|
|
235
|
+
onStyleSync(map) {
|
|
236
|
+
if (!map) return
|
|
237
|
+
for (const path in map) {
|
|
238
|
+
const parts = String(path).split('.')
|
|
239
|
+
if (parts.length < 3 || parts[0] !== 'style') continue
|
|
240
|
+
const groupKey = parts[1]
|
|
241
|
+
const fieldKey = parts[2]
|
|
242
|
+
const group = this.model.styleForm.find((p) => p.itemKey === groupKey)
|
|
243
|
+
if (!group) continue
|
|
244
|
+
const child = group.itemChildren.properties.find((c) => c.itemKey === fieldKey)
|
|
245
|
+
if (!child) continue
|
|
246
|
+
child.itemValue = map[path]
|
|
247
|
+
this.model.applyStyleChange(group, [child])
|
|
248
|
+
}
|
|
249
|
+
this.model.refreshVisible()
|
|
250
|
+
this.scheduleApply()
|
|
251
|
+
},
|
|
252
|
+
scheduleApply() {
|
|
253
|
+
if (this._t) clearTimeout(this._t)
|
|
254
|
+
this._t = setTimeout(() => {
|
|
255
|
+
this.applyAll()
|
|
256
|
+
this.$emit('apply')
|
|
257
|
+
}, 250)
|
|
258
|
+
},
|
|
259
|
+
applyAll() {
|
|
260
|
+
if (this.model) this.model.applyToSchema()
|
|
261
|
+
},
|
|
262
|
+
/** 将改动暂存到内存工作副本(currentSchema,与预览同引用),并重建预览;
|
|
263
|
+
* 不写盘——落盘由顶栏「保存到本地」统一负责。_written=true 表示已提交到内存,
|
|
264
|
+
* 「取消」不再还原。 */
|
|
265
|
+
onApplyWrite() {
|
|
266
|
+
if (this._t) {
|
|
267
|
+
clearTimeout(this._t)
|
|
268
|
+
this._t = null
|
|
269
|
+
}
|
|
270
|
+
this.applyAll()
|
|
271
|
+
this._written = true
|
|
272
|
+
this.$emit('apply')
|
|
273
|
+
this.$emit('commit')
|
|
274
|
+
this.$emit('close')
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/** 丢弃本次编辑(还原到打开时快照)+ 关闭面板。
|
|
278
|
+
* 已本地保存(_written)的改动视为已提交,不再还原。 */
|
|
279
|
+
discardAndClose() {
|
|
280
|
+
if (this._t) { clearTimeout(this._t); this._t = null }
|
|
281
|
+
if (this._dirty && !this._written) {
|
|
282
|
+
this.restoreSnapshot()
|
|
283
|
+
this.$emit('apply')
|
|
284
|
+
}
|
|
285
|
+
this.$emit('close')
|
|
286
|
+
},
|
|
287
|
+
onClose() {
|
|
288
|
+
this.discardAndClose()
|
|
289
|
+
},
|
|
290
|
+
onCancel() {
|
|
291
|
+
this.discardAndClose()
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
showPicker 模拟展现层:底部弹出级联选择器。
|
|
3
|
+
相对固定视口 phone-viewport 定位(absolute inset:0),不随卡片内容滚动;
|
|
4
|
+
层级同 DeviceOverlay(z-index 1100),盖住弹窗卡片。
|
|
5
|
+
|
|
6
|
+
交互对齐 docs「showPicker - 选择器」:
|
|
7
|
+
- mode='single' 单列;mode='multi' 多列(一级驱动二级 children 级联)。
|
|
8
|
+
- 点击某列项即选中(中间高亮带提示当前项);点「确定」回传 result,点「取消」不回传。
|
|
9
|
+
- result 数组每项 { key, value },value 取节点 text。
|
|
10
|
+
-->
|
|
11
|
+
<template>
|
|
12
|
+
<div class="device-ui">
|
|
13
|
+
<transition name="picker-fade">
|
|
14
|
+
<div v-if="picker" class="pk-mask" @click.self="onCancel">
|
|
15
|
+
<div class="pk-sheet">
|
|
16
|
+
<div class="pk-bar">
|
|
17
|
+
<button type="button" class="pk-bar__btn pk-bar__btn--cancel" @click="onCancel">取消</button>
|
|
18
|
+
<div v-if="picker.title" class="pk-bar__title">{{ picker.title }}</div>
|
|
19
|
+
<button type="button" class="pk-bar__btn pk-bar__btn--ok" @click="onConfirm">确定</button>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="pk-cols" :class="'pk-cols--' + columns.length">
|
|
22
|
+
<div v-for="(col, ci) in columns" :key="ci" class="pk-col">
|
|
23
|
+
<div class="pk-col__band"></div>
|
|
24
|
+
<div class="pk-col__list">
|
|
25
|
+
<div
|
|
26
|
+
v-for="(item, ii) in col.items"
|
|
27
|
+
:key="item.key"
|
|
28
|
+
class="pk-item"
|
|
29
|
+
:class="{ 'pk-item--on': sel[ci] === ii }"
|
|
30
|
+
@click="select(ci, ii)"
|
|
31
|
+
>{{ item.text }}</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</transition>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
import { deviceUI, dismissPicker } from '@/runtime/jsapi-mock.js'
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
name: 'PickerOverlay',
|
|
46
|
+
data() {
|
|
47
|
+
return {
|
|
48
|
+
sel: [], // 每列选中索引(列数由 mode 决定)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
computed: {
|
|
52
|
+
picker() {
|
|
53
|
+
return deviceUI.picker
|
|
54
|
+
},
|
|
55
|
+
// 列定义:single -> 单列(顶级项,忽略 children);multi -> 两列(级联,第二列为第一列选中项的 children)
|
|
56
|
+
columns() {
|
|
57
|
+
if (!this.picker) return []
|
|
58
|
+
const ds = this.picker.dataSource || []
|
|
59
|
+
if (this.picker.mode === 'multi') {
|
|
60
|
+
const col1 = ds.map((n) => ({ key: String(n.key), text: n.text }))
|
|
61
|
+
const sel1 = this.sel[0] || 0
|
|
62
|
+
const children = (ds[sel1] && ds[sel1].children) || []
|
|
63
|
+
const col2 = children.map((n) => ({ key: String(n.key), text: n.text }))
|
|
64
|
+
return [{ items: col1, level: 0 }, { items: col2, level: 1 }]
|
|
65
|
+
}
|
|
66
|
+
return [{ items: ds.map((n) => ({ key: String(n.key), text: n.text })), level: 0 }]
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
watch: {
|
|
70
|
+
// picker 切换(含入参变化)时按 currentValue 初始化选中项
|
|
71
|
+
picker: {
|
|
72
|
+
immediate: true,
|
|
73
|
+
handler(p) {
|
|
74
|
+
if (!p) {
|
|
75
|
+
this.sel = []
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
const ds = p.dataSource || []
|
|
79
|
+
const cv = p.currentValue || []
|
|
80
|
+
if (p.mode === 'multi') {
|
|
81
|
+
const i1 = ds.findIndex((n) => String(n.key) === String(cv[0]))
|
|
82
|
+
const idx1 = i1 >= 0 ? i1 : 0
|
|
83
|
+
const children = (ds[idx1] && ds[idx1].children) || []
|
|
84
|
+
const i2 = children.findIndex((n) => String(n.key) === String(cv[1]))
|
|
85
|
+
this.sel = [idx1, i2 >= 0 ? i2 : children.length ? 0 : -1]
|
|
86
|
+
} else {
|
|
87
|
+
const i1 = ds.findIndex((n) => String(n.key) === String(cv[0]))
|
|
88
|
+
this.sel = [i1 >= 0 ? i1 : 0]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
methods: {
|
|
94
|
+
select(ci, ii) {
|
|
95
|
+
const next = this.sel.slice()
|
|
96
|
+
next[ci] = ii
|
|
97
|
+
// 切换第一列时重置第二列选中(级联)
|
|
98
|
+
if (ci === 0 && this.picker.mode === 'multi') {
|
|
99
|
+
const ds = this.picker.dataSource || []
|
|
100
|
+
const children = (ds[ii] && ds[ii].children) || []
|
|
101
|
+
next[1] = children.length ? 0 : -1
|
|
102
|
+
}
|
|
103
|
+
this.sel = next
|
|
104
|
+
},
|
|
105
|
+
buildResult() {
|
|
106
|
+
const p = this.picker
|
|
107
|
+
if (!p) return []
|
|
108
|
+
const ds = p.dataSource || []
|
|
109
|
+
// 回传对象携带 text(卡片回调读 result[0].text)与 value(文档出参规范),两者取节点 text。
|
|
110
|
+
const wrap = (n) => ({ key: String(n.key), text: n.text, value: n.text })
|
|
111
|
+
if (p.mode === 'multi') {
|
|
112
|
+
const n1 = ds[this.sel[0]] || ds[0]
|
|
113
|
+
const children = (n1 && n1.children) || []
|
|
114
|
+
const n2 = children[this.sel[1]] || children[0]
|
|
115
|
+
const arr = []
|
|
116
|
+
if (n1) arr.push(wrap(n1))
|
|
117
|
+
if (n2) arr.push(wrap(n2))
|
|
118
|
+
return arr
|
|
119
|
+
}
|
|
120
|
+
const n1 = ds[this.sel[0]] || ds[0]
|
|
121
|
+
return n1 ? [wrap(n1)] : []
|
|
122
|
+
},
|
|
123
|
+
onConfirm() {
|
|
124
|
+
dismissPicker(true, this.buildResult())
|
|
125
|
+
},
|
|
126
|
+
onCancel() {
|
|
127
|
+
dismissPicker(false)
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
</script>
|