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,516 @@
1
+ <template>
2
+ <div class="jt">
3
+ <!-- 仅根节点、且可编辑时显示批量控制条(运行态面板为只读,不需要工具栏) -->
4
+ <div v-if="isRoot && editable" class="jt__toolbar">
5
+ <button class="jt__tool" type="button" @click="setOpen(true)">全部展开</button>
6
+ <button class="jt__tool" type="button" @click="setOpen(false)">全部折叠</button>
7
+ <button class="jt__tool jt__tool--add" type="button" @click="addChild" title="添加子节点">+ 添加</button>
8
+ </div>
9
+
10
+ <ul class="jt__list" :class="{ 'jt__list--root': isRoot }">
11
+ <li v-for="(item, i) in entries" :key="i" class="jt__node">
12
+ <!-- 复合类型(对象 / 数组):可折叠 -->
13
+ <template v-if="isComplex(item.value)">
14
+ <div
15
+ class="jt__row jt__row--branch"
16
+ :class="{ 'jt__row--selected': isSelected(item) }"
17
+ @click="onBranchClick(item)"
18
+ @contextmenu.prevent="showContextMenu(item, $event)"
19
+ >
20
+ <span class="jt__caret" :class="{ 'jt__caret--open': isOpen(item.key) }">▶</span>
21
+ <span
22
+ v-if="!item.isArrayBranch"
23
+ class="jt__key jt__editable"
24
+ @dblclick.stop="startEditKey(item)"
25
+ >
26
+ <input
27
+ v-if="editingKey === item.key"
28
+ ref="keyInput"
29
+ class="jt__edit"
30
+ :value="item.keyLabel"
31
+ @keydown.enter="confirmEditKey(item, $event)"
32
+ @keydown.escape="cancelEdit"
33
+ @blur="confirmEditKey(item, $event)"
34
+ />
35
+ <template v-else>{{ item.keyLabel }}</template>
36
+ </span>
37
+ <span
38
+ class="jt__bracket jt__editable"
39
+ @dblclick.stop="editable && startEditComplex(item)"
40
+ >{{ item.isArrayBranch ? '[' : '{' }}</span>
41
+ <span v-if="!isOpen(item.key)" class="jt__preview jt__editable" @dblclick.stop="editable && startEditComplex(item)">{{ previewText(item.value) }}</span>
42
+ </div>
43
+
44
+ <!-- 复合类型整体编辑弹层 -->
45
+ <div v-if="editingComplex === item.key" class="jt__json-edit" @click.stop>
46
+ <textarea
47
+ ref="jsonInput"
48
+ class="jt__json-textarea"
49
+ :value="jsonEditText"
50
+ @input="jsonEditText = $event.target.value"
51
+ @keydown.escape="cancelEdit"
52
+ @keydown.ctrl.enter="confirmEditComplex(item)"
53
+ @keydown.meta.enter="confirmEditComplex(item)"
54
+ ></textarea>
55
+ <div class="jt__json-actions">
56
+ <button type="button" class="jt__json-btn" @click="confirmEditComplex(item)">确定</button>
57
+ <button type="button" class="jt__json-btn jt__json-btn--cancel" @click="cancelEdit">取消</button>
58
+ <span class="jt__json-hint">Ctrl+Enter 确认 · Esc 取消</span>
59
+ </div>
60
+ </div>
61
+
62
+ <JsonTree
63
+ v-if="isOpen(item.key)"
64
+ class="jt__child"
65
+ :data="item.value"
66
+ :depth="depth + 1"
67
+ :rt="rt"
68
+ :path="item.path"
69
+ :editable="editable"
70
+ />
71
+
72
+ <div v-if="isOpen(item.key)" class="jt__row jt__row--close">
73
+ <span class="jt__bracket">{{ item.isArrayBranch ? ']' : '}' }}</span>
74
+ <span class="jt__count">{{ item.count }} {{ item.isArrayBranch ? '项' : '键' }}</span>
75
+ </div>
76
+ </template>
77
+
78
+ <!-- 基础类型:叶子 -->
79
+ <div
80
+ v-else
81
+ class="jt__row jt__row--leaf"
82
+ :class="{ 'jt__row--selected': isSelected(item) }"
83
+ @click="selectNode(item)"
84
+ @contextmenu.prevent="showContextMenu(item, $event)"
85
+ >
86
+ <span class="jt__caret jt__caret--leaf"></span>
87
+ <span
88
+ v-if="item.keyLabel !== null && editable"
89
+ class="jt__key jt__editable"
90
+ @dblclick.stop="startEditKey(item)"
91
+ >
92
+ <input
93
+ v-if="editingKey === item.key"
94
+ ref="keyInput"
95
+ class="jt__edit"
96
+ :value="item.keyLabel"
97
+ @keydown.enter="confirmEditKey(item, $event)"
98
+ @keydown.escape="cancelEdit"
99
+ @blur="confirmEditKey(item, $event)"
100
+ />
101
+ <template v-else>{{ item.keyLabel }}</template>
102
+ </span>
103
+ <span v-if="item.keyLabel !== null && !editable" class="jt__key">{{ item.keyLabel }}</span>
104
+ <span v-if="item.keyLabel !== null" class="jt__colon">:</span>
105
+ <span
106
+ class="jt__val"
107
+ :class="['jt__val--' + item.type, { 'jt__editable': editable }]"
108
+ @dblclick.stop="editable && startEditValue(item)"
109
+ >
110
+ <input
111
+ v-if="editingValue === item.key"
112
+ ref="valInput"
113
+ class="jt__edit"
114
+ :value="formatForEdit(item.value, item.type)"
115
+ @keydown.enter="confirmEditValue(item, $event)"
116
+ @keydown.escape="cancelEdit"
117
+ @blur="confirmEditValue(item, $event)"
118
+ />
119
+ <template v-else>{{ formatPrimitive(item.value, item.type) }}</template>
120
+ </span>
121
+ <button v-if="editable" class="jt__del" type="button" @click.stop="deleteNode(item)" title="删除节点">×</button>
122
+ </div>
123
+ </li>
124
+ </ul>
125
+
126
+ <!-- 右键菜单(fixed 定位,全局可见) -->
127
+ <div
128
+ v-if="ctxMenu.visible"
129
+ class="jt__ctxmenu"
130
+ :style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px' }"
131
+ @click.stop
132
+ >
133
+ <button class="jt__ctxmenu-item" type="button" @click="ctxCopyPath">复制路径</button>
134
+ <!-- 删除节点仅在可编辑(绑定数据 tab)时提供;只读树(如运行态面板)只保留复制路径 -->
135
+ <button v-if="editable" class="jt__ctxmenu-item jt__ctxmenu-item--del" type="button" @click="ctxDelete">删除节点</button>
136
+ </div>
137
+ <div v-if="ctxMenu.visible" class="jt__ctxmenu-mask" @click="hideContextMenu" @contextmenu.prevent="hideContextMenu"></div>
138
+ </div>
139
+ </template>
140
+
141
+ <script>
142
+ import { reactive } from 'vue'
143
+ import { copyText } from '@/runtime/clipboard.js'
144
+ import { showAppToast } from '@/runtime/app-toast.js'
145
+
146
+ // 模块级共享状态:驱动「全部展开 / 全部折叠」和「选中节点」。
147
+ // 所有 JsonTree 实例(含递归子实例)共享同一份,避免 provide/inject
148
+ // 在嵌套层级被逐层覆盖的问题。
149
+ const treeState = reactive({ openAll: null, selectedPath: null })
150
+
151
+ /**
152
+ * 沿路径设置 rootScope 中的值 / 删除 / 重命名。
153
+ * path 为字符串数组,如 ['account', 'name'] 对应 rootScope.account.name
154
+ * Vue 3 的 Proxy 响应式能自动追踪新增/删除属性,直接赋值/delete 即可。
155
+ */
156
+ function setByPath(obj, path, value) {
157
+ let cur = obj
158
+ for (let i = 0; i < path.length - 1; i++) cur = cur[path[i]]
159
+ cur[path[path.length - 1]] = value
160
+ }
161
+
162
+ function deleteByPath(obj, path) {
163
+ let cur = obj
164
+ for (let i = 0; i < path.length - 1; i++) cur = cur[path[i]]
165
+ delete cur[path[path.length - 1]]
166
+ }
167
+
168
+ function renameKey(obj, path, oldKey, newKey) {
169
+ if (!newKey || newKey === oldKey) return false
170
+ let cur = obj
171
+ for (let i = 0; i < path.length; i++) cur = cur[path[i]]
172
+ if (newKey in cur) return false // 目标 key 已存在
173
+ const val = cur[oldKey]
174
+ delete cur[oldKey]
175
+ cur[newKey] = val
176
+ return true
177
+ }
178
+
179
+ function getByPath(obj, path) {
180
+ let cur = obj
181
+ for (const seg of path) {
182
+ if (cur == null || typeof cur !== 'object') return undefined
183
+ cur = cur[seg]
184
+ }
185
+ return cur
186
+ }
187
+
188
+ export default {
189
+ name: 'JsonTree',
190
+ props: {
191
+ data: { type: [Object, Array], default: () => ({}) },
192
+ depth: { type: Number, default: 0 },
193
+ rt: { type: Object, default: null },
194
+ path: { type: Array, default: () => [] },
195
+ editable: { type: Boolean, default: false },
196
+ schema: { type: Object, default: null },
197
+ },
198
+ data() {
199
+ return {
200
+ localOpen: {},
201
+ editingKey: null,
202
+ editingValue: null,
203
+ editingComplex: null,
204
+ jsonEditText: '',
205
+ ctxMenu: {
206
+ visible: false,
207
+ x: 0,
208
+ y: 0,
209
+ item: null,
210
+ },
211
+ }
212
+ },
213
+ computed: {
214
+ isRoot() {
215
+ return this.depth === 0
216
+ },
217
+ entries() {
218
+ const d = this.data
219
+ if (Array.isArray(d)) {
220
+ return d.map((v, i) => this.makeEntry(String(i), v, true, i))
221
+ }
222
+ if (d && typeof d === 'object') {
223
+ return Object.keys(d).map((k) => this.makeEntry(k, d[k], false, k))
224
+ }
225
+ return []
226
+ },
227
+ },
228
+ watch: {
229
+ data() {
230
+ this.initOpen()
231
+ // 数据整体替换(切换卡片)时清空选中,避免选中路径失效
232
+ if (this.isRoot) treeState.selectedPath = null
233
+ },
234
+ },
235
+ created() {
236
+ if (this.isRoot) treeState.openAll = null
237
+ this.initOpen()
238
+ },
239
+ methods: {
240
+ makeEntry(keyLabel, value, isArrayBranch, rawKey) {
241
+ const isArr = Array.isArray(value)
242
+ const count = isArr ? value.length : Object.keys(value || {}).length
243
+ return {
244
+ key: String(rawKey),
245
+ keyLabel,
246
+ value,
247
+ isArrayBranch,
248
+ isArray: isArr,
249
+ count,
250
+ type: this.valueType(value),
251
+ path: [...this.path, rawKey],
252
+ }
253
+ },
254
+ valueType(v) {
255
+ if (v === null) return 'null'
256
+ if (Array.isArray(v)) return 'array'
257
+ return typeof v
258
+ },
259
+ isComplex(v) {
260
+ return v !== null && typeof v === 'object'
261
+ },
262
+ initOpen() {
263
+ const m = {}
264
+ this.entries.forEach((e) => {
265
+ m[e.key] = this.depth < 2
266
+ })
267
+ this.localOpen = m
268
+ },
269
+ isOpen(key) {
270
+ if (treeState.openAll !== null) return treeState.openAll
271
+ return !!this.localOpen[key]
272
+ },
273
+ toggle(key) {
274
+ const next = !this.isOpen(key)
275
+ this.localOpen[key] = next
276
+ treeState.openAll = null
277
+ },
278
+ // ---- 节点选中 ----
279
+ isSelected(item) {
280
+ const sp = treeState.selectedPath
281
+ if (!sp || sp.length !== item.path.length) return false
282
+ return sp.every((seg, i) => String(seg) === String(item.path[i]))
283
+ },
284
+ selectNode(item) {
285
+ // 再次点击已选中节点则取消选中
286
+ if (this.isSelected(item)) {
287
+ treeState.selectedPath = null
288
+ } else {
289
+ treeState.selectedPath = item.path.slice()
290
+ }
291
+ },
292
+ onBranchClick(item) {
293
+ this.toggle(item.key)
294
+ // 选中该分支节点(供添加操作定位)
295
+ treeState.selectedPath = item.path.slice()
296
+ },
297
+ setOpen(v) {
298
+ treeState.openAll = v
299
+ },
300
+ previewText(val) {
301
+ if (Array.isArray(val)) return ' … ' + val.length + ' 项'
302
+ const keys = Object.keys(val)
303
+ const head = keys.slice(0, 3).join(', ')
304
+ return ' … ' + head + (keys.length > 3 ? ', …' : '') + ' '
305
+ },
306
+ formatPrimitive(v, type) {
307
+ if (v === null) return 'null'
308
+ if (type === 'string') return JSON.stringify(v)
309
+ return String(v)
310
+ },
311
+ formatForEdit(v, type) {
312
+ if (v === null) return 'null'
313
+ if (type === 'string') return v
314
+ return String(v)
315
+ },
316
+ // ---- 编辑功能 ----
317
+ startEditKey(item) {
318
+ this.cancelEdit()
319
+ this.editingKey = item.key
320
+ this.$nextTick(() => {
321
+ const inputs = this.$refs.keyInput
322
+ if (inputs) {
323
+ const input = Array.isArray(inputs) ? inputs[0] : inputs
324
+ input.focus()
325
+ input.select()
326
+ }
327
+ })
328
+ },
329
+ confirmEditKey(item, event) {
330
+ const newKey = event.target.value.trim()
331
+ if (!newKey || newKey === item.keyLabel) {
332
+ this.cancelEdit()
333
+ return
334
+ }
335
+ if (this.rt && this.rt.rootScope) {
336
+ // 父路径 = item.path 去掉最后一段(当前 key)
337
+ const parentPath = item.path.slice(0, -1)
338
+ const oldKey = item.path[item.path.length - 1]
339
+ const ok = renameKey(this.rt.rootScope, parentPath, oldKey, newKey)
340
+ if (!ok) {
341
+ this.cancelEdit()
342
+ return
343
+ }
344
+ this.rt.markDirty()
345
+ }
346
+ this.editingKey = null
347
+ },
348
+ startEditValue(item) {
349
+ this.cancelEdit()
350
+ this.editingValue = item.key
351
+ this.$nextTick(() => {
352
+ const inputs = this.$refs.valInput
353
+ if (inputs) {
354
+ const input = Array.isArray(inputs) ? inputs[0] : inputs
355
+ input.focus()
356
+ input.select()
357
+ }
358
+ })
359
+ },
360
+ confirmEditValue(item, event) {
361
+ const raw = event.target.value
362
+ let newValue = raw
363
+ // 按类型解析
364
+ if (item.type === 'number') {
365
+ const n = Number(raw)
366
+ if (!isNaN(n)) newValue = n
367
+ } else if (item.type === 'boolean') {
368
+ if (raw === 'true') newValue = true
369
+ else if (raw === 'false') newValue = false
370
+ } else if (item.type === 'null') {
371
+ if (raw === 'null') newValue = null
372
+ else newValue = raw // 从 null 改成其他类型
373
+ } else if (item.type === 'string') {
374
+ newValue = raw // 字符串直接用原始值
375
+ } else {
376
+ // 尝试 JSON 解析
377
+ try { newValue = JSON.parse(raw) } catch { /* 保持字符串 */ }
378
+ }
379
+ if (this.rt && this.rt.rootScope) {
380
+ setByPath(this.rt.rootScope, item.path, newValue)
381
+ this.rt.markDirty()
382
+ }
383
+ this.editingValue = null
384
+ },
385
+ // ---- 复合类型整体编辑 ----
386
+ startEditComplex(item) {
387
+ this.cancelEdit()
388
+ this.editingComplex = item.key
389
+ this.jsonEditText = JSON.stringify(item.value, null, 2)
390
+ this.$nextTick(() => {
391
+ const ta = this.$refs.jsonInput
392
+ if (ta) {
393
+ const textarea = Array.isArray(ta) ? ta[0] : ta
394
+ textarea.focus()
395
+ textarea.select()
396
+ }
397
+ })
398
+ },
399
+ confirmEditComplex(item) {
400
+ try {
401
+ const parsed = JSON.parse(this.jsonEditText)
402
+ if (this.rt && this.rt.rootScope) {
403
+ setByPath(this.rt.rootScope, item.path, parsed)
404
+ this.rt.markDirty()
405
+ }
406
+ this.cancelEdit()
407
+ } catch (e) {
408
+ // JSON 解析失败,不关闭编辑器,让用户修正
409
+ alert('JSON 解析失败: ' + e.message)
410
+ }
411
+ },
412
+ cancelEdit() {
413
+ this.editingKey = null
414
+ this.editingValue = null
415
+ this.editingComplex = null
416
+ this.jsonEditText = ''
417
+ },
418
+ deleteNode(item) {
419
+ if (this.rt && this.rt.rootScope) {
420
+ deleteByPath(this.rt.rootScope, item.path)
421
+ this.rt.markDirty()
422
+ }
423
+ },
424
+ addChild() {
425
+ if (!this.rt || !this.rt.rootScope) return
426
+ // 优先添加到选中的节点(若为 object/array);否则添加到最外层根节点
427
+ let data = this.rt.rootScope
428
+ const sp = treeState.selectedPath
429
+ if (sp && sp.length) {
430
+ const sel = getByPath(this.rt.rootScope, sp)
431
+ if (sel !== null && typeof sel === 'object') data = sel
432
+ }
433
+ if (Array.isArray(data)) {
434
+ data.push('')
435
+ this.rt.markDirty()
436
+ } else if (data && typeof data === 'object') {
437
+ // 生成唯一 key
438
+ let newKey = 'newKey'
439
+ let n = 1
440
+ while (newKey in data) { newKey = 'newKey' + (n > 1 ? n : ''); n++ }
441
+ data[newKey] = ''
442
+ this.rt.markDirty()
443
+ }
444
+ },
445
+ // ---- 右键菜单 ----
446
+ showContextMenu(item, event) {
447
+ this.ctxMenu.visible = true
448
+ this.ctxMenu.x = event.clientX
449
+ this.ctxMenu.y = event.clientY
450
+ this.ctxMenu.item = item
451
+ },
452
+ hideContextMenu() {
453
+ this.ctxMenu.visible = false
454
+ this.ctxMenu.item = null
455
+ },
456
+ ctxCopyPath() {
457
+ const item = this.ctxMenu.item
458
+ this.hideContextMenu()
459
+ if (!item) return
460
+ const keyPath = this.formatKeyPath(item.path)
461
+ copyText(keyPath).then((ok) => {
462
+ if (ok) showAppToast(`已复制路径:${keyPath}`, 'ok')
463
+ else showAppToast('复制失败,请手动复制', 'err')
464
+ })
465
+ },
466
+ /** 把路径数组格式化为「字段.数字[索引]」形式:数字段用方括号包裹,如 account_details[0].account_type_alias[0] */
467
+ formatKeyPath(path) {
468
+ let result = ''
469
+ for (const seg of path) {
470
+ if (typeof seg === 'number' || /^\d+$/.test(String(seg))) {
471
+ result += `[${seg}]`
472
+ } else {
473
+ result += result ? `.${seg}` : `${seg}`
474
+ }
475
+ }
476
+ return result
477
+ },
478
+ ctxDelete() {
479
+ const item = this.ctxMenu.item
480
+ this.hideContextMenu()
481
+ if (!item) return
482
+ // 检查是否被引用
483
+ const referenced = this.isNodeReferenced(item.path)
484
+ if (referenced) {
485
+ const key = item.path.join('.')
486
+ if (!confirm(`节点 “${key}” 在 schema 中被引用,删除后可能导致渲染异常。\n\n确定要删除吗?`)) {
487
+ return
488
+ }
489
+ }
490
+ this.deleteNode(item)
491
+ },
492
+ /** 检测节点是否被 schema 引用(属性绑定 $key 或事件代码 state.key) */
493
+ isNodeReferenced(path) {
494
+ if (!this.schema) return false
495
+ const schemaStr = JSON.stringify(this.schema)
496
+ // 检查路径中每一段是否被引用
497
+ // 例如 ['account', 'name'] 需要检查 'account' 和 'account.name'
498
+ for (let i = 0; i < path.length; i++) {
499
+ const partial = path.slice(0, i + 1).join('.')
500
+ const key = path[i]
501
+ // 属性绑定模式:$key 或 $path
502
+ if (schemaStr.includes('$' + key) || schemaStr.includes('$' + partial)) return true
503
+ // 事件代码模式:state.key 或 this.state.key
504
+ if (schemaStr.includes('state.' + key) || schemaStr.includes('state.' + partial)) return true
505
+ // 表达式模式:(key == ... 或 key.xxx
506
+ const exprRegex = new RegExp('\\b' + this.escapeRegex(key) + '\\b')
507
+ if (exprRegex.test(schemaStr)) return true
508
+ }
509
+ return false
510
+ },
511
+ escapeRegex(str) {
512
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
513
+ },
514
+ },
515
+ }
516
+ </script>
@@ -0,0 +1,68 @@
1
+ <!--
2
+ 官方 module 2109 / 545。
3
+ 作用域插槽 default 透传 { item, index };第 0 项不加间距。
4
+ -->
5
+ <template>
6
+ <div class="ce_listlayout" :style="rootStyle" @click="$emit('click', $event)">
7
+ <div class="ce_listlayout_con" :class="conClasses" :style="conStyleObj">
8
+ <div
9
+ v-for="(item, index) in value || []"
10
+ :key="index"
11
+ class="ce_listlayout_item"
12
+ :style="index === 0 ? {} : itemStyleObj"
13
+ >
14
+ <slot :item="item" :index="index" />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script>
21
+ import StyleParser from '../runtime/StyleParser.js'
22
+ import base from './base.js'
23
+
24
+ export default {
25
+ name: 'CeListLayout',
26
+ emits: ['click'],
27
+ mixins: [base],
28
+ props: {
29
+ styleProps: { type: Object, default: () => ({}) },
30
+ value: { type: Array, default: () => [] },
31
+ direction: { type: String, default: 'vertical' },
32
+ rowSpacing: { type: [String, Number], default: '' },
33
+ edgeInsets: { type: String, default: '' },
34
+ },
35
+ computed: {
36
+ itemStyleObj() {
37
+ const horizontal = this.direction === 'horizontal'
38
+ return {
39
+ marginLeft: horizontal ? this.adapter.doSize(this.rowSpacing) : 0,
40
+ marginTop: horizontal ? 0 : this.adapter.doSize(this.rowSpacing),
41
+ }
42
+ },
43
+ conClasses() {
44
+ return { ce_listlayout_horizontal: this.direction === 'horizontal' }
45
+ },
46
+ conStyleObj() {
47
+ const out = {}
48
+ if (this.edgeInsets) {
49
+ // edgeInsets 形如 "{top,left,bottom,right}"
50
+ const p = this.parsePadding(this.edgeInsets)
51
+ out.paddingLeft = this.adapter.doSize(p[1])
52
+ out.paddingTop = this.adapter.doSize(p[0])
53
+ out.paddingRight = this.adapter.doSize(p[3])
54
+ out.paddingBottom = this.adapter.doSize(p[2])
55
+ }
56
+ return out
57
+ },
58
+ rootStyle() {
59
+ return new StyleParser(this, this.styleProps).parse()
60
+ },
61
+ },
62
+ methods: {
63
+ parsePadding(v) {
64
+ return String(v).replace(/[{}]/g, '').split(',')
65
+ },
66
+ },
67
+ }
68
+ </script>
@@ -0,0 +1,51 @@
1
+ <!--
2
+ 事件日志单值渲染器(事件面板日志区块使用)。
3
+ - 基础类型(string/number/boolean/null/undefined/function):内联 span,按类型着色
4
+ (复用 json-tree.css 的 .jt__val--* 配色,与接口调用树一致,实现语法高亮)。
5
+ - 复合类型(对象 / 数组):渲染为可折叠 JSON 树(ApiPayloadTree),默认全部折叠,
6
+ 符合「JSON 默认收起」的要求;maxDepth 防护循环引用 / 超深结构。
7
+ -->
8
+ <template>
9
+ <span v-if="!complex" class="log__val" :class="'jt__val--' + typeClass">{{ display }}</span>
10
+ <div v-else class="log__json"><ApiPayloadTree :value="value" :max-depth="14" /></div>
11
+ </template>
12
+
13
+ <script>
14
+ import ApiPayloadTree from './ApiPayloadTree.vue'
15
+
16
+ export default {
17
+ name: 'LogValue',
18
+ components: { ApiPayloadTree },
19
+ props: {
20
+ // 当前日志实参(任意类型)
21
+ value: { default: null },
22
+ },
23
+ computed: {
24
+ complex() {
25
+ return this.value !== null && typeof this.value === 'object'
26
+ },
27
+ // 用于着色的类型类:与 jt__val--* 对齐(string/number/boolean/null/array/object)
28
+ typeClass() {
29
+ if (this.value === null || this.value === undefined) return 'null'
30
+ if (Array.isArray(this.value)) return 'array'
31
+ const t = typeof this.value
32
+ if (t === 'string' || t === 'number' || t === 'boolean') return t
33
+ // function / bigint / symbol 等:用柔和色(null 类)而非 object 紫
34
+ return 'null'
35
+ },
36
+ display() {
37
+ const v = this.value
38
+ if (v === undefined) return 'undefined'
39
+ if (v === null) return 'null'
40
+ if (typeof v === 'string') return v
41
+ if (typeof v === 'function') return '[Function ' + (v.name || 'anonymous') + ']'
42
+ if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') return String(v)
43
+ try {
44
+ return JSON.stringify(v)
45
+ } catch {
46
+ return String(v)
47
+ }
48
+ },
49
+ },
50
+ }
51
+ </script>