Compare commits

...

16 Commits

Author SHA1 Message Date
xingyy
016623b6a4 feat(i18n): 添加验证成功和失败的国际化文本
- 在 en-US、ja-JP 和 zh-TW 语言文件中添加了 "verifySuccess" 和 "verifyFailed" 两个新的国际化文本
- 这些文本将用于在不同语言环境下显示验证成功或失败的消息
2025-03-12 15:19:11 +08:00
xingyy
da60cce5d2 build(dependencies): 移除 vue3-puzzle-vcode 依赖
- 从 package.json 和 pnpm-lock.yaml 中移除了 vue3-puzzle-vcode
- 在 zh-CN.json 中添加了验证成功和失败的提示信息
2025-03-12 15:14:15 +08:00
xingyy
36fab5a203 feat(puzzleComponent): 添加加载状态并优化验证逻辑
- 在 puzzleComponent 中添加加载状态显示
- 优化验证逻辑,修复验证失败时滑块不重置的问题
- 在 login 页面中集成新的加载状态
- 调整验证码按钮的加载状态显示
2025-03-12 15:10:44 +08:00
xingyy
bc30f84ccd 1212 2025-03-12 14:49:07 +08:00
xingyy
85b9beb8f7 1212 2025-03-12 14:33:33 +08:00
xingyy
5894344c87 1212 2025-03-12 14:18:52 +08:00
xingyy
28b213df68 1212 2025-03-12 14:15:43 +08:00
xingyy
837da43dbb 1212 2025-03-12 14:14:37 +08:00
xingyy
29cf4490e1 refactor(components): 重构滑块验证码组件
- 移除 YourPuzzleComponent.vue 文件
- 新增 puzzleComponent 目录及 index.vue 文件
- 优化验证码组件的结构和功能
- 更新 login 页面中验证码的使用方式
2025-03-12 11:13:46 +08:00
xingyy
e9896d86d6 feat(login): 实现滑动验证码功能
- 新增滑动验证码
2025-03-12 10:18:30 +08:00
xingyy
7675d6687b style(YourPuzzleComponent): 调整拼图组件样式
- 固定背景图片大小为 320x189 像素
- 将滑块元素从 div 改为 img,并直接使用 sliderImageUrl 作为 src
- 调整滑块垂直位置,移除多余的减去 2 像素
2025-03-11 19:55:54 +08:00
xingyy
bfd60167fa fix(YourPuzzleComponent): 修复背景图未显示完全的问题
- 为 img 标签添加 object-fit: contain 样式,确保图片按比例显示
- 添加图片预加载功能,提高组件加载性能
- 增加图片加载失败的错误处理机制
- 优化滑块样式,设置背景图片不重复并居中显示
- 调整滑块位置,解决显示不完整的问题
2025-03-11 19:42:50 +08:00
xingyy
85523e8321 feat(auth): 添加滑动验证码功能
- 在 auth API 中新增 userCaptcha 和 userCaptchaValidate 方法
- 在登录页面集成滑动验证码组件
- 实现验证码获取
2025-03-11 19:34:31 +08:00
xingyy
4041b45cca feat(api-public): 新增公共直播室相关功能
- 添加 HTTP 请求工具和 API 接口定义
- 实现公共直播室页面组件和业务逻辑
- 集成阿里云播放器
- 添加指纹识别功能
- 优化错误处理和国际化支持
2025-03-11 15:21:02 +08:00
xingyy
083ef52e5d Merge branch 'main' into xingyy
# Conflicts:
#	app/stores/live/index.js
2025-03-11 11:55:19 +08:00
xingyy
c6a5897337 1212 2025-03-11 11:54:51 +08:00
19 changed files with 795 additions and 27 deletions

125
app/api-public/http.js Normal file
View File

