@bi-nova/ui-weapp 1.2.1 → 1.3.0-beta.202609181052
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/package.json +1 -1
- package/src/ad/utils.js +3 -2
- package/src/product-placement/README.md +607 -0
- package/src/product-placement/action-executor.js +293 -0
- package/src/product-placement/banner/index.js +106 -0
- package/src/product-placement/banner/index.json +3 -0
- package/src/product-placement/banner/index.wxml +48 -0
- package/src/product-placement/banner/index.wxss +133 -0
- package/src/product-placement/half-screen/index.js +129 -0
- package/src/product-placement/half-screen/index.json +6 -0
- package/src/product-placement/half-screen/index.wxml +64 -0
- package/src/product-placement/half-screen/index.wxss +145 -0
- package/src/product-placement/index.js +590 -0
- package/src/product-placement/index.json +8 -0
- package/src/product-placement/index.wxml +44 -0
- package/src/product-placement/index.wxss +11 -0
- package/src/product-placement/overlay/index.js +32 -0
- package/src/product-placement/overlay/index.json +3 -0
- package/src/product-placement/overlay/index.wxml +18 -0
- package/src/product-placement/overlay/index.wxss +55 -0
- package/src/product-placement/popup/index.js +112 -0
- package/src/product-placement/popup/index.json +6 -0
- package/src/product-placement/popup/index.wxml +54 -0
- package/src/product-placement/popup/index.wxss +124 -0
- package/src/product-placement/utils.js +295 -0
- package/src/shared/action-executor.js +336 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Description Weapp 通用动作执行器
|
|
3
|
+
* @Author wutao
|
|
4
|
+
* @Date 2026-09-14
|
|
5
|
+
* @LastEditors wutao
|
|
6
|
+
* @LastEditTime 2026-09-14 17:19:39
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 通用动作执行错误码 */
|
|
10
|
+
const DEFAULT_ERROR_CODES = {
|
|
11
|
+
invalidAction: 'WEAPP_ACTION_INVALID',
|
|
12
|
+
miniProgramFail: 'WEAPP_ACTION_MINI_PROGRAM_FAIL',
|
|
13
|
+
internalFail: 'WEAPP_ACTION_INTERNAL_FAIL',
|
|
14
|
+
apiRequestFail: 'WEAPP_ACTION_API_REQUEST_FAIL',
|
|
15
|
+
unsupportedApi: 'WEAPP_ACTION_API_UNSUPPORTED'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 判断接口参数是否具有有效值
|
|
20
|
+
* @param {unknown} value 参数值
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function hasValue(value) {
|
|
24
|
+
return value !== undefined && value !== null && value !== ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 向地址追加查询参数,并保留已有查询参数与哈希片段
|
|
29
|
+
* @param {string} url 原始地址
|
|
30
|
+
* @param {Record<string, string | number>} params 查询参数
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function appendUrlQueryParams(url, params) {
|
|
34
|
+
const hashIndex = url.indexOf('#')
|
|
35
|
+
const hash = hashIndex >= 0 ? url.slice(hashIndex) : ''
|
|
36
|
+
const baseUrl = hashIndex >= 0 ? url.slice(0, hashIndex) : url
|
|
37
|
+
const query = Object.keys(params)
|
|
38
|
+
.filter(key => hasValue(params[key]))
|
|
39
|
+
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`)
|
|
40
|
+
.join('&')
|
|
41
|
+
|
|
42
|
+
if (!query) return url
|
|
43
|
+
|
|
44
|
+
const separator = baseUrl.includes('?')
|
|
45
|
+
? /[?&]$/.test(baseUrl) ? '' : '&'
|
|
46
|
+
: '?'
|
|
47
|
+
|
|
48
|
+
return `${baseUrl}${separator}${query}${hash}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 标准化动作类型
|
|
53
|
+
* @param {unknown} type 原始动作类型
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function normalizeActionType(type) {
|
|
57
|
+
const rawType = String(type || '').trim()
|
|
58
|
+
|
|
59
|
+
if (rawType === 'miniProgram' || rawType.toLowerCase() === 'miniprogram') {
|
|
60
|
+
return 'mini_program'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return rawType.toLowerCase()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 调用动作错误回调
|
|
68
|
+
* @param {Record<string, any>} options 执行参数
|
|
69
|
+
* @param {string} errCode 错误码
|
|
70
|
+
* @param {string} errMsg 错误信息
|
|
71
|
+
* @returns {false}
|
|
72
|
+
*/
|
|
73
|
+
function emitError(options, errCode, errMsg) {
|
|
74
|
+
if (typeof options.onError === 'function') {
|
|
75
|
+
options.onError({ errCode, errMsg })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 调用动作成功回调
|
|
83
|
+
* @param {Record<string, any>} options 执行参数
|
|
84
|
+
* @param {Record<string, any>} detail 成功详情
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
function emitSuccess(options, detail) {
|
|
88
|
+
if (typeof options.onSuccess === 'function') {
|
|
89
|
+
options.onSuccess(detail)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 执行小程序跳转
|
|
95
|
+
* @param {Record<string, any>} options 执行参数
|
|
96
|
+
* @param {Record<string, any>} action 动作配置
|
|
97
|
+
* @param {Record<string, string>} errorCodes 错误码配置
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function executeMiniProgramAction(options, action, errorCodes) {
|
|
101
|
+
const appId = String(action.appId || '').trim()
|
|
102
|
+
const path = String(action.path || '')
|
|
103
|
+
const wxApi = typeof wx === 'undefined' ? null : wx
|
|
104
|
+
|
|
105
|
+
if (!appId) {
|
|
106
|
+
return emitError(options, errorCodes.invalidAction, '小程序动作缺少 appId')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!wxApi || typeof wxApi.navigateToMiniProgram !== 'function') {
|
|
110
|
+
return emitError(options, errorCodes.unsupportedApi, '当前环境不支持跳转其他小程序')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
wxApi.navigateToMiniProgram({
|
|
114
|
+
appId,
|
|
115
|
+
path,
|
|
116
|
+
success: () => {
|
|
117
|
+
emitSuccess(options, {
|
|
118
|
+
actionType: 'mini_program',
|
|
119
|
+
resultType: 'mini_program',
|
|
120
|
+
appId,
|
|
121
|
+
path
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
fail: (error) => {
|
|
125
|
+
emitError(
|
|
126
|
+
options,
|
|
127
|
+
errorCodes.miniProgramFail,
|
|
128
|
+
error && error.errMsg || '跳转到其他小程序失败'
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 执行小程序内部页面跳转
|
|
138
|
+
* @param {Record<string, any>} options 执行参数
|
|
139
|
+
* @param {Record<string, any>} action 动作配置
|
|
140
|
+
* @param {Record<string, string>} errorCodes 错误码配置
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
function executeInternalAction(options, action, errorCodes) {
|
|
144
|
+
const path = String(action.path || '').trim()
|
|
145
|
+
const wxApi = typeof wx === 'undefined' ? null : wx
|
|
146
|
+
|
|
147
|
+
if (!path) {
|
|
148
|
+
return emitError(options, errorCodes.invalidAction, '内部页面动作缺少 path')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!wxApi || typeof wxApi.navigateTo !== 'function') {
|
|
152
|
+
return emitError(options, errorCodes.unsupportedApi, '当前环境不支持页面跳转')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
wxApi.navigateTo({
|
|
156
|
+
url: path,
|
|
157
|
+
success: () => {
|
|
158
|
+
emitSuccess(options, {
|
|
159
|
+
actionType: 'internal',
|
|
160
|
+
resultType: 'internal',
|
|
161
|
+
path
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
fail: (error) => {
|
|
165
|
+
emitError(
|
|
166
|
+
options,
|
|
167
|
+
errorCodes.internalFail,
|
|
168
|
+
error && error.errMsg || '页面跳转失败'
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 执行 WebView 动作
|
|
178
|
+
* @param {Record<string, any>} options 执行参数
|
|
179
|
+
* @param {Record<string, any>} action 动作配置
|
|
180
|
+
* @param {Record<string, string>} errorCodes 错误码配置
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function executeWebAction(options, action, errorCodes) {
|
|
184
|
+
const url = String(action.url || '').trim()
|
|
185
|
+
|
|
186
|
+
if (!url) {
|
|
187
|
+
return emitError(options, errorCodes.invalidAction, 'Web 动作缺少 url')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (typeof options.onOpenWebview === 'function') {
|
|
191
|
+
options.onOpenWebview({ url })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
emitSuccess(options, {
|
|
195
|
+
actionType: 'web',
|
|
196
|
+
resultType: 'web',
|
|
197
|
+
url
|
|
198
|
+
})
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 校验动态接口动作运行时参数
|
|
204
|
+
* @param {Record<string, any>} requestParams 运行时参数
|
|
205
|
+
* @returns {string[]}
|
|
206
|
+
*/
|
|
207
|
+
function getInvalidApiParamNames(requestParams) {
|
|
208
|
+
return ['configId', 'equipmentValue', 'userId'].filter((name) => {
|
|
209
|
+
const value = requestParams[name]
|
|
210
|
+
|
|
211
|
+
if (name === 'userId') {
|
|
212
|
+
return !Number.isInteger(value) || value <= 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return !hasValue(value)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 执行动态接口动作
|
|
221
|
+
* @param {Record<string, any>} options 执行参数
|
|
222
|
+
* @param {Record<string, any>} action 动作配置
|
|
223
|
+
* @param {Record<string, string>} errorCodes 错误码配置
|
|
224
|
+
* @returns {boolean}
|
|
225
|
+
*/
|
|
226
|
+
function executeApiAction(options, action, errorCodes) {
|
|
227
|
+
const url = String(action.url || '').trim()
|
|
228
|
+
const wxApi = typeof wx === 'undefined' ? null : wx
|
|
229
|
+
const requestParams = options.apiRequestParams || {}
|
|
230
|
+
const invalidParamNames = getInvalidApiParamNames(requestParams)
|
|
231
|
+
|
|
232
|
+
if (!url) {
|
|
233
|
+
return emitError(options, errorCodes.invalidAction, '接口动作缺少 url')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (invalidParamNames.length) {
|
|
237
|
+
return emitError(
|
|
238
|
+
options,
|
|
239
|
+
errorCodes.apiRequestFail,
|
|
240
|
+
`接口动作缺少必要参数或参数值无效:${invalidParamNames.join('、')}`
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!wxApi || typeof wxApi.request !== 'function') {
|
|
245
|
+
return emitError(options, errorCodes.unsupportedApi, '当前环境不支持接口请求')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
wxApi.request({
|
|
249
|
+
url: appendUrlQueryParams(url, {
|
|
250
|
+
configId: requestParams.configId,
|
|
251
|
+
equipmentValue: requestParams.equipmentValue,
|
|
252
|
+
userId: requestParams.userId
|
|
253
|
+
}),
|
|
254
|
+
method: 'GET',
|
|
255
|
+
success: (response) => {
|
|
256
|
+
const responseData = response && response.data && typeof response.data === 'object'
|
|
257
|
+
? response.data
|
|
258
|
+
: null
|
|
259
|
+
const responseBody = responseData && responseData.body && typeof responseData.body === 'object'
|
|
260
|
+
? responseData.body
|
|
261
|
+
: null
|
|
262
|
+
const appId = String(responseBody && responseBody.appId || '').trim()
|
|
263
|
+
const path = String(
|
|
264
|
+
responseBody && (responseBody.path || responseBody.url) || ''
|
|
265
|
+
).trim().replace(/^\/+/, '')
|
|
266
|
+
|
|
267
|
+
if (String(responseData && responseData.code || '') !== '200' || !appId || !path) {
|
|
268
|
+
emitError(
|
|
269
|
+
options,
|
|
270
|
+
errorCodes.apiRequestFail,
|
|
271
|
+
responseData && responseData.message || '接口动作响应异常'
|
|
272
|
+
)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
executeMiniProgramAction(options, { appId, path }, errorCodes)
|
|
277
|
+
},
|
|
278
|
+
fail: (error) => {
|
|
279
|
+
emitError(
|
|
280
|
+
options,
|
|
281
|
+
errorCodes.apiRequestFail,
|
|
282
|
+
error && error.errMsg || '接口动作请求失败'
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 执行标准导航动作
|
|
292
|
+
* @param {Object} options 动作执行参数
|
|
293
|
+
* @param {Record<string, any>} options.action 标准动作配置
|
|
294
|
+
* @param {Record<string, any>} [options.apiRequestParams] 动态接口运行时参数
|
|
295
|
+
* @param {Record<string, string>} [options.errorCodes] 自定义错误码
|
|
296
|
+
* @param {(detail: Record<string, any>) => void} [options.onOpenWebview] WebView 回调
|
|
297
|
+
* @param {(detail: Record<string, any>) => void} [options.onSuccess] 成功回调
|
|
298
|
+
* @param {(detail: {errCode: string, errMsg: string}) => void} [options.onError] 失败回调
|
|
299
|
+
* @returns {boolean} 是否已进入动作执行流程
|
|
300
|
+
*/
|
|
301
|
+
function executeNavigationAction(options) {
|
|
302
|
+
const action = options && options.action || {}
|
|
303
|
+
const actionType = normalizeActionType(action.type)
|
|
304
|
+
const errorCodes = {
|
|
305
|
+
...DEFAULT_ERROR_CODES,
|
|
306
|
+
...(options && options.errorCodes || {})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (actionType === 'mini_program') {
|
|
310
|
+
return executeMiniProgramAction(options, action, errorCodes)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (actionType === 'internal') {
|
|
314
|
+
return executeInternalAction(options, action, errorCodes)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (actionType === 'web') {
|
|
318
|
+
return executeWebAction(options, action, errorCodes)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (actionType === 'api') {
|
|
322
|
+
return executeApiAction(options, action, errorCodes)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return emitError(
|
|
326
|
+
options,
|
|
327
|
+
errorCodes.invalidAction,
|
|
328
|
+
`不支持的动作类型:${actionType || 'empty'}`
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
appendUrlQueryParams,
|
|
334
|
+
executeNavigationAction,
|
|
335
|
+
normalizeActionType
|
|
336
|
+
}
|