@yaoxiu/marketing-dsl 1.1.0 → 1.3.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 +16 -9
- package/dist/index.cjs +134 -29
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +134 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ import { createRuntime } from '@yaoxiu/marketing-dsl';
|
|
|
47
47
|
|
|
48
48
|
const runtime = createRuntime(dsl, {
|
|
49
49
|
user: { shopName: '麦爆了旗舰店', version: '专业版' },
|
|
50
|
-
sources: { listRenewTiers:
|
|
50
|
+
sources: { listRenewTiers: params => api.getTiers(params) },
|
|
51
51
|
handlers: { setNeverRemind: () => localStorage.setItem('never', '1') },
|
|
52
52
|
emit: (event, payload) => console.log(event, payload),
|
|
53
53
|
});
|
|
@@ -56,11 +56,18 @@ const unsubscribe = runtime.subscribe(() => {
|
|
|
56
56
|
render(runtime.getTree()); // 每次通知后重新取树画一遍
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
// 用户信息异步到了、或者要切换编辑态,用 update 就地更新。
|
|
60
|
+
// 不要为此重建 runtime——重建会重新拉数据源、重放曝光埋点,
|
|
61
|
+
// 还会把用户已经切到的 tab 打回默认值。
|
|
62
|
+
runtime.update({ user: { shopName: '麦爆了旗舰店' } });
|
|
63
|
+
|
|
59
64
|
// 卸载时必须调,否则倒计时定时器不会停
|
|
60
65
|
runtime.destroy();
|
|
61
66
|
unsubscribe();
|
|
62
67
|
```
|
|
63
68
|
|
|
69
|
+
`update` 能改 `user` / `handlers` / `editMode`。换 `dsl` 本身要重建 runtime。
|
|
70
|
+
|
|
64
71
|
## 渲染树
|
|
65
72
|
|
|
66
73
|
`getTree()` 返回的是纯数据,所有解释工作都已经做完:插值算完了、条件判断过了、循环展开了、样式转成 CSS 了、坐标换算好了、动作绑好了。
|
|
@@ -97,14 +104,14 @@ unsubscribe();
|
|
|
97
104
|
|
|
98
105
|
这几条是设计红线,不会因为「就差一点点」而放开:
|
|
99
106
|
|
|
100
|
-
| 边界
|
|
101
|
-
|
|
|
102
|
-
| 不执行任意代码
|
|
103
|
-
| 不能沿原型链逃逸
|
|
104
|
-
| 不能通过样式作恶
|
|
105
|
-
| 不能执行 `javascript:` | 链接只允许 `http` / `https` / `mailto` / `tel` 和相对路径
|
|
106
|
-
| 不能打任意接口
|
|
107
|
-
| 不能调任意方法
|
|
107
|
+
| 边界 | 做法 |
|
|
108
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
109
|
+
| 不执行任意代码 | 手写解析器,不用 `eval` / `new Function`;不支持函数调用和赋值 |
|
|
110
|
+
| 不能沿原型链逃逸 | `constructor` / `__proto__` / `prototype` 一律读不到,函数值也读不到 |
|
|
111
|
+
| 不能通过样式作恶 | 只有白名单里的属性会输出,`position: fixed` 这类会被丢弃 |
|
|
112
|
+
| 不能执行 `javascript:` | 链接只允许 `http` / `https` / `mailto` / `tel` 和相对路径 |
|
|
113
|
+
| 不能打任意接口 | 数据源用注册名引用,配置里写不了 URL |
|
|
114
|
+
| 不能调任意方法 | `call` 只能调宿主注册在 `handlers` 里的方法 |
|
|
108
115
|
|
|
109
116
|
## 完整 DSL 语法
|
|
110
117
|
|
package/dist/index.cjs
CHANGED
|
@@ -326,9 +326,11 @@ function safeImageUrl(input) {
|
|
|
326
326
|
|
|
327
327
|
// src/actions.ts
|
|
328
328
|
function createDispatcher(options) {
|
|
329
|
-
const { setState, emit, openView, closeTop, closeAll
|
|
329
|
+
const { setState, emit, openView, closeTop, closeAll } = options;
|
|
330
330
|
function dispatch(action, context) {
|
|
331
331
|
if (!action || !action.type) return;
|
|
332
|
+
const handlers = options.handlers || {};
|
|
333
|
+
const editMode = !!options.editMode;
|
|
332
334
|
switch (action.type) {
|
|
333
335
|
case "sequence":
|
|
334
336
|
(action.actions || []).forEach((item) => dispatch(item, context));
|
|
@@ -398,10 +400,24 @@ function parseEndTime(to) {
|
|
|
398
400
|
if (typeof to === "number") return to;
|
|
399
401
|
if (to === void 0 || to === null || to === "") return 0;
|
|
400
402
|
const raw = String(to).trim();
|
|
403
|
+
if (!raw) return 0;
|
|
401
404
|
if (/^\d+$/.test(raw)) return Number(raw);
|
|
402
|
-
|
|
405
|
+
if (/^\d{4}年/.test(raw)) {
|
|
406
|
+
const normalizedZh = raw.replace(/年|月/g, "/").replace(/日/g, " ").replace(/时|点|分/g, ":").replace(/秒/g, "").replace(/:+/g, ":").replace(/\s+/g, " ").replace(/[:\s]+$/, "").replace(/(\s\d{1,2})$/, "$1:00").trim();
|
|
407
|
+
const time2 = new Date(normalizedZh).getTime();
|
|
408
|
+
return isNaN(time2) ? 0 : time2;
|
|
409
|
+
}
|
|
410
|
+
const isIso = /^\d{4}-\d{2}-\d{2}T/.test(raw);
|
|
411
|
+
const normalized = isIso ? raw : raw.replace(/-/g, "/");
|
|
412
|
+
const time = new Date(normalized).getTime();
|
|
403
413
|
return isNaN(time) ? 0 : time;
|
|
404
414
|
}
|
|
415
|
+
function isValidEndTime(to) {
|
|
416
|
+
if (typeof to === "number") return isFinite(to) && to > 0;
|
|
417
|
+
if (typeof to !== "string") return false;
|
|
418
|
+
if (to.indexOf("{{") > -1) return true;
|
|
419
|
+
return parseEndTime(to) > 0;
|
|
420
|
+
}
|
|
405
421
|
function computeParts(endTime, now = Date.now()) {
|
|
406
422
|
const raw = endTime - now;
|
|
407
423
|
const remain = raw > 0 ? raw : 0;
|
|
@@ -608,7 +624,13 @@ function resolveTree(input) {
|
|
|
608
624
|
);
|
|
609
625
|
const countdowns = { endTimes: [], precision: "s" };
|
|
610
626
|
const renderLayers = ready ? layers.map(
|
|
611
|
-
(item, index) => resolveLayer(
|
|
627
|
+
(item, index) => resolveLayer(
|
|
628
|
+
item.name,
|
|
629
|
+
item.view,
|
|
630
|
+
index === layers.length - 1,
|
|
631
|
+
input,
|
|
632
|
+
countdowns
|
|
633
|
+
)
|
|
612
634
|
) : [];
|
|
613
635
|
return {
|
|
614
636
|
ready: ready && layers.length > 0,
|
|
@@ -627,7 +649,9 @@ function resolveLayer(name, view, isTop, input, countdowns) {
|
|
|
627
649
|
const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
|
|
628
650
|
const maskClosable = !!stage.maskClosable;
|
|
629
651
|
const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
|
|
630
|
-
const nodes = (view.nodes || []).map(
|
|
652
|
+
const nodes = (view.nodes || []).map(
|
|
653
|
+
(node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)
|
|
654
|
+
).filter((el) => !!el);
|
|
631
655
|
return {
|
|
632
656
|
name,
|
|
633
657
|
type: view.type,
|
|
@@ -690,8 +714,12 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
690
714
|
base,
|
|
691
715
|
{
|
|
692
716
|
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
693
|
-
width: toLength(
|
|
694
|
-
|
|
717
|
+
width: toLength(
|
|
718
|
+
interpolate(stage.width === void 0 ? 320 : stage.width, context)
|
|
719
|
+
),
|
|
720
|
+
height: toLength(
|
|
721
|
+
interpolate(stage.height === void 0 ? 400 : stage.height, context)
|
|
722
|
+
)
|
|
695
723
|
},
|
|
696
724
|
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
697
725
|
toCssStyle(interpolateDeep(stage.style, context))
|
|
@@ -746,7 +774,12 @@ function resolveCloseButton(config, layerName, closeTop) {
|
|
|
746
774
|
tag: "img",
|
|
747
775
|
className: "dsl-close-img",
|
|
748
776
|
src: image,
|
|
749
|
-
style: {
|
|
777
|
+
style: {
|
|
778
|
+
width: "100%",
|
|
779
|
+
height: "100%",
|
|
780
|
+
objectFit: "contain",
|
|
781
|
+
display: "block"
|
|
782
|
+
}
|
|
750
783
|
}
|
|
751
784
|
]
|
|
752
785
|
};
|
|
@@ -796,7 +829,14 @@ function resolveNode(node, key, layout, context, input, countdowns) {
|
|
|
796
829
|
className: nodeClass("box", !!onClick),
|
|
797
830
|
style: Object.assign({ position: "relative" }, style),
|
|
798
831
|
onClick,
|
|
799
|
-
children: resolveChildren(
|
|
832
|
+
children: resolveChildren(
|
|
833
|
+
node.children,
|
|
834
|
+
key,
|
|
835
|
+
"absolute",
|
|
836
|
+
context,
|
|
837
|
+
input,
|
|
838
|
+
countdowns
|
|
839
|
+
)
|
|
800
840
|
};
|
|
801
841
|
case "flex":
|
|
802
842
|
return {
|
|
@@ -881,7 +921,14 @@ function resolveRepeat(node, key, context, input, countdowns) {
|
|
|
881
921
|
[node.itemName || "item"]: item,
|
|
882
922
|
[node.indexName || "index"]: index
|
|
883
923
|
});
|
|
884
|
-
return resolveNode(
|
|
924
|
+
return resolveNode(
|
|
925
|
+
node.template,
|
|
926
|
+
`${key}-${index}`,
|
|
927
|
+
"flow",
|
|
928
|
+
itemContext,
|
|
929
|
+
input,
|
|
930
|
+
countdowns
|
|
931
|
+
);
|
|
885
932
|
}).filter((el) => !!el);
|
|
886
933
|
}
|
|
887
934
|
function resolveTabs(node, key, context, input) {
|
|
@@ -923,10 +970,13 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
923
970
|
const parts = computeParts(endTime);
|
|
924
971
|
countdowns.endTimes.push(endTime);
|
|
925
972
|
if (node.precision === "cs") countdowns.precision = "cs";
|
|
926
|
-
if (
|
|
973
|
+
if (endTime && node.onEnd) {
|
|
927
974
|
const token = `${key}@${endTime}`;
|
|
928
|
-
|
|
929
|
-
|
|
975
|
+
const status = input.countdownEnds.get(token);
|
|
976
|
+
if (status === void 0) {
|
|
977
|
+
input.countdownEnds.set(token, parts.ended ? "done" : "waiting");
|
|
978
|
+
} else if (status === "waiting" && parts.ended) {
|
|
979
|
+
input.countdownEnds.set(token, "done");
|
|
930
980
|
input.dispatch(node.onEnd, context);
|
|
931
981
|
}
|
|
932
982
|
}
|
|
@@ -954,7 +1004,14 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
954
1004
|
tag: "div",
|
|
955
1005
|
className: "dsl-node dsl-flex dsl-countdown",
|
|
956
1006
|
style: Object.assign({ display: "flex" }, style),
|
|
957
|
-
children: resolveChildren(
|
|
1007
|
+
children: resolveChildren(
|
|
1008
|
+
node.children,
|
|
1009
|
+
key,
|
|
1010
|
+
"flow",
|
|
1011
|
+
childContext,
|
|
1012
|
+
input,
|
|
1013
|
+
countdowns
|
|
1014
|
+
)
|
|
958
1015
|
};
|
|
959
1016
|
}
|
|
960
1017
|
function toText(value) {
|
|
@@ -965,7 +1022,12 @@ function toText(value) {
|
|
|
965
1022
|
var noopEmit = () => {
|
|
966
1023
|
};
|
|
967
1024
|
function createRuntime(dsl, options = {}) {
|
|
968
|
-
const {
|
|
1025
|
+
const { sources = {}, emit = noopEmit } = options;
|
|
1026
|
+
const live = {
|
|
1027
|
+
user: options.user || {},
|
|
1028
|
+
handlers: options.handlers || {},
|
|
1029
|
+
editMode: !!options.editMode
|
|
1030
|
+
};
|
|
969
1031
|
const normalized = normalizeViews(dsl);
|
|
970
1032
|
let state = Object.assign({}, dsl.state || {});
|
|
971
1033
|
let resolvedData = {};
|
|
@@ -974,14 +1036,17 @@ function createRuntime(dsl, options = {}) {
|
|
|
974
1036
|
let loadFailed = false;
|
|
975
1037
|
let destroyed = false;
|
|
976
1038
|
const listeners = /* @__PURE__ */ new Set();
|
|
977
|
-
const
|
|
1039
|
+
const countdownEnds = /* @__PURE__ */ new Map();
|
|
978
1040
|
const notify = () => {
|
|
979
1041
|
if (destroyed) return;
|
|
980
1042
|
listeners.forEach((fn) => fn());
|
|
981
1043
|
};
|
|
982
1044
|
const ticker = createTicker({ onTick: notify });
|
|
983
1045
|
function buildContext() {
|
|
984
|
-
const context = Object.assign({}, resolvedData, {
|
|
1046
|
+
const context = Object.assign({}, resolvedData, {
|
|
1047
|
+
state,
|
|
1048
|
+
user: live.user
|
|
1049
|
+
});
|
|
985
1050
|
const derived = dsl.derived || {};
|
|
986
1051
|
Object.keys(derived).forEach((name) => {
|
|
987
1052
|
const config = derived[name] || {};
|
|
@@ -1042,8 +1107,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1042
1107
|
// 配置里 close 可以自带 reason(如 'never-remind'),要透传给埋点,不能吞掉
|
|
1043
1108
|
closeTop: (reason) => closeTop(reason),
|
|
1044
1109
|
closeAll: (reason) => closeAll(reason),
|
|
1045
|
-
handlers
|
|
1046
|
-
|
|
1110
|
+
get handlers() {
|
|
1111
|
+
return live.handlers;
|
|
1112
|
+
},
|
|
1113
|
+
get editMode() {
|
|
1114
|
+
return live.editMode;
|
|
1115
|
+
}
|
|
1047
1116
|
});
|
|
1048
1117
|
function fireLifecycle(viewName, hook, extra) {
|
|
1049
1118
|
const view = normalized.views[viewName];
|
|
@@ -1070,7 +1139,7 @@ function createRuntime(dsl, options = {}) {
|
|
|
1070
1139
|
return;
|
|
1071
1140
|
}
|
|
1072
1141
|
pending.push(
|
|
1073
|
-
Promise.resolve(source(ref.params || {}, user)).then((result) => {
|
|
1142
|
+
Promise.resolve(source(ref.params || {}, live.user)).then((result) => {
|
|
1074
1143
|
resolved[key] = result;
|
|
1075
1144
|
})
|
|
1076
1145
|
);
|
|
@@ -1101,13 +1170,19 @@ function createRuntime(dsl, options = {}) {
|
|
|
1101
1170
|
dispatch,
|
|
1102
1171
|
setState,
|
|
1103
1172
|
closeTop,
|
|
1104
|
-
|
|
1173
|
+
countdownEnds
|
|
1105
1174
|
});
|
|
1106
1175
|
ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
|
|
1107
1176
|
return tree;
|
|
1108
1177
|
}
|
|
1109
1178
|
return {
|
|
1110
1179
|
getTree,
|
|
1180
|
+
update(patch) {
|
|
1181
|
+
if (patch.user !== void 0) live.user = patch.user;
|
|
1182
|
+
if (patch.handlers !== void 0) live.handlers = patch.handlers;
|
|
1183
|
+
if (patch.editMode !== void 0) live.editMode = patch.editMode;
|
|
1184
|
+
notify();
|
|
1185
|
+
},
|
|
1111
1186
|
subscribe(listener) {
|
|
1112
1187
|
listeners.add(listener);
|
|
1113
1188
|
return () => listeners.delete(listener);
|
|
@@ -1186,7 +1261,10 @@ function validate(dsl) {
|
|
|
1186
1261
|
}
|
|
1187
1262
|
const doc = dsl;
|
|
1188
1263
|
if (doc.version !== DSL_VERSION) {
|
|
1189
|
-
add(
|
|
1264
|
+
add(
|
|
1265
|
+
"version",
|
|
1266
|
+
`version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`
|
|
1267
|
+
);
|
|
1190
1268
|
}
|
|
1191
1269
|
const dataKeys = validateData(doc.data, add);
|
|
1192
1270
|
const stateKeys = validateState(doc.state, add);
|
|
@@ -1286,7 +1364,10 @@ function validateStage(stage, add, warn, ctx, prefix) {
|
|
|
1286
1364
|
if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
|
|
1287
1365
|
if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
|
|
1288
1366
|
if (stage.height === "auto" && stage.layout !== "flow") {
|
|
1289
|
-
add(
|
|
1367
|
+
add(
|
|
1368
|
+
"stage.height",
|
|
1369
|
+
"height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0"
|
|
1370
|
+
);
|
|
1290
1371
|
}
|
|
1291
1372
|
}
|
|
1292
1373
|
function validateCloseButton(config, add, warn) {
|
|
@@ -1300,7 +1381,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1300
1381
|
}
|
|
1301
1382
|
if (config.offset !== void 0) {
|
|
1302
1383
|
if (!Array.isArray(config.offset) || config.offset.length !== 2) {
|
|
1303
|
-
add(
|
|
1384
|
+
add(
|
|
1385
|
+
"stage.closeButton.offset",
|
|
1386
|
+
"offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762"
|
|
1387
|
+
);
|
|
1304
1388
|
} else {
|
|
1305
1389
|
config.offset.forEach((value, index) => {
|
|
1306
1390
|
if (!checkLength(value)) {
|
|
@@ -1321,7 +1405,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1321
1405
|
if (config.style) {
|
|
1322
1406
|
Object.keys(config.style).forEach((key) => {
|
|
1323
1407
|
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1324
|
-
warn(
|
|
1408
|
+
warn(
|
|
1409
|
+
`stage.closeButton.style.${key}`,
|
|
1410
|
+
`\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`
|
|
1411
|
+
);
|
|
1325
1412
|
}
|
|
1326
1413
|
});
|
|
1327
1414
|
}
|
|
@@ -1392,7 +1479,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1392
1479
|
} else {
|
|
1393
1480
|
node.rect.forEach((value, index) => {
|
|
1394
1481
|
if (value !== void 0 && value !== null && !checkLength(value)) {
|
|
1395
|
-
add(
|
|
1482
|
+
add(
|
|
1483
|
+
`${path}.rect[${index}]`,
|
|
1484
|
+
`"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1485
|
+
);
|
|
1396
1486
|
}
|
|
1397
1487
|
});
|
|
1398
1488
|
}
|
|
@@ -1410,7 +1500,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1410
1500
|
);
|
|
1411
1501
|
}
|
|
1412
1502
|
if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
|
|
1413
|
-
add(
|
|
1503
|
+
add(
|
|
1504
|
+
`${path}.style.aspectRatio`,
|
|
1505
|
+
"aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
|
|
1506
|
+
);
|
|
1414
1507
|
}
|
|
1415
1508
|
});
|
|
1416
1509
|
}
|
|
@@ -1440,13 +1533,21 @@ function validateNodeByType(node, path, ctx) {
|
|
|
1440
1533
|
if (typeof node.bind !== "string") {
|
|
1441
1534
|
add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
|
|
1442
1535
|
}
|
|
1443
|
-
if (typeof node.stateKey !== "string")
|
|
1536
|
+
if (typeof node.stateKey !== "string")
|
|
1537
|
+
add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
|
|
1444
1538
|
if (node.labelField !== void 0 && typeof node.labelField !== "string") {
|
|
1445
1539
|
add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1446
1540
|
}
|
|
1447
1541
|
}
|
|
1448
1542
|
if (node.type === "countdown") {
|
|
1449
|
-
if (!node.to)
|
|
1543
|
+
if (!node.to) {
|
|
1544
|
+
add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
|
|
1545
|
+
} else if (!isValidEndTime(node.to)) {
|
|
1546
|
+
add(
|
|
1547
|
+
`${path}.to`,
|
|
1548
|
+
`"${node.to}" \u4E0D\u662F\u80FD\u8BC6\u522B\u7684\u65F6\u95F4\u3002\u53EF\u4EE5\u5199 "2026-09-01 18:00:00" / "2026-09-01" / "2026\u5E749\u67081\u65E5 18\u70B9" / ISO \u683C\u5F0F / \u65F6\u95F4\u6233`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1450
1551
|
if (node.precision !== void 0 && ["s", "cs"].indexOf(node.precision) === -1) {
|
|
1451
1552
|
add(`${path}.precision`, "precision \u53EA\u80FD\u662F 's'\uFF08\u6BCF\u79D2\uFF09\u6216 'cs'\uFF08\u5398\u79D2\uFF0C\u9010\u5E27\u5237\u65B0\uFF09");
|
|
1452
1553
|
}
|
|
@@ -1506,7 +1607,10 @@ function validateAction(action, path, ctx) {
|
|
|
1506
1607
|
add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
|
|
1507
1608
|
}
|
|
1508
1609
|
if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
|
|
1509
|
-
add(
|
|
1610
|
+
add(
|
|
1611
|
+
`${path}.mode`,
|
|
1612
|
+
"mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09"
|
|
1613
|
+
);
|
|
1510
1614
|
}
|
|
1511
1615
|
}
|
|
1512
1616
|
if (action.type === "sequence") {
|
|
@@ -1538,6 +1642,7 @@ exports.formatParts = formatParts;
|
|
|
1538
1642
|
exports.interpolate = interpolate;
|
|
1539
1643
|
exports.interpolateDeep = interpolateDeep;
|
|
1540
1644
|
exports.isLength = isLength;
|
|
1645
|
+
exports.isValidEndTime = isValidEndTime;
|
|
1541
1646
|
exports.normalizeViews = normalizeViews;
|
|
1542
1647
|
exports.parseEndTime = parseEndTime;
|
|
1543
1648
|
exports.safeImageUrl = safeImageUrl;
|
package/dist/index.d.cts
CHANGED
|
@@ -293,6 +293,16 @@ interface RuntimeOptions {
|
|
|
293
293
|
interface DslRuntime {
|
|
294
294
|
/** 当前渲染树。每次 subscribe 回调后重新取 */
|
|
295
295
|
getTree: () => RenderTree;
|
|
296
|
+
/**
|
|
297
|
+
* 更新宿主传进来的东西。
|
|
298
|
+
*
|
|
299
|
+
* user 常常是异步到的(弹窗先挂载、用户信息后到),
|
|
300
|
+
* 不给更新入口的话文案就永远停在初始值。
|
|
301
|
+
* editMode / handlers 同理,宿主可能中途换。
|
|
302
|
+
*
|
|
303
|
+
* 不会重新拉数据源,也不会重放 onShow 曝光埋点——那些是一次性的。
|
|
304
|
+
*/
|
|
305
|
+
update: (patch: Pick<RuntimeOptions, 'user' | 'handlers' | 'editMode'>) => void;
|
|
296
306
|
/** 状态变化时回调,返回取消订阅的函数 */
|
|
297
307
|
subscribe: (listener: () => void) => () => void;
|
|
298
308
|
/** 供调试台等外部读取,正常渲染不需要 */
|
|
@@ -428,9 +438,16 @@ interface CountdownParts {
|
|
|
428
438
|
type CountdownPrecision = 's' | 'cs';
|
|
429
439
|
/** 把 to 解析成时间戳。兼容 '2026-09-01 00:00:00' 这种 Safari 不认的格式 */
|
|
430
440
|
declare function parseEndTime(to: unknown): number;
|
|
441
|
+
/**
|
|
442
|
+
* 判断一个 to 能不能解析。给校验用。
|
|
443
|
+
*
|
|
444
|
+
* 解析失败会当成「已结束」,页面上只会看到一行「已结束」,
|
|
445
|
+
* 运营根本意识不到是自己时间写错了——所以必须在保存前就拦下来。
|
|
446
|
+
*/
|
|
447
|
+
declare function isValidEndTime(to: unknown): boolean;
|
|
431
448
|
/** 算出某个时刻的时间片段 */
|
|
432
449
|
declare function computeParts(endTime: number, now?: number): CountdownParts;
|
|
433
450
|
/** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
|
|
434
451
|
declare function formatParts(parts: CountdownParts, format?: string): string;
|
|
435
452
|
|
|
436
|
-
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, type CssStyle, DSL_VERSION, type Dsl, type DslAction, type DslActionBase, type DslCallAction, type DslCloseAction, type DslCloseAllAction, type DslCloseButton, type DslClosePosition, type DslDerived, type DslHandler, type DslLength, type DslNavigateAction, type DslNode, type DslNodeType, type DslOpenAction, type DslRect, type DslRuntime, type DslSequenceAction, type DslSetStateAction, type DslSource, type DslSourceRef, type DslStage, type DslStyle, type DslTrackAction, type DslView, type DslViewType, type Issue, NODE_TYPES, type NormalizedViews, type RenderElement, type RenderLayer, type RenderTree, type RuntimeEmit, type RuntimeEventName, type RuntimeEvents, type RuntimeOptions, SINGLE_VIEW_NAME, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
|
453
|
+
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, type CssStyle, DSL_VERSION, type Dsl, type DslAction, type DslActionBase, type DslCallAction, type DslCloseAction, type DslCloseAllAction, type DslCloseButton, type DslClosePosition, type DslDerived, type DslHandler, type DslLength, type DslNavigateAction, type DslNode, type DslNodeType, type DslOpenAction, type DslRect, type DslRuntime, type DslSequenceAction, type DslSetStateAction, type DslSource, type DslSourceRef, type DslStage, type DslStyle, type DslTrackAction, type DslView, type DslViewType, type Issue, NODE_TYPES, type NormalizedViews, type RenderElement, type RenderLayer, type RenderTree, type RuntimeEmit, type RuntimeEventName, type RuntimeEvents, type RuntimeOptions, SINGLE_VIEW_NAME, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
package/dist/index.d.ts
CHANGED
|
@@ -293,6 +293,16 @@ interface RuntimeOptions {
|
|
|
293
293
|
interface DslRuntime {
|
|
294
294
|
/** 当前渲染树。每次 subscribe 回调后重新取 */
|
|
295
295
|
getTree: () => RenderTree;
|
|
296
|
+
/**
|
|
297
|
+
* 更新宿主传进来的东西。
|
|
298
|
+
*
|
|
299
|
+
* user 常常是异步到的(弹窗先挂载、用户信息后到),
|
|
300
|
+
* 不给更新入口的话文案就永远停在初始值。
|
|
301
|
+
* editMode / handlers 同理,宿主可能中途换。
|
|
302
|
+
*
|
|
303
|
+
* 不会重新拉数据源,也不会重放 onShow 曝光埋点——那些是一次性的。
|
|
304
|
+
*/
|
|
305
|
+
update: (patch: Pick<RuntimeOptions, 'user' | 'handlers' | 'editMode'>) => void;
|
|
296
306
|
/** 状态变化时回调,返回取消订阅的函数 */
|
|
297
307
|
subscribe: (listener: () => void) => () => void;
|
|
298
308
|
/** 供调试台等外部读取,正常渲染不需要 */
|
|
@@ -428,9 +438,16 @@ interface CountdownParts {
|
|
|
428
438
|
type CountdownPrecision = 's' | 'cs';
|
|
429
439
|
/** 把 to 解析成时间戳。兼容 '2026-09-01 00:00:00' 这种 Safari 不认的格式 */
|
|
430
440
|
declare function parseEndTime(to: unknown): number;
|
|
441
|
+
/**
|
|
442
|
+
* 判断一个 to 能不能解析。给校验用。
|
|
443
|
+
*
|
|
444
|
+
* 解析失败会当成「已结束」,页面上只会看到一行「已结束」,
|
|
445
|
+
* 运营根本意识不到是自己时间写错了——所以必须在保存前就拦下来。
|
|
446
|
+
*/
|
|
447
|
+
declare function isValidEndTime(to: unknown): boolean;
|
|
431
448
|
/** 算出某个时刻的时间片段 */
|
|
432
449
|
declare function computeParts(endTime: number, now?: number): CountdownParts;
|
|
433
450
|
/** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
|
|
434
451
|
declare function formatParts(parts: CountdownParts, format?: string): string;
|
|
435
452
|
|
|
436
|
-
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, type CssStyle, DSL_VERSION, type Dsl, type DslAction, type DslActionBase, type DslCallAction, type DslCloseAction, type DslCloseAllAction, type DslCloseButton, type DslClosePosition, type DslDerived, type DslHandler, type DslLength, type DslNavigateAction, type DslNode, type DslNodeType, type DslOpenAction, type DslRect, type DslRuntime, type DslSequenceAction, type DslSetStateAction, type DslSource, type DslSourceRef, type DslStage, type DslStyle, type DslTrackAction, type DslView, type DslViewType, type Issue, NODE_TYPES, type NormalizedViews, type RenderElement, type RenderLayer, type RenderTree, type RuntimeEmit, type RuntimeEventName, type RuntimeEvents, type RuntimeOptions, SINGLE_VIEW_NAME, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
|
453
|
+
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, type CssStyle, DSL_VERSION, type Dsl, type DslAction, type DslActionBase, type DslCallAction, type DslCloseAction, type DslCloseAllAction, type DslCloseButton, type DslClosePosition, type DslDerived, type DslHandler, type DslLength, type DslNavigateAction, type DslNode, type DslNodeType, type DslOpenAction, type DslRect, type DslRuntime, type DslSequenceAction, type DslSetStateAction, type DslSource, type DslSourceRef, type DslStage, type DslStyle, type DslTrackAction, type DslView, type DslViewType, type Issue, NODE_TYPES, type NormalizedViews, type RenderElement, type RenderLayer, type RenderTree, type RuntimeEmit, type RuntimeEventName, type RuntimeEvents, type RuntimeOptions, SINGLE_VIEW_NAME, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
package/dist/index.js
CHANGED
|
@@ -324,9 +324,11 @@ function safeImageUrl(input) {
|
|
|
324
324
|
|
|
325
325
|
// src/actions.ts
|
|
326
326
|
function createDispatcher(options) {
|
|
327
|
-
const { setState, emit, openView, closeTop, closeAll
|
|
327
|
+
const { setState, emit, openView, closeTop, closeAll } = options;
|
|
328
328
|
function dispatch(action, context) {
|
|
329
329
|
if (!action || !action.type) return;
|
|
330
|
+
const handlers = options.handlers || {};
|
|
331
|
+
const editMode = !!options.editMode;
|
|
330
332
|
switch (action.type) {
|
|
331
333
|
case "sequence":
|
|
332
334
|
(action.actions || []).forEach((item) => dispatch(item, context));
|
|
@@ -396,10 +398,24 @@ function parseEndTime(to) {
|
|
|
396
398
|
if (typeof to === "number") return to;
|
|
397
399
|
if (to === void 0 || to === null || to === "") return 0;
|
|
398
400
|
const raw = String(to).trim();
|
|
401
|
+
if (!raw) return 0;
|
|
399
402
|
if (/^\d+$/.test(raw)) return Number(raw);
|
|
400
|
-
|
|
403
|
+
if (/^\d{4}年/.test(raw)) {
|
|
404
|
+
const normalizedZh = raw.replace(/年|月/g, "/").replace(/日/g, " ").replace(/时|点|分/g, ":").replace(/秒/g, "").replace(/:+/g, ":").replace(/\s+/g, " ").replace(/[:\s]+$/, "").replace(/(\s\d{1,2})$/, "$1:00").trim();
|
|
405
|
+
const time2 = new Date(normalizedZh).getTime();
|
|
406
|
+
return isNaN(time2) ? 0 : time2;
|
|
407
|
+
}
|
|
408
|
+
const isIso = /^\d{4}-\d{2}-\d{2}T/.test(raw);
|
|
409
|
+
const normalized = isIso ? raw : raw.replace(/-/g, "/");
|
|
410
|
+
const time = new Date(normalized).getTime();
|
|
401
411
|
return isNaN(time) ? 0 : time;
|
|
402
412
|
}
|
|
413
|
+
function isValidEndTime(to) {
|
|
414
|
+
if (typeof to === "number") return isFinite(to) && to > 0;
|
|
415
|
+
if (typeof to !== "string") return false;
|
|
416
|
+
if (to.indexOf("{{") > -1) return true;
|
|
417
|
+
return parseEndTime(to) > 0;
|
|
418
|
+
}
|
|
403
419
|
function computeParts(endTime, now = Date.now()) {
|
|
404
420
|
const raw = endTime - now;
|
|
405
421
|
const remain = raw > 0 ? raw : 0;
|
|
@@ -606,7 +622,13 @@ function resolveTree(input) {
|
|
|
606
622
|
);
|
|
607
623
|
const countdowns = { endTimes: [], precision: "s" };
|
|
608
624
|
const renderLayers = ready ? layers.map(
|
|
609
|
-
(item, index) => resolveLayer(
|
|
625
|
+
(item, index) => resolveLayer(
|
|
626
|
+
item.name,
|
|
627
|
+
item.view,
|
|
628
|
+
index === layers.length - 1,
|
|
629
|
+
input,
|
|
630
|
+
countdowns
|
|
631
|
+
)
|
|
610
632
|
) : [];
|
|
611
633
|
return {
|
|
612
634
|
ready: ready && layers.length > 0,
|
|
@@ -625,7 +647,9 @@ function resolveLayer(name, view, isTop, input, countdowns) {
|
|
|
625
647
|
const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
|
|
626
648
|
const maskClosable = !!stage.maskClosable;
|
|
627
649
|
const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
|
|
628
|
-
const nodes = (view.nodes || []).map(
|
|
650
|
+
const nodes = (view.nodes || []).map(
|
|
651
|
+
(node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)
|
|
652
|
+
).filter((el) => !!el);
|
|
629
653
|
return {
|
|
630
654
|
name,
|
|
631
655
|
type: view.type,
|
|
@@ -688,8 +712,12 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
688
712
|
base,
|
|
689
713
|
{
|
|
690
714
|
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
691
|
-
width: toLength(
|
|
692
|
-
|
|
715
|
+
width: toLength(
|
|
716
|
+
interpolate(stage.width === void 0 ? 320 : stage.width, context)
|
|
717
|
+
),
|
|
718
|
+
height: toLength(
|
|
719
|
+
interpolate(stage.height === void 0 ? 400 : stage.height, context)
|
|
720
|
+
)
|
|
693
721
|
},
|
|
694
722
|
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
695
723
|
toCssStyle(interpolateDeep(stage.style, context))
|
|
@@ -744,7 +772,12 @@ function resolveCloseButton(config, layerName, closeTop) {
|
|
|
744
772
|
tag: "img",
|
|
745
773
|
className: "dsl-close-img",
|
|
746
774
|
src: image,
|
|
747
|
-
style: {
|
|
775
|
+
style: {
|
|
776
|
+
width: "100%",
|
|
777
|
+
height: "100%",
|
|
778
|
+
objectFit: "contain",
|
|
779
|
+
display: "block"
|
|
780
|
+
}
|
|
748
781
|
}
|
|
749
782
|
]
|
|
750
783
|
};
|
|
@@ -794,7 +827,14 @@ function resolveNode(node, key, layout, context, input, countdowns) {
|
|
|
794
827
|
className: nodeClass("box", !!onClick),
|
|
795
828
|
style: Object.assign({ position: "relative" }, style),
|
|
796
829
|
onClick,
|
|
797
|
-
children: resolveChildren(
|
|
830
|
+
children: resolveChildren(
|
|
831
|
+
node.children,
|
|
832
|
+
key,
|
|
833
|
+
"absolute",
|
|
834
|
+
context,
|
|
835
|
+
input,
|
|
836
|
+
countdowns
|
|
837
|
+
)
|
|
798
838
|
};
|
|
799
839
|
case "flex":
|
|
800
840
|
return {
|
|
@@ -879,7 +919,14 @@ function resolveRepeat(node, key, context, input, countdowns) {
|
|
|
879
919
|
[node.itemName || "item"]: item,
|
|
880
920
|
[node.indexName || "index"]: index
|
|
881
921
|
});
|
|
882
|
-
return resolveNode(
|
|
922
|
+
return resolveNode(
|
|
923
|
+
node.template,
|
|
924
|
+
`${key}-${index}`,
|
|
925
|
+
"flow",
|
|
926
|
+
itemContext,
|
|
927
|
+
input,
|
|
928
|
+
countdowns
|
|
929
|
+
);
|
|
883
930
|
}).filter((el) => !!el);
|
|
884
931
|
}
|
|
885
932
|
function resolveTabs(node, key, context, input) {
|
|
@@ -921,10 +968,13 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
921
968
|
const parts = computeParts(endTime);
|
|
922
969
|
countdowns.endTimes.push(endTime);
|
|
923
970
|
if (node.precision === "cs") countdowns.precision = "cs";
|
|
924
|
-
if (
|
|
971
|
+
if (endTime && node.onEnd) {
|
|
925
972
|
const token = `${key}@${endTime}`;
|
|
926
|
-
|
|
927
|
-
|
|
973
|
+
const status = input.countdownEnds.get(token);
|
|
974
|
+
if (status === void 0) {
|
|
975
|
+
input.countdownEnds.set(token, parts.ended ? "done" : "waiting");
|
|
976
|
+
} else if (status === "waiting" && parts.ended) {
|
|
977
|
+
input.countdownEnds.set(token, "done");
|
|
928
978
|
input.dispatch(node.onEnd, context);
|
|
929
979
|
}
|
|
930
980
|
}
|
|
@@ -952,7 +1002,14 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
952
1002
|
tag: "div",
|
|
953
1003
|
className: "dsl-node dsl-flex dsl-countdown",
|
|
954
1004
|
style: Object.assign({ display: "flex" }, style),
|
|
955
|
-
children: resolveChildren(
|
|
1005
|
+
children: resolveChildren(
|
|
1006
|
+
node.children,
|
|
1007
|
+
key,
|
|
1008
|
+
"flow",
|
|
1009
|
+
childContext,
|
|
1010
|
+
input,
|
|
1011
|
+
countdowns
|
|
1012
|
+
)
|
|
956
1013
|
};
|
|
957
1014
|
}
|
|
958
1015
|
function toText(value) {
|
|
@@ -963,7 +1020,12 @@ function toText(value) {
|
|
|
963
1020
|
var noopEmit = () => {
|
|
964
1021
|
};
|
|
965
1022
|
function createRuntime(dsl, options = {}) {
|
|
966
|
-
const {
|
|
1023
|
+
const { sources = {}, emit = noopEmit } = options;
|
|
1024
|
+
const live = {
|
|
1025
|
+
user: options.user || {},
|
|
1026
|
+
handlers: options.handlers || {},
|
|
1027
|
+
editMode: !!options.editMode
|
|
1028
|
+
};
|
|
967
1029
|
const normalized = normalizeViews(dsl);
|
|
968
1030
|
let state = Object.assign({}, dsl.state || {});
|
|
969
1031
|
let resolvedData = {};
|
|
@@ -972,14 +1034,17 @@ function createRuntime(dsl, options = {}) {
|
|
|
972
1034
|
let loadFailed = false;
|
|
973
1035
|
let destroyed = false;
|
|
974
1036
|
const listeners = /* @__PURE__ */ new Set();
|
|
975
|
-
const
|
|
1037
|
+
const countdownEnds = /* @__PURE__ */ new Map();
|
|
976
1038
|
const notify = () => {
|
|
977
1039
|
if (destroyed) return;
|
|
978
1040
|
listeners.forEach((fn) => fn());
|
|
979
1041
|
};
|
|
980
1042
|
const ticker = createTicker({ onTick: notify });
|
|
981
1043
|
function buildContext() {
|
|
982
|
-
const context = Object.assign({}, resolvedData, {
|
|
1044
|
+
const context = Object.assign({}, resolvedData, {
|
|
1045
|
+
state,
|
|
1046
|
+
user: live.user
|
|
1047
|
+
});
|
|
983
1048
|
const derived = dsl.derived || {};
|
|
984
1049
|
Object.keys(derived).forEach((name) => {
|
|
985
1050
|
const config = derived[name] || {};
|
|
@@ -1040,8 +1105,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1040
1105
|
// 配置里 close 可以自带 reason(如 'never-remind'),要透传给埋点,不能吞掉
|
|
1041
1106
|
closeTop: (reason) => closeTop(reason),
|
|
1042
1107
|
closeAll: (reason) => closeAll(reason),
|
|
1043
|
-
handlers
|
|
1044
|
-
|
|
1108
|
+
get handlers() {
|
|
1109
|
+
return live.handlers;
|
|
1110
|
+
},
|
|
1111
|
+
get editMode() {
|
|
1112
|
+
return live.editMode;
|
|
1113
|
+
}
|
|
1045
1114
|
});
|
|
1046
1115
|
function fireLifecycle(viewName, hook, extra) {
|
|
1047
1116
|
const view = normalized.views[viewName];
|
|
@@ -1068,7 +1137,7 @@ function createRuntime(dsl, options = {}) {
|
|
|
1068
1137
|
return;
|
|
1069
1138
|
}
|
|
1070
1139
|
pending.push(
|
|
1071
|
-
Promise.resolve(source(ref.params || {}, user)).then((result) => {
|
|
1140
|
+
Promise.resolve(source(ref.params || {}, live.user)).then((result) => {
|
|
1072
1141
|
resolved[key] = result;
|
|
1073
1142
|
})
|
|
1074
1143
|
);
|
|
@@ -1099,13 +1168,19 @@ function createRuntime(dsl, options = {}) {
|
|
|
1099
1168
|
dispatch,
|
|
1100
1169
|
setState,
|
|
1101
1170
|
closeTop,
|
|
1102
|
-
|
|
1171
|
+
countdownEnds
|
|
1103
1172
|
});
|
|
1104
1173
|
ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
|
|
1105
1174
|
return tree;
|
|
1106
1175
|
}
|
|
1107
1176
|
return {
|
|
1108
1177
|
getTree,
|
|
1178
|
+
update(patch) {
|
|
1179
|
+
if (patch.user !== void 0) live.user = patch.user;
|
|
1180
|
+
if (patch.handlers !== void 0) live.handlers = patch.handlers;
|
|
1181
|
+
if (patch.editMode !== void 0) live.editMode = patch.editMode;
|
|
1182
|
+
notify();
|
|
1183
|
+
},
|
|
1109
1184
|
subscribe(listener) {
|
|
1110
1185
|
listeners.add(listener);
|
|
1111
1186
|
return () => listeners.delete(listener);
|
|
@@ -1184,7 +1259,10 @@ function validate(dsl) {
|
|
|
1184
1259
|
}
|
|
1185
1260
|
const doc = dsl;
|
|
1186
1261
|
if (doc.version !== DSL_VERSION) {
|
|
1187
|
-
add(
|
|
1262
|
+
add(
|
|
1263
|
+
"version",
|
|
1264
|
+
`version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`
|
|
1265
|
+
);
|
|
1188
1266
|
}
|
|
1189
1267
|
const dataKeys = validateData(doc.data, add);
|
|
1190
1268
|
const stateKeys = validateState(doc.state, add);
|
|
@@ -1284,7 +1362,10 @@ function validateStage(stage, add, warn, ctx, prefix) {
|
|
|
1284
1362
|
if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
|
|
1285
1363
|
if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
|
|
1286
1364
|
if (stage.height === "auto" && stage.layout !== "flow") {
|
|
1287
|
-
add(
|
|
1365
|
+
add(
|
|
1366
|
+
"stage.height",
|
|
1367
|
+
"height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0"
|
|
1368
|
+
);
|
|
1288
1369
|
}
|
|
1289
1370
|
}
|
|
1290
1371
|
function validateCloseButton(config, add, warn) {
|
|
@@ -1298,7 +1379,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1298
1379
|
}
|
|
1299
1380
|
if (config.offset !== void 0) {
|
|
1300
1381
|
if (!Array.isArray(config.offset) || config.offset.length !== 2) {
|
|
1301
|
-
add(
|
|
1382
|
+
add(
|
|
1383
|
+
"stage.closeButton.offset",
|
|
1384
|
+
"offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762"
|
|
1385
|
+
);
|
|
1302
1386
|
} else {
|
|
1303
1387
|
config.offset.forEach((value, index) => {
|
|
1304
1388
|
if (!checkLength(value)) {
|
|
@@ -1319,7 +1403,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1319
1403
|
if (config.style) {
|
|
1320
1404
|
Object.keys(config.style).forEach((key) => {
|
|
1321
1405
|
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1322
|
-
warn(
|
|
1406
|
+
warn(
|
|
1407
|
+
`stage.closeButton.style.${key}`,
|
|
1408
|
+
`\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`
|
|
1409
|
+
);
|
|
1323
1410
|
}
|
|
1324
1411
|
});
|
|
1325
1412
|
}
|
|
@@ -1390,7 +1477,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1390
1477
|
} else {
|
|
1391
1478
|
node.rect.forEach((value, index) => {
|
|
1392
1479
|
if (value !== void 0 && value !== null && !checkLength(value)) {
|
|
1393
|
-
add(
|
|
1480
|
+
add(
|
|
1481
|
+
`${path}.rect[${index}]`,
|
|
1482
|
+
`"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1483
|
+
);
|
|
1394
1484
|
}
|
|
1395
1485
|
});
|
|
1396
1486
|
}
|
|
@@ -1408,7 +1498,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1408
1498
|
);
|
|
1409
1499
|
}
|
|
1410
1500
|
if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
|
|
1411
|
-
add(
|
|
1501
|
+
add(
|
|
1502
|
+
`${path}.style.aspectRatio`,
|
|
1503
|
+
"aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
|
|
1504
|
+
);
|
|
1412
1505
|
}
|
|
1413
1506
|
});
|
|
1414
1507
|
}
|
|
@@ -1438,13 +1531,21 @@ function validateNodeByType(node, path, ctx) {
|
|
|
1438
1531
|
if (typeof node.bind !== "string") {
|
|
1439
1532
|
add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
|
|
1440
1533
|
}
|
|
1441
|
-
if (typeof node.stateKey !== "string")
|
|
1534
|
+
if (typeof node.stateKey !== "string")
|
|
1535
|
+
add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
|
|
1442
1536
|
if (node.labelField !== void 0 && typeof node.labelField !== "string") {
|
|
1443
1537
|
add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1444
1538
|
}
|
|
1445
1539
|
}
|
|
1446
1540
|
if (node.type === "countdown") {
|
|
1447
|
-
if (!node.to)
|
|
1541
|
+
if (!node.to) {
|
|
1542
|
+
add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
|
|
1543
|
+
} else if (!isValidEndTime(node.to)) {
|
|
1544
|
+
add(
|
|
1545
|
+
`${path}.to`,
|
|
1546
|
+
`"${node.to}" \u4E0D\u662F\u80FD\u8BC6\u522B\u7684\u65F6\u95F4\u3002\u53EF\u4EE5\u5199 "2026-09-01 18:00:00" / "2026-09-01" / "2026\u5E749\u67081\u65E5 18\u70B9" / ISO \u683C\u5F0F / \u65F6\u95F4\u6233`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1448
1549
|
if (node.precision !== void 0 && ["s", "cs"].indexOf(node.precision) === -1) {
|
|
1449
1550
|
add(`${path}.precision`, "precision \u53EA\u80FD\u662F 's'\uFF08\u6BCF\u79D2\uFF09\u6216 'cs'\uFF08\u5398\u79D2\uFF0C\u9010\u5E27\u5237\u65B0\uFF09");
|
|
1450
1551
|
}
|
|
@@ -1504,7 +1605,10 @@ function validateAction(action, path, ctx) {
|
|
|
1504
1605
|
add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
|
|
1505
1606
|
}
|
|
1506
1607
|
if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
|
|
1507
|
-
add(
|
|
1608
|
+
add(
|
|
1609
|
+
`${path}.mode`,
|
|
1610
|
+
"mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09"
|
|
1611
|
+
);
|
|
1508
1612
|
}
|
|
1509
1613
|
}
|
|
1510
1614
|
if (action.type === "sequence") {
|
|
@@ -1521,4 +1625,4 @@ function formatIssues(issues) {
|
|
|
1521
1625
|
return issues.map((item) => item.path ? `${item.path}: ${item.message}` : item.message).join("\n");
|
|
1522
1626
|
}
|
|
1523
1627
|
|
|
1524
|
-
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
|
1628
|
+
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|