@ -0,0 +1,125 @@
import {useRuntimeConfig} from '#app'
import {ofetch} from 'ofetch'
import {message} from '@/components/x-message/useMessage.js'
import { getFingerprint } from '@/utils/fingerprint'
let httpStatusErrorHandler
let http
// HTTP 状态码映射 - 使用i18n国际化
export function setupHttp() {
if (http) return http
const config = useRuntimeConfig()
const baseURL = config.public.NUXT_PUBLIC_API_BASE
const router = useRouter()
const i18n = useNuxtApp().$i18n
// 国际化的HTTP状态码映射
const HTTP_STATUS_MAP = {
400: i18n.t('http.error.badRequest'),
401: i18n.t('http.error.unauthorized'),
403: i18n.t('http.error.forbidden'),
404: i18n.t('http.error.notFound'),
500: i18n.t('http.error.serverError'),
502: i18n.t('http.error.badGateway'),
503: i18n.t('http.error.serviceUnavailable'),
504: i18n.t('http.error.gatewayTimeout')
}
const defaultOptions = {
baseURL,
headers: { 'Content-Type': 'application/json' },
timeout: 15000, // 15秒超时
retry: 3,
retryDelay: 1000,
}
http = ofetch.create({
...defaultOptions,
// 请求拦截
async onRequest({ options, request }) {
const fingerprint = await getFingerprint()
console.log('fingerprint',fingerprint)
// 添加 token
options.headers = {
'Authorization': '12312',
...options.headers,
'fingerprint':fingerprint,
'accept-language': i18n.locale.value
}
// GET 请求添加时间戳防止缓存
if (request.toLowerCase().includes('get')) {
options.params = {
...options.params,
_t: Date.now()
}
}
},
// 响应拦截
async onResponse({ response }) {
const data = response._data
// 处理业务错误
if (data.status === 1) {
message.error(data.msg || i18n.t('http.error.operationFailed'))
}
return response
},
// 响应错误处理
async onResponseError({ response, request }) {
// 网络错误
if (!response) {
message.error(i18n.t('http.error.networkError'))
return Promise.reject(new Error(i18n.t('http.error.networkError')))
}
const status = response.status
const data = response._data
// 处理 HTTP 状态错误
const errorMessage = data.msg || HTTP_STATUS_MAP[status] || i18n.t('http.error.requestFailed')
if (Array.isArray(data.msg)) {
data.msg.forEach(item => {
httpStatusErrorHandler?.(item, status)
})
} else {
httpStatusErrorHandler?.(errorMessage, status)
}
message.error(errorMessage)
return Promise.reject(data)
},
})
return http
}
export function createAbortController() {
return new AbortController()
}
export function injectHttpStatusErrorHandler(handler) {
httpStatusErrorHandler = handler
}
export function getHttp() {
if (!http) {
throw new Error(useNuxtApp().$i18n.t('http.error.httpNotInitialized'))
}
return http
}
// 导出请求工具函数
export async function request({url,...options}) {
const http = getHttp()
try {
return await http(url, {...options,body:options.data})
} catch (error) {
throw error
}
}

View File

@ -0,0 +1,25 @@
import { request } from "../http";
export async function defaultDetail(data) {
return await request( {
url:'/api/v1/m/auction/out/default/detail',
method: 'POST',
data
})
}
export async function getLink(data) {
return await request( {
url:'/api/v1/m/auction/out/log/sendlog/aljdfoqueoirhkjsadhfiu',
method: 'POST',
data
})
}
export async function outBuyList(data) {
return await request( {
url:'/api/v1/m/auction/out/buy/list',
method: 'POST',
data
})
}

View File

@ -22,4 +22,18 @@ export async function userUpdate(data) {
method: 'POST',
data
})
}
export async function userCaptcha(data) {
return await request( {
url:'/api/v1/m/user/captcha',
method: 'POST',
data
})
}
export async function userCaptchaValidate(data) {
return await request( {
url:'/mall/user/validate/captcha',
method: 'POST',
data
})
}

28
app/api/public/index.js Normal file
View File

@ -0,0 +1,28 @@
import { request } from "../http";
export async function defaultDetail(data) {
return await request( {
url:'/api/v1/m/auction/out/default/detail',
headers:{
'fingerprint':'12312'
},
method: 'POST',
data
})
}
export async function getLink(data) {
return await request( {
url:'/api/v1/m/auction/out/log/sendlog/aljdfoqueoirhkjsadhfiu',
method: 'POST',
data
})
}
export async function outBuyList(data) {
return await request( {
url:'/api/v1/m/auction/out/buy/list',
method: 'POST',
data
})
}

View File

