Compare commits
	
		
			8 Commits
		
	
	
		
			ebe909c4d7
			...
			415267e307
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 415267e307 | ||
|  | e9896d86d6 | ||
|  | 7675d6687b | ||
|  | bfd60167fa | ||
|  | 85523e8321 | ||
|  | 4041b45cca | ||
|  | 083ef52e5d | ||
|  | c6a5897337 | 
							
								
								
									
										125
									
								
								app/api-public/http.js
									
									
									
									
									
										Normal 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 | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								app/api-public/public/index.js
									
									
									
									
									
										Normal 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 | ||||||
|  |     }) | ||||||
|  | } | ||||||
| @ -23,3 +23,17 @@ export async function userUpdate(data) { | |||||||
|         data |         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
									
								
							
							
						
						| @ -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 | ||||||
|  |     }) | ||||||
|  | } | ||||||
							
								
								
									
										367
									
								
								app/components/YourPuzzleComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,367 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="puzzle-container"> | ||||||
|  |     <div class="puzzle-box" :style="{ height: boxHeight + 'px' }"> | ||||||
|  |       <!-- 背景图 --> | ||||||
|  |       <img :src="bgImageUrl" style="width: 320px;height: 191px;"  ref="bgImage" @load="onImageLoad" @error="handleImageError"> | ||||||
|  |       <!-- 滑块 --> | ||||||
|  |       <img  | ||||||
|  |         class="slider-block" | ||||||
|  |         :src="sliderImageUrl" | ||||||
|  |         :style="{ | ||||||
|  |           top: `${blockY}px`, | ||||||
|  |           left: `${moveX}px`, | ||||||
|  |           visibility: loaded ? 'visible' : 'hidden', | ||||||
|  |           width: '50px', | ||||||
|  |           height: '50px' | ||||||
|  |         }" | ||||||
|  |       ></img> | ||||||
|  |       <div v-if="verifySuccess || verifyError" :class="`text-#fff ${verifySuccess?'bg-#52C41A':'bg-#FF4D4F'} h-24px w-100% text-14px absolute left-0 bottom-0 text-center leading-24px`">{{ verifyTip }}</div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- 滑动条 --> | ||||||
|  |     <div class="slider-container"> | ||||||
|  |       <div class="slider-track"> | ||||||
|  |         <div  | ||||||
|  |           class="slider-bar" | ||||||
|  |           :style="{ width: `${moveX}px` }" | ||||||
|  |         ></div> | ||||||
|  |         <div  | ||||||
|  |           class="slider-button" | ||||||
|  |           :style="{ left: `${moveX}px` }" | ||||||
|  |           @mousedown="handleMouseDown" | ||||||
|  |           @touchstart="handleTouchStart" | ||||||
|  |         > | ||||||
|  |           <div class="slider-icon"></div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |      | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup> | ||||||
|  | import { ref, onMounted, onBeforeUnmount } from 'vue' | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   show: { | ||||||
|  |     type: Boolean, | ||||||
|  |     default: false | ||||||
|  |   }, | ||||||
|  |   blockY: { | ||||||
|  |     type: Number, | ||||||
|  |     required: true | ||||||
|  |   }, | ||||||
|  |   bgImageUrl: { | ||||||
|  |     type: String, | ||||||
|  |     required: true | ||||||
|  |   }, | ||||||
|  |   sliderImageUrl: { | ||||||
|  |     type: String, | ||||||
|  |     required: true | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['leave']) | ||||||
|  | 
 | ||||||
|  | // 响应式状态 | ||||||
|  | const moveX = ref(0) | ||||||
|  | const startX = ref(0) | ||||||
|  | const oldMoveX = ref(0) | ||||||
|  | const maxMoveX = ref(0) | ||||||
|  | const boxHeight = ref(0) | ||||||
|  | const loaded = ref(false) | ||||||
|  | const isDragging = ref(false) | ||||||
|  | const verifySuccess = ref(false) | ||||||
|  | const verifyError = ref(false) | ||||||
|  | const verifyTip = ref('') | ||||||
|  | 
 | ||||||
