@tbox.cn/app-contracts-mall 0.1.1 → 0.9.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.
Files changed (78) hide show
  1. package/README.md +16 -2
  2. package/dist/alipay-activity/cards.d.ts +305 -0
  3. package/dist/assembly.d.ts +54 -0
  4. package/dist/chunk-KIZD6FZ2.js +43 -0
  5. package/dist/deployment.d.ts +42 -0
  6. package/dist/domain/activity.d.ts +64 -0
  7. package/dist/domain/coupon.d.ts +2 -0
  8. package/dist/domain/entitlement.d.ts +53 -0
  9. package/dist/domain/offer.d.ts +111 -0
  10. package/dist/domain/operation.d.ts +22 -0
  11. package/dist/domain/parking.d.ts +195 -12
  12. package/dist/domain/points.d.ts +3 -2
  13. package/dist/domain/shopping-guide.d.ts +201 -0
  14. package/dist/guard.d.ts +50 -0
  15. package/dist/index.d.ts +22 -2
  16. package/dist/member/cards.d.ts +940 -0
  17. package/dist/parking/cards.d.ts +1271 -0
  18. package/dist/promotion/cards.d.ts +2529 -0
  19. package/dist/query-context.d.ts +47 -0
  20. package/dist/runtime.d.ts +20 -0
  21. package/dist/runtime.js +1281 -0
  22. package/dist/scope.d.ts +76 -0
  23. package/dist/server/index.d.ts +11 -0
  24. package/dist/server/index.js +80 -0
  25. package/dist/server/mall-context.d.ts +24 -0
  26. package/dist/server/mall-control-plane.d.ts +8 -0
  27. package/dist/server/request-credentials.d.ts +22 -0
  28. package/dist/services/customer-service.d.ts +11 -0
  29. package/dist/services/mall-info.d.ts +26 -0
  30. package/dist/services/member.d.ts +243 -9
  31. package/dist/services/parking.d.ts +105 -12
  32. package/dist/services/promotion.d.ts +100 -4
  33. package/dist/services/shopping-guide.d.ts +97 -0
  34. package/dist/shared/cards.d.ts +42 -0
  35. package/dist/shopping-guide/cards.d.ts +7039 -0
  36. package/package.json +19 -8
  37. package/src/alipay-activity/cards.ts +66 -0
  38. package/src/assembly.ts +103 -0
  39. package/src/deployment.ts +44 -0
  40. package/src/domain/activity.ts +66 -0
  41. package/src/domain/coupon.ts +3 -0
  42. package/src/domain/entitlement.ts +56 -0
  43. package/src/domain/offer.ts +120 -0
  44. package/src/domain/operation.ts +40 -0
  45. package/src/domain/parking.ts +206 -12
  46. package/src/domain/points.ts +3 -2
  47. package/src/domain/shopping-guide.ts +242 -0
  48. package/src/guard.ts +48 -0
  49. package/src/index.ts +22 -2
  50. package/src/member/cards.ts +209 -0
  51. package/src/parking/cards.ts +233 -0
  52. package/src/promotion/cards.ts +290 -0
  53. package/src/query-context.ts +81 -0
  54. package/src/runtime.ts +41 -0
  55. package/src/scope.ts +92 -0
  56. package/src/server/index.ts +11 -0
  57. package/src/server/mall-context.ts +39 -0
  58. package/src/server/mall-control-plane.ts +56 -0
  59. package/src/server/request-credentials.ts +43 -0
  60. package/src/service-slots.json +23 -0
  61. package/src/services/customer-service.ts +12 -0
  62. package/src/services/mall-info.ts +31 -0
  63. package/src/services/member.ts +354 -9
  64. package/src/services/parking.ts +161 -11
  65. package/src/services/promotion.ts +124 -4
  66. package/src/services/shopping-guide.ts +138 -0
  67. package/src/shared/cards.ts +25 -0
  68. package/src/shopping-guide/cards.ts +438 -0
  69. package/tests/assembly-validate-scope.test.ts +132 -0
  70. package/tests/cards.test.ts +586 -0
  71. package/tests/context-unification.test.ts +73 -0
  72. package/tests/mall-control-plane.test.ts +86 -0
  73. package/tests/member-status.test.ts +197 -0
  74. package/tests/query-context.test.ts +111 -0
  75. package/tests/request-credentials.test.ts +38 -0
  76. package/tests/resolve-primitives.test.ts +55 -0
  77. package/tests/service-slots.test.ts +78 -0
  78. package/tsconfig.json +1 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * 会员域卡片数据契约(011 K2:member 卡 zod schema,TIER1;券面 0.4.0 净删——promotion 承接)。