@ -0,0 +1,258 @@
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
//i18n
import { useI18n } from 'vue-i18n'
const {t} =useI18n()
const props = defineProps({
options:Object,
loading: Boolean,
})
const emit = defineEmits(['leave'])
const moveX = ref(0)
const loaded = ref(false)
const isDragging = ref(false)
const isVerifying = ref(false)
const maxMoveX = ref(0)
const bgImage = ref(null)
const verifyStatus = reactive({
show: false,
type: '',
message: ''
})
const dragState = reactive({
startX: 0,
oldMoveX: 0
})
const onImageLoad = () => {
if (!bgImage.value?.complete) return
const img = bgImage.value
const scale = img.width / img.naturalWidth
const blockSize = Math.round(50 * scale)
maxMoveX.value = img.width - blockSize
loaded.value = true
}
const onImageError = () => {
console.error('Image failed to load')
maxMoveX.value = 270
loaded.value = true
}
//
const startDrag = (e) => {
isDragging.value = true
dragState.startX = e.touches?.[0].clientX ?? e.clientX
dragState.oldMoveX = moveX.value
}
const onDrag = (e) => {
if (!isDragging.value) return
const clientX = e.touches?.[0].clientX ?? e.clientX
let newMoveX = dragState.oldMoveX + (clientX - dragState.startX)
moveX.value = Math.max(0, Math.min(newMoveX, maxMoveX.value))
}
const endDrag = async () => {
if (!isDragging.value) return
isDragging.value = false
isVerifying.value = true
try {
emit('leave', moveX.value, (success) => {
showVerifyResult(success)
})
} catch (error) {
showVerifyResult(false)
}
}
//
const showVerifyResult = (success) => {
verifyStatus.show = true
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)
}
//
onMounted(() => {
window.addEventListener('mousemove', onDrag)
window.addEventListener('mouseup', endDrag)
window.addEventListener('touchmove', onDrag)
window.addEventListener('touchend', endDrag)
})
onBeforeUnmount(() => {
window.removeEventListener('mousemove', onDrag)
window.removeEventListener('mouseup', endDrag)
window.removeEventListener('touchmove', onDrag)
window.removeEventListener('touchend', endDrag)
})
</script>
<template>
<div class="m-auto bg-white p-15px rd-10px touch-none select-none">
<div class="relative w-full overflow-hidden bg-#f8f8f8 rd-10px" :style="{ width: `${options?.canvasWidth}px`, height: `${options?.canvasHeight}px` }">
<!-- 加载状态 -->
<div v-if="loading" class="absolute inset-0 flex flex-col items-center justify-center bg-#f8f8f8">
<div class="fancy-loader">
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
<div class="fancy-loader-bar"></div>
</div>
</div>
<!-- 背景图 -->
<img
v-else
:src="options?.canvasSrc"
class="pointer-events-none w-full h-full"
ref="bgImage"
@load="onImageLoad"
@error="onImageError"
>
<!-- 滑块 -->
<img
:src="options?.blockSrc"
class="absolute cursor-pointer will-change-transform transform-gpu"
v-if="!loading"
:class="{ 'transition-all duration-300 ease-out': !isDragging }"
:style="{
top: `${options?.blockY}px`,
left: `${moveX}px`,
visibility: loaded ? 'visible' : 'hidden',
width: `${options?.blockWidth}px`, height: `${options?.blockHeight}px`
}"
>
<transition name="fade-slide">
<div
v-if="verifyStatus.show"
class="absolute left-0 bottom-0 w-full h-25px leading-25px text-center text-14px text-white"
:class="verifyStatus.type === 'success' ? 'bg-#52c41a' : 'bg-#ff4d4f'"
>
{{ verifyStatus.message }}
</div>
</transition>
</div>
<!-- 滑动条 -->
<div class="relative mt-15px h-40px">
<div class="relative h-40px bg-#f5f5f5 rd-20px">
<div
class="absolute h-full bg-#91d5ff rd-20px"
:class="{ 'transition-all duration-300 ease-out': !isDragging }"
:style="{ width: `${moveX}px` }"
></div>
<div
class="absolute top-0 w-40px h-40px bg-white rd-full shadow-[0_2px_6px_rgba(0,0,0,0.15)] cursor-pointer will-change-transform"
:class="{ 'transition-all duration-300 ease-out': !isDragging }"
:style="{ left: `${moveX}px` }"
@mousedown.prevent="startDrag"
@touchstart.prevent="startDrag"
>
<div
class="absolute top-50% left-50% translate--50% w-20px h-20px bg-#1890ff rd-full"
:class="{ 'animate-loading': isVerifying }"
></div>
</div>
</div>
</div>
</div>
</template>
<style>
@keyframes loading {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
.animate-loading {
animation: loading 1s linear infinite;
}
/* 添加过渡动画样式 */
.fade-slide-enter-active,
.fade-slide-leave-active {
transition: all 0.3s ease;
}
.fade-slide-enter-from,
.fade-slide-leave-to {
transform: translateY(100%);
opacity: 0;
}
/* 加载动画样式 */
.fancy-loader {
display: flex;
align-items: flex-end;
justify-content: center;
width: 60px;
height: 40px;
}
.fancy-loader-bar {
width: 6px;
height: 15px;
margin: 0 3px;
background-color: #1890ff;
border-radius: 3px;
animation: fancy-loading 1s ease-in-out infinite;
}
.fancy-loader-bar:nth-child(1) {
animation-delay: 0s;
}
.fancy-loader-bar:nth-child(2) {
animation-delay: 0.2s;
}
.fancy-loader-bar:nth-child(3) {
animation-delay: 0.4s;
}
.fancy-loader-bar:nth-child(4) {
animation-delay: 0.6s;
}
@keyframes fancy-loading {
0% {
transform: scaleY(0.5);
opacity: 0.5;
}
50% {
transform: scaleY(1.2);
opacity: 1;
}
100% {
transform: scaleY(0.5);
opacity: 0.5;
}
}
/* 保留原有的spin动画用于其他地方 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@ -1,15 +1,22 @@
<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} from "@/api/auth/index.js";
import {senCode, userLogin,userCaptcha,userCaptchaValidate,} from "@/api/auth/index.js";
import {authStore} from "@/stores/auth/index.js";
import {message} from '@/components/x-message/useMessage.js'
import {fddCheck} from "~/api/goods/index.js";
import zu6020 from '@/static/images/zu6020@2x.png'
import PuzzleComponent from '@/components/puzzleComponent/index.vue'
import { ref } from 'vue';
const {userInfo,token,selectedZone}= authStore()
const router = useRouter();
const route = useRoute();
const { locale } = useI18n()
const imgs=ref([zu6020])
definePageMeta({
name: 'login',
i18n: 'login.title'
@ -17,6 +24,7 @@ definePageMeta({
const loadingRef=ref({
loading1:false,
loading2:false,
loading3:false,
})
const isExist=ref(false)// true
const isReal=ref(false) //isReal
@ -79,21 +87,31 @@ onMounted(()=>{
selectedCountry.value=route.query.countryName || defaultCountry.name
})
const vanSwipeRef=ref(null)
const captcha=ref({
nonceStr: "",
blockX: 256 ,
blockWidth:50,
blockHeight:50,
canvasWidth:320,
canvasHeight:191,
place:0,
canvasSrc:'',
blockSrc:'',
blockY:0
})
const getCode =async () => {
loadingRef.value.loading1=true
const res=await senCode({
telNum:phoneNum.value,
zone:selectedZone.value
})
loadingRef.value.loading1=false
isShow.value=true
loadingRef.value.loading1=true
const res=await userCaptcha(captcha.value)
if (res.status===0){
captcha.value.canvasSrc=`data:image/png;base64,${res.data.canvasSrc}`
captcha.value.blockSrc=`data:image/png;base64,${res.data.blockSrc}`
captcha.value.blockY=res.data.blockY
captcha.value.nonceStr=res.data.nonceStr
loadingRef.value.loading1=false
}
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
startCountdown();
}
const goBack = () => {
code.value = ''
@ -146,6 +164,43 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('resize', () => {})
})
const isShow=ref(false)
const onLeave =async (moveX, callback) => {
const res=await senCode({
telNum:phoneNum.value,
zone:selectedZone.value,
verifyCaptcha:{
blockX:moveX,
nonceStr:captcha.value.nonceStr
}
})
if (res.status===408){
callback(false)
getCode()
message.warning(res.msg)
}else if ([0,407].includes(res.status)){
callback(true)
if (res.status===407){
message.warning(res.msg)
}
setTimeout(() => {
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
startCountdown();
isShow.value=false
}, 1000)
}else {
callback(true)
setTimeout(() => {
isShow.value=false
},1000)
}
}
</script>
<template>
@ -174,7 +229,7 @@ onUnmounted(() => {
</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>
<van-button v-if="phoneNum" :loading-text="$t('login.getCode')" color="#2B53AC" block style="height: 48px" @click="getCode">{{ $t('login.getCode') }}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode') }}</van-button>
</div>
</div>
@ -221,6 +276,15 @@ onUnmounted(() => {
<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>
<van-popup v-model:show="isShow" round style="max-width: initial" teleport="body">
<PuzzleComponent
v-if="isShow"
:loading="loadingRef.loading1"
:options="captcha"
@leave="onLeave"
/>
</van-popup>
</div>
</template>
@ -238,4 +302,15 @@ onUnmounted(() => {
width: 41px;
height: 41px;
}
.verify-popup-content {
width: 90vw;
max-width: 350px;
padding: 20px;
box-sizing: border-box;
}
:deep(.van-popup) {
background: transparent;
}
</style>

View File

@ -0,0 +1,76 @@
<script setup>
import {publicStore} from "@/stores/public/index.js";
import {useI18n} from 'vue-i18n'
import {outBuyList} from "@/api-public/public/index.js";
const {auctionData} = publicStore()
function formatThousands(num) {
return Number(num).toLocaleString();
}
const headList=[
{
label:useI18n().t('live_room.head'),
color:'#D03050',
value:'head'
},
{
label:useI18n().t('live_room.out'),
color:'#939393',
value:'out'
},
{
label:useI18n().t('live_room.success'),
color:'#34B633',
value:'success'
}
]
const buyList=ref([])
const headItem=(statusCode)=>{
return headList.find(x=>x.value===statusCode)
}
onMounted(async()=>{
const res=await outBuyList({uuid:auctionData.value.uuid})
buyList.value=res.data.buys
})
</script>
<template>
<div
id="list-container"
class="w-344px h-86px overflow-y-auto bg-#fff rounded-4px text-14px text-#939393 pt-7px pb-7px px-11px flex flex-col justify-between"
>
<transition-group name="list" tag="div">
<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}`">
{{ headItem(item.statusCode).label }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
{{ item.auctionType==='local'? $t('live_room.spot'):$t('live_room.network') }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
{{ item.createdAt }}
</div>
<div class="text-start shrink-0 w-1/4 truncate">
{{item.baseCurrency}}{{ formatThousands(item.baseMoney) }}
</div>
</div>
</template>
</transition-group>
</div>
</template>
<style scoped>
.list-enter-active, .list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from, .list-leave-to {
opacity: 0;
transform: translateY(20px);
}
</style>

View File

@ -0,0 +1,123 @@
<script setup>
import { defaultDetail,getLink } from '@/api-public/public/index.js'
import { liveStore } from '@/stores/live/index.js'
import AliyunPlayer from 'aliyun-aliplayer'
import { publicStore } from '@/stores/public/index.js'
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
import broadcast from './components/broadcast/index.vue'
const {decryptUtils} = liveStore()
const {auctionData} = publicStore()
definePageMeta({
layout: 'publicLiveRoom'
})
const {t}= useI18n()
const pullLink = ref('')
const player = ref(null)
const loading1=ref(false)
const handlePlayerError = (error) => {
showConfirmDialog({
message: t('live_room.error_mess'),
showCancelButton: true
}).then(() => {
initializePlayer()
}).catch(() => {
})
}
const initializePlayer = async () => {
try {
if (player.value) {
player.value.dispose()
}
//
const isWechat = /MicroMessenger/i.test(navigator.userAgent)
const playerConfig = {
id: 'J_prismPlayer1',
source: pullLink.value,
isLive: true,
preload: true,
autoplay: true, // true
muted: true, //
diagnosisButtonVisible:false,
// vodRetry:10,
// liveRetry:10,
autoplayPolicy: {
fallbackToMute: true
},
width: '100%', //
height: '100%', //
skinLayout: false,
controlBarVisibility: 'never',
license: {
domain: "szjixun.cn",
key: "OProxmWaOZ2XVHXLtf4030126521c43429403194970aa8af9"
}
}
player.value = new AliyunPlayer(playerConfig, (playerInstance) => {
//
if (isWechat) {
const startPlay = () => {
playerInstance?.play()
document.removeEventListener('WeixinJSBridgeReady', startPlay)
document.removeEventListener('touchstart', startPlay)
}
document.addEventListener('WeixinJSBridgeReady', startPlay)
document.addEventListener('touchstart', startPlay)
}
loading1.value = true
playerInstance?.play()
})
player.value.on('playing', () => {
loading1.value = false
})
player.value.on('loading', () => {
})
player.value.on('error', handlePlayerError)
} catch (error) {
console.log('error',error)
showConfirmDialog({
message: t('live_room.error_mess'),
showCancelButton: true
}).then(() => {
initializePlayer()
}).catch(() => {
})
}
}
onMounted(async () => {
const res = await defaultDetail({})
if(res.status === 0){
auctionData.value = res.data
}
const linkRes = await getLink({uuid:auctionData.value.uuid})
pullLink.value =decryptUtils.decryptData(linkRes.data.code)
initializePlayer()
})
</script>
<template>
<div class="grow-1 relative">
<van-nav-bar :title="auctionData.title" />
<div id="J_prismPlayer1" class="w-100vw" style="height: calc(100vh - var(--van-nav-bar-height));"></div>
<div v-if="loading1" class="absolute left-1/2 transform translate-x--1/2 top-1/2 translate-y--1/2">
<van-loading type="spinner" >直播加载中...</van-loading>
</div>
<div class="absolute left-1/2 transform -translate-x-1/2" style="bottom:calc(var(--safe-area-inset-bottom) + 46px)">
<broadcast></broadcast>
</div>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@ -1,6 +1,9 @@
import { setupHttp } from '@/api/http'
import { setupHttp as setupHttp1} from '@/api-collect-code/http'
import { setupHttp as setupHttp1} from '@/api-collect-code/http'
import { setupHttp as setupHttp2} from '@/api-public/http'
export default defineNuxtPlugin(() => {
setupHttp()
setupHttp1()
setupHttp2()
})

BIN
app/static/images/reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -248,6 +248,7 @@ export const liveStore = createGlobalState(() => {
}
}
return{
decryptUtils,
wsClient,
fullLive,
isMinWindow,

View File

@ -0,0 +1,8 @@
import {createGlobalState, useLocalStorage} from '@vueuse/core'
export const publicStore = createGlobalState(() => {
const auctionData=useLocalStorage('auctionData',{})
return {
auctionData
}
})

17
app/utils/fingerprint.js Normal file
View File

@ -0,0 +1,17 @@
import FingerprintJS from '@fingerprintjs/fingerprintjs'
export async function getFingerprint() {
try {
// 初始化 FingerprintJS
const fp = await FingerprintJS.load()
// 获取访问者的指纹
const result = await fp.get()
// 返回指纹哈希值
return result.visitorId
} catch (error) {
console.error('获取浏览器指纹失败:', error)
return null
}
}

View File

@ -642,7 +642,9 @@
"submit": "Submit",
"cancel": "Cancel",
"pleaseInput": "Please enter",
"pleaseSelect": "Please select"
"pleaseSelect": "Please select",
"verifySuccess": "Verification successful",
"verifyFailed": "Verification failed"
},
"upload": {
"text": "Click to Upload",

View File

@ -642,7 +642,9 @@
"submit": "送信",
"cancel": "キャンセル",
"pleaseInput": "入力してください",
"pleaseSelect": "選択してください"
"pleaseSelect": "選択してください",
"verifySuccess": "認証成功",
"verifyFailed": "認証失敗"
},
"upload": {
"text": "アップロードするにはクリック",

View File

@ -646,7 +646,9 @@
"submit": "提交",
"cancel": "取消",
"pleaseInput": "请输入",
"pleaseSelect": "请选择"
"pleaseSelect": "请选择",
"verifySuccess": "验证成功",
"verifyFailed": "验证失败"
},
"upload": {
"text": "点击上传",

View File

@ -642,7 +642,9 @@
"submit": "提交",
"cancel": "取消",
"pleaseInput": "請輸入",
"pleaseSelect": "請選擇"
"pleaseSelect": "請選擇",
"verifySuccess": "驗證成功",
"verifyFailed": "驗證失敗"
},
"upload": {
"text": "點擊上傳",

View File

@ -809,8 +809,8 @@ packages:
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
engines: {node: '>= 16'}
'@intlify/shared@11.1.1':
resolution: {integrity: sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==}
'@intlify/shared@11.1.2':
resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==}
engines: {node: '>= 16'}
'@intlify/unplugin-vue-i18n@6.0.3':
@ -5342,14 +5342,14 @@ snapshots:
'@intlify/shared@11.0.0-rc.1': {}
'@intlify/shared@11.1.1': {}
'@intlify/shared@11.1.2': {}
'@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.20.1(jiti@2.4.2))(rollup@4.34.6)(typescript@5.7.3)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2))
'@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))
'@intlify/shared': 11.1.1
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
'@intlify/shared': 11.1.2
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
'@rollup/pluginutils': 5.1.4(rollup@4.34.6)
'@typescript-eslint/scope-manager': 8.24.0
'@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3)
@ -5373,11 +5373,11 @@ snapshots:
'@intlify/utils@0.13.0': {}
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@babel/parser': 7.26.8
optionalDependencies:
'@intlify/shared': 11.1.1
'@intlify/shared': 11.1.2
'@vue/compiler-dom': 3.5.13
vue: 3.5.13(typescript@5.7.3)
vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.3))

View File

@ -1,4 +1,11 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["app/*"] //
}
},
"include": ["app/**/*"]
}