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.
Files changed (167) hide show
  1. package/README.md +211 -0
  2. package/dist/cli.js +76 -0
  3. package/dist/config.js +63 -0
  4. package/dist/core.js +2 -0
  5. package/dist/preview-editor/assets/apple-touch-icon-BQcES9QK.png +0 -0
  6. package/dist/preview-editor/assets/default-image-wzZYQ86K.png +0 -0
  7. package/dist/preview-editor/assets/favicon-16-Cg6Nrosk.png +0 -0
  8. package/dist/preview-editor/assets/favicon-32-B_1744sp.png +0 -0
  9. package/dist/preview-editor/assets/index-BYB0hngG.js +72 -0
  10. package/dist/preview-editor/assets/index-CpBZEQDy.css +1 -0
  11. package/dist/preview-editor/assets/site-icon-CFMif8z4.png +0 -0
  12. package/dist/preview-editor/assets/svg-symbols-B2exBrAr.svg +118 -0
  13. package/dist/preview-editor/index.html +17 -0
  14. package/dist/server-core/_vendor/finmall-env.mjs +96 -0
  15. package/dist/server-core/assetProxy.js +154 -0
  16. package/dist/server-core/bbox.js +153 -0
  17. package/dist/server-core/browser.js +108 -0
  18. package/dist/server-core/buildDataFromSchema.js +50 -0
  19. package/dist/server-core/cardFiles.js +188 -0
  20. package/dist/server-core/cardHistory.js +156 -0
  21. package/dist/server-core/cardImage.js +85 -0
  22. package/dist/server-core/cardImportMeta.js +139 -0
  23. package/dist/server-core/cardListCache.js +68 -0
  24. package/dist/server-core/cardOpenFolder.js +135 -0
  25. package/dist/server-core/cardSave.js +127 -0
  26. package/dist/server-core/cardSidecar.js +72 -0
  27. package/dist/server-core/cardSync.js +259 -0
  28. package/dist/server-core/cardValidate.js +134 -0
  29. package/dist/server-core/index.js +82 -0
  30. package/dist/server-core/logger.js +241 -0
  31. package/dist/server-core/nodeModels.js +52 -0
  32. package/dist/server-core/renderStatus.js +207 -0
  33. package/dist/server-core/screenshot.js +123 -0
  34. package/dist/server-core/sliceStatic.js +44 -0
  35. package/dist/standalone.js +447 -0
  36. package/dist/vendored/App.vue +2330 -0
  37. package/dist/vendored/api.js +142 -0
  38. package/dist/vendored/assets/apple-touch-icon.png +0 -0
  39. package/dist/vendored/assets/default-image.png +0 -0
  40. package/dist/vendored/assets/favicon-16.png +0 -0
  41. package/dist/vendored/assets/favicon-32.png +0 -0
  42. package/dist/vendored/assets/logo.png +0 -0
  43. package/dist/vendored/assets/site-icon.png +0 -0
  44. package/dist/vendored/assets/svg-symbols.svg +118 -0
  45. package/dist/vendored/assets/svgIcons/icons.svg +54 -0
  46. package/dist/vendored/assets/svgIcons/index.js +39 -0
  47. package/dist/vendored/components/AbsoluteLayout.vue +42 -0
  48. package/dist/vendored/components/AlertCardInstance.vue +58 -0
  49. package/dist/vendored/components/AlertCardLayer.vue +59 -0
  50. package/dist/vendored/components/ApiPayloadTree.vue +168 -0
  51. package/dist/vendored/components/AppToast.vue +37 -0
  52. package/dist/vendored/components/Button.vue +37 -0
  53. package/dist/vendored/components/Canvas.vue +65 -0
  54. package/dist/vendored/components/CardGallery.vue +243 -0
  55. package/dist/vendored/components/CardPreview.vue +72 -0
  56. package/dist/vendored/components/CardSelect.vue +200 -0
  57. package/dist/vendored/components/CheckGroup.vue +131 -0
  58. package/dist/vendored/components/Column.vue +25 -0
  59. package/dist/vendored/components/ComponentPalette.vue +294 -0
  60. package/dist/vendored/components/ConfirmDialog.vue +39 -0
  61. package/dist/vendored/components/CustomPlaceholder.vue +33 -0
  62. package/dist/vendored/components/DatePickerOverlay.vue +345 -0
  63. package/dist/vendored/components/DebugPanel.vue +806 -0
  64. package/dist/vendored/components/DeviceOverlay.vue +70 -0
  65. package/dist/vendored/components/Divider.vue +22 -0
  66. package/dist/vendored/components/Dropdown.vue +191 -0
  67. package/dist/vendored/components/EventEditor.vue +247 -0
  68. package/dist/vendored/components/GridLayout.vue +67 -0
  69. package/dist/vendored/components/HoverGuides.vue +122 -0
  70. package/dist/vendored/components/Icon.vue +34 -0
  71. package/dist/vendored/components/Image.vue +34 -0
  72. package/dist/vendored/components/Input.vue +90 -0
  73. package/dist/vendored/components/JsonTree.vue +516 -0
  74. package/dist/vendored/components/ListLayout.vue +68 -0
  75. package/dist/vendored/components/LogValue.vue +51 -0
  76. package/dist/vendored/components/Map.vue +110 -0
  77. package/dist/vendored/components/MultiColumn.vue +49 -0
  78. package/dist/vendored/components/MultiText.vue +58 -0
  79. package/dist/vendored/components/NodeInspector.vue +295 -0
  80. package/dist/vendored/components/PickerOverlay.vue +131 -0
  81. package/dist/vendored/components/ProgressSlider.vue +167 -0
  82. package/dist/vendored/components/PropControl.vue +241 -0
  83. package/dist/vendored/components/PropTree.vue +128 -0
  84. package/dist/vendored/components/RadioButton.vue +74 -0
  85. package/dist/vendored/components/RemotePreview.vue +171 -0
  86. package/dist/vendored/components/RenderTree.vue +437 -0
  87. package/dist/vendored/components/Root.vue +30 -0
  88. package/dist/vendored/components/Row.vue +25 -0
  89. package/dist/vendored/components/SelectionOverlay.vue +105 -0
  90. package/dist/vendored/components/StructureTree.vue +949 -0
  91. package/dist/vendored/components/SvgCode.vue +19 -0
  92. package/dist/vendored/components/SvgSymbols.vue +30 -0
  93. package/dist/vendored/components/Switch.vue +38 -0
  94. package/dist/vendored/components/Text.vue +32 -0
  95. package/dist/vendored/components/Video.vue +70 -0
  96. package/dist/vendored/components/ViewContainer.vue +52 -0
  97. package/dist/vendored/components/base.js +12 -0
  98. package/dist/vendored/components/icons.js +47 -0
  99. package/dist/vendored/components/index.js +58 -0
  100. package/dist/vendored/components/setters/SetBgPosition.vue +64 -0
  101. package/dist/vendored/components/setters/SetBgSize.vue +90 -0
  102. package/dist/vendored/components/setters/SetCode.vue +90 -0
  103. package/dist/vendored/components/setters/SetColor.vue +244 -0
  104. package/dist/vendored/components/setters/SetCustomSelect.vue +58 -0
  105. package/dist/vendored/components/setters/SetExpr.vue +107 -0
  106. package/dist/vendored/components/setters/SetGradient.vue +125 -0
  107. package/dist/vendored/components/setters/SetGrid9.vue +68 -0
  108. package/dist/vendored/components/setters/SetIconSelect.vue +67 -0
  109. package/dist/vendored/components/setters/SetIfForm.vue +61 -0
  110. package/dist/vendored/components/setters/SetImage.vue +43 -0
  111. package/dist/vendored/components/setters/SetList.vue +139 -0
  112. package/dist/vendored/components/setters/SetNumber.vue +118 -0
  113. package/dist/vendored/components/setters/SetNumberStyle.vue +113 -0
  114. package/dist/vendored/components/setters/SetPadding.vue +77 -0
  115. package/dist/vendored/components/setters/SetSegment.vue +160 -0
  116. package/dist/vendored/components/setters/SetSelect.vue +51 -0
  117. package/dist/vendored/components/setters/SetSwitch.vue +49 -0
  118. package/dist/vendored/components/setters/SetText.vue +52 -0
  119. package/dist/vendored/components/setters/props.js +23 -0
  120. package/dist/vendored/fidelity/baseline-messageOverview.json +87 -0
  121. package/dist/vendored/fidelity/calibration.json +29 -0
  122. package/dist/vendored/fidelity/fidelity-messageOverview-after.html +114 -0
  123. package/dist/vendored/fidelity/fidelity-messageOverview-after.json +272 -0
  124. package/dist/vendored/fidelity/fidelity-messageOverview.html +185 -0
  125. package/dist/vendored/fidelity/fidelity-messageOverview.json +336 -0
  126. package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +262 -0
  127. package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +502 -0
  128. package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +201 -0
  129. package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +417 -0
  130. package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +125 -0
  131. package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +338 -0
  132. package/dist/vendored/index.html +16 -0
  133. package/dist/vendored/main.js +36 -0
  134. package/dist/vendored/meta/action-meta.json +141 -0
  135. package/dist/vendored/meta/component-meta.json +2714 -0
  136. package/dist/vendored/meta/directive-meta.json +40 -0
  137. package/dist/vendored/meta/style-meta.json +1086 -0
  138. package/dist/vendored/runtime/StyleParser.js +179 -0
  139. package/dist/vendored/runtime/adapter.js +166 -0
  140. package/dist/vendored/runtime/alert-card.js +125 -0
  141. package/dist/vendored/runtime/app-toast.js +33 -0
  142. package/dist/vendored/runtime/assets.js +16 -0
  143. package/dist/vendored/runtime/canvas-context.js +141 -0
  144. package/dist/vendored/runtime/card-runtime.js +289 -0
  145. package/dist/vendored/runtime/clipboard-node.js +83 -0
  146. package/dist/vendored/runtime/clipboard.js +29 -0
  147. package/dist/vendored/runtime/evaluate.js +152 -0
  148. package/dist/vendored/runtime/event-model.js +300 -0
  149. package/dist/vendored/runtime/hoverGuide.js +31 -0
  150. package/dist/vendored/runtime/jsapi-mock.js +256 -0
  151. package/dist/vendored/runtime/node-model.js +217 -0
  152. package/dist/vendored/runtime/property.js +364 -0
  153. package/dist/vendored/runtime/selectionOverlay.js +53 -0
  154. package/dist/vendored/runtime/toPreview.js +648 -0
  155. package/dist/vendored/standalone-main.js +36 -0
  156. package/dist/vendored/standalone.html +12 -0
  157. package/dist/vendored/styles/app.css +2565 -0
  158. package/dist/vendored/styles/card-gallery.css +234 -0
  159. package/dist/vendored/styles/card-select.css +281 -0
  160. package/dist/vendored/styles/components.css +383 -0
  161. package/dist/vendored/styles/dropdown.css +337 -0
  162. package/dist/vendored/styles/json-tree.css +308 -0
  163. package/dist/vendored/styles/node-inspector.css +1003 -0
  164. package/dist/vendored/styles/setters.css +962 -0
  165. package/dist/vendored/styles/standalone.css +27 -0
  166. package/dist/vendored/styles/structure-tree.css +320 -0
  167. package/package.json +42 -0
