2
0
Files
fe.wfc.user/src/views/home/modules/card-data.vue
2024-12-06 09:46:24 +08:00

283 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useBillingStore } from '@/store/modules/billing/billing';
import { useI18n } from 'vue-i18n';
import { useFormRules } from '@/hooks/common/form';
import type { Ref } from 'vue';
import type { Rule } from 'ant-design-vue/es/form';
const { t } = useI18n();
const { patternRules } = useFormRules();
interface RechargeOption {
amount: number;
displayAmount: string;
price: number;
selected?: boolean;
}
const emailAddress = ref('');
const emailError = ref('');
const customAmount = ref<string | number | undefined>(undefined);
const selectedAmount = ref<number | null>(null);
const isCustomMode = ref<boolean>(false);
const rechargeOptions: Ref<RechargeOption[]> = ref([
{ amount: 10, displayAmount: '10元', price: 10.00 },
{ amount: 20, displayAmount: '20元', price: 20.00 },
{ amount: 30, displayAmount: '30元', price: 30.00 },
{ amount: 50, displayAmount: '50元', price: 50.00 },
{ amount: 100, displayAmount: '100元', price: 100.00 },
{ amount: 200, displayAmount: '200元', price: 200.00 },
]);
const billingStore = useBillingStore();
const paymentAmount = computed(() => {
if (customAmount.value !== undefined && customAmount.value !== '') {
return Number(customAmount.value);
}
return selectedAmount.value || 0;
});
const handleOptionSelect = (amount: number) => {
isCustomMode.value = false;
selectedAmount.value = amount;
customAmount.value = undefined;
};
const handleCustomMode = () => {
isCustomMode.value = true;
selectedAmount.value = null;
};
const handleCustomAmount = (value: string | number | undefined) => {
customAmount.value = value;
};
// 添加点击外部关闭自定义输入的处理
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (isCustomMode.value &&
!target?.closest('.special-option') &&
customAmount.value === undefined) {
isCustomMode.value = false;
}
};
// 创建一个函数来处理 blur 事件
const handleBlur = () => {
if (customAmount.value === undefined || customAmount.value === '') {
isCustomMode.value = false;
}
};
onMounted(() => {
document.addEventListener('click', handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
const balance = computed(() => billingStore.balance);
// 创建邮箱验证规则,将 trigger 改为 'blur'
const emailRules = computed<Rule[]>(() => [
{ required: true, message: t('page.login.register.emailRequired'), trigger: 'blur' },
patternRules.email // 预定义规则已经设置为 blur 触发
]);
// 移除 change 事件处理,改用 blur 事件处理
const handleEmailBlur = async (e: FocusEvent) => {
const target = e.target as HTMLInputElement;
emailAddress.value = target.value;
try {
// 使用验证规则进行验证
await Promise.all(emailRules.value.map(rule => {
if (rule.validator) {
return rule.validator(rule, target.value, () => {});
}
if (rule.pattern) {
if (!rule.pattern.test(target.value)) {
throw rule.message;
}
}
return Promise.resolve();
}));
emailError.value = '';
} catch (error) {
emailError.value = error as string;
}
};
</script>
<template>
<div class="recharge-container p-4">
<!-- 邮箱输入部分 -->
<div class="email-section mb-6">
<div class="text-lg font-bold mb-2">{{ t('page.carddata.email') }}</div>
<div class="email-input-wrapper">
<AInput
v-model:value="emailAddress"
:placeholder="t('page.login.common.emailPlaceholder')"
class="email-input"
size="large"
:status="emailError ? 'error' : ''"
@blur="handleEmailBlur"
/>
<div v-if="emailError" class="error-message">
<span class="text-red-500">{{ emailError }}</span>
</div>
</div>
</div>
<!-- 充值金额选择 -->
<div class="amount-section mb-6">
<div class="text-lg font-bold mb-2">{{ t('page.carddata.Rechargeamount') }}</div>
<div class="grid grid-cols-3 gap-4">
<div
v-for="option in rechargeOptions"
:key="option.amount"
class="recharge-option cursor-pointer"
:class="{ 'selected': selectedAmount === option.amount }"
@click="handleOptionSelect(option.amount)"
>
<div class="text-lg font-medium">{{ option.displayAmount }}</div>
<div class="text-sm text-gray-500">{{ t('page.carddata.price') }} {{ option.price.toFixed(2) }}</div>
</div>
<!-- 自定义充选项 -->
<div
class="special-option"
:class="{ 'selected': isCustomMode }"
@click="handleCustomMode"
>
<template v-if="!isCustomMode">
<AInputNumber
v-model:value="customAmount"
:placeholder="t('page.carddata.Customization')"
:min="1"
:max="999999"
class="w-full"
@change="handleCustomAmount"
@click.stop
@blur="handleBlur"
/>
</template>
</div>
</div>
</div>
<!-- 支付部分 -->
<div class="payment-section mt-8 bg-white p-4 rounded-lg border border-gray-100">
<div class="flex justify-between items-center mb-4">
<span class="text-gray-600">{{ t('page.carddata.Remainingbalance') }}{{ balance }}</span>
<span class="text-blue-500">{{ t('page.carddata.Theamountreceived') }}{{ paymentAmount.toFixed(2) }}</span>
</div>
<AButton
type="primary"
size="large"
block
:disabled="!paymentAmount"
>
¥{{ paymentAmount.toFixed(2) }} {{ t('page.carddata.pay') }}
</AButton>
</div>
</div>
</template>
<style scoped>
.recharge-option {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
text-align: center;
transition: all 0.3s;
height: 76px;
display: flex;
flex-direction: column;
justify-content: center;
}
.recharge-option.selected,
.special-option.selected {
border-color: #1890ff;
background-color: #e6f7ff;
}
.special-option {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 0;
text-align: center;
display: flex;
align-items: stretch;
gap: 0;
transition: all 0.3s;
height: 76px;
}
.special-option :deep(.ant-input-number) {
width: 100%;
height: 100%;
border-radius: 8px;
border: none;
display: flex;
align-items: center;
}
.special-option :deep(.ant-input-number-input-wrap) {
height: 100%;
width: 100%;
}
.special-option :deep(.ant-input-number-input) {
height: 100%;
width: 100%;
text-align: center;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center;
&::placeholder {
text-align: center;
color: #999;
}
}
.special-option :deep(.ant-input-number:focus),
.special-option :deep(.ant-input-number-focused) {
box-shadow: none;
border: none;
}
.payment-section {
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.email-input-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
.email-input {
border-radius: 8px;
}
.error-message {
font-size: 12px;
line-height: 1.5;
padding-left: 4px;
min-height: 18px;
}
/* 移除之前的后缀样式 */
:deep(.ant-input-suffix) {
display: none;
}
</style>