|  | // 重置方法 | ||||||
|  | const reset = () => { | ||||||
|  |   moveX.value = 0 | ||||||
|  |   verifySuccess.value = false | ||||||
|  |   verifyError.value = false | ||||||
|  |   verifyTip.value = '' | ||||||
|  |   isDragging.value = false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DOM引用 | ||||||
|  | const bgImage = ref(null) | ||||||
|  | 
 | ||||||
|  | // 方法 | ||||||
|  | const onImageLoad = () => { | ||||||
|  |   if (bgImage.value) { | ||||||
|  |     try { | ||||||
|  |       const img = bgImage.value | ||||||
|  |       // 确保图片已经完全加载 | ||||||
|  |       if (!img.complete) { | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       const scale = img.width / img.naturalWidth // 计算图片缩放比例 | ||||||
|  |       boxHeight.value = img.height | ||||||
|  |        | ||||||
|  |       // 根据图片实际显示大小调整滑块大小 | ||||||
|  |       const blockSize = Math.round(50 * scale) | ||||||
|  |       document.documentElement.style.setProperty('--block-size', blockSize + 'px') | ||||||
|  |        | ||||||
|  |       maxMoveX.value = img.width - blockSize // 使用实际显示的滑块大小计算最大移动距离 | ||||||
|  |       loaded.value = true | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('Image load error:', error) | ||||||
|  |       // 设置默认值以确保组件仍然可用 | ||||||
|  |       const defaultBlockSize = 50 | ||||||
|  |       document.documentElement.style.setProperty('--block-size', defaultBlockSize + 'px') | ||||||
|  |       maxMoveX.value = 320 - defaultBlockSize // 使用默认容器宽度 | ||||||
|  |       loaded.value = true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 添加图片错误处理 | ||||||
|  | const handleImageError = () => { | ||||||
|  |   console.error('Image failed to load') | ||||||
|  |   // 设置默认值以确保组件仍然可用 | ||||||
|  |   const defaultBlockSize = 50 | ||||||
|  |   document.documentElement.style.setProperty('--block-size', defaultBlockSize + 'px') | ||||||
|  |   maxMoveX.value = 320 - defaultBlockSize | ||||||
|  |   loaded.value = true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleMouseDown = (event) => { | ||||||
|  |   event.preventDefault() | ||||||
|  |   startX.value = event.clientX | ||||||
|  |   oldMoveX.value = moveX.value | ||||||
|  |   isDragging.value = true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleTouchStart = (event) => { | ||||||
|  |   event.preventDefault() | ||||||
|  |   const touch = event.touches[0] | ||||||
|  |   startX.value = touch.clientX | ||||||
|  |   oldMoveX.value = moveX.value | ||||||
|  |   isDragging.value = true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const move = (clientX) => { | ||||||
|  |   let diff = clientX - startX.value | ||||||
|  |   let newMoveX = oldMoveX.value + diff | ||||||
|  |    | ||||||
|  |   // 限制移动范围 | ||||||
|  |   if (newMoveX < 0) newMoveX = 0 | ||||||
|  |   if (newMoveX > maxMoveX.value) newMoveX = maxMoveX.value | ||||||
|  |    | ||||||
|  |   moveX.value = Math.round(newMoveX) // 取整数避免小数点导致的模糊 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleMouseMove = (event) => { | ||||||
|  |   if (!isDragging.value) return | ||||||
|  |   move(event.clientX) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleTouchMove = (event) => { | ||||||
|  |   if (!isDragging.value) return | ||||||
|  |   event.preventDefault() // 防止页面滚动 | ||||||
|  |   move(event.touches[0].clientX) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleMouseUp = async () => { | ||||||
|  |   if (!isDragging.value) return | ||||||
|  |   isDragging.value = false | ||||||
|  |    | ||||||
|  |   try { | ||||||
|  |     emit('leave', moveX.value, (success) => { | ||||||
|  |       if (success) { | ||||||
|  |         verifySuccess.value = true | ||||||
|  |         verifyError.value = false | ||||||
|  |         verifyTip.value = '验证成功' | ||||||
|  |       } else { | ||||||
|  |         verifySuccess.value = false | ||||||
|  |         verifyError.value = true | ||||||
|  |         verifyTip.value = '验证失败' | ||||||
|  |     | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |   } catch (error) { | ||||||
|  |     verifySuccess.value = false | ||||||
|  |     verifyError.value = true | ||||||
|  |     verifyTip.value = '验证失败' | ||||||
|  |   }finally{ | ||||||
|  |     setTimeout(() => { | ||||||
|  |       reset() | ||||||
|  |     }, 2000) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleTouchEnd = () => { | ||||||
|  |   handleMouseUp() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 预加载图片 | ||||||
|  | const preloadImages = () => { | ||||||
|  |   const bgImg = new Image() | ||||||
|  |   const sliderImg = new Image() | ||||||
|  |    | ||||||
|  |   bgImg.onload = () => { | ||||||
|  |     if (bgImage.value) { | ||||||
|  |       onImageLoad() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   bgImg.src = props.bgImageUrl | ||||||
|  |   sliderImg.src = props.sliderImageUrl | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 生命周期钩子 | ||||||
|  | onMounted(() => { | ||||||
|  |   preloadImages() | ||||||
|  |   window.addEventListener('mousemove', handleMouseMove) | ||||||
|  |   window.addEventListener('mouseup', handleMouseUp) | ||||||
|  |   window.addEventListener('touchmove', handleTouchMove) | ||||||
|  |   window.addEventListener('touchend', handleTouchEnd) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | onBeforeUnmount(() => { | ||||||
|  |   window.removeEventListener('mousemove', handleMouseMove) | ||||||
|  |   window.removeEventListener('mouseup', handleMouseUp) | ||||||
|  |   window.removeEventListener('touchmove', handleTouchMove) | ||||||
|  |   window.removeEventListener('touchend', handleTouchEnd) | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | :root { | ||||||
|  |   --block-size: 50px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .puzzle-container { | ||||||
|  |   position: relative; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   background: #fff; | ||||||
|  |   padding: 15px; | ||||||
|  |   border-radius: 10px; | ||||||
|  |   touch-action: none; | ||||||
|  |   -webkit-touch-callout: none; | ||||||
|  |   -webkit-user-select: none; | ||||||
|  |   user-select: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .puzzle-box { | ||||||
|  |   position: relative; | ||||||
|  |   width: 100%; | ||||||
|  |   overflow: hidden; | ||||||
|  |   background: #f8f8f8; | ||||||
|  |   border-radius: 8px; | ||||||
|  |   touch-action: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .bg-image { | ||||||
|  |   display: block; | ||||||
|  |   width: 100%; | ||||||
|  |   height: auto; | ||||||
|  |   -webkit-user-select: none; | ||||||
|  |   user-select: none; | ||||||
|  |   pointer-events: none; | ||||||
|  |   object-fit: contain; | ||||||
|  |   max-width: 100%; | ||||||
|  |   -webkit-touch-callout: none; | ||||||
|  |   -webkit-tap-highlight-color: transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-block { | ||||||
|  |   position: absolute; | ||||||
|  |   width: var(--block-size); | ||||||
|  |   height: var(--block-size); | ||||||
|  |   background-size: 100% 100%; | ||||||
|  |   background-repeat: no-repeat; | ||||||
|  |   background-position: center; | ||||||
|  |   cursor: pointer; | ||||||
|  |   -webkit-user-select: none; | ||||||
|  |   user-select: none; | ||||||
|  |   touch-action: none; | ||||||
|  |   will-change: transform; | ||||||
|  |   transform: translateZ(0); | ||||||
|  |   backface-visibility: hidden; | ||||||
|  |   -webkit-touch-callout: none; | ||||||
|  |   -webkit-tap-highlight-color: transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-container { | ||||||
|  |   position: relative; | ||||||
|  |   margin-top: 15px; | ||||||
|  |   height: 40px; | ||||||
|  |   touch-action: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-track { | ||||||
|  |   position: relative; | ||||||
|  |   height: 40px; | ||||||
|  |   background: #f5f5f5; | ||||||
|  |   border-radius: 20px; | ||||||
|  |   touch-action: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-bar { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   background: #91d5ff; | ||||||
|  |   border-radius: 20px; | ||||||
|  |   will-change: transform; | ||||||
|  |   transform-origin: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-button { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 0; | ||||||
|  |   width: 40px; | ||||||
|  |   height: 40px; | ||||||
|  |   background: #fff; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); | ||||||
|  |   cursor: pointer; | ||||||
|  |   will-change: transform; | ||||||
|  |   transform: translateZ(0); | ||||||
|  |   backface-visibility: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .slider-icon { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 50%; | ||||||
|  |   left: 50%; | ||||||
|  |   transform: translate(-50%, -50%); | ||||||
|  |   width: 20px; | ||||||
|  |   height: 20px; | ||||||
|  |   background: #1890ff; | ||||||
|  |   border-radius: 50%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .verify-result-bar { | ||||||
|  |   position: absolute; | ||||||
|  |   left: 0; | ||||||
|  |   top: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 40px; | ||||||
|  |   line-height: 40px; | ||||||
|  |   text-align: center; | ||||||
|  |   font-size: 14px; | ||||||
|  |   border-radius: 20px; | ||||||
|  |   transition: all 0.3s; | ||||||
|  |   z-index: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .verify-result-bar.success { | ||||||
|  |   background: #52c41a; | ||||||
|  |   color: #fff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .verify-result-bar.error { | ||||||
|  |   background: #ff4d4f; | ||||||
|  |   color: #fff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* 移除旧的提示样式 */ | ||||||
|  | .verify-tip { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/de_DE/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 600 KiB | 
| After Width: | Height: | Size: 610 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/de_DE/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
| After Width: | Height: | Size: 6.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/en_US/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 681 KiB | 
| After Width: | Height: | Size: 691 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/en_US/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 5.8 KiB | 
| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/ja_JP/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 600 KiB | 
| After Width: | Height: | Size: 611 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/ja_JP/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.4 KiB | 
| After Width: | Height: | Size: 6.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/reload.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/zh_CN/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 681 KiB | 
| After Width: | Height: | Size: 691 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/zh_CN/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.2 KiB | 
| After Width: | Height: | Size: 7.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/zh_TW/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 600 KiB | 
| After Width: | Height: | Size: 611 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/components/slider-verify/slider-verify-image/zh_TW/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.8 KiB | 
| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										418
									
								
								app/components/slider-verify/slider-verify/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,418 @@ | |||||||
|  | <template> | ||||||
|  | 	<view class="slider-verify-box" v-if="isShow"> | ||||||
|  | 		<view :style="`background-image: url('../../static/image/slider-verify/${ | ||||||
|  | 				$i18n.locale | ||||||
|  | 			}/${isError ? 'bg_error' : 'bg'}.png');`" class="verifyBox"> | ||||||
|  | 			<!-- <view class="slider-title">{{ $t('authentication.title') }}</view> --> | ||||||
|  | 			<image class="slider-verify-box-close" src="/static/image/icon/close.png" @click.stop="hideSliderBox"> | ||||||
|  | 			</image> | ||||||
|  | 			<view class="slide-content"> | ||||||
|  | 				<view class="slider-pintu"> | ||||||
|  | 					<!-- <u-icon | ||||||
|  | 						name="reload" | ||||||
|  | 						size="32" | ||||||
|  | 						color="#fff" | ||||||
|  | 						class="reload" | ||||||
|  | 						@tap="refreshVerify" | ||||||
|  | 						v-if="!isLoading" | ||||||
|  | 					></u-icon> --> | ||||||
|  | 					<image src="../../static/image/slider-verify/reload.png" mode="widthFix" style="width: 38rpx" | ||||||
|  | 						class="reload" @tap="refreshVerify" v-if="!isLoading"></image> | ||||||
|  | 					<view class="load" v-if="isLoading"> | ||||||
|  | 						<van-loading type="spinner" /> | ||||||
|  | 						<!-- <u-loading-icon text="" textSize="16" :vertical="true"></u-loading-icon> --> | ||||||
|  | 					</view> | ||||||
|  | 					<template v-else> | ||||||
|  | 						<image id="pintuImg" :src="canvasSrc" class="pintu"></image> | ||||||
|  | 						<view class="pintukuai" :style="{ top: '0px', left: oldx + 'px' }"> | ||||||
|  | 							<image :src="blockSrc" :style="{ | ||||||
|  | 									top: blockY + 'px', | ||||||
|  | 									left: oldx + 'px', | ||||||
|  | 									width: blockWidth + 'px', | ||||||
|  | 									height: blockHeight + 'px' | ||||||
|  | 								}"></image> | ||||||
|  | 						</view> | ||||||
|  | 						<view class="mark" v-if="isMess"> | ||||||
|  | 							<image :src="`../../static/image/slider-verify/${$i18n.locale}/error.png`" mode="widthFix" | ||||||
|  | 								v-if="isError"></image> | ||||||
|  | 							<image :src="`../../static/image/slider-verify/${$i18n.locale}/success.png`" mode="widthFix" | ||||||
|  | 								v-else></image> | ||||||
|  | 						</view> | ||||||
|  | 					</template> | ||||||
|  | 				</view> | ||||||
|  | 				<view class="slider-movearea" @touchend="endTouchMove"> | ||||||
|  | 					<movable-area :animation="true"> | ||||||
|  | 						<movable-view :class=" | ||||||
|  | 								isLoading | ||||||
|  | 									? 'movable-view btn_info' | ||||||
|  | 									: isError | ||||||
|  | 									? 'movable-view btn_error' | ||||||
|  | 									: 'movable-view btn_success' | ||||||
|  | 							" :x="x" direction="horizontal" @change="startMove"></movable-view> | ||||||
|  | 					</movable-area> | ||||||
|  | 					<view class="huadao">{{ | ||||||
|  | 						$t('authentication.content') | ||||||
|  | 					}}</view> | ||||||
|  | 					<view :class=" | ||||||
|  | 							isError ? 'huadao_done error_bg' : 'huadao_done' | ||||||
|  | 						" :style="'width:' + doneWindth + 'px;'"></view> | ||||||
|  | 				</view> | ||||||
|  | 			</view> | ||||||
|  | 		</view> | ||||||
|  | 	</view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | 	import { | ||||||
|  | 		postDataByParams | ||||||
|  | 	} from '../../utils/api.js' | ||||||
|  | 
 | ||||||
|  | 	export default { | ||||||
|  | 		name: 'slider-verify', | ||||||
|  | 		props: { | ||||||
|  | 			isShow: { | ||||||
|  | 				type: Boolean, | ||||||
|  | 				default: true | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		data() { | ||||||
|  | 			return { | ||||||
|  | 				x: 0, //初始距离 | ||||||
|  | 				oldx: 0, //移动的距离 | ||||||
|  | 				top: 0, //拼图的top距离 | ||||||
|  | 				canvasSrc: '', | ||||||
|  | 				blockSrc: '', | ||||||
|  | 				blockY: '', | ||||||
|  | 				nonceStr: '', | ||||||
|  | 				blockWidth: 50, //块图像的宽度(blockWidth大于14) | ||||||
|  | 				blockHeight: 50, | ||||||
|  | 				isLoading: true, | ||||||
|  | 				doneWindth: 0, | ||||||
|  | 				isError: false, | ||||||
|  | 				isMess: false | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		watch: { | ||||||
|  | 			// 每次打开重新刷新拼图 | ||||||
|  | 			isShow(newValue, oldValue) { | ||||||
|  | 				if (newValue) { | ||||||
|  | 					this.refreshVerify() //刷新 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		mounted() { | ||||||
|  | 			var that = this | ||||||
|  | 			// that.refreshVerify(); | ||||||
|  | 			that.getCaptcha() | ||||||
|  | 			// console.log(this.$i18n.locale, 'this.$i18n.locale') | ||||||
|  | 		}, | ||||||
|  | 		methods: { | ||||||
|  | 			async getCaptcha() { | ||||||
|  | 				this.isLoading = true | ||||||
|  | 				var that = this | ||||||
|  | 				let url = 'generate/captcha' | ||||||
|  | 				let params = { | ||||||
|  | 					canvasWidth: 320, //画布的宽度(canvasWidth大于41并且(canvasWidth-10)/2 - 1> blockWidth) | ||||||
|  | 					canvasHeight: 190, //画布的高度(canvasHeight大于26并且 canvasHeight - blockHeight > 11 | ||||||
|  | 					blockWidth: this.blockWidth, //块图像的宽度(blockWidth大于14) | ||||||
|  | 					blockHeight: this.blockHeight, //块图像的高度(blockHeight大于14) | ||||||
|  | 					// blockRadius: 9, //块图像的圆角半径 | ||||||
|  | 					place: 0 //图像来源标识,0表示URL下载,1表示本地文件  一般用0 | ||||||
|  | 				} | ||||||
|  | 				postDataByParams(url, params) | ||||||
|  | 					.then((res) => { | ||||||
|  | 						if (res.status === 0) { | ||||||
|  | 							that.canvasSrc = 'data:image/jpg;base64,' + res.data.canvasSrc | ||||||
|  | 							that.blockSrc = 'data:image/jpg;base64,' + res.data.blockSrc | ||||||
|  | 							that.blockY = res.data.blockY | ||||||
|  | 							that.nonceStr = res.data.nonceStr | ||||||
|  | 							that.isLoading = false | ||||||
|  | 						} else { | ||||||
|  | 							this.$emit('sliderError', encodeURIComponent(JSON.stringify(res))) | ||||||
|  | 						} | ||||||
|  | 					}) | ||||||
|  | 					.catch((err) => { | ||||||
|  | 						this.$emit('sliderError', encodeURIComponent(JSON.stringify(err)), true) | ||||||
|  | 					}); | ||||||
|  | 			}, | ||||||
|  | 			//刷新验证 | ||||||
|  | 			refreshVerify() { | ||||||
|  | 				this.x = 1 | ||||||
|  | 				this.oldx = 1 | ||||||
|  | 				setTimeout(() => { | ||||||
|  | 					this.x = 0 | ||||||
|  | 					this.oldx = 0 | ||||||
|  | 					this.doneWindth = 0 | ||||||
|  | 				}, 300) | ||||||
|  | 				this.getCaptcha() | ||||||
|  | 				this.isError = false | ||||||
|  | 				this.isMess = false | ||||||
|  | 			}, | ||||||
|  | 			/* 滑动中 */ | ||||||
|  | 			startMove(e) { | ||||||
|  | 				// console.log(e.detail.x) | ||||||
|  | 				this.oldx = e.detail.x / 2 - 5 | ||||||
|  | 				if (e.detail.x > 1) { | ||||||
|  | 					this.doneWindth = e.detail.x + 30 | ||||||
|  | 				} else { | ||||||
|  | 					this.doneWindth = 0 | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			/* 滑动结束 */ | ||||||
|  | 			async endTouchMove() { | ||||||
|  | 				var that = this | ||||||
|  | 				// console.log('滑块结束') | ||||||
|  | 				let url = 'validate/captcha' | ||||||
|  | 				let params = { | ||||||
|  | 					nonceStr: this.nonceStr, | ||||||
|  | 					blockX: this.oldx * 2 + 5 | ||||||
|  | 				} | ||||||
|  | 				postDataByParams(url, params) | ||||||
|  | 					.then((res) => { | ||||||
|  | 						this.isMess = true | ||||||
|  | 						if (res.status == 0) { | ||||||
|  | 							setTimeout(() => { | ||||||
|  | 								that.$emit('touchSliderResult', res.data.nonceStr) | ||||||
|  | 							}, 1000) | ||||||
|  | 						} else { | ||||||
|  | 							this.isError = true | ||||||
|  | 							this.$emit('sliderError', encodeURIComponent(JSON.stringify(res))) | ||||||
|  | 						} | ||||||
|  | 					}) | ||||||
|  | 					.catch((err) => { | ||||||
|  | 						this.$emit('sliderError', encodeURIComponent(JSON.stringify(err)), true) | ||||||
|  | 					}); | ||||||
|  | 			}, | ||||||
|  | 			/* 重置阴影位置 */ | ||||||
|  | 			/* resetMove() { | ||||||
|  | 				this.x = 1; | ||||||
|  | 				this.oldx = 1; | ||||||
|  | 				setTimeout(() => { | ||||||
|  | 					this.x = 0; | ||||||
|  | 					this.oldx = 0; | ||||||
|  | 				}, 300); | ||||||
|  | 			}, */ | ||||||
|  | 			// 关闭 | ||||||
|  | 			closeSlider() { | ||||||
|  | 				this.$emit('touchSliderResult', false) | ||||||
|  | 			}, | ||||||
|  | 			//隐藏滑块验证弹窗 | ||||||
|  | 			hideSliderBox() { | ||||||
|  | 				this.$emit('hideSliderBox') | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss"> | ||||||
|  | 	.slider-verify-box { | ||||||
|  | 		position: fixed; | ||||||
|  | 		top: 0; | ||||||
|  | 		left: 0; | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100%; | ||||||
|  | 		background-color: rgba(0, 0, 0, 0.5); | ||||||
|  | 		z-index: 999; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.error_box { | ||||||
|  | 		// background: url('../../static/image/slider-verify/bg_error.png') no-repeat !important; | ||||||
|  | 		background-size: 100% 100% !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.verifyBox { | ||||||
|  | 		// width: 588rpx; | ||||||
|  | 		// height: 662rpx; | ||||||
|  | 		padding: 218rpx 45rpx 30rpx 45rpx; | ||||||
|  | 		position: absolute; | ||||||
|  | 		top: 50%; | ||||||
|  | 		left: 50%; | ||||||
|  | 		transform: translate(-50%, -50%); | ||||||
|  | 		// width: 85%; | ||||||
|  | 		// background-color: #fff; | ||||||
|  | 		background-repeat: no-repeat; | ||||||
|  | 		// background: url('../../static/image/slider-verify/bg.png') no-repeat; | ||||||
|  | 		background-size: 100% 100%; | ||||||
|  | 		border-radius: 20upx; | ||||||
|  | 		// box-shadow: 0 0 5upx rgba(0, 0, 0, 1); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		.slider-verify-box-close { | ||||||
|  | 			width: 40rpx; | ||||||
|  | 			height: 40rpx; | ||||||
|  | 			position: absolute; | ||||||
|  | 			top: 0; | ||||||
|  | 			right: 0; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		.slider-title { | ||||||
|  | 			font-size: 36upx; | ||||||
|  | 			text-align: center; | ||||||
|  | 			padding: 12rpx 0; | ||||||
|  | 			color: #000000; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		.slide-content { | ||||||
|  | 			// width: 560rpx; | ||||||
|  | 			// padding: 0 ; | ||||||
|  | 			// margin: 0 auto; | ||||||
|  | 
 | ||||||
|  | 			.slide-tips { | ||||||
|  | 				font-size: 28rpx; | ||||||
|  | 				color: rgba(2, 20, 33, 0.45); | ||||||
|  | 				padding: 0.5em 0; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			.slider-pintu { | ||||||
|  | 				position: relative; | ||||||
|  | 				width: 100%; | ||||||
|  | 				border-radius: 10rpx; | ||||||
|  | 				overflow: hidden; | ||||||
|  | 
 | ||||||
|  | 				.reload { | ||||||
|  | 					position: absolute; | ||||||
|  | 					right: 10rpx; | ||||||
|  | 					top: 10rpx; | ||||||
|  | 					z-index: 110; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				.load { | ||||||
|  | 					width: 320px; | ||||||
|  | 					height: 190px; | ||||||
|  | 					display: flex; | ||||||
|  | 					align-items: center; | ||||||
|  | 					justify-content: center; | ||||||
|  | 					margin: 0 auto; | ||||||
|  | 					background: #000000; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				.pintu { | ||||||
|  | 					width: 320px; | ||||||
|  | 					height: 190px; | ||||||
|  | 					display: block; | ||||||
|  | 					margin: 0 auto; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				.pintukuai { | ||||||
|  | 					position: absolute; | ||||||
|  | 					/* top: 0; | ||||||
|  | 				left: 0; */ | ||||||
|  | 					z-index: 100; | ||||||
|  | 					box-shadow: 0 0 5upx rgba(0, 0, 0, 0.3); | ||||||
|  | 
 | ||||||
|  | 					image { | ||||||
|  | 						display: block; | ||||||
|  | 						position: absolute; | ||||||
|  | 						top: 0; | ||||||
|  | 						left: 0; | ||||||
|  | 						/* width: 560rpx; | ||||||
|  | 					height: 315rpx; */ | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				.mark { | ||||||
|  | 					position: absolute; | ||||||
|  | 					top: 0; | ||||||
|  | 					left: 0; | ||||||
|  | 					right: 0; | ||||||
|  | 					bottom: 0; | ||||||
|  | 					background: #000000; | ||||||
|  | 					z-index: 101; | ||||||
|  | 					display: flex; | ||||||
|  | 					align-items: center; | ||||||
|  | 					justify-content: center; | ||||||
|  | 
 | ||||||
|  | 					image { | ||||||
|  | 						width: 60%; | ||||||
|  | 						height: 50rpx; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			.yinying { | ||||||
|  | 				position: absolute; | ||||||
|  | 				width: 120rpx; | ||||||
|  | 				height: 120rpx; | ||||||
|  | 				background-color: rgba(0, 0, 0, 0.5); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.slider-movearea { | ||||||
|  | 		position: relative; | ||||||
|  | 		height: 80upx; | ||||||
|  | 		width: 320px; | ||||||
|  | 		margin-top: 25rpx; | ||||||
|  | 
 | ||||||
|  | 		movable-area { | ||||||
|  | 			width: 100%; | ||||||
|  | 			height: 64rpx; | ||||||
|  | 
 | ||||||
|  | 			.movable-view { | ||||||
|  | 				width: 100upx; | ||||||
|  | 				height: 74rpx; | ||||||
|  | 				border-radius: 40upx; | ||||||
|  | 				// background-color: #699A70; | ||||||
|  | 				background-image: url(../../static/image/slider-verify/icon-button-normal.png); | ||||||
|  | 				background-repeat: no-repeat; | ||||||
|  | 				background-size: auto 30upx; | ||||||
|  | 				background-position: center; | ||||||
|  | 				position: relative; | ||||||
|  | 				z-index: 100; | ||||||
|  | 				border: 1px solid transparent; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.btn_info { | ||||||
|  | 		background-color: #878787 !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.btn_error { | ||||||
|  | 		background-color: #fd343c !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.btn_success { | ||||||
|  | 		background-color: #699a70 !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.error_bg { | ||||||
|  | 		background-color: #ffb7ba !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.success_bg { | ||||||
|  | 		background-color: #aad0b0 !important; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.huadao { | ||||||
|  | 		width: 100%; | ||||||
|  | 		// width: 320px; | ||||||
|  | 		height: 66upx; | ||||||
|  | 		line-height: 66upx; | ||||||
|  | 		background: #ededed; | ||||||
|  | 		// box-shadow: inset 0 0 5upx #EDEDED; | ||||||
|  | 		border-radius: 40rpx; | ||||||
|  | 		color: #bcbcbc; | ||||||
|  | 		text-align: center; | ||||||
|  | 		box-sizing: border-box; | ||||||
|  | 		position: absolute; | ||||||
|  | 		top: 7rpx; | ||||||
|  | 		left: 0; | ||||||
|  | 		font-size: 28rpx; | ||||||
|  | 		z-index: 99; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.huadao_done { | ||||||
|  | 		height: 66upx; | ||||||
|  | 		line-height: 66upx; | ||||||
|  | 		background: #aad0b0; | ||||||
|  | 		border-radius: 40rpx; | ||||||
|  | 		text-align: center; | ||||||
|  | 		box-sizing: border-box; | ||||||
|  | 		position: absolute; | ||||||
|  | 		top: 7rpx; | ||||||
|  | 		left: 0; | ||||||
|  | 		font-size: 28rpx; | ||||||
|  | 		z-index: 99; | ||||||
|  | 	} | ||||||
|  | </style> | ||||||
| @ -1,15 +1,20 @@ | |||||||
| <script setup> | <script setup> | ||||||
| import { useRouter, useRoute } from 'vue-router'; | import { useRouter, useRoute } from 'vue-router'; | ||||||
|  | import Vcode from "vue3-puzzle-vcode"; | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import countryCode from '../countryRegion/data/index.js' | 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 {authStore} from "@/stores/auth/index.js"; | ||||||
| import {message} from '@/components/x-message/useMessage.js' | import {message} from '@/components/x-message/useMessage.js' | ||||||
| import {fddCheck} from "~/api/goods/index.js"; | import {fddCheck} from "~/api/goods/index.js"; | ||||||
|  | import zu6020 from '@/static/images/zu6020@2x.png' | ||||||
|  | import YourPuzzleComponent from '@/components/YourPuzzleComponent.vue' | ||||||
| const {userInfo,token,selectedZone}= authStore() | const {userInfo,token,selectedZone}= authStore() | ||||||
| const router = useRouter(); | const router = useRouter(); | ||||||
| const route = useRoute(); | const route = useRoute(); | ||||||
| const { locale } = useI18n() | const { locale } = useI18n() | ||||||
|  | const imgs=ref([zu6020]) | ||||||
|  | 
 | ||||||
| definePageMeta({ | definePageMeta({ | ||||||
|   name: 'login', |   name: 'login', | ||||||
|   i18n: 'login.title' |   i18n: 'login.title' | ||||||
| @ -79,21 +84,45 @@ onMounted(()=>{ | |||||||
|   selectedCountry.value=route.query.countryName || defaultCountry.name |   selectedCountry.value=route.query.countryName || defaultCountry.name | ||||||
| }) | }) | ||||||
| const vanSwipeRef=ref(null) | const vanSwipeRef=ref(null) | ||||||
|  | const captcha=ref({ | ||||||
|  |   "nonceStr": "397b5e08168c31c4",  | ||||||
|  | "blockX": 256  | ||||||
|  | }) | ||||||
|  | const captchaUrl=ref('') | ||||||
|  | const captchaVerifyUrl=ref('') | ||||||
|  | const blockY=ref(0) | ||||||
| const getCode =async () => { | const getCode =async () => { | ||||||
|   loadingRef.value.loading1=true | loadingRef.value.loading1=true | ||||||
|  const res=await senCode({ |   const res=await userCaptcha({ | ||||||
|    telNum:phoneNum.value, | "canvasWidth": 320, //画布的宽度(canvasWidth大于41并且(canvasWidth-10)/2 - 1> blockWidth) | ||||||
|    zone:selectedZone.value | "canvasHeight": 191, //画布的高度(canvasHeight大于26并且 canvasHeight - blockHeight > 11 | ||||||
|  }) | "blockWidth": 50, //块图像的宽度(blockWidth大于14) | ||||||
|   loadingRef.value.loading1=false | "blockHeight": 50, //块图像的高度(blockHeight大于14) | ||||||
|  | // "blockRadius": 25, //块图像的圆角半径 | ||||||
|  | "place": 0 //图像来源标识,0表示URL下载,1表示本地文件  一般用0 | ||||||
|  |   }) | ||||||
|   if (res.status===0){ |   if (res.status===0){ | ||||||
| 
 |     captchaUrl.value=`data:image/png;base64,${res.data.canvasSrc}` | ||||||
| 
 |     captchaVerifyUrl.value=`data:image/png;base64,${res.data.blockSrc}` | ||||||
|  |     blockY.value=res.data.blockY | ||||||
|  |     captcha.value.nonceStr=res.data.nonceStr | ||||||
|  |     isShow.value=true | ||||||
|  |     loadingRef.value.loading1=false | ||||||
|   } |   } | ||||||
|   pane.value = 1 | //   loadingRef.value.loading1=true | ||||||
|   vanSwipeRef.value?.swipeTo(pane.value) | //  const res=await senCode({ | ||||||
|  | //    telNum:phoneNum.value, | ||||||
|  | //    zone:selectedZone.value | ||||||
|  | //  }) | ||||||
|  | //   loadingRef.value.loading1=false | ||||||
|  | //   if (res.status===0){ | ||||||
| 
 | 
 | ||||||
|   startCountdown(); | 
 | ||||||
|  | //   } | ||||||
|  | //   pane.value = 1 | ||||||
|  | //   vanSwipeRef.value?.swipeTo(pane.value) | ||||||
|  |   | ||||||
|  | //   startCountdown(); | ||||||
| } | } | ||||||
| const goBack = () => { | const goBack = () => { | ||||||
|   code.value = '' |   code.value = '' | ||||||
| @ -146,6 +175,41 @@ onMounted(() => { | |||||||
| onUnmounted(() => { | onUnmounted(() => { | ||||||
|   window.removeEventListener('resize', () => {}) |   window.removeEventListener('resize', () => {}) | ||||||
| }) | }) | ||||||
|  | const isShow=ref(false) | ||||||
|  | const onSuccess=()=>{ | ||||||
|  |   // userCaptchaValidate() | ||||||
|  |   isShow.value=false | ||||||
|  | } | ||||||
|  | const onClose=()=>{ | ||||||
|  |   isShow.value=false | ||||||
|  | } | ||||||
|  | const onLeave =async (moveX, callback) => { | ||||||
|  |    loadingRef.value.loading1=true | ||||||
|  |  const res=await senCode({ | ||||||
|  |    telNum:phoneNum.value, | ||||||
|  |    zone:selectedZone.value, | ||||||
|  |    verifyCaptcha:{ | ||||||
|  |     blockX:moveX, | ||||||
|  |     nonceStr:captcha.value.nonceStr | ||||||
|  |    } | ||||||
|  |  }) | ||||||
|  |   loadingRef.value.loading1=false | ||||||
|  |   if (res.status===408){ | ||||||
|  |   callback(false) | ||||||
|  |   getCode() | ||||||
|  |   }else{ | ||||||
|  |     callback(true) | ||||||
|  |     setTimeout(() => { | ||||||
|  |         pane.value = 1 | ||||||
|  |   vanSwipeRef.value?.swipeTo(pane.value) | ||||||
|  |   startCountdown(); | ||||||
|  |   isShow.value=false | ||||||
|  |     }, 1000) | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  |   | ||||||
|  | 
 | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
| @ -221,6 +285,15 @@ onUnmounted(() => { | |||||||
|     <div v-if="!isKeyboardVisible" class="text-center text-14px absolute left-1/2 transform translate-x--1/2 bottom-20px"> |     <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> |       {{ $t('login.agreement') }}<span class="text-#3454AF " @click="$router.push('/privacyPolicy')">{{ $t('login.privacyPolicy') }}</span> | ||||||
|     </div> |     </div> | ||||||
|  |     <van-popup v-model:show="isShow" round> | ||||||
|  |       <YourPuzzleComponent | ||||||
|  |       :blockY="blockY" | ||||||
|  |       :bgImageUrl="captchaUrl" | ||||||
|  |       :sliderImageUrl="captchaVerifyUrl"  | ||||||
|  |       @leave="onLeave" | ||||||
|  |     /> | ||||||
|  |     </van-popup> | ||||||
|  | 
 | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -238,4 +311,15 @@ onUnmounted(() => { | |||||||
|   width: 41px; |   width: 41px; | ||||||
|   height: 41px; |   height: 41px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .verify-popup-content { | ||||||
|  |   width: 90vw; | ||||||
|  |   max-width: 350px; | ||||||
|  |   padding: 20px; | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | :deep(.van-popup) { | ||||||
|  |   background: transparent; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  | |||||||
							
								
								
									
										76
									
								
								app/pages/publicLiveRoom/components/broadcast/index.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										123
									
								
								app/pages/publicLiveRoom/index.client.vue
									
									
									
									
									
										Normal 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> | ||||||
| @ -1,6 +1,9 @@ | |||||||
| import { setupHttp } from '@/api/http' | 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(() => { | export default defineNuxtPlugin(() => { | ||||||
|   setupHttp() |   setupHttp() | ||||||
|   setupHttp1() |   setupHttp1() | ||||||
|  |   setupHttp2() | ||||||
| }) | }) | ||||||
|  | 
 | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								app/static/images/reset.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
| @ -248,6 +248,7 @@ export const liveStore = createGlobalState(() => { | |||||||
|         } |         } | ||||||
|         } |         } | ||||||
|     return{ |     return{ | ||||||
|  |         decryptUtils, | ||||||
|         wsClient, |         wsClient, | ||||||
|         fullLive, |         fullLive, | ||||||
|         isMinWindow, |         isMinWindow, | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								app/stores/public/index.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						| @ -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 | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -38,7 +38,8 @@ | |||||||
|     "vue-demi": "^0.14.10", |     "vue-demi": "^0.14.10", | ||||||
|     "vue-pdf-embed": "^2.1.2", |     "vue-pdf-embed": "^2.1.2", | ||||||
|     "vue-router": "^4.5.0", |     "vue-router": "^4.5.0", | ||||||
|     "vue-signature-pad": "^3.0.2" |     "vue-signature-pad": "^3.0.2", | ||||||
|  |     "vue3-puzzle-vcode": "1.1.6-nuxt" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@iconify-json/carbon": "^1.2.5", |     "@iconify-json/carbon": "^1.2.5", | ||||||
|  | |||||||
| @ -77,6 +77,9 @@ importers: | |||||||
|       vue-signature-pad: |       vue-signature-pad: | ||||||
|         specifier: ^3.0.2 |         specifier: ^3.0.2 | ||||||
|         version: 3.0.2(vue@3.5.13(typescript@5.7.3)) |         version: 3.0.2(vue@3.5.13(typescript@5.7.3)) | ||||||
|  |       vue3-puzzle-vcode: | ||||||
|  |         specifier: 1.1.6-nuxt | ||||||
|  |         version: 1.1.6-nuxt | ||||||
|     devDependencies: |     devDependencies: | ||||||
|       '@iconify-json/carbon': |       '@iconify-json/carbon': | ||||||
|         specifier: ^1.2.5 |         specifier: ^1.2.5 | ||||||
| @ -809,8 +812,8 @@ packages: | |||||||
|     resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} |     resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} | ||||||
|     engines: {node: '>= 16'} |     engines: {node: '>= 16'} | ||||||
| 
 | 
 | ||||||
|   '@intlify/shared@11.1.1': |   '@intlify/shared@11.1.2': | ||||||
|     resolution: {integrity: sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==} |     resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==} | ||||||
|     engines: {node: '>= 16'} |     engines: {node: '>= 16'} | ||||||
| 
 | 
 | ||||||
|   '@intlify/unplugin-vue-i18n@6.0.3': |   '@intlify/unplugin-vue-i18n@6.0.3': | ||||||
| @ -4618,6 +4621,9 @@ packages: | |||||||
|     peerDependencies: |     peerDependencies: | ||||||
|       vue: ^3.2.0 |       vue: ^3.2.0 | ||||||
| 
 | 
 | ||||||
|  |   vue3-puzzle-vcode@1.1.6-nuxt: | ||||||
|  |     resolution: {integrity: sha512-V3DrPIYznxko8jBAtZtmsNPw9QmkPnFicQ0p9B192vC3ncRv4IDazhLC7D/cY/OGq0OeqXmk2DiOcBR7dyt8GQ==} | ||||||
|  | 
 | ||||||
|   vue@3.5.13: |   vue@3.5.13: | ||||||
|     resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} |     resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @ -5342,14 +5348,14 @@ snapshots: | |||||||
| 
 | 
 | ||||||
|   '@intlify/shared@11.0.0-rc.1': {} |   '@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))': |   '@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: |     dependencies: | ||||||
|       '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2)) |       '@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/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3))) | ||||||
|       '@intlify/shared': 11.1.1 |       '@intlify/shared': 11.1.2 | ||||||
|       '@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)) | ||||||
|       '@rollup/pluginutils': 5.1.4(rollup@4.34.6) |       '@rollup/pluginutils': 5.1.4(rollup@4.34.6) | ||||||
|       '@typescript-eslint/scope-manager': 8.24.0 |       '@typescript-eslint/scope-manager': 8.24.0 | ||||||
|       '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) |       '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) | ||||||
| @ -5373,11 +5379,11 @@ snapshots: | |||||||
| 
 | 
 | ||||||
|   '@intlify/utils@0.13.0': {} |   '@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: |     dependencies: | ||||||
|       '@babel/parser': 7.26.8 |       '@babel/parser': 7.26.8 | ||||||
|     optionalDependencies: |     optionalDependencies: | ||||||
|       '@intlify/shared': 11.1.1 |       '@intlify/shared': 11.1.2 | ||||||
|       '@vue/compiler-dom': 3.5.13 |       '@vue/compiler-dom': 3.5.13 | ||||||
|       vue: 3.5.13(typescript@5.7.3) |       vue: 3.5.13(typescript@5.7.3) | ||||||
|       vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.3)) |       vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.3)) | ||||||
| @ -9896,6 +9902,8 @@ snapshots: | |||||||
|       signature_pad: 3.0.0-beta.4 |       signature_pad: 3.0.0-beta.4 | ||||||
|       vue: 3.5.13(typescript@5.7.3) |       vue: 3.5.13(typescript@5.7.3) | ||||||
| 
 | 
 | ||||||
|  |   vue3-puzzle-vcode@1.1.6-nuxt: {} | ||||||
|  | 
 | ||||||
|   vue@3.5.13(typescript@5.7.3): |   vue@3.5.13(typescript@5.7.3): | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@vue/compiler-dom': 3.5.13 |       '@vue/compiler-dom': 3.5.13 | ||||||
|  | |||||||
| @ -1,4 +1,11 @@ | |||||||
| { | { | ||||||
|   // https://nuxt.com/docs/guide/concepts/typescript |   // https://nuxt.com/docs/guide/concepts/typescript | ||||||
|   "extends": "./.nuxt/tsconfig.json" |   "extends": "./.nuxt/tsconfig.json", | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "baseUrl": ".", | ||||||
|  |     "paths": { | ||||||
|  |       "@/*": ["app/*"]  // 确保这里的路径别名配置和你的项目配置一致 | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "include": ["app/**/*"] | ||||||
| } | } | ||||||
|  | |||||||