@shwfed/config 2.6.0 → 2.7.1
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/dist/mcp.mjs +1212 -1045
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/preview/assets/config-B5FFtD0s.js +1 -0
- package/dist/preview/assets/config-BpWP2vu_.js +1 -0
- package/dist/preview/assets/{config-Co--BPbb.js → config-C2OqUTNd.js} +1 -1
- package/dist/preview/assets/{config-VChcvg_y.js → config-D7cjMBeK.js} +1 -1
- package/dist/preview/assets/config-DhORWTZC.js +1 -0
- package/dist/preview/assets/config-DuuYvFG_.js +1 -0
- package/dist/preview/assets/config-dpwN2-UY.js +1 -0
- package/dist/preview/assets/{config-Du7AdGIY.js → config-eP0EblYK.js} +1 -1
- package/dist/preview/assets/{config-LdNKbqCx.js → config-hs_pZ5MM.js} +1 -1
- package/dist/preview/assets/{definition.vue_vue_type_script_setup_true_lang-Ma8i-2ox.js → definition.vue_vue_type_script_setup_true_lang-B8-Uydoy.js} +1 -1
- package/dist/preview/assets/index-BGFrUxgg.js +680 -0
- package/dist/preview/assets/index-BoGW90Pq.css +1 -0
- package/dist/preview/assets/index-Bw16PZhL.js +1 -0
- package/dist/preview/assets/index-CG261V86.js +1 -0
- package/dist/preview/assets/item-aVe51Gy6.js +1 -0
- package/dist/preview/assets/runtime-3rNI0KDH.js +1 -0
- package/dist/preview/assets/runtime-BOn8EwHL.js +1 -0
- package/dist/preview/assets/runtime-Bwr-rb58.js +1 -0
- package/dist/preview/assets/runtime-CXQuhSAX.js +1 -0
- package/dist/preview/assets/runtime-CYGmRjmI.js +1 -0
- package/dist/preview/assets/runtime-Ca79Fs6I.js +1 -0
- package/dist/preview/assets/runtime-CfVt6IWe.js +1 -0
- package/dist/preview/assets/runtime-DYj-R8SZ.js +1 -0
- package/dist/preview/assets/runtime-Dd1GqYeP.js +1 -0
- package/dist/preview/index.html +2 -2
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.d.vue.ts +59 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.vue +452 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.vue.d.ts +59 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.d.vue.ts +7 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.vue +112 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.vue.d.ts +7 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.d.vue.ts +59 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.vue +47 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/runtime.vue.d.ts +59 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/schema.d.ts +92 -0
- package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/schema.js +117 -0
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.multi/schema.js +1 -1
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.combobox.single/schema.js +1 -1
- package/package.json +2 -1
- package/dist/preview/assets/config-BF-HYbrD.js +0 -1
- package/dist/preview/assets/config-CevoqLCe.js +0 -1
- package/dist/preview/assets/config-DMOAQ9zl.js +0 -1
- package/dist/preview/assets/config-RACtdV3v.js +0 -1
- package/dist/preview/assets/config-oBOXGUjR.js +0 -1
- package/dist/preview/assets/index-C-nzF9-u.js +0 -1
- package/dist/preview/assets/index-CHEiFlnE.css +0 -1
- package/dist/preview/assets/index-rxUrWg1Y.js +0 -680
- package/dist/preview/assets/runtime-BNzaUtd-.js +0 -1
- package/dist/preview/assets/runtime-CAj4SjAs.js +0 -1
- package/dist/preview/assets/runtime-CE_42oyr.js +0 -1
- package/dist/preview/assets/runtime-CLMz0SYI.js +0 -1
- package/dist/preview/assets/runtime-COCfVWBL.js +0 -1
- package/dist/preview/assets/runtime-GfHY6wxJ.js +0 -1
- package/dist/preview/assets/runtime-Y00C-S73.js +0 -1
- package/dist/preview/assets/runtime-llw5ZA1Z.js +0 -1
- package/dist/preview/assets/runtime-wAJ77Q3a.js +0 -1
package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/config.vue
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import { Icon } from "@iconify/vue";
|
|
4
|
+
import { Button } from "../../../../ui/button";
|
|
5
|
+
import { Field, FieldLabel } from "../../../../ui/field";
|
|
6
|
+
import { InputGroup, InputGroupInput, InputGroupNumberField } from "../../../../ui/input-group";
|
|
7
|
+
import { ExpressionEditor } from "../../../../ui/expression-editor";
|
|
8
|
+
import { Locale as LocaleField } from "../../../../ui/locale";
|
|
9
|
+
import { Markdown } from "../../../../ui/markdown";
|
|
10
|
+
import { ScrollArea } from "../../../../ui/scroll-area";
|
|
11
|
+
import { Separator } from "../../../../ui/separator";
|
|
12
|
+
import {
|
|
13
|
+
useTreeDnd
|
|
14
|
+
} from "../../../../../composables/useTreeDnd";
|
|
15
|
+
import {
|
|
16
|
+
dataSourceSchema,
|
|
17
|
+
getStructFieldDescription,
|
|
18
|
+
getStructFieldTitle,
|
|
19
|
+
itemSchema,
|
|
20
|
+
schema
|
|
21
|
+
} from "./schema";
|
|
22
|
+
defineOptions({ name: "ShwfedBlockAnimatedNumberConfig" });
|
|
23
|
+
const block = defineModel({ type: null, ...{ required: true } });
|
|
24
|
+
const blockSchema = schema(() => {
|
|
25
|
+
});
|
|
26
|
+
const itemSchemaObj = itemSchema(() => {
|
|
27
|
+
});
|
|
28
|
+
const dsSchema = dataSourceSchema(() => {
|
|
29
|
+
});
|
|
30
|
+
const fieldTitle = (f) => getStructFieldTitle(blockSchema, f) ?? f;
|
|
31
|
+
const fieldDescription = (f) => getStructFieldDescription(blockSchema, f);
|
|
32
|
+
const itemTitle = (f) => getStructFieldTitle(itemSchemaObj, f) ?? f;
|
|
33
|
+
const itemDescription = (f) => getStructFieldDescription(itemSchemaObj, f);
|
|
34
|
+
const dsTitle = (f) => getStructFieldTitle(dsSchema, f) ?? f;
|
|
35
|
+
const dsDescription = (f) => getStructFieldDescription(dsSchema, f);
|
|
36
|
+
const JSON_VAR = {
|
|
37
|
+
json: { type: "optional<dyn>", label: "HTTP \u54CD\u5E94\u4F53\uFF08\u914D\u7F6E\u4E86\u8BF7\u6C42\u65F6\u53EF\u7528\uFF09" }
|
|
38
|
+
};
|
|
39
|
+
const viewMode = ref("item");
|
|
40
|
+
const selectedId = ref(block.value.items[0]?.id ?? "");
|
|
41
|
+
const selectedItem = computed(
|
|
42
|
+
() => block.value.items.find((i) => i.id === selectedId.value) ?? null
|
|
43
|
+
);
|
|
44
|
+
function selectGeneral() {
|
|
45
|
+
viewMode.value = "general";
|
|
46
|
+
}
|
|
47
|
+
function selectItem(id) {
|
|
48
|
+
viewMode.value = "item";
|
|
49
|
+
selectedId.value = id;
|
|
50
|
+
}
|
|
51
|
+
function itemLabel(it, i) {
|
|
52
|
+
const name = it.name?.trim();
|
|
53
|
+
return name && name.length > 0 ? name : `\u6570\u5B57 ${i + 1}`;
|
|
54
|
+
}
|
|
55
|
+
function setDisplayName(v) {
|
|
56
|
+
const s = String(v ?? "");
|
|
57
|
+
block.value = { ...block.value, displayName: s.length > 0 ? s : void 0 };
|
|
58
|
+
}
|
|
59
|
+
function setLocale(v) {
|
|
60
|
+
block.value = { ...block.value, locale: v };
|
|
61
|
+
}
|
|
62
|
+
function writeItems(next) {
|
|
63
|
+
block.value = { ...block.value, items: next };
|
|
64
|
+
}
|
|
65
|
+
function addItem() {
|
|
66
|
+
const it = { id: crypto.randomUUID(), dataSource: { value: "0" } };
|
|
67
|
+
writeItems([...block.value.items, it]);
|
|
68
|
+
selectItem(it.id);
|
|
69
|
+
}
|
|
70
|
+
function removeItem(id) {
|
|
71
|
+
if (block.value.items.length <= 1) return;
|
|
72
|
+
const i = block.value.items.findIndex((x) => x.id === id);
|
|
73
|
+
if (i < 0) return;
|
|
74
|
+
const next = block.value.items.filter((x) => x.id !== id);
|
|
75
|
+
writeItems(next);
|
|
76
|
+
if (selectedId.value === id) {
|
|
77
|
+
selectedId.value = next[Math.max(0, i - 1)]?.id ?? "";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function patchItem(id, patch) {
|
|
81
|
+
writeItems(block.value.items.map((i) => i.id === id ? { ...i, ...patch } : i));
|
|
82
|
+
}
|
|
83
|
+
function setName(id, v) {
|
|
84
|
+
const s = String(v ?? "");
|
|
85
|
+
patchItem(id, { name: s.length > 0 ? s : void 0 });
|
|
86
|
+
}
|
|
87
|
+
function patchDataSource(id, patch) {
|
|
88
|
+
const it = block.value.items.find((x) => x.id === id);
|
|
89
|
+
if (!it) return;
|
|
90
|
+
patchItem(id, { dataSource: { ...it.dataSource, ...patch } });
|
|
91
|
+
}
|
|
92
|
+
function setRequest(id, v) {
|
|
93
|
+
const it = block.value.items.find((x) => x.id === id);
|
|
94
|
+
if (!it) return;
|
|
95
|
+
if (v.trim() === "") {
|
|
96
|
+
patchItem(id, { dataSource: { value: it.dataSource.value } });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
patchDataSource(id, { request: v });
|
|
100
|
+
}
|
|
101
|
+
function setValue(id, v) {
|
|
102
|
+
patchDataSource(id, { value: v });
|
|
103
|
+
}
|
|
104
|
+
function setInterval(id, v) {
|
|
105
|
+
patchItem(id, { pollingInterval: typeof v === "number" && v > 0 ? Math.round(v) : void 0 });
|
|
106
|
+
}
|
|
107
|
+
function setPrefix(id, v) {
|
|
108
|
+
patchItem(id, { prefix: v.length > 0 ? v : void 0 });
|
|
109
|
+
}
|
|
110
|
+
function setSuffix(id, v) {
|
|
111
|
+
patchItem(id, { suffix: v.length > 0 ? v : void 0 });
|
|
112
|
+
}
|
|
113
|
+
function setStyle(id, v) {
|
|
114
|
+
patchItem(id, { style: v.trim().length > 0 ? v : void 0 });
|
|
115
|
+
}
|
|
116
|
+
const ROW_KIND = "animated-number-item-row";
|
|
117
|
+
function rowId(i) {
|
|
118
|
+
return `item-${i}`;
|
|
119
|
+
}
|
|
120
|
+
function moveItem(from, to) {
|
|
121
|
+
if (from === to || from < 0 || to < 0) return;
|
|
122
|
+
if (from >= block.value.items.length || to >= block.value.items.length) return;
|
|
123
|
+
const next = [...block.value.items];
|
|
124
|
+
const [moved] = next.splice(from, 1);
|
|
125
|
+
if (!moved) return;
|
|
126
|
+
next.splice(to, 0, moved);
|
|
127
|
+
writeItems(next);
|
|
128
|
+
}
|
|
129
|
+
function onRowDrop(e) {
|
|
130
|
+
if (e.source.kind !== ROW_KIND || e.target.kind !== ROW_KIND) return;
|
|
131
|
+
const from = block.value.items.findIndex((_, i) => rowId(i) === e.source.id);
|
|
132
|
+
const to = block.value.items.findIndex((_, i) => rowId(i) === e.target.id);
|
|
133
|
+
if (from < 0 || to < 0) return;
|
|
134
|
+
if (e.instruction === "reorder-above") moveItem(from, from < to ? to - 1 : to);
|
|
135
|
+
else if (e.instruction === "reorder-below") moveItem(from, from <= to ? to : to + 1);
|
|
136
|
+
}
|
|
137
|
+
const dnd = useTreeDnd({ onRowDrop });
|
|
138
|
+
const pickDragHandle = (el) => el.querySelector(".drag-handle");
|
|
139
|
+
function rowConfig(i) {
|
|
140
|
+
return {
|
|
141
|
+
kind: ROW_KIND,
|
|
142
|
+
canDrop: (src) => src.kind === ROW_KIND,
|
|
143
|
+
blockInstructions: (src) => {
|
|
144
|
+
const blocked = ["make-child", "reparent"];
|
|
145
|
+
if (src.id === rowId(i)) blocked.push("reorder-above", "reorder-below");
|
|
146
|
+
return blocked;
|
|
147
|
+
},
|
|
148
|
+
dragHandle: pickDragHandle
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<template>
|
|
154
|
+
<div class="flex min-w-0 gap-3 min-h-96">
|
|
155
|
+
<!-- Left rail: general config + reorderable items -->
|
|
156
|
+
<div class="flex w-52 shrink-0 flex-col">
|
|
157
|
+
<div
|
|
158
|
+
class="row pl-2"
|
|
159
|
+
:class="viewMode === 'general' ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'"
|
|
160
|
+
@click="selectGeneral()"
|
|
161
|
+
>
|
|
162
|
+
<Icon
|
|
163
|
+
icon="fluent:settings-20-regular"
|
|
164
|
+
class="size-4 shrink-0"
|
|
165
|
+
/>
|
|
166
|
+
<span class="flex-1 truncate">通用配置</span>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<Separator class="my-2" />
|
|
170
|
+
|
|
171
|
+
<ScrollArea class="flex-1">
|
|
172
|
+
<div class="flex flex-col gap-0.5">
|
|
173
|
+
<div
|
|
174
|
+
v-for="(it, i) in block.items"
|
|
175
|
+
:key="it.id"
|
|
176
|
+
:ref="dnd.rowRef(rowId(i), rowConfig(i))"
|
|
177
|
+
class="row group"
|
|
178
|
+
:class="viewMode === 'item' && selectedId === it.id ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'"
|
|
179
|
+
:data-instruction="dnd.instructionFor(rowId(i)) ?? void 0"
|
|
180
|
+
@click="selectItem(it.id)"
|
|
181
|
+
>
|
|
182
|
+
<Icon
|
|
183
|
+
icon="fluent:re-order-dots-vertical-20-regular"
|
|
184
|
+
class="drag-handle size-4 shrink-0 cursor-grab text-zinc-400"
|
|
185
|
+
/>
|
|
186
|
+
<span class="flex-1 truncate">{{ itemLabel(it, i) }}</span>
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
class="ml-auto inline-flex size-5 shrink-0 items-center justify-center rounded text-zinc-300 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-red-50 hover:text-red-500 disabled:opacity-0"
|
|
190
|
+
:disabled="block.items.length <= 1"
|
|
191
|
+
aria-label="删除数字项"
|
|
192
|
+
@click.stop="removeItem(it.id)"
|
|
193
|
+
>
|
|
194
|
+
<Icon
|
|
195
|
+
icon="fluent:delete-20-regular"
|
|
196
|
+
class="size-3.5"
|
|
197
|
+
/>
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</ScrollArea>
|
|
202
|
+
|
|
203
|
+
<Button
|
|
204
|
+
variant="ghost"
|
|
205
|
+
size="sm"
|
|
206
|
+
class="mt-1 w-full justify-center"
|
|
207
|
+
@click="addItem()"
|
|
208
|
+
>
|
|
209
|
+
<Icon
|
|
210
|
+
icon="fluent:add-20-regular"
|
|
211
|
+
class="size-4"
|
|
212
|
+
/>
|
|
213
|
+
<span>新增数字</span>
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<Separator orientation="vertical" />
|
|
218
|
+
|
|
219
|
+
<!-- Right pane -->
|
|
220
|
+
<div class="min-w-0 flex-1">
|
|
221
|
+
<!-- General -->
|
|
222
|
+
<div
|
|
223
|
+
v-if="viewMode === 'general'"
|
|
224
|
+
class="flex flex-col gap-4"
|
|
225
|
+
>
|
|
226
|
+
<Field orientation="vertical">
|
|
227
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
228
|
+
<template
|
|
229
|
+
v-if="fieldDescription('displayName')"
|
|
230
|
+
#tooltip
|
|
231
|
+
>
|
|
232
|
+
<Markdown
|
|
233
|
+
:source="fieldDescription('displayName')"
|
|
234
|
+
block
|
|
235
|
+
class="prose prose-sm prose-zinc"
|
|
236
|
+
/>
|
|
237
|
+
</template>
|
|
238
|
+
{{ fieldTitle("displayName") }}
|
|
239
|
+
</FieldLabel>
|
|
240
|
+
<InputGroup>
|
|
241
|
+
<InputGroupInput
|
|
242
|
+
:model-value="block.displayName ?? ''"
|
|
243
|
+
placeholder="例:核心指标"
|
|
244
|
+
@update:model-value="setDisplayName"
|
|
245
|
+
/>
|
|
246
|
+
</InputGroup>
|
|
247
|
+
</Field>
|
|
248
|
+
|
|
249
|
+
<Field orientation="vertical">
|
|
250
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
251
|
+
<template
|
|
252
|
+
v-if="fieldDescription('locale')"
|
|
253
|
+
#tooltip
|
|
254
|
+
>
|
|
255
|
+
<Markdown
|
|
256
|
+
:source="fieldDescription('locale')"
|
|
257
|
+
block
|
|
258
|
+
class="prose prose-sm prose-zinc"
|
|
259
|
+
/>
|
|
260
|
+
</template>
|
|
261
|
+
{{ fieldTitle("locale") }}
|
|
262
|
+
</FieldLabel>
|
|
263
|
+
<LocaleField
|
|
264
|
+
markdown
|
|
265
|
+
translate-hint="animated number caption"
|
|
266
|
+
:model-value="block.locale"
|
|
267
|
+
@update:model-value="setLocale"
|
|
268
|
+
/>
|
|
269
|
+
</Field>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- Item editor -->
|
|
273
|
+
<div
|
|
274
|
+
v-else-if="selectedItem"
|
|
275
|
+
class="flex flex-col gap-4"
|
|
276
|
+
>
|
|
277
|
+
<Field orientation="vertical">
|
|
278
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
279
|
+
<template
|
|
280
|
+
v-if="itemDescription('name')"
|
|
281
|
+
#tooltip
|
|
282
|
+
>
|
|
283
|
+
<Markdown
|
|
284
|
+
:source="itemDescription('name')"
|
|
285
|
+
block
|
|
286
|
+
class="prose prose-sm prose-zinc"
|
|
287
|
+
/>
|
|
288
|
+
</template>
|
|
289
|
+
{{ itemTitle("name") }}
|
|
290
|
+
</FieldLabel>
|
|
291
|
+
<InputGroup>
|
|
292
|
+
<InputGroupInput
|
|
293
|
+
:model-value="selectedItem.name ?? ''"
|
|
294
|
+
placeholder="例:在线人数"
|
|
295
|
+
@update:model-value="(v) => setName(selectedItem.id, v)"
|
|
296
|
+
/>
|
|
297
|
+
</InputGroup>
|
|
298
|
+
</Field>
|
|
299
|
+
|
|
300
|
+
<Field orientation="vertical">
|
|
301
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
302
|
+
<template
|
|
303
|
+
v-if="dsDescription('request')"
|
|
304
|
+
#tooltip
|
|
305
|
+
>
|
|
306
|
+
<Markdown
|
|
307
|
+
:source="dsDescription('request')"
|
|
308
|
+
block
|
|
309
|
+
class="prose prose-sm prose-zinc"
|
|
310
|
+
/>
|
|
311
|
+
</template>
|
|
312
|
+
{{ dsTitle("request") }}
|
|
313
|
+
</FieldLabel>
|
|
314
|
+
<ExpressionEditor
|
|
315
|
+
:model-value="selectedItem.dataSource.request ?? ''"
|
|
316
|
+
placeholder="可选;如 http.get('/api/online')"
|
|
317
|
+
result-type="HttpRequest"
|
|
318
|
+
multiline
|
|
319
|
+
class="min-h-20"
|
|
320
|
+
@update:model-value="(v) => setRequest(selectedItem.id, v)"
|
|
321
|
+
/>
|
|
322
|
+
</Field>
|
|
323
|
+
|
|
324
|
+
<div class="grid grid-cols-2 gap-3">
|
|
325
|
+
<Field orientation="vertical">
|
|
326
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
327
|
+
<template
|
|
328
|
+
v-if="dsDescription('value')"
|
|
329
|
+
#tooltip
|
|
330
|
+
>
|
|
331
|
+
<Markdown
|
|
332
|
+
:source="dsDescription('value')"
|
|
333
|
+
block
|
|
334
|
+
class="prose prose-sm prose-zinc"
|
|
335
|
+
/>
|
|
336
|
+
</template>
|
|
337
|
+
{{ dsTitle("value") }}
|
|
338
|
+
</FieldLabel>
|
|
339
|
+
<ExpressionEditor
|
|
340
|
+
:model-value="selectedItem.dataSource.value"
|
|
341
|
+
placeholder="返回数字的表达式,如 json.count"
|
|
342
|
+
result-type="number"
|
|
343
|
+
:extra-vars="JSON_VAR"
|
|
344
|
+
@update:model-value="(v) => setValue(selectedItem.id, v)"
|
|
345
|
+
/>
|
|
346
|
+
</Field>
|
|
347
|
+
|
|
348
|
+
<Field orientation="vertical">
|
|
349
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
350
|
+
<template
|
|
351
|
+
v-if="itemDescription('pollingInterval')"
|
|
352
|
+
#tooltip
|
|
353
|
+
>
|
|
354
|
+
<Markdown
|
|
355
|
+
:source="itemDescription('pollingInterval')"
|
|
356
|
+
block
|
|
357
|
+
class="prose prose-sm prose-zinc"
|
|
358
|
+
/>
|
|
359
|
+
</template>
|
|
360
|
+
{{ itemTitle("pollingInterval") }}
|
|
361
|
+
</FieldLabel>
|
|
362
|
+
<InputGroup>
|
|
363
|
+
<InputGroupNumberField
|
|
364
|
+
:model-value="selectedItem.pollingInterval"
|
|
365
|
+
:min="1"
|
|
366
|
+
@update:model-value="(v) => setInterval(selectedItem.id, v)"
|
|
367
|
+
/>
|
|
368
|
+
</InputGroup>
|
|
369
|
+
</Field>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<Separator />
|
|
373
|
+
|
|
374
|
+
<div class="grid grid-cols-2 gap-3">
|
|
375
|
+
<Field orientation="vertical">
|
|
376
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
377
|
+
<template
|
|
378
|
+
v-if="itemDescription('prefix')"
|
|
379
|
+
#tooltip
|
|
380
|
+
>
|
|
381
|
+
<Markdown
|
|
382
|
+
:source="itemDescription('prefix')"
|
|
383
|
+
block
|
|
384
|
+
class="prose prose-sm prose-zinc"
|
|
385
|
+
/>
|
|
386
|
+
</template>
|
|
387
|
+
{{ itemTitle("prefix") }}
|
|
388
|
+
</FieldLabel>
|
|
389
|
+
<ExpressionEditor
|
|
390
|
+
:model-value="selectedItem.prefix ?? ''"
|
|
391
|
+
placeholder="如 '¥'"
|
|
392
|
+
result-type="string"
|
|
393
|
+
:extra-vars="JSON_VAR"
|
|
394
|
+
@update:model-value="(v) => setPrefix(selectedItem.id, v)"
|
|
395
|
+
/>
|
|
396
|
+
</Field>
|
|
397
|
+
|
|
398
|
+
<Field orientation="vertical">
|
|
399
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
400
|
+
<template
|
|
401
|
+
v-if="itemDescription('suffix')"
|
|
402
|
+
#tooltip
|
|
403
|
+
>
|
|
404
|
+
<Markdown
|
|
405
|
+
:source="itemDescription('suffix')"
|
|
406
|
+
block
|
|
407
|
+
class="prose prose-sm prose-zinc"
|
|
408
|
+
/>
|
|
409
|
+
</template>
|
|
410
|
+
{{ itemTitle("suffix") }}
|
|
411
|
+
</FieldLabel>
|
|
412
|
+
<ExpressionEditor
|
|
413
|
+
:model-value="selectedItem.suffix ?? ''"
|
|
414
|
+
placeholder="如 ' 人'"
|
|
415
|
+
result-type="string"
|
|
416
|
+
:extra-vars="JSON_VAR"
|
|
417
|
+
@update:model-value="(v) => setSuffix(selectedItem.id, v)"
|
|
418
|
+
/>
|
|
419
|
+
</Field>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<Field orientation="vertical">
|
|
423
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
424
|
+
<template
|
|
425
|
+
v-if="itemDescription('style')"
|
|
426
|
+
#tooltip
|
|
427
|
+
>
|
|
428
|
+
<Markdown
|
|
429
|
+
:source="itemDescription('style')"
|
|
430
|
+
block
|
|
431
|
+
class="prose prose-sm prose-zinc"
|
|
432
|
+
/>
|
|
433
|
+
</template>
|
|
434
|
+
{{ itemTitle("style") }}
|
|
435
|
+
</FieldLabel>
|
|
436
|
+
<ExpressionEditor
|
|
437
|
+
:model-value="selectedItem.style ?? ''"
|
|
438
|
+
placeholder="返回 CSS 字符串或样式对象的表达式,如 'font-size: 3rem'"
|
|
439
|
+
:extra-vars="JSON_VAR"
|
|
440
|
+
multiline
|
|
441
|
+
class="min-h-16"
|
|
442
|
+
@update:model-value="(v) => setStyle(selectedItem.id, v)"
|
|
443
|
+
/>
|
|
444
|
+
</Field>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</template>
|
|
449
|
+
|
|
450
|
+
<style scoped>
|
|
451
|
+
.row{align-items:center;border-radius:.25rem;cursor:pointer;display:flex;font-size:.875rem;gap:.375rem;padding-block:.375rem;padding-left:.5rem;padding-right:.5rem;position:relative;transition:background-color .1s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}[data-instruction=reorder-above]:before,[data-instruction=reorder-below]:after{background:var(--primary,#2563eb);content:"";height:2px;left:.5rem;pointer-events:none;position:absolute;right:.5rem;z-index:1}[data-instruction=reorder-above]:before{top:-1px}[data-instruction=reorder-below]:after{bottom:-1px}
|
|
452
|
+
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type Value } from './schema.js';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
modelValue: Value;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:modelValue": (value: {
|
|
7
|
+
readonly type: "com.shwfed.block.animated.number";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly locale?: readonly [{
|
|
10
|
+
readonly locale: "zh";
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}, ...{
|
|
13
|
+
readonly locale: "en" | "ja" | "ko";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}[]] | undefined;
|
|
16
|
+
readonly displayName?: string | undefined;
|
|
17
|
+
readonly compatibilityDate: "2026-06-01";
|
|
18
|
+
readonly items: readonly {
|
|
19
|
+
readonly name?: string | undefined;
|
|
20
|
+
readonly style?: string | undefined;
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly prefix?: string | undefined;
|
|
23
|
+
readonly dataSource: {
|
|
24
|
+
readonly value: string;
|
|
25
|
+
readonly request?: string | undefined;
|
|
26
|
+
};
|
|
27
|
+
readonly pollingInterval?: number | undefined;
|
|
28
|
+
readonly suffix?: string | undefined;
|
|
29
|
+
}[];
|
|
30
|
+
}) => any;
|
|
31
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
32
|
+
"onUpdate:modelValue"?: ((value: {
|
|
33
|
+
readonly type: "com.shwfed.block.animated.number";
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly locale?: readonly [{
|
|
36
|
+
readonly locale: "zh";
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}, ...{
|
|
39
|
+
readonly locale: "en" | "ja" | "ko";
|
|
40
|
+
readonly message: string;
|
|
41
|
+
}[]] | undefined;
|
|
42
|
+
readonly displayName?: string | undefined;
|
|
43
|
+
readonly compatibilityDate: "2026-06-01";
|
|
44
|
+
readonly items: readonly {
|
|
45
|
+
readonly name?: string | undefined;
|
|
46
|
+
readonly style?: string | undefined;
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly prefix?: string | undefined;
|
|
49
|
+
readonly dataSource: {
|
|
50
|
+
readonly value: string;
|
|
51
|
+
readonly request?: string | undefined;
|
|
52
|
+
};
|
|
53
|
+
readonly pollingInterval?: number | undefined;
|
|
54
|
+
readonly suffix?: string | undefined;
|
|
55
|
+
}[];
|
|
56
|
+
}) => any) | undefined;
|
|
57
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
58
|
+
declare const _default: typeof __VLS_export;
|
|
59
|
+
export default _default;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ItemValue } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
item: ItemValue;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
package/dist/runtime/components/config/blocks/2026-06-01/com.shwfed.block.animated.number/item.vue
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
import { Fetch } from "fx-fetch";
|
|
5
|
+
import { useIntervalFn, useWindowFocus } from "@vueuse/core";
|
|
6
|
+
import NumberFlow from "@number-flow/vue";
|
|
7
|
+
import { cel } from "../../../../../utils/cel";
|
|
8
|
+
import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
|
|
9
|
+
defineOptions({ name: "ShwfedBlockAnimatedNumberItem" });
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
item: { type: null, required: true }
|
|
12
|
+
});
|
|
13
|
+
const inheritedContext = injectCELContext();
|
|
14
|
+
const json = ref(void 0);
|
|
15
|
+
const numberValue = ref(0);
|
|
16
|
+
function baseContext() {
|
|
17
|
+
return celBindings(inheritedContext);
|
|
18
|
+
}
|
|
19
|
+
function toFiniteNumber(v) {
|
|
20
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
21
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
22
|
+
const n = Number(v);
|
|
23
|
+
return Number.isFinite(n) ? n : 0;
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const jsonOption = computed(
|
|
28
|
+
() => json.value === void 0 ? Option.none() : Option.some(json.value)
|
|
29
|
+
);
|
|
30
|
+
function evalString(expression) {
|
|
31
|
+
if (!expression) return "";
|
|
32
|
+
try {
|
|
33
|
+
const r = Effect.runSync(cel(expression, { ...baseContext(), json: jsonOption.value }));
|
|
34
|
+
return r === null || r === void 0 ? "" : String(r);
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const prefixText = computed(() => evalString(props.item.prefix));
|
|
40
|
+
const suffixText = computed(() => evalString(props.item.suffix));
|
|
41
|
+
function evalStyle(expression) {
|
|
42
|
+
if (!expression) return void 0;
|
|
43
|
+
try {
|
|
44
|
+
const r = Effect.runSync(cel(expression, { ...baseContext(), json: jsonOption.value }));
|
|
45
|
+
if (r === null || r === void 0) return void 0;
|
|
46
|
+
if (typeof r === "string") return r;
|
|
47
|
+
if (typeof r === "object") return r;
|
|
48
|
+
return String(r);
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const styleValue = computed(() => evalStyle(props.item.style));
|
|
54
|
+
async function refresh() {
|
|
55
|
+
const ds = props.item.dataSource;
|
|
56
|
+
const ctx = baseContext();
|
|
57
|
+
const program = Effect.gen(function* () {
|
|
58
|
+
let jsonOpt = Option.none();
|
|
59
|
+
if (ds.request) {
|
|
60
|
+
const builder = yield* cel(ds.request, ctx);
|
|
61
|
+
jsonOpt = Option.some(yield* builder.json());
|
|
62
|
+
}
|
|
63
|
+
const value = yield* cel(ds.value, { ...ctx, json: jsonOpt });
|
|
64
|
+
return { json: Option.getOrUndefined(jsonOpt), value };
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
const result = await Effect.runPromise(Effect.provide(program, Fetch.layer));
|
|
68
|
+
json.value = result.json;
|
|
69
|
+
numberValue.value = toFiniteNumber(result.value);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.warn("[shwfed-animated-number] fetch failed", e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const pollingEnabled = computed(() => (props.item.pollingInterval ?? 0) > 0);
|
|
75
|
+
const intervalMs = computed(() => Math.max(1, props.item.pollingInterval ?? 1) * 1e3);
|
|
76
|
+
const focused = useWindowFocus();
|
|
77
|
+
const { pause, resume } = useIntervalFn(() => void refresh(), intervalMs, { immediate: false });
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
void refresh();
|
|
80
|
+
if (pollingEnabled.value && focused.value) resume();
|
|
81
|
+
});
|
|
82
|
+
watch(focused, (isFocused) => {
|
|
83
|
+
if (!pollingEnabled.value) return;
|
|
84
|
+
if (isFocused) {
|
|
85
|
+
void refresh();
|
|
86
|
+
resume();
|
|
87
|
+
} else {
|
|
88
|
+
pause();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
watch(
|
|
92
|
+
() => [
|
|
93
|
+
props.item.dataSource.request ?? "",
|
|
94
|
+
props.item.dataSource.value,
|
|
95
|
+
props.item.pollingInterval ?? 0
|
|
96
|
+
].join("|"),
|
|
97
|
+
() => {
|
|
98
|
+
void refresh();
|
|
99
|
+
if (pollingEnabled.value && focused.value) resume();
|
|
100
|
+
else pause();
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<template>
|
|
106
|
+
<NumberFlow
|
|
107
|
+
:value="numberValue"
|
|
108
|
+
:prefix="prefixText"
|
|
109
|
+
:suffix="suffixText"
|
|
110
|
+
:style="styleValue"
|
|
111
|
+
/>
|
|
112
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ItemValue } from './schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
item: ItemValue;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|