Compare commits

...

25 Commits

Author SHA1 Message Date
xingyy
9334819414 refactor(liveRoom): 删除未使用的加密相关代码
- 移除了 cryptConfig、generateKey、decrypt 和 decryptData 等未使用的加密相关函数
- 优化了代码结构,提高了代码的可读性和维护性
2025-02-11 11:36:36 +08:00
xingyy
86198811aa feat(live): 优化直播间功能并添加画中画支持
- 更新直播源地址
- 添加画中画功能,支持视频拖动和缩放
- 实现直播加密密钥生成和数据解密
- 优化直播房间组件,支持全屏和缩略图模式
- 新增日志发送接口
2025-02-11 11:34:24 +08:00
xingyy
10cba595b0 refactor(live): 优化直播室功能和交互
- 修改艺术品状态更新逻辑,仅在未售出时允许改变状态
- 添加支付按钮点击后跳转到签名协议页面
- 优化侧边按钮组件,增加支付按钮的显示逻辑
2025-02-10 17:02:58 +08:00
xingyy
eb02645658 feat(api-collect-code): 新增离线二维码删除功能并优化相关页面
- 新增 offlineQrcodeDelete 函数用于删除离线二维码
- 在我的收藏码页面添加删除功能,支持单个二维码删除
2025-02-10 16:26:54 +08:00
xingyy
69ad600f0d feat(collectCode): 优化收款二维码功能
- 新增二维码生成和预览功能
- 添加拍品号验证逻辑
- 优化对话框样式和交互
-调整 API 调用方式
2025-02-10 15:47:26 +08:00
xingyy
34add8d226 feat(signature): 添加签名功能相关页面
- 新增签名面板页面,用于用户签名
- 新增个人信息页面,用于填写个人相关资料- 新增协议页面,用于展示拍卖规则等协议内容
-优化直播页面竞拍结束提示逻辑
- 修复国家区域选择页面路由问题
- 优化个人主页下拉刷新功能
2025-02-10 10:56:38 +08:00
xingyy
e8a89b184e refactor(live): 优化直播页面消息提示和界面显示
- 移除不必要的空行和逗号
- 统一消息提示的展示时间
- 为艺术品结束提示添加背景色和边框色
- 在竞价信息中添加货币符号
- 调整侧边按钮弹窗的动画效果
2025-02-08 17:40:54 +08:00
xingyy
f1bd2b183f perf(tangPopup): 优化滚动定位逻辑
- 移除了 setTimeout 包裹,直接执行滚动定位逻辑
- 保留了原有的滚动定位功能,没有引入新功能
2025-02-08 17:10:23 +08:00
xingyy
ad19345db6 feat(x-popup): 增加弹窗内列表自动定位功能- 在 x-popup 组件中添加 list-container 类名,用于后续定位操作
- 在 tangPopup.vue 中实现 scrollToCurrentItem 函数,用于滚动到当前选中的物品
- 监听弹窗显示状态,当弹窗显示时调用 scrollToCurrentItem 函数
2025-02-08 17:00:09 +08:00
xingyy
7e4fbc84ad refactor(components): 优化消息组件样式和动画
- 修正了 live/index.js 中的样式对象- 优化了 x-message 组件的样式计算逻辑
- 改进了 tangPopup 组件的动画效果,使过渡更快且不完全消失
2025-02-08 16:34:03 +08:00
xingyy
a791248752 refactor(app): 优化代码结构和功能
- 移除多处 console.log 语句,清理无用代码
- 在消息组件中添加 info 类型配置并实现 info 消息显示功能
- 优化 WebSocket 消息处理逻辑,提高代码可读性
2025-02-08 16:24:26 +08:00
xingyy
c86e449d52 style(tangPopup): 为正在竞拍的标识添加闪烁动画效果
- 在 tangPopup.vue 文件中,为正在竞拍的标识添加了 blink 类名
- 在样式部分新增了 .blink 类的样式,使用 keyframes 创建 fade 动
2025-02-08 15:31:02 +08:00
xingyy
36793c5c5a feat(live): 实现直播间竞拍功能并优化相关页面
- 新增 artworkBuy API 实现艺术品购买功能
- 重构 WebSocket连接逻辑,优化消息处理- 更新直播间页面,支持实时竞拍和消息提示
-调整艺术详情和用户中心页面样式
- 优化消息组件样式和展示逻辑
2025-02-08 15:21:00 +08:00
xingyy
47aa573641 refactor/artDetail: 重构艺术品详情页面
- 移除 goodStore 的使用,改为直接调用 userArtwork API
- 通过路由参数获取艺术品 UUID,动态加载详情数据
- 更新模板,使用新获取的数据渲染页面
- 修改 profile 页面跳转到艺术品详情的方式,传递 UUID 参数
2025-02-08 10:16:54 +08:00
xingyy
aec3825a3b feat(component): 优化消息组件并添加新功能
- 重构 x-message 组件,支持更多自定义选项
- 添加 artDetail 页面用于展示艺术品详情
- 修改 liveRoom 页面,接入新的消息提示功能- 优化 profile 页面布局,增加去支付按钮
- 调整 home 页面,集成新的消息系统
- 修改 websocket 插件,支持携带 token 认证
2025-02-08 10:06:21 +08:00
xingyy
5d645a8106 refactor(home): 优化首页艺术品列表功能
- 移除了 x-image 组件中的多余属性
- 更新了 ItemList 组件中的 LOT 编号显示逻辑
-将 home 页面中的 v-show 改为 v-if
- 重构了 tangPopup 组件,添加了下拉刷新和上拉加载更多功能
2025-02-06 16:29:17 +08:00
xingyy
510b839a1b feat(liveRoom): 实现直播间拍卖数据实时更新和显示
- 从 liveStore 中获取 auctionData,用于展示当前拍卖信息
- 在模板中添加拍卖数据的动态显示,包括当前价、下口价等信息
- 实现拍卖状态的实时更新和对应 UI 的变化
- 优化竞拍列表的展示逻辑,根据不同的拍卖状态显示相应内容
- 在弹窗中添加当前拍卖作品的标识和状态
2025-02-06 15:43:23 +08:00
xingyy
bd56b05e60 refactor(collect-code): 重构收藏码功能
- 修改了 API 接口命名和路径,以适应新的业务逻辑
- 优化了登录流程,增加了验证码登录方式
- 重构了个人中心页面,增加了新的功能组件
- 新增了支付相关页面和逻辑- 优化了代码结构和命名,提高了可维护性
2025-02-06 14:03:09 +08:00
xingyy
8d01653dac feat(collectCode): 新增签名功能相关页面
- 添加签名面板页面,实现签名提交和清除功能
- 更新个人信息页面布局,增加下一步按钮
- 新增协议页面,实现支付前协议展示和同意签字功能
- 在 uno.config.js 中添加自定义样式规则,用于覆盖默认按钮样式
2025-02-06 10:20:11 +08:00
xingyy
2e08e6efcb feat(collect-code): 新增收款二维码功能
- 添加新的 API接口和相关组件
- 实现用户认证和艺术品列表展示- 新增个人资料填写页面- 优化首页和登录页面样式
2025-02-05 17:00:22 +08:00
xingyy
41ad9aeed8 style:优化支付结果对话框样式
- 移除全局样式覆盖,提高代码可维护性- 调整对话框样式为局部应用,避免不必要的样式冲突
- 优化 HTML 结构,提升代码可读性
2025-01-23 20:13:06 +08:00
xingyy
ff053a5a8c feat(image): 添加 webp 图片格式支持
- 在 x-image组件中添加 webp 格式支持
- 在 nuxt.config.js 中配置 ipx 图片处理- 添加 sharp 库以支持图片格式转换
- 修改 live store 中的 show1 变量初始值
- 调整 PaymentResults 组件中 price 的默认值
2025-01-23 20:02:20 +08:00
xingyy
e30b993601 refactor: 将导入路径从波浪号(~)改为 @- 修改了多个文件中的导入路径,将 ~/ 替换为 @/
- 这个改动统一了项目中的导入路径格式,提高了代码的一致性和可维护性
2025-01-23 19:43:45 +08:00
xingyy
b876aac28a fix(components): 修复手机端点击事件导致按钮状态异常
- 在 x-button 组件中添加 event.stopPropagation() 以防止事件冒泡- 更新 SideButton 组件中的事件处理方式,确保点击事件正确触发
2025-01-23 19:41:09 +08:00
xingyy
7916b009e6 refactor(liveRoom): 重构直播室功能
- 移除不必要的导入和未使用的变量
- 优化拍卖数据获取逻辑
- 添加 WebSocket 连接和消息处理功能
- 更新侧边按钮组件,显示实时拍卖数据
-增加拍品详情弹窗功能
2025-01-23 19:29:29 +08:00
61 changed files with 2784 additions and 357 deletions

View File

@ -0,0 +1,23 @@
import { request } from '@/api/http.js'
export async function checkPhone(data) {
return await request({
url:'/api/v1/common/check/phone',
method: 'POST',
data
})
}
export async function userSend(data) {
return await request( {
url:'/api/v1/m/user/send',
method: 'POST',
data
})
}
export async function mobileLogin(data) {
return await request( {
url:'/api/v1/m/user/mobile/login',
method: 'POST',
data
})
}

View File

@ -0,0 +1,31 @@
import { request } from '@/api/http.js'
export async function offlineQrcodeList(data) {
return await request( {
url:'/api/v1/offlineQrcode/query',
method: 'POST',
data
})
}
export async function offlineQrcodeCreate(data) {
return await request ({
url:'/api/v1/offlineQrcode/create',
method: 'POST',
data
})
}
export async function offlineQrcodeDelete(data) {
return await request ({
url:'/api/v1/offlineQrcode/delete',
method: 'POST',
data
})
}
export async function userArtworks(data) {
return await request( {
url:'/api/v1/m/user/artworks',
method: 'POST',
data
})
}

View File

@ -0,0 +1,128 @@
import {useRuntimeConfig} from '#app'
import {ofetch} from 'ofetch'
import {message} from '@/components/x-message/useMessage.js'
import {codeAuthStore} from "@/stores-collect-code/auth/index.js"
let httpStatusErrorHandler
let http
// HTTP 状态码映射
const HTTP_STATUS_MAP = {
400: '请求参数错误',
401: '未授权或登录过期',
403: '访问被禁止',
404: '请求的资源不存在',
500: '服务器内部错误',
502: '网关错误',
503: '服务暂时不可用',
504: '网关超时'
}
export function setupHttp() {
if (http) return http
const {token}= codeAuthStore()
const config = useRuntimeConfig()
const baseURL = config.public.NUXT_PUBLIC_API_COLLECT_CODE
const router = useRouter()
const defaultOptions = {
baseURL,
headers: { 'Content-Type': 'application/json' },
timeout: 15000, // 15秒超时
retry: 3,
retryDelay: 1000,
}
http = ofetch.create({
...defaultOptions,
// 请求拦截
async onRequest({ options, request }) {
// 添加 token
options.headers = {
...options.headers,
Authorization: token.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 || '操作失败')
}
// 处理登录失效
if (data.status === 401) {
message.error('登录已过期,请重新登录')
token.value = '' // 清除 token
router.replace('/collectCode/login')
}
return response
},
// 响应错误处理
async onResponseError({ response, request }) {
// 网络错误
if (!response) {
message.error('网络连接失败,请检查网络设置')
return Promise.reject(new Error('网络错误'))
}
const status = response.status
const data = response._data
// 处理 HTTP 状态错误
const errorMessage = data.msg || HTTP_STATUS_MAP[status] || '请求失败'
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('HTTP client not initialized. Call setupHttp first.')
}
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

@ -30,3 +30,27 @@ export async function userArtworks(data) {
data
})
}
export async function userArtwork(data) {
return await request( {
url:'/api/v1/m/user/artwork',
method: 'POST',
data
})
}
export async function artworkBuy(data) {
return await request( {
url:'/api/v1/m/artwork/buy',
method: 'POST',
data
})
}
export async function logSendlog(data) {
return await request( {
url:'/api/v1/m/auction/log/sendlog',
method: 'POST',
data
})
}

View File

@ -51,6 +51,9 @@ provide('slideDirection', slideDirection)
</template>
<style>
:root:root {
--van-dialog-radius: 8px
}
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
@ -79,4 +82,5 @@ provide('slideDirection', slideDirection)
:root {
--safe-area-inset-bottom: env(safe-area-inset-bottom);
}
</style>

View File

