Compare commits

...

11 Commits

Author SHA1 Message Date
xingyy
5fe9645cfb 123 2025-03-14 13:15:53 +08:00
xingyy
9402c8b719 refactor(app): 优化代码和性能
- 在 puzzleComponent 中添加 watch 监听 loading 状态,以重置验证状态
- 修改 personal-Info 页面中路由跳转方式
- 在 nuxt.config.js 中添加 imagemin 插件,优化图片压缩
- 更新 package.json,添加 vite-plugin-imagemin 依赖
2025-03-14 10:57:50 +08:00
xingyy
3b60502ae3 12 2025-03-14 10:08:45 +08:00
xingyy
ff90f4a3a1 1231 2025-03-14 10:07:39 +08:00
xingyy
f01d711d55 1231 2025-03-14 09:48:26 +08:00
xingyy
e57f758804 1231 2025-03-13 11:29:39 +08:00
xingyy
a378a45b19 1231 2025-03-13 11:19:50 +08:00
xingyy
ecf14e5eeb style: 统一拍品详情 2025-03-12 16:33:49 +08:00
xingyy
80efe786e0 12312 2025-03-12 15:57:31 +08:00
xingyy
b005d33dca 1231 2025-03-12 15:47:52 +08:00
xingyy
ffafe1358d refactor(login): 移除未使用的验证码组件
- 从 login 页面中移除了未使用的 Vcode 组件导入

refactor(publicLiveRoom): 优化拍卖数据获取逻辑

- 重构了 broadcast 组件中的数据获取逻辑
- 添加了定时器,实现每 10 秒自动刷新拍卖数据
-
2025-03-12 15:28:40 +08:00
13 changed files with 2384 additions and 97 deletions

View File

@ -1,7 +1,7 @@
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue'
//i18n
import { useI18n } from 'vue-i18n'
const {t} =useI18n()
@ -40,7 +40,15 @@
maxMoveX.value = img.width - blockSize
loaded.value = true
}
watch(()=>{
return props.loading
},(newVal)=>{
if(!newVal){
verifyStatus.show = false
verifyStatus.message = ''
moveX.value = 0
}
})
const onImageError = () => {
console.error('Image failed to load')
maxMoveX.value = 270
@ -84,11 +92,7 @@
verifyStatus.type = success ? 'success' : 'error'
verifyStatus.message = success ? t('components.form.verifySuccess') : t('components.form.verifyFailed')
isVerifying.value = false
setTimeout(() => {
verifyStatus.show = false
verifyStatus.message = ''
moveX.value = 0
}, 1500)
}
//

View File

