Compare commits
No commits in common. "23a127b0d3bd7c951877fea02825176a29ed8931" and "f1c7c2a66d9cb26e5e0863280d0d34a5ee62d6f4" have entirely different histories.
23a127b0d3
...
f1c7c2a66d
@ -1,4 +1,4 @@
|
||||
import { request } from '@/api-collect-code/http.js'
|
||||
import { request } from '@/api/http.js'
|
||||
|
||||
export async function checkPhone(data) {
|
||||
return await request({
|
||||
@ -9,7 +9,7 @@ export async function checkPhone(data) {
|
||||
}
|
||||
export async function userSend(data) {
|
||||
return await request( {
|
||||
url:'/api/v1/m/user/mobile/send',
|
||||
url:'/api/v1/m/user/send',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { request } from '@/api-collect-code/http.js'
|
||||
import { request } from '@/api/http.js'
|
||||
|
||||
export async function offlineQrcodeList(data) {
|
||||
return await request( {
|
||||
@ -56,7 +56,7 @@ export async function offlineQrcode(data) {
|
||||
export async function createOrder(data) {
|
||||
|
||||
return await request( {
|
||||
url:'/api/v1/offlineQrcode/createOrder/V2',
|
||||
url:'/api/v1/offlineQrcode/createOrder',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
|
@ -9,9 +9,9 @@ let http
|
||||
// HTTP 状态码映射 - 使用i18n国际化
|
||||
export function setupHttp() {
|
||||
if (http) return http
|
||||
const {codeToken}= codeAuthStore()
|
||||
const {token}= codeAuthStore()
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.NUXT_PUBLIC_API_BASE
|
||||
const baseURL = config.public.NUXT_PUBLIC_API_COLLECT_CODE
|
||||
const router = useRouter()
|
||||
const i18n = useNuxtApp().$i18n
|
||||
|
||||
@ -43,7 +43,7 @@ export function setupHttp() {
|
||||
// 添加 token
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: codeToken.value,
|
||||
Authorization: token.value,
|
||||
'accept-language': i18n.locale.value
|
||||
}
|
||||
|
||||
@ -64,11 +64,11 @@ export function setupHttp() {
|
||||
if (data.status === 1) {
|
||||
message.error(data.msg || i18n.t('http.error.operationFailed'))
|
||||
}
|
||||
console.log('拦截响应',data)
|
||||
|
||||
// 处理登录失效
|
||||
if (data.status === 401) {
|
||||
message.error(i18n.t('http.error.loginExpired'))
|
||||
codeToken.value = '' // 清除 token
|
||||
token.value = '' // 清除 token
|
||||
router.replace('/collectCode/login')
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ export async function fddCheck(data) {
|
||||
export async function createBuyOrder(data) {
|
||||
|
||||
return await request( {
|
||||
url:'/api/v1/m/auction/createBuyOrder/v2',
|
||||
url:'/api/v1/m/auction/createBuyOrder',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
|
16
app/app.vue
16
app/app.vue
@ -1,20 +1,23 @@
|
||||
<script setup>
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {message} from '@/components/x-message/useMessage.js'
|
||||
import {hideMinWindow1} from "@/components/floatingBubble/floating.js";
|
||||
import AppSkeleton from '@/components/app-skeleton/index.vue'
|
||||
const {t} = useI18n()
|
||||
// message.success('success')
|
||||
useHead({
|
||||
title: t('appSetting.appName'),
|
||||
title: useI18n().t('appSetting.appName'),
|
||||
meta: [
|
||||
{name: 'description', content: t('appSetting.appDescription')},
|
||||
{name: 'keywords', content: t('appSetting.appKeyWords')},
|
||||
{name: 'description', content: useI18n().t('appSetting.appDescription')},
|
||||
{name: 'keywords', content: useI18n().t('appSetting.appKeyWords')},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
// 添加路由中间件来处理过渡方向
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const slideDirection = ref('slide-left')
|
||||
const { locale } = useI18n()
|
||||
// locale.value = 'en-US'
|
||||
// 记录路由历史
|
||||
const routeHistory = ref([])
|
||||
|
||||
@ -37,13 +40,10 @@ if (to.path==='/'){
|
||||
|
||||
// 提供过渡名称给页面组件
|
||||
provide('slideDirection', slideDirection)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<client-only>
|
||||
<!-- 骨架屏组件 -->
|
||||
|
||||
<VanConfigProvider>
|
||||
<NuxtLoadingIndicator
|
||||
color="repeating-linear-gradient(to right,var(--c-primary) 0%,var(--c-primary-active) 100%)"/>
|
||||
|
@ -31,7 +31,7 @@ const subTitle = computed(() => {
|
||||
return route.meta.subTitle ? t(route.meta.subTitle) : ''
|
||||
})
|
||||
const showLeftArrow = computed(() => route.name && routeWhiteList.includes(route.name))
|
||||
|
||||
console.log('route.name',route.name)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,276 +0,0 @@
|
||||
<template>
|
||||
<div class="stripe-container">
|
||||
<form id="payment-form" @submit.prevent="handleSubmit">
|
||||
<div id="payment-element">
|
||||
<!--Stripe.js injects the Payment Element-->
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner hidden" id="spinner"></div>
|
||||
<span id="button-text">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" class="hidden"></div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// The items the customer wants to buy
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }]
|
||||
|
||||
const stripe = ref(null)
|
||||
const elements = ref(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 初始化 Stripe
|
||||
stripe.value = window.Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV")
|
||||
await initialize()
|
||||
} catch (error) {
|
||||
showMessage("Failed to initialize payment system")
|
||||
}
|
||||
})
|
||||
|
||||
// Fetches a payment intent and captures the client secret
|
||||
const initialize = async () => {
|
||||
try {
|
||||
const response = await fetch("/create-payment-intent", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ items }),
|
||||
})
|
||||
const { clientSecret } = await response.json()
|
||||
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
}
|
||||
|
||||
// 创建 elements 实例
|
||||
elements.value = stripe.value.elements({
|
||||
appearance,
|
||||
clientSecret,
|
||||
})
|
||||
|
||||
// 创建并挂载 Payment Element
|
||||
const paymentElement = elements.value.create("payment", {
|
||||
layout: "accordion",
|
||||
})
|
||||
|
||||
// 确保挂载到正确的 DOM 元素
|
||||
const mountElement = document.getElementById("payment-element")
|
||||
if (mountElement) {
|
||||
paymentElement.mount(mountElement)
|
||||
} else {
|
||||
throw new Error("Payment element mount point not found")
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage("Failed to load payment form")
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!stripe.value || !elements.value) {
|
||||
showMessage("Payment system not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const { error } = await stripe.value.confirmPayment({
|
||||
elements: elements.value,
|
||||
confirmParams: {
|
||||
return_url: window.location.origin + "/complete.html",
|
||||
},
|
||||
})
|
||||
|
||||
if (error) {
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message)
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.")
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
showMessage("Payment processing failed")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const showMessage = (messageText) => {
|
||||
const messageContainer = document.querySelector("#payment-message")
|
||||
|
||||
messageContainer.classList.remove("hidden")
|
||||
messageContainer.textContent = messageText
|
||||
|
||||
setTimeout(() => {
|
||||
messageContainer.classList.add("hidden")
|
||||
messageContainer.textContent = ""
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
// Show a spinner on payment submission
|
||||
const setLoading = (isLoading) => {
|
||||
if (isLoading) {
|
||||
// Disable the button and show a spinner
|
||||
document.querySelector("#submit").disabled = true
|
||||
document.querySelector("#spinner").classList.remove("hidden")
|
||||
document.querySelector("#button-text").classList.add("hidden")
|
||||
} else {
|
||||
document.querySelector("#submit").disabled = false
|
||||
document.querySelector("#spinner").classList.add("hidden")
|
||||
document.querySelector("#button-text").classList.remove("hidden")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stripe-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form {
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,194 +0,0 @@
|
||||
<script setup>
|
||||
// 骨架屏组件,用于应用初始加载时显示
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div class="app-skeleton">
|
||||
<!-- 顶部导航栏骨架 -->
|
||||
<div class="skeleton-header">
|
||||
<div class="skeleton-avatar"></div>
|
||||
<div class="skeleton-title"></div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域骨架 -->
|
||||
<div class="skeleton-content">
|
||||
<!-- 轮播图骨架 -->
|
||||
<div class="skeleton-banner"></div>
|
||||
|
||||
<!-- 菜单项骨架 -->
|
||||
<div class="skeleton-menu">
|
||||
<div class="skeleton-menu-item" v-for="i in 4" :key="i"></div>
|
||||
</div>
|
||||
|
||||
<!-- 列表项骨架 -->
|
||||
<div class="skeleton-list">
|
||||
<div class="skeleton-list-item" v-for="i in 3" :key="i">
|
||||
<div class="skeleton-list-image"></div>
|
||||
<div class="skeleton-list-content">
|
||||
<div class="skeleton-list-title"></div>
|
||||
<div class="skeleton-list-desc"></div>
|
||||
<div class="skeleton-list-price"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部导航栏骨架 -->
|
||||
<div class="skeleton-tabbar">
|
||||
<div class="skeleton-tab-item" v-for="i in 4" :key="i"></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.app-skeleton {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 骨架屏动画 */
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-header, .skeleton-avatar, .skeleton-title, .skeleton-banner,
|
||||
.skeleton-menu-item, .skeleton-list-image, .skeleton-list-title,
|
||||
.skeleton-list-desc, .skeleton-list-price, .skeleton-tab-item {
|
||||
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
||||
background-size: 400% 100%;
|
||||
animation: skeleton-loading 1.4s ease infinite;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.skeleton-header {
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
width: 120px;
|
||||
height: 16px;
|
||||
margin-left: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.skeleton-content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.skeleton-banner {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 菜单项 */
|
||||
.skeleton-menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.skeleton-menu-item {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 列表项 */
|
||||
.skeleton-list-item {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.skeleton-list-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeleton-list-content {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.skeleton-list-title {
|
||||
height: 16px;
|
||||
width: 80%;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.skeleton-list-desc {
|
||||
height: 12px;
|
||||
width: 60%;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.skeleton-list-price {
|
||||
height: 16px;
|
||||
width: 40%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 底部导航栏 */
|
||||
.skeleton-tabbar {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.skeleton-tab-item {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
@ -4,7 +4,6 @@
|
||||
*/
|
||||
import { createApp } from 'vue'
|
||||
import MinWindow from '@/components/floatingBubble/index.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
// 全局单例状态管理
|
||||
let minWindowInstance = null // 组件实例引用
|
||||
@ -18,6 +17,7 @@ let container = null // DOM容器元素
|
||||
*/
|
||||
export const showMinWindow1 = (props = {}) => {
|
||||
// 服务端渲染时直接返回
|
||||
console.log('!process.client',!process.client)
|
||||
if (!process.client) return null
|
||||
|
||||
// 如果实例已存在,避免重复创建
|
||||
@ -39,26 +39,12 @@ export const showMinWindow1 = (props = {}) => {
|
||||
|
||||
// 创建Vue应用实例
|
||||
const app = createApp(MinWindow, props)
|
||||
|
||||
// 获取当前 Nuxt 应用的 i18n 配置
|
||||
const nuxtApp = window?.__nuxt
|
||||
const i18nConfig = nuxtApp?.$i18n?.options || {
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {}
|
||||
}
|
||||
|
||||
// 为独立组件创建 i18n 实例
|
||||
const i18n = createI18n(i18nConfig)
|
||||
|
||||
// 安装 i18n
|
||||
app.use(i18n)
|
||||
|
||||
minWindowApp = app
|
||||
minWindowInstance = app.mount(container)
|
||||
|
||||
return minWindowInstance
|
||||
} catch (error) {
|
||||
console.error('创建浮动气泡时发生错误:', error)
|
||||
// 发生错误时确保清理资源
|
||||
hideMinWindow1()
|
||||
return null
|
||||
@ -70,7 +56,7 @@ export const showMinWindow1 = (props = {}) => {
|
||||
* 清理所有相关资源和DOM元素
|
||||
*/
|
||||
export const hideMinWindow1 = () => {
|
||||
|
||||
console.log('!minWindowApp && !container', !minWindowApp && !container);
|
||||
|
||||
if (!minWindowApp && !container) return
|
||||
|
||||
@ -91,7 +77,8 @@ export const hideMinWindow1 = () => {
|
||||
document.body.removeChild(existingContainer)
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
console.error('清理浮动气泡时发生错误:', error)
|
||||
} finally {
|
||||
// 重置所有状态
|
||||
minWindowApp = null
|
||||
minWindowInstance = null
|
||||
|
@ -6,12 +6,9 @@
|
||||
import { watch, onUnmounted } from 'vue'
|
||||
import { hideMinWindow1 } from './floating'
|
||||
|
||||
// 从 nuxt/app 导入 useNuxtApp
|
||||
const { $i18n } = useNuxtApp()
|
||||
|
||||
// 替换原来的 useI18n
|
||||
const t = (key) => $i18n.t(key)
|
||||
const { t } = useI18n()
|
||||
|
||||
// 组件属性定义
|
||||
const props = defineProps({
|
||||
/** 点击气泡时的回调函数 */
|
||||
onClick: {
|
||||
|
@ -28,6 +28,7 @@ export const showMinWindow = (snapshot, props = {}) => {
|
||||
})
|
||||
|
||||
app.config.errorHandler = (err) => {
|
||||
console.error('MinWindow Error:', err)
|
||||
hideMinWindow()
|
||||
}
|
||||
|
||||
|
@ -1,331 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV")
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }]
|
||||
const elements = ref(null)
|
||||
const paymentMessage = ref('')
|
||||
const isLoading = ref(false)
|
||||
const showSpinner = ref(false)
|
||||
|
||||
async function initialize() {
|
||||
const clientSecret = 'pi_3QxII1AB1Vm8VfJq1OyR3bkz_secret_d8fgL53X6T3MQpYfi2lRH3V1F'
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
}
|
||||
elements.value = stripe.elements({ appearance, clientSecret })
|
||||
|
||||
const paymentElementOptions = {
|
||||
layout: "accordion",
|
||||
}
|
||||
|
||||
const paymentElement = elements.value.create("payment", paymentElementOptions)
|
||||
paymentElement.mount("#payment-element")
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements: elements.value,
|
||||
confirmParams: {
|
||||
return_url: "http://localhost:4242/complete",
|
||||
},
|
||||
})
|
||||
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message)
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.")
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
function showMessage(messageText) {
|
||||
paymentMessage.value = messageText
|
||||
setTimeout(() => {
|
||||
paymentMessage.value = ''
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
isLoading.value = loading
|
||||
showSpinner.value = loading
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="payment-form" @submit="handleSubmit">
|
||||
<div id="payment-element">
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div>
|
||||
<span id="button-text" :class="{ hidden: showSpinner }">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" :class="{ hidden: !paymentMessage }">{{ paymentMessage }}</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form {
|
||||
width:100vw;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
/* 其他样式保持不变... */
|
||||
</style>
|
@ -1,115 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV")
|
||||
const statusIcon = ref('')
|
||||
const statusText = ref('')
|
||||
const intentId = ref('')
|
||||
const intentStatus = ref('')
|
||||
const viewDetailsUrl = ref('')
|
||||
const iconColor = ref('')
|
||||
|
||||
const SuccessIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const ErrorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const InfoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
|
||||
<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
function setPaymentDetails(intent) {
|
||||
let status = "Something went wrong, please try again."
|
||||
let color = "#DF1B41"
|
||||
let icon = ErrorIcon
|
||||
|
||||
if (!intent) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
switch (intent.status) {
|
||||
case "succeeded":
|
||||
status = "Payment succeeded"
|
||||
color = "#30B130"
|
||||
icon = SuccessIcon
|
||||
break
|
||||
case "processing":
|
||||
status = "Your payment is processing."
|
||||
color = "#6D6E78"
|
||||
icon = InfoIcon
|
||||
break
|
||||
case "requires_payment_method":
|
||||
status = "Your payment was not successful, please try again."
|
||||
break
|
||||
}
|
||||
|
||||
iconColor.value = color
|
||||
statusIcon.value = icon
|
||||
statusText.value = status
|
||||
intentId.value = intent.id
|
||||
intentStatus.value = intent.status
|
||||
viewDetailsUrl.value = `https://dashboard.stripe.com/payments/${intent.id}`
|
||||
}
|
||||
|
||||
function setErrorState() {
|
||||
iconColor.value = "#DF1B41"
|
||||
statusIcon.value = ErrorIcon
|
||||
statusText.value = "Something went wrong, please try again."
|
||||
}
|
||||
|
||||
async function checkStatus() {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const clientSecret = urlParams.get("payment_intent_client_secret")
|
||||
|
||||
if (!clientSecret) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret)
|
||||
setPaymentDetails(paymentIntent)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="payment-status">
|
||||
<div id="status-icon" :style="{ backgroundColor: iconColor }" v-html="statusIcon"></div>
|
||||
<h2 id="status-text">{{ statusText }}</h2>
|
||||
<div id="details-table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="TableLabel">id</td>
|
||||
<td id="intent-id" class="TableContent">{{ intentId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="TableLabel">status</td>
|
||||
<td id="intent-status" class="TableContent">{{ intentStatus }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a :href="viewDetailsUrl" id="view-details" rel="noopener noreferrer" target="_blank">
|
||||
View details
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/>
|
||||
<path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/>
|
||||
</svg>
|
||||
</a>
|
||||
<NuxtLink id="retry-button" to="/checkout">Test another</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 将原来 checkout.css 中的样式复制到这里 */
|
||||
</style>
|
@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const {t} =useI18n()
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Number, String]
|
||||
@ -23,7 +21,7 @@ const props = defineProps({
|
||||
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => useI18n().t('components.form.pleaseSelect')
|
||||
default: '请选择'
|
||||
},
|
||||
|
||||
disabled: {
|
||||
@ -77,9 +75,9 @@ const openPopup=()=>{
|
||||
@confirm="onConfirm"
|
||||
@cancel="show = false"
|
||||
:default-index="columns.findIndex(x => x.value === modelValue)"
|
||||
:title="t('components.form.pleaseSelect')"
|
||||
:confirm-button-text="t('components.dialog.confirm')"
|
||||
:cancel-button-text="t('components.dialog.cancel')"
|
||||
title="请选择"
|
||||
confirm-button-text="确定"
|
||||
cancel-button-text="取消"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
@ -15,7 +15,8 @@ const initData = async () => {
|
||||
const res = await userArtwork({uuid})
|
||||
if (res.status === 0) {
|
||||
detail.value = res.data
|
||||
}
|
||||
console.log('detail',detail.value)
|
||||
}
|
||||
}
|
||||
const router = useRouter();
|
||||
const position = ref({x: window?.innerWidth - 120 || 0, y: 240})
|
||||
@ -58,7 +59,8 @@ const goPay=()=>{
|
||||
}else if (detail.value.status===4){
|
||||
router.push('/payment')
|
||||
}
|
||||
//router.push('/payment')
|
||||
console.log('detail',detail.value)
|
||||
//router.push('/payment')
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', onDrag)
|
||||
|
@ -1,402 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import {authStore} from "~/stores/auth/index.js";
|
||||
import {orderQuery} from "~/api/goods/index.js";
|
||||
import { WebSocketClient } from '@/utils/websocket'
|
||||
const config = useRuntimeConfig()
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: 'Stripe支付'
|
||||
})
|
||||
|
||||
const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY)
|
||||
const route = useRoute()
|
||||
const baseURL = config.public.NUXT_PUBLIC_API_BASE
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }]
|
||||
const elements = ref(null)
|
||||
const paymentMessage = ref('')
|
||||
const isLoading = ref(false)
|
||||
const showSpinner = ref(false)
|
||||
let pollTimer = null
|
||||
let timeoutTimer = null
|
||||
const router = useRouter()
|
||||
const startPolling = () => {
|
||||
pollTimer = setInterval(async () => {
|
||||
const res = await orderQuery({
|
||||
orderNo: route.query.payUid
|
||||
})
|
||||
if (res.status === 0) {
|
||||
if (res.data.status !== 3) {
|
||||
clearInterval(pollTimer)
|
||||
clearTimeout(timeoutTimer)
|
||||
router.replace({
|
||||
path: route.query.returnUrl,
|
||||
query: {
|
||||
orderNo: route.query.payUid
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
/* timeoutTimer = setTimeout(() => {
|
||||
clearInterval(pollTimer)
|
||||
setLoading(false)
|
||||
}, 180000)*/
|
||||
}
|
||||
let wsClient=null
|
||||
const watchWebSocket = () => {
|
||||
wsClient = new WebSocketClient(
|
||||
config.public.NUXT_PUBLIC_SOCKET_URL
|
||||
)
|
||||
const ws = wsClient.connect('/api/v1/order/ws/v2', {
|
||||
PayUid: route.query.payUid,
|
||||
})
|
||||
ws.onOpen(() => {
|
||||
})
|
||||
ws.onMessage((event) => {
|
||||
router.replace({
|
||||
path: route.query.returnUrl,
|
||||
query: {
|
||||
orderNo: route.query.payUid
|
||||
}
|
||||
})
|
||||
})
|
||||
ws.onClose(() => {
|
||||
})
|
||||
}
|
||||
async function initialize() {
|
||||
const clientSecret = route.query.stripeKey
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
}
|
||||
elements.value = stripe.elements({ appearance, clientSecret })
|
||||
|
||||
const paymentElementOptions = {
|
||||
layout: "accordion",
|
||||
}
|
||||
|
||||
const paymentElement = elements.value.create("payment", paymentElementOptions)
|
||||
paymentElement.mount("#payment-element")
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements: elements.value,
|
||||
confirmParams: {
|
||||
return_url: `${baseURL}${route.query.returnUrl}?orderNo=${route.query.payUid}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (error) {
|
||||
/* clearInterval(pollTimer)
|
||||
clearTimeout(timeoutTimer)*/
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message)
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.")
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(messageText) {
|
||||
paymentMessage.value = messageText
|
||||
setTimeout(() => {
|
||||
paymentMessage.value = ''
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
isLoading.value = loading
|
||||
showSpinner.value = loading
|
||||
}
|
||||
|
||||
|
||||
onUnmounted(()=>{
|
||||
wsClient.disconnect()
|
||||
clearTimeout(timeoutTimer)
|
||||
clearInterval(pollTimer)
|
||||
|
||||
})
|
||||
onMounted(() => {
|
||||
watchWebSocket()
|
||||
initialize()
|
||||
startPolling()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="payment-form" @submit="handleSubmit">
|
||||
<div id="payment-element">
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner" :class="{ hidden: !showSpinner }" id="spinner"></div>
|
||||
<span id="button-text" :class="{ hidden: showSpinner }">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" :class="{ hidden: !paymentMessage }">{{ paymentMessage }}</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form {
|
||||
width:100vw;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
/* 其他样式保持不变... */
|
||||
</style>
|
@ -8,7 +8,7 @@ import {message} from '@/components/x-message/useMessage.js'
|
||||
import FingerprintJS from '@fingerprintjs/fingerprintjs'
|
||||
import {checkPhone, mobileLogin, userSend} from "@/api-collect-code/auth/index.js";
|
||||
|
||||
const {userInfo, codeToken, fingerprint} = codeAuthStore()
|
||||
const {userInfo, token, fingerprint} = codeAuthStore()
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const {locale} = useI18n()
|
||||
@ -33,9 +33,10 @@ const startCountdown = () => {
|
||||
}, 1000);
|
||||
}
|
||||
const countdown = ref(0);
|
||||
const phoneNum = ref('')
|
||||
const code = ref('')
|
||||
const phoneNum = ref('17630920520')
|
||||
const code = ref('123789')
|
||||
const pane = ref(0)
|
||||
const showKeyboard = ref(false);
|
||||
const getFingerprint = async () => {
|
||||
const fp = await FingerprintJS.load()
|
||||
const result = await fp.get()
|
||||
@ -49,29 +50,36 @@ const checkFingerprint = async () => {
|
||||
await router.push('/collectCode/mine')
|
||||
}
|
||||
}
|
||||
const codeInput = ref(null)
|
||||
const isFocused = ref(false)
|
||||
checkFingerprint()
|
||||
const vanSwipeRef = ref(null)
|
||||
const getCode = async () => {
|
||||
loadingRef.value.loading1 = true
|
||||
try {
|
||||
const res = await checkPhone({
|
||||
tel: phoneNum.value,
|
||||
})
|
||||
const res = await checkPhone({
|
||||
tel: phoneNum.value,
|
||||
})
|
||||
loadingRef.value.loading1 = false
|
||||
if (res.status === 0) {
|
||||
const res = await userSend({telNum: phoneNum.value, zone: '+86'})
|
||||
if (res.status === 0) {
|
||||
const sendRes = await userSend({telNum: phoneNum.value, zone: '+86'})
|
||||
startCountdown()
|
||||
pane.value = 1
|
||||
await nextTick()
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
pane.value = 1
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
showKeyboard.value = true
|
||||
}
|
||||
}
|
||||
/* loadingRef.value.loading1 = false
|
||||
if (res.status === 0) {
|
||||
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取验证码失败:', error)
|
||||
} finally {
|
||||
loadingRef.value.loading1 = false
|
||||
}
|
||||
pane.value = 1
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
showKeyboard.value = true
|
||||
startCountdown();*/
|
||||
/* pane.value = 1
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
showKeyboard.value=true
|
||||
startCountdown();*/
|
||||
|
||||
}
|
||||
const changeToPwd = async () => {
|
||||
loginType.value = loginType.value === 0 ? 1 : 0
|
||||
@ -90,7 +98,7 @@ const goLogin = async () => {
|
||||
})
|
||||
if (res.status === 0) {
|
||||
userInfo.value = res.data.accountInfo
|
||||
codeToken.value = res.data.token
|
||||
token.value = res.data.token
|
||||
fingerprint.value = await getFingerprint()
|
||||
|
||||
await router.push('/collectCode/mine');
|
||||
@ -106,124 +114,98 @@ const togglePasswordVisibility = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grow-1 w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-bottom bg-cover px-[31px] pt-[86px]">
|
||||
<div class="h-[100vh] w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-cover px-[31px] pt-[86px]">
|
||||
<div class="w-full flex justify-center mb-[100px] flex-col items-center">
|
||||
<img class="h-[105px] w-[189px]" src="@/static/images/ghfggff.png" alt="">
|
||||
<img class="h-[29px] w-[108px]" src="@/static/images/qrcodetext.png" alt="">
|
||||
</div>
|
||||
<van-swipe ref="vanSwipeRef" :show-indicators="false" :touchable="false" :lazy-render="true" :loop="false">
|
||||
<van-swipe-item>
|
||||
<div v-if="pane === 0">
|
||||
<div>
|
||||
<div class="">
|
||||
<div class="border-b-[1.7px] mt-[8px]">
|
||||
<van-field v-model="phoneNum" clearable :placeholder="$t('collectCode.login.phoneNumberPlaceholder')">
|
||||
<template #label>
|
||||
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
|
||||
{{ $t('collectCode.login.phoneNumber') }}
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
<div class="border-b-[1.7px] mt-[8px]" v-if="loginType === 1">
|
||||
<van-field
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
clearable
|
||||
:placeholder="$t('collectCode.login.passwordPlaceholder')"
|
||||
>
|
||||
<template #label>
|
||||
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
|
||||
{{ $t('collectCode.login.password') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #button>
|
||||
<div class="flex justify-center items-center">
|
||||
<van-icon
|
||||
size="20"
|
||||
:name="showPassword ? 'eye-o' : 'closed-eye'"
|
||||
@click="togglePasswordVisibility"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
<div class="flex justify-end mt-[10px]">
|
||||
<div class="text-[14px] text-[#2B53AC]" @click="changeToPwd">
|
||||
{{ loginType === 0 ? $t('collectCode.login.passwordLogin') : $t('collectCode.login.codeLogin') }}
|
||||
</div>
|
||||
<div v-show="pane === 0">
|
||||
<div class="">
|
||||
<div class="border-b-[1.7px] mt-[8px]">
|
||||
<van-field v-model="phoneNum" clearable :placeholder="$t('collectCode.login.phoneNumberPlaceholder')">
|
||||
<template #label>
|
||||
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
|
||||
{{ $t('collectCode.login.phoneNumber') }}
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
<div class="border-b-[1.7px] mt-[8px]" v-show="loginType === 1">
|
||||
<van-field
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
clearable
|
||||
:placeholder="$t('collectCode.login.passwordPlaceholder')"
|
||||
>
|
||||
<template #label>
|
||||
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
|
||||
{{ $t('collectCode.login.password') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #button>
|
||||
<div class="flex justify-center items-center">
|
||||
<van-icon
|
||||
size="20"
|
||||
:name="showPassword ? 'eye-o' : 'closed-eye'"
|
||||
@click="togglePasswordVisibility"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
<div class="flex justify-end mt-[10px]">
|
||||
<div class="text-[14px] text-[#2B53AC]" @click="changeToPwd">
|
||||
{{ loginType === 0 ? $t('collectCode.login.passwordLogin') : $t('collectCode.login.codeLogin') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[55px]">
|
||||
<div v-if="loginType === 0">
|
||||
<van-button :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('collectCode.login.getCode')"
|
||||
type="primary" block style="height: 48px" @click="getCode">{{
|
||||
$t('collectCode.login.getCode')
|
||||
}}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">
|
||||
{{ $t('collectCode.login.getCode') }}
|
||||
</van-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<van-button type="primary" v-if="password" block :loading="loadingRef.loading2"
|
||||
:loading-text="$t('collectCode.login.login')"
|
||||
style="height: 48px;margin-top:10px" @click="goLogin">{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">
|
||||
{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
</div>
|
||||
<div/>
|
||||
</div>
|
||||
<div class="mt-[55px]">
|
||||
<div v-if="loginType === 0">
|
||||
<van-button :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('collectCode.login.getCode')"
|
||||
type="primary" block style="height: 48px" @click="getCode">{{ $t('collectCode.login.getCode') }}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('collectCode.login.getCode') }}</van-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<van-button type="primary" v-if="password" block :loading="loadingRef.loading2" :loading-text="$t('collectCode.login.login')"
|
||||
style="height: 48px;margin-top:10px" @click="goLogin">{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('collectCode.login.login') }}</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
<van-swipe-item>
|
||||
<div v-if="pane == 1">
|
||||
<div>
|
||||
<div class="flex mb-[16px]">
|
||||
<div class="text-[16px] text-[#BDBDBD] mr-[10px]">{{ $t('collectCode.login.hasSendTo') }}</div>
|
||||
<div class="text-[16px] text-[#000]">+86 {{ phoneNum }}</div>
|
||||
<div v-show="pane === 1">
|
||||
<div class="flex mb-[16px]">
|
||||
<div class="text-[16px] text-[#BDBDBD] mr-[10px]">{{ $t('collectCode.login.hasSendTo') }}</div>
|
||||
<div class="text-[16px] text-[#000]">+86 {{ phoneNum }}</div>
|
||||
</div>
|
||||
<van-password-input :value="code" :gutter="10" :mask="false" focused @focus="showKeyboard = true"/>
|
||||
<div class="flex justify-between">
|
||||
<div :class="`${countdown>0?'text-#BDBDBD':'text-#2B53AC'} text-14px`">
|
||||
{{ $t('collectCode.login.reSend') }}<span v-if="countdown>0">({{ countdown }})</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<van-password-input
|
||||
:value="code"
|
||||
:gutter="10"
|
||||
:mask="false"
|
||||
:focused="isFocused"
|
||||
/>
|
||||
<input
|
||||
v-model="code"
|
||||
type="tel"
|
||||
maxlength="6"
|
||||
ref="codeInput"
|
||||
class="opacity-0 absolute top-0 left-0 h-full w-full z-999"
|
||||
@input="code = $event.target.value.replace(/\D/g, '').slice(0, 6)"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div :class="`${countdown>0?'text-#BDBDBD':'text-#2B53AC'} text-14px`">
|
||||
{{ $t('collectCode.login.reSend') }}<span v-if="countdown>0">({{ countdown }})</span>
|
||||
</div>
|
||||
<div @click="goBack" class="text-#2B53AC text-14px">
|
||||
{{ $t('collectCode.login.back') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[17px]">
|
||||
<van-button v-if="code.length === 6" type="primary" block :loading="loadingRef.loading2"
|
||||
:loading-text="$t('collectCode.login.login')" style="height: 48px" @click="goLogin">
|
||||
{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">
|
||||
{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
<div @click="goBack" class="text-#2B53AC text-14px">
|
||||
{{ $t('collectCode.login.back') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[17px]">
|
||||
<van-button v-if="code.length === 6" type="primary" block :loading="loadingRef.loading2"
|
||||
:loading-text="$t('collectCode.login.login')" style="height: 48px" @click="goLogin">
|
||||
{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">
|
||||
{{ $t('collectCode.login.login') }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<van-number-keyboard v-model="code" :show="showKeyboard" @blur="showKeyboard = false"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -6,7 +6,7 @@ import QRCode from 'qrcode'
|
||||
import { showImagePreview } from 'vant';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const t = useI18n().t;
|
||||
const t = useI18n();
|
||||
|
||||
const statusLabel=[
|
||||
{label: t('collectCode.qrcode.status.paid'), value:2, color:'#18A058'},
|
||||
@ -33,6 +33,7 @@ const getQRBase64 = async () => {
|
||||
errorCorrectionLevel: 'H'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('生成二维码失败:', err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -44,26 +45,7 @@ const openQrCode=async ()=>{
|
||||
QRUrl.value=base64
|
||||
show.value=true
|
||||
}
|
||||
/**
|
||||
* 将数字格式化为"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>
|
||||
@ -77,7 +59,7 @@ function formatNumber(num) {
|
||||
<XImage class="w-57px h-56px rounded-4px" :src="data.hdPic"></XImage>
|
||||
</div>
|
||||
<div class="text-12px text-#1E1E1E">
|
||||
<div>{{ $t('collectCode.qrcode.card.lotNo') }}{{ formatNumber(data.lotNo) }}</div>
|
||||
<div>{{ $t('collectCode.qrcode.card.lotNo') }}{{ data.lotNo }}</div>
|
||||
<div>{{ $t('collectCode.qrcode.card.creator') }}{{ data.userName }}</div>
|
||||
<div>{{ $t('collectCode.qrcode.card.createTime') }}{{data.createdAt}}</div>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@ const localState = ref({
|
||||
showDetail: false,
|
||||
showHeight: ''
|
||||
})
|
||||
const {t} =useI18n()
|
||||
|
||||
const { userInfo, } = codeAuthStore()
|
||||
const {getOfflineQrcodeList,itemList, loading: storeLoading,pageRef}= goodStore()
|
||||
const initData = async () => {
|
||||
@ -30,6 +30,7 @@ const initData = async () => {
|
||||
}
|
||||
const show=ref(false)
|
||||
const close=()=>{
|
||||
console.log('show',show.value)
|
||||
show.value=false
|
||||
}
|
||||
const logOut=()=>{
|
||||
@ -48,15 +49,7 @@ const confirm=async ()=>{
|
||||
message.warning(t('collectCode.message.lotNoRequired'))
|
||||
return false
|
||||
}
|
||||
function is25Format(num) {
|
||||
return /^25\d{3}$/.test(String(num));
|
||||
}
|
||||
if (!is25Format(createForm.value.lotNo)){
|
||||
message.warning(t('collectCode.message.lotNoType'))
|
||||
return
|
||||
}
|
||||
|
||||
const res=await offlineQrcodeCreate({...createForm.value,price:String(createForm.value.price),lotNo:createForm.value.lotNo-25000})
|
||||
const res=await offlineQrcodeCreate({...createForm.value,price:String(createForm.value.price)})
|
||||
if (res.status===0){
|
||||
show.value=false
|
||||
onRefresh()
|
||||
@ -80,11 +73,8 @@ const loadMore = async () => {
|
||||
const abnormal=ref(false)
|
||||
const abnormalRow=ref({})
|
||||
const inputLotNo=async (data)=>{
|
||||
if (createForm.value.lotNo<=25000){
|
||||
return
|
||||
}
|
||||
const res=await offlineQrcodeList({
|
||||
lotNo:createForm.value.lotNo-25000
|
||||
lotNo:createForm.value.lotNo
|
||||
})
|
||||
if (res.status===0){
|
||||
if (res.data.Data?.length>0){
|
||||
|
@ -10,7 +10,7 @@ import {codeAuthStore} from "~/stores-collect-code/auth/index.js";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {t} = useI18n();
|
||||
const {checkoutSessionUrl,qrUid,qrData,codePKey,codePayUid} = codeAuthStore()
|
||||
const {checkoutSessionUrl,qrUid,qrData} = codeAuthStore()
|
||||
const payStatus = ref(0)
|
||||
definePageMeta({
|
||||
i18n: 'payment.title'
|
||||
@ -19,7 +19,6 @@ const changePayStatus = () => {
|
||||
payStatus.value = payStatus.value === 0 ? 1 : 0
|
||||
}
|
||||
const amount = ref('')
|
||||
const router = useRouter()
|
||||
const confirmPay = async () => {
|
||||
if (payStatus.value === 1 && !amount.value) {
|
||||
message.warning(t('collectCode.payment.enterAmount'))
|
||||
@ -41,18 +40,7 @@ const confirmPay = async () => {
|
||||
testReturnEndPoint: '/collectCode/payment/result'
|
||||
})
|
||||
if (res.status === 0) {
|
||||
codePKey.value=res.data.checkoutSessionUrl
|
||||
codePayUid.value=res.data.payUid
|
||||
router.push({
|
||||
path:'/checkoutPage',
|
||||
query:{
|
||||
payUid:res.data.payUid,
|
||||
returnUrl:'/collectCode/payment/result',
|
||||
stripeKey:res.data.checkoutSessionUrl
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
window.location.href = res.data.checkoutSessionUrl
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,8 +75,8 @@ const handleInput = (e) => {
|
||||
{{ qrData?.leftPrice }}
|
||||
</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="`${$t('collectCode.payment.maxAmount')} ${qrData.currency} ${qrData?.leftPrice}`" @input="handleInput">
|
||||
<input v-model="amount" class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text"
|
||||
:placeholder="$t('collectCode.payment.maxAmount', { currency: qrData.currency, price: qrData?.leftPrice })" @input="handleInput">
|
||||
</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">
|
||||
|
@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import {VueSignaturePad} from 'vue-signature-pad';
|
||||
import { showToast } from 'vant';
|
||||
import { onMounted } from 'vue';
|
||||
import {codeAuthStore} from "~/stores-collect-code/auth/index.js";
|
||||
import {signOffline} from "~/api/goods/index.js";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {t} = useI18n();
|
||||
const {formData,number}=codeAuthStore()
|
||||
const signaturePad = ref(null);
|
||||
@ -12,10 +14,11 @@ definePageMeta({
|
||||
});
|
||||
const router = useRouter();
|
||||
const imgUrl = ref('');
|
||||
|
||||
const show = ref(false);
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
};
|
||||
|
||||
const submitSignature = () => {
|
||||
if (signaturePad.value?.isEmpty()) {
|
||||
showToast(t('collectCode.signature.pleaseSign'));
|
||||
@ -23,7 +26,7 @@ const submitSignature = () => {
|
||||
}
|
||||
const { data } = signaturePad.value?.saveSignature();
|
||||
imgUrl.value = data;
|
||||
confirm()
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const clearSignature = () => {
|
||||
@ -34,7 +37,7 @@ const confirm=async ()=>{
|
||||
const res=await signOffline({
|
||||
userInfo:formData.value,
|
||||
signOrder:Number(number.value),
|
||||
signImgFileData:imgUrl.value,
|
||||
signImgFileData:imgUrl.value
|
||||
})
|
||||
if (res.status===0){
|
||||
router.push('/collectCode/signature/result')
|
||||
@ -48,7 +51,6 @@ const confirm=async ()=>{
|
||||
<client-only>
|
||||
<VueSignaturePad
|
||||
width="100%"
|
||||
height="93%"
|
||||
class="signature bg-#fff rounded-10px mb-10px"
|
||||
ref="signaturePad"
|
||||
/>
|
||||
@ -64,6 +66,9 @@ const confirm=async ()=>{
|
||||
{{ $t('collectCode.signature.confirm') }}
|
||||
</van-button>
|
||||
</div>
|
||||
<van-dialog v-model:show="show" show-cancel-button @confirm="confirm">
|
||||
<img class="w-300px h-200px" :src="imgUrl" />
|
||||
</van-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -20,7 +20,7 @@ const columns = ref([
|
||||
{text: t('realAuth.female'), value: 2},
|
||||
])
|
||||
const columns1 = ref([
|
||||
{text: t('realAuth.idTypeString'), value: 1},
|
||||
{text: t('realAuth.idCard'), value: 1},
|
||||
{text: t('realAuth.passport'), value: 2},
|
||||
{text: t('realAuth.other'), value: 3},
|
||||
])
|
||||
@ -72,6 +72,7 @@ const initData= async ()=>{
|
||||
}
|
||||
}
|
||||
const nextClick=async ()=>{
|
||||
console.log('number.value',number.value)
|
||||
//扫号牌
|
||||
if (number.value==1){
|
||||
if (!isFormComplete(formData.value)){
|
||||
@ -90,8 +91,6 @@ const nextClick=async ()=>{
|
||||
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
|
||||
@ -138,9 +137,8 @@ initData()
|
||||
<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"/>
|
||||
<x-van-select v-model="formData.cardType" :label="$t('realAuth.idCard')" :columns="columns1"/>
|
||||
<van-field :label="$t('realAuth.idCard')" v-model="formData.cardId" class="mb-10px" :placeholder="$t('realAuth.idCardPlaceholder')"/>
|
||||
<x-van-select v-model="formData.gender" :label="$t('realAuth.gender')" :columns="columns"/>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
@ -39,7 +39,8 @@ const fetchPmblPdf = async () => {
|
||||
})
|
||||
pmblUrl.value = res.data?.viewUrl // 假设接口返回的PDF URL在data字段中
|
||||
} catch (error) {
|
||||
}
|
||||
console.error('获取拍卖笔录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听折叠面板变化
|
||||
|
@ -8,6 +8,7 @@ definePageMeta({
|
||||
i18n: 'countryRegion.title',
|
||||
})
|
||||
const router = useRouter()
|
||||
console.log('router',router)
|
||||
const { t, locale } = useI18n()
|
||||
const value = ref('');
|
||||
const alphabet = computed(() => {
|
||||
@ -143,6 +144,7 @@ const handleCountrySelect = (country) => {
|
||||
}
|
||||
|
||||
initData()
|
||||
console.log('searchCountry',searchCountry.value)
|
||||
// 监听语言变化,重新初始化数据
|
||||
watch(locale, () => {
|
||||
initData()
|
||||
|
@ -1,19 +1,15 @@
|
||||
<script setup>
|
||||
import CheckoutPage from '@/components/stripe/CheckoutPage.vue'
|
||||
import CompletePage from '@/components/stripe/CompletePage.vue'
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: 'Stripe支付'
|
||||
i18n: 'menu.profile',
|
||||
})
|
||||
const route = useRoute()
|
||||
const key=route.query.key??''
|
||||
|
||||
const url=route.query.url??''
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CheckoutPage/>
|
||||
<!-- <iframe class="w-100vw h-100vh" :src="`/stripe/checkout.html?key=${key}`"></iframe> -->
|
||||
<iframe :src="url"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -40,26 +40,6 @@ 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>
|
||||
@ -91,9 +71,9 @@ const openShow = async (item) => {
|
||||
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]"
|
||||
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
|
||||
>
|
||||
Lot{{ formatNumber(item.index) }}
|
||||
LOT{{ item.index }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-[8px]">
|
||||
|
@ -3,9 +3,9 @@ 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 {message} from '@/components/x-message/useMessage.js'
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
const {auctionDetail,getArtworkList} = goodStore();
|
||||
const {getAuctionDetail, auctionDetail,getArtworkList} = goodStore();
|
||||
const {fullLive} = liveStore()
|
||||
const changeLive = () => {
|
||||
if (!fullLive.value){
|
||||
@ -16,7 +16,7 @@ const changeLive = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await getAuctionDetail()
|
||||
</script>
|
||||
<template>
|
||||
<div class="grow-1">
|
||||
@ -27,13 +27,13 @@ const changeLive = () => {
|
||||
<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 class="text-12px">{{auctionDetail.startDate}} {{$t('home.text2')}}</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 class="text-12px mb-54px">本场拍卖会{{auctionDetail.isLiving===2?'未开始':'已结束'}}</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 class="text-12px">{{auctionDetail.startDate}} {{$t('home.text2')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
|
@ -40,7 +40,7 @@ const headItem=(statusCode)=>{
|
||||
<div class="text-#939393 text-14px">{{ $t('live_room.next_lot') }}</div>
|
||||
</template>
|
||||
<template v-else-if="auctionData.auctionPriceList?.buys?.length>0">
|
||||
<div v-for="(item, index) in auctionData.auctionPriceList?.buys" :key="index" class="flex flex-shrink-0">
|
||||
<div v-for="(item, index) in auctionData.auctionPriceList?.buys" :key="index" class="flex flex-shrink-0 h-25px">
|
||||
<div class="text-start shrink-0 w-60px" :style="`color: ${headItem(item.statusCode).color}`" >{{ headItem(item.statusCode).label }}</div>
|
||||
<div class="text-start shrink-0 w-80px">{{ item.auctionType==='local'? $t('live_room.spot'):$t('live_room.network') }}</div>
|
||||
<div class="text-start shrink-0 w-80px">{{ item.createdAt }}</div>
|
||||
|
@ -30,6 +30,7 @@ const captureVideoFrame = () => {
|
||||
try {
|
||||
const video = document.querySelector('#J_prismPlayer video')
|
||||
if (!video) {
|
||||
console.error('未找到视频元素')
|
||||
return null
|
||||
}
|
||||
|
||||
@ -41,6 +42,7 @@ const captureVideoFrame = () => {
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||
return canvas.toDataURL('image/jpeg', 0.9)
|
||||
} catch (error) {
|
||||
console.error('获取视频截图失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ const captureVideoFrame = () => {
|
||||
try {
|
||||
const video = document.querySelector('#J_prismPlayer video')
|
||||
if (!video) {
|
||||
console.error('未找到视频元素')
|
||||
return null
|
||||
}
|
||||
|
||||
@ -129,6 +130,7 @@ const captureVideoFrame = () => {
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||
return canvas.toDataURL('image/jpeg', 0.9)
|
||||
} catch (error) {
|
||||
console.error('获取视频截图失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -142,7 +144,8 @@ const handleCapture = () => {
|
||||
onClick:()=>{
|
||||
router.replace('/')
|
||||
fullLive.value=true
|
||||
}
|
||||
console.log('执行')
|
||||
}
|
||||
})*!/
|
||||
}*/
|
||||
showMinWindow1({
|
||||
|
@ -66,7 +66,8 @@ const loadMore = async () => {
|
||||
watch(()=>{
|
||||
return auctionData.value?.artwork?.index
|
||||
},(newValue)=>{
|
||||
})
|
||||
console.log('newValue',newValue)
|
||||
})
|
||||
watch(()=>props.show,(newValue)=>{
|
||||
if (newValue){
|
||||
nextTick(()=>{
|
||||
|
@ -13,12 +13,16 @@ import {message} from "~/components/x-message/useMessage.js"
|
||||
import {showConfirmDialog} from 'vant';
|
||||
import {artworkBuy} from "@/api/goods/index.js"
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {CountUp} from 'countup.js'
|
||||
const { t } = useI18n()
|
||||
const { auctionDetail,getAuctionDetail} = goodStore();
|
||||
const countUpRef = ref(null)
|
||||
const nextPriceRef = ref(null)
|
||||
const { auctionDetail} = goodStore();
|
||||
const player = ref(null)
|
||||
const {quoteStatus, show, playerId, show1, auctionData, getSocketData, getLiveLink, fullLive} = liveStore()
|
||||
const pullLink = ref('')
|
||||
const handlePlayerError = (error) => {
|
||||
console.error('播放器错误:', error)
|
||||
showConfirmDialog({
|
||||
message: t('live_room.error_mess'),
|
||||
showCancelButton: true
|
||||
@ -80,7 +84,8 @@ const initializePlayer = async () => {
|
||||
|
||||
})
|
||||
player.value.on('loading', () => {
|
||||
})
|
||||
console.log('loading')
|
||||
})
|
||||
player.value.on('error', handlePlayerError)
|
||||
} catch (error) {
|
||||
showConfirmDialog({
|
||||
@ -90,12 +95,12 @@ const initializePlayer = async () => {
|
||||
initializePlayer()
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
console.error('播放器初始化失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await getAuctionDetail()
|
||||
pullLink.value = await getLiveLink()
|
||||
if (auctionDetail.value.isLiving===1){
|
||||
initializePlayer()
|
||||
|
@ -19,7 +19,6 @@ const loadingRef=ref({
|
||||
})
|
||||
const isExist=ref(false)//帐号是否存在 true存在
|
||||
const isReal=ref(false) //isReal 是否实名过
|
||||
const codeInput=ref(null)
|
||||
function goToPage() {
|
||||
router.push('/countryRegion');
|
||||
}
|
||||
@ -41,9 +40,11 @@ const countdown = ref(0);
|
||||
const phoneNum = ref('')
|
||||
const code = ref('')
|
||||
const pane = ref(0)
|
||||
const showKeyboard = ref(false);
|
||||
// 根据语言获取默认国家
|
||||
const getDefaultCountry = () => {
|
||||
let defaultCode = 'CN' // 默认中国大陆
|
||||
console.log('locale.value',locale.value)
|
||||
switch (locale.value) {
|
||||
case 'zh-CN':
|
||||
defaultCode = 'CN'
|
||||
@ -73,6 +74,7 @@ const defaultCountry = getDefaultCountry()
|
||||
|
||||
|
||||
const selectedCountry = ref(route.query.countryName || defaultCountry.name)
|
||||
console.log('selectedCountry',selectedCountry.value)
|
||||
onMounted(()=>{
|
||||
selectedZone.value=route.query.zone || defaultCountry.zone
|
||||
})
|
||||
@ -98,8 +100,13 @@ const getCode =async () => {
|
||||
}
|
||||
pane.value = 1
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
|
||||
showKeyboard.value=true
|
||||
startCountdown();
|
||||
/* pane.value = 1
|
||||
vanSwipeRef.value?.swipeTo(pane.value)
|
||||
showKeyboard.value=true
|
||||
startCountdown();*/
|
||||
|
||||
}
|
||||
const goBack = () => {
|
||||
code.value = ''
|
||||
@ -129,7 +136,8 @@ const goLogin =async () => {
|
||||
if (res1.status===0){
|
||||
window.location.href=res1.data.h5Url
|
||||
}
|
||||
}else {
|
||||
console.log('123')
|
||||
}else {
|
||||
await router.push('/');
|
||||
}
|
||||
}
|
||||
@ -137,7 +145,7 @@ const goLogin =async () => {
|
||||
}
|
||||
const isKeyboardVisible = ref(false)
|
||||
const windowHeight = ref(window.innerHeight)
|
||||
const isFocused = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// 记录初始窗口高度
|
||||
windowHeight.value = window.innerHeight
|
||||
@ -155,7 +163,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-bottom bg-cover grow-1 px-[31px] pt-[86px]">
|
||||
<div class="h-screen-nav w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-cover px-[31px] pt-[86px]">
|
||||
<div class="w-full flex justify-center mb-[100px]">
|
||||
<img class="h-[105px] w-[189px]" src="@/static/images/ghfggff.png" alt="">
|
||||
</div>
|
||||
@ -178,6 +186,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div class="mt-[55px]">
|
||||
<van-button :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('login.getCode')" color="#2B53AC" block style="height: 48px" @click="getCode">{{ $t('login.getCode') }}</van-button>
|
||||
@ -191,24 +200,7 @@ onUnmounted(() => {
|
||||
<div class="text-[16px] text-[#BDBDBD] mr-[10px]">{{ $t('login.hasSendTo') }}</div>
|
||||
<div class="text-[16px] text-[#000]">+{{ selectedZone }} {{ phoneNum }}</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<van-password-input
|
||||
:value="code"
|
||||
:gutter="10"
|
||||
:mask="false"
|
||||
:focused="isFocused"
|
||||
/>
|
||||
<input
|
||||
v-model="code"
|
||||
type="tel"
|
||||
maxlength="6"
|
||||
ref="codeInput"
|
||||
class="opacity-0 absolute top-0 left-0 h-full w-full"
|
||||
@input="code = $event.target.value.replace(/\D/g, '').slice(0, 6)"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
</div>
|
||||
<van-password-input :value="code" :gutter="10" :mask="false" focused @focus="showKeyboard = true" />
|
||||
<div class="flex justify-between">
|
||||
<div :class="`${countdown>0?'text-#BDBDBD':'text-#2B53AC'} text-14px`">
|
||||
{{ $t('login.reSend') }}<span v-if="countdown>0">({{countdown}})</span>
|
||||
@ -224,6 +216,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<van-number-keyboard v-model="code" :show="showKeyboard" @blur="showKeyboard = false" />
|
||||
<div v-if="!isKeyboardVisible" class="text-center text-14px absolute left-1/2 transform translate-x--1/2 bottom-20px">
|
||||
{{ $t('login.agreement') }}<span class="text-#3454AF " @click="$router.push('/privacyPolicy')">{{ $t('login.privacyPolicy') }}</span>
|
||||
</div>
|
||||
|
@ -1,363 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
const config = useRuntimeConfig()
|
||||
const stripe = Stripe(config.public.NUXT_PUBLIC_PKEY)
|
||||
const statusIcon = ref('')
|
||||
const statusText = ref('')
|
||||
const intentId = ref('')
|
||||
const intentStatus = ref('')
|
||||
const viewDetailsUrl = ref('')
|
||||
const iconColor = ref('')
|
||||
|
||||
const SuccessIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const ErrorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
const InfoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
|
||||
<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
|
||||
</svg>`
|
||||
|
||||
function setPaymentDetails(intent) {
|
||||
let status = "Something went wrong, please try again."
|
||||
let color = "#DF1B41"
|
||||
let icon = ErrorIcon
|
||||
|
||||
if (!intent) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
switch (intent.status) {
|
||||
case "succeeded":
|
||||
status = "Payment succeeded"
|
||||
color = "#30B130"
|
||||
icon = SuccessIcon
|
||||
break
|
||||
case "processing":
|
||||
status = "Your payment is processing."
|
||||
color = "#6D6E78"
|
||||
icon = InfoIcon
|
||||
break
|
||||
case "requires_payment_method":
|
||||
status = "Your payment was not successful, please try again."
|
||||
break
|
||||
}
|
||||
|
||||
iconColor.value = color
|
||||
statusIcon.value = icon
|
||||
statusText.value = status
|
||||
intentId.value = intent.id
|
||||
intentStatus.value = intent.status
|
||||
viewDetailsUrl.value = `https://dashboard.stripe.com/payments/${intent.id}`
|
||||
}
|
||||
|
||||
function setErrorState() {
|
||||
iconColor.value = "#DF1B41"
|
||||
statusIcon.value = ErrorIcon
|
||||
statusText.value = "Something went wrong, please try again."
|
||||
}
|
||||
|
||||
async function checkStatus() {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const clientSecret = urlParams.get("payment_intent_client_secret")
|
||||
|
||||
if (!clientSecret) {
|
||||
setErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret)
|
||||
setPaymentDetails(paymentIntent)
|
||||
router.push({
|
||||
path:'/payment/result',
|
||||
query:{
|
||||
orderNo:route.query.orderNo
|
||||
}
|
||||
})
|
||||
}
|
||||
const router=useRouter()
|
||||
const route=useRoute()
|
||||
onMounted(() => {
|
||||
checkStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="payment-status">
|
||||
<div id="status-icon" :style="{ backgroundColor: iconColor }" v-html="statusIcon"></div>
|
||||
<h2 id="status-text">{{ statusText }}</h2>
|
||||
<div id="details-table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="TableLabel">id</td>
|
||||
<td id="intent-id" class="TableContent">{{ intentId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="TableLabel">status</td>
|
||||
<td id="intent-status" class="TableContent">{{ intentStatus }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a :href="viewDetailsUrl" id="view-details" rel="noopener noreferrer" target="_blank">
|
||||
View details
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/>
|
||||
<path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/>
|
||||
</svg>
|
||||
</a>
|
||||
<NuxtLink id="retry-button" to="/checkout">Test another</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Variables */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,9 +4,8 @@ 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";
|
||||
const {checkoutSessionUrl,payment,payUid}= authStore()
|
||||
const {checkoutSessionUrl,payment}= authStore()
|
||||
const payStatus=ref(0)
|
||||
definePageMeta({
|
||||
i18n: 'payment.title'
|
||||
@ -31,7 +30,6 @@ const confirmPay=async ()=>{
|
||||
message: t('payment.loading'),
|
||||
forbidClick: true,
|
||||
});
|
||||
|
||||
const res=await createBuyOrder({
|
||||
buyUid:payment.value.buyUid,
|
||||
price:payStatus.value===0?payment.value.leftPrice:amount.value,
|
||||
@ -39,20 +37,17 @@ const confirmPay=async ()=>{
|
||||
testReturnHost:window.location.origin,
|
||||
testReturnEndPoint:'/payment/result'
|
||||
})
|
||||
if (res.status===0){
|
||||
/* router.push({
|
||||
path:'/externallinks',
|
||||
query:{
|
||||
url:res.data.checkoutSessionUrl
|
||||
}
|
||||
})*/
|
||||
window.location.href=res.data.checkoutSessionUrl
|
||||
}
|
||||
}
|
||||
|
||||
if (res.status===0){
|
||||
checkoutSessionUrl.value=res.data.checkoutSessionUrl
|
||||
payUid.value=res.data.payUid
|
||||
router.push({
|
||||
path:'/checkoutPage',
|
||||
query:{
|
||||
payUid:res.data.payUid,
|
||||
returnUrl:'/payment/result',
|
||||
stripeKey:res.data.checkoutSessionUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleInput = (e) => {
|
||||
// 只允许数字和小数点,且只保留两位小数
|
||||
const value = e.target.value
|
||||
|
@ -1,94 +1,44 @@
|
||||
<script setup>
|
||||
import {orderQuery} from "~/api/goods/index.js";
|
||||
import { showLoadingToast, closeToast } from 'vant';
|
||||
|
||||
definePageMeta({
|
||||
i18n: 'payment.text1',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const {t} = useI18n();
|
||||
const {t}=useI18n();
|
||||
const route = useRoute();
|
||||
const resData = ref({})
|
||||
let timer = null
|
||||
let startTime = Date.now()
|
||||
|
||||
const queryOrder = async () => {
|
||||
// 首先检查是否已经超过5秒
|
||||
if (Date.now() - startTime > 5000) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
return
|
||||
}
|
||||
|
||||
showLoadingToast({
|
||||
message: '加载中...',
|
||||
forbidClick: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await orderQuery({
|
||||
orderNo: route.query.orderNo
|
||||
})
|
||||
|
||||
if (res.status === 0) {
|
||||
resData.value = res.data
|
||||
|
||||
// 只在支付成功时停止轮询
|
||||
if (resData.value.status === 1) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
await queryOrder()
|
||||
|
||||
// 开始轮询
|
||||
timer = setInterval(async () => {
|
||||
await queryOrder()
|
||||
}, 1000)
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
closeToast()
|
||||
}
|
||||
const resData=ref({})
|
||||
const res=await orderQuery({
|
||||
orderNo:route.query.orderNo
|
||||
})
|
||||
|
||||
const statusLabel = {
|
||||
1: t('payment.text2'),
|
||||
2: t('payment.text3'),
|
||||
3: t('payment.text4'),
|
||||
4: t('payment.text5'),
|
||||
if (res.status===0){
|
||||
resData.value=res.data
|
||||
}
|
||||
|
||||
const goHome = () => {
|
||||
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="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 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">
|
||||
{{ t('payment.result.backToHome') }}
|
||||
{{ t('payment.backToHome') }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -37,7 +37,8 @@ const fetchData = async () => {
|
||||
showMyList.value = groupByDate(res.data.data)
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
console.error('获取数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onRefresh = async () => {
|
||||
@ -77,13 +78,6 @@ fetchData()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置选项 -->
|
||||
<div class="px-16px mb-20px">
|
||||
<van-cell-group inset>
|
||||
<!-- 移除语言设置入口 -->
|
||||
</van-cell-group>
|
||||
</div>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<div class="grow-1 flex flex-col">
|
||||
<div class="border-b-1px border-b-#D3D3D3 px-16px">
|
||||
|
@ -1,18 +1,13 @@
|
||||
<script setup>
|
||||
import {authStore} from "@/stores/auth/index.js";
|
||||
import {useI18n} from 'vue-i18n'
|
||||
const {t} = useI18n()
|
||||
const {t:$t} = useI18n()
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const columns1 = ref([
|
||||
{text: t('realAuth.idCard'), value: '1'},
|
||||
{text: t('realAuth.passport'), value: '2'},
|
||||
{text: t('realAuth.other'), value: '3'},
|
||||
])
|
||||
const {userInfo}= authStore()
|
||||
|
||||
</script>
|
||||
@ -40,7 +35,7 @@ const {userInfo}= authStore()
|
||||
<template v-if="type===1">
|
||||
<div class="flex mb-20px" >
|
||||
<div class="mr-10px">{{$t('realAuth.name')}}:</div>
|
||||
<div>{{userInfo.realName||userInfo.userExtend.realName||''}}</div>
|
||||
<div>{{userInfo.realName}}</div>
|
||||
</div>
|
||||
<div class="flex mb-20px">
|
||||
<div class="mr-10px">{{$t('realAuth.gender')}}:</div>
|
||||
@ -62,14 +57,6 @@ const {userInfo}= authStore()
|
||||
<div class="mr-10px">{{$t('realAuth.bankCard')}}:</div>
|
||||
<div>{{userInfo.userExtend.bankNo}}</div>
|
||||
</div>
|
||||
<div class="flex mb-20px">
|
||||
<div class="mr-10px">{{$t('realAuth.idTye')}}:</div>
|
||||
<div>{{columns1.find(x=>x.value===userInfo.userExtend.idType)?.text}}</div>
|
||||
</div>
|
||||
<div class="flex mb-20px">
|
||||
<div class="mr-10px">{{$t('realAuth.idNumber')}}:</div>
|
||||
<div>{{userInfo.userExtend.idNo}}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -17,11 +17,6 @@ const { locale } = useI18n()
|
||||
const {userInfo,selectedZone}= authStore()
|
||||
const active=ref(locale.value==='zh-CN'?0:1)
|
||||
const { t } = useI18n()
|
||||
const columns1 = ref([
|
||||
{text: t('realAuth.idCard'), value: '1'},
|
||||
{text: t('realAuth.passport'), value: '2'},
|
||||
{text: t('realAuth.other'), value: '3'},
|
||||
])
|
||||
const form=ref({
|
||||
realName: "",
|
||||
sex:'',
|
||||
@ -129,12 +124,6 @@ const goLogin=()=>{
|
||||
<van-field v-model="form.userExtend.bankNo" :label="$t('realAuth.bankCard')" clearable
|
||||
:placeholder="$t('realAuth.bankCardPlaceholder')"></van-field>
|
||||
</div>
|
||||
<div class="border-b-[1.7px] mt-[8px]">
|
||||
<x-van-select v-model="form.userExtend.idType" :label="$t('realAuth.idTye')" :columns="columns1"/>
|
||||
</div>
|
||||
<div class="border-b-[1.7px] mt-[8px]">
|
||||
<van-field :label="$t('realAuth.idNumber')" v-model="form.userExtend.idNo" class="mb-10px" :placeholder="$t('realAuth.idNumberPlaceholder')"/>
|
||||
</div>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
@ -146,7 +135,7 @@ const goLogin=()=>{
|
||||
<detail :type="active"></detail>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
<div class="flex justify-between shrink-0 mb-20px" v-if="statusCode===0">
|
||||
<div class="flex justify-between" v-if="statusCode===0">
|
||||
<van-button style="width: 151px;height: 48px" color="#E9F1F8" @click="goLogin">
|
||||
<div class="text-#2B53AC text-16px">{{ $t('realAuth.cancel') }}</div>
|
||||
</van-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {showToast,showLoadingToast } from 'vant';
|
||||
import {showToast} from 'vant';
|
||||
import {onMounted, onUnmounted, ref} from 'vue';
|
||||
import {signOffline, signOnline} from "~/api/goods/index.js";
|
||||
import {VueSignaturePad} from "vue-signature-pad";
|
||||
@ -10,26 +10,55 @@ const {t:$t} = useI18n()
|
||||
definePageMeta({
|
||||
layout: ''
|
||||
})
|
||||
const { payment} = authStore()
|
||||
const { userInfo ,payment} = authStore()
|
||||
const signaturePad = ref(null);
|
||||
const isLandscapeMode = ref(false);
|
||||
|
||||
const checkScreenOrientation = () => {
|
||||
|
||||
const orientation = screen.orientation?.type || window.orientation;
|
||||
if (orientation === 'landscape-primary' || orientation === 'landscape-secondary' ||
|
||||
orientation === 90 || orientation === -90) {
|
||||
isLandscapeMode.value = true;
|
||||
} else {
|
||||
isLandscapeMode.value = false;
|
||||
showToast($t('signature.tips.landscape'));
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
nextTick(() => {
|
||||
checkScreenOrientation();
|
||||
});
|
||||
window.addEventListener('orientationchange', checkScreenOrientation);
|
||||
screen.orientation?.addEventListener('change', checkScreenOrientation);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('orientationchange', checkScreenOrientation);
|
||||
screen.orientation?.removeEventListener('change', checkScreenOrientation);
|
||||
});
|
||||
const imgUrl = ref('')
|
||||
const show = ref(false)
|
||||
const clearSignature = () => {
|
||||
signaturePad.value?.clearSignature();
|
||||
};
|
||||
const toast=ref(false)
|
||||
const submitSignature = () => {
|
||||
if (signaturePad.value?.isEmpty()) {
|
||||
showToast($t('collectCode.signature.pleaseSign'));
|
||||
showToast($t('signature.pleaseSign'));
|
||||
return;
|
||||
}
|
||||
toast.value=showLoadingToast({
|
||||
message: '加载中...',
|
||||
forbidClick: true,
|
||||
});
|
||||
const { data } = signaturePad.value?.saveSignature(); // 返回 base64 格式的图片数据
|
||||
imgUrl.value = data;
|
||||
confirm()
|
||||
|
||||
show.value = true;
|
||||
nextTick(() => {
|
||||
const overlay = document.querySelector('.signature-container .van-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.width = '100vw';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.right = '0';
|
||||
}
|
||||
})
|
||||
};
|
||||
const confirm = async () => {
|
||||
const res = await signOnline({
|
||||
@ -37,8 +66,7 @@ const confirm = async () => {
|
||||
signImgFileData: imgUrl.value
|
||||
})
|
||||
if (res.status===0){
|
||||
await router.push('/payment')
|
||||
toast.value?.close()
|
||||
router.push('/payment')
|
||||
}
|
||||
}
|
||||
const goBack = () => {
|
||||
@ -47,28 +75,50 @@ router.back()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="signature-container">
|
||||
<div class="flex flex-col h-100vh px-20px py-20px bg-gray w-100vw">
|
||||
<client-only>
|
||||
<div class="signature-container bg-gray ">
|
||||
<template v-if="isLandscapeMode">
|
||||
<div class="signature-content px-10px py-10px">
|
||||
<VueSignaturePad
|
||||
width="100%"
|
||||
height="93%"
|
||||
class="signature bg-#fff rounded-10px mb-10px"
|
||||
ref="signaturePad"
|
||||
/>
|
||||
</client-only>
|
||||
<div class="flex justify-evenly">
|
||||
<van-button class="!h-40px mr-15px" type="primary" @click="goBack">
|
||||
{{ $t('collectCode.signature.back') }}
|
||||
</van-button>
|
||||
<van-button class="!h-40px" type="warning" @click="clearSignature">
|
||||
{{ $t('collectCode.signature.clear') }}
|
||||
</van-button>
|
||||
<van-button class="!h-40px" type="primary" @click="submitSignature">
|
||||
{{ $t('collectCode.signature.confirm') }}
|
||||
</van-button>
|
||||
<div class="control-buttons justify-evenly">
|
||||
<van-button
|
||||
class="control-button"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="goBack"
|
||||
>
|
||||
{{ $t('signature.action.back') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
class="control-button"
|
||||
size="mini"
|
||||
type="warning"
|
||||
@click="clearSignature"
|
||||
>
|
||||
{{ $t('signature.action.clear') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
class="control-button"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="submitSignature"
|
||||
>
|
||||
{{ $t('signature.action.confirm') }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="orientation-hint">
|
||||
<p>{{$t('signature.tips.landscape')}}</p>
|
||||
</div>
|
||||
</template>
|
||||
<van-dialog v-model:show="show" class="signature-dialog" show-cancel-button @confirm="confirm">
|
||||
<img class="h-100px" :src="imgUrl"/>
|
||||
</van-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -33,7 +33,8 @@ const fetchPmblPdf = async () => {
|
||||
})
|
||||
pmblUrl.value = res.data?.viewUrl // 假设接口返回的PDF URL在data字段中
|
||||
} catch (error) {
|
||||
}
|
||||
console.error('获取拍卖笔录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听折叠面板变化
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { setupHttp } from '@/api/http'
|
||||
import { setupHttp as setupHttp1} from '@/api-collect-code/http'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
setupHttp()
|
||||
setupHttp1()
|
||||
})
|
||||
|
@ -15,28 +15,22 @@ export default defineNuxtPlugin(() => {
|
||||
if (import.meta.client) {
|
||||
const i18n = useNuxtApp().$i18n
|
||||
const { setLocale } = i18n
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// 获取系统语言
|
||||
const getSystemLanguage = () => {
|
||||
const browserLang = navigator.language
|
||||
|
||||
// 将浏览器语言映射到应用支持的语言
|
||||
if (browserLang.startsWith('zh')) {
|
||||
return browserLang.includes('TW') || browserLang.includes('HK') ? 'zh-TW' : 'zh-CN'
|
||||
} else if (browserLang.startsWith('ja')) {
|
||||
return 'ja-JP'
|
||||
} else if (browserLang.startsWith('en')) {
|
||||
return 'en-US'
|
||||
}
|
||||
|
||||
// 默认返回中文
|
||||
return 'zh-CN'
|
||||
// 暂时设置固定语言,用于调试
|
||||
// 可以根据需要修改这里的语言代码:'zh-CN' | 'en-US' | 'ja-JP' | 'zh-TW'
|
||||
const fixedLang = 'zh-CN'
|
||||
setLocale(fixedLang)
|
||||
Locale.use(fixedLang)
|
||||
|
||||
// 原自动检测系统语言的逻辑(暂时注释)
|
||||
/* const lang = localStorage.getItem('lang')
|
||||
if (lang) {
|
||||
setLocale(lang as TypeLocale)
|
||||
Locale.use(lang)
|
||||
}
|
||||
|
||||
// 使用系统语言
|
||||
const systemLang = getSystemLanguage()
|
||||
setLocale(systemLang as TypeLocale)
|
||||
Locale.use(systemLang)
|
||||
else {
|
||||
setLocale(i18n.locale.value)
|
||||
Locale.use(i18n.locale.value)
|
||||
}*/
|
||||
}
|
||||
})
|
||||
|
@ -3,5 +3,6 @@ import VConsole from 'vconsole'
|
||||
export default defineNuxtPlugin(() => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const vConsole = new VConsole()
|
||||
console.log('VConsole is enabled')
|
||||
}
|
||||
})
|
89
app/plugins/websocket.ts
Normal file
89
app/plugins/websocket.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import {authStore} from "@/stores/auth";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const config = useRuntimeConfig()
|
||||
const { token } = authStore()
|
||||
const ws = reactive({
|
||||
instance: null as WebSocket | null,
|
||||
isConnected: false,
|
||||
|
||||
// 修改 connect 方法接收路径和数据对象
|
||||
connect(path: string, data?: Record<string, any>) {
|
||||
if (this.instance?.readyState === WebSocket.OPEN) {
|
||||
this.instance.close()
|
||||
}
|
||||
|
||||
// 构建查询字符串
|
||||
const queryString =data
|
||||
? '?' + Object.entries({ token: token.value,...data})
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&')
|
||||
: ''
|
||||
|
||||
// 构建完整的 WebSocket URL
|
||||
const wsUrl = `${config.public.NUXT_PUBLIC_SOCKET_URL}${path}${queryString}`
|
||||
this.instance = new WebSocket(wsUrl)
|
||||
|
||||
this.instance.onopen = () => {
|
||||
this.isConnected = true
|
||||
console.log('WebSocket 已连接')
|
||||
}
|
||||
|
||||
this.instance.onclose = () => {
|
||||
this.isConnected = false
|
||||
console.log('WebSocket 已断开')
|
||||
/* this.reconnect(path, data)*/
|
||||
}
|
||||
|
||||
this.instance.onerror = (error) => {
|
||||
console.error('WebSocket 错误:', error)
|
||||
}
|
||||
|
||||
this.instance.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
this.handleMessage(data)
|
||||
} catch (error) {
|
||||
console.error('消息解析错误:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 更新重连方法以支持数据对象
|
||||
reconnect(path: string, data?: Record<string, any>) {
|
||||
setTimeout(() => {
|
||||
console.log('尝试重新连接...')
|
||||
this.connect(path, data)
|
||||
}, 3000)
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
send(data: any) {
|
||||
if (this.instance?.readyState === WebSocket.OPEN) {
|
||||
this.instance.send(JSON.stringify(data))
|
||||
} else {
|
||||
console.warn('WebSocket 未连接,无法发送消息')
|
||||
}
|
||||
},
|
||||
|
||||
// 关闭连接
|
||||
disconnect() {
|
||||
if (this.instance) {
|
||||
this.instance.close()
|
||||
this.instance = null
|
||||
}
|
||||
},
|
||||
|
||||
// 消息处理
|
||||
handleMessage(data: any) {
|
||||
// 触发自定义事件,让组件可以监听
|
||||
window.dispatchEvent(new CustomEvent('ws-message', { detail: data }))
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
provide: {
|
||||
ws
|
||||
}
|
||||
}
|
||||
})
|
BIN
app/static/video/example.mp4
Normal file
BIN
app/static/video/example.mp4
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
import { createGlobalState,useLocalStorage } from '@vueuse/core'
|
||||
export const codeAuthStore = createGlobalState(() => {
|
||||
const codeToken=useLocalStorage('codeToken','')
|
||||
const token=useLocalStorage('token','')
|
||||
const RefreshToken=useLocalStorage('RefreshToken','')
|
||||
const userInfo=useLocalStorage('userInfo',{})
|
||||
const fingerprint=useLocalStorage('fingerprint','')
|
||||
@ -26,11 +26,7 @@ export const codeAuthStore = createGlobalState(() => {
|
||||
currency:''
|
||||
})
|
||||
const qrData=useLocalStorage('qrData',{})
|
||||
const codePKey=useLocalStorage('codePKey','')
|
||||
const codePayUid=useLocalStorage('codePayUid','')
|
||||
return{
|
||||
codePKey,
|
||||
codePayUid,
|
||||
qrData,
|
||||
qrUid,
|
||||
cpayment,
|
||||
@ -41,7 +37,7 @@ export const codeAuthStore = createGlobalState(() => {
|
||||
formData,
|
||||
userInfo,
|
||||
RefreshToken,
|
||||
codeToken,
|
||||
token,
|
||||
fingerprint
|
||||
}
|
||||
})
|
@ -1,7 +1,4 @@
|
||||
import { createGlobalState,useLocalStorage } from '@vueuse/core'
|
||||
import { WebSocketClient } from '@/utils/websocket'
|
||||
import {message} from "~/components/x-message/useMessage.js";
|
||||
|
||||
export const authStore = createGlobalState(() => {
|
||||
const token=useLocalStorage('token','')
|
||||
const RefreshToken=useLocalStorage('RefreshToken','')
|
||||
@ -15,9 +12,8 @@ export const authStore = createGlobalState(() => {
|
||||
buyUid:'',
|
||||
auctionArtworkUuid:''
|
||||
})
|
||||
const payUid=useLocalStorage('payUid','')
|
||||
|
||||
return{
|
||||
payUid,
|
||||
selectedZone,
|
||||
payment,
|
||||
checkoutSessionUrl,
|
||||
|
@ -34,7 +34,8 @@ export const goodStore = createGlobalState(() => {
|
||||
auctionDetail.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
} finally {
|
||||
console.error('获取拍卖详情错误:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
@ -68,6 +69,7 @@ export const goodStore = createGlobalState(() => {
|
||||
}
|
||||
return { finished: true, items: [] }
|
||||
} catch (err) {
|
||||
console.error('获取艺术品列表错误:', err)
|
||||
return { finished: true, items: [] }
|
||||
} finally {
|
||||
loading.value = false
|
||||
@ -83,7 +85,8 @@ export const goodStore = createGlobalState(() => {
|
||||
artWorkDetail.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
} finally {
|
||||
console.error('获取艺术品详情错误:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
@ -197,10 +197,12 @@ export const liveStore = createGlobalState(() => {
|
||||
|
||||
// WebSocket 事件处理
|
||||
ws.onOpen(() => {
|
||||
})
|
||||
console.log('WebSocket connected')
|
||||
})
|
||||
|
||||
ws.onMessage((data) => {
|
||||
auctionData.value = data.data
|
||||
console.log(' auctionData.value', auctionData.value)
|
||||
const { wsType, tip } = data.data || {}
|
||||
|
||||
switch (wsType) {
|
||||
@ -208,11 +210,13 @@ export const liveStore = createGlobalState(() => {
|
||||
handleTipMessage(tip?.tipType)
|
||||
break
|
||||
case WS_TYPES.STOP_ARTWORK:
|
||||
console.log('changeQuote',quoteStatus.value)
|
||||
//quoteStatus.value = false
|
||||
break
|
||||
case WS_TYPES.OVER:
|
||||
|
||||
quoteStatus.value = false
|
||||
console.log('changeQuote',quoteStatus.value)
|
||||
message.success(createMessageConfig(
|
||||
t('live_room.text10'),
|
||||
'#575757',
|
||||
@ -226,13 +230,16 @@ export const liveStore = createGlobalState(() => {
|
||||
break
|
||||
}
|
||||
|
||||
})
|
||||
console.log('onmessage', data)
|
||||
})
|
||||
|
||||
ws.onClose(() => {
|
||||
})
|
||||
console.log('WebSocket disconnected')
|
||||
})
|
||||
|
||||
ws.onError((error) => {
|
||||
})
|
||||
console.error('WebSocket error:', error)
|
||||
})
|
||||
}
|
||||
const changeStatus = () => {
|
||||
if (auctionData.value.artwork?.isSelling&&!auctionData.value.artwork.isSoled){
|
||||
@ -242,7 +249,8 @@ export const liveStore = createGlobalState(() => {
|
||||
quoteStatus.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('changeQuote',quoteStatus.value)
|
||||
}
|
||||
return{
|
||||
fullLive,
|
||||
isMinWindow,
|
||||
|
44
app/utils/format.js
Normal file
44
app/utils/format.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 格式化价格
|
||||
* @param {number} price - 价格数值
|
||||
* @param {string} currency - 货币符号,默认为 ¥
|
||||
* @returns {string} 格式化后的价格字符串
|
||||
*/
|
||||
export const formatPrice = (price, currency = '¥') => {
|
||||
if (price == null || isNaN(price)) return `${currency}0`
|
||||
|
||||
// 将价格转换为数字
|
||||
const numPrice = Number(price)
|
||||
|
||||
// 处理小数点,保留两位小数
|
||||
const formattedPrice = numPrice.toFixed(2)
|
||||
|
||||
// 添加千位分隔符
|
||||
const parts = formattedPrice.toString().split('.')
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
|
||||
return `${currency}${parts.join('.')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字
|
||||
* @param {number} num - 需要格式化的数字
|
||||
* @returns {string} 格式化后的数字字符串
|
||||
*/
|
||||
export const formatNumber = (num) => {
|
||||
if (num == null || isNaN(num)) return '0'
|
||||
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化百分比
|
||||
* @param {number} value - 需要格式化的值
|
||||
* @param {number} decimals - 小数位数,默认为 2
|
||||
* @returns {string} 格式化后的百分比字符串
|
||||
*/
|
||||
export const formatPercent = (value, decimals = 2) => {
|
||||
if (value == null || isNaN(value)) return '0%'
|
||||
|
||||
return `${(Number(value) * 100).toFixed(decimals)}%`
|
||||
}
|
10
app/utils/preload.ts
Normal file
10
app/utils/preload.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default function preload() {
|
||||
return `
|
||||
;(function() {
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const setting = localStorage.getItem('nuxt-color-mode') || 'auto';
|
||||
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
||||
document.documentElement.classList.toggle('van-theme-dark', true);
|
||||
})()
|
||||
`
|
||||
}
|
5
env/.env.prod
vendored
5
env/.env.prod
vendored
@ -1,5 +1,4 @@
|
||||
# 生产环境配置
|
||||
NUXT_PUBLIC_API_BASE=https://auction.szjixun.cn
|
||||
NUXT_PUBLIC_SOCKET_URL=wss://auction.szjixun.cn
|
||||
NUXT_PUBLIC_API_BASE=https://auction.yixunlink.com
|
||||
NUXT_PUBLIC_SOCKET_URL=wss://auction.yixunlink.com
|
||||
NUXT_API_SECRET=prod-secret
|
||||
NUXT_PUBLIC_PKEY=pk_live_51QfbSAAB1Vm8VfJqEVY2uFHPn9N4sDbOaCzht8IVKoylYBrYvdUsmsnCzGxIoN9skBCvI5PsxLJcf4PlytXIr1aX00mFJBXSB8
|
3
env/.env.test
vendored
3
env/.env.test
vendored
@ -2,5 +2,4 @@
|
||||
NUXT_PUBLIC_API_BASE=https://auction-test.szjixun.cn
|
||||
NUXT_PUBLIC_API_COLLECT_CODE=http://auction-test.szjixun.cn
|
||||
NUXT_API_SECRET=test-secret
|
||||
NUXT_PUBLIC_SOCKET_URL=wss://auction-test.szjixun.cn
|
||||
NUXT_PUBLIC_PKEY=pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV
|
||||
NUXT_PUBLIC_SOCKET_URL=wss://auction-test.szjixun.cn
|
@ -133,8 +133,8 @@
|
||||
"otherTab": "Non-Mainland Residents",
|
||||
"cnTabDesc": "Please fill in ID card information",
|
||||
"otherTabDesc": "Please upload personal information",
|
||||
"idCard": "ID number",
|
||||
"idCardPlaceholder": "Please enter your ID number",
|
||||
"idCard": "ID Card Number",
|
||||
"idCardPlaceholder": "Please enter ID card number",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Please enter your name",
|
||||
"gender": "Gender",
|
||||
@ -157,13 +157,7 @@
|
||||
"male": "Male",
|
||||
"female": "Female"
|
||||
}
|
||||
},
|
||||
"idTye": "Document Type",
|
||||
"passport": "passport",
|
||||
"other": "other",
|
||||
"idNumber": "ID number",
|
||||
"idNumberPlaceholder": "Please enter your ID number",
|
||||
"idTypeString": "ID card"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"text1": "Artist",
|
||||
@ -191,10 +185,7 @@
|
||||
"my_lots": "My Lots",
|
||||
"go_home": "Go to Home",
|
||||
"text1": "Click to enter live room",
|
||||
"text2": "Beijing Time",
|
||||
"text3": "This auction",
|
||||
"text4": "Not started",
|
||||
"text5": "Ended"
|
||||
"text2": "Beijing Time"
|
||||
},
|
||||
"live_room": {
|
||||
"error_mess": "Failed to get live content, retry?",
|
||||
@ -269,9 +260,7 @@
|
||||
"placeholder": {
|
||||
"amount": "Maximum {currency}{price}"
|
||||
},
|
||||
"amount": "Payment Amount",
|
||||
"amountRequired": "Please enter the amount",
|
||||
"exceedAmount": "Can't be greater than the total amount"
|
||||
"amount": "Payment Amount"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
@ -297,8 +286,7 @@
|
||||
"transfer": "Auction Transfer Confirmation"
|
||||
},
|
||||
"error": {
|
||||
"getRecord": "Failed to get auction record",
|
||||
"incompleteForm": "Please fill in the complete information"
|
||||
"getRecord": "Failed to get auction record"
|
||||
},
|
||||
"action": {
|
||||
"agree": "Agree and Sign",
|
||||
@ -423,7 +411,7 @@
|
||||
"fullPayment": "Pay in Full",
|
||||
"partialPayment": "Partial Payment",
|
||||
"confirmPayment": "Confirm Payment",
|
||||
"maxAmount": "most",
|
||||
"maxAmount": "Maximum {currency}{price}",
|
||||
"enterAmount": "Please enter amount",
|
||||
"exceedTotal": "Cannot exceed total amount"
|
||||
},
|
||||
@ -438,8 +426,7 @@
|
||||
"message": {
|
||||
"amountRequired": "Please enter amount",
|
||||
"lotNoRequired": "Please enter Lot No.",
|
||||
"deleteSuccess": "Delete successful",
|
||||
"lotNoType": "Lot number format error"
|
||||
"deleteSuccess": "Delete successful"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
|
@ -133,8 +133,8 @@
|
||||
"otherTab": "中国本土以外の居住者",
|
||||
"cnTabDesc": "身分証明書の情報を入力してください",
|
||||
"otherTabDesc": "個人情報をアップロードしてください",
|
||||
"idCard": "ID番号",
|
||||
"idCardPlaceholder": "ID番号を入力してください",
|
||||
"idCard": "身分証明書番号",
|
||||
"idCardPlaceholder": "身分証明書番号を入力してください",
|
||||
"name": "名前",
|
||||
"namePlaceholder": "名前を入力してください",
|
||||
"gender": "性別",
|
||||
@ -157,13 +157,7 @@
|
||||
"male": "男性",
|
||||
"female": "女性"
|
||||
}
|
||||
},
|
||||
"idTye": "ドキュメントタイプ",
|
||||
"passport": "パスポート",
|
||||
"other": "他の",
|
||||
"idNumber": "ID番号",
|
||||
"idNumberPlaceholder": "ID番号を入力してください",
|
||||
"idTypeString": "IDカード"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"text1": "アーティスト",
|
||||
@ -191,10 +185,7 @@
|
||||
"my_lots": "マイ商品",
|
||||
"go_home": "ホームへ",
|
||||
"text1": "クリックしてライブルームに入る",
|
||||
"text2": "北京時間",
|
||||
"text3": "このオークション",
|
||||
"text4": "開始していません",
|
||||
"text5": "終了しました"
|
||||
"text2": "北京時間"
|
||||
},
|
||||
"live_room": {
|
||||
"error_mess": "ライブコンテンツの取得に失敗しました。再試行しますか?",
|
||||
@ -269,9 +260,7 @@
|
||||
"placeholder": {
|
||||
"amount": "最大 {currency}{price}"
|
||||
},
|
||||
"amount": "支払い金額",
|
||||
"amountRequired": "金額を入力してください",
|
||||
"exceedAmount": "総額よりも大きくすることはできません"
|
||||
"amount": "支払い金額"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
@ -297,8 +286,7 @@
|
||||
"transfer": "オークション譲渡確認"
|
||||
},
|
||||
"error": {
|
||||
"getRecord": "オークション記録の取得に失敗しました",
|
||||
"incompleteForm": "完全な情報を入力してください"
|
||||
"getRecord": "オークション記録の取得に失敗しました"
|
||||
},
|
||||
"action": {
|
||||
"agree": "同意して署名",
|
||||
@ -423,7 +411,7 @@
|
||||
"fullPayment": "全額支払い",
|
||||
"partialPayment": "一部支払い",
|
||||
"confirmPayment": "支払い確認",
|
||||
"maxAmount": "ほとんど",
|
||||
"maxAmount": "最大 {currency}{price}",
|
||||
"enterAmount": "金額を入力してください",
|
||||
"exceedTotal": "合計金額を超えることはできません"
|
||||
},
|
||||
@ -438,8 +426,7 @@
|
||||
"message": {
|
||||
"amountRequired": "金額を入力してください",
|
||||
"lotNoRequired": "ロット番号を入力してください",
|
||||
"deleteSuccess": "削除成功",
|
||||
"lotNoType": "ロット番号形式エラー"
|
||||
"deleteSuccess": "削除成功"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
@ -672,4 +659,4 @@
|
||||
"httpNotInitialized": "HTTPクライアントが初期化されていません。先にsetupHttpを呼び出してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -133,8 +133,8 @@
|
||||
"otherTab": "非大陆居民",
|
||||
"cnTabDesc": "请填写身份证相关信息",
|
||||
"otherTabDesc": "请上传个人相关信息",
|
||||
"idCard": "证件号",
|
||||
"idCardPlaceholder": "请输入证件号",
|
||||
"idCard": "身份证号",
|
||||
"idCardPlaceholder": "请输入身份证号",
|
||||
"name": "姓名",
|
||||
"namePlaceholder": "请输入姓名",
|
||||
"gender": "性别",
|
||||
@ -157,13 +157,7 @@
|
||||
"male": "男",
|
||||
"female": "女"
|
||||
}
|
||||
},
|
||||
"idTye": "证件类型",
|
||||
"passport": "护照",
|
||||
"other": "其他",
|
||||
"idNumber": "证件号",
|
||||
"idNumberPlaceholder": "请输入证件号",
|
||||
"idTypeString": "身份证"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"text1": "作者",
|
||||
@ -176,7 +170,7 @@
|
||||
"text8": "拍卖品详情"
|
||||
},
|
||||
"art_detail_page": {
|
||||
"button": "去支付",
|
||||
"button": "去支付",
|
||||
"prompt_title": "恭喜您",
|
||||
"prompt_desc": "竞拍成功"
|
||||
},
|
||||
@ -191,10 +185,7 @@
|
||||
"my_lots": "我的拍品",
|
||||
"go_home": "去首页",
|
||||
"text1": "点击进入直播间",
|
||||
"text2": "北京时间",
|
||||
"text3": "本场拍卖会",
|
||||
"text4": "未开始",
|
||||
"text5": "已结束"
|
||||
"text2": "北京时间"
|
||||
},
|
||||
"live_room": {
|
||||
"error_mess": "直播内容获取失败,是否重新获取",
|
||||
@ -205,7 +196,7 @@
|
||||
"confirm": "确认出价",
|
||||
"button": "点击'开启出价',即刻参与竞拍",
|
||||
"start": "开始拍卖",
|
||||
"head": "领先",
|
||||
"head":"领先",
|
||||
"out": "出局",
|
||||
"success": "成交",
|
||||
"next_lot": "即将开始下一个拍品",
|
||||
@ -234,7 +225,7 @@
|
||||
},
|
||||
"personal": {
|
||||
"title": "请填写个人相关信息",
|
||||
"text": "文本",
|
||||
"text":"文本",
|
||||
"next": "下一步"
|
||||
},
|
||||
"payment": {
|
||||
@ -245,8 +236,7 @@
|
||||
"fail": "支付失败",
|
||||
"unpaid": "未支付",
|
||||
"expired": "支付已过期",
|
||||
"partial": "部分支付",
|
||||
"backToHome": "回到首页"
|
||||
"partial": "部分支付"
|
||||
},
|
||||
"text1": "支付结果",
|
||||
"text2": "支付成功",
|
||||
@ -270,9 +260,7 @@
|
||||
"placeholder": {
|
||||
"amount": "最多"
|
||||
},
|
||||
"amount": "支付金额",
|
||||
"amountRequired": "请输入金额",
|
||||
"exceedAmount": "不能大于全部金额"
|
||||
"amount": "支付金额"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
@ -298,8 +286,7 @@
|
||||
"transfer": "《拍卖移交确认书》"
|
||||
},
|
||||
"error": {
|
||||
"getRecord": "获取拍卖笔录失败",
|
||||
"incompleteForm": "请填写完整信息"
|
||||
"getRecord": "获取拍卖笔录失败"
|
||||
},
|
||||
"action": {
|
||||
"agree": "同意并签字",
|
||||
@ -426,8 +413,7 @@
|
||||
"confirmPayment": "确认支付",
|
||||
"text1": "最多",
|
||||
"enterAmount": "请输入金额",
|
||||
"exceedTotal": "不得高于全部金额",
|
||||
"maxAmount": "最多"
|
||||
"exceedTotal": "不得高于全部金额"
|
||||
},
|
||||
"signature": {
|
||||
"resultText": "领取您的专属号牌",
|
||||
@ -441,8 +427,7 @@
|
||||
"message": {
|
||||
"amountRequired": "请输入金额",
|
||||
"lotNoRequired": "请输入Lot号",
|
||||
"deleteSuccess": "删除成功",
|
||||
"lotNoType": "Lot号格式错误"
|
||||
"deleteSuccess": "删除成功"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
@ -675,4 +660,4 @@
|
||||
"httpNotInitialized": "HTTP客户端未初始化,请先调用setupHttp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -184,8 +184,8 @@
|
||||
"otherTab": "非大陸居民",
|
||||
"cnTabDesc": "請填寫身份證相關信息",
|
||||
"otherTabDesc": "請上傳個人相關信息",
|
||||
"idCard": "證件號",
|
||||
"idCardPlaceholder": "請輸入證件號",
|
||||
"idCard": "身份證號",
|
||||
"idCardPlaceholder": "請輸入身份證號",
|
||||
"name": "姓名",
|
||||
"namePlaceholder": "請輸入姓名",
|
||||
"gender": "性別",
|
||||
@ -208,13 +208,7 @@
|
||||
"male": "男",
|
||||
"female": "女"
|
||||
}
|
||||
},
|
||||
"idTye": "證件類型",
|
||||
"passport": "護照",
|
||||
"other": "其他",
|
||||
"idNumber": "證件號",
|
||||
"idNumberPlaceholder": "請輸入證件號",
|
||||
"idTypeString": "身份證"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"text1": "作者",
|
||||
@ -242,10 +236,7 @@
|
||||
"my_lots": "我的拍品",
|
||||
"go_home": "去首頁",
|
||||
"text1": "點擊進入直播間",
|
||||
"text2": "北京時間",
|
||||
"text3": "本場拍賣會",
|
||||
"text4": "未開始",
|
||||
"text5": "已結束"
|
||||
"text2": "北京時間"
|
||||
},
|
||||
"live_room": {
|
||||
"error_mess": "直播內容獲取失敗,是否重新獲取",
|
||||
@ -320,9 +311,7 @@
|
||||
"placeholder": {
|
||||
"amount": "最多{currency}{price}"
|
||||
},
|
||||
"amount": "支付金額",
|
||||
"amountRequired": "請輸入金額",
|
||||
"exceedAmount": "不能大於全部金額"
|
||||
"amount": "支付金額"
|
||||
},
|
||||
"signature": {
|
||||
"protocol": {
|
||||
@ -348,8 +337,7 @@
|
||||
"transfer": "《拍賣移交確認書》"
|
||||
},
|
||||
"error": {
|
||||
"getRecord": "獲取拍賣筆錄失敗",
|
||||
"incompleteForm": "請填寫完整信息"
|
||||
"getRecord": "獲取拍賣筆錄失敗"
|
||||
},
|
||||
"action": {
|
||||
"agree": "同意並簽字",
|
||||
@ -423,7 +411,7 @@
|
||||
"fullPayment": "支付全部",
|
||||
"partialPayment": "支付部分",
|
||||
"confirmPayment": "確認支付",
|
||||
"maxAmount": "最多",
|
||||
"maxAmount": "最多{currency}{price}",
|
||||
"enterAmount": "請輸入金額",
|
||||
"exceedTotal": "不得高於全部金額"
|
||||
},
|
||||
@ -438,8 +426,7 @@
|
||||
"message": {
|
||||
"amountRequired": "請輸入金額",
|
||||
"lotNoRequired": "請輸入Lot號",
|
||||
"deleteSuccess": "刪除成功",
|
||||
"lotNoType": "Lot號格式錯誤"
|
||||
"deleteSuccess": "刪除成功"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
@ -673,3 +660,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import dotenv from 'dotenv'
|
||||
import process from 'node:process'
|
||||
import preload from './app/utils/preload'
|
||||
import { currentLocales } from './i18n/i18n'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
const envFile = process.env.ENV_FILE || '.env.test'
|
||||
dotenv.config({ path: `./env/${envFile}` })
|
||||
const publicConfig = Object.entries(process.env)
|
||||
@ -12,19 +11,6 @@ const publicConfig = Object.entries(process.env)
|
||||
return config
|
||||
}, {})
|
||||
|
||||
let httpsOptions = {}
|
||||
|
||||
try {
|
||||
// 读取文件并转换为字符串
|
||||
const key = fs.readFileSync(path.resolve(__dirname, 'ssl/localhost-key.pem'), 'utf-8')
|
||||
const cert = fs.readFileSync(path.resolve(__dirname, 'ssl/localhost.pem'), 'utf-8')
|
||||
|
||||
httpsOptions = { key, cert }
|
||||
} catch (error) {
|
||||
// 失败时使用HTTP
|
||||
httpsOptions = false
|
||||
}
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@vant/nuxt',
|
||||
@ -50,17 +36,20 @@ export default defineNuxtConfig({
|
||||
postcss: {
|
||||
plugins: {
|
||||
'autoprefixer': {},
|
||||
'postcss-px-to-viewport': {
|
||||
viewportWidth: 375, // 设计稿宽度
|
||||
viewportUnit: 'vmin', // 关键配置
|
||||
fontViewportUnit: 'vmin', // 字体单位
|
||||
unitPrecision: 5,
|
||||
propList: ['*'],
|
||||
selectorBlackList: [],
|
||||
minPixelValue: 1,
|
||||
mediaQuery: false,
|
||||
exclude: /@nuxt/
|
||||
}
|
||||
|
||||
// https://github.com/wswmsword/postcss-mobile-forever
|
||||
'postcss-mobile-forever': {
|
||||
appSelector: '#__nuxt',
|
||||
viewportWidth: 375,
|
||||
// devtools excluded
|
||||
exclude: /@nuxt/,
|
||||
border: true,
|
||||
rootContainingBlockSelectorList: [
|
||||
'van-tabbar',
|
||||
'van-popup',
|
||||
'van-overlay',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
@ -68,10 +57,11 @@ export default defineNuxtConfig({
|
||||
lazy: true,
|
||||
strategy: 'no_prefix',
|
||||
detectBrowserLanguage: {
|
||||
useCookie: false,
|
||||
useCookie: true,
|
||||
cookieKey: 'i18n_redirected',
|
||||
redirectOn: 'root',
|
||||
alwaysRedirect: true,
|
||||
fallbackLocale: 'zh-CN'
|
||||
},
|
||||
defaultLocale: 'zh-CN',
|
||||
vueI18n: './i18n/i18n.config.ts',
|
||||
@ -87,13 +77,6 @@ export default defineNuxtConfig({
|
||||
link: [
|
||||
{ rel: 'icon', href: '/favicon.ico', sizes: 'any' },
|
||||
],
|
||||
// stripe支付CDN引用
|
||||
script: [
|
||||
{
|
||||
src: 'https://js.stripe.com/v3/',
|
||||
defer: true // 可选,建议添加 defer
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover,user-scalable=no' },
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
@ -101,6 +84,9 @@ export default defineNuxtConfig({
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: '#ffffff' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
||||
],
|
||||
script: [
|
||||
{ innerHTML: preload(), type: 'text/javascript', tagPosition: 'head' },
|
||||
],
|
||||
},
|
||||
},
|
||||
nitro: {
|
||||
@ -149,10 +135,9 @@ export default defineNuxtConfig({
|
||||
compatibilityVersion: 4,
|
||||
},
|
||||
// 指定 Nuxt 应用程序的兼容性日期,确保应用程序在未来的 Nuxt 版本中保持稳定性
|
||||
compatibilityDate: '2025-02-28',
|
||||
compatibilityDate: '2025-01-09',
|
||||
devServer: {
|
||||
// https: httpsOptions,
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
host: '0.0.0.0', // Set the host to 'localhost'
|
||||
port: 3000, // Set the port to 3000 or any other port you prefer
|
||||
},
|
||||
})
|
@ -23,6 +23,7 @@
|
||||
"@vueuse/core": "^12.4.0",
|
||||
"aliyun-aliplayer": "^2.28.5",
|
||||
"axios": "^1.7.9",
|
||||
"countup.js": "^2.8.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
@ -47,7 +48,7 @@
|
||||
"bumpp": "^9.9.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"ipx": "^3.0.1",
|
||||
"postcss-px-to-viewport": "^1.1.1",
|
||||
"postcss-mobile-forever": "^4.3.1",
|
||||
"sass": "^1.83.1",
|
||||
"sass-loader": "^16.0.4",
|
||||
"sharp": "^0.33.5",
|
||||
|
@ -29,6 +29,9 @@ importers:
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
countup.js:
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@ -96,9 +99,9 @@ importers:
|
||||
ipx:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1(db0@0.2.4)(ioredis@5.5.0)
|
||||
postcss-px-to-viewport:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
postcss-mobile-forever:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.2(postcss@8.5.2)
|
||||
sass:
|
||||
specifier: ^1.83.1
|
||||
version: 1.85.0
|
||||
@ -2096,6 +2099,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
countup.js@2.8.0:
|
||||
resolution: {integrity: sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==}
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
@ -3604,6 +3610,11 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.4.31
|
||||
|
||||
postcss-mobile-forever@4.3.2:
|
||||
resolution: {integrity: sha512-l1YuvxouJ7wt2awKtomuTDKXkNlFcUvC62sVUD9+Gr2AdyZRTtP81izo/IHfS2fbbFCY5JzvuTGmAJ2SyEqv+w==}
|
||||
peerDependencies:
|
||||
postcss: ^8.0.0
|
||||
|
||||
postcss-normalize-charset@7.0.0:
|
||||
resolution: {integrity: sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==}
|
||||
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
|
||||
@ -3664,9 +3675,6 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.4.31
|
||||
|
||||
postcss-px-to-viewport@1.1.1:
|
||||
resolution: {integrity: sha512-2x9oGnBms+e0cYtBJOZdlwrFg/mLR4P1g2IFu7jYKvnqnH/HLhoKyareW2Q/x4sg0BgklHlP1qeWo2oCyPm8FQ==}
|
||||
|
||||
postcss-reduce-initial@7.0.2:
|
||||
resolution: {integrity: sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==}
|
||||
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
|
||||
@ -7009,6 +7017,8 @@ snapshots:
|
||||
typescript: 5.7.3
|
||||
optional: true
|
||||
|
||||
countup.js@2.8.0: {}
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
crc32-stream@6.0.0:
|
||||
@ -8747,6 +8757,10 @@ snapshots:
|
||||
postcss: 8.5.2
|
||||
postcss-selector-parser: 6.1.2
|
||||
|
||||
postcss-mobile-forever@4.3.2(postcss@8.5.2):
|
||||
dependencies:
|
||||
postcss: 8.5.2
|
||||
|
||||
postcss-normalize-charset@7.0.0(postcss@8.5.2):
|
||||
dependencies:
|
||||
postcss: 8.5.2
|
||||
@ -8798,11 +8812,6 @@ snapshots:
|
||||
postcss: 8.5.2
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-px-to-viewport@1.1.1:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
postcss: 8.5.2
|
||||
|
||||
postcss-reduce-initial@7.0.2(postcss@8.5.2):
|
||||
dependencies:
|
||||
browserslist: 4.24.4
|
||||
|
@ -1,242 +0,0 @@
|
||||
/* Variables */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
color: rgb(105, 115, 134);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#payment-element {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Buttons and links */
|
||||
button {
|
||||
background: #0055DE;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(115%);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* spinner/processing state, errors */
|
||||
.spinner,
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
text-indent: -99999px;
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.spinner:before,
|
||||
.spinner:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
background: #0055DE;
|
||||
border-radius: 20.4px 0 0 20.4px;
|
||||
top: -0.2px;
|
||||
left: -0.2px;
|
||||
-webkit-transform-origin: 10.4px 10.2px;
|
||||
transform-origin: 10.4px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease 1.5s;
|
||||
animation: loading 2s infinite ease 1.5s;
|
||||
}
|
||||
.spinner:after {
|
||||
width: 10.4px;
|
||||
height: 10.2px;
|
||||
background: #0055DE;
|
||||
border-radius: 0 10.2px 10.2px 0;
|
||||
top: -0.1px;
|
||||
left: 10.2px;
|
||||
-webkit-transform-origin: 0px 10.2px;
|
||||
transform-origin: 0px 10.2px;
|
||||
-webkit-animation: loading 2s infinite ease;
|
||||
animation: loading 2s infinite ease;
|
||||
}
|
||||
|
||||
/* Payment status page */
|
||||
#payment-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
width: 30vw;
|
||||
min-width: 500px;
|
||||
min-height: 380px;
|
||||
align-self: center;
|
||||
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);
|
||||
border-radius: 7px;
|
||||
padding: 40px;
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 1s ease forwards;
|
||||
}
|
||||
|
||||
#status-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #30313D;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: Arial, sans-serif;
|
||||
display: block;
|
||||
}
|
||||
a:hover {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
|
||||
#details-table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tbody tr:first-child td {
|
||||
border-top: 1px solid #E6E6E6; /* Top border */
|
||||
padding-top: 10px;
|
||||
}
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid #E6E6E6; /* Bottom border */
|
||||
}
|
||||
td {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.TableContent {
|
||||
text-align: right;
|
||||
color: #6D6E78;
|
||||
}
|
||||
|
||||
.TableLabel {
|
||||
font-weight: 600;
|
||||
color: #30313D;
|
||||
}
|
||||
|
||||
#view-details {
|
||||
color: #0055DE;
|
||||
}
|
||||
|
||||
#retry-button {
|
||||
text-align: center;
|
||||
background: #0055DE;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
form, #payment-status{
|
||||
width: 80vw;
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Accept a payment</title>
|
||||
<meta name="description" content="A demo of a payment on Stripe" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="checkout.css" />
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="checkout.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Display a payment form -->
|
||||
<form id="payment-form">
|
||||
<div id="payment-element">
|
||||
<!--Stripe.js injects the Payment Element-->
|
||||
</div>
|
||||
<button id="submit">
|
||||
<div class="spinner hidden" id="spinner"></div>
|
||||
<span id="button-text">Pay now</span>
|
||||
</button>
|
||||
<div id="payment-message" class="hidden"></div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
@ -1,89 +0,0 @@
|
||||
// This is your test publishable API key.
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV");
|
||||
|
||||
// The items the customer wants to buy
|
||||
const items = [{ id: "xl-tshirt", amount: 1000 }];
|
||||
|
||||
let elements;
|
||||
|
||||
initialize();
|
||||
|
||||
document
|
||||
.querySelector("#payment-form")
|
||||
.addEventListener("submit", handleSubmit);
|
||||
|
||||
// Fetches a payment intent and captures the client secret
|
||||
async function initialize() {
|
||||
// const response = await fetch("/create-payment-intent", {
|
||||
// method: "POST",
|
||||
// headers: { "Content-Type": "application/json" },
|
||||
// body: JSON.stringify({ items }),
|
||||
// });
|
||||
// const { clientSecret } = await response.json();
|
||||
const clientSecret='pi_3QxII1AB1Vm8VfJq1OyR3bkz_secret_d8fgL53X6T3MQpYfi2lRH3V1F'
|
||||
const appearance = {
|
||||
theme: 'stripe',
|
||||
};
|
||||
elements = stripe.elements({ appearance, clientSecret });
|
||||
|
||||
const paymentElementOptions = {
|
||||
layout: "accordion",
|
||||
};
|
||||
|
||||
const paymentElement = elements.create("payment", paymentElementOptions);
|
||||
paymentElement.mount("#payment-element");
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
// Make sure to change this to your payment completion page
|
||||
return_url: "http://localhost:4242/complete.html",
|
||||
},
|
||||
});
|
||||
|
||||
// This point will only be reached if there is an immediate error when
|
||||
// confirming the payment. Otherwise, your customer will be redirected to
|
||||
// your `return_url`. For some payment methods like iDEAL, your customer will
|
||||
// be redirected to an intermediate site first to authorize the payment, then
|
||||
// redirected to the `return_url`.
|
||||
if (error.type === "card_error" || error.type === "validation_error") {
|
||||
showMessage(error.message);
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.");
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// ------- UI helpers -------
|
||||
|
||||
function showMessage(messageText) {
|
||||
const messageContainer = document.querySelector("#payment-message");
|
||||
|
||||
messageContainer.classList.remove("hidden");
|
||||
messageContainer.textContent = messageText;
|
||||
|
||||
setTimeout(function () {
|
||||
messageContainer.classList.add("hidden");
|
||||
messageContainer.textContent = "";
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
// Show a spinner on payment submission
|
||||
function setLoading(isLoading) {
|
||||
if (isLoading) {
|
||||
// Disable the button and show a spinner
|
||||
document.querySelector("#submit").disabled = true;
|
||||
document.querySelector("#spinner").classList.remove("hidden");
|
||||
document.querySelector("#button-text").classList.add("hidden");
|
||||
} else {
|
||||
document.querySelector("#submit").disabled = false;
|
||||
document.querySelector("#spinner").classList.add("hidden");
|
||||
document.querySelector("#button-text").classList.remove("hidden");
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Order Status</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="checkout.css" />
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="complete.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Display the order status -->
|
||||
<div id="payment-status">
|
||||
<div id="status-icon"></div>
|
||||
<h2 id="status-text"></h2>
|
||||
<div id="details-table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="TableLabel">id</td>
|
||||
<td id="intent-id" class="TableContent"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="TableLabel">status</td>
|
||||
<td id="intent-status" class="TableContent"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="#" id="view-details" rel="noopener noreferrer" target="_blank">View details
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/> <path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/></svg>
|
||||
</a>
|
||||
<a id="retry-button" href="/checkout.html">Test another</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,85 +0,0 @@
|
||||
// ------- UI Resources -------
|
||||
const SuccessIcon =
|
||||
`<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
|
||||
</svg>`;
|
||||
|
||||
const ErrorIcon =
|
||||
`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
|
||||
</svg>`;
|
||||
|
||||
const InfoIcon =
|
||||
`<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
|
||||
<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
|
||||
</svg>`;
|
||||
|
||||
// ------- UI helpers -------
|
||||
function setPaymentDetails(intent) {
|
||||
let statusText = "Something went wrong, please try again.";
|
||||
let iconColor = "#DF1B41";
|
||||
let icon = ErrorIcon;
|
||||
|
||||
|
||||
if (!intent) {
|
||||
setErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (intent.status) {
|
||||
case "succeeded":
|
||||
statusText = "Payment succeeded";
|
||||
iconColor = "#30B130";
|
||||
icon = SuccessIcon;
|
||||
break;
|
||||
case "processing":
|
||||
statusText = "Your payment is processing.";
|
||||
iconColor = "#6D6E78";
|
||||
icon = InfoIcon;
|
||||
break;
|
||||
case "requires_payment_method":
|
||||
statusText = "Your payment was not successful, please try again.";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
document.querySelector("#status-icon").style.backgroundColor = iconColor;
|
||||
document.querySelector("#status-icon").innerHTML = icon;
|
||||
document.querySelector("#status-text").textContent= statusText;
|
||||
document.querySelector("#intent-id").textContent = intent.id;
|
||||
document.querySelector("#intent-status").textContent = intent.status;
|
||||
document.querySelector("#view-details").href = `https://dashboard.stripe.com/payments/${intent.id}`;
|
||||
}
|
||||
|
||||
function setErrorState() {
|
||||
document.querySelector("#status-icon").style.backgroundColor = "#DF1B41";
|
||||
document.querySelector("#status-icon").innerHTML = ErrorIcon;
|
||||
document.querySelector("#status-text").textContent= "Something went wrong, please try again.";
|
||||
document.querySelector("#details-table").classList.add("hidden");
|
||||
document.querySelector("#view-details").classList.add("hidden");
|
||||
}
|
||||
|
||||
// Stripe.js instance
|
||||
const stripe = Stripe("pk_test_51QfbSAAB1Vm8VfJq3AWsR4k2mZjnlF7XFrmlbc6XVXrtwXquAUfwzZmOFDbxMIAwqJBgqao8KLt2wmPc4vNOCTeo00WB78KtfV");
|
||||
|
||||
checkStatus();
|
||||
|
||||
// Fetches the payment intent status after payment submission
|
||||
async function checkStatus() {
|
||||
const clientSecret = new URLSearchParams(window.location.search).get(
|
||||
"payment_intent_client_secret"
|
||||
);
|
||||
|
||||
if (!clientSecret) {
|
||||
setErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
|
||||
|
||||
setPaymentDetails(paymentIntent);
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const url = getRequestURL(event)
|
||||
|
||||
// 只处理 create-payment-intent 请求
|
||||
if (url.pathname === '/create-payment-intent' && event.method === 'POST') {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { items } = body
|
||||
|
||||
// 计算总金额
|
||||
const amount = items.reduce((total: number, item: any) => total + item.amount, 0)
|
||||
|
||||
// 模拟创建支付意向的响应
|
||||
// 注意:clientSecret 格式应该类似于 'pi_xxxxx_secret_xxxxx'
|
||||
return {
|
||||
clientSecret: `pi_${Math.random().toString(36).substring(2)}_secret_${Math.random().toString(36).substring(2)}`
|
||||
}
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '创建支付意向失败'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
3
server/tsconfig.json
Normal file
3
server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFtE2AWs1o0X/A
|
||||
0+506WAG49xbO3KJQTWKwHl5pfeP0GieVjV3qKXkHXl9Fyhg9oJnCAz2nzxzavAP
|
||||
W9NevDbX7KdEiZQjDhpgO+V9IGUqqJKEQnpVk57G3fHBX40nQ4zAQ/S1Vj4Aw9Tk
|
||||
iqNFgLRX3HTAT0NO3eXENJbsDdGxtOuLn5h+DWThrqCaUK2Xnkee3eG/VwgKJKVH
|
||||
LWuC8xyb1NydtchaRFBjoO9oT6Tb/MGeBOrNH9e7Ndh9mCFepXUhzNxrN1fKjn9s
|
||||
107pSEeZb4il2K7GqE0W6r6Y3eeBR2+mEBRqeFnZObT0JkBLI2QIrAwOnFO6ObQC
|
||||
ZaEjwHcZAgMBAAECggEASMZB8Ql7qyXS3OwmTqrJSj/+ESck1hlG2DhZfsn1At84
|
||||
Y3BgZheSWRHwcndfybFz9vEjtHSRD/tBOqYWfDzUA099kuEBwpWiZ+Ika5bNJpK+
|
||||
vCisV2vrelCgeQnvL5DR8sQRA98nG6j6aNYPm7nwqJbh8xg6MoHD3iFtnJ7JnZvQ
|
||||
pSa6Z9qq4Po+cp63/U3yEzFeiVVDTMQJMVClANUCX3jLHs8B85WMbb1eKKFe/xCA
|
||||
n2BWlFVI7Hld+hxhKWkc71+kafC5hUz1w88FcBaN2W/DAtJgKC0dHYATwLCUGC4Z
|
||||
CoCZfB7b3JOzK4mGJ/XxxUcBUk+oweExOrYwCfW4MQKBgQDJbTM4qgW2ca/Xc7cp
|
||||
zKvclgtkJ8rWbVZqMYW6fpXoOdhhxjJSx+LfeGk1whz3Xj04EdSZbRI6zYVaHfHa
|
||||
HFkA2Na/Wi9hboid4WVXUi5RXzthVTOYi1jAJNmK4R25wuSdcGVoYxSrSLyDcHbx
|
||||
MF2cFdQ4A386L+RcoDYzImWCXwKBgQD7RO4DZJgkqDng6YFrYqIdQkJn6keXl4yW
|
||||
Hq1FGaa7XDjlun/X2jT0xJJPFcwLQLbWwrkwmUYN/VQEbYwYlyB0MegF9VflfILl
|
||||
/leCXC8/9WEknQkPqu8N1JiYhajIKLxfQX35nW/oK+S5prJOBxNw+3Of/S43R5Xe
|
||||
60EEI9iphwKBgQCrs/Sn5vd7sKnOpYuLjDcskJMhS3JzGz1AxPpUIbgz/6tenY8k
|
||||
VdQl3wUAmHoMvD6/XyO1re6ORcfZLBGQdf3A5RcagwxEp+65dvvmVd256844iGK1
|
||||
NIPxNvhilMe8JFCxjLBFLcDeyeA4w1QBAdOqTEldfk2kElM+SiwppraVTQKBgCUP
|
||||
O5OgiJgPf8neZsox1/s8xJKTCVAgeAnEKIYijGbh6Tpo0WZCtsDLJVEow9l9B/qQ
|
||||
6cNzN9PkYznr9lfCInVAzxnh377nKF9Hrhx6ADYMuPEvgCChc3S0wHTuccBj0bSy
|
||||
8iOYxuKVZrzDC1Va0dE+JQWZz/EzS7V/OS2lI9WNAoGBAKujoPDn36/hJ/Zr8XAM
|
||||
CEbOi0q0N7I37aRKO8Wm55SCGDYWtBlu+NiIMqk3gzgomtm/cVF+fUNv0BOKc+hx
|
||||
x6PQE98AEn5LdGeqLpDY66vhyR8WGUyCBPB+Dn8OFFT+njL2E8NcQi0kS3t/YlR/
|
||||
oobyxGhm4M1fM8HtGwqQJX60
|
||||
-----END PRIVATE KEY-----
|
@ -1,26 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEbjCCAtagAwIBAgIRAJjJaJNy+AO5JPuasTslJMQwDQYJKoZIhvcNAQELBQAw
|
||||
gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxREVT
|
||||
S1RPUC1DOTVCMVIzXDM3MzYzQERFU0tUT1AtQzk1QjFSMyAo6YKi6Zuo5p2oKTFB
|
||||
MD8GA1UEAww4bWtjZXJ0IERFU0tUT1AtQzk1QjFSM1wzNzM2M0BERVNLVE9QLUM5
|
||||
NUIxUjMgKOmCoumbqOadqCkwHhcNMjUwMzAyMDIzMzM3WhcNMjcwNjAyMDIzMzM3
|
||||
WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4
|
||||
BgNVBAsMMURFU0tUT1AtQzk1QjFSM1wzNzM2M0BERVNLVE9QLUM5NUIxUjMgKOmC
|
||||
oumbqOadqCkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFtE2AWs1o
|
||||
0X/A0+506WAG49xbO3KJQTWKwHl5pfeP0GieVjV3qKXkHXl9Fyhg9oJnCAz2nzxz
|
||||
avAPW9NevDbX7KdEiZQjDhpgO+V9IGUqqJKEQnpVk57G3fHBX40nQ4zAQ/S1Vj4A
|
||||
w9TkiqNFgLRX3HTAT0NO3eXENJbsDdGxtOuLn5h+DWThrqCaUK2Xnkee3eG/VwgK
|
||||
JKVHLWuC8xyb1NydtchaRFBjoO9oT6Tb/MGeBOrNH9e7Ndh9mCFepXUhzNxrN1fK
|
||||
jn9s107pSEeZb4il2K7GqE0W6r6Y3eeBR2+mEBRqeFnZObT0JkBLI2QIrAwOnFO6
|
||||
ObQCZaEjwHcZAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
|
||||
BgEFBQcDATAfBgNVHSMEGDAWgBQVJpbTujNcXUH/91CnxLerp/gKbDAUBgNVHREE
|
||||
DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBAERAae9YDQgjnVtDQUWL
|
||||
kIbMowvN6BospgO2srV+aXCDLbB22jnq4cGsTpVjxo80Nl6M0iSRz29K+jy4YFsL
|
||||
efTOeks1EpVQB/UnYuo391p5wzevXwa3s7dH7Oc+917y8JDiLNnSVEct+tk4zeOZ
|
||||
QbVzx6Gexiii7k1uSG/G1NYrRiXf3ggM93Fyu5NM+u8CzZvWm46ix9reYimVqfPa
|
||||
VjHsiQnmKbh+CD6iDWm9y1jUxqBay4cAbo2AVxIvBDdsC9KSCTsbP4hBPx9foy1U
|
||||
cLRxUGsWTVPPS2BmP8o6CSa2tNPeVNCWSP89tanY2mzGErfVXLV8t5E4awF0ea+a
|
||||
kbjyG3svVC6/rLo8LpFPonr4mQWfGcFntmGUC314d5z1ZCCS5ENEWAGZ3b3XzPsU
|
||||
Yh2QQnt4gtvWaTRqwqhSL9DLFp106/tok3hq8MyDFcxTxWKyDZsgaieoRGnF11EW
|
||||
tdIqnK9nwVOyAzaO603SuEoMiGBpb9nj/cAFsvm56YUVrg==
|
||||
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue
Block a user