@ -12,6 +12,7 @@ const show = computed(() => {
const initData=()=>{
active.value=route.path==='/profile'?1:0
}
watchEffect(initData)
onMounted(()=>{
initData()
})

View File

@ -1,5 +1,5 @@
<script setup>
import { useAppHeaderRouteNames as routeWhiteList } from '~/config'
import { useAppHeaderRouteNames as routeWhiteList } from '@/config'
import { goodStore } from "@/stores/goods/index.js";
const { fullLive } = goodStore()
const route = useRoute()

View File

@ -0,0 +1,207 @@
<template>
<div
v-show="isVisible"
:class="['floating-video', { minimized: isMinimized }]"
class="!aspect-video"
:style="[positionStyle, containerStyle]"
@touchstart.prevent="handleTouchStart"
@touchmove.prevent="handleTouchMove"
@touchend="handleTouchEnd"
>
<video
ref="videoRef"
class="video-player "
controls
@loadedmetadata="handleVideoMetadata"
>
<source src="@/static/video/example.mp4" type="video/mp4" />
您的浏览器不支持 HTML5 视频
</video>
<div class="control-bar">
<div class="minimize-btn" @click="toggleMinimize">
<span v-if="isMinimized"></span>
<span v-else></span>
</div>
<div class="close-btn" @click="closeVideo"></div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
isVisible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close'])
const videoRef = ref(null)
const aspectRatio = ref(16 / 9) //
const isMinimized = ref(false)
const position = ref({ x: 20, y: 20 })
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
// 0
const windowSize = ref({
width: 0,
height: 0
})
const handleVideoMetadata = () => {
const video = videoRef.value
if (video.videoWidth && video.videoHeight) {
aspectRatio.value = video.videoWidth / video.videoHeight
}
}
const containerDimensions = computed(() => {
const baseWidth = isMinimized.value ? 150 : 300
const height = baseWidth / aspectRatio.value
return { width: baseWidth, height }
})
const containerStyle = computed(() => {
return {
width: `${containerDimensions.value.width}px`,
height: `${containerDimensions.value.height}px`
}
})
const positionStyle = computed(() => ({
transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
}))
const handleTouchStart = (event) => {
isDragging.value = true
const touch = event.touches[0]
dragStart.value = {
x: touch.clientX - position.value.x,
y: touch.clientY - position.value.y
}
}
const handleTouchMove = (event) => {
if (!isDragging.value) return
const touch = event.touches[0]
let newX = touch.clientX - dragStart.value.x
let newY = touch.clientY - dragStart.value.y
//
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
newX = Math.max(0, Math.min(newX, maxX))
newY = Math.max(0, Math.min(newY, maxY))
requestAnimationFrame(() => {
position.value = { x: newX, y: newY }
})
}
const handleTouchEnd = () => {
isDragging.value = false
}
const toggleMinimize = () => {
isMinimized.value = !isMinimized.value
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
position.value = {
x: Math.max(0, Math.min(position.value.x, maxX)),
y: Math.max(0, Math.min(position.value.y, maxY))
}
}
const closeVideo = () => {
emit('close')
}
const handleResize = () => {
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
position.value = {
x: Math.max(0, Math.min(position.value.x, maxX)),
y: Math.max(0, Math.min(position.value.y, maxY))
}
}
//
onMounted(() => {
//
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
})
</script>
<style scoped>
/* 样式部分保持不变 */
.floating-video {
position: fixed;
z-index: 1000;
background: #000;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: width 0.3s ease, height 0.3s ease;
overflow: hidden;
transform: translate3d(0, 0, 0);
will-change: transform;
touch-action: none;
}
.video-player {
width: 100%;
height: 100%;
object-fit: fill!important;
}
.control-bar {
position: absolute;
top: 0;
right: 0;
display: flex;
gap: 8px;
padding: 8px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
}
.minimize-btn,
.close-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
cursor: pointer;
color: white;
font-size: 14px;
}
.minimize-btn:hover,
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
</style>

View File

@ -1,30 +1,28 @@
<script setup>
/*
* 此组件的目的是使用该组件包裹的内容具有按压状态效果
* */
import {ref, defineEmits} from "vue";
const emit = defineEmits(["click"]);
import { ref } from "vue";
const isButtonActive = ref(false);
const handleButtonPress = () => {
const handleButtonPress = (event) => {
event.stopPropagation();
isButtonActive.value = true;
};
const handleButtonRelease = () => {
const handleButtonRelease = (event) => {
event.stopPropagation();
isButtonActive.value = false;
emit("click")
};
</script>
<template>
<div
:class="[
'transition-all duration-200',
isButtonActive ? 'scale-95' : ''
]"
@touchstart="handleButtonPress"
@touchend="handleButtonRelease"
@touchstart.stop="handleButtonPress"
@touchend.stop="handleButtonRelease"
>
<slot></slot>
</div>
</template>
</template>

View File

@ -36,18 +36,12 @@ const showImage = () => {
<template>
<nuxt-img
v-if="src"
loading="lazy"
v-bind="{ ...props, ...$attrs }"
style="object-fit: cover"
@click="showImage"
:src="src"
:sizes="sizes"
:format="format"
:quality="quality"
placeholder
/>
<van-empty v-else description="暂无" />
</template>
<style scoped>

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

View File

@ -1,21 +1,65 @@
<script setup>
import { ref, computed } from 'vue'
import MessageContent from './message/index.vue'
import { ref } from 'vue'
const visible = ref(false)
const messageType = ref('success')
const messageText = ref('')
const messageType = ref('')
const showIcon = ref(true)
const customStyle = ref({})
const title = ref({})
const subTitle = ref({})
const containerStyle = computed(() => {
const { top, bottom, left, right, transform, ...otherStyles } = customStyle.value || {}
const baseStyle = {
position: 'fixed',
zIndex: 9999
}
const horizontalPosition = left || right
? { left, right }
: { left: '50%', transform: 'translateX(-50%)' }
const verticalPosition = {}
if (bottom !== undefined) {
verticalPosition.bottom = bottom
} else {
verticalPosition.top = top || '50px'
}
return {
...baseStyle,
...horizontalPosition,
...verticalPosition,
...otherStyles
}
})
const emit = defineEmits(['after-leave'])
const showMessage = ({ type = 'warning', message, duration = 2000 }) => {
messageText.value = message
messageType.value = type
const showMessage = (options) => {
if (typeof options === 'string') {
messageText.value = options
title.value = {}
subTitle.value = {}
} else {
messageText.value = options.message || ''
title.value = options.title || {}
subTitle.value = options.subTitle || {}
}
messageType.value = options.type || 'success'
showIcon.value = options.icon !== false
customStyle.value = options.style || {}
visible.value = true
setTimeout(() => {
visible.value = false
}, duration)
}, options.duration || 2000)
}
defineExpose({ showMessage })
</script>
@ -26,9 +70,12 @@ defineExpose({ showMessage })
>
<MessageContent
v-if="visible"
:text="messageText"
:type="messageType"
class="fixed top-50px left-1/2 -translate-x-1/2 z-9999"
:message="messageText"
:type="messageType"
:title="title"
:sub-title="subTitle"
:show-icon="showIcon"
:style="containerStyle"
/>
</transition>
</template>

View File

@ -1,22 +1,50 @@
<script setup>
import error from '../images/error.png'
import success from '../images/success.png'
import warning from '../images/warning.png'
import info from '../images/info.png'
const props = defineProps({
type: {
type: String,
default: 'success',
validator: value => ['success', 'error', 'warning'].includes(value),
default: 'success'
},
text: {
message: {
type: String,
default: '',
default: ''
},
title: {
type: Object,
default: () => ({
text: '',
color: '',
align: 'left'
})
},
subTitle: {
type: Object,
default: () => ({
text: '',
color: '',
align: 'left'
})
},
showIcon: {
type: Boolean,
default: true
},
style: {
type: Object,
default: () => ({})
}
})
const typeConfig = {
info: {
imgSrc: info,
borderColor: '#C6DFFB',
bgColor: '#ECF5FE',
},
success: {
imgSrc: success,
borderColor: '#C5E7D5',
@ -24,34 +52,70 @@ const typeConfig = {
},
error: {
imgSrc: error,
borderColor: '#F3CBD3',
bgColor: '#FBEEF1',
borderColor: '#FFD4D4',
bgColor: '#FFF0F0',
},
warning: {
imgSrc: warning,
borderColor: '#FAE0B5',
bgColor: '#FEF7ED',
},
borderColor: '#FFE2BA',
bgColor: '#FFF7EC',
}
}
// 使 props.style
const finalStyle = computed(() => {
return {
borderColor: props.style?.borderColor || typeConfig[props.type].borderColor,
backgroundColor: props.style?.backgroundColor || typeConfig[props.type].bgColor,
width: props.style?.width || '343px',
height: props.style?.height || 'auto',
minHeight: '46px',
...props.style
}
})
</script>
<template>
<div
class="box-border min-h-[46px] w-[343px] flex items-center border rounded-[4px] px-[15px] shadow-sm"
:style="{
borderColor: typeConfig[type].borderColor,
backgroundColor: typeConfig[type].bgColor,
}"
:class="`box-border flex items-center border rounded-[4px] px-[15px] shadow-sm py-8px ${!message?'justify-center':''}`"
:style="finalStyle"
>
<div class="mr-[9px] h-[20px] w-[20px]">
<div v-if="showIcon" class="mr-[12px]">
<img
:src="typeConfig[type].imgSrc"
class="h-full w-full"
class="w-20px h-20px"
style="object-fit: contain"
alt=""
>
</div>
<div class="text-[14px] text-black leading-normal">
{{ text }}
<div class="flex flex-col justify-center">
<!-- 如果是简单文本模式 -->
<div v-if="message" class="text-[14px] line-height-none">
{{ message }}
</div>
<!-- 如果是标题+副标题模式 -->
<template v-else>
<div
v-if="title.text"
class="text-[14px] line-height-20px"
:style="{
color: title.color || 'inherit',
textAlign: title.align || 'left'
}"
>
{{ title.text }}
</div>
<div
v-if="subTitle.text"
class="text-[12px] leading-normal mt-1 line-height-17px"
:style="{
color: subTitle.color || '#939393',
textAlign: subTitle.align || 'left'
}"
>
{{ subTitle.text }}
</div>
</template>
</div>
</div>
</template>

View File