@ -26,6 +26,11 @@ const columns1 = ref([
{text: t('realAuth.passport'), value: 2},
{text: t('realAuth.other'), value: 3},
])
/**
* 根据当前语言获取默认国家/地区信息
* @returns {{zone: string, name: string}} 返回国家区号和名称
*/
const getDefaultCountry = () => {
let defaultCode = 'CN' //
switch (locale.value) {
@ -53,11 +58,20 @@ const getDefaultCountry = () => {
}
}
const goCountryRegion=()=>{
/**
* 跳转到国家/地区选择页面
*/
const goCountryRegion = () => {
router.push({
path:'/countryRegion'
path: '/countryRegion'
})
}
/**
* 检查表单是否完整填写
* @param {Object} obj - 要检查的表单对象
* @returns {boolean} 是否完整
*/
function isFormComplete(obj) {
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
@ -71,15 +85,25 @@ function isFormComplete(obj) {
return true;
}
const getData=async ()=>{
const res=await offlineQrcode({
qrUid:qrUid.value
})
if (res.status===0){
qrData.value=res.data
/**
* 获取线下二维码支付状态
*/
const getData = async () => {
const res = await offlineQrcode({
qrUid: qrUid.value
})
if (res.status === 0) {
qrData.value = res.data
}
}
const initData= async ()=>{
/**
* 初始化页面数据
* 1. 处理URL参数
* 2. 检查支付状态(扫付款码场景)
* 3. 设置默认国家/地区信息
*/
const initData = async () => {
if (route.query.number){
number.value=Number(route.query.number)
}
@ -90,7 +114,7 @@ const initData= async ()=>{
if (number.value==2){
await getData()
if (qrData.value.payStatus===4){
router.replace('/collectCode/payment')
router.push('/collectCode/payment')
}
}
if(!formData.value.countryCode){
@ -101,7 +125,18 @@ const initData= async ()=>{
formData.value.countryCode=route.query.zone
}
}
const nextClick=async ()=>{
/**
* 处理下一步按钮点击
* 场景1(number=1): 扫号牌进入
* - 验证表单完整性
* - 检查用户号牌是否存在
* - 根据国家区号判断签署方式(国内用法大大,国外直接签字)
*
* 场景2(number=2): 扫付款码进入
* - 验证必填信息(手机区号用户名)
*/
const nextClick = async () => {
//
if (number.value==1){
if (!isFormComplete(formData.value)){

View File

@ -1,86 +1,139 @@
<script setup>
import pdfView from './pdfView/index.vue'
import { contractView } from "~/api/goods/index.js"
import {codeAuthStore} from "~/stores-collect-code/auth/index.js";
import {signOffline} from "~/api/goods/index.js";
import {useI18n} from "vue-i18n";
import { contractView, signOffline } from "~/api/goods/index.js"
import { codeAuthStore } from "~/stores-collect-code/auth/index.js"
import { useI18n } from "vue-i18n"
import { fddInfo } from "@/api-collect-code/goods/index.js"
import { showLoadingToast } from 'vant';
definePageMeta({
i18n: 'signature.protocol.title'
})
const {t} =useI18n()
const {formData,number,qrData}=codeAuthStore()
const activeNames = ref([])
const { t } = useI18n()
const { formData, number, qrData } = codeAuthStore()
const activeNames = ref('')
const router = useRouter()
const pmblUrl = ref('') // URL
const pmblUrl = ref('')
//
/**
* 根据签署顺序(number)返回不同的协议列表
* number = 1: 买家签署阶段展示竞买协议竞买须知拍卖公告拍卖规则
* number = 2: 卖家签署阶段展示拍卖成交确认书拍卖笔录
*/
const protocolList = computed(() => {
if(number.value==1){
return [
{ id: '4', title: t('signature.agreement.buyerAgreement'), pdfName: 'jmxy', type: 'local' },
{ id: '3', title: t('signature.agreement.buyerGuide'), pdfName: 'jmxz', type: 'local' },
{ id: '1', title: t('signature.agreement.notice'), pdfName: 'pmgg', type: 'local' },
{ id: '2', title: t('signature.agreement.rules'), pdfName: 'pmgz', type: 'local' },
]
}else if(number.value==2) {
return [
{ id: '6', title: t('signature.agreement.transfer'), pdfName: 'pmyjqrs', type: 'local' },
{ id: '5', title: t('signature.agreement.record'), pdfName: pmblUrl.value, type: 'remote' }
]
}
if (number.value === 1) {
return [
{ id: '4', title: t('signature.agreement.buyerAgreement'), pdfName: 'jmxy', type: 'local' },
{ id: '3', title: t('signature.agreement.buyerGuide'), pdfName: 'jmxz', type: 'local' },
{ id: '1', title: t('signature.agreement.notice'), pdfName: 'pmgg', type: 'local' },
{ id: '2', title: t('signature.agreement.rules'), pdfName: 'pmgz', type: 'local' },
]
} else if (number.value === 2) {
return [
{ id: '6', title: t('signature.agreement.transfer'), pdfName: 'pmyjqrs', type: 'local' },
{ id: '5', title: t('signature.agreement.record'), pdfName: pmblUrl.value, type: 'remote' }
]
}
return []
})
// PDF
/**
* 获取拍卖笔录PDF
* 通过拍品UUID获取拍卖笔录的查看地址
*/
const fetchPmblPdf = async () => {
try {
const res = await contractView({
auctionArtworkUuid: qrData.value.auctionArtworkUuid,
})
pmblUrl.value = res.data?.viewUrl // PDF URLdata
pmblUrl.value = res.data?.viewUrl
} catch (error) {
}
console.error('获取拍卖笔录失败:', error)
}
}
//
/**
* 折叠面板变化处理
* 当打开拍卖笔录面板时获取PDF地址
*/
const handleCollapseChange = (name) => {
activeNames.value = name
// PDF
if (name === '5' && !pmblUrl.value) {
fetchPmblPdf()
}
}
/**
* 确认签署处理
* 1. 获取用户法大大认证信息
* 2. 根据用户类型和地区判断签署流程:
* - 特殊用户且isMainland=1: 走大陆签署流程
* - 特殊用户且isMainland=0: 走非大陆签署流程
* - 普通用户:
* - 大陆用户(countryCode=86且身份证): 走大陆签署流程
* - 其他用户: 走非大陆签署流程
*/
const confirm = async () => {
const toast= showLoadingToast({
message: '加载中...',
forbidClick: true,
});
const confirm=async ()=>{
if (formData.value.countryCode==='86'&&formData.value.cardType===1){
const res=await signOffline({
userInfo:formData.value,
auctionArtworkUuid:qrData.value.auctionArtworkUuid,
signOrder:Number(number.value),
})
if (res.status===0){
window.location.href=res.data.fddVerifyUrl
try {
const fddResponse = await fddInfo({ phone: formData.value.phone })
if (fddResponse.status === 0) {
const { userId, isMainland } = fddResponse.data
//
if (userId) {
if (isMainland === 1) {
await handleMainlandSign()
} else {
router.push('/collectCode/signature/panel')
}
return
}
//
const isMainlandUser = formData.value.countryCode === '86' && formData.value.cardType === 1
if (isMainlandUser) {
await handleMainlandSign()
} else {
router.push('/collectCode/signature/panel')
}
}
}else {
router.push('/collectCode/signature/panel')
} catch (error) {
console.error('签署确认失败:', error)
}finally{
toast.close();
}
}
const goSignature = () => {
router.push({
path: '/signature/panel'
/**
* 处理大陆用户签署流程
*/
const handleMainlandSign = async () => {
const res = await signOffline({
userInfo: formData.value,
auctionArtworkUuid: qrData.value.auctionArtworkUuid,
signOrder: Number(number.value),
})
if (res.status === 0) {
window.location.href = res.data.fddVerifyUrl
}
}
</script>
<template>
<div class="bg-#EBEBEB h-screen-nav flex flex-col">
<!-- 顶部提示信息 -->
<div class="h-50px text-14px text-#191919 bg-#fff flex items-center px-21px mb-6px shrink-0">
{{ t('signature.tips.prePayment') }}
</div>
<!-- 协议列表折叠面板 -->
<van-collapse
accordion
v-model="activeNames"
@ -97,13 +150,14 @@ const goSignature = () => {
<div class="text-#2B53AC text-14px">{{ item.title }}</div>
</template>
<pdfView
:pdf-name="item.pdfName"
:type="item.type"
:is-active="activeNames === item.id"
:pdf-name="item.pdfName"
:type="item.type"
:is-active="activeNames === item.id"
/>
</van-collapse-item>
</van-collapse>
<!-- 底部确认按钮 -->
<div class="h-81px bg-#fff flex justify-center pt-7px border-t">
<van-button
color="#2B53AC"

View File

@ -21,7 +21,7 @@ await getAuctionDetail()
<template>
<div class="grow-1 flex flex-col">
<client-only>
<div class="relative" @click="changeLive">
<div class="relative bg-#000" @click="changeLive">
<liveRoom :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/>
<div v-if="auctionDetail.isLiving===1" class="absolute h-188px w-screen pt-36px flex flex-col text-#fff top-0 left-0 items-center">
<div class="text-18px mb-5px">{{ auctionDetail.title }}</div>

View File

@ -183,7 +183,7 @@ const tipOpen = () => {
<paymentResults v-model:show="show1" type="error"/>
<div v-if="auctionData?.wsType==='newArtwork'"
class="w-344px h-31px rounded-4px absolute top-9px bg-[#151824]/45 backdrop-blur-[10px] backdrop-saturate-[180%] left-1/2 transform translate-x--1/2 flex text-#fff text-14px items-center px-12px line-height-none">
<div class="mr-11px whitespace-nowrap">LOT{{ Number(auctionData.artwork.index+25000) }}</div>
<div class="mr-11px whitespace-nowrap">Lot{{ Number(auctionData.artwork.index+25000) }}</div>
<div class="mr-10px truncate">{{ auctionData.artwork.name }}</div>
<div class="whitespace-nowrap">{{ t('live_room.start') }}</div>
</div>

View File

@ -1,6 +1,5 @@
<script setup>
import { useRouter, useRoute } from 'vue-router';
import Vcode from "vue3-puzzle-vcode";
import { useI18n } from 'vue-i18n'
import countryCode from '../countryRegion/data/index.js'
import {senCode, userLogin,userCaptcha,userCaptchaValidate,} from "@/api/auth/index.js";
@ -103,7 +102,13 @@ blockY:0
const getCode =async () => {
isShow.value=true
loadingRef.value.loading1=true
const res=await userCaptcha(captcha.value)
const res=await userCaptcha({
canvasWidth:captcha.value.canvasWidth,
canvasHeight:captcha.value.canvasHeight,
blockWidth:captcha.value.blockWidth,
blockHeight:captcha.value.blockHeight,
place:captcha.value.place
})
if (res.status===0){
captcha.value.canvasSrc=`data:image/png;base64,${res.data.canvasSrc}`
captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}`

View File

@ -2,6 +2,7 @@
import {publicStore} from "@/stores/public/index.js";
import {useI18n} from 'vue-i18n'
import {outBuyList} from "@/api-public/public/index.js";
import { onUnmounted } from 'vue'
const {auctionData} = publicStore()
function formatThousands(num) {
@ -26,12 +27,29 @@ const headList=[
}
]
const buyList=ref([])
const timer = ref(null)
const headItem=(statusCode)=>{
return headList.find(x=>x.value===statusCode)
}
const fetchBuyList = async () => {
const res = await outBuyList({uuid: auctionData.value.uuid})
buyList.value = res.data.buys
}
onMounted(async()=>{
const res=await outBuyList({uuid:auctionData.value.uuid})
buyList.value=res.data.buys
await fetchBuyList()
timer.value = setInterval(async () => {
await fetchBuyList()
}, 10000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
</script>
@ -44,17 +62,16 @@ onMounted(async()=>{
<template v-if="buyList?.length>0">
<div v-for="(item, index) in buyList" :key="index" class="flex flex-shrink-0">
<!-- 将每列宽度改为相等(约86px)添加文本溢出处理 -->
<div class="text-start shrink-0 w-1/4 truncate" :style="`color: ${headItem(item.statusCode).color}`">
<div class="text-start shrink-0 w-1/6 break-words" :style="`color: ${headItem(item.statusCode).color}`">
{{ headItem(item.statusCode).label }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
<div class="text-start shrink-0 w-[28%] break-words">
{{ item.auctionType==='local'? $t('live_room.spot'):$t('live_room.network') }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
<div class="text-start shrink-0 w-[28%] break-words">
{{ item.createdAt }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
<div class="text-start shrink-0 w-[28%] break-words">
{{item.baseCurrency}}{{ formatThousands(item.baseMoney) }}
</div>
</div>

View File

@ -49,6 +49,7 @@ const initializePlayer = async () => {
height: '100%', //
skinLayout: false,
controlBarVisibility: 'never',
env: 'SEA' ,
license: {
domain: "szjixun.cn",
key: "OProxmWaOZ2XVHXLtf4030126521c43429403194970aa8af9"
@ -119,5 +120,7 @@ onMounted(async () => {
<style scoped lang="scss">
:deep(.prism-license-watermark) {
display: none !important;
}
</style>

View File

@ -27,6 +27,7 @@ export const goodStore = createGlobalState(() => {
// 获取拍卖详情
const getAuctionDetail = async () => {
try {
loading.value = true
const res = await defaultDetail({})

View File

@ -493,7 +493,7 @@
"detail": "Lot Details",
"description": "Lot Description"
},
"lotDetail": "LOT Details",
"lotDetail": "Lot Details",
"refresh": {
"success": "Refresh successful",
"noMore": "No more data"

View File

@ -1,5 +1,6 @@
import dotenv from 'dotenv'
import process from 'node:process'
import imagemin from 'vite-plugin-imagemin'
import { currentLocales } from './i18n/i18n'
import fs from 'fs'
import path from 'path'
@ -29,13 +30,8 @@ export default defineNuxtConfig({
modules: [
'@vant/nuxt',
'@unocss/nuxt',
'@nuxtjs/i18n',
'@nuxtjs/i18n'
],
image: {
provider: 'ipx',
format: ['webp'],
quality: 80,
},
runtimeConfig: {
// 私有配置,只有在服务端可用
apiSecret: process.env.NUXT_API_SECRET,
@ -115,6 +111,13 @@ export default defineNuxtConfig({
vite: {
build: {
target: 'esnext',
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除 console
drop_debugger: true, // 移除 debugger
}
}
},
optimizeDeps: {
include: [
@ -123,6 +126,35 @@ export default defineNuxtConfig({
'is-https',
],
},
plugins: [
imagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 70
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
})
]
},
experimental: {

View File

@ -53,7 +53,8 @@
"sass-loader": "^16.0.4",
"sharp": "^0.33.5",
"typescript": "~5.7.2",
"vant": "^4.9.15"
"vant": "^4.9.15",
"vite-plugin-imagemin": "^0.6.1"
},
"pnpm": {
"peerDependencyRules": {

File diff suppressed because it is too large Load Diff