Files
Aida_Purchaser_Front/src/views/pay/payment.vue
李志鹏 f52594efdd 支付
2026-06-01 11:39:23 +08:00

370 lines
9.4 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.
<template>
<div class="payment">
<div class="header" @click="onBack">
<span class="icon"><svg-icon name="back" size="30" /></span>
<span class="text">{{ $t('Pay.PaymentDetails') }}</span>
</div>
<!-- 未支付 -->
<template v-if="paymentStatus !== ORDER_STATUS.SUCCESS">
<div class="paylist">
<div class="item">
<img src="@/assets/images/pay/stripe.png" alt="" />
<span>{{ $t('Pay.CreditDebitCard') }}</span>
</div>
</div>
<div class="agreement">
<el-checkbox v-model="agreement">
<div class="text" v-html="$t('Pay.AgreementText')"></div
></el-checkbox>
</div>
<div class="title">
<span class="icon"><svg-icon name="card" size="20" /></span>
<span class="text">{{ $t('Pay.PayWithStripe') }}</span>
</div>
<template v-if="!query.paymentId">
<div class="tip">{{ $t('Pay.PayTip1') }}</div>
<div class="buttons">
<button custom="black" @click="onPayWith">
<span class="text">{{ $t('Pay.PayWith') }}</span>
<span class="icon pay"><svg-icon name="pay-stripe" /></span>
</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">{{ $t('Pay.Cancel') }}</span>
</div>
</template>
<!-- 已支付等待确认 -->
<template v-if="query.paymentId">
<div class="tip">{{ $t('Pay.PayTip2') }}</div>
<div class="buttons">
<button custom="black" @click="getOrderStatus">
{{ $t('Pay.IHaveCompletedPayment') }}
</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">{{ $t('Pay.Back') }}</span>
</div>
</template>
</template>
<!-- 已支付 -->
<template v-if="paymentStatus === ORDER_STATUS.SUCCESS">
<div class="success">
<img src="@/assets/images/pay/success.png" alt="" />
<div class="title">{{ $t('Pay.PurchaseSuccessful') }}</div>
<div class="tip">{{ $t('Pay.PurchaseSuccessfulTip') }}</div>
</div>
<div class="progres" v-if="downloadInfo.status !== DOWNLOAD_STATUS.null">
<el-progress
:text-inside="true"
:percentage="downloadInfo.progress * 100"
:status="progressStatus"
:duration="0.3"
>
<span class="text"
>{{ FormatBytes(downloadInfo.loaded, { unitBig: true }) }} /
{{ FormatBytes(downloadInfo.total, { unitBig: true }) }}</span
>
</el-progress>
</div>
<div class="buttons">
<button custom="black" @click="handleDownloadAllAssets">
{{ $t('Pay.DownloadAllAssets') }}
</button>
<button custom="black-box">
<span class="icon"><svg-icon name="order-file" size="18" /></span>
<span class="text">{{ $t('Pay.ExportInvoice') }}</span>
</button>
</div>
<div class="buttons">
<span class="text" @click="onBack">{{ $t('Pay.ContinueShopping') }}</span>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { FormatBytes } from '@/utils/tools'
import { fetchDownloadItemsByGet } from '@/api/user'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { CreateOrder, ORDER_STATUS, GetOrderStatus } from '@/api/shoppingCart'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
const onBack = () => router.back()
const router = useRouter()
const route = useRoute()
const query = computed(() => route.query)
const props = defineProps({
ids: {
type: Array,
default: () => []
}
})
onMounted(() => {
if (query.value.paymentLink && query.value.paymentId) {
createOrderCall({
paymentLink: query.value.paymentLink,
paymentId: query.value.paymentId
})
}
})
onBeforeUnmount(() => {
clearInterval(viewTime.value)
})
const agreement = ref(false)
const paymentStatus = ref(ORDER_STATUS.PENDING)
const onPayWith = () => {
if (!agreement.value) {
return ElMessage.warning('Please agree to the Terms & Conditions and Privacy Policy.')
}
const ids = [...props.ids]
if (ids.length === 0) return
CreateOrder(ids, true).then(createOrderCall)
}
const createOrderCall = (res: any) => {
const url = res.paymentLink
const paymentId = res.paymentId
openView(url)
router.replace({
query: {
list: query.value.list,
paymentId
}
})
}
const getOrderStatus = () => {
const paymentId = query.value.paymentId as string
GetOrderStatus(paymentId).then((res: any) => {
const status = res === null ? ORDER_STATUS.PENDING : (res as number)
paymentStatus.value = status
if (status !== ORDER_STATUS.SUCCESS) {
ElMessage.warning('订单未支付')
}
})
}
const viewTime = ref(null)
const openView = (url, width = 600, height = 800) => {
const left = (screen.width - width) / 2
const top = (screen.height - height) / 2
const view = window.open(
url,
'_blank',
'width=' + width + ', height=' + height + ', left=' + left + ', top=' + top
)
clearInterval(viewTime.value)
viewTime.value = setInterval(() => {
if (view.closed) {
clearInterval(viewTime.value)
getOrderStatus()
}
}, 500)
}
const DOWNLOAD_STATUS = {
null: 0, // 未开始
dowloading: 1, // 下载中
success: 2, // 下载成功
failed: 3 // 下载失败
}
const downloadInfo = ref({
progress: 0,
status: DOWNLOAD_STATUS.null,
total: 0,
loaded: 0
})
const progressStatus = computed(() => {
if (downloadInfo.value.status === DOWNLOAD_STATUS.dowloading) {
return 'warning'
} else if (downloadInfo.value.status === DOWNLOAD_STATUS.success) {
return 'success'
} else if (downloadInfo.value.status === DOWNLOAD_STATUS.failed) {
return 'exception'
} else {
return 'warning'
}
})
const handleDownloadAllAssets = () => {
const ids = props.ids as string[]
downloadInfo.value.status = DOWNLOAD_STATUS.dowloading
downloadInfo.value.progress = 0
fetchDownloadItemsByGet({ ids }, (event) => {
downloadInfo.value.progress = event.progress
downloadInfo.value.loaded = event.loaded
downloadInfo.value.total = event.total
})
.then((res) => {
console.log(res)
const disposition = res.headers['content-disposition']
const fileName = disposition?.split('filename=')[1]?.replace(/"/g, '') || 'download.zip'
const blob = res.data
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
const timestamp = new Date().getTime()
link.download = fileName || `wardrobe_download_${timestamp}.zip`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
})
.catch((error) => {
downloadInfo.value.status = DOWNLOAD_STATUS.failed
})
.finally(() => {
downloadInfo.value.progress = 1
downloadInfo.value.status = DOWNLOAD_STATUS.success
})
}
</script>
<style lang="less" scoped>
.payment {
display: flex;
flex-direction: column;
> .header {
display: flex;
align-items: center;
gap: 2rem;
cursor: pointer;
user-select: none;
> .text {
font-family: KaiseiOpti-Bold;
font-size: 4rem;
}
}
> .paylist {
margin: 4rem 0 0 4rem;
padding: 2.7rem 0;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 0.1rem solid #c4c4c4;
> .item {
display: flex;
flex-direction: column;
align-items: center;
> img {
width: auto;
height: 3rem;
margin-bottom: 1.6rem;
}
> span {
font-family: KaiseiOpti-Regular;
font-size: 1.2rem;
color: #585858;
}
}
}
> .agreement {
margin-top: 2.4rem;
margin-left: 2rem;
margin-bottom: 10rem;
display: flex;
> .el-checkbox {
--el-checkbox-font-size: 1.4rem;
--el-checkbox-input-width: 1.4rem;
--el-checkbox-input-height: 1.4rem;
--el-checkbox-checked-bg-color: #000;
--el-checkbox-checked-input-border-color: #000;
--el-checkbox-input-border: 0.1rem solid #c4c4c4;
--el-checkbox-bg-color: #fff;
--el-checkbox-border-radius: 0;
}
.text {
color: #666;
word-break: break-word;
white-space: pre-wrap;
> * {
font-family: KaiseiOpti-Bold;
text-decoration: underline;
color: #232323;
}
}
}
> .title {
display: flex;
align-items: center;
gap: 1rem;
}
> .tip {
margin: 60px auto;
max-width: 59rem;
background: #f6f6f6;
border-left: 0.2rem solid #979797;
padding: 1.5rem 2rem;
font-size: 1.4rem;
line-height: 2.5rem;
color: #585858;
}
> .buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.8rem;
gap: 1.2rem;
> button {
min-width: 28rem;
height: 5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.8rem;
text-transform: uppercase;
> .icon.pay {
width: 7rem;
--svg-icon-width: auto;
--svg-icon-height: 2rem;
}
}
> span {
user-select: none;
cursor: pointer;
font-size: 1.4rem;
color: #979797;
}
}
> .success {
margin: 12rem auto;
max-width: 39rem;
display: flex;
align-items: center;
flex-direction: column;
> img {
width: 19.8rem;
height: auto;
margin-bottom: 2.4rem;
}
> .title {
font-size: 2rem;
margin-bottom: 0.8rem;
}
> .tip {
font-family: KaiseiOpti-Regular;
font-size: 1.4rem;
color: #666;
text-align: center;
}
}
> .progres {
margin: 2rem auto;
max-width: 50rem;
width: 100%;
.text {
font-size: 1.4rem;
line-height: 2.2rem;
color: #000;
}
&:deep(.el-progress) {
.el-progress-bar__outer {
height: 2.2rem !important;
}
}
}
}
</style>