3
+ */
4
+ import { z } from 'zod';
5
+
6
+ export const memberAuthStateSchema = z.enum(['authenticated', 'unauthenticated']);
7
+ export const memberStateSchema = z.enum(['unknown', 'active', 'not_enrolled', 'closed']);
8
+
9
+ /** PRD 6.6 首批会员偏好问题;卡片展示、意图路由与记忆写入共用同一真源。 */
10
+ export const MEMBER_PREFERENCE_VALUES = [
11
+ '我常来买衣服',
12
+ '我常带娃来玩',
13
+ '我常来吃美食',
14
+ ] as const;
15
+ export const memberPreferenceValueSchema = z.enum(MEMBER_PREFERENCE_VALUES);
16
+ export type MemberPreferenceValue = z.infer<typeof memberPreferenceValueSchema>;
17
+
18
+ export const MEMBER_PREFERENCE_PROMPTS: ReadonlyArray<{
19
+ value: MemberPreferenceValue;
20
+ query: string;
21
+ }> = MEMBER_PREFERENCE_VALUES.map((value) => ({ value, query: value }));
22
+
23
+ /** 按偏好触发导购能力时使用的检索语句;与用户可见按钮文案解耦。 */
24
+ export const MEMBER_PREFERENCE_RECOMMENDATION_QUERIES: Readonly<Record<MemberPreferenceValue, string>> = {
25
+ '我常来买衣服': '推荐适合购买的服装门店和商品',
26
+ '我常带娃来玩': '推荐适合带娃的亲子娱乐门店',
27
+ '我常来吃美食': '推荐值得去的餐饮美食门店',
28
+ };
29
+
30
+ /** member-info:账户 + 资料摘要 */
31
+ export const memberInfoCardDataSchema = z.object({
32
+ userId: z.string().min(1),
33
+ authState: memberAuthStateSchema,
34
+ state: memberStateSchema,
35
+ displayName: z.string().optional(),
36
+ avatarUrl: z.string().optional(),
37
+ contactDisplay: z.string().optional(),
38
+ memberRef: z.string().optional(),
39
+ registeredAt: z.string().optional(),
40
+ labels: z.array(z.object({ labelRef: z.string(), name: z.string() })),
41
+ });
42
+ export type MemberInfoCardData = z.infer<typeof memberInfoCardDataSchema>;
43
+
44
+ /** member-card-summary:会员卡面(真实卡名、卡图与积分摘要) */
45
+ export const memberCardSummaryCardDataSchema = z.object({
46
+ userId: z.string().min(1),
47
+ cardRef: z.string(),
48
+ cardDisplay: z.string(),
49
+ status: z.enum(['active', 'inactive', 'frozen', 'expired', 'unknown']),
50
+ levelName: z.string().optional(),
51
+ availablePoints: z.number().int().min(0),
52
+ cardImageUrl: z.string().optional(),
53
+ pointsRuleImageUrl: z.string().url().optional(),
54
+ pointsRuleContent: z.string().min(1).max(20_000).optional(),
55
+ pointsUrl: z.string().url().optional(),
56
+ presentation: z.object({
57
+ variant: z.enum(['blue', 'silver', 'gold', 'black']),
58
+ labelsEmbedded: z.boolean().optional(),
59
+ }).optional(),
60
+ progress: z.object({
61
+ daySpentCents: z.number().int().min(0),
62
+ yearSpentCents: z.number().int().min(0),
63
+ percent: z.number().min(0).max(100),
64
+ message: z.string().min(1).max(256),
65
+ }).optional(),
66
+ benefits: z.array(z.object({
67
+ benefitRef: z.string().min(1),
68
+ name: z.string().min(1).max(128),
69
+ iconUrl: z.string().url().optional(),
70
+ description: z.string().min(1).max(128).optional(),
71
+ action: z.object({
72
+ kind: z.enum(['miniapp', 'dialog', 'toast']),
73
+ url: z.string().url().optional(),
74
+ title: z.string().min(1).max(128).optional(),
75
+ text: z.string().min(1).max(2_048).optional(),
76
+ }).optional(),
77
+ })).max(20).optional(),
78
+ /** 入会后偏好引导;首批固定三项,保留数组形态便于后续扩展。 */
79
+ preferencePrompts: z.array(z.object({
80
+ value: memberPreferenceValueSchema,
81
+ query: z.string().min(1).max(128),
82
+ })).max(8).optional(),
83
+ });
84
+ export type MemberCardSummaryCardData = z.infer<typeof memberCardSummaryCardDataSchema>;
85
+
86
+ /**
87
+ * member-point-usage-list(011 K3:余额、厂商会员标签与积分流水)。
88
+ */
89
+ export const memberPointUsageListCardDataSchema = z.object({
90
+ userId: z.string().min(1),
91
+ balance: z.number().int(),
92
+ membershipLabel: z.string().optional(),
93
+ items: z.array(
94
+ z.object({
95
+ usageRecordRef: z.string(),
96
+ title: z.string(),
97
+ amount: z.string(),
98
+ usedAt: z.string().optional(),
99
+ description: z.string().optional(),
100
+ }),
101
+ ),
102
+ total: z.number().int().min(0),
103
+ });
104
+ export type MemberPointUsageListCardData = z.infer<typeof memberPointUsageListCardDataSchema>;
105
+
106
+ /** member-enrollment-form(入会表单;015 升级:授权预填 + consent + formVersion) */
107
+ export const memberEnrollmentFormCardDataSchema = z.object({
108
+ userId: z.string().min(1),
109
+ state: memberStateSchema,
110
+ /** 表单版本(幂等键组成部分) */
111
+ formVersion: z.string().min(1),
112
+ /** 表单字段(readOnly = 授权预填锁定;prefill = 预填值) */
113
+ fields: z.array(
114
+ z.object({
115
+ fieldId: z.string(),
116
+ label: z.string(),
117
+ type: z.enum(['text', 'tel', 'select', 'date']),
118
+ required: z.boolean(),
119
+ readOnly: z.boolean().optional(),
120
+ prefill: z.string().optional(),
121
+ placeholder: z.string().optional(),
122
+ options: z.array(z.string()).optional(),
123
+ }),
124
+ ),
125
+ /** 隐私/条款同意项(required 必勾) */
126
+ consents: z.array(
127
+ z.object({
128
+ consentId: z.string(),
129
+ version: z.string(),
130
+ title: z.string(),
131
+ required: z.boolean(),
132
+ url: z.string().optional(),
133
+ }),
134
+ ),
135
+ /** 支付宝授权状态(required → 渲染授权按钮;authorized → mobile 预填 readOnly) */
136
+ authorization: z.object({
137
+ status: z.enum(['required', 'authorized', 'unavailable']),
138
+ mobileMasked: z.string().optional(),
139
+ }),
140
+ /** 手动填写模式提示(授权不可用降级) */
141
+ manualNotice: z.string().optional(),
142
+ });
143
+ export type MemberEnrollmentFormCardData = z.infer<typeof memberEnrollmentFormCardDataSchema>;
144
+
145
+ /** member-enrollment-status(入会结果状态卡;pending 态附显式刷新按钮,R4) */
146
+ export const memberEnrollmentStatusCardDataSchema = z.object({
147
+ userId: z.string().min(1),
148
+ attemptRef: z.string().optional(),
149
+ state: z.enum(['pending_confirmation', 'confirmation_timeout', 'active', 'failed']),
150
+ memberRef: z.string().optional(),
151
+ message: z.string().optional(),
152
+ });
153
+ export type MemberEnrollmentStatusCardData = z.infer<typeof memberEnrollmentStatusCardDataSchema>;
154
+
155
+ /** member-enrollment-cta:开卡入会引导卡(点击跳转 alipays 链接) */
156
+ export const memberEnrollmentCtaCardDataSchema = z.object({
157
+ userId: z.string().min(1),
158
+ authState: z.literal('authenticated'),
159
+ state: z.enum(['not_enrolled', 'closed']),
160
+ /**
161
+ * alipays:// 深链,由部署配置注入;**可选透传素材**——出卡不依赖它,
162
+ * 壳能力(getLoginState/openGate)缺席时兜底 openLink 用,未配置恒出卡(无此字段)
163
+ */
164
+ enrollUrl: z.string().min(1).refine((value) => value.startsWith('alipays://') || value.startsWith('https://'), {
165
+ message: 'enrollUrl must use alipays:// or https://',
166
+ }).optional(),
167
+ title: z.string().optional(),
168
+ subtitle: z.string().optional(),
169
+ /**
170
+ * 入会权益提示列表(工具层注入通用文案;不得包含折扣/免费停车等未经系统确认的承诺)。
171
+ * 缺省不展示,由渲染层按有无决定是否渲染区块。
172
+ */
173
+ perks: z.array(z.string()).optional(),
174
+ });
175
+ export type MemberEnrollmentCtaCardData = z.infer<typeof memberEnrollmentCtaCardDataSchema>;
176
+
177
+ /**
178
+ * member-login-cta:未登录引导卡(点击跳转商场小程序登录页,登录后回到智能体)。
179
+ *
180
+ * 与 member-enrollment-cta 的分界:**登录 ≠ 入会**。这张卡的前提是「还没有身份」,
181
+ * 故不带 userId / authState——那两个字段此刻根本拿不到。开卡入会是登录之后的下一步。
182
+ *
183
+ * 说明文案由组件写死(「登录后可解锁全部功能」):曾按触发场景分别注入,实测各处理由
184
+ * 大同小异,留一个 optional 字段只会变成没人写的死字段。真要分场景再加回来。
185
+ */
186
+ export const memberLoginCtaCardDataSchema = z.object({
187
+ /**
188
+ * 商场小程序登录页深链,由部署配置注入;**可选透传素材**——出卡不依赖它,
189
+ * 壳能力(getLoginState)缺席时兜底 openLink 用,未配置恒出卡(无此字段)
190
+ */
191
+ loginUrl: z.string().min(1).refine((value) => value.startsWith('alipays://') || value.startsWith('https://'), {
192
+ message: 'loginUrl must use alipays:// or https://',
193
+ }).optional(),
194
+ title: z.string().optional(),
195
+ });
196
+ export type MemberLoginCtaCardData = z.infer<typeof memberLoginCtaCardDataSchema>;
197
+
198
+ /** 7 卡 schema map(cardType → dataSchema;券面 0.4.0 净删——promotion 承接) */
199
+ export const memberCardDataSchemas = {
200
+ 'member-info': memberInfoCardDataSchema,
201
+ 'member-card-summary': memberCardSummaryCardDataSchema,
202
+ 'member-point-usage-list': memberPointUsageListCardDataSchema,
203
+ 'member-enrollment-form': memberEnrollmentFormCardDataSchema,
204
+ 'member-enrollment-status': memberEnrollmentStatusCardDataSchema,
205
+ 'member-enrollment-cta': memberEnrollmentCtaCardDataSchema,
206
+ 'member-login-cta': memberLoginCtaCardDataSchema,
207
+ } as const;
208
+
209
+ export type MemberCardType = keyof typeof memberCardDataSchemas;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * 停车域卡片数据契约(015:parking 5 卡 zod schema,TIER1)。
3
+ * 源:POC parking 11 卡型收敛(V6/D7);金额展示层字符串化(D11)。
4
+ */
5
+ import { z } from 'zod';
6
+
7
+ /** parking-payment:缴费确认主卡(报价 + 权益 + revision) */
8
+
9
+ /**
10
+ * 厂商协议 openParkingPay 的 orderInfo zod schema(与 ParkingPayOrderInfo 对齐)。
11
+ * 壳用这些字段自己拼确认页,不回查费。parkingId 协议已定 string。
12
+ */
13
+ export const parkingOrderInfoSchema = z.object({
14
+ /** 停车秒数 */
15
+ staySecond: z.number().int().nonnegative().optional(),
16
+ /** 停车费,单位分 */
17
+ parkingFee: z.number().int().nonnegative().optional(),
18
+ /** 车牌绑定状态 */
19
+ bindingStatus: z.number().int().optional(),
20
+ /** 车牌 */
21
+ plateNumber: z.string().min(1).optional(),
22
+ /** 1=在场缴费,2=逃费/欠费 */
23
+ orderType: z.number().int(),
24
+ /** 车场侧订单号 */
25
+ spParkingOrderId: z.string().min(1).optional(),
26
+ /** 车场 ID(协议定 string) */
27
+ parkingId: z.string().min(1),
28
+ /** 入场时间 */
29
+ entranceTime: z.string().min(1).optional(),
30
+ /** 逗号分隔的能力枚举,如 "LEVEL_DISCOUNT,POINT_DISCOUNT,COUPON" */
31
+ supportDiscount: z.string().min(1).optional(),
32
+ });
33
+ export const parkingPaymentCardDataSchema = z.object({
34
+ quoteRef: z.string().min(1),
35
+ plateNo: z.string().min(1),
36
+ entryAtText: z.string().optional(),
37
+ durationText: z.string().optional(),
38
+ /** 费用明细(label + 金额字符串) */
39
+ feeItems: z.array(z.object({ name: z.string(), amountText: z.string() })),
40
+ totalText: z.string(),
41
+ discountText: z.string().optional(),
42
+ payableText: z.string(),
43
+ /** 当前请求主体的会员状态;旧历史卡可缺省。 */
44
+ membership: z
45
+ .object({
46
+ state: z.enum(['unauthenticated', 'unknown', 'active', 'not_enrolled', 'closed', 'unavailable']),
47
+ availablePoints: z.number().int().min(0).optional(),
48
+ })
49
+ .optional(),
50
+ /** 服务端归一化后的支付入口;客户端不解析权益标题。 */
51
+ paymentOptions: z
52
+ .object({
53
+ direct: z.object({
54
+ /** 已应用积分时 Provider 未必能预览无积分金额;缺省时客户端不得提前承诺金额。 */
55
+ payableText: z.string().optional(),
56
+ benefitRefs: z.array(z.string()),
57
+ }),
58
+ points: z
59
+ .discriminatedUnion('kind', [
60
+ z.object({
61
+ kind: z.literal('exact'),
62
+ pointsToUse: z.number().int().positive(),
63
+ payableText: z.string(),
64
+ benefitRefs: z.array(z.string()).min(1),
65
+ }),
66
+ z.object({
67
+ kind: z.literal('requires_reprice'),
68
+ title: z.string().min(1),
69
+ benefitRefs: z.array(z.string()).min(1),
70
+ }),
71
+ ])
72
+ .optional(),
73
+ })
74
+ .optional(),
75
+ /** 已生效权益 */
76
+ appliedBenefits: z.array(
77
+ z.object({
78
+ benefitRef: z.string(),
79
+ title: z.string(),
80
+ discountText: z.string().optional(),
81
+ }),
82
+ ),
83
+ /** 可变更权益(reprice 候选;autoSelected 预勾选) */
84
+ selectableBenefits: z.array(
85
+ z.object({
86
+ benefitRef: z.string(),
87
+ title: z.string(),
88
+ autoSelected: z.boolean(),
89
+ description: z.string().optional(),
90
+ /** 权益类型(券 / 积分抵扣 / 等级权益);客户端据此分组与判定互斥,不得从标题猜测 */
91
+ kind: z.enum(['points', 'discount', 'coupon', 'other']).optional(),
92
+ /** 预估优惠金额文案(本地展示用;权威金额以 reprice 回填为准) */
93
+ discountText: z.string().optional(),
94
+ /**
95
+ * 与本权益互斥的类型。三类优惠默认可叠加,互斥是厂商按券配置的例外——
96
+ * 标记 ['points'] 表示该券不可与积分抵扣叠加。判定双向生效。
97
+ */
98
+ exclusiveWithKinds: z.array(z.enum(['points', 'discount', 'coupon'])).optional(),
99
+ }),
100
+ ),
101
+ /** 乐观锁(提交校验;过期 → 提示重新查询) */
102
+ revision: z.number().int().min(1),
103
+ caveats: z.array(z.string()),
104
+ /**
105
+ * 支付交接包:客户端卡据 kind 调壳 tbox 能力收款——
106
+ * kind:'pay' → openParkingPay(传 orderInfo,壳跳确认页/收银台);kind:'unpaid' → openParkingUnpaidOrder(传 orderNo)。
107
+ * 缺省 = 走内部 confirm-payment(line)支付链。
108
+ */
109
+ payHandoff: z
110
+ .object({
111
+ kind: z.enum(['pay', 'unpaid']),
112
+ parkingStoreId: z.string().min(1).optional(),
113
+ orderInfo: parkingOrderInfoSchema.optional(),
114
+ orderNo: z.string().min(1).optional(),
115
+ })
116
+ .optional(),
117
+ /**
118
+ * 支付进度(缺省 = 未发起):一经写入这张报价单即终结,按钮恒置灰。
119
+ * 报价是一次性的(token singleUse + revision 乐观锁),发起支付后不可再提交。
120
+ */
121
+ settlement: z
122
+ .object({
123
+ state: z.enum(['paying', 'paid', 'failed']),
124
+ text: z.string().min(1),
125
+ })
126
+ .optional(),
127
+ });
128
+ export type ParkingPaymentCardData = z.infer<
129
+ typeof parkingPaymentCardDataSchema
130
+ >;
131
+
132
+ /** parking-payment-result:支付结果(reconcile 权威) */
133
+ export const parkingPaymentResultCardDataSchema = z.object({
134
+ paymentRef: z.string().min(1),
135
+ plateNo: z.string(),
136
+ status: z.enum(['PAID', 'CANCELLED', 'UNKNOWN']),
137
+ amountText: z.string().optional(),
138
+ message: z.string(),
139
+ /** 失败/取消:可重新发起(重新查询报价) */
140
+ retryable: z.boolean().optional(),
141
+ /** 离场时限提醒(PAID:缴费后免费离场窗口,超时重新计费) */
142
+ leaveNotice: z.string().optional(),
143
+ /** 回执明细(车牌 / 停车时长 / 缴费时间等,服务端已格式化) */
144
+ details: z
145
+ .array(z.object({ label: z.string(), value: z.string() }))
146
+ .optional(),
147
+ });
148
+ export type ParkingPaymentResultCardData = z.infer<
149
+ typeof parkingPaymentResultCardDataSchema
150
+ >;
151
+
152
+ /** parking-payment-records:缴费记录 */
153
+ export const parkingPaymentRecordsCardDataSchema = z.object({
154
+ items: z.array(
155
+ z.object({
156
+ recordRef: z.string().min(1),
157
+ plateNo: z.string(),
158
+ amountText: z.string(),
159
+ paidAtText: z.string(),
160
+ status: z.enum(['PAID', 'REFUNDED', 'CLOSED', 'UNKNOWN']),
161
+ statusText: z.string().optional(),
162
+ parkName: z.string().optional(),
163
+ plazaName: z.string().optional(),
164
+ /** 展开态字段(entry/exit/单号脱敏) */
165
+ entryAtText: z.string().optional(),
166
+ exitAtText: z.string().optional(),
167
+ payOrderNoMasked: z.string().optional(),
168
+ orderCompleted: z.boolean().optional(),
169
+ invoiceEligible: z.boolean().optional(),
170
+ invoiceIssued: z.boolean().optional(),
171
+ }),
172
+ ),
173
+ total: z.number().int().min(0),
174
+ hasMore: z.boolean(),
175
+ /** 厂商口径 caveats(厂商事实,7 条并入) */
176
+ caveats: z.array(z.string()),
177
+ /** 详情加载、开票提交等动作反馈 */
178
+ message: z.string().optional(),
179
+ });
180
+ export type ParkingPaymentRecordsCardData = z.infer<
181
+ typeof parkingPaymentRecordsCardDataSchema
182
+ >;
183
+
184
+ /** parking-vehicle-management:绑定车辆列表(parked 缺省 = provider 无入场查询能力) */
185
+ export const parkingVehicleManagementCardDataSchema = z.object({
186
+ bound: z.array(z.object({
187
+ vehicleRef: z.string(),
188
+ plateNo: z.string(),
189
+ parked: z.boolean().optional(),
190
+ })),
191
+ message: z.string().optional(),
192
+ /**
193
+ * 会员状态(缺省 = 未探测):仅 not_enrolled 才展示入会引导。
194
+ * 与 parking-payment 的 membership 同源,避免两张卡对同一用户给出不同判断。
195
+ */
196
+ membership: z
197
+ .object({
198
+ state: z.enum(['unauthenticated', 'unknown', 'active', 'not_enrolled', 'closed', 'unavailable']),
199
+ })
200
+ .optional(),
201
+ });
202
+ export type ParkingVehicleManagementCardData = z.infer<
203
+ typeof parkingVehicleManagementCardDataSchema
204
+ >;
205
+
206
+ /**
207
+ * 中国大陆车牌正则(双端单一真源):server zod 入参与 client plateValid
208
+ * 同源引用——曾双份漂移(server 严格 / client 宽松 ≥5 字符)。
209
+ * 首位放开字母:支持省份汉字 + 字母(A-HJ-NP-Z,无 I/O)双形态首位。
210
+ * 普通牌 = 首位 + 字母 + 5 位字母数字(末位可含挂学警港澳)= 7 字符;
211
+ * 新能源 = 首位 + 字母 + 字母数字 + 5 位字母数字 = 8 字符。
212
+ */
213
+ export const PARKING_PLATE_PATTERN =
214
+ /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-HJ-NP-Z][A-HJ-NP-Z](?:[A-HJ-NP-Z0-9]{6}|[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳])$/;
215
+
216
+ /** parking-info:综合信息(三合一 kind;待支付单已并入 parking-payment) */
217
+ export const parkingInfoCardDataSchema = z.object({
218
+ kind: z.enum(['chargeRules', 'benefitPolicy', 'vehicleLocation']),
219
+ title: z.string().min(1),
220
+ entries: z.array(z.object({ label: z.string(), value: z.string() })),
221
+ });
222
+ export type ParkingInfoCardData = z.infer<typeof parkingInfoCardDataSchema>;
223
+
224
+ /** 5 卡 schema map(cardType → dataSchema) */
225
+ export const parkingCardDataSchemas = {
226
+ 'parking-payment': parkingPaymentCardDataSchema,
227
+ 'parking-payment-result': parkingPaymentResultCardDataSchema,
228
+ 'parking-payment-records': parkingPaymentRecordsCardDataSchema,
229
+ 'parking-vehicle-management': parkingVehicleManagementCardDataSchema,
230
+ 'parking-info': parkingInfoCardDataSchema,
231
+ } as const;
232
+
233
+ export type ParkingCardType = keyof typeof parkingCardDataSchemas;