@yaoxiu/marketing-dsl 1.5.2 → 2.1.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeViews, createTicker, interpolateDeep, interpolate, evaluate, resolveNodeStyle, toLength, toCssStyle, parseEndTime, computeParts, formatParts } from './chunk-FKZKLCVV.js';
2
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, toCssStyle, toLength, validate } from './chunk-FKZKLCVV.js';
1
+ import { normalizeViews, createTicker, evaluate, resolveNodeStyle, interpolateDeep, interpolate, toLength, toCssStyle, parseEndTime, computeParts, formatParts } from './chunk-7TWWOK7Q.js';
2
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, toCssStyle, toLength, validate, validateUserFields } from './chunk-7TWWOK7Q.js';
3
3
 
4
4
  // src/url.ts
5
5
  var SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
@@ -22,13 +22,13 @@ function safeImageUrl(input) {
22
22
  // src/actions.ts
23
23
  function createDispatcher(options) {
24
24
  const { setState, emit, openView, closeTop, closeAll } = options;
25
- function dispatch(action, context) {
25
+ function run(action, context, interaction) {
26
26
  if (!action || !action.type) return;
27
27
  const handlers = options.handlers || {};
28
28
  const editMode = !!options.editMode;
29
29
  switch (action.type) {
30
30
  case "sequence":
31
- (action.actions || []).forEach((item) => dispatch(item, context));
31
+ (action.actions || []).forEach((item) => run(item, context, interaction));
32
32
  return;
33
33
  case "navigate": {
34
34
  const url = safeUrl(interpolate(action.url, context));
@@ -38,6 +38,7 @@ function createDispatcher(options) {
38
38
  }
39
39
  const target = action.target || "_blank";
40
40
  emit("navigate", { url, target });
41
+ if (!interaction.navigate) interaction.navigate = { url, target };
41
42
  if (editMode) return;
42
43
  if (typeof window === "undefined") return;
43
44
  if (target === "_self") window.location.href = url;
@@ -51,20 +52,25 @@ function createDispatcher(options) {
51
52
  return;
52
53
  // 关掉栈顶那层;只剩一层时就是整体关闭
53
54
  case "close":
55
+ interaction.closes = true;
54
56
  closeTop(action.reason);
55
57
  return;
56
58
  case "closeAll":
59
+ interaction.closes = true;
57
60
  closeAll(action.reason);
58
61
  return;
59
62
  case "setState":
60
63
  setState(action.key, interpolate(action.value, context));
61
64
  return;
62
- case "track":
63
- emit("track", {
65
+ case "track": {
66
+ const payload = {
64
67
  event: String(interpolate(action.event, context)),
65
68
  params: interpolateDeep(action.params || {}, context)
66
- });
69
+ };
70
+ emit("track", payload);
71
+ if (!interaction.track) interaction.track = payload;
67
72
  return;
73
+ }
68
74
  case "call": {
69
75
  const handler = handlers[action.name];
70
76
  if (typeof handler !== "function") {
@@ -84,6 +90,13 @@ function createDispatcher(options) {
84
90
  });
85
91
  }
86
92
  }
93
+ function dispatch(action, context, meta) {
94
+ if (!action || !action.type) return;
95
+ const interaction = { trigger: (meta == null ? void 0 : meta.trigger) || "action" };
96
+ if ((meta == null ? void 0 : meta.reason) !== void 0) interaction.reason = meta.reason;
97
+ run(action, context, interaction);
98
+ emit("interaction", interaction);
99
+ }
87
100
  return dispatch;
88
101
  }
89
102
 
@@ -91,9 +104,7 @@ function createDispatcher(options) {
91
104
  function resolveTree(input) {
92
105
  const { normalized, viewStack, ready } = input;
93
106
  const layers = viewStack.map((name) => ({ name, view: normalized.views[name] })).filter((item) => !!item.view);
94
- const hasPopup = Object.keys(normalized.views).some(
95
- (name) => normalized.views[name].type === "popup"
96
- );
107
+ const hasPopup = layers.some((item) => item.view.type === "popup");
97
108
  const countdowns = { endTimes: [], precision: "s" };
98
109
  const renderLayers = ready ? layers.map(
99
110
  (item, index) => resolveLayer(
@@ -107,8 +118,10 @@ function resolveTree(input) {
107
118
  return {
108
119
  ready: ready && layers.length > 0,
109
120
  hasPopup,
110
- // 配置里有弹窗视图时,根容器要撑满宿主容器,弹窗层才有地方铺
111
- rootStyle: hasPopup ? { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" } : { position: "relative" },
121
+ // 根容器恒为 relative:它要待在宿主给的位置上(banner / 公告坑位是流式的普通 div,
122
+ // 根容器一脱流坑位就塌成 0、内容按最近定位祖先飘走)。弹窗铺满视口由弹窗层自己
123
+ // `position: fixed` 完成,不再靠根容器撑开,见 layerStyle。
124
+ rootStyle: { position: "relative" },
112
125
  layers: renderLayers,
113
126
  countdownEndTimes: countdowns.endTimes,
114
127
  countdownPrecision: countdowns.precision
@@ -154,7 +167,15 @@ function resolveLayer(name, view, isTop, input, countdowns) {
154
167
  };
155
168
  }
156
169
  function layerStyle(isPopup, isTop) {
157
- const style = isPopup ? { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" } : {};
170
+ const style = isPopup ? {
171
+ position: "fixed",
172
+ top: "0",
173
+ right: "0",
174
+ bottom: "0",
175
+ left: "0",
176
+ // 弹窗层可能挂在页面中段的坑位里,不提层会被后面的内容盖住
177
+ zIndex: "1000"
178
+ } : {};
158
179
  if (!isTop) style.pointerEvents = "none";
159
180
  return style;
160
181
  }
@@ -182,7 +203,7 @@ function resolveStageStyle(stage, isPopup, context) {
182
203
  base.margin = "auto";
183
204
  base.flex = "none";
184
205
  }
185
- return Object.assign(
206
+ const style = Object.assign(
186
207
  base,
187
208
  {
188
209
  // width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
@@ -196,6 +217,11 @@ function resolveStageStyle(stage, isPopup, context) {
196
217
  // stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
197
218
  toCssStyle(interpolateDeep(stage.style, context))
198
219
  );
220
+ if (!isPopup && style.maxWidth !== void 0 && style.margin === void 0) {
221
+ style.marginLeft = "auto";
222
+ style.marginRight = "auto";
223
+ }
224
+ return style;
199
225
  }
200
226
  function resolveCloseButton(config, layerName, closeTop) {
201
227
  if (!config || config.show === false) return void 0;
@@ -659,7 +685,11 @@ function createRuntime(dsl, options = {}) {
659
685
  const view = normalized.views[viewName];
660
686
  const action = view && view.stage && view.stage[hook];
661
687
  if (!action) return;
662
- dispatch(action, Object.assign({}, buildContext(), extra));
688
+ const reason = extra && extra.closeReason;
689
+ dispatch(action, Object.assign({}, buildContext(), extra), {
690
+ trigger: hook === "onShow" ? "show" : "close",
691
+ reason: typeof reason === "string" ? reason : void 0
692
+ });
663
693
  }
664
694
  function loadData() {
665
695
  const data = dsl.data || {};
@@ -690,8 +720,8 @@ function createRuntime(dsl, options = {}) {
690
720
  resolvedData = resolved;
691
721
  loading = false;
692
722
  notify();
693
- emit("ready", { keys: Object.keys(resolved) });
694
723
  fireLifecycle(normalized.entry, "onShow");
724
+ emit("ready", { keys: Object.keys(resolved) });
695
725
  }).catch((error) => {
696
726
  if (destroyed) return;
697
727
  loading = false;
@@ -1,41 +1,58 @@
1
1
  'use strict';
2
2
 
3
3
  // src/report/map.ts
4
- function buildExposureReport(id) {
4
+ function pickInteractionTrack(payload) {
5
+ const track = payload == null ? void 0 : payload.track;
6
+ if (!track) return void 0;
7
+ return { name: track.event, params: track.params };
8
+ }
9
+ function buildExposureReport(id, extra) {
5
10
  if (!id) return null;
6
- return { id, event: "exposure" };
11
+ const params = { id, event: "exposure" };
12
+ if (extra && (extra.name !== void 0 || extra.params !== void 0)) {
13
+ params.extra = extra;
14
+ }
15
+ return params;
7
16
  }
8
- function mapRuntimeEventToReport(id, event, payload) {
17
+ function buildCloseReport(id, options) {
9
18
  if (!id) return null;
10
- switch (event) {
11
- case "track": {
12
- const data = payload != null ? payload : {};
13
- return {
14
- id,
15
- event: "click",
16
- extra: { trigger: "track", name: data.event, params: data.params }
17
- };
18
- }
19
- case "navigate": {
20
- const data = payload != null ? payload : {};
21
- return {
22
- id,
23
- event: "click",
24
- extra: { trigger: "navigate", url: data.url }
25
- };
26
- }
27
- case "close": {
28
- const data = payload != null ? payload : {};
29
- return {
30
- id,
31
- event: "close",
32
- reason: data.reason
33
- };
34
- }
35
- default:
36
- return null;
19
+ const params = {
20
+ id,
21
+ event: "close",
22
+ reason: options == null ? void 0 : options.reason
23
+ };
24
+ const extra = options == null ? void 0 : options.extra;
25
+ if (extra && (extra.name !== void 0 || extra.params !== void 0)) {
26
+ params.extra = extra;
27
+ }
28
+ return params;
29
+ }
30
+ function buildClickExtra(payload) {
31
+ const { track, navigate } = payload;
32
+ if (track) {
33
+ const extra = {
34
+ trigger: "track",
35
+ name: track.event,
36
+ params: track.params
37
+ };
38
+ if (navigate == null ? void 0 : navigate.url) extra.url = navigate.url;
39
+ return extra;
37
40
  }
41
+ if (navigate) return { trigger: "navigate", url: navigate.url };
42
+ return null;
43
+ }
44
+ function mapRuntimeEventToReport(id, event, payload) {
45
+ if (!id) return null;
46
+ if (event !== "interaction") return null;
47
+ const data = payload != null ? payload : {};
48
+ if (data.trigger !== "action") return null;
49
+ if (data.closes) return null;
50
+ const extra = buildClickExtra(data);
51
+ if (!extra) return null;
52
+ return { id, event: "click", extra };
38
53
  }
39
54
 
55
+ exports.buildCloseReport = buildCloseReport;
40
56
  exports.buildExposureReport = buildExposureReport;
41
57
  exports.mapRuntimeEventToReport = mapRuntimeEventToReport;
58
+ exports.pickInteractionTrack = pickInteractionTrack;
@@ -1,4 +1,4 @@
1
- import { E as RuntimeEventName } from '../types-BgUaJUAu.cjs';
1
+ import { G as RuntimeEventName, E as DslInteractionEvent } from '../types-Bj0xyWnx.cjs';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。
@@ -19,23 +19,39 @@ import { E as RuntimeEventName } from '../types-BgUaJUAu.cjs';
19
19
  * - `close` 关闭:用户关掉了这条物料
20
20
  */
21
21
  type MarketingReportEvent = 'exposure' | 'click' | 'close';
22
+ /**
23
+ * 运营配的埋点信息(`track` 动作的事件名与参数)
24
+ *
25
+ * 挂在 `stage.onShow` / `stage.onClose` 上的 `track` 属于「曝光埋点 / 关闭埋点」,
26
+ * **不算点击**,所以它们进的是 `exposure` / `close` 的 `extra`,而不是产出一条 `click`。
27
+ * 早期实现把这类埋点名一律映射成 `click`,直接把点击量放大了数倍。
28
+ */
29
+ interface MarketingTrackExtra {
30
+ /** 运营在配置里写的埋点名 */
31
+ name?: string;
32
+ /** 运营在配置里写的自定义参数,原样透传 */
33
+ params?: Record<string, unknown>;
34
+ }
22
35
  /**
23
36
  * 点击上报的附加信息(**只在 `event='click'` 时有值**)
24
37
  *
25
38
  * 运营在 DSL 里自定义的埋点名 / 参数一律放这里,
26
39
  * **不要塞进 `reason`** —— `reason` 的语义被「关闭原因」独占。
40
+ *
41
+ * 一次点击里同时配了埋点和跳转(`sequence: [track, navigate]`,最常见的写法)时,
42
+ * 只产出**一条** click:`trigger='track'`,跳转地址并进 `url`。
27
43
  */
28
44
  interface MarketingClickExtra {
29
45
  /** 点击来源:`track` = 运营配的埋点动作;`navigate` = 跳转动作 */
30
46
  trigger: 'track' | 'navigate';
31
47
  /** `trigger='track'` 时:运营在配置里写的埋点名 */
32
48
  name?: string;
33
- /** `trigger='navigate'` 时:跳转目标地址 */
49
+ /** 跳转目标地址:`trigger='navigate'` 时必有;`trigger='track'` 时若同一次点击里还配了跳转也会带上 */
34
50
  url?: string;
35
51
  /** 运营在配置里写的自定义参数,原样透传 */
36
52
  params?: Record<string, unknown>;
37
53
  }
38
- /** 曝光上报入参:只有 id */
54
+ /** 曝光上报入参:运营在 `stage.onShow` 里配了埋点时带上 `extra` */
39
55
  interface MarketingExposureReportParams {
40
56
  /** 物料 id,取自 `MarketingMaterial.id` */
41
57
  id: string;
@@ -43,8 +59,14 @@ interface MarketingExposureReportParams {
43
59
  event: 'exposure';
44
60
  /** 曝光事件无关闭原因 */
45
61
  reason?: never;
46
- /** 曝光事件无点击附加信息 */
47
- extra?: never;
62
+ /**
63
+ * 运营配在 `stage.onShow` 里的埋点名 / 参数,没配则不带。
64
+ *
65
+ * 【为什么并进曝光而不是单独上报】那条 `track` 记的就是「这条物料露出了」,
66
+ * 与曝光是同一件事,单独上报会与前台统一发的 exposure 重复计数;
67
+ * 但也不能直接丢掉 —— 运营配了就该在后端看得到。
68
+ */
69
+ extra?: MarketingTrackExtra;
48
70
  }
49
71
  /** 关闭上报入参:可带关闭原因 */
50
72
  interface MarketingCloseReportParams {
@@ -58,8 +80,8 @@ interface MarketingCloseReportParams {
58
80
  * `action`(配置里 close 动作未写 reason 的默认值)/ 运营自定义值。
59
81
  */
60
82
  reason?: string;
61
- /** 关闭事件无点击附加信息 */
62
- extra?: never;
83
+ /** 运营配在 `stage.onClose` 里的埋点名 / 参数,没配则不带 */
84
+ extra?: MarketingTrackExtra;
63
85
  }
64
86
  /** 点击上报入参:必须带 extra 说明点了什么 */
65
87
  interface MarketingClickReportParams {
@@ -76,7 +98,10 @@ interface MarketingClickReportParams {
76
98
  * 上报物料事件的入参(可辨识联合,按 `event` 区分)
77
99
  *
78
100
  * 用联合而非「三个可选字段」是为了把契约写进类型里:`reason` 只配 `close`、
79
- * `extra` 只配 `click`,写错直接编译不过。不该出现的字段声明成 `?: never`
101
+ * 点击详情(`MarketingClickExtra`,带 `trigger`)只配 `click`,写错直接编译不过。
102
+ * `exposure` / `close` 的 `extra` 是另一种形态(`MarketingTrackExtra`,只有 `name` / `params`),
103
+ * 装的是运营配在 `stage.onShow` / `onClose` 里的埋点名 —— 它们不是点击,混不进 click 那一支。
104
+ * 不该出现的字段声明成 `?: never`
80
105
  * 而不是省略,这样消费方(formatter / 上报层 / 调试台预览)仍能直接读
81
106
  * `params.reason`、`params.extra`,无需先做类型窄化 —— 约束留在调用侧,消费侧照旧好写。
82
107
  */
@@ -88,39 +113,44 @@ type MarketingReportParams = MarketingExposureReportParams | MarketingCloseRepor
88
113
  * 前台坑位组件与后台调试台的「上报预览」共用这一份:
89
114
  * 调试台要展示的就是**真实会发给后端的 JSON**,映射有第二份实现,
90
115
  * 前台改了口径而调试台忘了改,调试台就会骗人。
116
+ *
117
+ * 【只认 `interaction`,不认 `track` / `navigate`】
118
+ * 动作粒度的事件不能直接当上报口径:`sequence: [track, navigate]`(先埋点再跳转)
119
+ * 是最常见的写法,两个动作各报一条 → 点击量翻倍;而 `track` 还能挂在
120
+ * `stage.onShow` / `onClose` 上,动作粒度看不出时机 → 曝光和关闭也被算成点击。
121
+ * 解释器为此在**交互粒度**上多抛了一条 `interaction`(一次手势 / 一个时机一条),
122
+ * 上报只认它。
91
123
  */
92
124
 
93
125
  /**
94
126
  * 会产生上报的运行时事件及其载荷。
95
127
  *
96
- * 字段一律可选:宿主(Vue 组件的 `$event`、调试台的手工构造)拿到的载荷未必齐全,
128
+ * 字段一律可选(`Partial`):宿主(Vue 组件的 `$event`、调试台的手工构造)拿到的载荷未必齐全,
97
129
  * 缺字段是允许的,不该在映射这一步炸掉。
98
130
  */
99
131
  interface MarketingReportableEventPayloads {
100
- /** 埋点动作:`event` 是运营配的埋点名,`params` 是自定义参数 */
101
- track: {
102
- event?: string;
103
- params?: Record<string, unknown>;
104
- };
105
- /** 跳转动作:`url` 是目标地址(跳转本身由渲染壳完成,这里只记一笔) */
106
- navigate: {
107
- url?: string;
108
- target?: string;
109
- };
110
- /** 关闭:`reason` 是关闭原因 */
111
- close: {
112
- reason?: string;
113
- };
132
+ /** 一次交互的汇总事件,见 {@link DslInteractionEvent} */
133
+ interaction: Partial<DslInteractionEvent>;
114
134
  }
115
135
  /** 会产生上报的运行时事件名 */
116
136
  type MarketingReportableEventName = keyof MarketingReportableEventPayloads;
117
137
  /**
118
138
  * 事件名 → 载荷类型。
119
139
  *
120
- * 不产生上报的事件(`call` / `error` / `state-change` / `view-change` / `ready` / `open`)
121
- * 载荷为 `unknown`:调用方照常传,映射返回 `null`。
140
+ * 不产生上报的事件(`track` / `navigate` / `close` / `call` / `error` / `state-change` /
141
+ * `view-change` / `ready` / `open`)载荷为 `unknown`:调用方照常传,映射返回 `null`。
122
142
  */
123
143
  type MarketingRuntimeEventPayload<K extends RuntimeEventName> = K extends MarketingReportableEventName ? MarketingReportableEventPayloads[K] : unknown;
144
+ /**
145
+ * 把交互事件里的埋点动作转成上报用的 `extra`(字段名 `event` → `name`)。
146
+ *
147
+ * 给宿主组装曝光 / 关闭上报用:`trigger='show'` / `'close'` 的 interaction 不产出点击,
148
+ * 但运营配的埋点名不能凭空消失,由宿主并进 exposure / close 的 `extra`。
149
+ *
150
+ * @param payload interaction 事件载荷
151
+ * @returns 埋点信息;没配埋点时返回 `undefined`
152
+ */
153
+ declare function pickInteractionTrack(payload?: Partial<DslInteractionEvent> | null): MarketingTrackExtra | undefined;
124
154
  /**
125
155
  * 曝光上报载荷。
126
156
  *
@@ -128,17 +158,36 @@ type MarketingRuntimeEventPayload<K extends RuntimeEventName> = K extends Market
128
158
  * 解释器根本不知道自己被画出来了。
129
159
  *
130
160
  * @param id 物料 id,为空(未取到物料)时返回 `null`,不产生上报
161
+ * @param extra 运营配在 `stage.onShow` 里的埋点信息,可用 {@link pickInteractionTrack} 从
162
+ * `trigger='show'` 的 interaction 事件里取;没有则不带
163
+ * @returns 上报载荷,或 `null`
164
+ */
165
+ declare function buildExposureReport(id: string | undefined | null, extra?: MarketingTrackExtra | null): MarketingExposureReportParams | null;
166
+ /**
167
+ * 关闭上报载荷。
168
+ *
169
+ * 与曝光同理由:关闭由宿主统一上报一条 —— `close` 运行时事件才是「这条物料真的关了」,
170
+ * 运营配不配 `stage.onClose` 都得有这条。
171
+ *
172
+ * @param id 物料 id,为空时返回 `null`
173
+ * @param options `reason` 取自 `close` 运行时事件;`extra` 取自 `trigger='close'` 的 interaction
131
174
  * @returns 上报载荷,或 `null`
132
175
  */
133
- declare function buildExposureReport(id: string | undefined | null): MarketingExposureReportParams | null;
176
+ declare function buildCloseReport(id: string | undefined | null, options?: {
177
+ reason?: string;
178
+ extra?: MarketingTrackExtra | null;
179
+ }): MarketingCloseReportParams | null;
134
180
  /**
135
181
  * 把一个 DSL 运行时事件映射成上报载荷。
136
182
  *
137
183
  * 口径(与 `docs/marketing/README.md` 的契约一一对应):
138
- * - `track` → `click`,`extra = { trigger: 'track', name, params }`
139
- * - `navigate` → `click`,`extra = { trigger: 'navigate', url }`
140
- * - `close` → `close`,带 `reason`
141
- * - 其余事件(`call` / `error` / `state-change` / `view-change` / `ready` / `open`)不产生上报
184
+ * - `interaction` 且 `trigger='action'` → 一条 `click`,`extra` 见 {@link buildClickExtra}
185
+ * - `interaction` 且 `trigger='show'` / `'close'` → `null`:曝光 / 关闭由宿主统一上报,
186
+ * 运营配在这两个时机上的埋点名走 {@link buildExposureReport} / {@link buildCloseReport} 的 `extra`
187
+ * - `interaction` 且 `trigger='action'` 但 `closes` 为真(手势里执行了关闭动作)→ `null`:
188
+ * 点关闭按钮的本质是关闭,不再算一次点击
189
+ * - 其余事件(`track` / `navigate` / `close` / `call` / `error` / `state-change` /
190
+ * `view-change` / `ready` / `open`)不产生上报 —— 它们是动作粒度的事件,按它们上报会重复计数
142
191
  *
143
192
  * @param id 物料 id,为空时返回 `null`(拿不到 id 的上报对后端没有意义)
144
193
  * @param event 运行时事件名
@@ -147,4 +196,4 @@ declare function buildExposureReport(id: string | undefined | null): MarketingEx
147
196
  */
148
197
  declare function mapRuntimeEventToReport<K extends RuntimeEventName>(id: string | undefined | null, event: K, payload?: MarketingRuntimeEventPayload<K>): MarketingReportParams | null;
149
198
 
150
- export { type MarketingClickExtra, type MarketingClickReportParams, type MarketingCloseReportParams, type MarketingExposureReportParams, type MarketingReportEvent, type MarketingReportParams, type MarketingReportableEventName, type MarketingReportableEventPayloads, type MarketingRuntimeEventPayload, buildExposureReport, mapRuntimeEventToReport };
199
+ export { type MarketingClickExtra, type MarketingClickReportParams, type MarketingCloseReportParams, type MarketingExposureReportParams, type MarketingReportEvent, type MarketingReportParams, type MarketingReportableEventName, type MarketingReportableEventPayloads, type MarketingRuntimeEventPayload, type MarketingTrackExtra, buildCloseReport, buildExposureReport, mapRuntimeEventToReport, pickInteractionTrack };
@@ -1,4 +1,4 @@
1
- import { E as RuntimeEventName } from '../types-BgUaJUAu.js';
1
+ import { G as RuntimeEventName, E as DslInteractionEvent } from '../types-Bj0xyWnx.js';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。
@@ -19,23 +19,39 @@ import { E as RuntimeEventName } from '../types-BgUaJUAu.js';
19
19
  * - `close` 关闭:用户关掉了这条物料
20
20
  */
21
21
  type MarketingReportEvent = 'exposure' | 'click' | 'close';
22
+ /**
23
+ * 运营配的埋点信息(`track` 动作的事件名与参数)
24
+ *
25
+ * 挂在 `stage.onShow` / `stage.onClose` 上的 `track` 属于「曝光埋点 / 关闭埋点」,
26
+ * **不算点击**,所以它们进的是 `exposure` / `close` 的 `extra`,而不是产出一条 `click`。
27
+ * 早期实现把这类埋点名一律映射成 `click`,直接把点击量放大了数倍。
28
+ */
29
+ interface MarketingTrackExtra {
30
+ /** 运营在配置里写的埋点名 */
31
+ name?: string;
32
+ /** 运营在配置里写的自定义参数,原样透传 */
33
+ params?: Record<string, unknown>;
34
+ }
22
35
  /**
23
36
  * 点击上报的附加信息(**只在 `event='click'` 时有值**)
24
37
  *
25
38
  * 运营在 DSL 里自定义的埋点名 / 参数一律放这里,
26
39
  * **不要塞进 `reason`** —— `reason` 的语义被「关闭原因」独占。
40
+ *
41
+ * 一次点击里同时配了埋点和跳转(`sequence: [track, navigate]`,最常见的写法)时,
42
+ * 只产出**一条** click:`trigger='track'`,跳转地址并进 `url`。
27
43
  */
28
44
  interface MarketingClickExtra {
29
45
  /** 点击来源:`track` = 运营配的埋点动作;`navigate` = 跳转动作 */
30
46
  trigger: 'track' | 'navigate';
31
47
  /** `trigger='track'` 时:运营在配置里写的埋点名 */
32
48
  name?: string;
33
- /** `trigger='navigate'` 时:跳转目标地址 */
49
+ /** 跳转目标地址:`trigger='navigate'` 时必有;`trigger='track'` 时若同一次点击里还配了跳转也会带上 */
34
50
  url?: string;
35
51
  /** 运营在配置里写的自定义参数,原样透传 */
36
52
  params?: Record<string, unknown>;
37
53
  }
38
- /** 曝光上报入参:只有 id */
54
+ /** 曝光上报入参:运营在 `stage.onShow` 里配了埋点时带上 `extra` */
39
55
  interface MarketingExposureReportParams {
40
56
  /** 物料 id,取自 `MarketingMaterial.id` */
41
57
  id: string;
@@ -43,8 +59,14 @@ interface MarketingExposureReportParams {
43
59
  event: 'exposure';
44
60
  /** 曝光事件无关闭原因 */
45
61
  reason?: never;
46
- /** 曝光事件无点击附加信息 */
47
- extra?: never;
62
+ /**
63
+ * 运营配在 `stage.onShow` 里的埋点名 / 参数,没配则不带。
64
+ *
65
+ * 【为什么并进曝光而不是单独上报】那条 `track` 记的就是「这条物料露出了」,
66
+ * 与曝光是同一件事,单独上报会与前台统一发的 exposure 重复计数;
67
+ * 但也不能直接丢掉 —— 运营配了就该在后端看得到。
68
+ */
69
+ extra?: MarketingTrackExtra;
48
70
  }
49
71
  /** 关闭上报入参:可带关闭原因 */
50
72
  interface MarketingCloseReportParams {
@@ -58,8 +80,8 @@ interface MarketingCloseReportParams {
58
80
  * `action`(配置里 close 动作未写 reason 的默认值)/ 运营自定义值。
59
81
  */
60
82
  reason?: string;
61
- /** 关闭事件无点击附加信息 */
62
- extra?: never;
83
+ /** 运营配在 `stage.onClose` 里的埋点名 / 参数,没配则不带 */
84
+ extra?: MarketingTrackExtra;
63
85
  }
64
86
  /** 点击上报入参:必须带 extra 说明点了什么 */
65
87
  interface MarketingClickReportParams {
@@ -76,7 +98,10 @@ interface MarketingClickReportParams {
76
98
  * 上报物料事件的入参(可辨识联合,按 `event` 区分)
77
99
  *
78
100
  * 用联合而非「三个可选字段」是为了把契约写进类型里:`reason` 只配 `close`、
79
- * `extra` 只配 `click`,写错直接编译不过。不该出现的字段声明成 `?: never`
101
+ * 点击详情(`MarketingClickExtra`,带 `trigger`)只配 `click`,写错直接编译不过。
102
+ * `exposure` / `close` 的 `extra` 是另一种形态(`MarketingTrackExtra`,只有 `name` / `params`),
103
+ * 装的是运营配在 `stage.onShow` / `onClose` 里的埋点名 —— 它们不是点击,混不进 click 那一支。
104
+ * 不该出现的字段声明成 `?: never`
80
105
  * 而不是省略,这样消费方(formatter / 上报层 / 调试台预览)仍能直接读
81
106
  * `params.reason`、`params.extra`,无需先做类型窄化 —— 约束留在调用侧,消费侧照旧好写。
82
107
  */
@@ -88,39 +113,44 @@ type MarketingReportParams = MarketingExposureReportParams | MarketingCloseRepor
88
113
  * 前台坑位组件与后台调试台的「上报预览」共用这一份:
89
114
  * 调试台要展示的就是**真实会发给后端的 JSON**,映射有第二份实现,
90
115
  * 前台改了口径而调试台忘了改,调试台就会骗人。
116
+ *
117
+ * 【只认 `interaction`,不认 `track` / `navigate`】
118
+ * 动作粒度的事件不能直接当上报口径:`sequence: [track, navigate]`(先埋点再跳转)
119
+ * 是最常见的写法,两个动作各报一条 → 点击量翻倍;而 `track` 还能挂在
120
+ * `stage.onShow` / `onClose` 上,动作粒度看不出时机 → 曝光和关闭也被算成点击。
121
+ * 解释器为此在**交互粒度**上多抛了一条 `interaction`(一次手势 / 一个时机一条),
122
+ * 上报只认它。
91
123
  */
92
124
 
93
125
  /**
94
126
  * 会产生上报的运行时事件及其载荷。
95
127
  *
96
- * 字段一律可选:宿主(Vue 组件的 `$event`、调试台的手工构造)拿到的载荷未必齐全,
128
+ * 字段一律可选(`Partial`):宿主(Vue 组件的 `$event`、调试台的手工构造)拿到的载荷未必齐全,
97
129
  * 缺字段是允许的,不该在映射这一步炸掉。
98
130
  */
99
131
  interface MarketingReportableEventPayloads {
100
- /** 埋点动作:`event` 是运营配的埋点名,`params` 是自定义参数 */
101
- track: {
102
- event?: string;
103
- params?: Record<string, unknown>;
104
- };
105
- /** 跳转动作:`url` 是目标地址(跳转本身由渲染壳完成,这里只记一笔) */
106
- navigate: {
107
- url?: string;
108
- target?: string;
109
- };
110
- /** 关闭:`reason` 是关闭原因 */
111
- close: {
112
- reason?: string;
113
- };
132
+ /** 一次交互的汇总事件,见 {@link DslInteractionEvent} */
133
+ interaction: Partial<DslInteractionEvent>;
114
134
  }
115
135
  /** 会产生上报的运行时事件名 */
116
136
  type MarketingReportableEventName = keyof MarketingReportableEventPayloads;
117
137
  /**
118
138
  * 事件名 → 载荷类型。
119
139
  *
120
- * 不产生上报的事件(`call` / `error` / `state-change` / `view-change` / `ready` / `open`)
121
- * 载荷为 `unknown`:调用方照常传,映射返回 `null`。
140
+ * 不产生上报的事件(`track` / `navigate` / `close` / `call` / `error` / `state-change` /
141
+ * `view-change` / `ready` / `open`)载荷为 `unknown`:调用方照常传,映射返回 `null`。
122
142
  */
123
143
  type MarketingRuntimeEventPayload<K extends RuntimeEventName> = K extends MarketingReportableEventName ? MarketingReportableEventPayloads[K] : unknown;
144
+ /**
145
+ * 把交互事件里的埋点动作转成上报用的 `extra`(字段名 `event` → `name`)。
146
+ *
147
+ * 给宿主组装曝光 / 关闭上报用:`trigger='show'` / `'close'` 的 interaction 不产出点击,
148
+ * 但运营配的埋点名不能凭空消失,由宿主并进 exposure / close 的 `extra`。
149
+ *
150
+ * @param payload interaction 事件载荷
151
+ * @returns 埋点信息;没配埋点时返回 `undefined`
152
+ */
153
+ declare function pickInteractionTrack(payload?: Partial<DslInteractionEvent> | null): MarketingTrackExtra | undefined;
124
154
  /**
125
155
  * 曝光上报载荷。
126
156
  *
@@ -128,17 +158,36 @@ type MarketingRuntimeEventPayload<K extends RuntimeEventName> = K extends Market
128
158
  * 解释器根本不知道自己被画出来了。
129
159
  *
130
160
  * @param id 物料 id,为空(未取到物料)时返回 `null`,不产生上报
161
+ * @param extra 运营配在 `stage.onShow` 里的埋点信息,可用 {@link pickInteractionTrack} 从
162
+ * `trigger='show'` 的 interaction 事件里取;没有则不带
163
+ * @returns 上报载荷,或 `null`
164
+ */
165
+ declare function buildExposureReport(id: string | undefined | null, extra?: MarketingTrackExtra | null): MarketingExposureReportParams | null;
166
+ /**
167
+ * 关闭上报载荷。
168
+ *
169
+ * 与曝光同理由:关闭由宿主统一上报一条 —— `close` 运行时事件才是「这条物料真的关了」,
170
+ * 运营配不配 `stage.onClose` 都得有这条。
171
+ *
172
+ * @param id 物料 id,为空时返回 `null`
173
+ * @param options `reason` 取自 `close` 运行时事件;`extra` 取自 `trigger='close'` 的 interaction
131
174
  * @returns 上报载荷,或 `null`
132
175
  */
133
- declare function buildExposureReport(id: string | undefined | null): MarketingExposureReportParams | null;
176
+ declare function buildCloseReport(id: string | undefined | null, options?: {
177
+ reason?: string;
178
+ extra?: MarketingTrackExtra | null;
179
+ }): MarketingCloseReportParams | null;
134
180
  /**
135
181
  * 把一个 DSL 运行时事件映射成上报载荷。
136
182
  *
137
183
  * 口径(与 `docs/marketing/README.md` 的契约一一对应):
138
- * - `track` → `click`,`extra = { trigger: 'track', name, params }`
139
- * - `navigate` → `click`,`extra = { trigger: 'navigate', url }`
140
- * - `close` → `close`,带 `reason`
141
- * - 其余事件(`call` / `error` / `state-change` / `view-change` / `ready` / `open`)不产生上报
184
+ * - `interaction` 且 `trigger='action'` → 一条 `click`,`extra` 见 {@link buildClickExtra}
185
+ * - `interaction` 且 `trigger='show'` / `'close'` → `null`:曝光 / 关闭由宿主统一上报,
186
+ * 运营配在这两个时机上的埋点名走 {@link buildExposureReport} / {@link buildCloseReport} 的 `extra`
187
+ * - `interaction` 且 `trigger='action'` 但 `closes` 为真(手势里执行了关闭动作)→ `null`:
188
+ * 点关闭按钮的本质是关闭,不再算一次点击
189
+ * - 其余事件(`track` / `navigate` / `close` / `call` / `error` / `state-change` /
190
+ * `view-change` / `ready` / `open`)不产生上报 —— 它们是动作粒度的事件,按它们上报会重复计数
142
191
  *
143
192
  * @param id 物料 id,为空时返回 `null`(拿不到 id 的上报对后端没有意义)
144
193
  * @param event 运行时事件名
@@ -147,4 +196,4 @@ declare function buildExposureReport(id: string | undefined | null): MarketingEx
147
196
  */
148
197
  declare function mapRuntimeEventToReport<K extends RuntimeEventName>(id: string | undefined | null, event: K, payload?: MarketingRuntimeEventPayload<K>): MarketingReportParams | null;
149
198
 
150
- export { type MarketingClickExtra, type MarketingClickReportParams, type MarketingCloseReportParams, type MarketingExposureReportParams, type MarketingReportEvent, type MarketingReportParams, type MarketingReportableEventName, type MarketingReportableEventPayloads, type MarketingRuntimeEventPayload, buildExposureReport, mapRuntimeEventToReport };
199
+ export { type MarketingClickExtra, type MarketingClickReportParams, type MarketingCloseReportParams, type MarketingExposureReportParams, type MarketingReportEvent, type MarketingReportParams, type MarketingReportableEventName, type MarketingReportableEventPayloads, type MarketingRuntimeEventPayload, type MarketingTrackExtra, buildCloseReport, buildExposureReport, mapRuntimeEventToReport, pickInteractionTrack };