统一暗色模式页面/顶栏/Handsontable 选中色,调整下单到仓方式文案与提示,CustomLeftMenu 卡片统一 60px;补充揽收单、问题件处理与 H5 相关页面。 Co-authored-by: Cursor <cursoragent@cursor.com>
591 lines
15 KiB
Vue
591 lines
15 KiB
Vue
<template>
|
|
<div class="deposit-page animate__animated animate__fadeInUp animate__faster">
|
|
<div class="deposit-layout">
|
|
<section class="info-panel">
|
|
<div class="panel-header">
|
|
<p class="panel-subtitle">{{ $t('user.recharge.deposit.panel.subtitle') }}</p>
|
|
<h2>{{ $t('user.recharge.deposit.panel.title') }}</h2>
|
|
<p class="panel-desc">{{ $t('user.recharge.deposit.panel.desc') }}</p>
|
|
</div>
|
|
|
|
<a-steps :current="current" direction="vertical" class="steps">
|
|
<a-step :title="$t('user.recharge.deposit.steps.selectAmount.title')" :description="$t('user.recharge.deposit.steps.selectAmount.desc')" />
|
|
<a-step :title="$t('user.recharge.deposit.steps.uploadVoucher.title')" :description="$t('user.recharge.deposit.steps.uploadVoucher.desc')" />
|
|
<a-step :title="$t('user.recharge.deposit.steps.review.title')" :description="$t('user.recharge.deposit.steps.review.desc')" />
|
|
</a-steps>
|
|
|
|
<div class="tips-card">
|
|
<p class="tips-title">{{ $t('user.recharge.deposit.tips.title') }}</p>
|
|
<ul>
|
|
<li>{{ $t('user.recharge.deposit.tips.item1') }}</li>
|
|
<li>{{ $t('user.recharge.deposit.tips.item2') }}</li>
|
|
<li>{{ $t('user.recharge.deposit.tips.item3') }}</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="form-panel">
|
|
<div class="form-header">
|
|
<div class="amount-box">
|
|
<p class="amount-label">{{ $t('user.recharge.deposit.form.currentAmount') }}</p>
|
|
<p class="amount-value">¥{{ finalAmount || '--' }}</p>
|
|
</div>
|
|
<span class="step-chip">{{ $t('user.recharge.deposit.form.stepChip', { step: current + 1, tip: currentTip }) }}</span>
|
|
</div>
|
|
|
|
<a-form :model="formModel">
|
|
<div v-if="current === 0" class="form-block">
|
|
<p class="block-title">{{ $t('user.recharge.deposit.form.selectAmount') }}</p>
|
|
<a-radio-group
|
|
v-model="rechargeAmount"
|
|
type="button"
|
|
button-style="solid"
|
|
class="amount-buttons"
|
|
>
|
|
<a-radio :value="500">¥500</a-radio>
|
|
<a-radio :value="1000">¥1,000</a-radio>
|
|
<a-radio :value="1500">¥1,500</a-radio>
|
|
<a-radio :value="3000">¥3,000</a-radio>
|
|
<a-radio :value="0">{{ $t('user.recharge.deposit.form.custom') }}</a-radio>
|
|
</a-radio-group>
|
|
|
|
<div
|
|
v-if="rechargeAmount === 0"
|
|
class="custom-amount animate__animated animate__fadeIn"
|
|
>
|
|
<label>{{ $t('user.recharge.deposit.form.inputAmount') }}</label>
|
|
<a-input-number
|
|
v-model="customAmount"
|
|
:min="1"
|
|
:precision="2"
|
|
style="width: 240px"
|
|
>
|
|
<template #prefix>¥</template>
|
|
<template #suffix>RMB</template>
|
|
</a-input-number>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="current === 0" class="form-block">
|
|
<p class="block-title">{{ $t('user.recharge.deposit.form.paymentMethod') }}</p>
|
|
<div class="pay-type-grid">
|
|
<button
|
|
type="button"
|
|
class="pay-card"
|
|
:class="{ active: payType === 'zfb' }"
|
|
@click="payType = 'zfb'"
|
|
>
|
|
<img :src="zfbIcon" :alt="$t('user.recharge.deposit.payment.alipay')" />
|
|
<div>
|
|
<p class="pay-name">{{ $t('user.recharge.deposit.payment.alipay') }}</p>
|
|
<p class="pay-desc">{{ $t('user.recharge.deposit.payment.alipayDesc') }}</p>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="pay-card"
|
|
:class="{ active: payType === 'wx' }"
|
|
@click="payType = 'wx'"
|
|
>
|
|
<img :src="wxIcon" :alt="$t('user.recharge.deposit.payment.wechat')" />
|
|
<div>
|
|
<p class="pay-name">{{ $t('user.recharge.deposit.payment.wechat') }}</p>
|
|
<p class="pay-desc">{{ $t('user.recharge.deposit.payment.wechatDesc') }}</p>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="qr-wrapper">
|
|
<div class="qr-image" v-if="payType === 'zfb'">
|
|
<a-image :src="zfbQrCode" :preview="true" width="220" />
|
|
</div>
|
|
<div class="qr-image" v-else>
|
|
<a-image :src="wxQrCode" :preview="true" width="220" />
|
|
</div>
|
|
<p class="qr-tip">{{ $t('user.recharge.deposit.form.qrTip') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="current === 1" class="form-block">
|
|
<p class="block-title">{{ $t('user.recharge.deposit.form.uploadVoucher') }}</p>
|
|
<p class="block-desc">{{ $t('user.recharge.deposit.form.uploadVoucherDesc') }}</p>
|
|
|
|
<a-upload
|
|
name="rechargeScreenshot"
|
|
:file-list="fileList"
|
|
list-type="picture-card"
|
|
:show-upload-list="true"
|
|
accept="image/*"
|
|
:custom-request="handleUpload"
|
|
@change="handleFileChange"
|
|
>
|
|
<template #upload-button>
|
|
<div v-if="!previewUrl" class="upload-card">
|
|
<icon-plus />
|
|
<span>{{ $t('user.recharge.deposit.form.uploadScreenshot') }}</span>
|
|
</div>
|
|
</template>
|
|
</a-upload>
|
|
<div v-if="previewUrl" class="preview-wrap">
|
|
<img :src="previewUrl" alt="screenshot" class="preview-image" />
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="current === 2" class="form-block">
|
|
<a-result status="success" :title="$t('user.recharge.deposit.result.title')" :sub-title="$t('user.recharge.deposit.result.subtitle')">
|
|
<template #extra>
|
|
<a-space>
|
|
<a-button @click="reset">{{ $t('user.recharge.deposit.result.actions.rechargeAgain') }}</a-button>
|
|
<a-button type="primary" @click="goToRecord">{{ $t('user.recharge.deposit.result.actions.viewBill') }}</a-button>
|
|
</a-space>
|
|
</template>
|
|
</a-result>
|
|
</div>
|
|
|
|
<div class="action-row">
|
|
<a-button v-if="current === 1" @click="current--">{{ $t('user.recharge.deposit.actions.prevStep') }}</a-button>
|
|
<a-button
|
|
v-if="current === 1"
|
|
type="primary"
|
|
:loading="submitting"
|
|
@click="submit"
|
|
>
|
|
{{ $t('user.recharge.deposit.actions.submitReview') }}
|
|
</a-button>
|
|
<a-button v-if="current === 0" type="primary" @click="handleNext">
|
|
{{ $t('user.recharge.deposit.actions.nextStep') }}
|
|
</a-button>
|
|
</div>
|
|
</a-form>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { useI18n } from 'vue-i18n';
|
|
import { computed, reactive, ref } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { Message } from '@arco-design/web-vue';
|
|
import { IconPlus } from '@arco-design/web-vue/es/icon';
|
|
import zfbIcon from '@/assets/zfb.png';
|
|
import wxIcon from '@/assets/wx.png';
|
|
import zfbQrCode from '@/assets/zfb-biubuy.png';
|
|
import wxQrCode from '@/assets/wx-biubuy.png';
|
|
import { submitRechargeApplication } from '@/api/recharge/rechargeRecord';
|
|
|
|
const { t } = useI18n();
|
|
|
|
defineOptions({
|
|
name: 'UserRechargeDeposit',
|
|
});
|
|
|
|
const router = useRouter();
|
|
const current = ref(0);
|
|
const formModel = reactive({});
|
|
const rechargeAmount = ref(500);
|
|
const customAmount = ref(0);
|
|
const payType = ref<'zfb' | 'wx'>('zfb');
|
|
const fileList = ref<any[]>([]);
|
|
const screenshotFile = ref<File | null>(null);
|
|
const previewUrl = ref('');
|
|
const submitting = ref(false);
|
|
|
|
const finalAmount = computed(() => {
|
|
return rechargeAmount.value === 0 ? customAmount.value : rechargeAmount.value;
|
|
});
|
|
|
|
const currentTip = computed(() => {
|
|
const tips = [
|
|
t('user.recharge.deposit.steps.tip.selectAmount'),
|
|
t('user.recharge.deposit.steps.tip.uploadVoucher'),
|
|
t('user.recharge.deposit.steps.tip.review'),
|
|
];
|
|
return tips[current.value] || t('user.recharge.deposit.steps.tip.complete');
|
|
});
|
|
|
|
const handleNext = () => {
|
|
if (rechargeAmount.value === 0 && customAmount.value <= 0) {
|
|
Message.warning(t('user.recharge.deposit.message.selectAmount'));
|
|
return;
|
|
}
|
|
current.value++;
|
|
};
|
|
|
|
const handleUpload = async (options: any) => {
|
|
const file = options?.fileItem?.file ?? options?.file;
|
|
if (!file) {
|
|
Message.error(t('user.recharge.deposit.message.uploadFailed'));
|
|
options?.onError?.(new Error('no file'));
|
|
return;
|
|
}
|
|
if (previewUrl.value.startsWith('blob:')) {
|
|
URL.revokeObjectURL(previewUrl.value);
|
|
}
|
|
screenshotFile.value = file as File;
|
|
previewUrl.value = URL.createObjectURL(file as File);
|
|
fileList.value = [{ uid: String(Date.now()), name: (file as File).name, url: previewUrl.value }];
|
|
options?.onSuccess?.(file);
|
|
Message.success(t('user.recharge.deposit.message.uploadSuccess'));
|
|
};
|
|
|
|
const handleFileChange = (files: any) => {
|
|
fileList.value = files;
|
|
if (!files?.length) {
|
|
screenshotFile.value = null;
|
|
if (previewUrl.value.startsWith('blob:')) {
|
|
URL.revokeObjectURL(previewUrl.value);
|
|
}
|
|
previewUrl.value = '';
|
|
}
|
|
};
|
|
|
|
const submit = async () => {
|
|
if (rechargeAmount.value === 0 && customAmount.value <= 0) {
|
|
Message.warning(t('user.recharge.deposit.message.selectAmount'));
|
|
return;
|
|
}
|
|
if (finalAmount.value < 0.01) {
|
|
Message.warning(t('user.recharge.deposit.message.selectAmount'));
|
|
return;
|
|
}
|
|
|
|
submitting.value = true;
|
|
try {
|
|
await submitRechargeApplication({
|
|
rechargeAmount: Number(finalAmount.value),
|
|
payType: payType.value === 'zfb' ? 2 : 1,
|
|
screenshotFile: screenshotFile.value ?? undefined,
|
|
});
|
|
Message.success(t('user.recharge.deposit.message.submitSuccess'));
|
|
current.value++;
|
|
} catch (error: any) {
|
|
Message.error(error?.message || t('user.recharge.deposit.message.submitFailed'));
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
current.value = 0;
|
|
rechargeAmount.value = 500;
|
|
customAmount.value = 0;
|
|
payType.value = 'zfb';
|
|
fileList.value = [];
|
|
screenshotFile.value = null;
|
|
if (previewUrl.value.startsWith('blob:')) {
|
|
URL.revokeObjectURL(previewUrl.value);
|
|
}
|
|
previewUrl.value = '';
|
|
};
|
|
|
|
const goToRecord = () => {
|
|
router.push({ name: 'FinanceRechargeRecord' });
|
|
};
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.deposit-page {
|
|
min-height: 100vh;
|
|
padding: 40px 0 60px;
|
|
background: radial-gradient(circle at top, #eff4ff, #f7f8fb 60%);
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.deposit-layout {
|
|
width: 1000px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 320px 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
.info-panel {
|
|
padding: 24px;
|
|
background: #0d1d3d;
|
|
border-radius: 24px;
|
|
color: #f5f8ff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
box-shadow: 0 20px 40px rgba(1, 14, 38, 0.25);
|
|
}
|
|
|
|
.panel-subtitle {
|
|
font-size: 13px;
|
|
letter-spacing: 2px;
|
|
text-transform: uppercase;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
.panel-header h2 {
|
|
font-size: 24px;
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.panel-desc {
|
|
color: rgba(255, 255, 255, 0.75);
|
|
}
|
|
|
|
.steps {
|
|
padding: 0;
|
|
|
|
:deep(.arco-steps-item-title) {
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
:deep(.arco-steps-item-description) {
|
|
color: rgba(255, 255, 255, 0.7);
|
|
}
|
|
|
|
:deep(.arco-steps-icon) {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.tips-card {
|
|
margin-top: auto;
|
|
padding: 14px;
|
|
border-radius: 16px;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
|
|
ul {
|
|
margin: 6px 0 0;
|
|
padding-left: 18px;
|
|
line-height: 1.6;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
}
|
|
|
|
.tips-title {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.form-panel {
|
|
padding: 28px 32px 34px;
|
|
background: var(--app-card-bg);
|
|
border-radius: 32px;
|
|
box-shadow: 0 20px 60px rgba(31, 52, 108, 0.12);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 22px;
|
|
}
|
|
|
|
.form-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.amount-box {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.amount-label {
|
|
font-size: 13px;
|
|
color: #8a93a8;
|
|
}
|
|
|
|
.amount-value {
|
|
font-size: 30px;
|
|
font-weight: 700;
|
|
color: #1f2f4d;
|
|
}
|
|
|
|
.step-chip {
|
|
padding: 6px 14px;
|
|
border-radius: 999px;
|
|
background: #eef3ff;
|
|
color: #2b63ff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.form-block + .form-block {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.block-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
margin-bottom: 10px;
|
|
color: #1f2f4d;
|
|
}
|
|
|
|
.block-desc {
|
|
margin-top: -4px;
|
|
margin-bottom: 12px;
|
|
color: #6f7c97;
|
|
}
|
|
|
|
.amount-buttons {
|
|
width: 100%;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
gap: 8px;
|
|
|
|
:deep(.arco-radio-button) {
|
|
text-align: center;
|
|
border-radius: 16px;
|
|
font-weight: 600;
|
|
border: 1px solid #d7dff0;
|
|
transition: all 0.2s ease;
|
|
line-height: 34px;
|
|
height: 44px;
|
|
|
|
&.arco-radio-checked {
|
|
border-color: #2b63ff;
|
|
color: #2b63ff;
|
|
box-shadow: 0 8px 18px rgba(43, 99, 255, 0.22);
|
|
background: rgba(43, 99, 255, 0.08);
|
|
}
|
|
}
|
|
}
|
|
|
|
.custom-amount {
|
|
margin-top: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
|
|
label {
|
|
color: #6f7c97;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.pay-type-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.pay-card {
|
|
border: 1px solid #e4eaf5;
|
|
border-radius: 20px;
|
|
padding: 14px 18px;
|
|
display: flex;
|
|
gap: 14px;
|
|
align-items: center;
|
|
background: #f8faff;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
|
|
img {
|
|
width: 40px;
|
|
height: 40px;
|
|
}
|
|
|
|
&.active {
|
|
border-color: #2b63ff;
|
|
background: #ecf1ff;
|
|
box-shadow: 0 14px 28px rgba(43, 99, 255, 0.18);
|
|
}
|
|
}
|
|
|
|
.pay-name {
|
|
font-weight: 600;
|
|
color: #1f2f4d;
|
|
}
|
|
|
|
.pay-desc {
|
|
font-size: 12px;
|
|
color: #7f8ba8;
|
|
}
|
|
|
|
.qr-wrapper {
|
|
text-align: center;
|
|
}
|
|
|
|
.qr-image {
|
|
border: 2px dashed #dbe3f4;
|
|
border-radius: 16px;
|
|
padding: 14px;
|
|
background: #fdfefe;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
|
|
:deep(.arco-image),
|
|
:deep(.arco-image-with-footer-inner) {
|
|
width: 220px !important;
|
|
height: 220px !important;
|
|
}
|
|
|
|
:deep(.arco-image-img) {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
border-radius: 12px;
|
|
}
|
|
}
|
|
|
|
.qr-tip {
|
|
margin-top: 8px;
|
|
font-size: 13px;
|
|
color: #7b869f;
|
|
}
|
|
|
|
.upload-card {
|
|
width: 200px;
|
|
height: 200px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px dashed #d7dff0;
|
|
border-radius: 24px;
|
|
color: #9da9bf;
|
|
background: #f8faff;
|
|
gap: 8px;
|
|
|
|
.arco-icon {
|
|
font-size: 30px;
|
|
}
|
|
}
|
|
|
|
.preview-wrap {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.preview-image {
|
|
max-width: 220px;
|
|
max-height: 220px;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--color-border-2);
|
|
object-fit: contain;
|
|
}
|
|
|
|
.action-row {
|
|
margin-top: 10px;
|
|
display: flex;
|
|
gap: 14px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
:deep(.arco-upload-list-picture-card) {
|
|
.arco-upload-list-item {
|
|
width: 220px;
|
|
height: 220px;
|
|
border-radius: 24px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.deposit-layout {
|
|
width: 94%;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.info-panel {
|
|
order: 2;
|
|
}
|
|
}
|
|
</style>
|
|
|