@bi-nova/ui-weapp 2.0.0-beta.202610211621 → 2.0.0-beta.202610211744

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bi-nova/ui-weapp",
3
- "version": "2.0.0-beta.202610211621",
3
+ "version": "2.0.0-beta.202610211744",
4
4
  "description": "BI Nova SDK WeChat Mini Program Components",
5
5
  "miniprogram": "src",
6
6
  "license": "ISC",
@@ -14,7 +14,7 @@
14
14
  "@bi-nova/core": "^1.0.1"
15
15
  },
16
16
  "devDependencies": {
17
- "@bi-nova/core": "2.0.0-beta.202610211621"
17
+ "@bi-nova/core": "2.0.0-beta.202610211744"
18
18
  },
19
19
  "publishConfig": {
20
20
  "registry": "https://registry.npmjs.org",
@@ -0,0 +1,152 @@
1
+ # bi-alipay-touch 支付宝碰一碰组件(微信原生)
2
+
3
+ 支付宝碰一碰取号与插件卡片组件,基于 `ad-plugin/direct-card` 插件实现,取号统一走 `@bi-nova/services` 的 `nova.alipayTouch.resolvePhone`。
4
+
5
+ - 仅支持微信小程序;组件通过 `plugin://ad-plugin/direct-card` 静态引用插件,宿主必须声明插件。
6
+ - 取号可能消费后台号码:不缓存、不预取、不自动重试,同一身份只自动取号一次。
7
+
8
+ ## 宿主接入
9
+
10
+ ### 1. 声明插件与插件导出
11
+
12
+ `app.json`:
13
+
14
+ ```json
15
+ {
16
+ "plugins": {
17
+ "ad-plugin": {
18
+ "version": "2.1.2",
19
+ "provider": "wx7d4e2b019582003c",
20
+ "export": "exportToPlugin.js"
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ 小程序根目录(与 `pages` 同级)必须存在 `exportToPlugin.js`,否则插件无法正常运行:
27
+
28
+ ```js
29
+ module.exports = {
30
+ wxMiniRequest() {
31
+ return wx.request
32
+ },
33
+ }
34
+ ```
35
+
36
+ ### 2. 初始化 SDK
37
+
38
+ ```js
39
+ const nova = createNova({ appId: 'your-app-id', autoEnv: true })
40
+
41
+ nova.use(createServices({
42
+ env: 'prod',
43
+ getAuthToken: () => getToken(),
44
+ // external 模式:与京东保险外部对接一致,经开放网关签名直连
45
+ externalEnv: 'prod',
46
+ externalAppId: '由对接方提供的应用标识',
47
+ externalAppSecret: '由对接方提供的应用密钥',
48
+ }))
49
+ ```
50
+
51
+ ### 3. 注册并使用组件
52
+
53
+ 页面 `index.json`:
54
+
55
+ ```json
56
+ {
57
+ "usingComponents": {
58
+ "bi-alipay-touch": "@bi-nova/ui-weapp/alipay-touch/index"
59
+ }
60
+ }
61
+ ```
62
+
63
+ 集团登录态模式:
64
+
65
+ ```xml
66
+ <bi-alipay-touch
67
+ space-code="YOUR_SPACE_CODE"
68
+ mode="internal"
69
+ display-mode="offscreen"
70
+ bind:ready="onReady"
71
+ bind:unavailable="onUnavailable"
72
+ bind:success="onSuccess"
73
+ bind:error="onError"
74
+ />
75
+ ```
76
+
77
+ 第三方开放接口模式:
78
+
79
+ ```xml
80
+ <bi-alipay-touch
81
+ space-code="YOUR_SPACE_CODE"
82
+ mode="external"
83
+ request-options="{{ alipayTouchOptions }}"
84
+ display-mode="inline"
85
+ bind:ready="onReady"
86
+ bind:error="onError"
87
+ />
88
+ ```
89
+
90
+ ```js
91
+ Page({
92
+ data: {
93
+ alipayTouchOptions: {
94
+ thirdPlatform: 'your-platform',
95
+ thirdPlatformUserId: 'user-id',
96
+ openid: 'user-openid-in-this-weapp',
97
+ },
98
+ },
99
+ })
100
+ ```
101
+
102
+ ## 属性
103
+
104
+ | 属性 | 类型 / 默认 | 说明 |
105
+ | --- | --- | --- |
106
+ | spaceCode | String / `''` | 插件广告位/场景编码,必填;缺失时取号前直接报配置错误 |
107
+ | mode | `'internal'` / `'external'` / 默认 `internal` | 取号通道。internal 使用集团登录态;external 走宿主后端代理的开放接口,不自动降级 |
108
+ | requestOptions | Object / `null` | external 模式身份参数:`thirdPlatform`、`thirdPlatformUserId`、`openid` 必填,`phone` 可选;`appid` 可显式传入但必须与当前宿主一致,冲突直接报错。internal 模式不采纳其中的自报身份 |
109
+ | displayMode | `'inline'` / `'offscreen'` / 默认 `offscreen` | `inline` 页面内正常文档流展示;`offscreen` 保持挂载但移出可见区域。切换只改布局,不重新取号 |
110
+ | show | Boolean / `true` | 是否允许挂载插件。初始 `false` 不自动取号,首次变 `true` 才加载;已有有效结果时重新挂载不重复取号 |
111
+ | autoLoad | Boolean / `true` | attached 后是否自动取号一次。异步登录场景可置 `false`,登录完成后通过实例方法 `load()` 手动取号 |
112
+ | sdkKey | String / `'biNova'` | 全局 SDK 实例在 `app` / `app.globalData` 上的挂载键 |
113
+ | contextKey | String / `''` | 宿主账户/登录周期标识,仅用于组件结果失效;账户切换时必须更新或销毁重建组件 |
114
+
115
+ ## 事件
116
+
117
+ 所有事件 `detail` 均携带公共上下文:`mode`、`appid`、`spaceCode`、`displayMode`、`contextKey`;事件与日志不输出手机号、openid、token。
118
+
119
+ | 事件 | 时机 | 额外字段 |
120
+ | --- | --- | --- |
121
+ | ready | 本次有效取号数据齐全(canUse=true 且 openid/phone 完整),准备挂载插件;每个有效请求周期一次 | 无 |
122
+ | unavailable | 成功取得 canUse=false 结果,非异常 | `reason: 'notAvailable'`;后台未返回原因时不伪造详细原因 |
123
+ | success | 插件 onSuccessEvent 回调 | `stage: 'plugin'`、插件 `code`、`message`;不承诺支付或奖励发放成功 |
124
+ | error | 配置、取号、响应或插件错误 | `stage: 'config' / 'resolve' / 'plugin'`、`code`、`message`;SDK 错误码与插件错误码分域 |
125
+
126
+ 错误阶段说明:
127
+
128
+ - `stage='config'`:`ALIPAY_TOUCH_UNSUPPORTED_ENV`(非微信环境)、`ALIPAY_TOUCH_SPACE_CODE_MISSING`、`ALIPAY_TOUCH_MODE_INVALID`、`ALIPAY_TOUCH_APPID_MISSING`、`ALIPAY_TOUCH_APPID_MISMATCH`、`ALIPAY_TOUCH_REQUEST_OPTIONS_MISSING`、`ALIPAY_TOUCH_SDK_NOT_READY`(SDK 未挂载或未安装 services 插件)。
129
+ - `stage='resolve'`:`code` 为 `@bi-nova/services` 的 `AlipayTouchErrorCode` 数值错误码,或组件侧 `ALIPAY_TOUCH_INVALID_RESPONSE` / `ALIPAY_TOUCH_RESOLVE_EXCEPTION`。
130
+ - `stage='plugin'`:`code` 为插件错误码,已知 1001/1003/1004 仅有初始文案,未知码原样透传。
131
+
132
+ ## 实例方法
133
+
134
+ ```js
135
+ const touch = this.selectComponent('#alipayTouch')
136
+ const result = await touch.load()
137
+ ```
138
+
139
+ `load(): Promise<{ code, message, data }>` 手动发起一次取号;同一身份并发调用复用当前 Promise。取号失败后宿主显式再次 `load()` 属于新的业务尝试,需接受后台限用结果。
140
+
141
+ ## 状态与生命周期约定
142
+
143
+ - 组件状态:`idle → resolving → unavailable / ready / error`;插件自身 success/error 是事件阶段,不把 success 当发券或奖励已完成。
144
+ - `mode`、有效请求身份或 `contextKey` 变更:隐藏插件、清理号码、旧请求序号失效,再按 `autoLoad`/`show` 决定是否加载新上下文。
145
+ - `spaceCode` 变更只更新插件场景,已有有效取号结果可沿用,不单独消费新号码。
146
+ - `onShow`、`setData`、`displayMode` 切换、React 重渲染(Taro 包装)都不会自动再次取号。
147
+ - 超时/失效响应可能已被后台消费号码:前端丢弃旧响应不等于撤销后台分配,不回池、不补偿。
148
+ - 同一业务位置只挂载一个实例,避免多个实例同时消费号码。
149
+
150
+ ## 埋点说明
151
+
152
+ SDK 不内置 QT 及业务埋点。宿主如需兼容旧埋点,可将 `ready` 映射原"取号就绪"事件、`success` 映射插件成功事件、`error` 映射错误事件;`offscreen` 模式下 `ready` 不代表用户可见曝光。
@@ -0,0 +1,419 @@
1
+ /**
2
+ * @Description: 支付宝碰一碰取号组件(微信原生)
3
+ * @Author: wutao
4
+ * @Date: 2026-10-21
5
+ * @LastEditors: wutao
6
+ * @LastEditTime: 2026-10-21
7
+ */
8
+
9
+ /** 组件状态:空闲 / 取号中 / 不可用 / 就绪 / 错误 */
10
+ const STATUS = {
11
+ IDLE: 'idle',
12
+ RESOLVING: 'resolving',
13
+ UNAVAILABLE: 'unavailable',
14
+ READY: 'ready',
15
+ ERROR: 'error'
16
+ }
17
+
18
+ /** 插件错误码初始文案,实施联调时按当前插件文档核实 */
19
+ const PLUGIN_ERROR_MESSAGES = {
20
+ 1001: '入参校验不通过',
21
+ 1003: '模版校验失败',
22
+ 1004: '无广告/无券'
23
+ }
24
+
25
+ Component({
26
+ properties: {
27
+ /** 插件广告位/场景编码,由宿主提供 */
28
+ spaceCode: { type: String, value: '' },
29
+ /** 取号通道:internal 集团登录态 / external 第三方开放接口,不自动降级 */
30
+ mode: { type: String, value: 'internal' },
31
+ /** external 模式身份参数:thirdPlatform、thirdPlatformUserId、openid、可选 phone 与 appid */
32
+ requestOptions: { type: Object, value: null },
33
+ /** 展示方式:inline 页面内 / offscreen 屏幕外加载 */
34
+ displayMode: { type: String, value: 'offscreen' },
35
+ /** 是否允许挂载插件;初始 false 不自动取号,首次变 true 才加载 */
36
+ show: { type: Boolean, value: true },
37
+ /** attached 后是否自动取号一次;异步登录场景可置 false 后手动 load */
38
+ autoLoad: { type: Boolean, value: true },
39
+ /** 宿主全局 SDK 实例挂载键 */
40
+ sdkKey: { type: String, value: 'biNova' },
41
+ /** 宿主账户/登录周期标识,仅用于组件结果失效 */
42
+ contextKey: { type: String, value: '' }
43
+ },
44
+
45
+ data: {
46
+ status: STATUS.IDLE,
47
+ openId: '',
48
+ phone: ''
49
+ },
50
+
51
+ lifetimes: {
52
+ created() {
53
+ /** 请求序号:身份变更或销毁后递增,使晚到的异步结果失效 */
54
+ this.requestToken = 0
55
+ /** 当前进行中的取号 Promise,同一身份并发 load 复用 */
56
+ this.activeRequest = null
57
+ /** 当前进行中请求的身份键 */
58
+ this.activeRequestKey = ''
59
+ /** 最近一次自动加载覆盖的身份键,防止 observer 重复触发取号 */
60
+ this.lastAutoLoadKey = ''
61
+ /** 最近一次请求使用的身份参数快照 */
62
+ this.lastIdentitySnapshot = ''
63
+ },
64
+ attached() {
65
+ this.autoLoadIfNeeded('attached')
66
+ },
67
+ detached() {
68
+ this.requestToken = (this.requestToken || 0) + 1
69
+ this.activeRequest = null
70
+ this.activeRequestKey = ''
71
+ this.setData({ status: STATUS.IDLE, openId: '', phone: '' })
72
+ }
73
+ },
74
+
75
+ observers: {
76
+ spaceCode() {
77
+ // spaceCode 只影响插件场景,不单独消费新号码;补齐后允许首次加载。
78
+ this.autoLoadIfNeeded('spaceCode')
79
+ },
80
+ 'mode, contextKey'() {
81
+ // 通道或登录周期变更视为身份变更:清理结果后按配置重新加载。
82
+ if (this.hasIdentityChanged()) {
83
+ this.clearResolvedState()
84
+ this.autoLoadIfNeeded('identity')
85
+ }
86
+ },
87
+ requestOptions() {
88
+ if (this.properties.mode !== 'external') return
89
+ if (this.hasIdentityChanged()) {
90
+ this.clearResolvedState()
91
+ this.autoLoadIfNeeded('requestOptions')
92
+ }
93
+ },
94
+ show(nextShow) {
95
+ // 已有有效结果时重新挂载即可,不重复取号。
96
+ if (!nextShow || this.data.status === STATUS.READY) return
97
+ this.autoLoadIfNeeded('show')
98
+ }
99
+ },
100
+
101
+ methods: {
102
+ /**
103
+ * 获取全局 SDK 实例
104
+ * @returns {Record<string, any> | null}
105
+ */
106
+ getSdkInstance() {
107
+ const app = typeof getApp === 'function' ? getApp() : null
108
+
109
+ if (!app) return null
110
+
111
+ const sdkKey = this.properties.sdkKey || 'biNova'
112
+ return app[sdkKey] || app.globalData && app.globalData[sdkKey] || null
113
+ },
114
+
115
+ /**
116
+ * 获取当前小程序 appId
117
+ * @returns {string}
118
+ */
119
+ getMiniProgramAppId() {
120
+ try {
121
+ if (typeof wx !== 'undefined' && typeof wx.getAccountInfoSync === 'function') {
122
+ const accountInfo = wx.getAccountInfoSync()
123
+ return accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.appId || ''
124
+ }
125
+ } catch (error) {
126
+ return ''
127
+ }
128
+
129
+ return ''
130
+ },
131
+
132
+ /**
133
+ * 构建事件公共上下文
134
+ * @returns {Record<string, any>}
135
+ */
136
+ getEventContext() {
137
+ return {
138
+ mode: this.properties.mode,
139
+ appid: this.getMiniProgramAppId(),
140
+ spaceCode: this.properties.spaceCode || '',
141
+ displayMode: this.properties.displayMode,
142
+ contextKey: this.properties.contextKey || ''
143
+ }
144
+ },
145
+
146
+ /**
147
+ * 触发组件错误事件
148
+ * @param {'config' | 'resolve' | 'plugin'} stage 错误阶段
149
+ * @param {string | number} code 错误码,与插件错误码分域
150
+ * @param {string} message 错误消息
151
+ * @returns {void}
152
+ */
153
+ triggerComponentError(stage, code, message) {
154
+ this.triggerEvent('error', {
155
+ ...this.getEventContext(),
156
+ stage,
157
+ code,
158
+ message: message || '碰一碰组件错误'
159
+ })
160
+ },
161
+
162
+ /**
163
+ * 构建取号参数并完成取号前校验
164
+ * @returns {{ error: { code: string, message: string } } | { options: Record<string, any>, key: string }}
165
+ */
166
+ buildResolveOptions() {
167
+ if (typeof wx === 'undefined' || typeof wx.getAccountInfoSync !== 'function') {
168
+ return { error: { code: 'ALIPAY_TOUCH_UNSUPPORTED_ENV', message: '当前环境不支持碰一碰组件,仅支持微信小程序' } }
169
+ }
170
+
171
+ if (!this.properties.spaceCode) {
172
+ return { error: { code: 'ALIPAY_TOUCH_SPACE_CODE_MISSING', message: '缺少插件场景编码 spaceCode' } }
173
+ }
174
+
175
+ const mode = this.properties.mode
176
+ if (mode !== 'internal' && mode !== 'external') {
177
+ return { error: { code: 'ALIPAY_TOUCH_MODE_INVALID', message: `不支持的取号通道:${mode},请使用 internal 或 external` } }
178
+ }
179
+
180
+ const currentAppId = this.getMiniProgramAppId()
181
+ if (!currentAppId) {
182
+ return { error: { code: 'ALIPAY_TOUCH_APPID_MISSING', message: '未能取得当前微信小程序 appId' } }
183
+ }
184
+
185
+ const requestOptions = this.properties.requestOptions && typeof this.properties.requestOptions === 'object'
186
+ ? this.properties.requestOptions
187
+ : {}
188
+ const declaredAppId = String(requestOptions.appid || '').trim()
189
+
190
+ if (mode === 'external') {
191
+ if (declaredAppId && declaredAppId !== currentAppId) {
192
+ return { error: { code: 'ALIPAY_TOUCH_APPID_MISMATCH', message: `requestOptions.appid(${declaredAppId}) 与当前宿主 appId(${currentAppId}) 不一致` } }
193
+ }
194
+
195
+ const thirdPlatform = String(requestOptions.thirdPlatform || '').trim()
196
+ const thirdPlatformUserId = String(requestOptions.thirdPlatformUserId || '').trim()
197
+ const openid = String(requestOptions.openid || '').trim()
198
+
199
+ if (!thirdPlatform || !thirdPlatformUserId || !openid) {
200
+ return { error: { code: 'ALIPAY_TOUCH_REQUEST_OPTIONS_MISSING', message: 'external 模式需要在 requestOptions 提供 thirdPlatform、thirdPlatformUserId、openid' } }
201
+ }
202
+
203
+ const options = {
204
+ mode: 'external',
205
+ thirdPlatform,
206
+ thirdPlatformUserId,
207
+ openid,
208
+ appid: currentAppId
209
+ }
210
+ const phone = String(requestOptions.phone || '').trim()
211
+
212
+ if (phone) {
213
+ options.phone = phone
214
+ }
215
+
216
+ return { options, key: this.buildRequestKey(options) }
217
+ }
218
+
219
+ const options = { mode: 'internal', appid: currentAppId }
220
+
221
+ return { options, key: this.buildRequestKey(options) }
222
+ },
223
+
224
+ /**
225
+ * 构建取号身份键(不含 spaceCode,spaceCode 变更不消费新号码)
226
+ * @param {Record<string, any>} options 取号参数
227
+ * @returns {string}
228
+ */
229
+ buildRequestKey(options) {
230
+ return [
231
+ this.properties.contextKey || '',
232
+ JSON.stringify(options)
233
+ ].join('__')
234
+ },
235
+
236
+ /**
237
+ * 判断有效请求身份是否发生变化
238
+ *
239
+ * 配置非法(如 spaceCode 缺失)时快照为空串,
240
+ * 与上一次有效身份不同同样视为变更,用于清理旧结果。
241
+ * @returns {boolean}
242
+ */
243
+ hasIdentityChanged() {
244
+ const built = this.buildResolveOptions()
245
+ const snapshot = 'error' in built ? '' : built.key
246
+
247
+ if (snapshot === this.lastIdentitySnapshot) return false
248
+
249
+ this.lastIdentitySnapshot = snapshot
250
+ return true
251
+ },
252
+
253
+ /**
254
+ * 清理取号结果,使旧响应失效
255
+ * @returns {void}
256
+ */
257
+ clearResolvedState() {
258
+ this.requestToken = (this.requestToken || 0) + 1
259
+ this.activeRequest = null
260
+ this.activeRequestKey = ''
261
+ this.setData({ status: STATUS.IDLE, openId: '', phone: '' })
262
+ },
263
+
264
+ /**
265
+ * 按配置触发自动取号
266
+ * @param {string} reason 触发原因
267
+ * @returns {void}
268
+ */
269
+ autoLoadIfNeeded(reason) {
270
+ if (!this.properties.autoLoad || !this.properties.show) return
271
+
272
+ const built = this.buildResolveOptions()
273
+
274
+ if ('error' in built) return
275
+
276
+ // 同一身份只自动加载一次,observer 重复触发、displayMode 切换不再取号。
277
+ if (this.lastAutoLoadKey === built.key) return
278
+ if (this.activeRequestKey === built.key) return
279
+ if (this.data.status === STATUS.READY && this.lastIdentitySnapshot === built.key) return
280
+
281
+ this.lastAutoLoadKey = built.key
282
+ this.load().catch(() => {})
283
+ },
284
+
285
+ /**
286
+ * 手动/自动取号入口
287
+ *
288
+ * 同一身份并发调用复用当前 Promise;身份变更后的旧响应不会写回本实例。
289
+ * @returns {Promise<{code: number | string, message: string, data: Record<string, any> | null}>}
290
+ */
291
+ async load() {
292
+ const built = this.buildResolveOptions()
293
+
294
+ if ('error' in built) {
295
+ this.setData({ status: STATUS.ERROR })
296
+ this.triggerComponentError('config', built.error.code, built.error.message)
297
+ return { code: built.error.code, message: built.error.message, data: null }
298
+ }
299
+
300
+ const sdk = this.getSdkInstance()
301
+
302
+ if (!sdk || !sdk.alipayTouch || typeof sdk.alipayTouch.resolvePhone !== 'function') {
303
+ const code = 'ALIPAY_TOUCH_SDK_NOT_READY'
304
+ const message = '未获取到可用的碰一碰服务,请先安装 services 插件并注入全局 nova 实例'
305
+ this.setData({ status: STATUS.ERROR })
306
+ this.triggerComponentError('config', code, message)
307
+ return { code, message, data: null }
308
+ }
309
+
310
+ if (this.activeRequest && this.activeRequestKey === built.key) {
311
+ return this.activeRequest
312
+ }
313
+
314
+ const requestToken = (this.requestToken || 0) + 1
315
+ this.requestToken = requestToken
316
+ this.lastIdentitySnapshot = built.key
317
+ this.activeRequestKey = built.key
318
+
319
+ this.setData({ status: STATUS.RESOLVING, openId: '', phone: '' })
320
+
321
+ this.activeRequest = (async () => {
322
+ try {
323
+ const result = await sdk.alipayTouch.resolvePhone(built.options)
324
+
325
+ if (requestToken !== this.requestToken) return result
326
+
327
+ return this.applyResolveResult(result)
328
+ } catch (error) {
329
+ if (requestToken !== this.requestToken) {
330
+ return { code: 'ALIPAY_TOUCH_STALE_RESPONSE', message: '取号结果已失效', data: null }
331
+ }
332
+
333
+ const message = error && error.message || '碰一碰取号异常'
334
+ this.setData({ status: STATUS.ERROR })
335
+ this.triggerComponentError('resolve', 'ALIPAY_TOUCH_RESOLVE_EXCEPTION', message)
336
+ return { code: 'ALIPAY_TOUCH_RESOLVE_EXCEPTION', message, data: null }
337
+ } finally {
338
+ if (requestToken === this.requestToken) {
339
+ this.activeRequest = null
340
+ this.activeRequestKey = ''
341
+ }
342
+ }
343
+ })()
344
+
345
+ return this.activeRequest
346
+ },
347
+
348
+ /**
349
+ * 应用取号结果并派发组件事件
350
+ * @param {{code: number, message: string, data: Record<string, any> | null}} result 服务结果
351
+ * @returns {{code: number, message: string, data: Record<string, any> | null}} 原样返回服务结果
352
+ */
353
+ applyResolveResult(result) {
354
+ if (!result || typeof result.code !== 'number' || result.code !== 0) {
355
+ this.setData({ status: STATUS.ERROR })
356
+ this.triggerComponentError('resolve', result && result.code, result && result.message || '碰一碰取号失败')
357
+ return result || { code: 'ALIPAY_TOUCH_RESOLVE_FAILED', message: '碰一碰取号失败', data: null }
358
+ }
359
+
360
+ const data = result.data || {}
361
+
362
+ if (data.canUse === true && data.openid && data.phone) {
363
+ this.setData({ status: STATUS.READY, openId: data.openid, phone: data.phone })
364
+ this.triggerEvent('ready', this.getEventContext())
365
+ return result
366
+ }
367
+
368
+ if (data.canUse === false) {
369
+ this.setData({ status: STATUS.UNAVAILABLE, openId: '', phone: '' })
370
+ this.triggerEvent('unavailable', { ...this.getEventContext(), reason: 'notAvailable' })
371
+ return result
372
+ }
373
+
374
+ // canUse 非法或字段不完整:数据不完整不挂载插件。
375
+ this.setData({ status: STATUS.ERROR })
376
+ this.triggerComponentError('resolve', 'ALIPAY_TOUCH_INVALID_RESPONSE', '取号响应数据不完整')
377
+ return result
378
+ },
379
+
380
+ /**
381
+ * 插件成功回调,只转发白名单字段
382
+ * @param {Record<string, any>} event 插件事件
383
+ * @returns {void}
384
+ */
385
+ onPluginSuccess(event) {
386
+ const detail = event && event.detail && typeof event.detail === 'object' ? event.detail : {}
387
+ const code = detail.code !== undefined ? detail.code : ''
388
+ const message = detail.message !== undefined ? detail.message : ''
389
+
390
+ this.triggerEvent('success', {
391
+ ...this.getEventContext(),
392
+ stage: 'plugin',
393
+ code,
394
+ message
395
+ })
396
+ },
397
+
398
+ /**
399
+ * 插件错误回调,未知码安全读取
400
+ * @param {Record<string, any>} event 插件事件
401
+ * @returns {void}
402
+ */
403
+ onPluginError(event) {
404
+ const detail = event && event.detail && typeof event.detail === 'object' ? event.detail : {}
405
+ const code = detail.code !== undefined ? detail.code : ''
406
+ const knownMessage = PLUGIN_ERROR_MESSAGES[detail.code]
407
+ const message = detail.message !== undefined && detail.message !== null && detail.message !== ''
408
+ ? String(detail.message)
409
+ : knownMessage || `插件未知错误: ${code || ''}`
410
+
411
+ this.triggerEvent('error', {
412
+ ...this.getEventContext(),
413
+ stage: 'plugin',
414
+ code,
415
+ message
416
+ })
417
+ }
418
+ }
419
+ })
@@ -0,0 +1,6 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {
4
+ "direct-card": "plugin://ad-plugin/direct-card"
5
+ }
6
+ }
@@ -0,0 +1,10 @@
1
+ <view class="bi-alipay-touch {{displayMode === 'inline' ? 'bi-alipay-touch--inline' : 'bi-alipay-touch--offscreen'}}">
2
+ <direct-card
3
+ wx:if="{{status === 'ready' && show}}"
4
+ spaceCode="{{spaceCode}}"
5
+ openId="{{openId}}"
6
+ phone="{{phone}}"
7
+ bind:onSuccessEvent="onPluginSuccess"
8
+ bind:onErrorEvent="onPluginError"
9
+ />
10
+ </view>
@@ -0,0 +1,16 @@
1
+ .bi-alipay-touch {
2
+ width: 100%;
3
+ }
4
+
5
+ /* 页面内展示:正常文档流,不侵入插件内部样式 */
6
+ .bi-alipay-touch--inline {
7
+ position: relative;
8
+ width: 100%;
9
+ }
10
+
11
+ /* 屏幕外加载:保持插件挂载但移出可见区域,不能用 display:none 替代 */
12
+ .bi-alipay-touch--offscreen {
13
+ position: absolute;
14
+ left: -3000rpx;
15
+ z-index: -999;
16
+ }