@bi-nova/ui-weapp 1.2.1 → 1.3.0-beta.202609151700

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.
@@ -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
+ }