cardbuddy 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/dist/cli.js +76 -0
- package/dist/config.js +63 -0
- package/dist/core.js +2 -0
- package/dist/preview-editor/assets/apple-touch-icon-BQcES9QK.png +0 -0
- package/dist/preview-editor/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/preview-editor/assets/favicon-16-Cg6Nrosk.png +0 -0
- package/dist/preview-editor/assets/favicon-32-B_1744sp.png +0 -0
- package/dist/preview-editor/assets/index-BYB0hngG.js +72 -0
- package/dist/preview-editor/assets/index-CpBZEQDy.css +1 -0
- package/dist/preview-editor/assets/site-icon-CFMif8z4.png +0 -0
- package/dist/preview-editor/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/preview-editor/index.html +17 -0
- package/dist/server-core/_vendor/finmall-env.mjs +96 -0
- package/dist/server-core/assetProxy.js +154 -0
- package/dist/server-core/bbox.js +153 -0
- package/dist/server-core/browser.js +108 -0
- package/dist/server-core/buildDataFromSchema.js +50 -0
- package/dist/server-core/cardFiles.js +188 -0
- package/dist/server-core/cardHistory.js +156 -0
- package/dist/server-core/cardImage.js +85 -0
- package/dist/server-core/cardImportMeta.js +139 -0
- package/dist/server-core/cardListCache.js +68 -0
- package/dist/server-core/cardOpenFolder.js +135 -0
- package/dist/server-core/cardSave.js +127 -0
- package/dist/server-core/cardSidecar.js +72 -0
- package/dist/server-core/cardSync.js +259 -0
- package/dist/server-core/cardValidate.js +134 -0
- package/dist/server-core/index.js +82 -0
- package/dist/server-core/logger.js +241 -0
- package/dist/server-core/nodeModels.js +52 -0
- package/dist/server-core/renderStatus.js +207 -0
- package/dist/server-core/screenshot.js +123 -0
- package/dist/server-core/sliceStatic.js +44 -0
- package/dist/standalone.js +447 -0
- package/dist/vendored/App.vue +2330 -0
- package/dist/vendored/api.js +142 -0
- package/dist/vendored/assets/apple-touch-icon.png +0 -0
- package/dist/vendored/assets/default-image.png +0 -0
- package/dist/vendored/assets/favicon-16.png +0 -0
- package/dist/vendored/assets/favicon-32.png +0 -0
- package/dist/vendored/assets/logo.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svg-symbols.svg +118 -0
- package/dist/vendored/assets/svgIcons/icons.svg +54 -0
- package/dist/vendored/assets/svgIcons/index.js +39 -0
- package/dist/vendored/components/AbsoluteLayout.vue +42 -0
- package/dist/vendored/components/AlertCardInstance.vue +58 -0
- package/dist/vendored/components/AlertCardLayer.vue +59 -0
- package/dist/vendored/components/ApiPayloadTree.vue +168 -0
- package/dist/vendored/components/AppToast.vue +37 -0
- package/dist/vendored/components/Button.vue +37 -0
- package/dist/vendored/components/Canvas.vue +65 -0
- package/dist/vendored/components/CardGallery.vue +243 -0
- package/dist/vendored/components/CardPreview.vue +72 -0
- package/dist/vendored/components/CardSelect.vue +200 -0
- package/dist/vendored/components/CheckGroup.vue +131 -0
- package/dist/vendored/components/Column.vue +25 -0
- package/dist/vendored/components/ComponentPalette.vue +294 -0
- package/dist/vendored/components/ConfirmDialog.vue +39 -0
- package/dist/vendored/components/CustomPlaceholder.vue +33 -0
- package/dist/vendored/components/DatePickerOverlay.vue +345 -0
- package/dist/vendored/components/DebugPanel.vue +806 -0
- package/dist/vendored/components/DeviceOverlay.vue +70 -0
- package/dist/vendored/components/Divider.vue +22 -0
- package/dist/vendored/components/Dropdown.vue +191 -0
- package/dist/vendored/components/EventEditor.vue +247 -0
- package/dist/vendored/components/GridLayout.vue +67 -0
- package/dist/vendored/components/HoverGuides.vue +122 -0
- package/dist/vendored/components/Icon.vue +34 -0
- package/dist/vendored/components/Image.vue +34 -0
- package/dist/vendored/components/Input.vue +90 -0
- package/dist/vendored/components/JsonTree.vue +516 -0
- package/dist/vendored/components/ListLayout.vue +68 -0
- package/dist/vendored/components/LogValue.vue +51 -0
- package/dist/vendored/components/Map.vue +110 -0
- package/dist/vendored/components/MultiColumn.vue +49 -0
- package/dist/vendored/components/MultiText.vue +58 -0
- package/dist/vendored/components/NodeInspector.vue +295 -0
- package/dist/vendored/components/PickerOverlay.vue +131 -0
- package/dist/vendored/components/ProgressSlider.vue +167 -0
- package/dist/vendored/components/PropControl.vue +241 -0
- package/dist/vendored/components/PropTree.vue +128 -0
- package/dist/vendored/components/RadioButton.vue +74 -0
- package/dist/vendored/components/RemotePreview.vue +171 -0
- package/dist/vendored/components/RenderTree.vue +437 -0
- package/dist/vendored/components/Root.vue +30 -0
- package/dist/vendored/components/Row.vue +25 -0
- package/dist/vendored/components/SelectionOverlay.vue +105 -0
- package/dist/vendored/components/StructureTree.vue +949 -0
- package/dist/vendored/components/SvgCode.vue +19 -0
- package/dist/vendored/components/SvgSymbols.vue +30 -0
- package/dist/vendored/components/Switch.vue +38 -0
- package/dist/vendored/components/Text.vue +32 -0
- package/dist/vendored/components/Video.vue +70 -0
- package/dist/vendored/components/ViewContainer.vue +52 -0
- package/dist/vendored/components/base.js +12 -0
- package/dist/vendored/components/icons.js +47 -0
- package/dist/vendored/components/index.js +58 -0
- package/dist/vendored/components/setters/SetBgPosition.vue +64 -0
- package/dist/vendored/components/setters/SetBgSize.vue +90 -0
- package/dist/vendored/components/setters/SetCode.vue +90 -0
- package/dist/vendored/components/setters/SetColor.vue +244 -0
- package/dist/vendored/components/setters/SetCustomSelect.vue +58 -0
- package/dist/vendored/components/setters/SetExpr.vue +107 -0
- package/dist/vendored/components/setters/SetGradient.vue +125 -0
- package/dist/vendored/components/setters/SetGrid9.vue +68 -0
- package/dist/vendored/components/setters/SetIconSelect.vue +67 -0
- package/dist/vendored/components/setters/SetIfForm.vue +61 -0
- package/dist/vendored/components/setters/SetImage.vue +43 -0
- package/dist/vendored/components/setters/SetList.vue +139 -0
- package/dist/vendored/components/setters/SetNumber.vue +118 -0
- package/dist/vendored/components/setters/SetNumberStyle.vue +113 -0
- package/dist/vendored/components/setters/SetPadding.vue +77 -0
- package/dist/vendored/components/setters/SetSegment.vue +160 -0
- package/dist/vendored/components/setters/SetSelect.vue +51 -0
- package/dist/vendored/components/setters/SetSwitch.vue +49 -0
- package/dist/vendored/components/setters/SetText.vue +52 -0
- package/dist/vendored/components/setters/props.js +23 -0
- package/dist/vendored/fidelity/baseline-messageOverview.json +87 -0
- package/dist/vendored/fidelity/calibration.json +29 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +114 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +272 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.html +185 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.json +336 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +262 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +502 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +201 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +417 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +125 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +338 -0
- package/dist/vendored/index.html +16 -0
- package/dist/vendored/main.js +36 -0
- package/dist/vendored/meta/action-meta.json +141 -0
- package/dist/vendored/meta/component-meta.json +2714 -0
- package/dist/vendored/meta/directive-meta.json +40 -0
- package/dist/vendored/meta/style-meta.json +1086 -0
- package/dist/vendored/runtime/StyleParser.js +179 -0
- package/dist/vendored/runtime/adapter.js +166 -0
- package/dist/vendored/runtime/alert-card.js +125 -0
- package/dist/vendored/runtime/app-toast.js +33 -0
- package/dist/vendored/runtime/assets.js +16 -0
- package/dist/vendored/runtime/canvas-context.js +141 -0
- package/dist/vendored/runtime/card-runtime.js +289 -0
- package/dist/vendored/runtime/clipboard-node.js +83 -0
- package/dist/vendored/runtime/clipboard.js +29 -0
- package/dist/vendored/runtime/evaluate.js +152 -0
- package/dist/vendored/runtime/event-model.js +300 -0
- package/dist/vendored/runtime/hoverGuide.js +31 -0
- package/dist/vendored/runtime/jsapi-mock.js +256 -0
- package/dist/vendored/runtime/node-model.js +217 -0
- package/dist/vendored/runtime/property.js +364 -0
- package/dist/vendored/runtime/selectionOverlay.js +53 -0
- package/dist/vendored/runtime/toPreview.js +648 -0
- package/dist/vendored/standalone-main.js +36 -0
- package/dist/vendored/standalone.html +12 -0
- package/dist/vendored/styles/app.css +2565 -0
- package/dist/vendored/styles/card-gallery.css +234 -0
- package/dist/vendored/styles/card-select.css +281 -0
- package/dist/vendored/styles/components.css +383 -0
- package/dist/vendored/styles/dropdown.css +337 -0
- package/dist/vendored/styles/json-tree.css +308 -0
- package/dist/vendored/styles/node-inspector.css +1003 -0
- package/dist/vendored/styles/setters.css +962 -0
- package/dist/vendored/styles/standalone.css +27 -0
- package/dist/vendored/styles/structure-tree.css +320 -0
- package/package.json +42 -0
|
@@ -0,0 +1,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>
|