feat(collect-code): 实现线下扫码支付功能

- 新增 offlineQrcode 和 createOrder 接口
- 实现个人资讯页面的数据获取和处理
- 添加支付页面,支持全款和部分款项支付
- 优化签名协议页面,使用新接口获取数据
- 新增支付结果页面,展示支付状态和金额
This commit is contained in:
xingyy 2025-02-20 11:35:23 +08:00
parent 6836990825
commit 43b1afb8f0
9 changed files with 189 additions and 49 deletions

View File

@ -45,3 +45,19 @@ export async function sessionUserNoCreate(data) {
data
})
}
export async function offlineQrcode(data) {
return await request( {
url:'/api/v1/offlineQrcode/info',
method: 'POST',
data
})
}
export async function createOrder(data) {
return await request( {
url:'/api/v1/offlineQrcode/createOrder',
method: 'POST',
data
})
}

View File

@ -44,13 +44,16 @@ const displayText = computed(() => {
const selected = props.columns.find(x => x.value === props.modelValue)
return selected?.text || ''
})
const openPopup=()=>{
show.value=true
}
</script>
<template>
<div>
<van-field
:model-value="displayText"
@click="show = true"
@click="openPopup"
readonly
:disabled="disabled"
:required="required"
@ -64,14 +67,14 @@ const displayText = computed(() => {
v-model:show="show"
destroy-on-close
position="bottom"
teleport="#__nuxt"
teleport="body"
safe-area-inset-bottom
>
<van-picker
:columns="columns"
@confirm="onConfirm"
@cancel="show = false"
:default-index="columns.findIndex(x => x.value === value)"
:default-index="columns.findIndex(x => x.value === modelValue)"
title="请选择"
confirm-button-text="确定"
cancel-button-text="取消"

View File

@ -4,7 +4,7 @@ import XImage from "@/components/x-image/index.vue";
import {useRuntimeConfig} from "#app";
import QRCode from 'qrcode'
import { showImagePreview } from 'vant';
import {offlineQrcodeDelete} from "~/api-collect-code/goods/index.js";
import {offlineQrcode, offlineQrcodeDelete} from "~/api-collect-code/goods/index.js";
const statusLabel=[
{label:'已付款',value:2,color:'#18A058'}, {label:'未付款',value:1,color:'#CF3050'}, {label:'已部分付款',value:4,color:'#F09F1F'}
@ -22,8 +22,9 @@ const itemLabel=(data)=>{
}
const config = useRuntimeConfig()
const getQRBase64 = async () => {
console.log(`http://192.168.88.25:3000/collectCode/signature/personal-Info?number=2&auctionArtworkUuid=${openRo.value.auctionArtworkUuid}&qrUid=${props.data.qrUid}&price=${props.data.price}&currency=${props.data.currency}`)
try {
return await QRCode.toDataURL(`${config.public.NUXT_PUBLIC_API_BASE}/collectCode/payment`, {
return await QRCode.toDataURL(`http://192.168.88.25:3000/collectCode/signature/personal-Info?number=2&auctionArtworkUuid=${openRo.value.auctionArtworkUuid}&qrUid=${props.data.qrUid}&price=${props.data.price}&currency=${props.data.currency}`, {
width: 200,
margin: 4,
errorCorrectionLevel: 'H'
@ -33,7 +34,15 @@ const getQRBase64 = async () => {
return null
}
}
const openQrCode=async ()=>{
const openRo=ref({})
const openQrCode=async (data)=>{
const res=await offlineQrcode({
qrUid:data.qrUid
})
if (res.status===0){
openRo.value=res.data
}
console.log(res,'res')
const base64=await getQRBase64()
showImagePreview([base64])
}
@ -57,7 +66,7 @@ const openQrCode=async ()=>{
</div>
<div class="flex flex-col justify-end ml-auto ">
<div class="flex w-55px h-26px bg-#2B53AC rounded-4px justify-center items-center">
<div @click="openQrCode" class="text-12px text-#fff line-height-none mt-0.5px mr-5px">查看</div>
<div @click="openQrCode(data)" class="text-12px text-#fff line-height-none mt-0.5px mr-5px">查看</div>
<div >
<img class="w-12px h-12px" src="@/static/images/icon-design-42@3x.png" alt="">
</div>

View File

@ -1,41 +1,87 @@
<script setup>
const payStatus=ref(0)
const changePayStatus=()=>{
payStatus.value=payStatus.value===0?1:0
import {liveStore} from "~/stores/live/index.js";
import {createBuyOrder} from "~/api/goods/index.js";
import {goodStore} from "~/stores/goods/index.js";
import {showLoadingToast, closeToast} from 'vant';
import {authStore} from "~/stores/auth/index.js";
import {message} from "~/components/x-message/useMessage.js";
import {createOrder} from "~/api-collect-code/goods/index.js";
import {codeAuthStore} from "~/stores-collect-code/auth/index.js";
const {checkoutSessionUrl,qrUid,qrData} = codeAuthStore()
const payStatus = ref(0)
definePageMeta({
title: '线下支付'
})
const changePayStatus = () => {
payStatus.value = payStatus.value === 0 ? 1 : 0
}
const validateInput = (e) => {
const amount = ref('')
const confirmPay = async () => {
if (payStatus.value === 1 && !amount.value) {
message.warning('请输入金额')
return
}
if (Number(qrData.value.price) < Number(amount.value)) {
message.warning('不得高于全部金额')
return
}
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
const res = await createOrder({
price: payStatus.value === 0 ? qrData.value.price : amount.value,
currency: qrData.value.currency,
qrUid:qrUid.value,
testReturnHost: 'http://192.168.88.25:3000',
testReturnEndPoint: '/collectCode/payment/result'
})
if (res.status === 0) {
window.location.href = res.data.checkoutSessionUrl
}
}
const handleInput = (e) => {
//
const value = e.target.value
const char = String.fromCharCode(e.charCode)
if (!/[\d.]/.test(char)) {
e.preventDefault()
return
//
let newValue = value.replace(/[^\d.]/g, '')
//
newValue = newValue.replace(/\.{2,}/g, '.')
//
newValue = newValue.replace(/^(\d*\.\d*)\./, '$1')
//
if (newValue.indexOf('.') > 0) {
newValue = newValue.slice(0, newValue.indexOf('.') + 3)
}
// 0
newValue = newValue.replace(/^0+(\d)/, '$1')
if (char === '.' && (value.includes('.') || !value)) {
e.preventDefault()
return
}
if (value.includes('.') && value.split('.')[1]?.length >= 2) {
e.preventDefault()
return
}
amount.value = newValue
}
</script>
<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">
<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">
<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?'支付全部':'支付部分'}}</div>
<div class="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">RMB 5000</div>
<div class="mb-12px">
<input class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" placeholder="最多RMB5,000" @keydown="validateInput">
<div class="text-#1A1A1A text-16px mb-25px font-bold">{{ payStatus === 0 ? '支付全部' : '支付部分' }}</div>
<div class="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">{{ qrData.currency }}
{{ qrData?.price }}
</div>
<div class="mb-12px" v-else>
<input v-model="amount" class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text"
:placeholder="`最多${qrData.currency}${qrData?.price}`" @input="handleInput">
</div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{ payStatus === 1 ? '支付全部' : '支付部分' }}</div>
<div class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="confirmPay">
确认支付
</van-button>
</div>
</div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{payStatus===1?'支付全部':'支付部分'}}</div>
</div>
</template>
<style scoped>

View File

@ -0,0 +1,44 @@
<script setup>
import {orderQuery} from "~/api/goods/index.js";
definePageMeta({
i18n: 'payment.text1',
})
const router = useRouter()
const {t}=useI18n();
const route = useRoute();
const resData=ref({})
const res=await orderQuery({
orderNo:route.query.orderNo
})
if (res.status===0){
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('/')
}
</script>
<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="flex flex-col items-center mt-150px">
<img class="w-119px h-120px mb-36px" 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 class="w-full mt-auto mb-40px">
<van-button type="primary" block @click="goHome">
回到首页
</van-button>
</div>-->
</div>
</template>
<style scoped>
</style>

View File

@ -4,9 +4,9 @@ import XVanSelect from '@/components/x-van-select/index.vue'
import XVanDate from '@/components/x-van-date/index.vue'
import {codeAuthStore} from "@/stores-collect-code/auth/index.js";
import {message} from "@/components/x-message/useMessage.js";
import {fddInfo} from "~/api-collect-code/goods/index.js";
import {fddInfo, offlineQrcode} from "~/api-collect-code/goods/index.js";
import {signOffline} from "~/api/goods/index.js";
const {formData,number,auctionArtworkUuid}=codeAuthStore()
const {formData,number,auctionArtworkUuid,qrUid,qrData}=codeAuthStore()
definePageMeta({
layout: 'default',
i18n: 'menu.profile',
@ -42,15 +42,24 @@ function isFormComplete(obj) {
return true;
}
const initData=()=>{
const getData=async ()=>{
const res=await offlineQrcode({
qrUid:qrUid.value
})
if (res.status===0){
qrData.value=res.data
}
}
const initData= async ()=>{
if (route.query.number){
number.value=Number(route.query.number)
}
if (route.query.auctionArtworkUuid){
auctionArtworkUuid.value=route.query.auctionArtworkUuid
if (route.query.qrUid){
qrUid.value=route.query.qrUid
}
if (route.query.lotNo){
auctionArtworkUuid.value=route.query.lotNo
await getData()
if (qrData.value.payStatus===4){
router.replace('/collectCode/payment')
}
if (route.query.zone){
formData.value.countryCode=route.query.zone
@ -115,13 +124,16 @@ initData()
</template>
</van-field>
<van-field label="姓名" v-model="formData.userName" class="mb-10px" placeholder="请输入姓名"/>
<x-van-select v-if="number===1" v-model="formData.gender" label="性别" :columns="columns"/>
<x-van-date v-if="number===1" label="出生日期" v-model="formData.birthday" />
<van-field v-if="number===1" label="家庭住址" v-model="formData.address" class="mb-10px" placeholder="请输入家庭住址"/>
<van-field v-if="number===1" label="所属银行" v-model="formData.bankName" class="mb-10px" placeholder="请输入所属银行"/>
<van-field v-if="number===1" label="银行卡号码" v-model="formData.bankNo" class="mb-10px" placeholder="请输入银行卡号码"/>
<x-van-select v-if="number===1" v-model="formData.cardType" label="证件类型" :columns="columns1"/>
<van-field v-if="number===1" label="证件号" v-model="formData.cardId" class="mb-10px" placeholder="请输入证件号"/>
<template v-if="number===1">
<x-van-select v-model="formData.gender" label="性别" :columns="columns"/>
<x-van-date label="出生日期" v-model="formData.birthday" />
<van-field label="家庭住址" v-model="formData.address" class="mb-10px" placeholder="请输入家庭住址"/>
<van-field label="所属银行" v-model="formData.bankName" class="mb-10px" placeholder="请输入所属银行"/>
<van-field label="银行卡号码" v-model="formData.bankNo" class="mb-10px" placeholder="请输入银行卡号码"/>
<x-van-select v-model="formData.cardType" label="证件类型" :columns="columns1"/>
<van-field label="证件号" v-model="formData.cardId" class="mb-10px" placeholder="请输入证件号"/>
</template>
</div>
<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">下一步</van-button>

View File

@ -7,13 +7,13 @@ definePageMeta({
title:'签署'
})
const activeNames = ref(['1']);
const {formData,number,lotNo,price}=codeAuthStore()
const {formData,number,auctionArtworkUuid,qrData}=codeAuthStore()
const confirm=async ()=>{
if (formData.value.countryCode==='86'&&formData.value.cardType===1){
const res=await signOffline({
userInfo:formData.value,
bidNum:lotNo.value,
bidPrice:price.value,
auctionArtworkUuid:qrData.value.auctionArtworkUuid,
signOrder:Number(number.value),
})
if (res.status===0){

View File

@ -20,7 +20,16 @@ export const codeAuthStore = createGlobalState(() => {
const price=useLocalStorage('price',undefined)
const auctionArtworkUuid=useLocalStorage('auctionArtworkUuid',undefined)
const number=useLocalStorage('number',undefined)
const qrUid=useLocalStorage('qrUid',undefined)
const cpayment=useLocalStorage('cpayment',{
price:'',
currency:''
})
const qrData=useLocalStorage('qrData',{})
return{
qrData,
qrUid,
cpayment,
lotNo,
price,
auctionArtworkUuid,

View File

@ -10,6 +10,7 @@ export const authStore = createGlobalState(() => {
leftCurrency:'',
buyUid:''
})
return{
payment,
checkoutSessionUrl,