@@ -0,0 +1,167 @@
1
+ <!--
2
+ 进度滑块(官方 module 1981 / 504)。
3
+ 结构对齐在线编辑器渲染产物:
4
+ ce_slider > ce_slider_rect(bg) > ce_slider_bar(fg) + ce_slider_con > ce_slider_block
5
+ 支持点击 / 拖动改变值(按 step 吸附),与在线交互一致。
6
+ -->
7
+ <template>
8
+ <div class="ce_slider" :style="rootStyle">
9
+ <div
10
+ ref="rect"
11
+ class="ce_slider_rect"
12
+ :style="rectStyle"
13
+ @pointerdown="onDown"
14
+ >
15
+ <div class="ce_slider_bar" :style="barStyle" />
16
+ <div class="ce_slider_con" :style="{ left: conLeft }">
17
+ <div
18
+ class="ce_slider_block"
19
+ :class="{ ce_dragging: dragging }"
20
+ :style="blockStyleObj"
21
+ />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ import base from './base.js'
29
+
30
+ export default {
31
+ name: 'CeProgressSlider',
32
+ mixins: [base],
33
+ props: {
34
+ styleProps: { type: Object, default: () => ({}) },
35
+ value: { type: [String, Number], default: 0 },
36
+ minProgress: { type: [String, Number], default: 0 },
37
+ maxProgress: { type: [String, Number], default: 100 },
38
+ step: { type: [String, Number], default: 1 },
39
+ backgroundColor: { type: String, default: '#e6e6e6' },
40
+ foregroundColor: { type: String, default: '#0a59f5' },
41
+ processHeight: { type: [String, Number], default: 5 },
42
+ blockStyle: { type: String, default: 'circle' },
43
+ blockColor: { type: String, default: '#ffffff' },
44
+ blockImage: { type: String, default: '' },
45
+ blockWidth: { type: [String, Number], default: 15 },
46
+ blockHeight: { type: [String, Number], default: 15 },
47
+ },
48
+ emits: ['change', 'selectChange'],
49
+ data() {
50
+ return { dragging: false }
51
+ },
52
+ computed: {
53
+ rootStyle() {
54
+ // 注意:滑块根不裁切自身内容。滑块块在 0%/100% 时会落在轨道端点、且
55
+ // 按下时整体放大 1.2×,若这里 overflow:hidden 会把块裁掉。
56
+ return { height: 'auto', boxSizing: 'border-box' }
57
+ },
58
+ min() {
59
+ const n = Number(this.minProgress)
60
+ return Number.isFinite(n) ? n : 0
61
+ },
62
+ max() {
63
+ const n = Number(this.maxProgress)
64
+ return Number.isFinite(n) ? n : 100
65
+ },
66
+ stepVal() {
67
+ const n = Number(this.step)
68
+ return n > 0 ? n : 1
69
+ },
70
+ numValue() {
71
+ const n = Number(this.value)
72
+ return Number.isFinite(n) ? n : this.min
73
+ },
74
+ percent() {
75
+ const span = this.max - this.min || 1
76
+ const p = ((this.numValue - this.min) / span) * 100
77
+ return Math.max(0, Math.min(100, p))
78
+ },
79
+ conLeft() {
80
+ // 滑块块定位:轨道两侧各内缩半个滑块宽,使块在 0%/100% 时仍完整落在节点盒内
81
+ // (对齐线上滑块「滑块块不出轨」的呈现,不被节点 overflow:hidden 裁切)。
82
+ const bw = Number(this.blockWidth) || 15
83
+ const half = bw / 2
84
+ return `calc(${half}px + (100% - ${bw}px) * ${this.percent} / 100)`
85
+ },
86
+ rectStyle() {
87
+ const h = Number(this.processHeight) || 5
88
+ return {
89
+ backgroundColor: this.adapter.doColor(this.backgroundColor),
90
+ height: this.adapter.doSize(h),
91
+ borderRadius: this.adapter.doSize(h),
92
+ marginTop: this.adapter.doSize(8),
93
+ marginBottom: this.adapter.doSize(8),
94
+ }
95
+ },
96
+ barStyle() {
97
+ const h = Number(this.processHeight) || 5
98
+ const bw = Number(this.blockWidth) || 15
99
+ // 填充条与滑块块两端对齐:从半个滑块宽起,宽度按「(满宽 - 滑块宽) × 百分比」计算,
100
+ // 使 0%/100% 时填充条与滑块块始终落在轨道内,不被节点 overflow:hidden 裁切。
101
+ return {
102
+ backgroundColor: this.adapter.doColor(this.foregroundColor),
103
+ left: this.adapter.doSize(bw / 2),
104
+ width: `calc((100% - ${bw}px) * ${this.percent} / 100)`,
105
+ borderTopLeftRadius: this.adapter.doSize(h),
106
+ borderBottomLeftRadius: this.adapter.doSize(h),
107
+ }
108
+ },
109
+ blockStyleObj() {
110
+ const w = Number(this.blockWidth) || 15
111
+ const h = Number(this.blockHeight) || 15
112
+ const out = {
113
+ width: this.adapter.doSize(w),
114
+ height: this.adapter.doSize(h),
115
+ boxShadow: 'rgba(0,0,0,0.3) 0 0 3px 0',
116
+ }
117
+ if (this.blockStyle === 'image' && this.blockImage) {
118
+ out.backgroundImage = `url(${this.adapter.doReplaceUrl(this.doI18n(this.blockImage))})`
119
+ out.borderRadius = '0'
120
+ } else if (this.blockStyle === 'circle') {
121
+ out.backgroundColor = this.adapter.doColor(this.blockColor)
122
+ out.borderRadius = this.adapter.doSize(Math.min(w, h) / 2)
123
+ } else {
124
+ out.backgroundColor = this.adapter.doColor(this.blockColor)
125
+ out.borderRadius = this.adapter.doSize(2)
126
+ }
127
+ return out
128
+ },
129
+ },
130
+ methods: {
131
+ clampStep(v) {
132
+ const clamped = Math.max(this.min, Math.min(this.max, v))
133
+ const stepped = Math.round((clamped - this.min) / this.stepVal) * this.stepVal + this.min
134
+ return Math.max(this.min, Math.min(this.max, stepped))
135
+ },
136
+ valueFromEvent(ev) {
137
+ const el = this.$refs.rect
138
+ if (!el) return this.numValue
139
+ const rect = el.getBoundingClientRect()
140
+ const ratio = (ev.clientX - rect.left) / rect.width
141
+ return this.clampStep(this.min + ratio * (this.max - this.min))
142
+ },
143
+ onDown(ev) {
144
+ this.dragging = true
145
+ this.setValue(this.valueFromEvent(ev))
146
+ window.addEventListener('pointermove', this.onMove)
147
+ window.addEventListener('pointerup', this.onUp)
148
+ },
149
+ onMove(ev) {
150
+ if (!this.dragging) return
151
+ this.setValue(this.valueFromEvent(ev))
152
+ },
153
+ onUp() {
154
+ this.dragging = false
155
+ window.removeEventListener('pointermove', this.onMove)
156
+ window.removeEventListener('pointerup', this.onUp)
157
+ },
158
+ setValue(v) {
159
+ this.$emit('change', v)
160
+ this.$emit('selectChange', v)
161
+ },
162
+ },
163
+ beforeUnmount() {
164
+ this.onUp()
165
+ },
166
+ }
167
+ </script>
@@ -0,0 +1,241 @@
1
+ <!--
2
+ 类型化属性控件(meta 驱动的分派注册表)。
3
+
4
+ 参照 ubomp-h5-sub 的「控件注册 + itemValueFrom 切换」范式:
5
+ - 按 meta.itemType 渲染对应类型化设置器(颜色/数字/枚举/开关/文本/图标/表达式…);
6
+ - 按 meta.itemTypeRange 决定是否显示「值来源」切换:const('0') / var('1') / image('2');
7
+ - 写回:直接 mutate 传入的 Property 实例的 itemValue / itemValueFrom,
8
+ 再由父级(PropTree / NodeInspector)统一刷新预览或定点并入样式实例树。
9
+
10
+ 本组件只渲染「单个属性」,不负责字段发现(那是 PropTree / NodeInspector 的事)。
11
+
12
+ 所有设置器统一 props:value / options / watches / itemInfo / disabled,
13
+ 统一 emit:input(新值) / change(触发预览) / sync(批量样式,仅 CustomSelect)。
14
+ -->
15
+ <template>
16
+ <div class="pc" :class="{ 'pc--readonly': disabled }">
17
+ <!-- 值来源切换(仅当 meta 声明了多于一种来源 且 允许切换时显示) -->
18
+ <div class="pc-srcs" v-if="sources.length > 1 && bExchange">
19
+ <button
20
+ v-for="s in sources"
21
+ :key="s.key"
22
+ type="button"
23
+ class="pc-src"
24
+ :class="{ 'is-active': property.itemValueFrom === s.key }"
25
+ :disabled="disabled"
26
+ @click="setSource(s.key)"
27
+ >{{ s.label }}</button>
28
+ </div>
29
+
30
+ <div class="pc-body">
31
+ <!-- 图片素材来源 -->
32
+ <SetImage
33
+ v-if="property.itemValueFrom === '2'"
34
+ :value="property.itemValue"
35
+ :options="property.itemOption"
36
+ :item-info="property"
37
+ :disabled="disabled"
38
+ @input="onInput"
39
+ @change="onChange"
40
+ />
41
+
42
+ <!-- 变量 / 表达式来源:带语法高亮、可拖拽调高度 -->
43
+ <SetExpr
44
+ v-else-if="property.itemValueFrom === '1'"
45
+ :value="property.itemValue"
46
+ :disabled="disabled"
47
+ :item-info="property"
48
+ @input="onInput"
49
+ @change="onChange"
50
+ />
51
+
52
+ <!-- 常量来源:按 itemType 分派 -->
53
+ <component
54
+ v-else
55
+ :is="setterComp.comp"
56
+ :value="property.itemValue"
57
+ :options="property.itemOption"
58
+ :watches="watches"
59
+ :item-info="property"
60
+ :disabled="disabled"
61
+ :kind="setterComp.kind"
62
+ :multiline="setterComp.multiline"
63
+ :boolean="setterComp.boolean"
64
+ @input="onInput"
65
+ @change="onChange"
66
+ @sync="onSync"
67
+ />
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script>
73
+ import SetText from './setters/SetText.vue'
74
+ import SetNumber from './setters/SetNumber.vue'
75
+ import SetSelect from './setters/SetSelect.vue'
76
+ import SetSegment from './setters/SetSegment.vue'
77
+ import SetSwitch from './setters/SetSwitch.vue'
78
+ import SetColor from './setters/SetColor.vue'
79
+ import SetIconSelect from './setters/SetIconSelect.vue'
80
+ import SetIfForm from './setters/SetIfForm.vue'
81
+ import SetNumberStyle from './setters/SetNumberStyle.vue'
82
+ import SetPadding from './setters/SetPadding.vue'
83
+ import SetGrid9 from './setters/SetGrid9.vue'
84
+ import SetBgPosition from './setters/SetBgPosition.vue'
85
+ import SetBgSize from './setters/SetBgSize.vue'
86
+ import SetGradient from './setters/SetGradient.vue'
87
+ import SetCustomSelect from './setters/SetCustomSelect.vue'
88
+ import SetList from './setters/SetList.vue'
89
+ import SetImage from './setters/SetImage.vue'
90
+ import SetCode from './setters/SetCode.vue'
91
+ import SetExpr from './setters/SetExpr.vue'
92
+
93
+ const FROM_LABEL = { '0': '常量', '1': '变量', '2': '图片' }
94
+ // meta.itemTypeRange 字符串 -> 允许的来源 key
95
+ const RANGE_KEYS = { const: '0', var: '1', image: '2' }
96
+
97
+ /** itemType -> 设置器组件 + 附加 props(kind / multiline / boolean) */
98
+ function resolveSetter(itemType) {
99
+ switch (itemType) {
100
+ case 'InputNumber': return { comp: SetNumber }
101
+ case 'Color': return { comp: SetColor }
102
+ case 'Select': return { comp: SetSelect }
103
+ case 'TabSelect': return { comp: SetSegment, kind: 'TabSelect' }
104
+ case 'Input': return { comp: SetText }
105
+ case 'Textarea': return { comp: SetText, multiline: true }
106
+ case 'Switcher': return { comp: SetSwitch }
107
+ case 'BooleanForm': return { comp: SetSwitch, boolean: true }
108
+ case 'PaddingStyle': return { comp: SetPadding }
109
+ case 'NumberStyle': return { comp: SetNumberStyle }
110
+ case 'PositionStyle': return { comp: SetGrid9, kind: 'PositionStyle' }
111
+ case 'BackgroundPositionStyle': return { comp: SetBgPosition }
112
+ case 'BackgroundSizeStyle': return { comp: SetBgSize }
113
+ case 'GradientStyle': return { comp: SetGradient }
114
+ case 'FontStyle': return { comp: SetSegment, kind: 'FontStyle' }
115
+ case 'TextDecorationStyle': return { comp: SetSegment, kind: 'TextDecorationStyle' }
116
+ case 'JustifyContentStyle': return { comp: SetSegment, kind: 'JustifyContentStyle' }
117
+ case 'AlignItemsStyle': return { comp: SetSegment, kind: 'AlignItemsStyle' }
118
+ case 'IconSelect': return { comp: SetIconSelect }
119
+ case 'CustomSelect': return { comp: SetCustomSelect }
120
+ case 'List': return { comp: SetList }
121
+ case 'IfForm': return { comp: SetIfForm }
122
+ case 'Code': return { comp: SetCode }
123
+ // 未识别 / (none) / 缺省:普通文本框,保证可回填、可编辑
124
+ default: return { comp: SetText }
125
+ }
126
+ }
127
+
128
+ export default {
129
+ name: 'PropControl',
130
+ components: {
131
+ SetText, SetNumber, SetSelect, SetSegment, SetSwitch, SetColor,
132
+ SetIconSelect, SetIfForm, SetNumberStyle, SetPadding, SetGrid9,
133
+ SetBgPosition, SetBgSize, SetGradient, SetCustomSelect, SetList, SetImage, SetCode, SetExpr,
134
+ },
135
+ props: {
136
+ property: { type: Object, required: true },
137
+ watches: { type: Object, default: () => ({}) },
138
+ disabled: { type: Boolean, default: false },
139
+ /**
140
+ * 是否允许显示「常量 / 变量 / 图片」来源切换。
141
+ * 对齐线上编辑器 editorOptions.bExchange:指令(directives)区块固定为 false,
142
+ * 故显隐设置 / 具体条件这类指令子项不渲染切换 tab,直接以常量形态呈现。
143
+ */
144
+ bExchange: { type: Boolean, default: true },
145
+ },
146
+ emits: ['change', 'sync'],
147
+ computed: {
148
+ itemType() {
149
+ return (this.property && this.property.itemType) || ''
150
+ },
151
+ sources() {
152
+ const raw = (this.property && this.property.itemTypeRange) || 'const'
153
+ const keys = String(raw).split(',').map((s) => s.trim()).filter(Boolean)
154
+ let list = keys
155
+ .map((k) => ({ key: RANGE_KEYS[k], label: FROM_LABEL[RANGE_KEYS[k]] }))
156
+ .filter((x) => x.key)
157
+ // 当前 itemValueFrom 若不在声明来源内(schema 越界或缺失),也纳入以免丢值;
158
+ // 但仅当它是合法来源键('0'/'1'/'2')时才塞入,忽略 undefined/非法值,
159
+ // 避免单一来源属性(如 Code 脚本)被误渲染出脏来源按钮。
160
+ const cur = String(this.property.itemValueFrom)
161
+ if (/^[012]$/.test(cur) && !list.some((s) => s.key === cur)) {
162
+ list = list.concat([{ key: cur, label: FROM_LABEL[cur] || '来源' + cur }])
163
+ }
164
+ return list
165
+ },
166
+ setterComp() {
167
+ return resolveSetter(this.itemType)
168
+ },
169
+ },
170
+ methods: {
171
+ onInput(v) {
172
+ this.property.itemValue = v
173
+ // 输入即触发预览刷新:NodeInspector 的 scheduleApply 自带 250ms 防抖,
174
+ // 故高频输入只会触发一次(约 250ms 后)重建预览,满足「即时预览」预期(问题 #6)。
175
+ this.$emit('change')
176
+ },
177
+ onChange() {
178
+ this.$emit('change')
179
+ },
180
+ onSync(map) {
181
+ this.$emit('sync', map)
182
+ },
183
+ setSource(k) {
184
+ if (this.disabled) return
185
+ const prop = this.property
186
+ const to = String(k)
187
+ if (prop.itemValueFrom === to) return
188
+ // 切换来源:先把「当前来源」的实时值归档进缓存,再切到目标来源;
189
+ // 目标来源有缓存值则原样回填,没有(从未编辑过)才给默认值。
190
+ // 这样每种来源(常量/变量/图片)各存各的,互不复写 —— 修复「切走再切回内容被清空」。
191
+ if (!prop._valuesByFrom) {
192
+ prop._valuesByFrom = { '0': undefined, '1': undefined, '2': undefined }
193
+ }
194
+ prop._valuesByFrom[prop.itemValueFrom] = prop.itemValue
195
+ prop.itemValueFrom = to
196
+ if (prop._valuesByFrom[to] !== undefined) {
197
+ prop.itemValue = prop._valuesByFrom[to]
198
+ } else if (to === '0') {
199
+ const d = prop.itemDefaultValue
200
+ prop.itemValue = d === undefined || d === null ? this.defaultFor() : d
201
+ prop._valuesByFrom[to] = prop.itemValue
202
+ } else {
203
+ // 变量('1') / 图片('2') 首次进入时给空串作为起点(非清空,后续切回会回填)
204
+ prop.itemValue = ''
205
+ prop._valuesByFrom[to] = ''
206
+ }
207
+ this.$emit('change')
208
+ },
209
+ defaultFor() {
210
+ switch (this.itemType) {
211
+ case 'Color': return '#000000'
212
+ case 'InputNumber': return 0
213
+ case 'Switcher': return '0'
214
+ case 'BooleanForm': return false
215
+ case 'TabSelect':
216
+ case 'Select': {
217
+ const opt = this.property.itemOption || {}
218
+ const list = opt.iParamList || opt.iTypeList || []
219
+ return list.length ? list[0].key : ''
220
+ }
221
+ default: return ''
222
+ }
223
+ },
224
+ },
225
+ }
226
+ </script>
227
+
228
+ <style scoped>
229
+ .pc { display: flex; flex-direction: column; gap: 6px; }
230
+ /* .pc-body 是块级容器,唯一子节点 .st-switch 是 inline-flex(内联级原子盒子)。
231
+ 行盒高度由继承的 line-height(body 默认 16px / normal ≈ 21.5px)决定,而非 18px 的开关本身,
232
+ 导致 .pc-body 被撑到 21.5px 且开关按 baseline 对齐而非垂直居中。置 0 让行盒坍缩为 18px。 */
233
+ .pc-body { line-height: 0; }
234
+ .pc-srcs { display: flex; gap: 4px; line-height: 1.5; }
235
+ .pc-src {
236
+ flex: 1; font-size: 12px; padding: 3px 0; border: 1px solid var(--border);
237
+ background: transparent; color: var(--text-dim); border-radius: 6px; cursor: pointer;
238
+ }
239
+ .pc-src.is-active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
240
+ .pc--readonly .pc-src { opacity: .6; cursor: not-allowed; }
241
+ </style>
@@ -0,0 +1,128 @@
1
+ <!--
2
+ 属性树递归渲染器。
3
+
4
+ 职责(与编辑器右侧面板的字段组织一致):
5
+ - group 类型 / 含 itemChildren 的属性 → 渲染可折叠分组头,递归渲染其子属性;
6
+ - 叶子属性 → 「标签 + 控件」一行,控件交给全局 <prop-control>;
7
+ - 把「从根到当前叶子的链」(Property[]) 透传给父级,父级据此决定:
8
+ * style 模式:调用 NodeEditModel.applyStyleChange(链首分组, 链身);
9
+ * property / directive 模式:整树导出(applyToSchema)。
10
+
11
+ 本组件通过全局注册 self-reference(prop-tree)+ 全局 prop-control 实现递归,
12
+ 避免与 PropControl 互相 import 造成的循环依赖。
13
+ -->
14
+ <template>
15
+ <div class="ptree" :class="{ 'ptree--plain': plain }">
16
+ <template v-for="p in properties" :key="p.itemId">
17
+ <!-- 分组(自身可见 且 存在可见后代才渲染,避免空分组) -->
18
+ <div v-if="isGroup(p) && groupVisible(p)" class="ptree__group">
19
+ <div class="ptree__group-head" type="button" @click="toggle(p)">
20
+ <span class="ptree__caret" :class="{ 'is-open': isOpen(p) }">▸</span>
21
+ <span class="ptree__group-name">{{ p.itemName || p.itemKey }}</span>
22
+ </div>
23
+ <div v-show="isOpen(p)" class="ptree__group-body">
24
+ <prop-tree
25
+ v-if="p.itemChildren && p.itemChildren.properties.length"
26
+ :properties="p.itemChildren.properties"
27
+ :mode="mode"
28
+ :depth="depth + 1"
29
+ :chain="chain.concat(p)"
30
+ :watches="watches"
31
+ :disabled="disabled"
32
+ :b-exchange="bExchange"
33
+ :plain="plain"
34
+ @leaf-change="$emit('leaf-change', $event)"
35
+ @leaf-sync="$emit('leaf-sync', $event)"
36
+ />
37
+ </div>
38
+ </div>
39
+
40
+ <!-- 叶子(itemCondition 判定为不可见时不渲染,与线上编辑器一致) -->
41
+ <div
42
+ v-else-if="p.itemVisible !== false"
43
+ class="ptree__row"
44
+ :class="{ 'ptree__row--disabled': disabled, 'ptree__row--block': isBlock(p) }"
45
+ >
46
+ <label class="ptree__label" :title="p.itemDesc || p.itemName || p.itemKey">{{ p.itemName || p.itemKey || p.itemType || '字段' }}</label>
47
+ <div class="ptree__control">
48
+ <prop-control
49
+ :property="p"
50
+ :watches="watches"
51
+ :disabled="disabled"
52
+ :b-exchange="bExchange"
53
+ @change="onLeaf(p)"
54
+ @sync="onSync"
55
+ />
56
+ </div>
57
+ </div>
58
+ </template>
59
+ </div>
60
+ </template>
61
+
62
+ <script>
63
+ export default {
64
+ name: 'PropTree',
65
+ props: {
66
+ properties: { type: Array, default: () => [] },
67
+ mode: { type: String, default: 'property' },
68
+ /** 嵌套深度:顶层 ptree 为 1,每深入一层 +1(用于决定是否显示分组计数) */
69
+ depth: { type: Number, default: 1 },
70
+ /** 从根到当前层父分组的链(Property[]),叶子变更时拼接上自身 */
71
+ chain: { type: Array, default: () => [] },
72
+ /** 同级联动快照,供 itemWatch 使用(如 flexDirection 决定对齐图标方向) */
73
+ watches: { type: Object, default: () => ({}) },
74
+ disabled: { type: Boolean, default: false },
75
+ /** 是否允许显示来源切换 tab(指令区块由父级传 false) */
76
+ bExchange: { type: Boolean, default: true },
77
+ /** 无 hover 高亮模式(事件抽屉脚本/参数区用,深/浅主题一致) */
78
+ plain: { type: Boolean, default: false },
79
+ },
80
+ emits: ['leaf-change', 'leaf-sync'],
81
+ data() {
82
+ // 分组默认展开
83
+ return { openMap: {} }
84
+ },
85
+ methods: {
86
+ isGroup(p) {
87
+ if (!p) return false
88
+ if (p.itemType === 'group') return true
89
+ const kids = p.itemChildren && p.itemChildren.properties
90
+ return !!(kids && kids.length)
91
+ },
92
+ childCount(p) {
93
+ const kids = p.itemChildren && p.itemChildren.properties
94
+ return kids ? kids.length : 0
95
+ },
96
+ /** 需要整行铺开的控件(代码编辑器这类高控件,横排会被挤扁) */
97
+ isBlock(p) {
98
+ return p && p.itemType === 'Code'
99
+ },
100
+ /** 分组是否应当渲染:自身可见 且 至少存在一个可见后代(叶子或子分组) */
101
+ groupVisible(p) {
102
+ if (p.itemVisible === false) return false
103
+ const kids = p.itemChildren && p.itemChildren.properties
104
+ if (!kids || !kids.length) return true
105
+ return kids.some((c) => {
106
+ if (c.itemVisible === false) return false
107
+ if (this.isGroup(c)) return this.groupVisible(c)
108
+ return true
109
+ })
110
+ },
111
+ isOpen(p) {
112
+ // 默认展开;仅当显式置为 false 时收起
113
+ return this.openMap[p.itemId] !== false
114
+ },
115
+ toggle(p) {
116
+ this.openMap[p.itemId] = this.openMap[p.itemId] === false
117
+ // 触发响应式
118
+ this.openMap = Object.assign({}, this.openMap)
119
+ },
120
+ onLeaf(p) {
121
+ this.$emit('leaf-change', this.chain.concat(p))
122
+ },
123
+ onSync(map) {
124
+ this.$emit('leaf-sync', map)
125
+ },
126
+ },
127
+ }
128
+ </script>
@@ -0,0 +1,74 @@
1
+ <!--
2
+ 单选按钮(官方 module 1979 / 502)。
3
+ 结构对齐在线编辑器渲染产物:
4
+ ce_radio_button > ce_radio_item > ce_radio_icon(svg) + span(text)
5
+ RadioButton 在 DSL 里是「单个独立单选」,无分组 key;
6
+ selected 状态由本地维护(点击切换),选中色用 selectColor、未选中用 unSelectColor。
7
+ -->
8
+ <template>
9
+ <span class="ce_radio_button" :style="rootStyle">
10
+ <div class="ce_radio_item" @click="toggle">
11
+ <span
12
+ v-if="buttonStyle !== 'Text'"
13
+ class="ce_radio_icon"
14
+ :style="{ width: iconSize + 'px', height: iconSize + 'px', marginRight: textSpacing + 'px' }"
15
+ >
16
+ <svg class="ce_svgradiostyle" :viewBox="'0 0 1024 1024'" :style="{ fill: color, stroke: color }">
17
+ <path v-for="(d, i) in radioPaths" :key="i" :d="d" />
18
+ </svg>
19
+ </span>
20
+ <span v-if="showText === '1' || showText === 1" :style="{ color, fontSize: fontSize + 'px' }">{{ doI18n(text) }}</span>
21
+ </div>
22
+ </span>
23
+ </template>
24
+
25
+ <script>
26
+ import base from './base.js'
27
+ import { RADIO_PATHS } from './icons.js'
28
+
29
+ export default {
30
+ name: 'CeRadioButton',
31
+ mixins: [base],
32
+ props: {
33
+ styleProps: { type: Object, default: () => ({}) },
34
+ value: { type: [String, Number], default: '' },
35
+ selectColor: { type: String, default: '#0082ff' },
36
+ unSelectColor: { type: String, default: '#333333' },
37
+ buttonStyle: { type: String, default: 'Circle' },
38
+ textSpacing: { type: [String, Number], default: '3' },
39
+ fontSize: { type: [String, Number], default: '14' },
40
+ iconSize: { type: [String, Number], default: '14' },
41
+ showText: { type: [String, Number], default: '1' },
42
+ text: { type: [String, Number], default: '' },
43
+ },
44
+ emits: ['change', 'selectChange'],
45
+ computed: {
46
+ rootStyle() {
47
+ return { overflow: 'hidden', boxSizing: 'border-box' }
48
+ },
49
+ selected() {
50
+ const v = this.value
51
+ return v !== '' && v != null && v !== false && v !== '0'
52
+ },
53
+ color() {
54
+ return this.selected ? this.selectColor : this.unSelectColor
55
+ },
56
+ radioPaths() {
57
+ if (this.selected && this.buttonStyle === 'CircleEmpty') {
58
+ return RADIO_PATHS.RadioButtonCircleEmptyCheck
59
+ }
60
+ if (this.buttonStyle === 'CircleEmpty') {
61
+ return RADIO_PATHS.RadioButtonCircleEmpty
62
+ }
63
+ return RADIO_PATHS.RadioButtonCircle
64
+ },
65
+ },
66
+ methods: {
67
+ toggle() {
68
+ const next = this.selected ? '' : (this.value || '1')
69
+ this.$emit('change', next)
70
+ this.$emit('selectChange', !!next)
71
+ },
72
+ },
73
+ }
74
+ </script>