cabloy 5.1.141 → 5.1.142
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/.cabloy-version +1 -1
- package/.github/workflows/playwright-e2e.yml +2 -2
- package/CHANGELOG.md +21 -0
- package/cabloy-docs/frontend/form-guide.md +47 -0
- package/cabloy-docs/frontend/markdown-guide.md +50 -17
- package/cabloy-docs/frontend/route-alias-guide.md +20 -0
- package/cabloy-docs/frontend/scripts.md +9 -11
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +25 -0
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +1 -1
- package/cabloy-docs/fullstack/quickstart.md +5 -4
- package/cabloy-docs/reference/repo-scripts.md +29 -42
- package/e2e/config/playwright.config.ts +34 -0
- package/e2e/scripts/e2e.ts +2 -27
- package/e2e/scripts/runE2e.ts +62 -0
- package/e2e/scripts/runE2eArgs.test.ts +75 -0
- package/e2e/scripts/runE2eArgs.ts +158 -0
- package/e2e/specs/{a-commerce/commerce.spec.ts → a-commerce.spec.ts} +361 -30
- package/e2e/specs/{cabloy-basic/account.spec.ts → account.spec.ts} +1 -2
- package/oxlint.config.ts +3 -0
- package/package.json +3 -9
- package/scripts/upgrade.ts +63 -68
- package/vona/packages-utils/lint/package.json +1 -1
- package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
- package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
- package/vona/pnpm-lock.yaml +41 -41
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +31 -15
- package/vona/src/suite/a-home/modules/home-user/package.json +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +15 -2
- package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +34 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +2 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +26 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/imageScene.markdown.ts +13 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +23 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/resource/locale/type.ts +1 -0
- package/zova/pnpm-lock.yaml +63 -18
- package/zova/src/front/config/config/config.cabloyBasicWeb.ts +3 -1
- package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +64 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +413 -134
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +76 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +67 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +7 -7
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +46 -9
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +50 -30
- package/zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +9 -15
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +10 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +11 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +723 -5
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +461 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/codeBlockLanguages.ts +42 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +20 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/test/lib/codeBlockLanguages.test.ts +25 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +26 -12
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockMockPayment.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockMockPayment.ts +8 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +13 -1
- package/zova/src/suite-vendor/a-pay/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -6
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
- package/e2e/config/playwright.basic.config.ts +0 -3
- package/e2e/config/playwright.commerce.config.ts +0 -3
- package/e2e/config/playwright.shared.config.ts +0 -38
- package/e2e/scripts/testE2eClean.ts +0 -56
- /package/e2e/specs/{cabloy-basic/basic.spec.ts → cabloy-basic.spec.ts} +0 -0
|
@@ -4,21 +4,97 @@ export default {
|
|
|
4
4
|
CartEmpty: 'Your cart is empty.',
|
|
5
5
|
Quantity: 'Quantity',
|
|
6
6
|
ClearCart: 'Clear cart',
|
|
7
|
+
Checkout: 'Checkout',
|
|
8
|
+
DeliveryAddress: 'Delivery address',
|
|
9
|
+
AddAddressBeforeCheckout: 'Add an address before checking out.',
|
|
10
|
+
PaymentMethod: 'Payment method',
|
|
11
|
+
Coupon: 'Coupon',
|
|
12
|
+
NoCoupon: 'No coupon',
|
|
13
|
+
CreateOrder: 'Create order',
|
|
14
|
+
Payment: 'Payment',
|
|
15
|
+
LoadingOrder: 'Loading order…',
|
|
16
|
+
OrderTitle: 'Order #{id}',
|
|
17
|
+
OrderStateAwaitingPayment: 'Awaiting payment',
|
|
18
|
+
OrderStatePaid: 'Paid',
|
|
19
|
+
OrderStateRefundRequested: 'Refund requested',
|
|
20
|
+
OrderStateRefundApproved: 'Refund approved',
|
|
21
|
+
OrderStateRefundRejected: 'Refund rejected',
|
|
22
|
+
OrderStateShipped: 'Shipped',
|
|
23
|
+
OrderStateRefunded: 'Refunded',
|
|
24
|
+
OrderStateCancelled: 'Cancelled',
|
|
25
|
+
OrderStateExpired: 'Expired',
|
|
26
|
+
DiscountAmount: 'Discount: {amount}',
|
|
27
|
+
Shipment: 'Shipment',
|
|
28
|
+
OrderLineSummary: '{quantity} × {unitPrice} = {lineTotal}',
|
|
29
|
+
PaymentStatusVerification: 'Payment status is verified before Commerce updates the order.',
|
|
30
|
+
StartPayment: 'Start payment',
|
|
31
|
+
PaymentSessionUnavailable: 'Payment session is unavailable. You can open your order.',
|
|
32
|
+
PaymentPreparationVerification: 'Payment preparation is being verified safely in the background.',
|
|
33
|
+
CheckPaymentStatus: 'Check payment status',
|
|
34
|
+
OpenOrder: 'Open order',
|
|
35
|
+
PaymentProviderReturnCanResume:
|
|
36
|
+
'You returned from the payment provider without completing payment. Your payment status has been refreshed. You can continue to payment or open your order.',
|
|
37
|
+
PaymentProviderReturn:
|
|
38
|
+
'You returned from the payment provider without completing payment. Your payment status has been refreshed. You can open your order.',
|
|
39
|
+
MockPaymentSimulator: 'Mock payment simulator',
|
|
40
|
+
MockPaymentSimulatorHelp:
|
|
41
|
+
'Available only when the server enables the development/test mock provider.',
|
|
42
|
+
PaymentSucceeded: 'Payment succeeded',
|
|
43
|
+
PaymentFailed: 'Payment failed',
|
|
44
|
+
CancelPayment: 'Cancel payment',
|
|
45
|
+
WaitingForPaymentSettlement: 'Waiting for verified provider confirmation and order settlement…',
|
|
46
|
+
PaymentSessionState: 'Payment session state: {state}',
|
|
47
|
+
PaymentSessionAndOrderState: 'Payment session: {paymentSessionState}; order: {orderState}.',
|
|
48
|
+
Unknown: 'unknown',
|
|
49
|
+
RefreshOrderStatus: 'Refresh order status',
|
|
50
|
+
PaymentCancelledByProvider:
|
|
51
|
+
'You returned from PayPal without completing payment. The order is still awaiting payment.',
|
|
52
|
+
PaymentApprovalRequired: 'Payment still needs your approval.',
|
|
53
|
+
PaymentProcessing:
|
|
54
|
+
'Your payment is still being processed. We will continue to verify it with the provider.',
|
|
55
|
+
PaymentOutcomeVerified: 'Payment outcome is verified. Commerce is still updating the order.',
|
|
56
|
+
PaymentConfirmationPending:
|
|
57
|
+
'Payment confirmation is still pending. We will continue to verify it with the provider.',
|
|
58
|
+
ContinueToPaymentHelp: 'Continue to the payment provider to complete payment.',
|
|
59
|
+
ContinueToPayment: 'Continue to payment',
|
|
60
|
+
PaymentPreparing: 'Payment is being prepared.',
|
|
61
|
+
PaymentPreparingRetry:
|
|
62
|
+
'Payment is being prepared; retrying in about {retryAfterSeconds} seconds.',
|
|
63
|
+
RefreshPaymentStatus: 'Refresh payment status',
|
|
64
|
+
PaymentCompleted: 'Payment is complete. Confirming the order…',
|
|
65
|
+
EmbeddedCheckoutUnavailable:
|
|
66
|
+
'Embedded checkout will be available when this payment provider is configured.',
|
|
7
67
|
RemoveCartItem: 'Remove',
|
|
8
68
|
Confirm: 'Confirm',
|
|
69
|
+
Cancel: 'Cancel',
|
|
9
70
|
ShipOrder: 'Ship order',
|
|
10
71
|
RequestRefund: 'Request refund',
|
|
11
72
|
RefundRequestHelp: 'Refunds are for the whole unshipped order only.',
|
|
12
73
|
RefundReason: 'Reason for refund',
|
|
13
74
|
RefundDecisionReason: 'Decision reason',
|
|
75
|
+
RefundDecision: 'Refund decision',
|
|
76
|
+
RefundExecution: 'Refund execution',
|
|
77
|
+
ReviewRefund: 'Review refund',
|
|
14
78
|
ApproveRefund: 'Approve refund',
|
|
15
79
|
RejectRefund: 'Reject refund',
|
|
16
80
|
ExecuteRefund: 'Execute refund',
|
|
17
81
|
InspectRefundRecovery: 'Inspect refund recovery',
|
|
82
|
+
LoadingRefundRecovery: 'Loading refund recovery…',
|
|
83
|
+
RefundRecoveryUnavailable:
|
|
84
|
+
'Refund recovery is not available yet. Execute the refund first to create a provider refund submission.',
|
|
85
|
+
RefundRecoveryLoadFailed: 'Unable to load refund recovery details.',
|
|
86
|
+
RetryInspectRefundRecovery: 'Retry inspection',
|
|
18
87
|
ReconcileProviderRefund: 'Reconcile provider refund',
|
|
19
88
|
RetryRefund: 'Retry refund',
|
|
20
89
|
RefundRecoveryReason: 'Recovery reason',
|
|
21
90
|
AcknowledgeRefundRetryRisk: 'I understand this retry reuses the original provider request.',
|
|
22
91
|
RefundRecoveryState: 'Provider operation: {state}; attempts: {attemptCount}',
|
|
23
92
|
RefundRecoveryProviderRefundId: 'Provider refund ID: {providerRefundId}',
|
|
93
|
+
RefundAwaitingProviderConfirmation:
|
|
94
|
+
'The refund is waiting for a verified provider confirmation. Do not resubmit it.',
|
|
95
|
+
MockRefundSimulatorHelp:
|
|
96
|
+
'Development/test mock only. This sends the signed provider webhook used to settle the refund.',
|
|
97
|
+
CompleteMockRefund: 'Complete mock refund',
|
|
98
|
+
MockRefundCompletionFailed: 'Unable to complete the mock refund. Try again.',
|
|
99
|
+
MockRefundCompleted: 'The mock refund was completed. The order is being settled.',
|
|
24
100
|
};
|
|
@@ -4,21 +4,88 @@ export default {
|
|
|
4
4
|
CartEmpty: '购物车为空。',
|
|
5
5
|
Quantity: '数量',
|
|
6
6
|
ClearCart: '清空购物车',
|
|
7
|
+
Checkout: '结算',
|
|
8
|
+
DeliveryAddress: '收货地址',
|
|
9
|
+
AddAddressBeforeCheckout: '请先添加收货地址再结算。',
|
|
10
|
+
PaymentMethod: '支付方式',
|
|
11
|
+
Coupon: '优惠券',
|
|
12
|
+
NoCoupon: '不使用优惠券',
|
|
13
|
+
CreateOrder: '创建订单',
|
|
14
|
+
Payment: '支付',
|
|
15
|
+
LoadingOrder: '正在加载订单……',
|
|
16
|
+
OrderTitle: '订单 #{id}',
|
|
17
|
+
OrderStateAwaitingPayment: '待支付',
|
|
18
|
+
OrderStatePaid: '已支付',
|
|
19
|
+
OrderStateRefundRequested: '申请退款',
|
|
20
|
+
OrderStateRefundApproved: '退款已批准',
|
|
21
|
+
OrderStateRefundRejected: '退款被拒绝',
|
|
22
|
+
OrderStateShipped: '已发货',
|
|
23
|
+
OrderStateRefunded: '已退款',
|
|
24
|
+
OrderStateCancelled: '已取消',
|
|
25
|
+
OrderStateExpired: '已过期',
|
|
26
|
+
DiscountAmount: '折扣:{amount}',
|
|
27
|
+
Shipment: '发货信息',
|
|
28
|
+
OrderLineSummary: '{quantity} × {unitPrice} = {lineTotal}',
|
|
29
|
+
PaymentStatusVerification: '在商城更新订单前,会先验证支付状态。',
|
|
30
|
+
StartPayment: '开始支付',
|
|
31
|
+
PaymentSessionUnavailable: '支付会话不可用。你可以打开订单。',
|
|
32
|
+
PaymentPreparationVerification: '正在后台安全地验证支付准备状态。',
|
|
33
|
+
CheckPaymentStatus: '检查支付状态',
|
|
34
|
+
OpenOrder: '打开订单',
|
|
35
|
+
PaymentProviderReturnCanResume:
|
|
36
|
+
'你在未完成支付的情况下从支付服务商返回。支付状态已刷新,你可以继续支付或打开订单。',
|
|
37
|
+
PaymentProviderReturn: '你在未完成支付的情况下从支付服务商返回。支付状态已刷新,你可以打开订单。',
|
|
38
|
+
MockPaymentSimulator: '模拟支付工具',
|
|
39
|
+
MockPaymentSimulatorHelp: '仅当服务端启用开发/测试模拟支付服务商时可用。',
|
|
40
|
+
PaymentSucceeded: '支付成功',
|
|
41
|
+
PaymentFailed: '支付失败',
|
|
42
|
+
CancelPayment: '取消支付',
|
|
43
|
+
WaitingForPaymentSettlement: '正在等待验证后的服务商确认和订单结算……',
|
|
44
|
+
PaymentSessionState: '支付会话状态:{state}',
|
|
45
|
+
PaymentSessionAndOrderState: '支付会话:{paymentSessionState};订单:{orderState}。',
|
|
46
|
+
Unknown: '未知',
|
|
47
|
+
RefreshOrderStatus: '刷新订单状态',
|
|
48
|
+
PaymentCancelledByProvider: '你在未完成支付的情况下从 PayPal 返回。订单仍在等待支付。',
|
|
49
|
+
PaymentApprovalRequired: '支付仍需要你的确认。',
|
|
50
|
+
PaymentProcessing: '支付仍在处理中,我们会继续向服务商核实支付状态。',
|
|
51
|
+
PaymentOutcomeVerified: '支付结果已验证,商城仍在更新订单。',
|
|
52
|
+
PaymentConfirmationPending: '支付确认仍在等待中,我们会继续向服务商核实支付状态。',
|
|
53
|
+
ContinueToPaymentHelp: '请前往支付服务商完成支付。',
|
|
54
|
+
ContinueToPayment: '继续支付',
|
|
55
|
+
PaymentPreparing: '正在准备支付。',
|
|
56
|
+
PaymentPreparingRetry: '正在准备支付;约 {retryAfterSeconds} 秒后重试。',
|
|
57
|
+
RefreshPaymentStatus: '刷新支付状态',
|
|
58
|
+
PaymentCompleted: '支付已完成,正在确认订单……',
|
|
59
|
+
EmbeddedCheckoutUnavailable: '配置此支付服务商后,即可使用嵌入式结算。',
|
|
7
60
|
RemoveCartItem: '移除',
|
|
8
61
|
Confirm: '确认',
|
|
62
|
+
Cancel: '取消',
|
|
9
63
|
ShipOrder: '发货',
|
|
10
64
|
RequestRefund: '申请退款',
|
|
11
65
|
RefundRequestHelp: '退款仅适用于整单且尚未发货的订单。',
|
|
12
66
|
RefundReason: '退款原因',
|
|
13
67
|
RefundDecisionReason: '审批原因',
|
|
68
|
+
RefundDecision: '退款审批',
|
|
69
|
+
RefundExecution: '退款执行',
|
|
70
|
+
ReviewRefund: '审核退款',
|
|
14
71
|
ApproveRefund: '批准退款',
|
|
15
72
|
RejectRefund: '拒绝退款',
|
|
16
73
|
ExecuteRefund: '执行退款',
|
|
17
74
|
InspectRefundRecovery: '查看退款恢复',
|
|
75
|
+
LoadingRefundRecovery: '正在加载退款恢复信息……',
|
|
76
|
+
RefundRecoveryUnavailable: '退款恢复暂不可用。请先执行退款,以创建服务商退款提交记录。',
|
|
77
|
+
RefundRecoveryLoadFailed: '无法加载退款恢复信息。',
|
|
78
|
+
RetryInspectRefundRecovery: '重试查看恢复',
|
|
18
79
|
ReconcileProviderRefund: '对账支付服务商退款',
|
|
19
80
|
RetryRefund: '重试退款',
|
|
20
81
|
RefundRecoveryReason: '恢复原因',
|
|
21
82
|
AcknowledgeRefundRetryRisk: '我了解本次重试会复用原支付服务商请求。',
|
|
22
83
|
RefundRecoveryState: '服务商操作:{state};尝试次数:{attemptCount}',
|
|
23
84
|
RefundRecoveryProviderRefundId: '服务商退款 ID:{providerRefundId}',
|
|
85
|
+
RefundAwaitingProviderConfirmation: '退款正在等待已验证的支付服务商确认,请勿重复提交。',
|
|
86
|
+
MockRefundSimulatorHelp:
|
|
87
|
+
'仅用于开发/测试模拟支付服务商,会发送用于结算退款的已签名服务商 Webhook。',
|
|
88
|
+
CompleteMockRefund: '完成模拟退款',
|
|
89
|
+
MockRefundCompletionFailed: '无法完成模拟退款,请重试。',
|
|
90
|
+
MockRefundCompleted: '模拟退款已完成,订单正在结算。',
|
|
24
91
|
};
|
|
@@ -60,7 +60,7 @@ export class ControllerPageCart extends BeanControllerPageBase {
|
|
|
60
60
|
{items.length > 0 && (
|
|
61
61
|
<>
|
|
62
62
|
<RouterLink class="btn btn-primary btn-sm" to={this._getCheckoutPagePath()}>
|
|
63
|
-
Checkout
|
|
63
|
+
{this.scope.locale.Checkout()}
|
|
64
64
|
</RouterLink>
|
|
65
65
|
<button class="btn btn-outline btn-sm" onClick={() => this.clear()}>
|
|
66
66
|
{this.scope.locale.ClearCart()}
|
|
@@ -84,10 +84,10 @@ export class ControllerPageCheckout extends BeanControllerPageBase {
|
|
|
84
84
|
return (
|
|
85
85
|
<ZPage>
|
|
86
86
|
<section class="mx-auto max-w-3xl p-6">
|
|
87
|
-
<h1 class="text-3xl font-semibold">Checkout</h1>
|
|
87
|
+
<h1 class="text-3xl font-semibold">{this.scope.locale.Checkout()}</h1>
|
|
88
88
|
<div class="mt-6 space-y-6">
|
|
89
89
|
<fieldset class="space-y-2">
|
|
90
|
-
<legend class="font-semibold">
|
|
90
|
+
<legend class="font-semibold">{this.scope.locale.DeliveryAddress()}</legend>
|
|
91
91
|
{addresses.map(address => (
|
|
92
92
|
<label class="flex cursor-pointer gap-3 rounded border border-base-300 p-3">
|
|
93
93
|
<input
|
|
@@ -102,11 +102,11 @@ export class ControllerPageCheckout extends BeanControllerPageBase {
|
|
|
102
102
|
</label>
|
|
103
103
|
))}
|
|
104
104
|
{addresses.length === 0 && (
|
|
105
|
-
<p class="text-base-content/70">
|
|
105
|
+
<p class="text-base-content/70">{this.scope.locale.AddAddressBeforeCheckout()}</p>
|
|
106
106
|
)}
|
|
107
107
|
</fieldset>
|
|
108
108
|
<fieldset class="space-y-2">
|
|
109
|
-
<legend class="font-semibold">
|
|
109
|
+
<legend class="font-semibold">{this.scope.locale.PaymentMethod()}</legend>
|
|
110
110
|
{paymentMethods?.items.map(method => (
|
|
111
111
|
<label class="flex cursor-pointer gap-3 rounded border border-base-300 p-3">
|
|
112
112
|
<input
|
|
@@ -120,10 +120,10 @@ export class ControllerPageCheckout extends BeanControllerPageBase {
|
|
|
120
120
|
))}
|
|
121
121
|
</fieldset>
|
|
122
122
|
<fieldset class="space-y-2">
|
|
123
|
-
<legend class="font-semibold">Coupon</legend>
|
|
123
|
+
<legend class="font-semibold">{this.scope.locale.Coupon()}</legend>
|
|
124
124
|
<label class="flex cursor-pointer gap-3 rounded border border-base-300 p-3">
|
|
125
125
|
<input type="radio" name="coupon" value="" v-model={this.couponGrantId} />
|
|
126
|
-
<span>
|
|
126
|
+
<span>{this.scope.locale.NoCoupon()}</span>
|
|
127
127
|
</label>
|
|
128
128
|
{coupons.map(coupon => (
|
|
129
129
|
<label class="flex cursor-pointer gap-3 rounded border border-base-300 p-3">
|
|
@@ -144,7 +144,7 @@ export class ControllerPageCheckout extends BeanControllerPageBase {
|
|
|
144
144
|
disabled={!this.addressId || !this.providerCandidateKey || this.submitting}
|
|
145
145
|
onClick={() => this.checkout()}
|
|
146
146
|
>
|
|
147
|
-
|
|
147
|
+
{this.scope.locale.CreateOrder()}
|
|
148
148
|
</button>
|
|
149
149
|
</div>
|
|
150
150
|
</section>
|
|
@@ -32,6 +32,31 @@ export class ControllerPageOrder extends BeanControllerPageBase {
|
|
|
32
32
|
return this.$$modelOrderMine.requestRefund(this.orderId);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
getOrderStateText(state: string) {
|
|
36
|
+
switch (state) {
|
|
37
|
+
case 'awaiting_payment':
|
|
38
|
+
return this.scope.locale.OrderStateAwaitingPayment();
|
|
39
|
+
case 'paid':
|
|
40
|
+
return this.scope.locale.OrderStatePaid();
|
|
41
|
+
case 'refund_requested':
|
|
42
|
+
return this.scope.locale.OrderStateRefundRequested();
|
|
43
|
+
case 'refund_approved':
|
|
44
|
+
return this.scope.locale.OrderStateRefundApproved();
|
|
45
|
+
case 'refund_rejected':
|
|
46
|
+
return this.scope.locale.OrderStateRefundRejected();
|
|
47
|
+
case 'shipped':
|
|
48
|
+
return this.scope.locale.OrderStateShipped();
|
|
49
|
+
case 'refunded':
|
|
50
|
+
return this.scope.locale.OrderStateRefunded();
|
|
51
|
+
case 'cancelled':
|
|
52
|
+
return this.scope.locale.OrderStateCancelled();
|
|
53
|
+
case 'expired':
|
|
54
|
+
return this.scope.locale.OrderStateExpired();
|
|
55
|
+
default:
|
|
56
|
+
return state;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
refundReason = '';
|
|
36
61
|
refundRequestIdempotencyKey?: string;
|
|
37
62
|
|
|
@@ -51,12 +76,17 @@ export class ControllerPageOrder extends BeanControllerPageBase {
|
|
|
51
76
|
return (
|
|
52
77
|
<ZPage>
|
|
53
78
|
<section class="mx-auto max-w-4xl p-6">
|
|
54
|
-
{!order && !query?.error &&
|
|
79
|
+
{!order && !query?.error && (
|
|
80
|
+
<p class="text-base-content/70">{this.scope.locale.LoadingOrder()}</p>
|
|
81
|
+
)}
|
|
55
82
|
{order && (
|
|
56
83
|
<>
|
|
57
|
-
<h1 class="text-3xl font-semibold">
|
|
84
|
+
<h1 class="text-3xl font-semibold">
|
|
85
|
+
{this.scope.locale.OrderTitle({ id: order.id })}
|
|
86
|
+
</h1>
|
|
58
87
|
<p class="mt-2 text-base-content/70">
|
|
59
|
-
{order.state} · $
|
|
88
|
+
{this.getOrderStateText(order.state)} · $
|
|
89
|
+
{(order.payableTotalCents / 100).toFixed(2)}
|
|
60
90
|
</p>
|
|
61
91
|
{order.state === 'paid' && !order.shipment && (
|
|
62
92
|
<article class="card mt-6 border border-base-300 bg-base-100 shadow-sm">
|
|
@@ -85,7 +115,7 @@ export class ControllerPageOrder extends BeanControllerPageBase {
|
|
|
85
115
|
<div class="mt-6 grid gap-6 md:grid-cols-2">
|
|
86
116
|
<article class="card border border-base-300 bg-base-100 shadow-sm">
|
|
87
117
|
<div class="card-body">
|
|
88
|
-
<h2 class="card-title">
|
|
118
|
+
<h2 class="card-title">{this.scope.locale.DeliveryAddress()}</h2>
|
|
89
119
|
<p>{order.addressSnapshot.recipientName}</p>
|
|
90
120
|
<p>{order.addressSnapshot.addressLine1}</p>
|
|
91
121
|
<p>
|
|
@@ -96,15 +126,19 @@ export class ControllerPageOrder extends BeanControllerPageBase {
|
|
|
96
126
|
</article>
|
|
97
127
|
<article class="card border border-base-300 bg-base-100 shadow-sm">
|
|
98
128
|
<div class="card-body">
|
|
99
|
-
<h2 class="card-title">Payment</h2>
|
|
129
|
+
<h2 class="card-title">{this.scope.locale.Payment()}</h2>
|
|
100
130
|
<p>{order.currency}</p>
|
|
101
|
-
<p>
|
|
131
|
+
<p>
|
|
132
|
+
{this.scope.locale.DiscountAmount({
|
|
133
|
+
amount: `$${(order.discountCents / 100).toFixed(2)}`,
|
|
134
|
+
})}
|
|
135
|
+
</p>
|
|
102
136
|
</div>
|
|
103
137
|
</article>
|
|
104
138
|
{order.shipment && (
|
|
105
139
|
<article class="card border border-base-300 bg-base-100 shadow-sm">
|
|
106
140
|
<div class="card-body">
|
|
107
|
-
<h2 class="card-title">Shipment</h2>
|
|
141
|
+
<h2 class="card-title">{this.scope.locale.Shipment()}</h2>
|
|
108
142
|
<p>{order.shipment.carrier}</p>
|
|
109
143
|
<p>{order.shipment.trackingNumber}</p>
|
|
110
144
|
<p class="text-sm text-base-content/70">
|
|
@@ -121,8 +155,11 @@ export class ControllerPageOrder extends BeanControllerPageBase {
|
|
|
121
155
|
<h2 class="card-title">{line.titleSnapshot}</h2>
|
|
122
156
|
<p class="text-sm text-base-content/70">{line.skuCodeSnapshot}</p>
|
|
123
157
|
<p>
|
|
124
|
-
{
|
|
125
|
-
|
|
158
|
+
{this.scope.locale.OrderLineSummary({
|
|
159
|
+
quantity: line.quantity,
|
|
160
|
+
unitPrice: `$${(line.unitPriceCents / 100).toFixed(2)}`,
|
|
161
|
+
lineTotal: `$${(line.lineTotalCents / 100).toFixed(2)}`,
|
|
162
|
+
})}
|
|
126
163
|
</p>
|
|
127
164
|
</div>
|
|
128
165
|
</article>
|
|
@@ -165,7 +165,7 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
165
165
|
} catch (error) {
|
|
166
166
|
if (isNotFoundError(error)) {
|
|
167
167
|
this.paymentSessionUnavailable = true;
|
|
168
|
-
this.message =
|
|
168
|
+
this.message = this.scope.locale.PaymentSessionUnavailable();
|
|
169
169
|
return;
|
|
170
170
|
}
|
|
171
171
|
throw error;
|
|
@@ -218,16 +218,16 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
218
218
|
private _pendingMessage() {
|
|
219
219
|
if (this.pendingSessionState === 'requires_action') {
|
|
220
220
|
return this.$query.providerResult === 'cancel'
|
|
221
|
-
?
|
|
222
|
-
:
|
|
221
|
+
? this.scope.locale.PaymentCancelledByProvider()
|
|
222
|
+
: this.scope.locale.PaymentApprovalRequired();
|
|
223
223
|
}
|
|
224
224
|
if (this.pendingSessionState === 'processing') {
|
|
225
|
-
return
|
|
225
|
+
return this.scope.locale.PaymentProcessing();
|
|
226
226
|
}
|
|
227
227
|
if (['succeeded', 'failed', 'cancelled', 'expired'].includes(this.pendingSessionState ?? '')) {
|
|
228
|
-
return
|
|
228
|
+
return this.scope.locale.PaymentOutcomeVerified();
|
|
229
229
|
}
|
|
230
|
-
return
|
|
230
|
+
return this.scope.locale.PaymentConfirmationPending();
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
protected render() {
|
|
@@ -250,25 +250,23 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
250
250
|
return (
|
|
251
251
|
<ZPage>
|
|
252
252
|
<section class="mx-auto max-w-xl p-6">
|
|
253
|
-
<h1 class="text-3xl font-semibold">Payment</h1>
|
|
254
|
-
<p class="mt-3 text-base-content/70">
|
|
255
|
-
Payment status is verified before Commerce updates the order.
|
|
256
|
-
</p>
|
|
253
|
+
<h1 class="text-3xl font-semibold">{this.scope.locale.Payment()}</h1>
|
|
254
|
+
<p class="mt-3 text-base-content/70">{this.scope.locale.PaymentStatusVerification()}</p>
|
|
257
255
|
{isCreated && (
|
|
258
256
|
<button
|
|
259
257
|
class="btn btn-primary mt-6"
|
|
260
258
|
disabled={this.submitting}
|
|
261
259
|
onClick={() => this.start()}
|
|
262
260
|
>
|
|
263
|
-
|
|
261
|
+
{this.scope.locale.StartPayment()}
|
|
264
262
|
</button>
|
|
265
263
|
)}
|
|
266
264
|
{(isStarting || this.paymentSessionUnavailable) && (
|
|
267
265
|
<section class="mt-6 rounded border border-base-300 p-4" aria-live="polite">
|
|
268
266
|
<p class="text-base-content/70">
|
|
269
267
|
{this.paymentSessionUnavailable
|
|
270
|
-
?
|
|
271
|
-
:
|
|
268
|
+
? this.scope.locale.PaymentSessionUnavailable()
|
|
269
|
+
: this.scope.locale.PaymentPreparationVerification()}
|
|
272
270
|
</p>
|
|
273
271
|
<div class="mt-3 flex flex-wrap gap-3">
|
|
274
272
|
{!this.paymentSessionUnavailable && (
|
|
@@ -278,11 +276,11 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
278
276
|
disabled={this.submitting || this.waitingForOrder}
|
|
279
277
|
onClick={() => this.reconcile()}
|
|
280
278
|
>
|
|
281
|
-
|
|
279
|
+
{this.scope.locale.CheckPaymentStatus()}
|
|
282
280
|
</button>
|
|
283
281
|
)}
|
|
284
282
|
<button class="btn btn-outline" type="button" onClick={() => this.openOrder()}>
|
|
285
|
-
|
|
283
|
+
{this.scope.locale.OpenOrder()}
|
|
286
284
|
</button>
|
|
287
285
|
</div>
|
|
288
286
|
</section>
|
|
@@ -291,8 +289,8 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
291
289
|
<section class="mt-6 rounded border border-base-300 p-4" aria-live="polite">
|
|
292
290
|
<p class="text-base-content/70">
|
|
293
291
|
{hasResumableCancelRedirect
|
|
294
|
-
?
|
|
295
|
-
:
|
|
292
|
+
? this.scope.locale.PaymentProviderReturnCanResume()
|
|
293
|
+
: this.scope.locale.PaymentProviderReturn()}
|
|
296
294
|
</p>
|
|
297
295
|
</section>
|
|
298
296
|
)}
|
|
@@ -300,6 +298,15 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
300
298
|
<ZPaymentNextAction
|
|
301
299
|
action={session?.nextAction}
|
|
302
300
|
disabled={this.submitting}
|
|
301
|
+
continueToPaymentHelpText={this.scope.locale.ContinueToPaymentHelp()}
|
|
302
|
+
continueToPaymentText={this.scope.locale.ContinueToPayment()}
|
|
303
|
+
embeddedCheckoutUnavailableText={this.scope.locale.EmbeddedCheckoutUnavailable()}
|
|
304
|
+
paymentCompletedText={this.scope.locale.PaymentCompleted()}
|
|
305
|
+
paymentPreparingText={this.scope.locale.PaymentPreparing()}
|
|
306
|
+
paymentPreparingRetryText={retryAfterSeconds =>
|
|
307
|
+
this.scope.locale.PaymentPreparingRetry({ retryAfterSeconds })
|
|
308
|
+
}
|
|
309
|
+
refreshPaymentStatusText={this.scope.locale.RefreshPaymentStatus()}
|
|
303
310
|
onRefresh={async () => {
|
|
304
311
|
await this.queryPaymentSession?.refetch();
|
|
305
312
|
}}
|
|
@@ -307,14 +314,14 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
307
314
|
)}
|
|
308
315
|
{isActionableCancelReturn && (
|
|
309
316
|
<button class="btn btn-outline mt-3" type="button" onClick={() => this.openOrder()}>
|
|
310
|
-
|
|
317
|
+
{this.scope.locale.OpenOrder()}
|
|
311
318
|
</button>
|
|
312
319
|
)}
|
|
313
320
|
{isMockSession && isActionable && (
|
|
314
321
|
<section class="mt-6 rounded border border-dashed border-base-300 p-4">
|
|
315
|
-
<h2 class="font-semibold">
|
|
322
|
+
<h2 class="font-semibold">{this.scope.locale.MockPaymentSimulator()}</h2>
|
|
316
323
|
<p class="mt-1 text-sm text-base-content/70">
|
|
317
|
-
|
|
324
|
+
{this.scope.locale.MockPaymentSimulatorHelp()}
|
|
318
325
|
</p>
|
|
319
326
|
<div class="mt-3 flex flex-wrap gap-3">
|
|
320
327
|
<button
|
|
@@ -322,32 +329,34 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
322
329
|
disabled={this.submitting || this.waitingForOrder}
|
|
323
330
|
onClick={() => this.settle('succeeded')}
|
|
324
331
|
>
|
|
325
|
-
|
|
332
|
+
{this.scope.locale.PaymentSucceeded()}
|
|
326
333
|
</button>
|
|
327
334
|
<button
|
|
328
335
|
class="btn btn-outline"
|
|
329
336
|
disabled={this.submitting || this.waitingForOrder}
|
|
330
337
|
onClick={() => this.settle('failed')}
|
|
331
338
|
>
|
|
332
|
-
|
|
339
|
+
{this.scope.locale.PaymentFailed()}
|
|
333
340
|
</button>
|
|
334
341
|
<button
|
|
335
342
|
class="btn btn-error btn-outline"
|
|
336
343
|
disabled={this.submitting || this.waitingForOrder}
|
|
337
344
|
onClick={() => this.settle('cancelled')}
|
|
338
345
|
>
|
|
339
|
-
|
|
346
|
+
{this.scope.locale.CancelPayment()}
|
|
340
347
|
</button>
|
|
341
348
|
</div>
|
|
342
349
|
</section>
|
|
343
350
|
)}
|
|
344
351
|
{this.waitingForOrder && (
|
|
345
352
|
<p class="mt-6 text-base-content/70" aria-live="polite">
|
|
346
|
-
|
|
353
|
+
{this.scope.locale.WaitingForPaymentSettlement()}
|
|
347
354
|
</p>
|
|
348
355
|
)}
|
|
349
356
|
{session && !isCreated && !isActionable && !this.waitingForOrder && (
|
|
350
|
-
<p class="mt-6 text-base-content/70">
|
|
357
|
+
<p class="mt-6 text-base-content/70">
|
|
358
|
+
{this.scope.locale.PaymentSessionState({ state: session.state })}
|
|
359
|
+
</p>
|
|
351
360
|
)}
|
|
352
361
|
{this.message && (
|
|
353
362
|
<div
|
|
@@ -360,8 +369,10 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
360
369
|
<span>{this.message}</span>
|
|
361
370
|
{this.pendingConfirmation && (
|
|
362
371
|
<p class="mt-1 text-sm">
|
|
363
|
-
|
|
364
|
-
|
|
372
|
+
{this.scope.locale.PaymentSessionAndOrderState({
|
|
373
|
+
paymentSessionState: this.pendingSessionState ?? this.scope.locale.Unknown(),
|
|
374
|
+
orderState: this.pendingOrderState ?? this.scope.locale.Unknown(),
|
|
375
|
+
})}
|
|
365
376
|
</p>
|
|
366
377
|
)}
|
|
367
378
|
</div>
|
|
@@ -371,6 +382,15 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
371
382
|
<ZPaymentNextAction
|
|
372
383
|
action={this.pendingNextAction}
|
|
373
384
|
disabled={this.submitting || this.waitingForOrder}
|
|
385
|
+
continueToPaymentHelpText={this.scope.locale.ContinueToPaymentHelp()}
|
|
386
|
+
continueToPaymentText={this.scope.locale.ContinueToPayment()}
|
|
387
|
+
embeddedCheckoutUnavailableText={this.scope.locale.EmbeddedCheckoutUnavailable()}
|
|
388
|
+
paymentCompletedText={this.scope.locale.PaymentCompleted()}
|
|
389
|
+
paymentPreparingText={this.scope.locale.PaymentPreparing()}
|
|
390
|
+
paymentPreparingRetryText={retryAfterSeconds =>
|
|
391
|
+
this.scope.locale.PaymentPreparingRetry({ retryAfterSeconds })
|
|
392
|
+
}
|
|
393
|
+
refreshPaymentStatusText={this.scope.locale.RefreshPaymentStatus()}
|
|
374
394
|
/>
|
|
375
395
|
) : isPendingTerminalState ? (
|
|
376
396
|
<button
|
|
@@ -379,7 +399,7 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
379
399
|
disabled={this.waitingForOrder}
|
|
380
400
|
onClick={() => this.refreshOrderStatus()}
|
|
381
401
|
>
|
|
382
|
-
|
|
402
|
+
{this.scope.locale.RefreshOrderStatus()}
|
|
383
403
|
</button>
|
|
384
404
|
) : (
|
|
385
405
|
<button
|
|
@@ -388,11 +408,11 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
388
408
|
disabled={this.submitting || this.waitingForOrder}
|
|
389
409
|
onClick={() => this.reconcile()}
|
|
390
410
|
>
|
|
391
|
-
|
|
411
|
+
{this.scope.locale.CheckPaymentStatus()}
|
|
392
412
|
</button>
|
|
393
413
|
)}
|
|
394
414
|
<button class="btn btn-sm" type="button" onClick={() => this.openOrder()}>
|
|
395
|
-
|
|
415
|
+
{this.scope.locale.OpenOrder()}
|
|
396
416
|
</button>
|
|
397
417
|
</div>
|
|
398
418
|
)}
|
|
@@ -94,9 +94,10 @@ export class ControllerFormFieldCaptcha extends BeanControllerBase {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
private setFieldCaptchaData() {
|
|
97
|
+
if (!this.captchaData) return;
|
|
97
98
|
this.$$form.setFieldValue(this.$props.name!, {
|
|
98
|
-
id: this.captchaData
|
|
99
|
-
token: this.captchaData
|
|
99
|
+
id: this.captchaData.id,
|
|
100
|
+
token: this.captchaData.token,
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -105,6 +106,9 @@ export class ControllerFormFieldCaptcha extends BeanControllerBase {
|
|
|
105
106
|
<>
|
|
106
107
|
<ZFormField
|
|
107
108
|
{...this.$props}
|
|
109
|
+
onFieldStateReady={() => {
|
|
110
|
+
this.setFieldCaptchaData();
|
|
111
|
+
}}
|
|
108
112
|
slotDefault={({ propsBucket, props }, $$formField) => {
|
|
109
113
|
const className = !propsBucket.needHandleBorder
|
|
110
114
|
? props.class
|
package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx
CHANGED
|
@@ -9,14 +9,7 @@ import { classes } from 'typestyle';
|
|
|
9
9
|
import { BeanControllerBase, ClientOnly, Use } from 'zova';
|
|
10
10
|
import { Controller } from 'zova-module-a-bean';
|
|
11
11
|
import { ZFormField } from 'zova-module-a-form';
|
|
12
|
-
import {
|
|
13
|
-
buildImagePreviewTitle,
|
|
14
|
-
inferImageRelationName,
|
|
15
|
-
openImagePreviewDialog,
|
|
16
|
-
resolveImagePreviewUrl,
|
|
17
|
-
} from 'zova-module-basic-image';
|
|
18
12
|
|
|
19
|
-
import type { IImageTransformOptions } from '../../types/image.js';
|
|
20
13
|
import type {
|
|
21
14
|
IImageUploaderRenderState,
|
|
22
15
|
IImageUploaderResult,
|
|
@@ -25,20 +18,21 @@ import type {
|
|
|
25
18
|
import { ZImageUploader } from '../../.metadata/component/imageUploader.js';
|
|
26
19
|
import 'vue-advanced-cropper/dist/style.css';
|
|
27
20
|
|
|
21
|
+
import { IImageResizeOptions } from '../../lib/imageTransform.jsx';
|
|
22
|
+
import {
|
|
23
|
+
buildImagePreviewTitle,
|
|
24
|
+
inferImageRelationName,
|
|
25
|
+
resolveImagePreviewUrl,
|
|
26
|
+
} from '../../lib/preview.js';
|
|
27
|
+
import { openImagePreviewDialog } from '../../lib/previewDialog.jsx';
|
|
28
|
+
|
|
28
29
|
declare module 'zova-module-a-openapi' {
|
|
29
30
|
export interface IResourceFormFieldRecord {
|
|
30
31
|
'basic-image:formFieldImage'?: IResourceFormFieldImageOptions;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export interface IResourceFormFieldImageResizeOptions {
|
|
35
|
-
width?: number;
|
|
36
|
-
height?: number;
|
|
37
|
-
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
|
38
|
-
background?: string;
|
|
39
|
-
quality?: number;
|
|
40
|
-
format?: IImageTransformOptions['format'];
|
|
41
|
-
}
|
|
35
|
+
export interface IResourceFormFieldImageResizeOptions extends IImageResizeOptions {}
|
|
42
36
|
|
|
43
37
|
export interface IResourceFormFieldImageOptions extends IResourceFormFieldOptionsBase {
|
|
44
38
|
imageScene?: keyof IImageSceneRecord | string;
|