feat(collect-code): 实现线下扫码支付功能
- 新增 offlineQrcode 和 createOrder 接口 - 实现个人资讯页面的数据获取和处理 - 添加支付页面,支持全款和部分款项支付 - 优化签名协议页面,使用新接口获取数据 - 新增支付结果页面,展示支付状态和金额
This commit is contained in:
parent
6836990825
commit
43b1afb8f0
@ -44,4 +44,20 @@ export async function sessionUserNoCreate(data) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
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
|
||||||
|
})
|
||||||
}
|
}
|
@ -44,13 +44,16 @@ const displayText = computed(() => {
|
|||||||
const selected = props.columns.find(x => x.value === props.modelValue)
|
const selected = props.columns.find(x => x.value === props.modelValue)
|
||||||
return selected?.text || ''
|
return selected?.text || ''
|
||||||
})
|
})
|
||||||
|
const openPopup=()=>{
|
||||||
|
show.value=true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<van-field
|
<van-field
|
||||||
:model-value="displayText"
|
:model-value="displayText"
|
||||||
@click="show = true"
|
@click="openPopup"
|
||||||
readonly
|
readonly
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:required="required"
|
:required="required"
|
||||||
@ -64,14 +67,14 @@ const displayText = computed(() => {
|
|||||||
v-model:show="show"
|
v-model:show="show"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
position="bottom"
|
position="bottom"
|
||||||
teleport="#__nuxt"
|
teleport="body"
|
||||||
safe-area-inset-bottom
|
safe-area-inset-bottom
|
||||||
>
|
>
|
||||||
<van-picker
|
<van-picker
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@confirm="onConfirm"
|
@confirm="onConfirm"
|
||||||
@cancel="show = false"
|
@cancel="show = false"
|
||||||
:default-index="columns.findIndex(x => x.value === value)"
|
:default-index="columns.findIndex(x => x.value === modelValue)"
|
||||||
title="请选择"
|
title="请选择"
|
||||||
confirm-button-text="确定"
|
confirm-button-text="确定"
|
||||||
cancel-button-text="取消"
|
cancel-button-text="取消"
|
||||||
|
@ -4,7 +4,7 @@ import XImage from "@/components/x-image/index.vue";
|
|||||||
import {useRuntimeConfig} from "#app";
|
import {useRuntimeConfig} from "#app";
|
||||||
import QRCode from 'qrcode'
|
import QRCode from 'qrcode'
|
||||||
import { showImagePreview } from 'vant';
|
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=[
|
const statusLabel=[
|
||||||
{label:'已付款',value:2,color:'#18A058'}, {label:'未付款',value:1,color:'#CF3050'}, {label:'已部分付款',value:4,color:'#F09F1F'}
|
{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 config = useRuntimeConfig()
|
||||||
const getQRBase64 = async () => {
|
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}¤cy=${props.data.currency}`)
|
||||||
try {
|
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}¤cy=${props.data.currency}`, {
|
||||||
width: 200,
|
width: 200,
|
||||||
margin: 4,
|
margin: 4,
|
||||||
errorCorrectionLevel: 'H'
|
errorCorrectionLevel: 'H'
|
||||||
@ -33,7 +34,15 @@ const getQRBase64 = async () => {
|
|||||||
return null
|
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()
|
const base64=await getQRBase64()
|
||||||
showImagePreview([base64])
|
showImagePreview([base64])
|
||||||
}
|
}
|
||||||
@ -57,7 +66,7 @@ const openQrCode=async ()=>{
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-end ml-auto ">
|
<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 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 >
|
<div >
|
||||||
<img class="w-12px h-12px" src="@/static/images/icon-design-42@3x.png" alt="">
|
<img class="w-12px h-12px" src="@/static/images/icon-design-42@3x.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,41 +1,87 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const payStatus=ref(0)
|
import {liveStore} from "~/stores/live/index.js";
|
||||||
const changePayStatus=()=>{
|
import {createBuyOrder} from "~/api/goods/index.js";
|
||||||
payStatus.value=payStatus.value===0?1:0
|
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 value = e.target.value
|
||||||
const char = String.fromCharCode(e.charCode)
|
// 清除非数字和小数点
|
||||||
|
let newValue = value.replace(/[^\d.]/g, '')
|
||||||
if (!/[\d.]/.test(char)) {
|
// 确保只有一个小数点
|
||||||
e.preventDefault()
|
newValue = newValue.replace(/\.{2,}/g, '.')
|
||||||
return
|
// 只保留第一个小数点
|
||||||
|
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)) {
|
amount.value = newValue
|
||||||
e.preventDefault()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.includes('.') && value.split('.')[1]?.length >= 2) {
|
|
||||||
e.preventDefault()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</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">
|
<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>
|
||||||
<div class="text-#1A1A1A text-16px mb-25px font-bold">{{payStatus===0?'支付全部':'支付部分'}}</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="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">{{ qrData.currency }}
|
||||||
<div class="mb-12px">
|
{{ qrData?.price }}
|
||||||
<input class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" placeholder="最多RMB5,000" @keydown="validateInput">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{payStatus===1?'支付全部':'支付部分'}}</div>
|
<div class="mb-12px" v-else>
|
||||||
</div>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
44
app/pages/collectCode/payment/result/index.vue
Normal file
44
app/pages/collectCode/payment/result/index.vue
Normal 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>
|
@ -4,9 +4,9 @@ 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} 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}=codeAuthStore()
|
const {formData,number,auctionArtworkUuid,qrUid,qrData}=codeAuthStore()
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
i18n: 'menu.profile',
|
i18n: 'menu.profile',
|
||||||
@ -42,15 +42,24 @@ function isFormComplete(obj) {
|
|||||||
return true;
|
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){
|
if (route.query.number){
|
||||||
number.value=Number(route.query.number)
|
number.value=Number(route.query.number)
|
||||||
}
|
}
|
||||||
if (route.query.auctionArtworkUuid){
|
if (route.query.qrUid){
|
||||||
auctionArtworkUuid.value=route.query.auctionArtworkUuid
|
qrUid.value=route.query.qrUid
|
||||||
}
|
}
|
||||||
if (route.query.lotNo){
|
await getData()
|
||||||
auctionArtworkUuid.value=route.query.lotNo
|
if (qrData.value.payStatus===4){
|
||||||
|
router.replace('/collectCode/payment')
|
||||||
}
|
}
|
||||||
if (route.query.zone){
|
if (route.query.zone){
|
||||||
formData.value.countryCode=route.query.zone
|
formData.value.countryCode=route.query.zone
|
||||||
@ -115,13 +124,16 @@ initData()
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<van-field label="姓名" v-model="formData.userName" class="mb-10px" placeholder="请输入姓名"/>
|
<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"/>
|
<template v-if="number===1">
|
||||||
<x-van-date v-if="number===1" label="出生日期" v-model="formData.birthday" />
|
<x-van-select v-model="formData.gender" label="性别" :columns="columns"/>
|
||||||
<van-field v-if="number===1" label="家庭住址" v-model="formData.address" class="mb-10px" placeholder="请输入家庭住址"/>
|
<x-van-date label="出生日期" v-model="formData.birthday" />
|
||||||
<van-field v-if="number===1" label="所属银行" v-model="formData.bankName" class="mb-10px" placeholder="请输入所属银行"/>
|
<van-field label="家庭住址" v-model="formData.address" class="mb-10px" placeholder="请输入家庭住址"/>
|
||||||
<van-field v-if="number===1" label="银行卡号码" v-model="formData.bankNo" class="mb-10px" placeholder="请输入银行卡号码"/>
|
<van-field label="所属银行" v-model="formData.bankName" class="mb-10px" placeholder="请输入所属银行"/>
|
||||||
<x-van-select v-if="number===1" v-model="formData.cardType" label="证件类型" :columns="columns1"/>
|
<van-field label="银行卡号码" v-model="formData.bankNo" class="mb-10px" placeholder="请输入银行卡号码"/>
|
||||||
<van-field v-if="number===1" label="证件号" v-model="formData.cardId" 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>
|
||||||
<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">下一步</van-button>
|
<van-button color="#2B53AC" class="w-213px van-btn-h-38px" @click="nextClick">下一步</van-button>
|
||||||
|
@ -7,13 +7,13 @@ definePageMeta({
|
|||||||
title:'签署'
|
title:'签署'
|
||||||
})
|
})
|
||||||
const activeNames = ref(['1']);
|
const activeNames = ref(['1']);
|
||||||
const {formData,number,lotNo,price}=codeAuthStore()
|
const {formData,number,auctionArtworkUuid,qrData}=codeAuthStore()
|
||||||
|
|
||||||
const confirm=async ()=>{
|
const confirm=async ()=>{
|
||||||
if (formData.value.countryCode==='86'&&formData.value.cardType===1){
|
if (formData.value.countryCode==='86'&&formData.value.cardType===1){
|
||||||
const res=await signOffline({
|
const res=await signOffline({
|
||||||
userInfo:formData.value,
|
userInfo:formData.value,
|
||||||
bidNum:lotNo.value,
|
auctionArtworkUuid:qrData.value.auctionArtworkUuid,
|
||||||
bidPrice:price.value,
|
|
||||||
signOrder:Number(number.value),
|
signOrder:Number(number.value),
|
||||||
})
|
})
|
||||||
if (res.status===0){
|
if (res.status===0){
|
||||||
|
@ -20,7 +20,16 @@ export const codeAuthStore = createGlobalState(() => {
|
|||||||
const price=useLocalStorage('price',undefined)
|
const price=useLocalStorage('price',undefined)
|
||||||
const auctionArtworkUuid=useLocalStorage('auctionArtworkUuid',undefined)
|
const auctionArtworkUuid=useLocalStorage('auctionArtworkUuid',undefined)
|
||||||
const number=useLocalStorage('number',undefined)
|
const number=useLocalStorage('number',undefined)
|
||||||
|
const qrUid=useLocalStorage('qrUid',undefined)
|
||||||
|
const cpayment=useLocalStorage('cpayment',{
|
||||||
|
price:'',
|
||||||
|
currency:''
|
||||||
|
})
|
||||||
|
const qrData=useLocalStorage('qrData',{})
|
||||||
return{
|
return{
|
||||||
|
qrData,
|
||||||
|
qrUid,
|
||||||
|
cpayment,
|
||||||
lotNo,
|
lotNo,
|
||||||
price,
|
price,
|
||||||
auctionArtworkUuid,
|
auctionArtworkUuid,
|
||||||
|
@ -10,6 +10,7 @@ export const authStore = createGlobalState(() => {
|
|||||||
leftCurrency:'',
|
leftCurrency:'',
|
||||||
buyUid:''
|
buyUid:''
|
||||||
})
|
})
|
||||||
|
|
||||||
return{
|
return{
|
||||||
payment,
|
payment,
|
||||||
checkoutSessionUrl,
|
checkoutSessionUrl,
|
||||||
|
Loading…
Reference in New Issue
Block a user