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,244 @@
1
+ <!--
2
+ 颜色设置器:对应编辑器 common/Color。
3
+
4
+ 使用第三方开源颜色选择器 @ckpack/vue-color 的 Sketch 控件(原生 <input type="color">
5
+ 不支持 alpha,且交互简陋)。支持 rgba / hex / 空串;选择器以弹层形式挂载到 body,
6
+ 避免被面板滚动容器裁切。色块预览使用棋盘格背景以展示透明效果。
7
+ 重置按钮用「循环箭头」图标(语义更贴切),替代原来的 × 字符。
8
+ -->
9
+ <template>
10
+ <div class="st-color">
11
+ <!-- 色块预览 + 文本输入 + 重置(顶行) -->
12
+ <div class="st-color__top" ref="top">
13
+ <button
14
+ ref="swatch"
15
+ class="st-color__swatch"
16
+ type="button"
17
+ :title="text || '未设置'"
18
+ :disabled="disabled"
19
+ @click="togglePop"
20
+ >
21
+ <span class="st-color__checker" />
22
+ <span class="st-color__fill" :style="{ background: displayColor || 'transparent' }" />
23
+ </button>
24
+
25
+ <input
26
+ class="ni-input st-color__text"
27
+ type="text"
28
+ placeholder="如 #333333 / rgba(0,0,0,.5)"
29
+ :disabled="disabled"
30
+ :value="text"
31
+ @input="onTextInput($event.target.value)"
32
+ @change="$emit('change')"
33
+ />
34
+
35
+ <button
36
+ class="st-color__clear"
37
+ type="button"
38
+ title="重置(清空颜色)"
39
+ :disabled="disabled"
40
+ @click="clear"
41
+ >
42
+ <svg viewBox="0 0 16 16" class="st-ic" aria-hidden="true"><use href="#icon-reset" /></svg>
43
+ </button>
44
+ </div>
45
+
46
+ <!-- 透明度滑块(仅当颜色值非空时显示),作为快捷调节 -->
47
+ <div v-if="text" class="st-color__alpha-row">
48
+ <span class="st-color__alpha-label">透明度</span>
49
+ <input
50
+ class="st-color__alpha-slider"
51
+ type="range"
52
+ min="0"
53
+ max="100"
54
+ :disabled="disabled"
55
+ :value="Math.round(alpha * 100)"
56
+ @input="onAlphaChange($event.target.value)"
57
+ @change="$emit('change')"
58
+ />
59
+ <span class="st-color__alpha-val">{{ Math.round(alpha * 100) }}%</span>
60
+ </div>
61
+
62
+ <!-- 第三方颜色选择器弹层(挂载到 body,避免被滚动容器裁切) -->
63
+ <Teleport to="body">
64
+ <div
65
+ v-if="popOpen"
66
+ ref="pop"
67
+ class="st-color__pop"
68
+ :style="popStyle"
69
+ @mousedown.stop
70
+ >
71
+ <Sketch
72
+ :model-value="pickerValue"
73
+ :disable-alpha="false"
74
+ @update:model-value="onPicker"
75
+ />
76
+ </div>
77
+ </Teleport>
78
+ </div>
79
+ </template>
80
+
81
+ <script>
82
+ import { setterProps, setterEmits } from './props.js'
83
+ import { Sketch } from '@ckpack/vue-color'
84
+
85
+ /** 解析任意 CSS 颜色为 { r, g, b, a },解析失败返回 null */
86
+ function parseColor(v) {
87
+ const s = String(v || '').trim()
88
+ if (!s) return null
89
+ // #RRGGBB
90
+ let m = s.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
91
+ if (m) return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16), a: 1 }
92
+ // #RGB
93
+ m = s.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i)
94
+ if (m) {
95
+ const p = (c) => parseInt(c + c, 16)
96
+ return { r: p(m[1]), g: p(m[2]), b: p(m[3]), a: 1 }
97
+ }
98
+ // #RRGGBBAA
99
+ m = s.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
100
+ if (m) return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16), a: parseInt(m[4], 16) / 255 }
101
+ // rgb() / rgba()
102
+ m = s.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i)
103
+ if (m) {
104
+ const a = m[4] !== undefined ? parseFloat(m[4]) : 1
105
+ return { r: Math.round(parseFloat(m[1])), g: Math.round(parseFloat(m[2])), b: Math.round(parseFloat(m[3])), a }
106
+ }
107
+ return null
108
+ }
109
+
110
+ /** {r,g,b,a} → #rrggbb */
111
+ function toHex(c) {
112
+ if (!c) return '#000000'
113
+ const p = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0')
114
+ return '#' + p(c.r) + p(c.g) + p(c.b)
115
+ }
116
+
117
+ /** {r,g,b,a} → rgba() 字符串 */
118
+ function toRgba(c) {
119
+ if (!c) return ''
120
+ if (c.a >= 1) return toHex(c)
121
+ return `rgba(${Math.round(c.r)}, ${Math.round(c.g)}, ${Math.round(c.b)}, ${+c.a.toFixed(2)})`
122
+ }
123
+
124
+ export default {
125
+ name: 'SetColor',
126
+ components: { Sketch },
127
+ props: setterProps,
128
+ emits: setterEmits,
129
+ data() {
130
+ return {
131
+ _rgb: null, // 缓存解析后的 {r,g,b,a},供滑块编辑时用
132
+ popOpen: false,
133
+ popW: 240,
134
+ popStyle: { position: 'fixed', left: '0px', top: '0px' },
135
+ }
136
+ },
137
+ computed: {
138
+ text() {
139
+ return this.value === undefined || this.value === null ? '' : String(this.value)
140
+ },
141
+ parsed() {
142
+ return parseColor(this.text) || { r: 0, g: 0, b: 0, a: 1 }
143
+ },
144
+ /** 传给 Sketch 的初始颜色(空值时给黑色作为起点) */
145
+ pickerValue() {
146
+ return this.text || '#000000'
147
+ },
148
+ /** 当前透明度 0~1 */
149
+ alpha() {
150
+ return this.parsed.a
151
+ },
152
+ /** 用于色块预览的完整颜色值(含 alpha) */
153
+ displayColor() {
154
+ return toRgba(this.parsed)
155
+ },
156
+ },
157
+ watch: {
158
+ text() {
159
+ this._rgb = parseColor(this.text)
160
+ },
161
+ },
162
+ beforeUnmount() {
163
+ this.removeDocListener()
164
+ },
165
+ methods: {
166
+ onTextInput(v) {
167
+ this._rgb = parseColor(v)
168
+ this.$emit('input', v)
169
+ },
170
+ onAlphaChange(pct) {
171
+ const a = Math.max(0, Math.min(1, parseInt(pct, 10) / 100))
172
+ const base = this._rgb || this.parsed
173
+ const c = { ...base, a }
174
+ this._rgb = c
175
+ this.$emit('input', toRgba(c))
176
+ this.$emit('change')
177
+ },
178
+ /** 第三方选择器回调:payload 含 hex / rgba([r,g,b,a]) / a */
179
+ onPicker(c) {
180
+ const rgba = c && c.rgba
181
+ let r, g, b, a
182
+ if (Array.isArray(rgba)) {
183
+ ;[r, g, b, a] = rgba
184
+ } else if (rgba) {
185
+ r = rgba.r; g = rgba.g; b = rgba.b; a = rgba.a
186
+ } else {
187
+ const parsed = parseColor(c.hex) || { r: 0, g: 0, b: 0, a: 1 }
188
+ r = parsed.r; g = parsed.g; b = parsed.b; a = parsed.a
189
+ }
190
+ a = a == null ? 1 : a
191
+ const hex = c.hex || toHex({ r, g, b, a: 1 })
192
+ this._rgb = { r, g, b, a }
193
+ this.$emit('input', a >= 1 ? hex : `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${+a.toFixed(2)})`)
194
+ this.$emit('change')
195
+ },
196
+ clear() {
197
+ if (this.disabled) return
198
+ this._rgb = null
199
+ this.closePop()
200
+ this.$emit('input', '')
201
+ this.$emit('change')
202
+ },
203
+ togglePop() {
204
+ if (this.disabled) return
205
+ this.popOpen ? this.closePop() : this.openPop()
206
+ },
207
+ openPop() {
208
+ const el = this.$refs.swatch
209
+ if (!el) return
210
+ const rect = el.getBoundingClientRect()
211
+ const topRect = (this.$refs.top && this.$refs.top.getBoundingClientRect()) || rect
212
+ const popW = Math.max(220, Math.min(Math.round(topRect.width), 400))
213
+ const popH = 320
214
+ const vw = window.innerWidth
215
+ const vh = window.innerHeight
216
+ let left = topRect.left
217
+ let top = rect.bottom + 6
218
+ if (left + popW > vw - 8) left = Math.max(8, vw - popW - 8)
219
+ if (top + popH > vh - 8) top = Math.max(8, rect.top - popH - 6)
220
+ this.popW = popW
221
+ this.popStyle = { position: 'fixed', left: left + 'px', top: top + 'px', width: popW + 'px', zIndex: 4000 }
222
+ this.popOpen = true
223
+ this.$nextTick(() => {
224
+ document.addEventListener('mousedown', this.onDocClick, true)
225
+ })
226
+ },
227
+ closePop() {
228
+ if (!this.popOpen) return
229
+ this.popOpen = false
230
+ this.removeDocListener()
231
+ },
232
+ removeDocListener() {
233
+ document.removeEventListener('mousedown', this.onDocClick, true)
234
+ },
235
+ onDocClick(e) {
236
+ const pop = this.$refs.pop
237
+ const sw = this.$refs.swatch
238
+ if (pop && pop.contains(e.target)) return
239
+ if (sw && sw.contains(e.target)) return
240
+ this.closePop()
241
+ },
242
+ },
243
+ }
244
+ </script>
@@ -0,0 +1,58 @@
1
+ <!--
2
+ 常用样式设置器:对应编辑器 custom/CustomSelect。
3
+
4
+ itemOption.list 每一项是一段「DOM 描述 + sync 映射」:
5
+ {type:'div', style:{…}, list:[{type:'span', innerText:'黑色', style:{…}}],
6
+ sync:{'style.font.color':'#333333', 'style.font.fontSize':'14', …}}
7
+ 点击后编辑器会把 sync 里的点号路径批量写回组件(style.<分组>.<字段>)。
8
+ 这里不直接改数据,而是向上抛 `sync` 事件,由 NodeInspector 统一落到 NodeEditModel,
9
+ 这样样式的定点写入仍然走 Style.applyChange,不会污染实例树。
10
+ -->
11
+ <template>
12
+ <div class="st-customsel">
13
+ <div
14
+ v-for="(info, i) in list"
15
+ :key="i"
16
+ class="st-customsel__item"
17
+ :style="normalizeStyle(info.style)"
18
+ :title="'应用:' + Object.keys(info.sync || {}).join('、')"
19
+ @click="apply(info)"
20
+ >
21
+ <span v-for="(c, ci) in info.list || []" :key="ci" :style="normalizeStyle(c.style)">{{ c.innerText }}</span>
22
+ <span v-if="!(info.list || []).length">{{ info.innerText || '样式' + (i + 1) }}</span>
23
+ </div>
24
+ <p v-if="!list.length" class="st-list__empty">未配置常用样式</p>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ import { setterProps, setterEmits } from './props.js'
30
+
31
+ /** 只保留视觉相关的样式,剔除会撑破面板的定位/尺寸类声明 */
32
+ const DROP = new Set(['width', 'height', 'lineHeight', 'marginRight', 'cursor', 'textAlign'])
33
+
34
+ export default {
35
+ name: 'SetCustomSelect',
36
+ props: setterProps,
37
+ emits: [...setterEmits, 'sync'],
38
+ computed: {
39
+ list() {
40
+ return this.options.list || []
41
+ },
42
+ },
43
+ methods: {
44
+ normalizeStyle(s) {
45
+ if (!s || typeof s !== 'object') return {}
46
+ const out = {}
47
+ for (const k in s) if (!DROP.has(k)) out[k] = s[k]
48
+ return out
49
+ },
50
+ apply(info) {
51
+ if (this.disabled) return
52
+ const sync = info.sync || {}
53
+ if (!Object.keys(sync).length) return
54
+ this.$emit('sync', sync)
55
+ },
56
+ },
57
+ }
58
+ </script>
@@ -0,0 +1,107 @@
1
+ <!--
2
+ 变量表达式设置器:节点属性「变量」来源(itemValueFrom === '1')下的表达式输入框。
3
+
4
+ 值形态:itemValue 是**裸字符串**(如 "$name" / "($a ? $b : $c)"),不是对象。
5
+ 编辑器实例由 CodeJar 托管 DOM,因此**不能**用 :value 双向绑定重渲染,
6
+ 只在挂载时灌一次初值;外部值变化(切换节点/非空聚焦时)通过「非聚焦才同步」守卫避免打断输入。
7
+
8
+ 语法高亮:复用 SetCode 的 CodeJar + Prism(javascript) 方案,并额外注册一条
9
+ "$ 变量"规则,让绑定变量(如 $name)以独立青色凸显,更贴合表达式语义。
10
+ 高度可调:容器 .st-expr 设 resize: vertical,底部右下角出现原生拖拽手柄,便于编辑长表达式。
11
+ -->
12
+ <template>
13
+ <div class="st-expr" :class="{ 'st-expr--disabled': disabled }">
14
+ <div ref="editor" class="st-expr__editor" :class="{ 'is-disabled': disabled }"></div>
15
+ <span v-if="placeholder && !code" class="st-expr__ph">{{ placeholder }}</span>
16
+ <div class="st-expr__foot">
17
+ <span class="st-expr__hint">{{ lines }} 行 · {{ chars }} 字符</span>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import { CodeJar } from 'codejar'
24
+ import Prism from 'prismjs'
25
+ import 'prismjs/components/prism-javascript'
26
+ import { setterProps, setterEmits } from './props.js'
27
+
28
+ // 表达式里 $name 形式的绑定变量单独高亮(在 javascript 规则之前插入)
29
+ if (Prism.languages.javascript && !Prism.languages.javascript['binding-var']) {
30
+ Prism.languages.insertBefore('javascript', 'keyword', {
31
+ 'binding-var': /\$[A-Za-z_$][\w$]*/,
32
+ })
33
+ }
34
+
35
+ export default {
36
+ name: 'SetExpr',
37
+ props: {
38
+ ...setterProps,
39
+ // 可选占位提示(如条件表达式的「表达式,如 $singleMode」)。空字符串则不渲染。
40
+ placeholder: { type: String, default: '' },
41
+ },
42
+ emits: setterEmits,
43
+ data() {
44
+ return { code: '' }
45
+ },
46
+ computed: {
47
+ lines() {
48
+ return this.code ? this.code.split('\n').length : 0
49
+ },
50
+ chars() {
51
+ return this.code.length
52
+ },
53
+ },
54
+ watch: {
55
+ // 外部值变化(如切换节点、撤销/重做)时同步,但正在编辑(聚焦)不打断光标
56
+ value(v) {
57
+ this.syncFromValue(v)
58
+ },
59
+ },
60
+ mounted() {
61
+ this.code = this.readValue(this.value)
62
+ this.$nextTick(() => this.initJar())
63
+ },
64
+ beforeUnmount() {
65
+ if (this._t) clearTimeout(this._t)
66
+ if (this._jar && this._jar.destroy) this._jar.destroy()
67
+ },
68
+ methods: {
69
+ readValue(v) {
70
+ if (v == null) return ''
71
+ return typeof v === 'string' ? v : String(v)
72
+ },
73
+ syncFromValue(v) {
74
+ const next = this.readValue(v)
75
+ if (next === this.code) return
76
+ const el = this.$refs.editor
77
+ // 用户正在输入时,DOM 已经是最新内容,不要回填以免光标跳到末尾
78
+ if (el && document.activeElement === el) return
79
+ this.code = next
80
+ if (this._jar) this._jar.updateCode(next)
81
+ },
82
+ initJar() {
83
+ const el = this.$refs.editor
84
+ if (!el) return
85
+ const highlight = (editor) => {
86
+ editor.innerHTML = Prism.highlight(editor.textContent, Prism.languages.javascript, 'javascript')
87
+ }
88
+ this._jar = CodeJar(el, highlight, {
89
+ tab: ' ',
90
+ indentOn: /[({[]$/,
91
+ addClosing: true,
92
+ history: true,
93
+ })
94
+ this._jar.updateCode(this.code)
95
+ this._jar.onUpdate((code) => this.onCode(code))
96
+ },
97
+ onCode(code) {
98
+ if (this.disabled) return
99
+ this.code = code
100
+ this.$emit('input', code)
101
+ // 表达式输入频繁,change(触发重建预览)做防抖
102
+ if (this._t) clearTimeout(this._t)
103
+ this._t = setTimeout(() => this.$emit('change'), 400)
104
+ },
105
+ },
106
+ }
107
+ </script>
@@ -0,0 +1,125 @@
1
+ <!--
2
+ 渐变设置器:对应编辑器 style/GradientStyle。
3
+ DSL 值是一整条 CSS 表达式:`linear-gradient(to bottom, #fff, #000)`。
4
+ 解析时要注意色值里可能带括号(rgba(...)),所以拆分逗号得跳过括号内部。
5
+ -->
6
+ <template>
7
+ <div class="st-gradient">
8
+ <div class="st-gradient__preview" :style="{ background: preview }" />
9
+
10
+ <Dropdown
11
+ :value="direction"
12
+ :options="DIRECTIONS.map(d => ({ key: d.key, value: d.desc }))"
13
+ size="sm"
14
+ block
15
+ :disabled="disabled"
16
+ @input="setDirection($event)"
17
+ />
18
+
19
+ <div v-for="(s, i) in stops" :key="'stop' + i" class="st-gradient__stop">
20
+ <SetColor :value="s.color" :disabled="disabled" @input="setStop(i, $event)" @change="emitOut" />
21
+ <button class="st-gradient__del" type="button" title="删除该渐变色" @click="removeStop(i)">✕</button>
22
+ </div>
23
+
24
+ <button class="st-minibtn" type="button" :disabled="disabled" @click="addStop">+ 新增渐变色</button>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ import { setterProps, setterEmits } from './props.js'
30
+ import SetColor from './SetColor.vue'
31
+ import Dropdown from '../Dropdown.vue'
32
+
33
+ const DIRECTIONS = [
34
+ { key: 'to bottom', desc: '从上到下' },
35
+ { key: 'to top', desc: '从下到上' },
36
+ { key: 'to left', desc: '从右到左' },
37
+ { key: 'to right', desc: '从左到右' },
38
+ { key: 'to bottom right', desc: '从左上到右下' },
39
+ { key: 'to bottom left', desc: '从右上到左下' },
40
+ { key: 'to top left', desc: '从右下到左上' },
41
+ { key: 'to top right', desc: '从左下到右上' },
42
+ ]
43
+
44
+ /** 按逗号切分但跳过括号内部(rgba(0,0,0,.5) 不能被拆开) */
45
+ function splitTop(str) {
46
+ const out = []
47
+ let depth = 0
48
+ let start = 0
49
+ for (let i = 0; i < str.length; i++) {
50
+ const ch = str[i]
51
+ if (ch === '(') depth++
52
+ else if (ch === ')') depth--
53
+ else if (ch === ',' && depth === 0) {
54
+ out.push(str.slice(start, i).trim())
55
+ start = i + 1
56
+ }
57
+ }
58
+ out.push(str.slice(start).trim())
59
+ return out.filter((s) => s !== '')
60
+ }
61
+
62
+ export default {
63
+ name: 'SetGradient',
64
+ components: { SetColor, Dropdown },
65
+ props: setterProps,
66
+ emits: setterEmits,
67
+ data() {
68
+ return { DIRECTIONS, direction: 'to bottom', stops: [] }
69
+ },
70
+ computed: {
71
+ preview() {
72
+ if (!this.stops.length) return 'transparent'
73
+ const colors = this.stops.map((s) => s.color).filter(Boolean)
74
+ if (colors.length < 2) return colors[0] || 'transparent'
75
+ return `linear-gradient(${this.direction}, ${colors.join(', ')})`
76
+ },
77
+ },
78
+ watch: {
79
+ value: { immediate: true, handler() { this.parse() } },
80
+ },
81
+ methods: {
82
+ parse() {
83
+ const raw = String(this.value || '').trim()
84
+ const m = raw.match(/linear-gradient\((.+)\)$/)
85
+ if (!m) {
86
+ this.direction = this.options.defaultDirection || DIRECTIONS[0].key
87
+ this.stops = []
88
+ return
89
+ }
90
+ const parts = splitTop(m[1])
91
+ this.direction = (parts[0] || '').toLowerCase() || DIRECTIONS[0].key
92
+ this.stops = parts.slice(1).map((c) => ({ color: c }))
93
+ },
94
+ setDirection(v) {
95
+ this.direction = v
96
+ this.emitValue()
97
+ this.$emit('change')
98
+ },
99
+ setStop(i, color) {
100
+ this.stops[i].color = color
101
+ this.emitValue()
102
+ },
103
+ addStop() {
104
+ if (this.disabled) return
105
+ this.stops.push({ color: '#ffffff' })
106
+ this.emitValue()
107
+ this.$emit('change')
108
+ },
109
+ removeStop(i) {
110
+ if (this.disabled) return
111
+ this.stops.splice(i, 1)
112
+ this.emitValue()
113
+ this.$emit('change')
114
+ },
115
+ emitValue() {
116
+ const colors = this.stops.map((s) => s.color).filter((c) => c !== '' && c !== undefined)
117
+ // 不足两个色标时渐变无意义,输出空串(与编辑器一致:清掉 backgroundImage)
118
+ this.$emit('input', colors.length ? `linear-gradient(${this.direction}, ${colors.join(', ')})` : '')
119
+ },
120
+ emitOut() {
121
+ this.$emit('change')
122
+ },
123
+ },
124
+ }
125
+ </script>
@@ -0,0 +1,68 @@
1
+ <!--
2
+ 九宫格对齐设置器:对应编辑器 style/PositionStyle 与 BackgroundPositionStyle 的左半部分。
3
+
4
+ 两者格子布局一致、只是取值不同:
5
+ PositionStyle TopLeft / TopCenter / … / BottomRight
6
+ BackgroundPositionStyle "0% 0%" / "50% 0%" / … / "100% 100%"
7
+ 通过 kind 切换取值表。
8
+ -->
9
+ <template>
10
+ <div class="st-grid9">
11
+ <div
12
+ v-for="c in cells"
13
+ :key="c.key"
14
+ class="st-grid9__cell"
15
+ :class="{ 'st-grid9__cell--on': String(c.key) === String(value) }"
16
+ :title="c.value"
17
+ @click="pick(c)"
18
+ >
19
+ <span class="st-grid9__dot" />
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script>
25
+ import { setterProps, setterEmits } from './props.js'
26
+
27
+ const POSITION = [
28
+ { key: 'TopLeft', value: '上左' },
29
+ { key: 'TopCenter', value: '上中' },
30
+ { key: 'TopRight', value: '上右' },
31
+ { key: 'CenterLeft', value: '中左' },
32
+ { key: 'Center', value: '中间' },
33
+ { key: 'CenterRight', value: '中右' },
34
+ { key: 'BottomLeft', value: '下左' },
35
+ { key: 'BottomCenter', value: '下中' },
36
+ { key: 'BottomRight', value: '下右' },
37
+ ]
38
+
39
+ const BG_POSITION = [
40
+ { key: '0% 0%', value: '左上对齐' },
41
+ { key: '50% 0%', value: '中上对齐' },
42
+ { key: '100% 0%', value: '右上对齐' },
43
+ { key: '0% 50%', value: '左中对齐' },
44
+ { key: '50% 50%', value: '居中对齐' },
45
+ { key: '100% 50%', value: '右中对齐' },
46
+ { key: '0% 100%', value: '左下对齐' },
47
+ { key: '50% 100%', value: '中下对齐' },
48
+ { key: '100% 100%', value: '右下对齐' },
49
+ ]
50
+
51
+ export default {
52
+ name: 'SetGrid9',
53
+ props: { ...setterProps, kind: { type: String, default: 'PositionStyle' } },
54
+ emits: setterEmits,
55
+ computed: {
56
+ cells() {
57
+ return this.kind === 'BackgroundPositionStyle' ? BG_POSITION : POSITION
58
+ },
59
+ },
60
+ methods: {
61
+ pick(c) {
62
+ if (this.disabled) return
63
+ this.$emit('input', c.key)
64
+ this.$emit('change')
65
+ },
66
+ },
67
+ }
68
+ </script>
@@ -0,0 +1,67 @@
1
+ <!--
2
+ 图标选择设置器:对应编辑器 custom/IconSelect。
3
+ 图标来自 sprite(itemOption.image / iIconList),预览复用工程已有的 SvgSymbols。
4
+ 收起时只显示当前图标 + 名称,点开展开成网格。
5
+ -->
6
+ <template>
7
+ <div class="st-iconsel-wrap">
8
+ <div class="st-iconsel">
9
+ <span class="st-iconsel__preview">
10
+ <SvgSymbols v-if="value" :icon="String(value)" color="currentColor" />
11
+ <span v-else style="font-size:11px;color:var(--text-faint)">无</span>
12
+ </span>
13
+ <input
14
+ class="ni-input"
15
+ type="text"
16
+ placeholder="图标 key"
17
+ :disabled="disabled"
18
+ :value="value || ''"
19
+ @input="$emit('input', $event.target.value)"
20
+ @change="$emit('change')"
21
+ />
22
+ <button class="st-minibtn" type="button" @click="open = !open">{{ open ? '收起' : '选择' }}</button>
23
+ </div>
24
+
25
+ <div v-if="open" class="st-iconsel__grid">
26
+ <span
27
+ v-for="ic in list"
28
+ :key="ic.key"
29
+ class="st-iconsel__cell"
30
+ :class="{ 'st-iconsel__cell--on': ic.key === value }"
31
+ :title="ic.name || ic.value || ic.key"
32
+ @click="pick(ic)"
33
+ >
34
+ <SvgSymbols :icon="ic.key" color="currentColor" />
35
+ </span>
36
+ <p v-if="!list.length" class="st-list__empty">该组件未提供图标列表</p>
37
+ </div>
38
+ </div>
39
+ </template>
40
+
41
+ <script>
42
+ import { setterProps, setterEmits } from './props.js'
43
+ import SvgSymbols from '../SvgSymbols.vue'
44
+
45
+ export default {
46
+ name: 'SetIconSelect',
47
+ components: { SvgSymbols },
48
+ props: setterProps,
49
+ emits: setterEmits,
50
+ data() {
51
+ return { open: false }
52
+ },
53
+ computed: {
54
+ list() {
55
+ return this.options.iIconList || []
56
+ },
57
+ },
58
+ methods: {
59
+ pick(ic) {
60
+ if (this.disabled) return
61
+ this.open = false
62
+ this.$emit('input', ic.key)
63
+ this.$emit('change')
64
+ },
65
+ },
66
+ }
67
+ </script>