From cfdc594e4f081ee9b286a8a76ae2a9ba3e3db3bb Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 7 Apr 2026 23:21:46 +0800 Subject: [PATCH] feat(payment): dynamic limit placeholders and open Stripe in new window --- .../payment/PaymentProviderDialog.vue | 20 ++++++++++++++++--- frontend/src/i18n/locales/en.ts | 4 +++- frontend/src/i18n/locales/zh.ts | 4 +++- frontend/src/views/user/PaymentView.vue | 4 +++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/payment/PaymentProviderDialog.vue b/frontend/src/components/payment/PaymentProviderDialog.vue index bbd006b447..bea4b5b9c2 100644 --- a/frontend/src/components/payment/PaymentProviderDialog.vue +++ b/frontend/src/components/payment/PaymentProviderDialog.vue @@ -158,7 +158,7 @@ type="number" :value="getLimitVal(lt.value, 'singleMin')" @input="setLimitVal(lt.value, 'singleMin', ($event.target as HTMLInputElement).value)" - class="input mt-0.5" min="1" step="0.01" :placeholder="t('admin.settings.payment.limitsHint')" + class="input mt-0.5" min="1" step="0.01" :placeholder="limitPlaceholder(lt.value)" />
@@ -167,7 +167,7 @@ type="number" :value="getLimitVal(lt.value, 'singleMax')" @input="setLimitVal(lt.value, 'singleMax', ($event.target as HTMLInputElement).value)" - class="input mt-0.5" min="1" step="0.01" :placeholder="t('admin.settings.payment.limitsHint')" + class="input mt-0.5" min="1" step="0.01" :placeholder="limitPlaceholder(lt.value)" />
@@ -176,7 +176,7 @@ type="number" :value="getLimitVal(lt.value, 'dailyLimit')" @input="setLimitVal(lt.value, 'dailyLimit', ($event.target as HTMLInputElement).value)" - class="input mt-0.5" min="1" step="0.01" :placeholder="t('admin.settings.payment.limitsHint')" + class="input mt-0.5" min="1" step="0.01" :placeholder="limitPlaceholder(lt.value)" />
@@ -327,6 +327,20 @@ function getLimitVal(paymentType: string, field: string): string { return val && val > 0 ? String(val) : '' } +/** Returns true if any limit field for this payment type has a value */ +function hasAnyLimit(paymentType: string): boolean { + const l = limits[paymentType] + if (!l) return false + return (l.singleMin > 0) || (l.singleMax > 0) || (l.dailyLimit > 0) +} + +/** Dynamic placeholder: "不限制" if sibling has value, "使用全局配置" if all empty */ +function limitPlaceholder(paymentType: string): string { + return hasAnyLimit(paymentType) + ? t('admin.settings.payment.limitsNoLimit') + : t('admin.settings.payment.limitsUseGlobal') +} + function setLimitVal(paymentType: string, field: string, val: string) { if (!limits[paymentType]) limits[paymentType] = {} const num = Number(val) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 9abb1df557..b4f5e6d07a 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -4398,7 +4398,9 @@ export default { limitSingleMin: 'Min per order', limitSingleMax: 'Max per order', limitDaily: 'Daily limit', - limitsHint: 'Leave empty to use global settings', + limitsHint: 'All empty = use global config; partially filled = empty means no limit', + limitsUseGlobal: 'Use global', + limitsNoLimit: 'No limit', productNamePrefix: 'Product Name Prefix', productNameSuffix: 'Product Name Suffix', preview: 'Preview', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 30569be74b..43bee04a83 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -4571,7 +4571,9 @@ export default { limitSingleMin: '单笔最低', limitSingleMax: '单笔最高', limitDaily: '每日限额', - limitsHint: '留空表示使用全局配置', + limitsHint: '全部留空使用全局配置,部分填写时留空项表示不限制', + limitsUseGlobal: '使用全局配置', + limitsNoLimit: '不限制', productNamePrefix: '商品名前缀', productNameSuffix: '商品名后缀', preview: '预览', diff --git a/frontend/src/views/user/PaymentView.vue b/frontend/src/views/user/PaymentView.vue index b4f79997cf..fd59f216c8 100644 --- a/frontend/src/views/user/PaymentView.vue +++ b/frontend/src/views/user/PaymentView.vue @@ -231,7 +231,9 @@ async function createOrder(orderAmount: number, orderType: string, planId?: numb plan_id: planId, }) if (result.client_secret) { - router.push({ path: '/payment/stripe', query: { order_id: String(result.order_id), client_secret: result.client_secret } }) + const stripeUrl = router.resolve({ path: '/payment/stripe', query: { order_id: String(result.order_id), client_secret: result.client_secret } }).href + window.open(stripeUrl, '_blank') + router.push({ path: '/payment/qrcode', query: { order_id: String(result.order_id), expires_at: result.expires_at || '', payment_type: 'stripe' } }) } else if (result.qr_code) { router.push({ path: '/payment/qrcode', query: { order_id: String(result.order_id), qr: result.qr_code || '', pay_url: result.pay_url || '', expires_at: result.expires_at || '', payment_type: selectedMethod.value } }) } else if (result.pay_url) {