二维码付款

This commit is contained in:
张 元山 2025-03-04 19:06:01 +08:00
parent da4a2c509c
commit 3c862d7dc4
14 changed files with 480 additions and 694 deletions

View File

@ -69,7 +69,6 @@ export function setupHttp() {
if (data.status === 401) { if (data.status === 401) {
message.error(i18n.t('http.error.loginExpired')) message.error(i18n.t('http.error.loginExpired'))
token.value = '' // 清除 token token.value = '' // 清除 token
router.replace('/login')
} }
return response return response

View File

@ -1,11 +1,8 @@
<template> <template>
<main class="flex flex-col min-h-svh"> <main class="flex flex-col min-h-svh">
<AppHeader class="h-[var(--van-nav-bar-height)]" />
<div class="flex-1 flex flex-col"> <div class="flex-1 flex flex-col">
<slot /> <slot />
</div> </div>
<AppFooter />
</main> </main>
</template> </template>
<script setup > <script setup></script>
</script>

View File

@ -1,145 +1,140 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' // stripe
import {authStore} from "~/stores/auth/index.js"; import { onMounted, ref } from "vue";
import {orderQuery} from "~/api/goods/index.js"; import { authStore } from "~/stores/auth/index.js";
import { WebSocketClient } from '@/utils/websocket' import { orderQuery } from "~/api/goods/index.js";
const config = useRuntimeConfig() import { WebSocketClient } from "@/utils/websocket";
const config = useRuntimeConfig();
definePageMeta({ definePageMeta({
layout: 'default', layout: "default",
title: 'Stripe支付' title: "Stripe支付",
}) });
const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY) const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY);
const route = useRoute() const route = useRoute();
const baseURL = config.public.NUXT_PUBLIC_API_BASE const baseURL = config.public.NUXT_PUBLIC_API_BASE;
const items = [{ id: "xl-tshirt", amount: 1000 }] const items = [{ id: "xl-tshirt", amount: 1000 }];
const elements = ref(null) const elements = ref(null);
const paymentMessage = ref('') const paymentMessage = ref("");
const isLoading = ref(false) const isLoading = ref(false);
const showSpinner = ref(false) const showSpinner = ref(false);
let pollTimer = null let pollTimer = null;
let timeoutTimer = null let timeoutTimer = null;
const router = useRouter() const router = useRouter();
const startPolling = () => { const startPolling = () => {
pollTimer = setInterval(async () => { pollTimer = setInterval(async () => {
const res = await orderQuery({ const res = await orderQuery({
orderNo: route.query.payUid orderNo: route.query.payUid,
}) });
if (res.status === 0) { if (res.status === 0) {
if (res.data.status !== 3) { if (res.data.status !== 3) {
clearInterval(pollTimer) clearInterval(pollTimer);
clearTimeout(timeoutTimer) clearTimeout(timeoutTimer);
router.replace({ router.replace({
path: route.query.returnUrl, path: route.query.returnUrl,
query: { query: {
orderNo: route.query.payUid orderNo: route.query.payUid,
} },
}) });
} }
} }
}, 1000) }, 1000);
/* timeoutTimer = setTimeout(() => { /* timeoutTimer = setTimeout(() => {
clearInterval(pollTimer) clearInterval(pollTimer)
setLoading(false) setLoading(false)
}, 180000)*/ }, 180000)*/
} };
let wsClient=null let wsClient = null;
const watchWebSocket = () => { const watchWebSocket = () => {
wsClient = new WebSocketClient( wsClient = new WebSocketClient(config.public.NUXT_PUBLIC_SOCKET_URL);
config.public.NUXT_PUBLIC_SOCKET_URL const ws = wsClient.connect("/api/v1/order/ws/v2", {
)
const ws = wsClient.connect('/api/v1/order/ws/v2', {
PayUid: route.query.payUid, PayUid: route.query.payUid,
}) });
ws.onOpen(() => { ws.onOpen(() => {});
})
ws.onMessage((event) => { ws.onMessage((event) => {
router.replace({ router.replace({
path: route.query.returnUrl, path: route.query.returnUrl,
query: { query: {
orderNo: route.query.payUid orderNo: route.query.payUid,
} },
}) });
}) });
ws.onClose(() => { ws.onClose(() => {});
}) };
}
async function initialize() { async function initialize() {
const clientSecret = route.query.stripeKey const clientSecret = route.query.stripeKey;
const appearance = { const appearance = {
theme: 'stripe', theme: "stripe",
} };
elements.value = stripe.elements({ appearance, clientSecret }) elements.value = stripe.elements({ appearance, clientSecret });
const paymentElementOptions = { const paymentElementOptions = {
layout: "accordion", layout: "accordion",
} };
const paymentElement = elements.value.create("payment", paymentElementOptions) const paymentElement = elements.value.create(
paymentElement.mount("#payment-element") "payment",
paymentElementOptions
);
paymentElement.mount("#payment-element");
} }
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault() e.preventDefault();
setLoading(true) setLoading(true);
const { error } = await stripe.confirmPayment({ const { error } = await stripe.confirmPayment({
elements: elements.value, elements: elements.value,
confirmParams: { confirmParams: {
return_url: `${baseURL}${route.query.returnUrl}?orderNo=${route.query.payUid}`, return_url: `${baseURL}${route.query.returnUrl}?orderNo=${route.query.payUid}`,
}, },
}) });
if (error) { if (error) {
/* clearInterval(pollTimer)
clearTimeout(timeoutTimer)*/
if (error.type === "card_error" || error.type === "validation_error") { if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message) showMessage(error.message);
} else { } else {
showMessage("An unexpected error occurred.") showMessage("An unexpected error occurred.");
} }
setLoading(false) setLoading(false);
} }
} }
function showMessage(messageText) { function showMessage(messageText) {
paymentMessage.value = messageText paymentMessage.value = messageText;
setTimeout(() => { setTimeout(() => {
paymentMessage.value = '' paymentMessage.value = "";
}, 4000) }, 4000);
} }
function setLoading(loading) { function setLoading(loading) {
isLoading.value = loading isLoading.value = loading;
showSpinner.value = loading showSpinner.value = loading;
} }
onUnmounted(() => {
onUnmounted(()=>{ wsClient.disconnect();
wsClient.disconnect() clearTimeout(timeoutTimer);
clearTimeout(timeoutTimer) clearInterval(pollTimer);
clearInterval(pollTimer) });
})
onMounted(() => { onMounted(() => {
watchWebSocket() watchWebSocket();
initialize() initialize();
startPolling() startPolling();
}) });
</script> </script>
<template> <template>
<form id="payment-form" @submit="handleSubmit"> <form id="payment-form" @submit="handleSubmit">
<div id="payment-element"> <div id="payment-element"></div>
</div>
<button id="submit"> <button id="submit">
<div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div> <div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div>
<span id="button-text" :class="{ hidden: showSpinner }">Pay now</span> <span id="button-text" :class="{ hidden: showSpinner }">Pay now</span>
</button> </button>
<div id="payment-message" :class="{ hidden: !paymentMessage }">{{ paymentMessage }}</div> <div id="payment-message" :class="{ hidden: !paymentMessage }">
{{ paymentMessage }}
</div>
</form> </form>
</template> </template>
@ -188,7 +183,7 @@ form {
/* Buttons and links */ /* Buttons and links */
button { button {
background: #0055DE; background: #0055de;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
color: #ffffff; color: #ffffff;
border-radius: 4px; border-radius: 4px;
@ -237,7 +232,7 @@ button:disabled {
.spinner:before { .spinner:before {
width: 10.4px; width: 10.4px;
height: 20.4px; height: 20.4px;
background: #0055DE; background: #0055de;
border-radius: 20.4px 0 0 20.4px; border-radius: 20.4px 0 0 20.4px;
top: -0.2px; top: -0.2px;
left: -0.2px; left: -0.2px;
@ -249,7 +244,7 @@ button:disabled {
.spinner:after { .spinner:after {
width: 10.4px; width: 10.4px;
height: 10.2px; height: 10.2px;
background: #0055DE; background: #0055de;
border-radius: 0 10.2px 10.2px 0; border-radius: 0 10.2px 10.2px 0;
top: -0.1px; top: -0.1px;
left: 10.2px; left: 10.2px;
@ -289,7 +284,7 @@ button:disabled {
h2 { h2 {
margin: 0; margin: 0;
color: #30313D; color: #30313d;
text-align: center; text-align: center;
} }
@ -315,11 +310,11 @@ table {
border-collapse: collapse; border-collapse: collapse;
} }
table tbody tr:first-child td { table tbody tr:first-child td {
border-top: 1px solid #E6E6E6; /* Top border */ border-top: 1px solid #e6e6e6; /* Top border */
padding-top: 10px; padding-top: 10px;
} }
table tbody tr:last-child td { table tbody tr:last-child td {
border-bottom: 1px solid #E6E6E6; /* Bottom border */ border-bottom: 1px solid #e6e6e6; /* Bottom border */
} }
td { td {
padding-bottom: 10px; padding-bottom: 10px;
@ -327,21 +322,21 @@ td {
.TableContent { .TableContent {
text-align: right; text-align: right;
color: #6D6E78; color: #6d6e78;
} }
.TableLabel { .TableLabel {
font-weight: 600; font-weight: 600;
color: #30313D; color: #30313d;
} }
#view-details { #view-details {
color: #0055DE; color: #0055de;
} }
#retry-button { #retry-button {
text-align: center; text-align: center;
background: #0055DE; background: #0055de;
color: #ffffff; color: #ffffff;
border-radius: 4px; border-radius: 4px;
border: 0; border: 0;
@ -378,7 +373,8 @@ td {
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
form, #payment-status{ form,
#payment-status {
width: 80vw; width: 80vw;
min-width: initial; min-width: initial;
} }
@ -388,7 +384,7 @@ td {
} }
form { form {
width:100vw; width: 100vw;
align-self: center; align-self: center;
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);

View File

@ -1,104 +1,110 @@
<script setup> <script setup>
import {liveStore} from "~/stores/live/index.js"; import { liveStore } from "~/stores/live/index.js";
import {createBuyOrder} from "~/api/goods/index.js"; import { createBuyOrder } from "~/api/goods/index.js";
import {goodStore} from "~/stores/goods/index.js"; import { goodStore } from "~/stores/goods/index.js";
import {showLoadingToast, closeToast} from 'vant'; import { showLoadingToast, closeToast } from "vant";
import {authStore} from "~/stores/auth/index.js"; import { authStore } from "~/stores/auth/index.js";
import {message} from "~/components/x-message/useMessage.js"; import { message } from "~/components/x-message/useMessage.js";
import {createOrder} from "~/api-collect-code/goods/index.js"; import { createOrder } from "~/api-collect-code/goods/index.js";
import {codeAuthStore} from "~/stores-collect-code/auth/index.js"; import { codeAuthStore } from "~/stores-collect-code/auth/index.js";
import {useI18n} from "vue-i18n"; import { useI18n } from "vue-i18n";
const {t} = useI18n(); const { t } = useI18n();
const {checkoutSessionUrl,qrUid,qrData,codePKey,codePayUid} = codeAuthStore() const { checkoutSessionUrl, qrUid, qrData, codePKey, codePayUid } =
const payStatus = ref(0) codeAuthStore();
const payStatus = ref(0);
definePageMeta({ definePageMeta({
i18n: 'payment.title' i18n: "payment.title",
}) });
const changePayStatus = () => { const changePayStatus = () => {
payStatus.value = payStatus.value === 0 ? 1 : 0 payStatus.value = payStatus.value === 0 ? 1 : 0;
} };
const amount = ref('') const amount = ref("");
const router = useRouter() const router = useRouter();
const confirmPay = async () => { const confirmPay = async () => {
if (payStatus.value === 1 && !amount.value) {
message.warning(t('collectCode.payment.enterAmount'))
return
}
if (Number(qrData.value.leftPrice) < Number(amount.value)) {
message.warning(t('collectCode.payment.exceedTotal'))
return
}
showLoadingToast({ showLoadingToast({
message: t('common.loading'), message: t("common.loading"),
forbidClick: true, forbidClick: true,
}); });
const res = await createOrder({ const res = await createOrder({
price: payStatus.value === 0 ? qrData.value.leftPrice : amount.value, price: payStatus.value === 0 ? qrData.value.leftPrice : amount.value,
currency: qrData.value.currency, currency: qrData.value.currency,
qrUid:qrUid.value, qrUid: qrUid.value,
testReturnHost:window.location.origin, testReturnHost: window.location.origin,
testReturnEndPoint: '/collectCode/payment/result' testReturnEndPoint: "/collectCode/payment/result",
}) });
if (res.status === 0) { if (res.status === 0) {
codePKey.value=res.data.checkoutSessionUrl codePKey.value = res.data.checkoutSessionUrl;
codePayUid.value=res.data.payUid codePayUid.value = res.data.payUid;
router.push({ router.push({
path:'/checkoutPage', path: "/checkoutPage",
query:{ query: {
payUid:res.data.payUid, payUid: res.data.payUid,
returnUrl:'/collectCode/payment/result', returnUrl: "/collectCode/payment/result",
stripeKey:res.data.checkoutSessionUrl stripeKey: res.data.checkoutSessionUrl,
},
});
} }
}) };
}
}
const handleInput = (e) => { const handleInput = (e) => {
// //
const value = e.target.value const value = e.target.value;
// //
let newValue = value.replace(/[^\d.]/g, '') let newValue = value.replace(/[^\d.]/g, "");
// //
newValue = newValue.replace(/\.{2,}/g, '.') newValue = newValue.replace(/\.{2,}/g, ".");
// //
newValue = newValue.replace(/^(\d*\.\d*)\./, '$1') newValue = newValue.replace(/^(\d*\.\d*)\./, "$1");
// //
if (newValue.indexOf('.') > 0) { if (newValue.indexOf(".") > 0) {
newValue = newValue.slice(0, newValue.indexOf('.') + 3) newValue = newValue.slice(0, newValue.indexOf(".") + 3);
} }
// 0 // 0
newValue = newValue.replace(/^0+(\d)/, '$1') newValue = newValue.replace(/^0+(\d)/, "$1");
amount.value = newValue amount.value = newValue;
} };
</script> </script>
<template> <template>
<div <div
class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover flex-grow-1 flex flex-col items-center pt-183px px-30px"> class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover flex-grow-1 flex flex-col items-center pt-183px px-30px"
>
<div class="mb-30px"> <div class="mb-30px">
<img class="w-126px h-126px" src="@/static/images/dddf34@2x.png" alt=""> <img class="w-126px h-126px" src="@/static/images/dddf34@2x.png" alt="" />
</div> </div>
<div class="text-#1A1A1A text-16px mb-25px font-bold">{{ payStatus === 0 ? $t('collectCode.payment.fullPayment') : $t('collectCode.payment.partialPayment') }}</div> <div class="text-#1A1A1A text-16px mb-25px font-bold">
<div class="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">{{ qrData.currency }} {{
payStatus === 0
? $t("collectCode.payment.fullPayment")
: $t("collectCode.payment.partialPayment")
}}
</div>
<div
class="text-#999999 text-16px mb-24px font-bold"
v-if="payStatus === 0"
>
{{ qrData.currency }}
{{ qrData?.leftPrice }} {{ qrData?.leftPrice }}
</div> </div>
<div class="mb-12px" v-else> <div class="mb-12px" v-else>
<input v-model="amount" class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" <input
:placeholder="`${$t('collectCode.payment.maxAmount')} ${qrData.currency} ${qrData?.leftPrice}`" @input="handleInput"> v-model="amount"
class="w-272px h-48px bg-#F3F3F3 px-11px text-16px"
type="text"
:placeholder="`${$t('collectCode.payment.maxAmount')} ${
qrData.currency
} ${qrData?.leftPrice}`"
@input="handleInput"
/>
</div> </div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{ payStatus === 1 ? $t('collectCode.payment.fullPayment') : $t('collectCode.payment.partialPayment') }}</div>
<div class="w-full mt-auto mb-40px"> <div class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="confirmPay"> <van-button type="primary" block @click="confirmPay">
{{ $t('collectCode.payment.confirmPayment') }} {{ $t("collectCode.payment.confirmPayment") }}
</van-button> </van-button>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1,37 +1,47 @@
<script setup> <script setup>
import {orderQuery} from "~/api/goods/index.js"; import { orderQuery } from "~/api/goods/index.js";
definePageMeta({ definePageMeta({
i18n: 'payment.text1', i18n: "payment.text1",
}) });
const router = useRouter() const router = useRouter();
const {t}=useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const resData=ref({}) const resData = ref({});
const res=await orderQuery({ const res = await orderQuery({
orderNo:route.query.orderNo orderNo: route.query.orderNo,
}) });
if (res.status===0){ if (res.status === 0) {
resData.value=res.data resData.value = res.data;
}
const statusLabel={
1:t('payment.text2'),
2:t('payment.text3'),
3:t('payment.text4'),
4:t('payment.text5'),
}
const goHome=()=>{
router.push('/')
} }
const statusLabel = {
1: t("payment.text2"),
2: t("payment.text3"),
3: t("payment.text4"),
4: t("payment.text5"),
};
const goHome = () => {
router.push("/");
};
</script> </script>
<template> <template>
<div class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover grow-1 flex flex-col items-center px-30px"> <div
class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover grow-1 flex flex-col items-center px-30px"
>
<div class="flex flex-col items-center mt-150px"> <div class="flex flex-col items-center mt-150px">
<img class="w-119px h-120px mb-36px" src="@/static/images/5554@2x1.png" alt=""> <img
<div class="text-#000 text-16px mb-25px">{{statusLabel[resData.status]}}!</div> class="w-119px h-120px mb-36px"
<div class="text-#999 text-16px">{{resData.currency}}{{resData.money}}</div> src="@/static/images/5554@2x1.png"
alt=""
/>
<div class="text-#000 text-16px mb-25px">
{{ statusLabel[resData.status] }}!
</div> </div>
<!-- <div class="w-full mt-auto mb-40px"> <div class="text-#999 text-16px">
{{ resData.currency }}{{ resData.money }}
</div>
</div>
<!-- <div class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="goHome"> <van-button type="primary" block @click="goHome">
回到首页 回到首页
</van-button> </van-button>
@ -39,6 +49,4 @@ const goHome=()=>{
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1,37 +1,37 @@
<script setup> <script setup>
import {useI18n} from "vue-i18n"; import { useI18n } from "vue-i18n";
import XVanSelect from '@/components/x-van-select/index.vue' import XVanSelect from "@/components/x-van-select/index.vue";
import XVanDate from '@/components/x-van-date/index.vue' import XVanDate from "@/components/x-van-date/index.vue";
import {codeAuthStore} from "@/stores-collect-code/auth/index.js"; import { codeAuthStore } from "@/stores-collect-code/auth/index.js";
import {message} from "@/components/x-message/useMessage.js"; import { message } from "@/components/x-message/useMessage.js";
import {fddInfo, offlineQrcode} from "~/api-collect-code/goods/index.js"; import { fddInfo, offlineQrcode } from "~/api-collect-code/goods/index.js";
import {signOffline} from "~/api/goods/index.js"; import { signOffline } from "~/api/goods/index.js";
const {formData,number,auctionArtworkUuid,qrUid,qrData}=codeAuthStore() const { formData, number, auctionArtworkUuid, qrUid, qrData } = codeAuthStore();
definePageMeta({ definePageMeta({
layout: 'default', layout: "default",
i18n: 'menu.profile', i18n: "menu.profile",
}) });
const {t} = useI18n() const { t } = useI18n();
const router = useRouter() const router = useRouter();
const route = useRoute() const route = useRoute();
const columns = ref([ const columns = ref([
{text: t('realAuth.male'), value: 1}, { text: t("realAuth.male"), value: 1 },
{text: t('realAuth.female'), value: 2}, { text: t("realAuth.female"), value: 2 },
]) ]);
const columns1 = ref([ const columns1 = ref([
{text: t('realAuth.idTypeString'), value: 1}, { text: t("realAuth.idTypeString"), value: 1 },
{text: t('realAuth.passport'), value: 2}, { text: t("realAuth.passport"), value: 2 },
{text: t('realAuth.other'), value: 3}, { text: t("realAuth.other"), value: 3 },
]) ]);
const goCountryRegion=()=>{ const goCountryRegion = () => {
router.push({ router.push({
path:'/countryRegion' path: "/countryRegion",
}) });
} };
function isFormComplete(obj) { function isFormComplete(obj) {
for (const key in obj) { for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) { if (typeof obj[key] === "object" && obj[key] !== null) {
if (!isFormComplete(obj[key])) { if (!isFormComplete(obj[key])) {
return false; return false;
} }
@ -42,116 +42,140 @@ function isFormComplete(obj) {
return true; return true;
} }
const getData=async ()=>{ const getData = async () => {
const res=await offlineQrcode({ const res = await offlineQrcode({
qrUid:qrUid.value qrUid: qrUid.value,
}) });
if (res.status===0){ if (res.status === 0) {
qrData.value=res.data qrData.value = res.data;
} }
} };
const initData= async ()=>{ const initData = async () => {
if (route.query.number){ if (route.query.number) {
number.value=Number(route.query.number) number.value = Number(route.query.number);
} }
if (route.query.qrUid){ if (route.query.qrUid) {
qrUid.value=route.query.qrUid qrUid.value = route.query.qrUid;
} }
// //
if (number.value==2){ if (number.value == 2) {
await getData() await getData();
if (qrData.value.payStatus===4){ if (qrData.value.payStatus === 4) {
router.replace('/collectCode/payment') router.replace("/collectCode/payment");
} }
} }
if (route.query.zone){ if (route.query.zone) {
formData.value.countryCode=route.query.zone formData.value.countryCode = route.query.zone;
}else {
formData.value.countryCode='86'
}
}
const nextClick=async ()=>{
//
if (number.value==1){
if (!isFormComplete(formData.value)){
message.warning(t('signature.error.incompleteForm'))
return
}
//
if (formData.value.countryCode==='86'&&formData.value.cardType===1){
const res=await fddInfo({
phone:formData.value.phone
})
if (res.status===0){
if (res.data.status===2){
router.push('/collectCode/signature/protocol')
}else {
const res1=await signOffline({
userInfo:formData.value,
signOrder:Number(number.value),
testReturnHost:window.location.origin,
testReturnEndPoint:'/collectCode/signature/protocol',
})
if (res1.status===0){
window.location.href=res1.data.fddVerifyUrl
}
}
}
} else { } else {
// formData.value.countryCode = "86";
router.push('/collectCode/signature/protocol')
} }
} else if(number.value==2) { };
if (!formData.value.phone || !formData.value.countryCode || !formData.value.userName){ const nextClick = async () => {
message.warning('请填写完整信息') //
return if (number.value == 2) {
if (
!formData.value.phone ||
!formData.value.countryCode ||
!formData.value.userName
) {
message.warning("请填写完整信息");
return;
} }
router.push('/collectCode/signature/protocol') router.push("/collectCode/signature/protocol");
} }
} };
initData() initData();
</script> </script>
<template> <template>
<div <div
class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] h-screen-nav bg-cover pt-77px flex-grow-1 flex flex-col "> class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] h-screen-nav bg-cover pt-77px flex-grow-1 flex flex-col"
>
<div class="text-16px text-#191919 font-bold mb-40px px-34px"> <div class="text-16px text-#191919 font-bold mb-40px px-34px">
{{ $t('personal.title') }} {{ $t("personal.title") }}
</div> </div>
<div class="grow-1 px-34px"> <div class="grow-1 px-34px">
<van-field v-model="formData.phone" type="tel" :label-width="161" :label="$t('personal.text')" class="mb-10px" :placeholder="$t('realAuth.phonePlaceholder')"> <van-field
v-model="formData.phone"
type="tel"
:label-width="161"
:label="$t('personal.text')"
class="mb-10px"
:placeholder="$t('realAuth.phonePlaceholder')"
>
<template #label> <template #label>
<div class="flex"> <div class="flex">
<div class="mr-41px whitespace-nowrap">{{ $t('profile.phone') }}</div> <div class="mr-41px whitespace-nowrap">
{{ $t("profile.phone") }}
</div>
<div @click="goCountryRegion"> <div @click="goCountryRegion">
<span class="mr-13px">+ {{ formData.countryCode }}</span> <span class="mr-13px">+ {{ formData.countryCode }}</span>
<van-icon name="arrow-down" class="text-#777777"/> <van-icon name="arrow-down" class="text-#777777" />
</div> </div>
</div> </div>
</template> </template>
</van-field> </van-field>
<van-field :label="$t('profile.name')" v-model="formData.userName" class="mb-10px" :placeholder="$t('realAuth.namePlaceholder')"/> <van-field
<template v-if="number===1"> :label="$t('profile.name')"
<x-van-select v-model="formData.gender" :label="$t('realAuth.gender')" :columns="columns"/> v-model="formData.userName"
<x-van-date :label="$t('realAuth.birthday')" v-model="formData.birthday" /> class="mb-10px"
<van-field :label="$t('realAuth.adress')" v-model="formData.address" class="mb-10px" :placeholder="$t('realAuth.adressPlaceholder')"/> :placeholder="$t('realAuth.namePlaceholder')"
<van-field :label="$t('realAuth.bank')" v-model="formData.bankName" class="mb-10px" :placeholder="$t('realAuth.bankPlaceholder')"/> />
<van-field :label="$t('realAuth.bankCard')" v-model="formData.bankNo" class="mb-10px" :placeholder="$t('realAuth.bankCardPlaceholder')"/> <template v-if="number === 1">
<x-van-select v-model="formData.cardType" :label="$t('realAuth.idTye')" :columns="columns1"/> <x-van-select
<van-field :label="$t('realAuth.idCard')" v-model="formData.cardId" class="mb-10px" :placeholder="$t('realAuth.idCardPlaceholder')"/> v-model="formData.gender"
:label="$t('realAuth.gender')"
:columns="columns"
/>
<x-van-date
:label="$t('realAuth.birthday')"
v-model="formData.birthday"
/>
<van-field
:label="$t('realAuth.adress')"
v-model="formData.address"
class="mb-10px"
:placeholder="$t('realAuth.adressPlaceholder')"
/>
<van-field
:label="$t('realAuth.bank')"
v-model="formData.bankName"
class="mb-10px"
:placeholder="$t('realAuth.bankPlaceholder')"
/>
<van-field
:label="$t('realAuth.bankCard')"
v-model="formData.bankNo"
class="mb-10px"
:placeholder="$t('realAuth.bankCardPlaceholder')"
/>
<x-van-select
v-model="formData.cardType"
:label="$t('realAuth.idTye')"
:columns="columns1"
/>
<van-field
:label="$t('realAuth.idCard')"
v-model="formData.cardId"
class="mb-10px"
:placeholder="$t('realAuth.idCardPlaceholder')"
/>
</template> </template>
</div> </div>
<div class="h-81px bg-#fff flex justify-center pt-7px border-t shrink-0"> <div class="h-81px bg-#fff flex justify-center pt-7px border-t shrink-0">
<van-button color="#2B53AC" class="w-213px van-btn-h-38px" @click="nextClick">{{ $t('personal.next') }}</van-button> <van-button
color="#2B53AC"
class="w-213px van-btn-h-38px"
@click="nextClick"
>{{ $t("personal.next") }}</van-button
>
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.van-cell.van-field){ :deep(.van-cell.van-field) {
padding-left: 0; padding-left: 0;
} }
</style> </style>

View File

@ -1,22 +1,18 @@
<script setup> <script setup>
import CheckoutPage from '@/components/stripe/CheckoutPage.vue' import CheckoutPage from "@/components/stripe/CheckoutPage.vue";
import CompletePage from '@/components/stripe/CompletePage.vue'
definePageMeta({ definePageMeta({
layout: 'default', layout: "default",
title: 'Stripe支付' title: "Stripe支付",
}) });
const route = useRoute() const route = useRoute();
const key=route.query.key??'' const key = route.query.key ?? "";
</script> </script>
<template> <template>
<div> <div>
<CheckoutPage/> <CheckoutPage />
<!-- <iframe class="w-100vw h-100vh" :src="`/stripe/checkout.html?key=${key}`"></iframe> --> <!-- <iframe class="w-100vw h-100vh" :src="`/stripe/checkout.html?key=${key}`"></iframe> -->
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1,18 +0,0 @@
<script setup>
import {goodStore} from "@/stores/goods/index.js";
import xImage from '@/components/x-image/index.vue'
const {
auctionDetail
} = goodStore()
</script>
<template>
<div class="px-16px pt-14px">
<div class="text-#575757 text-14px" v-html="auctionDetail.info">
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,28 +0,0 @@
<script setup>
import xPopup from '@/components/x-popup/index.vue'
import ItemDetail from "@/components/itemDetail/index.vue";
import {goodStore} from "@/stores/goods/index.js";
const {
artWorkDetail
} = goodStore()
const props = defineProps({
show: {
type: Boolean,
default: false
},
detailInfo: {
type: Object,
default: null
}
})
const emit = defineEmits(['update:show'])
const handleClose = () => {
emit('update:show', false)
}
</script>
<template>
<xPopup :show="show" :title="$t('home.lot_detail')" @update:show="handleClose">
<ItemDetail :detailInfo="detailInfo" />
</xPopup>
</template>

View File

@ -1,128 +0,0 @@
<script setup>
import { ref } from 'vue'
import { goodStore } from "@/stores/goods"
import DetailPopup from '../DetailPopup/index.vue'
import WaterfallFlow from '@/components/waterfallFlow/index.vue'
const {
itemList,
pageRef,
currentItem,
loading: storeLoading,
getArtworkList,
} = goodStore()
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
})
//
const loadMore = async () => {
pageRef.value.page++
const { finished } = await getArtworkList()
localState.value.finished = finished
}
//
const onRefresh = async () => {
try {
localState.value.refreshing = true
localState.value.finished = false
const { finished } = await getArtworkList(true)
localState.value.finished = finished
} finally {
localState.value.refreshing = false
}
}
//
const openShow = async (item) => {
localState.value.showDetail = true
currentItem.value = item
}
/**
* 将数字格式化为"250XX"格式其中XX是两位数
* @param {number} num - 要格式化的数字
* @return {string} - 格式化后的字符串
*/
function formatNumber(num) {
//
if (typeof num !== 'number' && isNaN(Number(num))) {
throw new Error('输入必须是有效数字');
}
// ()
const number = Number(num);
// 0
const formattedNum = number.toString().padStart(2, '0');
// "250"
return `250${formattedNum}`;
}
</script>
<template>
<div class="px-[16px] pt-[16px]">
<van-pull-refresh
v-model="localState.refreshing"
:success-duration="700"
@refresh="onRefresh"
>
<template #success>
<van-icon name="success" /> <span>{{ $t('home.refresh_show') }}</span>
</template>
<van-list
v-model:loading="storeLoading"
:finished="localState.finished"
:finished-text="$t('home.finished_text')"
@load="loadMore"
>
<div class="w-full flex gap-[16px]">
<WaterfallFlow :items="itemList" :column-count="2">
<template #default="{ item, index }">
<div
@click="openShow(item)"
class="w-full"
>
<div class="relative w-full">
<img
:src="item.artwork?.hdPic"
class="w-full object-cover rounded-4px"
/>
<div
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[55px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
>
Lot{{ formatNumber(item.index) }}
</div>
</div>
<div class="pt-[8px]">
<div class="text-[14px] text-[#000000] leading-[20px]">
{{ item?.artwork?.name }} | {{item?.artwork?.artistName}}
</div>
<div class="mt-[4px] text-[12px] text-[#575757]">
{{ $t('home.start_price') }}{{ item?.startPrice??0 }}
</div>
<div
v-if="item.soldPrice"
class="mt-[4px] text-[12px] text-[#b58047]"
>
{{ $t('home.close_price') }}{{ item?.soldPrice??0 }}
</div>
</div>
</div>
</template>
</WaterfallFlow>
</div>
</van-list>
</van-pull-refresh>
<DetailPopup v-model:show="localState.showDetail" :detailInfo="currentItem"></DetailPopup>
</div>
</template>
<style scoped>
.content {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
</style>

View File

@ -1,95 +0,0 @@
<script setup>
import liveRoom from '@/pages/liveRoom/index.client.vue';
import {goodStore} from "@/stores/goods/index.js";
import ItemList from './components/ItemList/index.vue'
import Cescribe from './components/Cescribe/index.vue'
import {liveStore} from "~/stores/live/index.js";
const {auctionDetail,getArtworkList} = goodStore();
const {fullLive} = liveStore()
const changeLive = () => {
if (!fullLive.value){
if (auctionDetail.value.isLiving===1){
fullLive.value = true;
getArtworkList(true)
}
}
}
</script>
<template>
<div class="grow-1">
<client-only>
<div class="relative" @click="changeLive">
<liveRoom :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/>
<div v-if="auctionDetail.isLiving===1" class="absolute h-188px w-screen pt-36px flex flex-col text-#fff top-0 left-0 items-center">
<div class="text-18px mb-5px">{{ auctionDetail.title }}</div>
<div class="text-12px mb-54px">{{ $t('home.text1') }}<van-icon name="arrow" /></div>
<div><span>-</span> <span class="text-12px mx-5px">{{auctionDetail.totalNum}}{{ $t('common.items') }}{{ $t('common.auction') }}</span> <span>-</span></div>
<div class="text-12px">{{auctionDetail.startDate}} {{auctionDetail.startTitle}}</div>
</div>
<div v-else class="absolute h-188px w-screen pt-36px flex flex-col text-#fff top-0 left-0 items-center bg-[url('@/static/images/z6022@2x.png')]">
<div class="text-18px mb-5px">{{ auctionDetail.title }}</div>
<div class="text-12px mb-54px">{{$t('home.text3')}}{{auctionDetail.isLiving===2?$t('home.text4'):$t('home.text5')}}</div>
<div><span>-</span> <span class="text-12px mx-5px">{{auctionDetail.totalNum}}{{ $t('common.items') }}{{ $t('common.auction') }}</span> <span>-</span></div>
<div class="text-12px">{{auctionDetail.startDate}} {{auctionDetail.startTitle}}</div>
</div>
</div>
</client-only>
<div v-if="!fullLive" class="bg-#fff">
<van-tabs sticky animated>
<van-tab :title="$t('home.tab1')">
<ItemList></ItemList>
</van-tab>
<van-tab :title="$t('home.tab2')">
<Cescribe></Cescribe>
</van-tab>
</van-tabs>
<van-back-top right="15vw" bottom="10vh"/>
</div>
</div>
</template>
<style scoped lang="scss">
.ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
:deep(.van-swipe__indicator) {
width: 8px;
height: 8px;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
:deep(.van-swipe__indicator:not(.van-swipe__indicator--active)) {
background: rgba(0, 0, 0, 0.8);
}
.changeLive {
width: 100%;
overflow: hidden;
transition: height 0.4s ease, transform 0.4s ease;
}
.changeLive.collapsed {
height: 188px;
}
.changeLive.expanded {
position: absolute;
z-index: 10;
height: calc(100vh - var(--van-nav-bar-height));
}
</style>

View File

@ -1,10 +1,10 @@
<script setup> <script setup>
import Home from './home/index.vue' import Payment from "./payment/index.vue";
definePageMeta({ definePageMeta({
layout: 'default', layout: "default",
i18n: 'menu.home', i18n: "menu.payment",
}) });
</script> </script>
<template> <template>
<Home/> <Payment />
</template> </template>

View File

@ -1,97 +1,118 @@
<script setup> <script setup>
import {liveStore} from "~/stores/live/index.js"; import { liveStore } from "~/stores/live/index.js";
import {createBuyOrder} from "~/api/goods/index.js"; import { createBuyOrder } from "~/api/goods/index.js";
import {goodStore} from "~/stores/goods/index.js"; import { goodStore } from "~/stores/goods/index.js";
import { showLoadingToast ,closeToast} from 'vant'; import { showLoadingToast, closeToast } from "vant";
import {authStore} from "~/stores/auth/index.js"; import { authStore } from "~/stores/auth/index.js";
import {message} from "~/components/x-message/useMessage.js"; import { message } from "~/components/x-message/useMessage.js";
const {checkoutSessionUrl,payment,payUid}= authStore() const { checkoutSessionUrl, payment, payUid } = authStore();
const payStatus=ref(0) const payStatus = ref(0);
definePageMeta({ definePageMeta({
i18n: 'payment.title' i18n: "payment.title",
}) });
const {t}=useI18n() const { t } = useI18n();
const router=useRouter() const router = useRouter();
const changePayStatus=()=>{ const changePayStatus = () => {
payStatus.value=payStatus.value===0?1:0 payStatus.value = payStatus.value === 0 ? 1 : 0;
} };
const { auctionData} = liveStore() const { auctionData } = liveStore();
const amount=ref('') const amount = ref("");
const confirmPay=async ()=>{ const confirmPay = async () => {
if (payStatus.value===1&&!amount.value){ if (payStatus.value === 1 && !amount.value) {
message.warning(t('payment.amountRequired')) message.warning(t("payment.amountRequired"));
return return;
} }
if (Number(payment.value.leftPrice)<Number(amount.value)){ if (Number(payment.value.leftPrice) < Number(amount.value)) {
message.warning(t('payment.exceedAmount')) message.warning(t("payment.exceedAmount"));
return return;
} }
showLoadingToast({ showLoadingToast({
message: t('payment.loading'), message: t("payment.loading"),
forbidClick: true, forbidClick: true,
}); });
const res=await createBuyOrder({ const res = await createBuyOrder({
buyUid:payment.value.buyUid, buyUid: payment.value.buyUid,
price:payStatus.value===0?payment.value.leftPrice:amount.value, price: payStatus.value === 0 ? payment.value.leftPrice : amount.value,
currency:payment.value.leftCurrency, currency: payment.value.leftCurrency,
testReturnHost:window.location.origin, testReturnHost: window.location.origin,
testReturnEndPoint:'/payment/result' testReturnEndPoint: "/payment/result",
}) });
if (res.status===0){ if (res.status === 0) {
checkoutSessionUrl.value=res.data.checkoutSessionUrl checkoutSessionUrl.value = res.data.checkoutSessionUrl;
payUid.value=res.data.payUid payUid.value = res.data.payUid;
router.push({ router.push({
path:'/checkoutPage', path: "/checkoutPage",
query:{ query: {
payUid:res.data.payUid, payUid: res.data.payUid,
returnUrl:'/payment/result', returnUrl: "/payment/result",
stripeKey:res.data.checkoutSessionUrl stripeKey: res.data.checkoutSessionUrl,
},
});
} }
}) };
}
}
const handleInput = (e) => { const handleInput = (e) => {
// //
const value = e.target.value const value = e.target.value;
// //
let newValue = value.replace(/[^\d.]/g, '') let newValue = value.replace(/[^\d.]/g, "");
// //
newValue = newValue.replace(/\.{2,}/g, '.') newValue = newValue.replace(/\.{2,}/g, ".");
// //
newValue = newValue.replace(/^(\d*\.\d*)\./, '$1') newValue = newValue.replace(/^(\d*\.\d*)\./, "$1");
// //
if (newValue.indexOf('.') > 0) { if (newValue.indexOf(".") > 0) {
newValue = newValue.slice(0, newValue.indexOf('.') + 3) newValue = newValue.slice(0, newValue.indexOf(".") + 3);
} }
// 0 // 0
newValue = newValue.replace(/^0+(\d)/, '$1') newValue = newValue.replace(/^0+(\d)/, "$1");
amount.value = newValue amount.value = newValue;
} };
</script> </script>
<template> <template>
<div class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover flex-grow-1 flex flex-col items-center pt-183px px-30px"> <div
class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover flex-grow-1 flex flex-col items-center pt-183px px-30px"
>
<div class="mb-30px"> <div class="mb-30px">
<img class="w-126px h-126px" src="@/static/images/dddf34@2x.png" alt=""> <img class="w-126px h-126px" src="@/static/images/dddf34@2x.png" alt="" />
</div>
<div class="text-#1A1A1A text-16px mb-25px font-bold">
{{
payStatus === 0 ? t("payment.fullPayment") : t("payment.partialPayment")
}}
</div>
<div
class="text-#999999 text-16px mb-24px font-bold"
v-if="payStatus === 0"
>
{{ payment.leftCurrency }} {{ payment?.leftPrice }}
</div> </div>
<div class="text-#1A1A1A text-16px mb-25px font-bold">{{payStatus===0 ? t('payment.fullPayment') : t('payment.partialPayment')}}</div>
<div class="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">{{payment.leftCurrency}} {{payment?.leftPrice}}</div>
<div class="mb-12px" v-else> <div class="mb-12px" v-else>
<input v-model="amount" class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" :placeholder="`${t('payment.placeholder.amount')}${payment.leftCurrency}${payment?.leftPrice}`" @input="handleInput"> <input
v-model="amount"
class="w-272px h-48px bg-#F3F3F3 px-11px text-16px"
type="text"
:placeholder="`${t('payment.placeholder.amount')}${
payment.leftCurrency
}${payment?.leftPrice}`"
@input="handleInput"
/>
</div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">
{{
payStatus === 1 ? t("payment.fullPayment") : t("payment.partialPayment")
}}
</div> </div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{payStatus===1 ? t('payment.fullPayment') : t('payment.partialPayment')}}</div>
<div class="w-full mt-auto mb-40px"> <div class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="confirmPay"> <van-button type="primary" block @click="confirmPay">
{{ t('payment.confirm') }} {{ t("payment.confirm") }}
</van-button> </van-button>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1,94 +1,102 @@
<script setup> <script setup>
import {orderQuery} from "~/api/goods/index.js"; //
import { showLoadingToast, closeToast } from 'vant'; import { orderQuery } from "~/api/goods/index.js";
import { showLoadingToast, closeToast } from "vant";
definePageMeta({ definePageMeta({
i18n: 'payment.text1', i18n: "payment.text1",
}) });
const router = useRouter();
const router = useRouter() const { t } = useI18n();
const {t} = useI18n();
const route = useRoute(); const route = useRoute();
const resData = ref({}) const resData = ref({});
let timer = null let timer = null;
let startTime = Date.now() let startTime = Date.now();
const queryOrder = async () => { const queryOrder = async () => {
// 5 // 5
if (Date.now() - startTime > 5000) { if (Date.now() - startTime > 5000) {
clearInterval(timer) clearInterval(timer);
closeToast() closeToast();
return return;
} }
showLoadingToast({ showLoadingToast({
message: '加载中...', message: "加载中...",
forbidClick: true, forbidClick: true,
}); });
try { try {
const res = await orderQuery({ const res = await orderQuery({
orderNo: route.query.orderNo orderNo: route.query.orderNo,
}) });
if (res.status === 0) { if (res.status === 0) {
resData.value = res.data resData.value = res.data;
// //
if (resData.value.status === 1) { if (resData.value.status === 1) {
clearInterval(timer) clearInterval(timer);
closeToast() closeToast();
} }
} }
} catch (error) { } catch (error) {
clearInterval(timer) clearInterval(timer);
closeToast() closeToast();
} }
} };
// //
await queryOrder() await queryOrder();
// //
timer = setInterval(async () => { timer = setInterval(async () => {
await queryOrder() await queryOrder();
}, 1000) }, 1000);
// //
onUnmounted(() => { onUnmounted(() => {
if (timer) { if (timer) {
clearInterval(timer) clearInterval(timer);
closeToast() closeToast();
} }
}) });
const statusLabel = { const statusLabel = {
1: t('payment.text2'), 1: t("payment.text2"),
2: t('payment.text3'), 2: t("payment.text3"),
3: t('payment.text4'), 3: t("payment.text4"),
4: t('payment.text5'), 4: t("payment.text5"),
} };
const goHome = () => { const goHome = () => {
router.push('/') router.push("/");
} };
</script> </script>
<template> <template>
<div <div
class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover grow-1 flex flex-col items-center px-30px"> class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover grow-1 flex flex-col items-center px-30px"
>
<div class="flex flex-col items-center mt-150px"> <div class="flex flex-col items-center mt-150px">
<img class="w-119px h-120px mb-36px" src="@/static/images/5554@2x1.png" alt=""> <img
<div class="text-#000 text-16px mb-25px">{{ statusLabel[resData.status] }}!</div> class="w-119px h-120px mb-36px"
<div class="text-#999 text-16px">{{ resData.currency }}{{ resData.money }}</div> src="@/static/images/5554@2x1.png"
alt=""
/>
<div class="text-#000 text-16px mb-25px">
{{ statusLabel[resData.status] }}!
</div>
<div class="text-#999 text-16px">
{{ resData.currency }}{{ resData.money }}
</div>
</div> </div>
<div class="w-full mt-auto mb-40px"> <div class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="goHome"> <van-button type="primary" block @click="goHome">
{{ t('payment.result.backToHome') }} {{ t("payment.result.backToHome") }}
</van-button> </van-button>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>