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,70 @@
1
+ <!--
2
+ 设备级模拟 UI 图层:alertDialog 确认对话框 + showToast 弱提示。
3
+ 相对固定视口 phone-viewport 定位(absolute inset:0),不随卡片内容滚动。
4
+ 层级高于 alertCard 图层(z-index 1100 > 1000),模拟原生对话框盖住弹窗卡片。
5
+ -->
6
+ <template>
7
+ <div class="device-ui">
8
+ <!-- alertDialog:原生风格居中确认对话框 -->
9
+ <transition name="dlg-fade">
10
+ <div v-if="dialog" class="dlg-mask" @click.self="onMaskClick">
11
+ <div class="dlg">
12
+ <div v-if="dialog.title" class="dlg__title">{{ dialog.title }}</div>
13
+ <div v-if="dialog.message" class="dlg__msg">{{ dialog.message }}</div>
14
+ <div class="dlg__btns">
15
+ <button
16
+ v-if="dialog.cancelButton"
17
+ type="button"
18
+ class="dlg__btn dlg__btn--cancel"
19
+ :class="{ 'dlg__btn--gray': dialog.cancelGrayStyle }"
20
+ @click="onCancel"
21
+ >{{ dialog.cancelButton }}</button>
22
+ <button type="button" class="dlg__btn dlg__btn--ok" @click="onOk">{{ dialog.okButton }}</button>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </transition>
27
+ <!-- showToast:居中弱提示(对齐 Vant Toast 视觉语言) -->
28
+ <transition name="toast-fade">
29
+ <div v-if="toast" class="dtoast" :class="{ 'dtoast--icon': toast.icon }">
30
+ <span v-if="toast.icon" class="dtoast__icon" :class="'dtoast__icon--' + toast.icon">
31
+ <svg class="dtoast__icon-svg" viewBox="0 0 24 24" aria-hidden="true">
32
+ <path v-if="toast.icon === 'ok'" d="M5 13l4 4L19 7" />
33
+ <path v-else d="M6 6l12 12M18 6L6 18" />
34
+ </svg>
35
+ </span>
36
+ <span class="dtoast__msg">{{ toast.message }}</span>
37
+ </div>
38
+ </transition>
39
+ </div>
40
+ </template>
41
+
42
+ <script>
43
+ import { deviceUI, dismissDialog } from '@/runtime/jsapi-mock.js'
44
+
45
+ export default {
46
+ name: 'DeviceOverlay',
47
+ computed: {
48
+ dialog() {
49
+ return deviceUI.dialog
50
+ },
51
+ toast() {
52
+ return deviceUI.toast
53
+ },
54
+ },
55
+ methods: {
56
+ onOk() {
57
+ dismissDialog({ ok: true, dismissByClickBg: false })
58
+ },
59
+ onCancel() {
60
+ dismissDialog({ ok: false, dismissByClickBg: false })
61
+ },
62
+ onMaskClick() {
63
+ // 仅当 clickBgToHide 为 true 时点击背景才关闭(dismissByClickBg 标记此关闭方式)
64
+ if (this.dialog && this.dialog.clickBgToHide) {
65
+ dismissDialog({ ok: false, dismissByClickBg: true })
66
+ }
67
+ },
68
+ },
69
+ }
70
+ </script>
@@ -0,0 +1,22 @@
1
+ <!-- 官方 module 1964 / 496:纯样式,无 props 无事件 -->
2
+ <template>
3
+ <div class="ce_divider" :style="styleObj" />
4
+ </template>
5
+
6
+ <script>
7
+ import StyleParser from '../runtime/StyleParser.js'
8
+ import base from './base.js'
9
+
10
+ export default {
11
+ name: 'CeDivider',
12
+ mixins: [base],
13
+ props: {
14
+ styleProps: { type: Object, default: () => ({}) },
15
+ },
16
+ computed: {
17
+ styleObj() {
18
+ return new StyleParser(this, this.styleProps).parse()
19
+ },
20
+ },
21
+ }
22
+ </script>
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <div ref="root" class="dd" :class="['dd--' + size, { 'dd--open': open, 'dd--block': block, 'dd--disabled': disabled, 'dd--borderless': borderless, 'dd--dropup': dropUp }]" :style="rootStyle">
3
+ <button
4
+ v-if="!hasTriggerSlot"
5
+ type="button"
6
+ class="dd__trigger"
7
+ :class="{ 'dd__trigger--active': open }"
8
+ :disabled="disabled"
9
+ @click="toggle"
10
+ >
11
+ <span class="dd__label" :title="displayText">{{ displayText }}</span>
12
+ <span
13
+ v-if="clearable && hasValue"
14
+ class="dd__clear"
15
+ role="button"
16
+ tabindex="-1"
17
+ title="清空"
18
+ @click.stop="clear"
19
+ >✕</span
20
+ >
21
+ <svg class="dd__chev" viewBox="0 0 16 16" :width="chevSize" :height="chevSize" aria-hidden="true"><use href="#icon-chevron" /></svg>
22
+ </button>
23
+ <div v-else class="dd__trigger dd__trigger--slot" @click="toggle">
24
+ <slot name="trigger" :open="open" :toggle="toggle" />
25
+ </div>
26
+
27
+ <transition name="dd-fade">
28
+ <div v-if="open" ref="pop" class="dd__pop" :class="['dd__pop--' + placement]">
29
+ <slot :close="close" :choose="choose">
30
+ <ul class="dd__list">
31
+ <li
32
+ v-for="opt in options"
33
+ :key="opt.key"
34
+ class="dd__item"
35
+ :class="{ 'dd__item--selected': opt.key === value, 'dd__item--disabled': opt.disabled }"
36
+ @mousemove="onHover(opt)"
37
+ @click="onItemClick(opt)"
38
+ >
39
+ <span class="dd__item-text">{{ opt.value }}</span>
40
+ <svg v-if="opt.key === value" class="dd__check" viewBox="0 0 16 16" :width="checkSize" :height="checkSize" aria-hidden="true"><use href="#icon-check" /></svg>
41
+ </li>
42
+ <li v-if="!options.length" class="dd__empty">{{ placeholder || '无选项' }}</li>
43
+ </ul>
44
+ </slot>
45
+ </div>
46
+ </transition>
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ export default {
52
+ name: 'Dropdown',
53
+ emits: ['input', 'change'],
54
+ props: {
55
+ value: { type: [String, Number, null], default: '' },
56
+ options: { type: Array, default: () => [] },
57
+ placeholder: { type: String, default: '请选择' },
58
+ // 尺寸:xs(表单紧凑 24px)/ sm(28px)/ md(顶栏 32px)/ lg(36px)
59
+ size: { type: String, default: 'sm' },
60
+ // block:撑满父容器宽度(表单内 select 用)
61
+ block: { type: Boolean, default: false },
62
+ disabled: { type: Boolean, default: false },
63
+ // 弹层对齐:bottom-start(左对齐)/ bottom-end(右对齐)
64
+ placement: { type: String, default: 'bottom-start' },
65
+ // 外部宽度覆盖(px),优先级高于 size
66
+ width: { type: [String, Number], default: null },
67
+ // 无边框模式:默认 false(有边框);true 时不显示边框与选中高亮效果
68
+ borderless: { type: Boolean, default: false },
69
+ // 触发器显示文案自定义(默认取选中项 value 或 placeholder)
70
+ label: { type: String, default: null },
71
+ // 可清空:选中值后触发器右侧显示 ✕ 清除按钮(默认 false,避免影响其它下拉)
72
+ clearable: { type: Boolean, default: false },
73
+ },
74
+ data() {
75
+ return { open: false, dropUp: false }
76
+ },
77
+ computed: {
78
+ hasTriggerSlot() {
79
+ return !!this.$slots.trigger
80
+ },
81
+ current() {
82
+ return this.options.find((o) => o.key === this.value) || null
83
+ },
84
+ hasValue() {
85
+ return this.value !== '' && this.value !== null && this.value !== undefined
86
+ },
87
+ displayText() {
88
+ if (this.label != null) return this.label
89
+ if (this.current) return this.current.value || String(this.current.key)
90
+ return this.placeholder || '请选择'
91
+ },
92
+ chevSize() {
93
+ return this.size === 'xs' ? 11 : this.size === 'md' || this.size === 'lg' ? 13 : 12
94
+ },
95
+ checkSize() {
96
+ return this.size === 'xs' ? 11 : 12
97
+ },
98
+ rootStyle() {
99
+ if (this.width != null) return { width: typeof this.width === 'number' ? this.width + 'px' : this.width }
100
+ return null
101
+ },
102
+ },
103
+ watch: {
104
+ open(v) {
105
+ if (v) {
106
+ document.addEventListener('click', this.onDocClick, true)
107
+ } else {
108
+ document.removeEventListener('click', this.onDocClick, true)
109
+ }
110
+ },
111
+ },
112
+ beforeUnmount() {
113
+ document.removeEventListener('click', this.onDocClick, true)
114
+ },
115
+ methods: {
116
+ toggle() {
117
+ if (this.disabled) return
118
+ if (!this.open) this.openMenu()
119
+ else this.open = false
120
+ },
121
+ openMenu() {
122
+ // 打开前先估算弹层高度,判断下方空间是否足够;不足且上方够则翻转到上方,
123
+ // 避免触发器位于滚动/裁剪容器底部时弹层被裁掉、需手动滚动才可见。
124
+ const root = this.$refs.root
125
+ if (root) {
126
+ const estH = Math.min(this.options.length * 31 + 12, 290)
127
+ this.dropUp = this.shouldDropUp(root, estH)
128
+ }
129
+ this.open = true
130
+ // 渲染后按弹层真实高度再校正一次(估算与实际有出入时兜底,避免闪烁)
131
+ this.$nextTick(() => {
132
+ const pop = this.$refs.pop
133
+ const root = this.$refs.root
134
+ if (!pop || !root) return
135
+ const want = this.shouldDropUp(root, pop.offsetHeight)
136
+ if (want !== this.dropUp) this.dropUp = want
137
+ })
138
+ },
139
+ /** 触发器位于容器底部、下方空间不足以容纳弹层且上方空间足够时返回 true(向上弹出) */
140
+ shouldDropUp(root, popH) {
141
+ const tr = root.getBoundingClientRect()
142
+ const clip = this.getClipRect(root)
143
+ const spaceBelow = clip.bottom - tr.bottom
144
+ const spaceAbove = tr.top - clip.top
145
+ return spaceBelow < popH && spaceAbove >= popH
146
+ },
147
+ /** 向上回溯找到最近的会裁剪/滚动内容的祖先,作为可见区域边界;
148
+ 找不到则回退到视口。弹层必须落在该矩形内才不会被裁掉。 */
149
+ getClipRect(el) {
150
+ let node = el.parentElement
151
+ while (node && node !== document.body) {
152
+ const ov = getComputedStyle(node).overflowY
153
+ if (ov === 'auto' || ov === 'scroll' || ov === 'hidden') {
154
+ return node.getBoundingClientRect()
155
+ }
156
+ node = node.parentElement
157
+ }
158
+ return document.documentElement.getBoundingClientRect()
159
+ },
160
+ close() {
161
+ this.open = false
162
+ this.dropUp = false
163
+ },
164
+ clear() {
165
+ if (this.disabled) return
166
+ this.close()
167
+ // 即使值未变也照常回传空串,确保父级能把字段重置为空
168
+ this.$emit('input', '')
169
+ this.$emit('change', '')
170
+ },
171
+ onDocClick(e) {
172
+ if (this.$refs.root && !this.$refs.root.contains(e.target)) this.close()
173
+ },
174
+ onHover() {},
175
+ onItemClick(opt) {
176
+ if (opt.disabled) return
177
+ this.choose(opt.key)
178
+ },
179
+ choose(key) {
180
+ this.close()
181
+ if (key !== this.value) {
182
+ this.$emit('input', key)
183
+ this.$emit('change', key)
184
+ } else {
185
+ // 即使值未变也允许父级响应(如菜单按钮只关心点击)
186
+ this.$emit('change', key)
187
+ }
188
+ },
189
+ },
190
+ }
191
+ </script>
@@ -0,0 +1,247 @@
1
+ <!--
2
+ 节点事件编辑抽屉(右侧滑出)。
3
+
4
+ 与属性编辑(NodeInspector)解耦:结构树的「事件」徽标 / 事件计数按钮
5
+ 点击时单独打开本抽屉,只编辑 eventactions,不涉及属性/样式。
6
+
7
+ 上下排布:上方事件开关列表,下方动作 / 脚本编辑区(复用 PropTree)。
8
+ 数据模型与 NodeInspector 事件页签一致(EventEditModel):
9
+ - 列出该节点所有可用事件(组件 events + 全局生命周期),每事件一个开关;
10
+ - 开关开 -> 选中后在下方展示动作类型选择 + 参数表单;
11
+ - 编辑即时(防抖)重建预览(emit apply),「应用」把改动暂存进内存工作副本(currentSchema),落盘由顶栏「保存到本地」负责;不再 emit write-back。
12
+ -->
13
+ <template>
14
+ <div class="ee-overlay" @click.self="onClose">
15
+ <div class="ee-panel">
16
+ <div class="ee-head">
17
+ <span class="ee-title">
18
+ 事件配置 · {{ compName }}
19
+ <em class="ee-cls">{{ schemaNode && schemaNode.className }}</em>
20
+ </span>
21
+ <button class="ee-close" type="button" @click="onClose"></button>
22
+ </div>
23
+
24
+ <div class="ee-body">
25
+ <aside class="ee-side">
26
+ <p v-if="!eventModel || !eventModel.eventOptions.length" class="ee-empty">该节点无可用事件</p>
27
+ <div v-else class="ee-evt-list">
28
+ <div
29
+ v-for="evt in eventModel.eventOptions"
30
+ :key="evt.eventKey"
31
+ class="ee-evt"
32
+ :class="{ 'is-on': isOn(evt) }"
33
+ @click="onSelectEvent(evt)"
34
+ >
35
+ <span class="ee-evt-name" :title="evt.eventDesc || evt.eventName || evt.eventKey">{{ evt.eventName || evt.eventKey }}</span>
36
+ <button
37
+ class="ee-switch"
38
+ type="button"
39
+ role="switch"
40
+ :aria-checked="isOn(evt)"
41
+ :class="{ 'is-on': isOn(evt) }"
42
+ @click.stop="onToggleEvent(evt)"
43
+ ><span class="ee-switch__dot"></span></button>
44
+ </div>
45
+ </div>
46
+ </aside>
47
+
48
+ <section class="ee-detail">
49
+ <template v-if="activeRow">
50
+ <div class="ee-detail-head">
51
+ <div class="ee-field-row">
52
+ <label class="ee-field-label">动作类型</label>
53
+ <Dropdown
54
+ :value="activeRow.actionType"
55
+ :options="actionOptions(activeRow)"
56
+ size="sm"
57
+ block
58
+ @change="onActionType(activeRow, $event)"
59
+ />
60
+ </div>
61
+ <p v-if="actionDesc(activeRow.actionType)" class="ee-evt-desc">{{ actionDesc(activeRow.actionType) }}</p>
62
+ </div>
63
+
64
+ <div class="ee-detail-body">
65
+ <prop-tree
66
+ v-if="activeRow.properties.length"
67
+ :key="activeRow.formKey"
68
+ :properties="activeRow.properties"
69
+ mode="event"
70
+ :plain="true"
71
+ @leaf-change="onEventLeaf"
72
+ />
73
+ <p v-else class="ee-empty">该动作无可配置参数</p>
74
+ <p v-if="activeRow.extraProperties.length" class="ee-evt-orphan">
75
+ 另有 {{ activeRow.extraProperties.length }} 项 meta 未定义的参数,已原样保留
76
+ </p>
77
+ </div>
78
+ </template>
79
+
80
+ <p v-else class="ee-empty ee-detail-empty">请在左侧开启并选择一个事件</p>
81
+ </section>
82
+ </div>
83
+
84
+ <div class="ee-foot">
85
+ <span class="ee-tip">编辑即时预览(不记历史);「应用」写入历史并关闭,落盘由顶栏「保存到本地」负责</span>
86
+ <div class="ee-foot-btns">
87
+ <button class="ee-btn ee-btn--ghost" type="button" @click="onCancel">取消</button>
88
+ <button class="ee-btn" type="button" @click="onApplyWrite">应用</button>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </template>
94
+
95
+ <script>
96
+ import { reactive } from 'vue'
97
+ import { EventEditModel, ACTION_MAP } from '@/runtime/event-model.js'
98
+
99
+ export default {
100
+ name: 'EventEditor',
101
+ props: {
102
+ comp: { type: Object, required: true },
103
+ compNames: { type: Object, default: () => ({}) },
104
+ rt: { type: Object, required: true },
105
+ },
106
+ emits: ['apply', 'commit', 'close'],
107
+ data() {
108
+ return {
109
+ eventModel: null,
110
+ activeKey: '',
111
+ _t: null,
112
+ _snapshot: null,
113
+ _dirty: false,
114
+ _written: false,
115
+ }
116
+ },
117
+ computed: {
118
+ schemaNode() {
119
+ return (this.comp && this.comp._schemaNode) || null
120
+ },
121
+ compName() {
122
+ const cn = this.schemaNode && this.schemaNode.className
123
+ if (!cn) return '节点'
124
+ return (this.compNames && this.compNames[cn]) || cn
125
+ },
126
+ activeRow() {
127
+ if (!this.eventModel || !this.activeKey) return null
128
+ return this.eventModel.rowForEvent(this.activeKey) || null
129
+ },
130
+ },
131
+ watch: {
132
+ comp: {
133
+ immediate: true,
134
+ handler() {
135
+ this.buildModel()
136
+ },
137
+ },
138
+ },
139
+ methods: {
140
+ buildModel() {
141
+ const sn = this.schemaNode
142
+ this.eventModel = sn ? reactive(new EventEditModel(sn)) : null
143
+ this.activeKey = ''
144
+ this._snapshot = sn ? this.snapshotEventActions(sn) : null
145
+ this._dirty = false
146
+ this._written = false
147
+ this.autoSelect()
148
+ },
149
+ snapshotEventActions(node) {
150
+ const hadKey = Object.prototype.hasOwnProperty.call(node, 'eventactions')
151
+ return {
152
+ hadKey,
153
+ eventactions: hadKey ? JSON.parse(JSON.stringify(node.eventactions || [])) : [],
154
+ }
155
+ },
156
+ restoreSnapshot() {
157
+ const sn = this.schemaNode
158
+ if (!sn || !this._snapshot) return
159
+ if (this._snapshot.hadKey) {
160
+ sn.eventactions = JSON.parse(JSON.stringify(this._snapshot.eventactions || []))
161
+ } else {
162
+ delete sn.eventactions
163
+ }
164
+ },
165
+ autoSelect() {
166
+ // 默认展开「第一个已开启事件」的设置区(驱动下方输入框区域),但不整行高亮;
167
+ // 没有已开启事件时保持空态。注意:仅选已开启项,不默认选中未开启的首项。
168
+ if (!this.eventModel) { this.activeKey = ''; return }
169
+ const firstOn = this.eventModel.eventOptions.find((e) => this.eventModel.rowForEvent(e.eventKey))
170
+ this.activeKey = firstOn ? firstOn.eventKey : ''
171
+ },
172
+ isOn(evt) {
173
+ return !!(this.eventModel && this.eventModel.rowForEvent(evt.eventKey))
174
+ },
175
+ onSelectEvent(evt) {
176
+ this.activeKey = evt.eventKey
177
+ },
178
+ onToggleEvent(evt) {
179
+ const wasOn = this.isOn(evt)
180
+ this.eventModel.toggleEvent(evt)
181
+ this._dirty = true
182
+ // 开启后展开该事件的下方设置区(驱动右侧详情区),但不整行高亮;
183
+ // 关闭当前正编辑的事件时收起详情区
184
+ if (!wasOn) {
185
+ this.activeKey = evt.eventKey
186
+ } else if (this.activeKey === evt.eventKey) {
187
+ this.activeKey = ''
188
+ }
189
+ this.scheduleApply()
190
+ },
191
+ actionOptions(row) {
192
+ return (this.eventModel.actionOptionsForEvent(row.eventKey, row.actionType) || []).map((a) => ({
193
+ key: a.actionType,
194
+ value: a.actionName + (a._unknown ? ' · meta 未定义' : ''),
195
+ }))
196
+ },
197
+ actionDesc(t) {
198
+ return (ACTION_MAP[t] && ACTION_MAP[t].actionDesc) || ''
199
+ },
200
+ onActionType(row, type) {
201
+ this.eventModel.setActionType(row, type)
202
+ this._dirty = true
203
+ this.scheduleApply()
204
+ },
205
+ onEventLeaf() {
206
+ this.eventModel.refreshVisible()
207
+ this._dirty = true
208
+ this.scheduleApply()
209
+ },
210
+ scheduleApply() {
211
+ if (this._t) clearTimeout(this._t)
212
+ this._t = setTimeout(() => {
213
+ this.applyAll()
214
+ this.$emit('apply')
215
+ }, 250)
216
+ },
217
+ applyAll() {
218
+ if (this.eventModel) this.eventModel.applyToSchema()
219
+ },
220
+ /** 将改动暂存到内存工作副本(currentSchema,与预览同引用),并重建预览;
221
+ * 不写盘——落盘由顶栏「保存到本地」统一负责。_written=true 表示已提交到内存,
222
+ * 「取消」不再还原;随后关闭抽屉。 */
223
+ onApplyWrite() {
224
+ if (this._t) {
225
+ clearTimeout(this._t)
226
+ this._t = null
227
+ }
228
+ this.applyAll()
229
+ this._written = true
230
+ this.$emit('apply')
231
+ this.$emit('commit')
232
+ this.$emit('close')
233
+ },
234
+ onCancel() {
235
+ this.onClose()
236
+ },
237
+ onClose() {
238
+ if (this._t) { clearTimeout(this._t); this._t = null }
239
+ if (this._dirty && !this._written) {
240
+ this.restoreSnapshot()
241
+ this.$emit('apply')
242
+ }
243
+ this.$emit('close')
244
+ },
245
+ },
246
+ }
247
+ </script>
@@ -0,0 +1,67 @@
1
+ <!-- 官方 module 2113 / 547:按 column 等分宽度,列间距用 marginRight 撑 -->
2
+ <template>
3
+ <div class="ce_gridlayout" :style="styleObj" @click="$emit('click', $event)">
4
+ <div
5
+ v-for="(item, index) in value || []"
6
+ :key="index"
7
+ class="ce_gridlayout_item"
8
+ :style="getItemStyle(index)"
9
+ >
10
+ <slot :item="item" :index="index" />
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import StyleParser from '../runtime/StyleParser.js'
17
+ import base from './base.js'
18
+
19
+ export default {
20
+ name: 'CeGridLayout',
21
+ emits: ['click'],
22
+ mixins: [base],
23
+ props: {
24
+ styleProps: { type: Object, default: () => ({}) },
25
+ value: { type: Array, default: () => [] },
26
+ column: { type: [Number, String], default: 2 },
27
+ rowSpacing: { type: [String, Number], default: '' },
28
+ itemSpacing: { type: [String, Number], default: '' },
29
+ edgeInsets: { type: String, default: '' },
30
+ },
31
+ computed: {
32
+ styleObj() {
33
+ const out = new StyleParser(this, this.styleProps).parse()
34
+ if (this.rowSpacing) out.rowGap = this.adapter.doSize(this.rowSpacing)
35
+ if (this.edgeInsets) {
36
+ const p = this.parsePadding(this.edgeInsets)
37
+ out.paddingLeft = this.adapter.doSize(p[1])
38
+ out.paddingTop = this.adapter.doSize(p[0])
39
+ out.paddingRight = this.adapter.doSize(p[3])
40
+ out.paddingBottom = this.adapter.doSize(p[2])
41
+ }
42
+ return out
43
+ },
44
+ },
45
+ methods: {
46
+ getItemStyle(index) {
47
+ const col = Number(this.column)
48
+ let width = 100 / col + '%'
49
+ let deduct = 0
50
+ let marginRight = 0
51
+ if (col > 1) {
52
+ if (this.itemSpacing) {
53
+ marginRight = this.adapter.doSize(this.itemSpacing)
54
+ deduct = this.adapter.doSize(((col - 1) * Number(this.itemSpacing)) / col)
55
+ }
56
+ width = `calc(${width} - ${deduct})`
57
+ // 每行最后一列不留右间距
58
+ if (index % col === col - 1) marginRight = 0
59
+ }
60
+ return { width, marginRight }
61
+ },
62
+ parsePadding(v) {
63
+ return String(v).replace(/[{}]/g, '').split(',')
64
+ },
65
+ },
66
+ }
67
+ </script>