@ -2,22 +2,60 @@ import { createApp, nextTick } from 'vue'
import MessagePopup from './index.vue'
const message = {
success(text, duration = 2000) {
success(options, duration = 2000) {
if (process.client) {
this.show({ type: 'success', message: text, duration })
if (typeof options === 'string') {
this.show({ type: 'success', message: options, duration })
} else {
this.show({
type: 'success',
...options,
duration
})
}
}
},
error(text, duration = 2000) {
error(options, duration = 2000) {
if (process.client) {
this.show({ type: 'error', message: text, duration })
if (typeof options === 'string') {
this.show({ type: 'error', message: options, duration })
} else {
this.show({
type: 'error',
...options,
duration
})
}
}
},
warning(text, duration = 2000) {
info(options, duration = 2000) {
if (process.client) {
this.show({ type: 'warning', message: text, duration })
if (typeof options === 'string') {
this.show({ type: 'info', message: options, duration })
} else {
this.show({
type: 'error',
...options,
duration
})
}
}
},
show({ type = 'success', message, duration = 2000 }) {
warning(options, duration = 2000) {
if (process.client) {
if (typeof options === 'string') {
this.show({ type: 'warning', message: options, duration })
} else {
this.show({
type: 'warning',
...options,
duration
})
}
}
},
show(options) {
if (!process.client) return
const container = document.createElement('div')
@ -32,11 +70,7 @@ const message = {
const instance = app.mount(container)
nextTick(() => {
instance.showMessage?.({
type,
message,
duration
})
instance.showMessage?.(options)
})
}
}

View File

@ -18,10 +18,13 @@ const close=()=>{
<template>
<van-popup
:show="show"
:transition-appear="true"
teleport="#__nuxt"
position="bottom"
@click-overlay="close"
:style="{ height: '74%' }"
v-bind="{...$attrs,...$props}"
:safe-area-inset-bottom="true"
>
<div class="flex flex-col h-full">
<!-- 标题栏 -->
@ -41,7 +44,7 @@ const close=()=>{
<!-- 内容区域 -->
<div class="flex-1 px-16px py-18px overflow-hidden relative">
<div class="h-full overflow-y-auto relative">
<div class="h-full overflow-y-auto relative list-container">
<slot/>
</div>
</div>

View File

@ -0,0 +1,109 @@
<script setup>
import { ref, computed } from 'vue'
import dayjs from 'dayjs'
const props = defineProps({
modelValue: {
type: [Date, String, Number],
default: () => new Date() //
},
label: {
type: String,
default: '日期'
},
required: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请选择日期'
},
disabled: {
type: Boolean,
default: false
},
minDate: {
type: Date,
default: () => new Date(1900, 0, 1)
},
maxDate: {
type: Date,
default: () => new Date(2100, 11, 31)
},
format: {
type: String,
default: 'YYYY-MM-DD'
}
})
const emit = defineEmits(['update:modelValue', 'change'])
const show = ref(false)
//
const displayText = computed(() => {
return dayjs(props.modelValue).format(props.format)
})
//
const defaultValue = computed(() => {
const date = props.modelValue || new Date()
return [
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
]
})
//
const onConfirm = ({ selectedValues }) => {
show.value = false
const date = new Date(selectedValues[0], selectedValues[1] - 1, selectedValues[2])
emit('update:modelValue', date)
emit('change', date)
}
//
const onCancel = () => {
show.value = false
}
//
const reset = () => {
emit('update:modelValue', new Date())
}
defineExpose({
reset
})
</script>
<template>
<div>
<van-field
:model-value="displayText"
@click="show = true"
readonly
:disabled="disabled"
:required="required"
:placeholder="placeholder"
:label="label"
class="mb-10px"
is-link
/>
<van-popup
v-model:show="show"
position="bottom"
>
<van-date-picker
:min-date="minDate"
:max-date="maxDate"
:model-value="defaultValue"
@confirm="onConfirm"
@cancel="onCancel"
title="选择日期"
/>
</van-popup>
</div>
</template>

View File

@ -0,0 +1,88 @@
<script setup>
import { ref } from 'vue'
const props = defineProps({
value: {
type: [Number, String]
},
columns: {
type: Array,
default: () => []
},
label: {
type: String,
default: ''
},
required: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:value', 'change'])
const show = ref(false)
const onConfirm = (value) => {
show.value = false
emit('update:value', value.value)
emit('change', value)
}
const displayText = computed(() => {
const selected = props.columns.find(x => x.value === props.value)
return selected?.text || ''
})
const reset = () => {
emit('update:value', undefined)
}
defineExpose({
reset
})
</script>
<template>
<div>
<van-field
:model-value="displayText"
@click="show = true"
readonly
:disabled="disabled"
:required="required"
:placeholder="placeholder"
:label="label"
class="mb-10px"
is-link
/>
<van-popup
v-model:show="show"
destroy-on-close
position="bottom"
safe-area-inset-bottom
>
<van-picker
:columns="columns"
@confirm="onConfirm"
@cancel="show = false"
:default-index="columns.findIndex(x => x.value === value)"
title="请选择"
confirm-button-text="确定"
cancel-button-text="取消"
/>
</van-popup>
</div>
</template>

View File

@ -0,0 +1,101 @@
<script setup>
import itemDetail from '@/components/itemDetail/index.vue'
import {userArtwork} from "~/api/goods/index.js";
const route = useRoute()
const detail = ref({})
const uuid = route.query.uuid
const initData = async () => {
const res = await userArtwork({uuid})
if (res.status === 0) {
detail.value = res.data
}
}
const position = ref({x: window?.innerWidth - 120 || 0, y: 240}) //
const startPosition = ref({x: 0, y: 0})
const isDragging = ref(false)
const startDrag = (e) => {
isDragging.value = true
const clientX = e.touches ? e.touches[0].clientX : e.clientX
const clientY = e.touches ? e.touches[0].clientY : e.clientY
startPosition.value = {
x: clientX - position.value.x,
y: clientY - position.value.y
}
}
const onDrag = (e) => {
if (isDragging.value) {
const clientX = e.touches ? e.touches[0].clientX : e.clientX
const clientY = e.touches ? e.touches[0].clientY : e.clientY
//
const maxX = window.innerWidth - 108 //
const maxY = window.innerHeight - 137 //
//
const x = Math.min(Math.max(0, clientX - startPosition.value.x), maxX)
const y = Math.min(Math.max(0, clientY - startPosition.value.y), maxY)
position.value = {x, y}
}
}
const stopDrag = () => {
isDragging.value = false
}
onMounted(() => {
//
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
//
document.addEventListener('touchmove', onDrag)
document.addEventListener('touchend', stopDrag)
})
onUnmounted(() => {
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchmove', onDrag)
document.removeEventListener('touchend', stopDrag)
})
initData()
</script>
<template>
<div class="relative h-screen-nav flex flex-col">
<itemDetail class="grow-1" :detail-info="detail.auctionArtworkInfo"/>
<div v-if="[1,3,4].includes(detail.status)" class="h-81px bg-#fff flex justify-center pt-7px">
<van-button class="w-213px !h-38px" type="primary">
<span class="text-#fff text-14px">去支付 RMB10,000</span>
</van-button>
</div>
<div
class="w-108px h-137px absolute cursor-move"
:style="{
left: position.x + 'px',
top: position.y + 'px'
}"
@mousedown="startDrag"
@touchstart.prevent="startDrag"
>
<img src="@/static/images/zd5530@2x.png" class="w-full h-full" alt="">
<div
class="flex flex-col items-center absolute bottom-25px text-14px text-#B58047 left-1/2 transform translate-x--1/2 whitespace-nowrap">
<div>恭喜您</div>
<div>竞拍成功</div>
</div>
</div>
</div>
</template>
<style scoped>
.cursor-move {
touch-action: none;
user-select: none;
}
</style>

View File

@ -2,18 +2,15 @@
import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'
import { senCode, userLogin } from "@/api/auth/index.js";
import { authStore } from "~/stores/auth/index.js";
import { codeAuthStore } from "@/stores-collect-code/auth/index.js";
import { message } from '@/components/x-message/useMessage.js'
// ... ...
import FingerprintJS from '@fingerprintjs/fingerprintjs'
const { userInfo, token,fingerprint } = authStore()
import {checkPhone, mobileLogin, userSend} from "@/api-collect-code/auth/index.js";
const { userInfo, token,fingerprint } = codeAuthStore()
const router = useRouter();
const route = useRoute();
const { locale } = useI18n()
definePageMeta({
title: '登录',
i18n: 'login.title',
})
const loadingRef = ref({
loading1: false,
loading2: false,
@ -56,11 +53,19 @@ checkFingerprint()
const vanSwipeRef = ref(null)
const getCode = async () => {
loadingRef.value.loading1 = true
const res = await senCode({
telNum: phoneNum.value,
zone: '86'
const res = await checkPhone({
tel: phoneNum.value,
})
loadingRef.value.loading1 = false
if (res.status === 0){
const res=await userSend({telNum:phoneNum.value,zone:'+86'})
if (res.status === 0){
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value = true
}
}
/* loadingRef.value.loading1 = false
if (res.status === 0) {
@ -68,7 +73,7 @@ const getCode = async () => {
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value = true
startCountdown();
startCountdown();*/
/* pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value=true
@ -85,10 +90,10 @@ const goBack = () => {
}
const goLogin = async () => {
loadingRef.value.loading2 = true
const res = await userLogin({
telNum: phoneNum.value,
zone: '86',
code: code.value
const res = await mobileLogin({
TelNum: phoneNum.value,
Password:loginType.value===1?password.value:'',
Code: loginType.value===0?code.value:''
})
if (res.status === 0) {
userInfo.value = res.data.accountInfo
@ -114,7 +119,7 @@ const goLogin = async () => {
<div v-show="pane === 0">
<div class="">
<div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="phoneNum" clearable :placeholder="$t('login.phonePlaceholder')">
<van-field v-model="phoneNum" clearable placeholder="请输入手机号">
<template #label>
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
手机号
@ -124,7 +129,7 @@ const goLogin = async () => {
</div>
<div class="border-b-[1.7px] mt-[8px]" v-show="loginType === 1">
<van-field v-model="password" clearable :placeholder="$t('login.passwordPlaceholder')">
<van-field v-model="password" clearable placeholder="请输入密码">
<template #label>
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
密码
@ -137,27 +142,19 @@ const goLogin = async () => {
<div class="text-[14px] text-[#2B53AC]">
{{ loginType === 0 ? '密码登录' : '验证码登录' }}
</div>
</div>
<div />
</div>
<div class="mt-[55px]">
<div v-if="loginType === 0">
<van-button :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('login.getCode')"
type="primary" 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>
<van-button :loading="loadingRef.loading1" v-if="phoneNum" loading-text="获取验证码"
type="primary" block style="height: 48px" @click="getCode">获取验证码</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">获取验证码</van-button>
</div>
<div v-else>
<van-button type="primary" v-if="password" block :loading="loadingRef.loading2" :loading-text="$t('login.login')"
style="height: 48px;margin-top:10px" @click="goLogin">{{
$t('login.login')
}}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{
$t('login.login')
}}</van-button>
<van-button type="primary" v-if="password" block :loading="loadingRef.loading2" loading-text="登录"
style="height: 48px;margin-top:10px" @click="goLogin">登录</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">登录</van-button>
</div>
</div>
</div>
@ -166,7 +163,7 @@ const goLogin = async () => {
<div v-show="pane === 1">
<div class="flex mb-[16px]">
<div class="text-[16px] text-[#BDBDBD] mr-[10px]">{{ $t('login.hasSendTo') }}</div>
<div class="text-[16px] text-[#000]">+{{ selectedZone }} {{ phoneNum }}</div>
<div class="text-[16px] text-[#000]">+86 {{ phoneNum }}</div>
</div>
<van-password-input :value="code" :gutter="10" :mask="false" focused @focus="showKeyboard = true" />
<div :class="`${countdown > 0 ? 'text-#BDBDBD' : 'text-#2B53AC'} text-14px`">

View File

@ -0,0 +1,72 @@
<script setup>
import XImage from "@/components/x-image/index.vue";
import {useRuntimeConfig} from "#app";
import QRCode from 'qrcode'
import { showImagePreview } from 'vant';
import {offlineQrcodeDelete} from "~/api-collect-code/goods/index.js";
const statusLabel=[
{label:'已付款',value:2,color:'#18A058'}, {label:'未付款',value:1,color:'#CF3050'}, {label:'已部分付款',value:4,color:'#F09F1F'}
]
const props = defineProps({
data: {
type: Object,
default: () => {
return {};
},
},
});
const itemLabel=(data)=>{
return statusLabel.find(x=>x.value===data.payStatus)
}
const config = useRuntimeConfig()
const getQRBase64 = async () => {
try {
return await QRCode.toDataURL(`${config.public.NUXT_PUBLIC_API_BASE}/collectCode/payment`, {
width: 200,
margin: 4,
errorCorrectionLevel: 'H'
})
} catch (err) {
console.error('生成二维码失败:', err)
return null
}
}
const openQrCode=async ()=>{
const base64=await getQRBase64()
showImagePreview([base64])
}
</script>
<template>
<div class="flex flex-col h-120px bg-#F7F7F7 rounded-4px px-13px">
<div class="flex h-40px border-b border-b-#F0F0F0 items-center justify-between px-8px">
<div class="text-14px text-#000">¥ {{data.paidPrice}}/{{data.price}}</div>
<div :class="`text-12px text-${itemLabel(data).color}`">{{itemLabel(data).label}}</div>
</div>
<div class="flex flex-grow-1 px-8px py-11px">
<div class="mr-8px">
<XImage class="w-57px h-56px rounded-4px" :src="data.hdPic"></XImage>
</div>
<div class="text-12px text-#1E1E1E">
<div>Lot{{ data.lotNo }}</div>
<div>创建人{{ data.userName }}</div>
<div>创建时间{{data.createdAt}}</div>
</div>
<div class="flex flex-col justify-end ml-auto ">
<div class="flex w-55px h-26px bg-#2B53AC rounded-4px justify-center items-center">
<div @click="openQrCode" class="text-12px text-#fff line-height-none mt-0.5px mr-5px">查看</div>
<div >
<img class="w-12px h-12px" src="@/static/images/icon-design-42@3x.png" alt="">
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,24 +1,102 @@
<script setup>
import { userArtworks } from "~/api/goods/index.js";
import { authStore } from "~/stores/auth/index.js";
import { userArtworks } from "@/api/goods/index.js";
import { codeAuthStore } from "@/stores-collect-code/auth/index.js";
import { showImagePreview } from 'vant';
import XImage from '@/components/x-image/index.vue'
import {useRouter} from "#vue-router";
import {goodStore} from "~/stores-collect-code/goods/index.js";
import {ref} from "vue";
import {offlineQrcodeCreate, offlineQrcodeDelete, offlineQrcodeList} from "~/api-collect-code/goods/index.js";
import codeCard from './components/codeCard/index.vue'
import {message} from "~/components/x-message/useMessage.js";
definePageMeta({
layout: 'default',
title: '收款二维码',
i18n: 'menu.profile',
})
const { userInfo } = authStore()
const initData = async () => {
const res = await userArtworks({})
if (res.status === 0) {
const router = useRouter();
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
})
const { userInfo, } = codeAuthStore()
const {getOfflineQrcodeList,itemList, loading: storeLoading,pageRef}= goodStore()
const initData = async () => {
onRefresh()
}
const show=ref(false)
const close=()=>{
console.log('show',show.value)
show.value=false
}
const logOut=()=>{
localStorage.clear()
router.push('/collectCode/login')
}
const createForm=ref({
lotNo:'',
price:'',
})
const confirm=async ()=>{
if (!createForm.value.price){
message.warning('请输入金额')
return false
}else if (!createForm.value.lotNo){
message.warning('请输入Lot号')
return false
}
const res=await offlineQrcodeCreate({...createForm.value,price:String(createForm.value.price)})
if (res.status===0){
show.value=false
}
}
const onRefresh = async () => {
try {
localState.value.refreshing = true
localState.value.finished = false
const { finished } = await getOfflineQrcodeList(true)
localState.value.finished = finished
} finally {
localState.value.refreshing = false
}
}
const loadMore = async () => {
pageRef.value.page++
const { finished } = await getOfflineQrcodeList()
localState.value.finished = finished
}
const abnormal=ref(false)
const abnormalRow=ref({})
const inputLotNo=async (data)=>{
const res=await offlineQrcodeList({
lotNo:createForm.value.lotNo
})
if (res.status===0){
if (res.data.Data?.length>0){
abnormal.value=true
abnormalRow.value=res.data.Data?.[0]
}
}
}
const deleteData=async (qrUid)=>{
const res=await offlineQrcodeDelete({
qrUid:qrUid
})
if (res.status===0){
getOfflineQrcodeList()
message.success('删除成功')
}
}
initData()
</script>
<template>
<div class="w-[100vw] bg-[url('@/static/images/3532@2x.png')] bg-cover pt-43px flex-grow-1 flex flex-col">
<div class="w-[100vw] bg-[url('@/static/images/3532@2x.png')] h-screen-nav bg-cover pt-43px flex-grow-1 flex flex-col">
<div class="flex items-center px-16px mb-43px">
<div class="mr-23px">
<img class="w-57px h-57px" src="@/static/images/5514@2x.png" alt="">
@ -27,29 +105,106 @@ initData()
<div class="text-18px text-#181818">{{ userInfo.realName }}</div>
<div class="text-#575757 text-14px">{{ userInfo.telNum }}</div>
</div>
<div class="flex-grow-1 flex justify-end">
<div class="grow-1 flex justify-end" @click="logOut">
<img class="w-40px h-40px" src="@/static/images/logout.png" alt="">
</div>
</div>
<div class="flex-grow-1">
<div class="border-b-1px border-b-#D3D3D3 px-16px flex">
<div class="text-#000 text-16px border-b-3 border-b-#2B53AC h-36px">线下付款二维码 </div>
</div>
<van-pull-refresh>
<van-list finished-text="没有更多了">
<van-swipe-cell>
<van-cell :border="false" title="单元格" value="内容" />
<template #right>
<van-button square type="danger" text="删除" />
<van-button square type="primary" text="收藏" />
</template>
</van-swipe-cell>
</van-list>
</van-pull-refresh>
<div class="border-b-1px border-b-#D3D3D3 px-16px flex">
<div class="text-#000 text-16px border-b-3 border-b-#2B53AC h-36px">线下付款二维码 </div>
</div>
<div class="grow-1 flex flex-col overflow-hidden py-15px">
<div class="overflow-auto">
<van-pull-refresh v-model="localState.refreshing"
success-text="刷新成功"
:success-duration="700"
@refresh="onRefresh">
<van-list v-model:loading="storeLoading"
:finished="localState.finished"
finished-text="没有更多了"
@load="loadMore" class="px-14px">
<template v-for="(item,index) of itemList" :key="item.qrUid">
<template v-if="item.payStatus===1">
<van-swipe-cell class="mb-14px" >
<codeCard :data="item"></codeCard>
<template #right>
<div class="w-65px h-full bg-#CF3050 flex items-center justify-center" @click="deleteData(item.qrUid)">
<img class="w-22px h-24px" src="@/static/images/delete3@.png" alt="">
</div>
</template>
</van-swipe-cell>
</template>
<template v-else>
<div class="mb-14px">
<codeCard :data="item"></codeCard>
</div>
</template>
</template>
</van-list>
</van-pull-refresh>
</div>
</div>
<div class="h-81px w-full flex justify-center shrink-0 pt-10px">
<div class="w-213px h-38px bg-#2B53AC text-#fff flex justify-center items-center text-14px rounded-4px" @click="show=true">
新增
</div>
</div>
<van-dialog v-model:show="show">
<div class="pt-18px pb-24px px-24px">
<div class="text-16px text-#000 font-bold text-center mb-26px">新增收款二维码</div>
<div class="">
<div class="flex mb-6px items-center">
<div class="w-58px">
<div class="text-#1A1A1A text-16px">金额</div>
<div class="text-#939393 text-12px">RMB</div>
</div>
<div>
<input v-model="createForm.price" type="number"
class="w-214px h-48px bg-#F3F3F3 rounded-4px px-11px text-16px" placeholder="请输入金额">
</div>
</div>
<div class="flex items-center">
<div class="w-58px">
<div class="text-#1A1A1A text-16px">Lot号</div>
</div>
<div>
<input type="number" v-model="createForm.lotNo" @input="inputLotNo" class="w-214px h-48px bg-#F3F3F3 rounded-4px px-11px text-16px" placeholder="请输入拍品序号">
</div>
</div>
</div>
<div class="flex flex-col items-center" v-if="abnormal">
<div class="text-#CF3050 text-12px mb-8px mt-4px">*该拍品号当前已存在收款二维码确定要创建吗</div>
<div>
<XImage class="w-116px h-116px rounded-4px mb-9px" :src="abnormalRow.hdPic"></XImage>
<div class="text-12px text-#575757 flex flex-col items-center">
<div>日出而作日落而息</div>
<div>张天赐</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="border-t flex">
<van-button class="w-50% h-56px" style="border: none;border-radius: 0;border-right: 1.5px solid #E7E7E7" @click="show=false">
<span class="text-#000 text-16px text-center">取消</span>
</van-button>
<van-button class="w-50% h-56px !rounded-0" style="border: none;border-radius: 0" @click="confirm">
<span class="text-#000 text-16px text-center text-#2B53AC">确定</span>
</van-button>
</div>
</template>
</van-dialog>
</div>
</template>
<style scoped>
<style scoped lang="scss">
:deep(.van-hairline--top.van-dialog__footer){
&>.van-button{
border-top: 1px solid #E7E7E7;
&.van-dialog__cancel{
border-right: 1px solid #E7E7E7;
}
}
}
</style>

View File

@ -0,0 +1,43 @@
<script setup>
const payStatus=ref(0)
const changePayStatus=()=>{
payStatus.value=payStatus.value===0?1:0
}
const validateInput = (e) => {
const value = e.target.value
const char = String.fromCharCode(e.charCode)
if (!/[\d.]/.test(char)) {
e.preventDefault()
return
}
if (char === '.' && (value.includes('.') || !value)) {
e.preventDefault()
return
}
if (value.includes('.') && value.split('.')[1]?.length >= 2) {
e.preventDefault()
return
}
}
</script>
<template>
<div class="w-[100vw] h-screen-nav bg-[url('@/static/images/3532@2x.png')] bg-cover flex-grow-1 flex flex-col items-center pt-183px">
<div class="mb-30px">
<img class="w-126px h-126px" src="@/static/images/dddf34@2x.png" alt="">
</div>
<div class="text-#1A1A1A text-16px mb-25px font-bold">{{payStatus===0?'支付全部':'支付部分'}}</div>
<div class="text-#999999 text-16px mb-24px font-bold" v-if="payStatus===0">RMB 5000</div>
<div class="mb-12px">
<input class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" placeholder="最多RMB5,000" @keydown="validateInput">
</div>
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{payStatus===1?'支付全部':'支付部分'}}</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,18 @@
<script setup>
const image = ref('');
import { showToast } from 'vant';
const onSubmit = (data) => {
image.value = data.image;
};
const onClear = () => showToast('clear');
</script>
<template>
<van-signature @submit="onSubmit" @clear="onClear" />
<van-image v-if="image" :src="image" />
</template>
<style scoped>
</style>

View File

@ -0,0 +1,58 @@
<script setup>
import {useI18n} from "vue-i18n";
import XVanSelect from '@/components/x-van-select/index.vue'
import XVanDate from '@/components/x-van-date/index.vue'
definePageMeta({
layout: 'default',
i18n: 'menu.profile',
})
const {t} = useI18n()
const showPicker = ref(false)
const showPicker1 = ref(false)
const onConfirm = () => {
}
const columns = ref([
{text: t('realAuth.male'), value: 1},
{text: t('realAuth.female'), value: 2},
])
</script>
<template>
<div
class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] h-screen-nav bg-cover pt-77px flex-grow-1 flex flex-col ">
<div class="text-16px text-#191919 font-bold mb-40px px-34px">
请填写个人相关信息
</div>
<div class="grow-1 px-34px">
<van-field type="tel" :label-width="161" label="文本" class="mb-10px" placeholder="请输入手机号">
<template #label>
<div class="flex">
<div class="mr-41px whitespace-nowrap">手机号</div>
<div>
<span class="mr-13px">+ 86</span>
<van-icon name="arrow-down" class="text-#777777"/>
</div>
</div>
</template>
</van-field>
<van-field label="姓名" class="mb-10px" placeholder="请输入姓名"/>
<x-van-select label="性别" :columns="columns"/>
<x-van-date label="出生日期"/>
<van-field label="家庭住址" class="mb-10px" placeholder="请输入家庭住址"/>
<van-field label="所属银行" class="mb-10px" placeholder="请输入所属银行"/>
<van-field label="银行卡号码" class="mb-10px" placeholder="请输入银行卡号码"/>
</div>
<div class="h-81px bg-#fff flex justify-center pt-7px border-t">
<van-button color="#2B53AC" class="w-213px van-btn-h-38px">下一步</van-button>
</div>
</div>
</template>
<style scoped lang="scss">
:deep(.van-cell.van-field){
padding-left: 0;
}
</style>

View File

@ -0,0 +1,40 @@
<script setup>
const activeNames = ref(['1']);
</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">支付前需同意以下内容并签字</div>
<van-collapse v-model="activeNames" class="grow-1">
<van-collapse-item name="1" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
<van-collapse-item name="2" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
<van-collapse-item name="3" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
</van-collapse>
<div class="h-81px bg-#fff flex justify-center pt-7px border-t">
<van-button color="#2B53AC" class="w-213px van-btn-h-38px">同意并签字</van-button>
</div>
</div>
</template>
<style scoped>
:deep(.van-cell__right-icon){
color: #ACACAC;
font-size: 12px;
}
</style>

View File

@ -9,6 +9,7 @@ definePageMeta({
i18n: 'countryRegion.title',
})
const router = useRouter()
console.log('router',router)
const { t, locale } = useI18n()
const value = ref('');
const alphabet = [
@ -90,10 +91,10 @@ const searchCountry = computed(() => {
});
const showIndexBar = computed(() => locale.value !== 'ja-JP')
const route = useRoute()
const handleCountrySelect = (country) => {
router.push({
path: '/login',
router.replace({
path: window.history.state.back,
query: {
zone: country.zone,
countryName: country.displayName

View File

@ -1,5 +1,5 @@
<script setup>
import {goodStore} from "~/stores/goods/index.js";
import {goodStore} from "@/stores/goods/index.js";
import xImage from '@/components/x-image/index.vue'
const {
auctionDetail

View File

@ -1,7 +1,7 @@
<script setup>
import xPopup from '@/components/x-popup/index.vue'
import ItemDetail from "@/components/itemDetail/index.vue";
import {goodStore} from "~/stores/goods/index.js";
import {goodStore} from "@/stores/goods/index.js";
const {
artWorkDetail
} = goodStore()
@ -9,6 +9,10 @@ const props = defineProps({
show: {
type: Boolean,
default: false
},
detailInfo: {
type: Object,
default: null
}
})
@ -19,6 +23,6 @@ const handleClose = () => {
</script>
<template>
<xPopup :show="show" title="拍品详情" @update:show="handleClose">
<ItemDetail :detailInfo="artWorkDetail" />
<ItemDetail :detailInfo="detailInfo" />
</xPopup>
</template>

View File

@ -65,7 +65,7 @@ const openShow = async (item) => {
finished-text="没有更多了"
@load="loadMore"
>
<div class="w-full flex gap-[16px]" v-memo="[itemList]">
<div class="w-full flex gap-[16px]">
<masonry-wall :items="itemList" :ssr-columns="2" :maxColumns="2" :minColumns="2" :gap="5">
<template #default="{ item, index }">
<div
@ -81,7 +81,7 @@ const openShow = async (item) => {
<div
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
>
LOT{{ index+1 }}
LOT{{ item.index }}
</div>
</div>
<div class="pt-[8px]">
@ -104,7 +104,7 @@ const openShow = async (item) => {
</div>
</van-list>
</van-pull-refresh>
<DetailPopup v-model:show="localState.showDetail"></DetailPopup>
<DetailPopup v-model:show="localState.showDetail" :detailInfo="artWorkDetail"></DetailPopup>
</div>
</template>

View File

@ -3,21 +3,27 @@ import liveRoom from '@/pages/liveRoom/index.client.vue';
import {goodStore} from "@/stores/goods/index.js";
import ItemList from './components/ItemList/index.vue'
import Cescribe from './components/Cescribe/index.vue'
import {message} from '@/components/x-message/useMessage.js'
import floatingVideo from '@/components/floatingVideo/index.vue'
import {liveStore} from "~/stores/live/index.js";
const {fullLive,getAuctionDetail,auctionDetail} = goodStore();
const {getLiveLink}= liveStore()
const changeLive = () => {
fullLive.value = true;
};
if (!auctionDetail.value.uuid){
await getAuctionDetail()
}
</script>
<template>
<div class="flex-grow-1">
<!-- <floatingVideo :is-visible="true"></floatingVideo>-->
<client-only>
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"
:fullLive="fullLive"/>
</client-only>
<div v-show="!fullLive" class="bg-#fff">
<div v-if="!fullLive" class="bg-#fff">
<van-tabs sticky animated>
<van-tab title="拍品列表">
<ItemList></ItemList>
@ -28,6 +34,7 @@ if (!auctionDetail.value.uuid){
</van-tabs>
<van-back-top right="15vw" bottom="10vh"/>
</div>
</div>
</template>
<style scoped lang="scss">

View File

@ -6,7 +6,7 @@
<script setup>
import { ref } from 'vue'
import SignaturePad from '~/components/SignaturePad.vue'
import SignaturePad from '@/components/SignaturePad.vue'
const signature = ref('')

View File

@ -1,68 +1,52 @@
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
const list = ref([
import {liveStore} from "@/stores/live/index.js";
import {authStore} from "~/stores/auth/index.js";
const {auctionData} = liveStore()
const {userInfo}= authStore()
const headList=[
{
a: '领先',
b: '现场竞价',
c: '10:12:12',
d: 'RMB5,500',
e: '我'
label:'领先',
color:'#D03050',
value:'head'
},
{
label:'出局',
color:'#939393',
value:'out'
},
{
label:'成交',
color:'#34B633',
value:'success'
}
]);
let intervalId = null;
const addItem = () => {
list.value.push({
a: '领先',
b: '现场竞价',
c: '10:12:12',
d: 'RMB5,500',
e: ''
});
nextTick(() => {
scrollToBottom();
});
};
const scrollToBottom = () => {
const container = document.getElementById('list-container');
if (container) {
setTimeout(() => {
container.scrollTop = container.scrollHeight;
}, 100);
}
};
onMounted(() => {
//
/* intervalId = setInterval(() => {
addItem();
}, 1000);*/
});
onUnmounted(() => {
//
if (intervalId) {
clearInterval(intervalId);
}
});
]
const headItem=(statusCode)=>{
return headList.find(x=>x.value===statusCode)
}
</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 flex flex-col justify-between"
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">
<div v-for="(item, index) in list" :key="index" class="flex flex-shrink-0 h-25px">
<div class="flex-grow-1 text-center">{{ item.a }}</div>
<div class="flex-grow-1 text-center">{{ item.b }}</div>
<div class="flex-grow-1 text-center">{{ item.c }}</div>
<div class="flex-grow-1 text-center">{{ item.d }}</div>
<div class="flex-grow-1 text-center">{{ item.e }}</div>
</div>
<template v-if="auctionData.wsType==='stopArtwork'">
<div class="text-#939393 text-14px">即将开始下一个拍品</div>
</template>
<template v-else-if="auctionData.auctionPriceList?.buys?.length>0">
<div v-for="(item, index) in auctionData.auctionPriceList?.buys" :key="index" class="flex flex-shrink-0 h-25px">
<div class="flex-grow-1 text-start shrink-0" :style="`color: ${headItem(item.statusCode).color}`" >{{ headItem(item.statusCode).label }}</div>
<div class="flex-grow-1 text-center shrink-0">{{ item.auctionType==='local'?'现场竞价':'网络竞价' }}</div>
<div class="flex-grow-1 text-center shrink-0">{{ item.createdAt }}</div>
<div class="flex-grow-1 text-center shrink-0">{{item.baseCurrency}}{{ item.baseMoney }}</div>
<div class="flex-grow-1 text-center text-#2B53AC shrink-0 w-20px">{{ item.userId===userInfo.ID?'我':'' }}</div>
</div>
</template>
<template v-if="auctionData.wsType==='newArtwork'">
<div class="text-#939393 text-14px">开始拍卖</div>
</template>
</transition-group>
</div>
</template>

View File

@ -9,6 +9,7 @@ const props = defineProps({
default: 0
}
})
const router = useRouter()
const emit = defineEmits(['update:show'])
const payStatus=ref(0)
const changePayStatus=()=>{
@ -18,6 +19,7 @@ const close=()=>{
emit('update:show',false)
}
const confirm=()=>{
router.push('/signature/protocol')
emit('update:show',false)
}

View File

@ -12,7 +12,7 @@ const props = defineProps({
},
price: {
type: Number,
default: 0
default: 1000
}
})
const emit = defineEmits(['cancel','update:show'])
@ -23,7 +23,7 @@ const cancel= () => {
<template>
<div>
<van-dialog :show="show" show-cancel-button :show-confirm-button="false" cancelButtonText="返回" cancelButtonColor="#2B53AC" @cancel="cancel">
<van-dialog style="overflow: visible" :show="show" show-cancel-button :show-confirm-button="false" cancelButtonText="返回" cancelButtonColor="#2B53AC" @cancel="cancel">
<div class="h-145px relative flex justify-center">
<img :src="type==='success' ? successImg : errorImg" class="w-119px h-120px absolute top--74px z-9999 left-1/2 transform translate-x--1/2" alt="">
<div class="mt-94px text-#A9A9A9 text-16px">{{price}}</div>
@ -33,9 +33,7 @@ const cancel= () => {
</template>
<style scoped>
:deep(.van-dialog) {
overflow: visible!important;
}
:deep(.van-hairline--top.van-dialog__footer){
border-top: 1px solid #E7E7E7;
border-bottom-left-radius:8px ;

View File

@ -1,33 +1,49 @@
<script setup>
import { ref } from "vue";
import {ref} from "vue";
import lockClosed from "@/static/images/lockdfd@2x.png";
import lockOpen from "@/static/images/lock4@2x.png";
import { liveStore } from "@/stores/live/index.js";
import {liveStore} from "@/stores/live/index.js";
import xButton from '@/components/x-button/index.vue'
import tangPopup from './tangPopup.vue'
import {goodStore} from "~/stores/goods/index.js";
const { quoteStatus, changeStatus,show } = liveStore();
import {goodStore} from "@/stores/goods/index.js";
import {authStore} from "~/stores/auth/index.js";
const {quoteStatus, changeStatus, show, auctionData, getSocketData} = liveStore();
const {pageRef} = goodStore();
const showTang=ref(false)
const openOne=()=>{
showTang.value=true
const {userInfo} = authStore()
const showTang = ref(false)
const openOne = () => {
showTang.value = true
}
const paySide = computed(() => {
//
if (auctionData.value.artwork.isSoled && auctionData.value.artwork.buyInfo.userID === userInfo.value.ID) {
return true
} else {
return false
}
})
const goPay = () => {
show.value = true
}
</script>
<template>
<div class="bg-white w-60px rounded-l-4px overflow-hidden">
<!-- 拍品信息 -->
<x-button @click="openOne">
<div class="w-60px h-60px text-center border-b border-gray-300 flex flex-col justify-center items-center text-#7D7D7F text-12px">
<van-button class="w-60px !h-60px" @click="openOne" style="border: none;border-radius: 0">
<div class="text-center flex flex-col justify-center items-center text-#7D7D7F text-12px">
<div>拍品</div>
<div>(1/{{pageRef.itemCount??0}})</div>
<div>({{ auctionData?.artwork?.index }}/{{ pageRef.itemCount ?? 0 }})</div>
</div>
</x-button>
</van-button>
<tangPopup v-model:show="showTang"></tangPopup>
<!-- 出价开关 -->
<x-button @click="changeStatus">
<div class="w-60px h-60px text-center border-b border-gray-300 flex flex-col justify-center items-center">
<div class="mb-1">
<van-button class="w-60px !h-60px" @click="changeStatus"
style="border-right: none;border-left: none;border-radius: 0;padding: 0">
<div class="text-center flex flex-col justify-center items-center">
<div class="mb-4px">
<img
:src="quoteStatus ? lockClosed : lockOpen"
class="w-16px h-21px"
@ -38,15 +54,15 @@ const openOne=()=>{
{{ quoteStatus ? '关闭出价' : '开启出价' }}
</div>
</div>
</x-button>
</van-button>
<!-- 支付 -->
<x-button @click="show = true">
<div class="w-60px h-60px text-center flex flex-col justify-center items-center text-yellow-600">
<van-button v-if="paySide" class="w-60px !h-60px" style="border: none;border-radius: 0" @click="goPay">
<div class="text-center flex flex-col justify-center items-center text-yellow-600">
<div class="text-10px">RMB</div>
<div class="text-12px">5,000</div>
<div class="text-10px">去支付</div>
</div>
</x-button>
</van-button>
</div>
</template>

View File

@ -1,8 +1,29 @@
<script setup>
import xPopup from '@/components/x-popup/index.vue'
import {goodStore} from "~/stores/goods/index.js";
import {goodStore} from "@/stores/goods/index.js";
import xImage from '@/components/x-image/index.vue'
const {pageRef,itemList} = goodStore();
import DetailPopup from '@/pages/home/components/DetailPopup/index.vue'
import {liveStore} from "~/stores/live/index.js";
import {ref} from "vue";
const {pageRef,itemList,getArtworkList, loading: storeLoading,} = goodStore();
const {auctionData} = liveStore()
const showDetail=ref(false)
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
})
const onRefresh = async () => {
try {
localState.value.refreshing = true
localState.value.finished = false
const { finished } = await getArtworkList(true)
localState.value.finished = finished
} finally {
localState.value.refreshing = false
}
}
const props = defineProps({
show: Boolean,
title: {
@ -10,44 +31,104 @@ const props = defineProps({
default: ''
}
})
const scrollToCurrentItem = () => {
if (!itemList.value?.length) return
const currentIndex = itemList.value.findIndex(
item => auctionData.value.artwork.index === item?.index
)
if (currentIndex > -1) {
const container = document.querySelector('.list-container')
const targetElement = document.querySelectorAll('.item-wrapper')[currentIndex]
if (targetElement && container) {
const containerTop = container.getBoundingClientRect().top
const elementTop = targetElement.getBoundingClientRect().top
const scrollTop = elementTop - containerTop + container.scrollTop
container.scrollTo({
top: scrollTop,
behavior: 'smooth'
})
}
}
}
const emit = defineEmits(['update:show'])
const showDetailInfo=ref(null)
const close = () => emit('update:show', false);
const openShow=(item)=>{
showDetailInfo.value=item
showDetail.value=true
}
const loadMore = async () => {
pageRef.value.page++
const { finished } = await getArtworkList()
localState.value.finished = finished
}
watch(()=>props.show,(newValue)=>{
if (newValue){
nextTick(()=>{
scrollToCurrentItem()
})
}
})
</script>
<template>
<x-popup :show="show" @update:show="close">
<template #title>
<div class="text-#000 text-16px">拍品列表</div>
<div class="text-#939393 text-16px ml-14px">{{ pageRef.itemCount }}个拍品</div>
</template>
<div>
<div
v-for="(item,index) of itemList"
:key="item.uuid"
class="flex mb-21px"
>
<div
class="mr-10px flex-shrink-0 rounded-4px overflow-hidden cursor-pointer"
<div>
<x-popup :show="show" @update:show="close">
<template #title>
<div class="text-#000 text-16px">拍品列表</div>
<div class="text-#939393 text-16px ml-14px">{{ pageRef.itemCount }}个拍品</div>
</template>
<div>
<van-pull-refresh
v-model="localState.refreshing"
success-text="刷新成功"
:success-duration="700"
@refresh="onRefresh"
>
<xImage
class="w-80px h-80px"
:src="item.artwork?.hdPic"
:alt="item?.artworkTitle"
loading="lazy"
/>
</div>
<div>
<div class="ellipsis line-height-20px text-16px font-600 min-h-40px">
{{ item.artworkTitle }}
</div>
<div class="text-14px text-#575757">起拍价RMB 1,000</div>
<div class="text-14px text-#B58047">成交价等待更新</div>
</div>
<template #success>
<van-icon name="success" /> <span>刷新成功</span>
</template>
<van-list
v-model:loading="storeLoading"
:finished="localState.finished"
finished-text="没有更多了"
@load="loadMore"
>
<div
v-for="(item,index) of itemList"
:key="item.uuid"
class="flex mb-21px item-wrapper"
@click="openShow(item)"
>
<div
class="mr-10px flex-shrink-0 rounded-4px overflow-hidden cursor-pointer relative"
>
<xImage
:preview="false"
class="w-80px h-80px"
:src="item.artwork?.hdPic"
:alt="item?.artworkTitle"
loading="lazy"
/>
<div class="w-45px h-17px bg-#2B53AC text-12px line-height-none flex justify-center items-center absolute top-2px left-2px text-#fff">LOT{{item.index}}</div>
<div v-if="auctionData.artwork.index===item?.index" class="w-80px h-20px bg-#B58047 flex line-height-none justify-center items-center text-#fff text-12px bottom-0 absolute blink">投屏中</div>
</div>
<div>
<div class="ellipsis line-height-20px text-16px font-600 min-h-40px">
{{ item.artworkTitle }}
</div>
<div class="text-14px text-#575757">起拍价RMB 1,000</div>
<div class="text-14px text-#B58047">成交价等待更新</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</div>
</x-popup>
</x-popup>
<DetailPopup v-model:show="showDetail" :detail-info="showDetailInfo"></DetailPopup>
</div>
</template>
<style scoped>
@ -58,4 +139,12 @@ const close = () => emit('update:show', false);
overflow: hidden;
text-overflow: ellipsis;
}
.blink {
animation: fade 1s linear infinite;
}
@keyframes fade {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>

View File

@ -1,51 +1,94 @@
<script setup>
import {ref, onMounted, onBeforeUnmount} from 'vue'
import {ref, onMounted, onBeforeUnmount, computed, watch} from 'vue'
import Aliplayer from 'aliyun-aliplayer'
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
import sideButton from '~/pages/liveRoom/components/SideButton/index.vue'
import broadcast from '~/pages/liveRoom/components/Broadcast/index.vue'
import {liveStore} from "~/stores/live/index.js";
import paymentResults from '~/pages/liveRoom/components/PaymentResults/index.vue'
import paymentInput from '~/pages/liveRoom/components/PaymentInput/index.vue'
import sideButton from '@/pages/liveRoom/components/SideButton/index.vue'
import broadcast from '@/pages/liveRoom/components/Broadcast/index.vue'
import {liveStore} from "@/stores/live/index.js"
import paymentResults from '@/pages/liveRoom/components/PaymentResults/index.vue'
import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
import xButton from '@/components/x-button/index.vue'
import {goodStore} from "~/stores/goods/index.js";
import {authStore} from "~/stores/auth/index.js";
const {auctionDetail,getAuctionDetail} = goodStore();
const { token } = authStore()
import {goodStore} from "@/stores/goods/index.js"
import {message} from "~/components/x-message/useMessage.js"
import {artworkBuy} from "@/api/goods/index.js"
import CryptoJS from 'crypto-js'
const {auctionDetail, getAuctionDetail} = goodStore()
const player = ref(null)
const {quoteStatus, changeStatus, show, playerId, show1} = liveStore()
const {quoteStatus, changeStatus, show, playerId, show1, auctionData, getSocketData,getLiveLink} = liveStore()
const isPlayerReady = ref(false)
const pullLink=ref('')
const props = defineProps({
fullLive: {
type: Boolean,
default: true,
},
})
const isPiPActive = ref(false)
const videoRef = ref(null)
//
const isPiPSupported = computed(() => {
return document.pictureInPictureEnabled ||
document.webkitPictureInPictureEnabled
})
//
const enterPiP = async () => {
try {
if (!videoRef.value) return
if (document.pictureInPictureElement) {
await document.exitPictureInPicture()
}
await videoRef.value.requestPictureInPicture()
isPiPActive.value = true
} catch (error) {
console.error('进入画中画模式失败:', error)
}
}
// 退
const exitPiP = async () => {
try {
if (document.pictureInPictureElement) {
await document.exitPictureInPicture()
isPiPActive.value = false
}
} catch (error) {
console.error('退出画中画模式失败:', error)
}
}
definePageMeta({
title: '主页',
i18n: 'login.title',
})
const config = useRuntimeConfig()
const playerConfig = {
id: playerId.value,
source: config.public.NUXT_PUBLIC_PLAYER_SOURCE,
isLive: true,
preload: true,
autoplayPolicy: {fallbackToMute: true},
controlBarVisibility: 'never',
}
const handlePlayerError = (error) => {
console.error('播放器错误:', error)
if (player.value) {
player.value?.play()
}
}
const initializePlayer = () => {
try {
if (player.value) {
player.value?.dispose()
}
const playerConfig = {
id: playerId.value,
source:pullLink.value,
isLive: true,
preload: true,
autoplayPolicy: {fallbackToMute: true},
controlBarVisibility: 'never',
}
player.value = new Aliplayer(playerConfig, (playerInstance) => {
isPlayerReady.value = true
playerInstance?.play()
@ -60,102 +103,108 @@ const initializePlayer = () => {
}
}
onMounted(async () => {
if (!auctionDetail.value.uuid){
await getAuctionDetail()
}
/* initializePlayer()*/
const { ws, messages, onMessage } = useWebSocket()
//
ws.connect('/api/v1/m/auction/live',{auctionUuid: auctionDetail.value.uuid,token:token.value})
/*// 发送消息
ws.send({ type: 'chat', content: 'Hello!' })*/
//
onMessage((data) => {
console.log('收到消息:', data)
})
pullLink.value= await getLiveLink()
initializePlayer()
})
onBeforeUnmount(() => {
if (player.value) {
player.value?.dispose()
player.value = null
}
})
const goPay = () => {
show.value = true
}
const fullLive1 = ref(false)
watch(()=>{
const fullLive1 = ref(false)
watch(() => {
return props.fullLive
}, (newVal) => {
if (newVal) {
getSocketData()
setTimeout(() => {
fullLive1.value = true
}, 400)
}else {
} else {
fullLive1.value = false
}
})
const goBuy = async () => {
const res = await artworkBuy({
auctionArtworkUuid: auctionData.value?.artwork?.uuid,
buyMoney: String(auctionData.value?.nowAuctionPrice?.nextPrice ?? 0)
})
if (res.status === 0) {
message.success('出价成功')
}
}
const tipOpen = () => {
message.warning('出价状态未开启')
}
</script>
<template>
<div class="relative h-full">
<div class="w-full h-full">
<video
class="h-full w-full"
autoplay
loop
muted
playsinline
style=" object-fit: cover"
>
<source src="@/static/video/example.mp4" type="video/mp4" />
您的浏览器不支持 HTML5 视频
</video>
</div>
<!-- <div :id="playerId" class="w-screen"
:style="fullLive?'height: calc(100vh - var(&#45;&#45;van-nav-bar-height))':'height:100%'"></div>-->
<div class="relative h-full ">
<div :id="playerId" class="w-full h-full"></div>
<transition>
<div v-if="fullLive1">
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
<div class="absolute left-1/2 transform -translate-x-1/2 flex flex-col items-center" style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">
<div class="absolute left-1/2 transform -translate-x-1/2 flex flex-col items-center"
style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">
<div class="text-16px text-#FFB25F font-600">
当前价RMB
<van-rolling-text class="my-rolling-text" :start-num="0" :duration="0.5" :target-num="3000" direction="up"/>
当前价{{ auctionData?.nowAuctionPrice?.currency }}
<van-rolling-text class="my-rolling-text" :start-num="0" :duration="0.5"
:target-num="auctionData?.nowAuctionPrice?.nowPrice??0" direction="up"/>
</div>
<div class="text-16px text-#fff font-600">
下口价RMB
<van-rolling-text class="my-rolling-text1" :start-num="0" :duration="0.5" :target-num="3500" direction="up"/>
下口价{{ auctionData?.nowAuctionPrice?.currency }}
<van-rolling-text class="my-rolling-text1" :start-num="0" :duration="0.5"
:target-num="auctionData?.nowAuctionPrice?.nextPrice??0" direction="up"/>
</div>
<x-button>
<div
:class="`w-344px h-[40px] ${quoteStatus ? 'bg-#FFB25F' : 'bg-#D6D6D8'} rounded-4px ${quoteStatus ? 'text-#fff' : 'text-#7D7D7F'} text-14px flex justify-center items-center mt-10px mb-10px`">
{{ quoteStatus ? '确认出价 RMB 3,000' : '点击"开启出价",即刻参与竞拍 ' }}
</div>
</x-button>
<div v-if="quoteStatus" class="mt-10px mb-10px">
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
<div>{{
`确认出价 ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice ?? 0}`
}}
</div>
</van-button>
</div>
<div v-else class="mt-10px mb-10px">
<van-button @click="tipOpen" color="#D6D6D8" class=" w-344px !h-[40px]" v-if="!quoteStatus">
<div class="text-#7D7D7F text-14px">点击"开启出价"即刻参与竞拍</div>
</van-button>
</div>
<broadcast></broadcast>
</div>
<paymentInput v-model:show="show"/>
<div>
</div>
<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{{ auctionData.artwork.index }}</div>
<div class="mr-10px truncate">{{ auctionData.artwork.name }}</div>
<div class="whitespace-nowrap">开始拍卖</div>
</div>
</div>
</transition>
</div>
</template>
<style>
:root:root {
--van-dialog-radius: 8px
<style lang="scss">
#J_prismPlayer{
width: 100%;
height: 100%!important;
&>video{
width: 100%;
height: 100%;
}
}
</style>
<style scoped>
@ -168,6 +217,7 @@ watch(()=>{
.v-leave-to {
opacity: 0;
}
.my-rolling-text {
--van-rolling-text-item-width: 10px;
--van-rolling-text-font-size: 16px;

View File

@ -3,7 +3,7 @@ import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'
import countryCode from '../countryRegion/data/index.js'
import {senCode, userLogin} 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'
const {userInfo,token}= authStore()
const router = useRouter();
@ -132,7 +132,7 @@ const goLogin =async () => {
</script>
<template>
<div class="h-[100vh] w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-cover px-[31px] pt-[86px]">
<div class="h-screen-nav w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-cover px-[31px] pt-[86px]">
<div class="w-full flex justify-center mb-[100px]">
<img class="h-[105px] w-[189px]" src="@/static/images/ghfggff.png" alt="">
</div>

View File

@ -1,12 +1,15 @@
<script setup>
import {userArtworks} from "~/api/goods/index.js";
import {authStore} from "~/stores/auth/index.js";
import {userArtworks} from "@/api/goods/index.js";
import {authStore} from "@/stores/auth/index.js";
import xImage from '@/components/x-image/index.vue'
import {goodStore} from "~/stores/goods/index.js";
import {ref} from "vue";
definePageMeta({
layout: 'default',
title: '我的',
i18n: 'menu.profile',
})
const {artWorkDetail} = goodStore()
const myList=ref([])
const showMyList=ref([])
const {userInfo}= authStore()
@ -19,12 +22,11 @@ const groupAndSortByDate=(data)=> {
acc[curr.userCreatedAt] = {
userCreatedAt: curr.userCreatedAt,
list: []
};
}
}
acc[curr.userCreatedAt].list.push(curr);
acc[curr.userCreatedAt].list.push(curr)
return acc;
}, {}))
.sort((a, b) => new Date(b.userCreatedAt) - new Date(a.userCreatedAt));
}, {})).sort((a, b) => new Date(b.userCreatedAt) - new Date(a.userCreatedAt));
}
const initData=async ()=>{
const res=await userArtworks({})
@ -33,7 +35,37 @@ const initData=async ()=>{
showMyList.value=groupAndSortByDate(myList.value)
}
}
const router = useRouter()
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
})
initData()
const goPay=()=>{
router.push({
path:'/signature/personal-Info'
})
}
const goDetail=(item)=>{
router.push({
path:'/artDetail',
query:{
uuid:item.uuid
}
})
}
const onRefresh = async () => {
try {
localState.value.refreshing = true
localState.value.finished = false
const { finished } = await getArtworkList(true)
localState.value.finished = finished
} finally {
localState.value.refreshing = false
}
}
</script>
<template>
@ -51,20 +83,30 @@ initData()
<div class="border-b-1px border-b-#D3D3D3 px-16px flex">
<div class="text-#000 text-16px border-b-3 border-b-#2B53AC h-36px">我的拍品</div>
</div>
<van-pull-refresh @refresh="initData">
<van-pull-refresh v-model="localState.refreshing"
success-text="刷新成功"
:success-duration="700"
@refresh="onRefresh">
<van-list
finished-text="没有更多了"
>
<div class="px-16px pt-14px" v-for="(item,index) of showMyList">
<div class="px-16px pt-14px" v-for="(item,index) of showMyList" >
<div class="text-#575757 text-14px mb-3px">{{item.userCreatedAt}}</div>
<div class="flex mb-22px" v-for="(item1,index1) of item.list">
<div class="flex mb-22px" v-for="(item1,index1) of item.list" @click="goDetail(item1)">
<div class="flex-shrink-0 mr-10px rounded-4px overflow-hidden">
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" alt=""/>
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" :preview="false" alt=""/>
</div>
<div class="flex flex-col justify-between">
<div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}</div>
<div class="text-#575757 text-14px line-height-none ">起拍价RMB 1,000</div>
<div class="text-#B58047 text-14px line-height-none">成交价RMB 10,000</div>
<div class="flex flex-col justify-between grow-1">
<div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}</div>
<div class="flex justify-between">
<div>
<div class="text-#575757 text-14px line-height-none mb-5px">起拍价RMB 1,000</div>
<div class="text-#B58047 text-14px line-height-none">成交价RMB 10,000</div>
</div>
<div v-if="[1,3,4].includes(item1.status)" @click.stop="goPay">
<van-button class="w-73px !h-30px" type="primary"><span class="text-12px">去支付</span></van-button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,10 +1,10 @@
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'
import {userUpdate} from "~/api/auth/index.js";
import {userUpdate} from "@/api/auth/index.js";
import {message} from '@/components/x-message/useMessage.js'
import detail from './components/detail.vue'
import {authStore} from "~/stores/auth/index.js";
import {authStore} from "@/stores/auth/index.js";
const router = useRouter();
const route = useRoute();
const showPicker = ref(false);

View File

@ -0,0 +1,18 @@
<script setup>
const image = ref('');
import { showToast } from 'vant';
const onSubmit = (data) => {
image.value = data.image;
};
const onClear = () => showToast('clear');
</script>
<template>
<van-signature @submit="onSubmit" @clear="onClear" />
<van-image v-if="image" :src="image" />
</template>
<style scoped>
</style>

View File

@ -0,0 +1,63 @@
<script setup>
import {useI18n} from "vue-i18n";
import XVanSelect from '@/components/x-van-select/index.vue'
import XVanDate from '@/components/x-van-date/index.vue'
definePageMeta({
name: 'personal-info',
})
const {t} = useI18n()
const showPicker = ref(false)
const showPicker1 = ref(false)
const onConfirm = () => {
}
const router = useRouter()
const columns = ref([
{text: t('realAuth.male'), value: 1},
{text: t('realAuth.female'), value: 2},
])
const goCountryRegion=()=>{
router.push({
path:'/countryRegion'
})
}
const adress=ref('')
</script>
<template>
<div
class="w-[100vw] bg-[url('@/static/images/asdfsdd.png')] h-screen-nav bg-cover pt-77px flex-grow-1 flex flex-col ">
<div class="text-16px text-#191919 font-bold mb-40px px-34px">
请填写个人相关信息
</div>
<div class="grow-1 px-34px">
<van-field type="tel" :label-width="161" label="文本" class="mb-10px" placeholder="请输入手机号">
<template #label>
<div class="flex">
<div class="mr-41px whitespace-nowrap">手机号</div>
<div @click="goCountryRegion">
<span class="mr-13px">+ 86</span>
<van-icon name="arrow-down" class="text-#777777"/>
</div>
</div>
</template>
</van-field>
<van-field label="姓名" class="mb-10px" placeholder="请输入姓名"/>
<x-van-select label="性别" :columns="columns"/>
<x-van-date label="出生日期"/>
<van-field v-model="adress" label="家庭住址" class="mb-10px" placeholder="请输入家庭住址"/>
<van-field label="所属银行" class="mb-10px" placeholder="请输入所属银行"/>
<van-field label="银行卡号码" class="mb-10px" placeholder="请输入银行卡号码"/>
</div>
<div class="h-81px bg-#fff flex justify-center pt-7px border-t">
<van-button color="#2B53AC" class="w-213px van-btn-h-38px">下一步</van-button>
</div>
</div>
</template>
<style scoped lang="scss">
:deep(.van-cell.van-field){
padding-left: 0;
}
</style>

View File

@ -0,0 +1,40 @@
<script setup>
const activeNames = ref(['1']);
</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">支付前需同意以下内容并签字</div>
<van-collapse v-model="activeNames" class="grow-1">
<van-collapse-item name="1" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
<van-collapse-item name="2" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
<van-collapse-item name="3" class="mb-6px">
<template #title>
<div class="text-#2B53AC text-14px">拍卖规则</div>
</template>
代码是写出来给人看的附带能在机器上运行
</van-collapse-item>
</van-collapse>
<div class="h-81px bg-#fff flex justify-center pt-7px border-t">
<van-button color="#2B53AC" class="w-213px van-btn-h-38px">同意并签字</van-button>
</div>
</div>
</template>
<style scoped>
:deep(.van-cell__right-icon){
color: #ACACAC;
font-size: 12px;
}
</style>

View File

@ -1,4 +1,4 @@
import { setupHttp } from '~/api/http'
import { setupHttp } from '@/api/http'
export default defineNuxtPlugin(() => {
setupHttp()

View File

@ -1,5 +1,8 @@
import {authStore} from "@/stores/auth";
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const { token } = authStore()
const ws = reactive({
instance: null as WebSocket | null,
isConnected: false,
@ -11,8 +14,8 @@ export default defineNuxtPlugin(() => {
}
// 构建查询字符串
const queryString = data
? '?' + Object.entries(data)
const queryString =data
? '?' + Object.entries({ token: token.value,...data})
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
: ''

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,13 @@
import { createGlobalState,useLocalStorage } from '@vueuse/core'
export const codeAuthStore = createGlobalState(() => {
const token=useLocalStorage('token','')
const RefreshToken=useLocalStorage('RefreshToken','')
const userInfo=useLocalStorage('userInfo',{})
const fingerprint=useLocalStorage('fingerprint','')
return{
userInfo,
RefreshToken,
token,
fingerprint
}
})

View File

@ -0,0 +1,83 @@
import { createGlobalState } from '@vueuse/core'
import { ref } from 'vue'
import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js"
import {offlineQrcodeList} from "~/api-collect-code/goods/index.js";
export const goodStore = createGlobalState(() => {
// 状态定义
const actionDetails = ref({})
const fullLive = ref(false)
const currentItem = ref({})
const myArtWorks = ref([])
const pageRef = ref({
page: 0,
pageSize: 5,
itemCount: 0
})
const artWorkDetail = ref(null)
const itemList = ref([])
const auctionDetail = ref({})
const loading = ref(false)
const error = ref(null)
// 重置分页
const resetPage = () => {
pageRef.value.page = 1
pageRef.value.pageSize=5
pageRef.value.itemCount = 0
}
// 获取艺术品列表
const getOfflineQrcodeList = async (isRefresh = false) => {
if (isRefresh) {
resetPage()
}
try {
loading.value = true
const res = await offlineQrcodeList({
page: pageRef.value.page,
pageSize: pageRef.value.pageSize
})
if (res.status === 0) {
const newItems = res.data.Data || []
if (isRefresh) {
itemList.value = newItems
} else {
itemList.value = [...itemList.value, ...newItems]
}
pageRef.value.itemCount = res.data.count || 0
return {
finished: !newItems.length || newItems.length < pageRef.value.pageSize,
items: newItems
}
}
return { finished: true, items: [] }
} catch (err) {
return { finished: true, items: [] }
} finally {
loading.value = false
}
}
return {
// 状态
actionDetails,
fullLive,
currentItem,
myArtWorks,
pageRef,
artWorkDetail,
itemList,
auctionDetail,
loading,
error,
// 方法
getOfflineQrcodeList,
resetPage
}
})

View File

@ -0,0 +1,43 @@
import { createGlobalState } from '@vueuse/core'
import {ref} from "vue";
import {goodStore} from "@/stores/goods/index.js";
import {authStore} from "@/stores/auth/index.js";
export const liveStore = createGlobalState(() => {
const {auctionDetail,getAuctionDetail} = goodStore();
const { token } = authStore()
const quoteStatus = ref(false)
const show = ref(false)
const show1=ref(true)
const playerId=ref('J_prismPlayer')
const auctionData=ref({})
const getSocketData=async ()=>{
if (!auctionDetail.value.uuid){
await getAuctionDetail()
}
const { ws, messages, onMessage } = useWebSocket()
// 连接
ws.connect('/api/v1/m/auction/live',{auctionUuid: auctionDetail.value.uuid,token:token.value})
/*// 发送消息
ws.send({ type: 'chat', content: 'Hello!' })*/
// 监听消息
onMessage((data) => {
auctionData.value = data
})
}
const changeStatus = () => {
quoteStatus.value = !quoteStatus.value
}
return{
auctionData,
getSocketData,
show1,
playerId,
show,
quoteStatus,
changeStatus
}
})

View File

View File

@ -1,6 +1,6 @@
import { createGlobalState } from '@vueuse/core'
import { createGlobalState,useLocalStorage } from '@vueuse/core'
import { ref } from 'vue'
import { artworkList, defaultDetail, artworkDetail } from "~/api/goods/index.js"
import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js"
export const goodStore = createGlobalState(() => {
// 状态定义
@ -13,7 +13,7 @@ export const goodStore = createGlobalState(() => {
pageSize: 5,
itemCount: 0
})
const artWorkDetail = ref(null)
const artWorkDetail = useLocalStorage('artWorkDetail',{})
const itemList = ref([])
const auctionDetail = ref({})
const loading = ref(false)

View File

@ -1,14 +1,238 @@
import { createGlobalState } from '@vueuse/core'
import {ref} from "vue";
import {goodStore} from "@/stores/goods/index.js";
import {authStore} from "@/stores/auth/index.js";
import {message} from "~/components/x-message/useMessage.js";
import { WebSocketClient } from '@/utils/websocket'
import {logSendlog} from "~/api/goods/index.js";
import CryptoJS from "crypto-js";
export const liveStore = createGlobalState(() => {
const {auctionDetail} = goodStore();
const { token } = authStore()
const quoteStatus = ref(false)
const show = ref(false)
const cleanup = ref(null)
const show1=ref(false)
const playerId=ref('J_prismPlayer')
const auctionData=ref({})
const socket=ref(null)
const config = useRuntimeConfig()
const pullLink=ref('')
// 解密工具函数
const decryptUtils = {
// 解密配置
cryptConfig: {
password: 'live-skkoql-1239-key',
salt: 'aldk100128ls',
iterations: 10000,
keySize: 32
},
// 生成密钥
generateKey(password, salt, iterations, keySize) {
return CryptoJS.PBKDF2(password, salt, {
keySize: keySize / 4,
iterations: iterations,
hasher: CryptoJS.algo.SHA1
}).toString(CryptoJS.enc.Hex)
},
// AES解密
decrypt(ciphertextBase64, key) {
const combined = CryptoJS.enc.Base64.parse(ciphertextBase64)
const iv = CryptoJS.lib.WordArray.create(combined.words.slice(0, 4))
const ciphertext = CryptoJS.lib.WordArray.create(combined.words.slice(4))
const decrypted = CryptoJS.AES.decrypt(
{ciphertext: ciphertext},
CryptoJS.enc.Hex.parse(key),
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
)
return decrypted.toString(CryptoJS.enc.Utf8)
},
// 解密数据的主函数
decryptData(encryptedData) {
const keyDerived = this.generateKey(
this.cryptConfig.password,
this.cryptConfig.salt,
this.cryptConfig.iterations,
this.cryptConfig.keySize
)
return this.decrypt(encryptedData, keyDerived)
}
}
const getLiveLink = () => {
return new Promise(async(resolve, reject) => {
const res = await logSendlog({
uuid: auctionDetail.value.uuid
})
if (res.status===0){
pullLink.value=decryptUtils.decryptData(res.data.code)
resolve(decryptUtils.decryptData(res.data.code))
}
})
}
const getSocketData = async () => {
const wsClient = new WebSocketClient(
config.public.NUXT_PUBLIC_SOCKET_URL,
token.value
)
const ws = wsClient.connect('/api/v1/m/auction/live', {
auctionUuid: auctionDetail.value.uuid,
})
ws.onOpen(() => {
console.log('WebSocket connected')
})
ws.onMessage((data) => {
auctionData.value = data.data
if (data.data?.wsType === 'tip' ) {
if (data.data?.tip?.tipType === 'falling'){
message.warning({
title: {
text: '即将落槌',
color: '#F09F1F',
align: 'center',
},
style: {
width: '151px',
bottom: '230px',
},
})
}else if (data.data?.tip?.tipType === 'othersBid'){
message.error({
title: {
text: '已有人出价',
color: '#CF3050',
align: 'center',
},
icon:false,
subTitle:{
text:'更新后再出价',
color: '#939393',
align: 'center',
},
style: {
width: '151px',
bottom: '230px'
},
})
}else if (data.data?.tip?.tipType === 'successBid'){
message.success({
title: {
text: '恭喜您,竞拍成功',
color: '#18A058',
align: 'center',
},
icon:false,
subTitle:{
text:'请缴款',
color: '#939393',
align: 'center',
},
style: {
width: '151px',
bottom: '230px'
},
})
}else if (data.data?.tip?.tipType === 'artworkOver'){
message.success({
title: {
text: '本拍品已结束',
color: '#575757',
align: 'center',
},
icon:false,
subTitle:{
text:'请期待下个拍品',
color: '#939393',
align: 'center',
},
style: {
width: '151px',
bottom: '230px',
backgroundColor: '#fff',
borderColor:'#fff'
},
})
}else if (data.data?.tip?.tipType === 'failBid'){
message.error({
title: {
text: '很遗憾,竞拍失败',
color: '#CF3050',
align: 'center',
},
icon:false,
subTitle:{
text:'竞拍结束',
color: '#939393',
align: 'center',
},
style: {
width: '186px',
bottom: '230px'
},
})
}
}else if (data.data?.wsType==='stopArtwor'){
quoteStatus.value=false
}else if (data.data?.wsType==='over'){
message.success({
title: {
text: '竞拍结束,谢谢参与',
color: '#575757',
align: 'center',
},
icon:false,
style: {
width: '195px',
bottom: '230px',
backgroundColor: '#fff',
borderColor:'#fff'
},
})
}
console.log('onmessage', data)
})
ws.onClose(() => {
console.log('WebSocket disconnected')
})
ws.onError((error) => {
console.error('WebSocket error:', error)
})
}
const changeStatus = () => {
quoteStatus.value = !quoteStatus.value
if (auctionData.value.artwork.isSelling&&!auctionData.value.artwork.isSoled){
quoteStatus.value = true
}else {
if (quoteStatus.value){
quoteStatus.value = false
}
}
}
return{
pullLink,
getLiveLink,
auctionData,
getSocketData,
show1,
playerId,
show,

65
app/utils/websocket.ts Normal file
View File

@ -0,0 +1,65 @@
export class WebSocketClient {
private socket: WebSocket | null = null
private baseUrl: string
private token: string
constructor(baseUrl: string, token: string) {
this.baseUrl = baseUrl
this.token = token
}
connect(path: string, params: Record<string, any> = {}) {
// 如果存在旧连接,先关闭
this.disconnect()
// 构建参数对象,自动添加 token
const queryParams = {
token: this.token,
...params
}
// 构建查询字符串
const queryString = '?' + Object.entries(queryParams)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
// 构建完整的 WebSocket URL
const wsUrl = `${this.baseUrl}${path}${queryString}`
this.socket = new WebSocket(wsUrl)
return {
onOpen: (callback: () => void) => {
this.socket!.onopen = callback
},
onMessage: (callback: (data: any) => void) => {
this.socket!.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
callback(data)
} catch (error) {
console.error('解析消息失败:', error)
}
}
},
onClose: (callback: () => void) => {
this.socket!.onclose = callback
},
onError: (callback: (error: Event) => void) => {
this.socket!.onerror = callback
}
}
}
disconnect() {
if (this.socket) {
this.socket.close()
this.socket = null
}
}
isConnected() {
return this.socket?.readyState === WebSocket.OPEN
}
}

6
env/.env.test vendored
View File

@ -1,7 +1,7 @@
# 测试环境配置
NUXT_PUBLIC_API_BASE=http://172.16.100.99:8005
NUXT_PUBLIC_WS_URL=ws://test-ws.example.com
NUXT_PUBLIC_API_BASE=https://auction-test.szjixun.cn
NUXT_PUBLIC_API_COLLECT_CODE=https://auction-test.szjixun.cn
NUXT_API_SECRET=test-secret
NUXT_PUBLIC_SOCKET_URL=ws://172.16.100.99:8005
# 阿里云播放器配置
NUXT_PUBLIC_PLAYER_SOURCE=artc://live-pull-sh-01.szjixun.cn/live/live?auth_key=1737080180-0-0-42ad4cf26ba26eee78ca7de9c524d1e0
NUXT_PUBLIC_PLAYER_SOURCE=artc://live-push-sh-01.szjixun.cn/live001/86180cae-1e07-4b8d-b45e-50d8ce800110?auth_key=1739255918-0-0-5251017e725a860570a59de7e4e2fd98

View File

@ -18,6 +18,11 @@ export default defineNuxtConfig({
'@nuxt/image',
'@nuxtjs/i18n',
],
image: {
provider: 'ipx',
format: ['webp'],
quality: 80,
},
runtimeConfig: {
// 私有配置,只有在服务端可用
apiSecret: process.env.NUXT_API_SECRET,

View File

@ -23,9 +23,12 @@
"@yeger/vue-masonry-wall": "^5.0.17",
"aliyun-aliplayer": "^2.28.5",
"axios": "^1.7.9",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"nuxt": "^3.15.0",
"pinyin": "4.0.0-alpha.2",
"qrcode": "^1.5.4",
"segmentit": "^2.0.3",
"vconsole": "^3.15.1",
"vue": "^3.5.13",
@ -39,9 +42,11 @@
"@vant/nuxt": "^1.0.6",
"bumpp": "^9.9.2",
"cross-env": "^7.0.3",
"ipx": "^3.0.1",
"postcss-mobile-forever": "^4.3.1",
"sass": "^1.83.1",
"sass-loader": "^16.0.4",
"sharp": "^0.33.5",
"typescript": "~5.7.2",
"vant": "^4.9.15"
},

View File

@ -29,6 +29,12 @@ importers:
axios:
specifier: ^1.7.9
version: 1.7.9
crypto-js:
specifier: ^4.2.0
version: 4.2.0
dayjs:
specifier: ^1.11.13
version: 1.11.13
dotenv:
specifier: ^16.4.7
version: 16.4.7
@ -38,6 +44,9 @@ importers:
pinyin:
specifier: 4.0.0-alpha.2
version: 4.0.0-alpha.2(segmentit@2.0.3)
qrcode:
specifier: ^1.5.4
version: 1.5.4
segmentit:
specifier: ^2.0.3
version: 2.0.3
@ -72,6 +81,9 @@ importers:
cross-env:
specifier: ^7.0.3
version: 7.0.3
ipx:
specifier: ^3.0.1
version: 3.0.1(db0@0.2.1)(ioredis@5.4.2)
postcss-mobile-forever:
specifier: ^4.3.1
version: 4.3.2(postcss@8.5.1)
@ -81,6 +93,9 @@ importers:
sass-loader:
specifier: ^16.0.4
version: 16.0.4(sass@1.83.4)(webpack@5.97.1(esbuild@0.24.2))
sharp:
specifier: ^0.33.5
version: 0.33.5
typescript:
specifier: ~5.7.2
version: 5.7.3
@ -251,6 +266,9 @@ packages:
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
engines: {node: '>=16.13'}
'@emnapi/runtime@1.3.1':
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
@ -615,6 +633,123 @@ packages:
'@iconify/utils@2.2.1':
resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==}
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.33.5':
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.0.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.0.4':
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.0.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-ia32@0.33.5':
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.33.5':
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@intlify/bundle-utils@10.0.0':
resolution: {integrity: sha512-BR5yLOkF2dzrARTbAg7RGAIPcx9Aark7p1K/0O285F7rfzso9j2dsa+S4dA67clZ0rToZ10NSSTfbyUptVu7Bg==}
engines: {node: '>= 18'}
@ -655,8 +790,8 @@ packages:
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
engines: {node: '>= 16'}
'@intlify/shared@11.0.1':
resolution: {integrity: sha512-lH164+aDDptHZ3dBDbIhRa1dOPQUp+83iugpc+1upTOWCnwyC1PVis6rSWNMMJ8VQxvtHQB9JMib48K55y0PvQ==}
'@intlify/shared@11.1.1':
resolution: {integrity: sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==}
engines: {node: '>= 16'}
'@intlify/unplugin-vue-i18n@6.0.3':
@ -1690,6 +1825,10 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-api@3.0.0:
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
@ -1733,6 +1872,9 @@ packages:
resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==}
engines: {node: '>=18'}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@ -1921,6 +2063,9 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
db0@0.2.1:
resolution: {integrity: sha512-BWSFmLaCkfyqbSEZBQINMVNjCVfrogi7GQ2RSy1tmtfK9OXlsup6lUMwLsqSD7FbAjD04eWFdXowSHHUp6SE/Q==}
peerDependencies:
@ -1958,6 +2103,10 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
@ -2027,6 +2176,9 @@ packages:
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
engines: {node: '>=0.3.1'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@ -2263,6 +2415,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@ -2511,6 +2667,10 @@ packages:
resolution: {integrity: sha512-AVnPGXJ8L41vjd11Z4akIF2yd14636Klxul3tBySxHA6PKfCOQPxBDkCFK5zcWh0z/keR6toh1eg8qzdBVUgdA==}
hasBin: true
ipx@3.0.1:
resolution: {integrity: sha512-OqbP9wLqpGXtI/le0sU4exCH5cb7kZS9jaV5xDDM8wZ62VJZBBhEjR0gXwK0agA/GfS4g/GGwY9fFSIFrTb4Gg==}
hasBin: true
iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
@ -2727,6 +2887,10 @@ packages:
resolution: {integrity: sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==}
engines: {node: '>=14'}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@ -3040,14 +3204,26 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@ -3162,6 +3338,10 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
postcss-calc@10.1.0:
resolution: {integrity: sha512-uQ/LDGsf3mgsSUEXmAt3VsCSHR3aKqtEIkmB+4PhzYwRYOW5MZs/GhCCFpsOtJJkP6EC6uGipbrnaTjqaJZcJw==}
engines: {node: ^18.12 || ^20.9 || >=22.0}
@ -3384,6 +3564,11 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -3448,6 +3633,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -3569,6 +3757,9 @@ packages:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@ -3576,6 +3767,10 @@ packages:
resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==}
engines: {node: '>=14.15.0'}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@ -4204,6 +4399,9 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@ -4218,6 +4416,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -4246,6 +4448,9 @@ packages:
engines: {node: '>= 0.10.0'}
hasBin: true
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -4276,10 +4481,18 @@ packages:
engines: {node: '>= 14'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
@ -4510,6 +4723,11 @@ snapshots:
dependencies:
mime: 3.0.0
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.23.1':
optional: true
@ -4699,8 +4917,7 @@ snapshots:
'@eslint/core': 0.10.0
levn: 0.4.1
'@fastify/accept-negotiator@1.1.0':
optional: true
'@fastify/accept-negotiator@1.1.0': {}
'@fingerprintjs/fingerprintjs@4.5.1':
dependencies:
@ -4738,6 +4955,81 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.3.1
optional: true
'@img/sharp-win32-ia32@0.33.5':
optional: true
'@img/sharp-win32-x64@0.33.5':
optional: true
'@intlify/bundle-utils@10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))':
dependencies:
'@intlify/message-compiler': 11.0.0-rc.1
@ -4781,14 +5073,14 @@ snapshots:
'@intlify/shared@11.0.0-rc.1': {}
'@intlify/shared@11.0.1': {}
'@intlify/shared@11.1.1': {}
'@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.18.0(jiti@2.4.2))(rollup@4.31.0)(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.18.0(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.0.1
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.0.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.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))
'@rollup/pluginutils': 5.1.4(rollup@4.31.0)
'@typescript-eslint/scope-manager': 8.21.0
'@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3)
@ -4812,11 +5104,11 @@ snapshots:
'@intlify/utils@0.13.0': {}
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.0.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.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))':
dependencies:
'@babel/parser': 7.26.5
optionalDependencies:
'@intlify/shared': 11.0.1
'@intlify/shared': 11.1.1
'@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))
@ -6257,6 +6549,8 @@ snapshots:
callsites@3.1.0: {}
camelcase@5.3.1: {}
caniuse-api@3.0.0:
dependencies:
browserslist: 4.24.4
@ -6308,6 +6602,12 @@ snapshots:
is-wsl: 3.1.0
is64bit: 2.0.0
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@ -6326,13 +6626,11 @@ snapshots:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
optional: true
color@4.2.3:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
optional: true
colord@2.9.3: {}
@ -6454,8 +6752,7 @@ snapshots:
cssesc@3.0.0: {}
cssfilter@0.0.10:
optional: true
cssfilter@0.0.10: {}
cssnano-preset-default@7.0.6(postcss@8.5.1):
dependencies:
@ -6507,6 +6804,8 @@ snapshots:
csstype@3.1.3: {}
dayjs@1.11.13: {}
db0@0.2.1: {}
debug@2.6.9:
@ -6519,6 +6818,8 @@ snapshots:
optionalDependencies:
supports-color: 9.4.0
decamelize@1.2.0: {}
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
@ -6562,6 +6863,8 @@ snapshots:
diff@7.0.0: {}
dijkstrajs@1.0.3: {}
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@ -6862,6 +7165,11 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@ -7160,12 +7468,49 @@ snapshots:
- uploadthing
optional: true
ipx@3.0.1(db0@0.2.1)(ioredis@5.4.2):
dependencies:
'@fastify/accept-negotiator': 1.1.0
citty: 0.1.6
consola: 3.4.0
defu: 6.1.4
destr: 2.0.3
etag: 1.8.1
h3: 1.14.0
image-meta: 0.2.1
listhen: 1.9.0
ofetch: 1.4.1
pathe: 1.1.2
sharp: 0.33.5
svgo: 3.3.2
ufo: 1.5.4
unstorage: 1.14.4(db0@0.2.1)(ioredis@5.4.2)
xss: 1.0.15
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
- '@azure/data-tables'
- '@azure/identity'
- '@azure/keyvault-secrets'
- '@azure/storage-blob'
- '@capacitor/preferences'
- '@deno/kv'
- '@netlify/blobs'
- '@planetscale/database'
- '@upstash/redis'
- '@vercel/blob'
- '@vercel/kv'
- aws4fetch
- db0
- idb-keyval
- ioredis
- uploadthing
iron-webcrypto@1.2.1: {}
is-arrayish@0.2.1: {}
is-arrayish@0.3.2:
optional: true
is-arrayish@0.3.2: {}
is-binary-path@2.1.0:
dependencies:
@ -7354,6 +7699,10 @@ snapshots:
mlly: 1.7.4
pkg-types: 1.3.1
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@ -7836,14 +8185,24 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
package-manager-detector@0.2.8: {}
@ -7930,6 +8289,8 @@ snapshots:
pluralize@8.0.0: {}
pngjs@5.0.0: {}
postcss-calc@10.1.0(postcss@8.5.1):
dependencies:
postcss: 8.5.1
@ -8146,6 +8507,12 @@ snapshots:
punycode@2.3.1: {}
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
queue-microtask@1.2.3: {}
queue-tick@1.0.1: {}
@ -8218,6 +8585,8 @@ snapshots:
require-from-string@2.0.2: {}
require-main-filename@2.0.0: {}
resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
@ -8355,6 +8724,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-blocking@2.0.0: {}
setprototypeof@1.2.0: {}
sharp@0.32.6:
@ -8371,6 +8742,32 @@ snapshots:
- bare-buffer
optional: true
sharp@0.33.5:
dependencies:
color: 4.2.3
detect-libc: 2.0.3
semver: 7.6.3
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
'@img/sharp-libvips-darwin-arm64': 1.0.4
'@img/sharp-libvips-darwin-x64': 1.0.4
'@img/sharp-libvips-linux-arm': 1.0.5
'@img/sharp-libvips-linux-arm64': 1.0.4
'@img/sharp-libvips-linux-s390x': 1.0.4
'@img/sharp-libvips-linux-x64': 1.0.4
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
'@img/sharp-linux-arm': 0.33.5
'@img/sharp-linux-arm64': 0.33.5
'@img/sharp-linux-s390x': 0.33.5
'@img/sharp-linux-x64': 0.33.5
'@img/sharp-linuxmusl-arm64': 0.33.5
'@img/sharp-linuxmusl-x64': 0.33.5
'@img/sharp-wasm32': 0.33.5
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@ -8404,7 +8801,6 @@ snapshots:
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
optional: true
sirv@3.0.0:
dependencies:
@ -9064,6 +9460,8 @@ snapshots:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-module@2.0.1: {}
which@2.0.2:
dependencies:
isexe: 2.0.0
@ -9074,6 +9472,12 @@ snapshots:
word-wrap@1.2.5: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@ -9094,7 +9498,8 @@ snapshots:
dependencies:
commander: 2.20.3
cssfilter: 0.0.10
optional: true
y18n@4.0.3: {}
y18n@5.0.8: {}
@ -9116,8 +9521,27 @@ snapshots:
yaml@2.7.0: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@21.1.1: {}
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
yargs@17.7.2:
dependencies:
cliui: 8.0.1

View File

@ -13,6 +13,15 @@ import {
// https://unocss.dev/guide/config-file
export default defineConfig({
rules: [
['h-screen-nav', { height: 'calc(100vh - var(--van-nav-bar-height))' }],
[/^van-btn-w-(\d+)px$/, ([_, d]) => ({
width: `${d}px !important` // 使用 !important 覆盖默认样式
})],
[/^van-btn-h-(\d+)px$/, ([_, d]) => ({
height: `${d}px !important`
})]
],
shortcuts: [
// shortcuts to multiple utilities
['btn', 'px-6 py-3 rounded-3 inline-block bg-primary text-white cursor-pointer hover:bg-primary-hover disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],