Compare commits

..

32 Commits

Author SHA1 Message Date
xingyy
107966dabc feat(app): 添加暗黑模式支持并优化主题样式
- 新增暗黑模式支持,使用 colorMode 模块管理主题切换
- 添加全局样式和默认主题样式文件
- 优化 app.vue 中的样式和布局
- 更新 i18n 插件配置,使用 TypeScript 类型
- 调整 nuxt.config.js 中的模块顺序和配置
2025-01-23 14:59:20 +08:00
xingyy
88d1dea0d2 refactor(app): 调整主页相关组件和页面的代码- 在 AppHeader 组件中添加调试日志,用于检查路由元数据
- 优化 home 页面的代码结构,移除未使用的导入和变量
- 在主页面添加 definePageMeta 以设置页面元数据
2025-01-23 14:34:51 +08:00
xingyy
6fc63427de Merge remote-tracking branch 'origin/main' 2025-01-23 14:27:07 +08:00
xingyy
8cb35f4f1b 12 2025-01-23 14:27:02 +08:00
xingyy
579c6df57b refactor(i18n):重命名文件并优化国际化插件代码
- 将 i18n.ts 重命名为 i18n.js,统一文件类型
- 移除了未使用的类型导入 TypeLocale
- 添加了对 vant国际化语言包的导入- 简化了 setLocale 调用,移除了不必要的类型转换
- 优化了代码格式和注释
2025-01-23 14:25:30 +08:00
xingyy
0d1342414b refactor(goods): 优化商品列表获取逻辑
- 移除了不必要的 console.log 语句
- 更新了 artworkList API 的导入路径
- 删除了 Home 组件中的冗余模板代码
2025-01-23 14:22:25 +08:00
xingyy
fbac90a177 refactor(app/config): 修改路由名称
- 将 useAppFooterRouteNames 中的 'home'路由改为 'index'
-保持 useAppHeaderRouteNames 不变
2025-01-23 14:17:49 +08:00
xingyy
44b593966e Merge remote-tracking branch 'origin/main' 2025-01-23 14:16:45 +08:00
xingyy
7ae4899e17 refactor(itemDetail): 移除未使用的代码
- 删除了未使用的 images 数组和 clickSwipe函数
- 注释掉了不再使用的 van-swipe 组件
2025-01-23 14:16:39 +08:00
scout
8b144a270c Merge branch 'qb' of https://gitea-inner.fontree.cn/scout666/liveh5-nuxt 2025-01-23 14:15:24 +08:00
scout
d8b880cf47 扫码 2025-01-23 14:11:41 +08:00
scout
88e8170755 feat(route): 添加collectCode相关路由配置 2025-01-23 14:11:21 +08:00
scout
0d087c0bc3 chore(deps): 添加@fingerprintjs/fingerprintjs依赖 2025-01-23 14:11:21 +08:00
scout
f4df2d0a78 feat(auth): 添加fingerprint存储 2025-01-23 14:11:20 +08:00
xingyy
f80f9c1651 feat(styles): 初始化默认主题样式并支持暗黑模式
- 新增 default-theme.css 文件,定义基础样式和暗黑模式样式
-移除 index.css 文件中的重复样式
- 在 nuxt.config.js 中引入 default-theme.css
- 优化了颜色变量和布局样式
2025-01-23 14:10:15 +08:00
xingyy
e541d0b21d refactor(app): 重构应用配置和样式
- 移除 colorMode 相关代码
- 删除全局样式文件
- 更新 nuxt 配置:
  - 添加 runtimeConfig
  - 更新 css 配置
  - 优化 vite构建配置
  - 新增 image 模块配置
- 更新路由配置
- 调整组件实现
- 更新环境变量加载方式
2025-01-23 14:04:34 +08:00
xingyy
3b8bd623c0 refactor(app): 重构应用配置和样式
- 移除 colorMode 相关代码
- 删除全局样式文件
- 更新 nuxt 配置:
  - 添加 runtimeConfig
  - 更新 css 配置
  - 优化 vite构建配置
  - 新增 image 模块配置
- 更新路由配置
- 调整组件实现
- 更新环境变量加载方式
2025-01-23 13:56:18 +08:00
xingyy
fb8a72b47a feat(ItemList): 优化下拉刷新组件
- 添加刷新成功状态显示
- 设置刷新成功状态持续时间为 700ms
- 在刷新成功时显示图标和提示文本
2025-01-23 12:05:13 +08:00
xingyy
a423a7f801 feat(home): 重构首页布局并添加瀑布流布局
- 移除 Column 组件,使用 vue-masonry-wall 实现瀑布流布局- 更新 ItemList 组件,集成瀑布流布局和新的详情弹窗
- 修改 DetailPopup 组件,使用新的详情信息结构
- 更新 itemDetail 组件,适配新的详情信息数据
- 在项目中添加 vue-masonry-wall 依赖
2025-01-23 11:08:54 +08:00
xingyy
e6fdd0354a perf(home): 为 Column 组件中的图片添加懒加载
- 在 Column 组件的图片标签中添加 loading="lazy" 属性
- 这个修改可以提高页面加载性能,实现图片的懒加载
2025-01-22 16:59:00 +08:00
xingyy
3225d91ecb refactor(components): 重构弹窗组件和详情页展示逻辑
- 修改实名认证详情页布局- 优化 item 详情展示方式
- 调整弹窗组件样式
- 重命名 ItemDetailSheet 为 DetailPopup
- 更新相关组件引用
2025-01-22 16:56:44 +08:00
xingyy
a36a98c576 style(x-image): 添加 object-fit: cover 样式
- 在 x-image 组件的 img 标签中添加 object-fit: cover 样式
- 确保图片在容器中等比例填充,保持图片的宽高比
2025-01-22 16:33:53 +08:00
xingyy
d3cb4d55b4 refactor(components): 优化多个组件的结构和功能
- 为 x-button 和 x-popup 组件添加注释说明
- 在 x-image 组件中添加 lazy 加载属性
- 优化 profile 页面的我的拍品列表展示
- 更新 tang
2025-01-22 16:23:48 +08:00
xingyy
331b4a73b2 refactor(app): 重构 LiveRoom 组件
- 将 LiveRoom 相关组件和文件重命名,统一使用小写开头
- 新增 x-button、x-image 和 x-popup 组件,替代原有 PressableButton 和 ImagePreview
-优化 SideButton 组件,使用新的 x-button 和 tangPopup 组件- 更新 LiveRoom 组件中的引用和使用方式
- 调整 tangPopup 组件,使用 goodStore 替代静态数据
2025-01-22 15:44:50 +08:00
xingyy
c74ba7bcb3 refactor(home): 移除首页中的拍品列表弹窗
- 删除了首页组件中的 x-popup组件引用
- 移除了首页模板中的拍品列表弹窗内容
- 新增 tang.vue 组件,用于在直播间展示拍品列表
2025-01-22 14:04:49 +08:00
xingyy
c445901806 1231 2025-01-22 14:02:22 +08:00
xingyy
8f38870c33 refactor(components): 新增 x-popup 组件并优化首页弹窗- 新增 x-popup 组件,用于替换原有的 van-popup
- 在首页引入并使用新的 x-popup组件
- 优化弹窗内容的展示结构
- 调整弹窗样式,增加标题插槽和关闭按钮
2025-01-22 14:01:13 +08:00
xingyy
05cd427430 refactor(app): 优化多个组件和 API 请求处理
- 移除不必要的 Promise.reject
- 修正请求体参数名
- 移除未使用的 import
- 优化首页布局和弹窗组件
- 添加文本溢出样式
2025-01-22 13:28:21 +08:00
xingyy
63e24791f2 refactor(api): 重构请求函数并优化代码格式
- 重构了 http.js 中的 request 函数,使用解构赋值简化参数
- 更新了 auth 和 goods模块中的请求函数,采用新的 request 函数格式
- 优化了代码格式,包括缩进、换行和空格
2025-01-22 11:34:36 +08:00
xingyy
635bca0fb6 refactor(api): 重构 HTTP 请求模块
- 新增请求拦截和响应拦截功能
- 添加 HTTP状态码映射和错误处理- 优化请求配置,增加超时和重试设置
- 新增 request 工具函数简化请求操作
- 更新 goods API 使用新的 request 函数
2025-01-22 11:24:41 +08:00
xingyy
6daf34856e refactor(goods): 重构艺术品列表和详情功能
- 优化艺术品列表加载和刷新逻辑
- 添加艺术品详情获取功能
-修复滚动文本组件属性
-优化代码结构和命名
2025-01-22 10:44:43 +08:00
xingyy
59269a7547 1231 2025-01-22 09:44:44 +08:00
39 changed files with 1641 additions and 371 deletions

View File

@ -1,23 +1,25 @@
import { getHttp } from '~/api/http.js' import { request } from '@/api/http.js'
export async function senCode(data) { export async function senCode(data) {
const http = getHttp()
return await http('/api/v1/m/user/send', { return await request({
url:'/api/v1/m/user/send',
method: 'POST', method: 'POST',
body: data, data
}) })
} }
export async function userLogin(data) { export async function userLogin(data) {
const http = getHttp()
return await http('/api/v1/m/user/login', { return await request( {
url:'/api/v1/m/user/login',
method: 'POST', method: 'POST',
body: data, data
}) })
} }
export async function userUpdate(data) { export async function userUpdate(data) {
const http = getHttp() return await request( {
return await http('/api/v1/m/user/update', { url:'/api/v1/m/user/update',
method: 'POST', method: 'POST',
body: data, data
}) })
} }

View File

@ -1,30 +1,32 @@
import { getHttp } from '~/api/http.js' import { request } from '@/api/http.js'
export async function artworkList(data) { export async function artworkList(data) {
const http = getHttp() return await request( {
return await http('/api/v1/m/auction/default/artwork/list', { url:'/api/v1/m/auction/default/artwork/list',
method: 'POST', method: 'POST',
body: data, data
}) })
} }
export async function defaultDetail(data) { export async function defaultDetail(data) {
const http = getHttp() return await request ({
return await http('/api/v1/m/auction/default/detail', { url:'/api/v1/m/auction/default/detail',
method: 'POST', method: 'POST',
body: data, data
}) })
} }
export async function artworkDetail(data) { export async function artworkDetail(data) {
const http = getHttp()
return await http('/api/v1/m/artwork/detail', { return await request( {
url:'/api/v1/m/artwork/detail',
method: 'POST', method: 'POST',
body: data, data,
}) })
} }
export async function userArtworks(data) { export async function userArtworks(data) {
const http = getHttp()
return await http('/api/v1/m/user/artworks', { return await request( {
url:'/api/v1/m/user/artworks',
method: 'POST', method: 'POST',
body: data, data
}) })
} }

View File

@ -1,52 +1,109 @@
import {useRuntimeConfig} from '#app'
import { useRuntimeConfig } from '#app' import {ofetch} from 'ofetch'
import { ofetch } from 'ofetch'
import {message} from '@/components/x-message/useMessage.js' import {message} from '@/components/x-message/useMessage.js'
import {authStore} from "@/stores/auth/index.js"; import {authStore} from "@/stores/auth/index.js"
let httpStatusErrorHandler
let httpStatusErrorHandler
let http let http
// HTTP 状态码映射
const HTTP_STATUS_MAP = {
400: '请求参数错误',
401: '未授权或登录过期',
403: '访问被禁止',
404: '请求的资源不存在',
500: '服务器内部错误',
502: '网关错误',
503: '服务暂时不可用',
504: '网关超时'
}
export function setupHttp() { export function setupHttp() {
if (http) if (http) return http
return http
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseURL = config.public.NUXT_PUBLIC_API_BASE const baseURL = config.public.NUXT_PUBLIC_API_BASE
const {token}= authStore() const { token } = authStore()
const router = useRouter() const router = useRouter()
http = ofetch.create({
const defaultOptions = {
baseURL, baseURL,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
async onRequest({ options }) { timeout: 15000, // 15秒超时
options.headers = {
...options.headers,
Authorization:token.value
}
},
async onResponse({ response }) {
if (response._data.status===1){
message.error(response._data.msg)
}
if (response._data.status===401){
router.replace('/login')
}
},
async onResponseError({ response }) {
const { message } = response._data
if (Array.isArray(message)) {
message.forEach((item) => {
httpStatusErrorHandler?.(item, response.status)
})
}
else {
httpStatusErrorHandler?.(message, response.status)
}
return Promise.reject(response._data)
},
retry: 3, retry: 3,
retryDelay: 1000, 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('/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) { export function injectHttpStatusErrorHandler(handler) {
@ -59,3 +116,13 @@ export function getHttp() {
} }
return http 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

@ -1,12 +1,12 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {message} from '@/components/x-message/useMessage.js' import {message} from '@/components/x-message/useMessage.js'
// message.success('success') // message.success('success')
useHead({ useHead({
title: useI18n().t('appSetting.appName'), title: useI18n().t('appSetting.appName'),
meta: [ meta: [
{ name: 'description', content: useI18n().t('appSetting.appDescription') }, {name: 'description', content: useI18n().t('appSetting.appDescription')},
{ name: 'keywords', content: useI18n().t('appSetting.appKeyWords') }, {name: 'keywords', content: useI18n().t('appSetting.appKeyWords')},
], ],
}) })
const color = useColorMode() const color = useColorMode()
@ -44,11 +44,11 @@ provide('slideDirection', slideDirection)
<template> <template>
<VanConfigProvider :theme="mode"> <VanConfigProvider :theme="mode">
<NuxtLoadingIndicator <NuxtLoadingIndicator
color="repeating-linear-gradient(to right,var(--c-primary) 0%,var(--c-primary-active) 100%)" /> color="repeating-linear-gradient(to right,var(--c-primary) 0%,var(--c-primary-active) 100%)"/>
<NuxtLayout> <NuxtLayout>
<NuxtPage :transition="{ <NuxtPage :transition="{
name: slideDirection name: slideDirection
}" /> }"/>
</NuxtLayout> </NuxtLayout>
</VanConfigProvider> </VanConfigProvider>
</template> </template>
@ -78,7 +78,8 @@ provide('slideDirection', slideDirection)
.slide-right-leave-to { .slide-right-leave-to {
transform: translateX(100%); transform: translateX(100%);
} }
:root{
:root {
--safe-area-inset-bottom: env(safe-area-inset-bottom); --safe-area-inset-bottom: env(safe-area-inset-bottom);
} }
</style> </style>

View File

@ -1,9 +1,8 @@
<script setup> <script setup>
import { useAppFooterRouteNames as names } from '~/config/index.js' import { useAppFooterRouteNames as names } from '@/config/index.js'
import MyIcon from "~/components/icons/MyIcon.vue"; import MyIcon from "@/components/icons/MyIcon.vue";
import HomeIcon from "~/components/icons/HomeIcon.vue"; import HomeIcon from "@/components/icons/HomeIcon.vue";
const route = useRoute() const route = useRoute()
const active = ref(0) const active = ref(0)
const show = computed(() => { const show = computed(() => {
if (route.name && names.includes(route.name)) if (route.name && names.includes(route.name))
@ -13,7 +12,6 @@ const show = computed(() => {
const initData=()=>{ const initData=()=>{
active.value=route.path==='/profile'?1:0 active.value=route.path==='/profile'?1:0
} }
onMounted(()=>{ onMounted(()=>{
initData() initData()
}) })
@ -21,7 +19,6 @@ onMounted(()=>{
<template> <template>
<div v-if="show"> <div v-if="show">
<van-tabbar v-model="active" route placeholder fixed> <van-tabbar v-model="active" route placeholder fixed>
<van-tabbar-item replace to="/"> <van-tabbar-item replace to="/">
<span>{{ $t('tabbar.home') }}</span> <span>{{ $t('tabbar.home') }}</span>

View File

@ -30,7 +30,9 @@ const subTitle = computed(() => {
return route.meta.subTitle ? t(route.meta.subTitle) : '' return route.meta.subTitle ? t(route.meta.subTitle) : ''
}) })
const showLeftArrow = computed(() => route.name && routeWhiteList.includes(route.name)) const showLeftArrow = computed(() => route.name && routeWhiteList.includes(route.name))
console.log('route.meta.i18n',route.meta.i18n)
console.log('t(route.meta.i18n)',route.meta.i18n)
console.log('route.meta.title',route.meta.title)
</script> </script>
<template> <template>

View File

@ -1,48 +1,39 @@
<script setup> <script setup>
import { ImagePreview } from 'vant'; import { showImagePreview } from 'vant';
const images = [
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/637d95b4-2ae9-4a74-bd60-a3a9d2ca2ca0.png', import xImage from '@/components/x-image/index.vue'
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/f7b65e23-ce21-41b4-8e58-9e6dc6950727.png', const props = defineProps({
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/41eceb23-d168-4049-ae8e-48c5328b192f.png', detailInfo: {
]; type: Object,
const clickSwipe=(index)=>{ default: null
showImagePreview({ }
images: images, })
startPosition:index,
})
}
</script> </script>
<template> <template>
<div> <div>
<van-swipe style="height: 188px" indicator-color="#B4B4B4" lazy-render > <div class="flex justify-center">
<van-swipe-item v-for="(image,index) in images" :key="image" @click="clickSwipe(index)"> <xImage class="h-188px" :src="detailInfo?.artwork?.hdPic"></xImage>
<van-image </div>
fit="contain"
width="100%"
:height="188"
:src="image"
/>
</van-swipe-item>
</van-swipe>
<div class="px-[16px] bg-[#fff] pt-[11px] mb-6px"> <div class="px-[16px] bg-[#fff] pt-[11px] mb-6px">
<div class="text-[#000] text-[16px] mb-[12px]">日出而作日落而息</div> <div class="text-[#000] text-[16px] mb-[12px]">{{detailInfo?.artworkTitle}}</div>
<div class="text-#575757 text-[14px] pb-8px"> <div class="text-#575757 text-[14px] pb-8px">
<div class="flex mb-[4px]"> <div class="flex mb-[4px]">
<div class="w-[70px]">作者</div> <div class="w-[70px]">作者</div>
<div>张天赐</div> <div>{{detailInfo?.artwork?.artistName??'-'}}</div>
</div> </div>
<div class="flex mb-[4px]"> <div class="flex mb-[4px]">
<div class="w-[70px] flex-shrink-0">总平尺数</div> <div class="w-[70px] flex-shrink-0">总平尺数</div>
<div></div> <div>{{detailInfo?.artwork?.ruler??'-'}}</div>
</div> </div>
<div class="flex mb-[4px]"> <div class="flex mb-[4px]">
<div class="w-[70px] flex-shrink-0">*</div> <div class="w-[70px] flex-shrink-0">*</div>
<div>100*200cm</div> <div>{{detailInfo?.artwork?.length}}*{{detailInfo?.artwork?.width}}cm</div>
</div> </div>
<div class="flex mb-[4px]"> <div class="flex mb-[4px]">
<div class="w-[70px] flex-shrink-0">画作简介</div> <div class="w-[70px] flex-shrink-0">画作简介</div>
<div>是打卡时间到卡上即可点击卡拉斯科健康就阿斯科利打卡时间到卡萨带手机的啊科技是打卡时</div> <div>{{detailInfo?.artwork?.abstract??'-'}}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,7 @@
<script setup> <script setup>
/*
* 此组件的目的是使用该组件包裹的内容具有按压状态效果
* */
import {ref, defineEmits} from "vue"; import {ref, defineEmits} from "vue";
const emit = defineEmits(["click"]); const emit = defineEmits(["click"]);

View File

@ -0,0 +1,56 @@
<script setup>
import { showImagePreview } from 'vant';
import { NuxtImg } from '#components'
const props = defineProps({
src: {
type: String,
default: ''
},
preview: {
type: Boolean,
default: true
},
//
sizes: {
type: Array,
default: () => [320, 640, 768, 1024]
},
//
format: {
type: String,
default: 'webp'
},
//
quality: {
type: Number,
default: 80
}
})
const showImage = () => {
if (props.preview) {
showImagePreview([props.src]);
}
}
</script>
<template>
<nuxt-img
loading="lazy"
v-bind="{ ...props, ...$attrs }"
style="object-fit: cover"
@click="showImage"
:src="src"
:sizes="sizes"
:format="format"
:quality="quality"
placeholder
/>
</template>
<style scoped>
:deep(img) {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,54 @@
<script setup>
/*
* 封装一个带标题栏的弹窗
* */
const props = defineProps({
show: {
type: Boolean,
default: false
},
title:''
})
const emit = defineEmits(['update:show'])
const close=()=>{
emit('update:show',false)
}
</script>
<template>
<van-popup
:show="show"
teleport="#__nuxt"
position="bottom"
:style="{ height: '74%' }"
v-bind="{...$attrs,...$props}"
>
<div class="flex flex-col h-full">
<!-- 标题栏 -->
<div class="flex items-center pl-16px pr-19px h-40px border-b-1px border-gray-300 shrink-0 relative w-full">
<slot v-if="$slots.title" name="title">
</slot>
<div v-else class="text-black text-16px text-center flex-grow-1">{{ title }}</div>
<van-icon
style="position: absolute"
class="right-19px"
size="20"
name="cross"
color="#939393"
@click="close"
/>
</div>
<!-- 内容区域 -->
<div class="flex-1 px-16px py-18px overflow-hidden relative">
<div class="h-full overflow-y-auto relative">
<slot/>
</div>
</div>
</div>
</van-popup>
</template>
<style scoped>
</style>

View File

@ -1,3 +1,3 @@
export const useAppFooterRouteNames= ['home', 'profile'] export const useAppFooterRouteNames= ['index', 'profile']
export const useAppHeaderRouteNames= ['home', 'profile','login'] export const useAppHeaderRouteNames= ['index', 'profile','login','collectCode-login','collectCode-mine']

View File

@ -0,0 +1,211 @@
<script setup>
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 { message } from '@/components/x-message/useMessage.js'
// ... ...
import FingerprintJS from '@fingerprintjs/fingerprintjs'
const { userInfo, token,fingerprint } = authStore()
const router = useRouter();
const route = useRoute();
const { locale } = useI18n()
definePageMeta({
title: '登录',
i18n: 'login.title',
})
const loadingRef = ref({
loading1: false,
loading2: false,
})
const password = ref('')
const loginType = ref(0)
const interval = ref(null)
const startCountdown = () => {
if (interval.value) {
clearInterval(interval.value);
}
countdown.value = 60;
interval.value = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(interval.value);
}
}, 1000);
}
const countdown = ref(0);
const phoneNum = ref('17630920520')
const code = ref('123789')
const pane = ref(0)
const showKeyboard = ref(false);
const getFingerprint = async () => {
const fp = await FingerprintJS.load()
const result = await fp.get()
return result.visitorId //
}
//
const checkFingerprint = async () => {
const tempFingerprint = await getFingerprint()
if (fingerprint && fingerprint === tempFingerprint) {
await router.push('/collectCode/mine')
}
}
checkFingerprint()
const vanSwipeRef = ref(null)
const getCode = async () => {
loadingRef.value.loading1 = true
const res = await senCode({
telNum: phoneNum.value,
zone: '86'
})
loadingRef.value.loading1 = false
if (res.status === 0) {
}
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value = true
startCountdown();
/* pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value=true
startCountdown();*/
}
const changeToPwd = async () => {
loginType.value = loginType.value === 0 ? 1 : 0
}
const goBack = () => {
code.value = ''
pane.value = 0
vanSwipeRef.value?.swipeTo(pane.value)
}
const goLogin = async () => {
loadingRef.value.loading2 = true
const res = await userLogin({
telNum: phoneNum.value,
zone: '86',
code: code.value
})
if (res.status === 0) {
userInfo.value = res.data.accountInfo
token.value = res.data.token
fingerprint.value = await getFingerprint()
await router.push('/collectCode/mine');
}
loadingRef.value.loading2 = false
}
</script>
<template>
<div class="h-[100vh] w-[100vw] bg-[url('@/static/images/asdfsdd.png')] bg-cover px-[31px] pt-[86px]">
<div class="w-full flex justify-center mb-[100px] flex-col items-center">
<img class="h-[105px] w-[189px]" src="@/static/images/ghfggff.png" alt="">
<img class="h-[29px] w-[108px]" src="@/static/images/qrcodetext.png" alt="">
</div>
<van-swipe ref="vanSwipeRef" :show-indicators="false" :touchable="false" :lazy-render="true" :loop="false">
<van-swipe-item>
<div v-show="pane === 0">
<div class="">
<div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="phoneNum" clearable :placeholder="$t('login.phonePlaceholder')">
<template #label>
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
手机号
</div>
</template>
</van-field>
</div>
<div class="border-b-[1.7px] mt-[8px]" v-show="loginType === 1">
<van-field v-model="password" clearable :placeholder="$t('login.passwordPlaceholder')">
<template #label>
<div class="text-[16px] text-[#1A1A1A] flex align-center justify-start">
密码
</div>
</template>
</van-field>
</div>
<div class="flex justify-end mt-[10px]" @click="changeToPwd">
<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>
</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>
</div>
</div>
</div>
</van-swipe-item>
<van-swipe-item>
<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>
<van-password-input :value="code" :gutter="10" :mask="false" focused @focus="showKeyboard = true" />
<div :class="`${countdown > 0 ? 'text-#BDBDBD' : 'text-#2B53AC'} text-14px`">
{{ $t('login.reSend') }}<span v-if="countdown > 0">({{ countdown }})</span>
</div>
<div class="mt-[17px]">
<van-button v-if="code.length === 6" type="primary" block :loading="loadingRef.loading2"
:loading-text="$t('login.login')" style="height: 48px" @click="goLogin">{{
$t('login.login')
}}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{
$t('login.login')
}}</van-button>
</div>
<div class="mt-[17px]">
<van-button type="primary" @click="goBack" block style="height: 48px">{{ $t('login.back')
}}</van-button>
</div>
</div>
</van-swipe-item>
</van-swipe>
<van-number-keyboard v-model="code" :show="showKeyboard" @blur="showKeyboard = false" />
</div>
</template>
<style scoped lang="scss">
:deep(.van-cell.van-field) {
padding-left: 0;
}
:deep(.van-password-input) {
margin: 0;
}
:deep(.van-password-input__item) {
border: 1px solid #E5E5E5;
width: 41px;
height: 41px;
}
</style>

View File

@ -0,0 +1,55 @@
<script setup>
import { userArtworks } from "~/api/goods/index.js";
import { authStore } from "~/stores/auth/index.js";
definePageMeta({
layout: 'default',
title: '收款二维码',
i18n: 'menu.profile',
})
const { userInfo } = authStore()
const initData = async () => {
const res = await userArtworks({})
if (res.status === 0) {
}
}
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="flex items-center px-16px mb-43px">
<div class="mr-23px">
<img class="w-57px h-57px" src="@/static/images/5514@2x.png" alt="">
</div>
<div class="flex flex-col">
<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">
<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>
</div>
</template>
<style scoped>
</style>

View File

@ -1,53 +0,0 @@
<template>
<div class="flex flex-1 flex-col gap-[16px]">
<div
v-for="(item, index) in items"
:key="index"
class="w-full"
@click="openShow(item,index)"
>
<div class="relative w-full">
<img
:src="item.artwork?.hdPic"
class="w-full object-cover rounded-4px"
/>
<div
class="absolute left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
>
LOT{{ item.index+1 }}
</div>
</div>
<div class="pt-[8px]">
<div class="text-[14px] text-[#000000] leading-[20px]">
{{ item.name }}
</div>
<div class="mt-[4px] text-[12px] text-[#575757]">
起拍价{{ item?.startPrice??0 }}
</div>
<div
v-if="item.soldPrice"
class="mt-[4px] text-[12px] text-[#b58047]"
>
成交价{{ item?.startPrice??0 }}
</div>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
items: Array,
colIndex: {
type: Number,
default: 0
}
});
const emit = defineEmits(['openShow']);
const openShow = (item,index) => {
emit('openShow', item,index);
};
</script>

View File

@ -0,0 +1,24 @@
<script setup>
import xPopup from '@/components/x-popup/index.vue'
import ItemDetail from "@/components/itemDetail/index.vue";
import {goodStore} from "~/stores/goods/index.js";
const {
artWorkDetail
} = goodStore()
const props = defineProps({
show: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:show'])
const handleClose = () => {
emit('update:show', false)
}
</script>
<template>
<xPopup :show="show" title="拍品详情" @update:show="handleClose">
<ItemDetail :detailInfo="artWorkDetail" />
</xPopup>
</template>

View File

@ -1,90 +1,116 @@
<script setup> <script setup>
import Column from "~/pages/home/components/Column/index.vue"; import { ref, computed } from 'vue'
import {goodStore} from "~/stores/goods"; import { useRect } from "@vant/use"
import {artworkDetail, artworkList} from "~/api/goods"; import { goodStore } from "@/stores/goods"
import {useRect} from "@vant/use"; import DetailPopup from '../DetailPopup/index.vue'
const {itemList, pageRef,auctionDetail,liveRef,artWorkDetail,currentItem} = goodStore(); import MasonryWall from '@yeger/vue-masonry-wall'
const loading = ref(false); const {
const showHeight = ref(''); itemList,
const show = ref(false); pageRef,
const loading1 = ref(false); auctionDetail,
const finished = ref(false); liveRef,
const getArtworkList=async ()=>{ artWorkDetail,
const res= await artworkList({auctionUuid: auctionDetail.value.uuid,...pageRef.value}) currentItem,
if (res.status===0){ loading: storeLoading,
if (Array.isArray(res.data.data)&&res.data.data?.length>0){ getArtworkList,
itemList.value.push(...res.data.data) getArtworkDetail
} } = goodStore()
if (!res.data.data){
finished.value=true
}
if (res.data.data?.length<pageRef.value.pageSize){
finished.value=true
}
pageRef.value.itemCount=res.data.count
}
}
const loadData = async () => {
pageRef.value.page++
await getArtworkList()
loading.value = false;
if (pageRef.value.itemCount <= itemList.value.length) {
finished.value = true
}
};
const columns = computed(() => {
const result = [[], []];
itemList.value.forEach((item, index) => {
result[index % 2].push({...item, index});
});
return result;
});
const refreshData = async () => {
pageRef.value.page = 1
finished.value=false
const res= await artworkList({auctionUuid: auctionDetail.value.uuid,...pageRef.value})
if (res.status===0){
itemList.value=res.data.data
pageRef.value.itemCount=res.data.count
}
loading1.value = false
}
const getDetail=async ()=>{
const res=await artworkDetail({uuid:currentItem.value.uuid})
if (res.status===0){
artWorkDetail.value
}
}
watch(itemList,(newValue)=>{
console.log('newValue',newValue)
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
}) })
const openShow = (item,index) => { //
currentItem.value=item const loadMore = async () => {
getDetail() pageRef.value.page++
const rect = useRect(liveRef.value.$el); const { finished } = await getArtworkList()
showHeight.value = rect.height; localState.value.finished = finished
nextTick(() => { }
show.value = true;
}); //
}; 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 openShow = async (item) => {
localState.value.showDetail = true
currentItem.value = item
getArtworkDetail(item.uuid)
}
</script> </script>
<template> <template>
<div class="px-[16px] pt-[16px]"> <div class="px-[16px] pt-[16px]">
<van-pull-refresh v-model="loading1" success-text="刷新成功" @refresh="refreshData"> <van-pull-refresh
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" v-model="localState.refreshing"
@load="loadData"> success-text="刷新成功"
:success-duration="700"
@refresh="onRefresh"
>
<template #success>
<van-icon name="success" /> 刷新成功
</template>
<van-list
v-model:loading="storeLoading"
:finished="localState.finished"
finished-text="没有更多了"
@load="loadMore"
>
<div class="w-full flex gap-[16px]"> <div class="w-full flex gap-[16px]">
<Column v-for="(column, colIndex) in columns" :key="colIndex" :colIndex="colIndex" :items="column" @openShow="openShow" /> <masonry-wall :items="itemList" :ssr-columns="2" :maxColumns="2" :minColumns="2" :gap="5">
<template #default="{ item, index }">
<div
@click="openShow(item)"
class="w-full"
>
<div class="relative w-full">
<img
:src="item.artwork?.hdPic"
class="w-full object-cover rounded-4px"
loading="lazy"
/>
<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 }}
</div>
</div>
<div class="pt-[8px]">
<div class="text-[14px] text-[#000000] leading-[20px]">
{{ item.name }}
</div>
<div class="mt-[4px] text-[12px] text-[#575757]">
起拍价{{ item?.startPrice??0 }}
</div>
<div
v-if="item.soldPrice"
class="mt-[4px] text-[12px] text-[#b58047]"
>
成交价{{ item?.startPrice??0 }}
</div>
</div>
</div>
</template>
</masonry-wall>
</div> </div>
</van-list> </van-list>
</van-pull-refresh> </van-pull-refresh>
<van-action-sheet teleport="#__nuxt" :round="false" v-model:show="show" title="拍品详情"> <DetailPopup v-model:show="localState.showDetail"></DetailPopup>
<div class="content bg-[#F0F0F0]" :style="`height: calc(100vh - ${showHeight + 85}px)`">
<itemDetail/>
</div>
</van-action-sheet>
</div> </div>
</template> </template>
<style scoped>
.content {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
</style>

View File

@ -1,24 +1,18 @@
<script setup> <script setup>
import LiveRoom from '@/pages/LiveRoom/index.client.vue'; import liveRoom from '@/pages/liveRoom/index.client.vue';
import {goodStore} from "@/stores/goods/index.js"; import {goodStore} from "@/stores/goods/index.js";
import ItemList from './components/ItemList/index.vue' import ItemList from './components/ItemList/index.vue'
import Cescribe from './components/Cescribe/index.vue' import Cescribe from './components/Cescribe/index.vue'
import {artworkList} from "~/api/goods/index.js"; const {fullLive, liveRef} = goodStore();
const {fullLive,getAuctionDetail,auctionDetail,itemList,pageRef,liveRef} = goodStore();
definePageMeta({
layout: 'default',
i18n: 'menu.home',
})
const changeLive = () => { const changeLive = () => {
fullLive.value = true; fullLive.value = true;
}; };
</script> </script>
<template> <template>
<div class="flex-grow-1"> <div class="flex-grow-1">
<client-only> <client-only>
<LiveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']" ref="liveRef" :fullLive="fullLive"/> <liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']" ref="liveRef"
:fullLive="fullLive"/>
</client-only> </client-only>
<div v-show="!fullLive" class="bg-#fff"> <div v-show="!fullLive" class="bg-#fff">
<van-tabs sticky animated> <van-tabs sticky animated>
@ -33,12 +27,14 @@ const changeLive = () => {
</div> </div>
</div> </div>
</template> </template>
<style>
:root:root {
--van-action-sheet-header-height: 39px;
}
</style>
<style scoped lang="scss"> <style scoped lang="scss">
.ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
:deep(.van-swipe__indicator) { :deep(.van-swipe__indicator) {
width: 8px; width: 8px;
height: 8px; height: 8px;
@ -59,7 +55,7 @@ const changeLive = () => {
.changeLive { .changeLive {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
transition: height 0.5s ease, transform 0.5s ease; transition: height 0.4s ease, transform 0.4s ease;
} }
.changeLive.collapsed { .changeLive.collapsed {

11
app/pages/index.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup>
import Home from './home/index.vue'
definePageMeta({
layout: 'default',
title: '主页',
i18n: 'menu.home',
})
</script>
<template>
<Home/>
</template>

View File

@ -1,24 +1,31 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import lockClosed from "~/static/images/lockdfd@2x.png"; import lockClosed from "@/static/images/lockdfd@2x.png";
import lockOpen from "~/static/images/lock4@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 PressableButton from './PressableButton.vue' import xButton from '@/components/x-button/index.vue'
const { quoteStatus, changeStatus,show,show1 } = liveStore(); import tangPopup from './tangPopup.vue'
import {goodStore} from "~/stores/goods/index.js";
const { quoteStatus, changeStatus,show } = liveStore();
const {pageRef} = goodStore();
const showTang=ref(false)
const openOne=()=>{
showTang.value=true
}
</script> </script>
<template> <template>
<div class="bg-white w-60px rounded-l-4px overflow-hidden"> <div class="bg-white w-60px rounded-l-4px overflow-hidden">
<!-- 拍品信息 --> <!-- 拍品信息 -->
<PressableButton> <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"> <div class="w-60px h-60px text-center border-b border-gray-300 flex flex-col justify-center items-center text-#7D7D7F text-12px">
<div>拍品</div> <div>拍品</div>
<div>(1/188)</div> <div>(1/{{pageRef.itemCount??0}})</div>
</div> </div>
</PressableButton> </x-button>
<tangPopup v-model:show="showTang"></tangPopup>
<!-- 出价开关 --> <!-- 出价开关 -->
<PressableButton @click="changeStatus"> <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="w-60px h-60px text-center border-b border-gray-300 flex flex-col justify-center items-center">
<div class="mb-1"> <div class="mb-1">
<img <img
@ -31,14 +38,15 @@ const { quoteStatus, changeStatus,show,show1 } = liveStore();
{{ quoteStatus ? '关闭出价' : '开启出价' }} {{ quoteStatus ? '关闭出价' : '开启出价' }}
</div> </div>
</div> </div>
</PressableButton> </x-button>
<!-- 支付 --> <!-- 支付 -->
<PressableButton @click="show = true"> <x-button @click="show = true">
<div class="w-60px h-60px text-center border-b border-gray-300 flex flex-col justify-center items-center text-yellow-600"> <div class="w-60px h-60px text-center flex flex-col justify-center items-center text-yellow-600">
<div class="text-10px">RMB</div> <div class="text-10px">RMB</div>
<div class="text-12px">5,000</div> <div class="text-12px">5,000</div>
<div class="text-10px">去支付</div> <div class="text-10px">去支付</div>
</div> </div>
</PressableButton> </x-button>
</div> </div>
</template> </template>

View File

@ -0,0 +1,61 @@
<script setup>
import xPopup from '@/components/x-popup/index.vue'
import {goodStore} from "~/stores/goods/index.js";
import xImage from '@/components/x-image/index.vue'
const {pageRef,itemList} = goodStore();
const props = defineProps({
show: Boolean,
title: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:show'])
const close = () => emit('update:show', false);
</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"
>
<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>
</div>
</div>
</x-popup>
</template>
<style scoped>
.ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -2,13 +2,12 @@
import {ref, onMounted, onBeforeUnmount} from 'vue' import {ref, onMounted, onBeforeUnmount} from 'vue'
import Aliplayer from 'aliyun-aliplayer' import Aliplayer from 'aliyun-aliplayer'
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css' import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
import sideButton from '~/pages/LiveRoom/components/SideButton/index.vue' import sideButton from '~/pages/liveRoom/components/SideButton/index.vue'
import broadcast from '~/pages/LiveRoom/components/Broadcast/index.vue' import broadcast from '~/pages/liveRoom/components/Broadcast/index.vue'
import {liveStore} from "~/stores/live/index.js"; import {liveStore} from "~/stores/live/index.js";
import paymentResults from '~/pages/LiveRoom/components/PaymentResults/index.vue' import paymentResults from '~/pages/liveRoom/components/PaymentResults/index.vue'
import paymentInput from '~/pages/LiveRoom/components/PaymentInput/index.vue' import paymentInput from '~/pages/liveRoom/components/PaymentInput/index.vue'
import PressableButton from '~/pages/LiveRoom/components/SideButton/PressableButton.vue' import xButton from '@/components/x-button/index.vue'
const player = ref(null) const player = ref(null)
const {quoteStatus, changeStatus, show, playerId, show1} = liveStore() const {quoteStatus, changeStatus, show, playerId, show1} = liveStore()
const isPlayerReady = ref(false) const isPlayerReady = ref(false)
@ -110,18 +109,18 @@ watch(()=>{
<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"> <div class="text-16px text-#FFB25F font-600">
当前价RMB 当前价RMB
<van-rolling-text class="my-rolling-text" :start-num="0" :target-num="3000" direction="up"/> <van-rolling-text class="my-rolling-text" :start-num="0" :duration="0.5" :target-num="3000" direction="up"/>
</div> </div>
<div class="text-16px text-#fff font-600"> <div class="text-16px text-#fff font-600">
下口价RMB 下口价RMB
<van-rolling-text class="my-rolling-text1" :start-num="0" :target-num="3500" direction="up"/> <van-rolling-text class="my-rolling-text1" :start-num="0" :duration="0.5" :target-num="3500" direction="up"/>
</div> </div>
<PressableButton> <x-button>
<div <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`"> :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' : '点击"开启出价",即刻参与竞拍 ' }} {{ quoteStatus ? '确认出价 RMB 3,000' : '点击"开启出价",即刻参与竞拍 ' }}
</div> </div>
</PressableButton> </x-button>
<broadcast></broadcast> <broadcast></broadcast>
</div> </div>
<paymentInput v-model:show="show"/> <paymentInput v-model:show="show"/>

View File

@ -158,7 +158,7 @@ const goLogin =async () => {
<div /> <div />
</div> </div>
<div class="mt-[55px]"> <div class="mt-[55px]">
<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 :loading="loadingRef.loading1" v-if="phoneNum" :loading-text="$t('login.getCode')" color="#2B53AC" block style="height: 48px" @click="getCode">{{ $t('login.getCode')
}}</van-button> }}</van-button>
<van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode') <van-button v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode')
}}</van-button> }}</van-button>

View File

@ -1,17 +1,36 @@
<script setup> <script setup>
import {userArtworks} from "~/api/goods/index.js"; import {userArtworks} from "~/api/goods/index.js";
import {authStore} from "~/stores/auth/index.js"; import {authStore} from "~/stores/auth/index.js";
import xImage from '@/components/x-image/index.vue'
definePageMeta({ definePageMeta({
layout: 'default', layout: 'default',
title: '我的', title: '我的',
i18n: 'menu.profile', i18n: 'menu.profile',
}) })
const myList=ref([])
const showMyList=ref([])
const {userInfo}= authStore() const {userInfo}= authStore()
const groupAndSortByDate=(data)=> {
if (!Array.isArray(data)) {
return
}
return Object.values(data.reduce((acc, curr) => {
if (!acc[curr.userCreatedAt]) {
acc[curr.userCreatedAt] = {
userCreatedAt: curr.userCreatedAt,
list: []
};
}
acc[curr.userCreatedAt].list.push(curr);
return acc;
}, {}))
.sort((a, b) => new Date(b.userCreatedAt) - new Date(a.userCreatedAt));
}
const initData=async ()=>{ const initData=async ()=>{
const res=await userArtworks({}) const res=await userArtworks({})
if (res.status===0){ if (res.status===0){
myList.value=res.data.data
showMyList.value=groupAndSortByDate(myList.value)
} }
} }
initData() initData()
@ -28,55 +47,22 @@ initData()
<div class="text-#575757 text-14px">{{userInfo.telNum}}</div> <div class="text-#575757 text-14px">{{userInfo.telNum}}</div>
</div> </div>
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1 ">
<div class="border-b-1px border-b-#D3D3D3 px-16px flex"> <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 class="text-#000 text-16px border-b-3 border-b-#2B53AC h-36px">我的拍品</div>
</div> </div>
<van-pull-refresh> <van-pull-refresh @refresh="initData">
<van-list <van-list
finished-text="没有更多了" finished-text="没有更多了"
> >
<div class="px-16px pt-14px"> <div class="px-16px pt-14px" v-for="(item,index) of showMyList">
<div class="text-#575757 text-14px mb-3px">2025.01.12</div> <div class="text-#575757 text-14px mb-3px">{{item.userCreatedAt}}</div>
<div class="flex mb-22px"> <div class="flex mb-22px" v-for="(item1,index1) of item.list">
<div class="flex-shrink-0 mr-10px"> <div class="flex-shrink-0 mr-10px rounded-4px overflow-hidden">
<img class="w-80px h-80px" src="@/static/images/ddfdcaer.png" alt=""> <x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" alt=""/>
</div> </div>
<div class="flex flex-col justify-between"> <div class="flex flex-col justify-between">
<div class="text-#000 text-16px ellipsis line-height-21px">张天赐 | 日出而作日落而息撒打算撒打算撒打决赛多久啊是世奥兰</div> <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>
</div>
<div class="flex">
<div class="flex-shrink-0 mr-10px">
<img class="w-80px h-80px" src="@/static/images/ddfdcaer.png" alt="">
</div>
<div class="flex flex-col justify-between">
<div class="text-#000 text-16px ellipsis line-height-21px">张天赐 | 日出而作日落而息撒打算撒打算撒打决赛多久啊是世奥兰</div>
<div class="text-#575757 text-14px line-height-none ">起拍价RMB 1,000</div>
<div class="text-[#fdc181ff] line-height-none">成交价RMB 10,000</div>
</div>
</div>
</div>
<div class="px-16px pt-14px">
<div class="text-#575757 text-14px mb-3px">2025.01.12</div>
<div class="flex mb-22px">
<div class="flex-shrink-0 mr-10px">
<img class="w-80px h-80px" src="@/static/images/ddfdcaer.png" alt="">
</div>
<div class="flex flex-col justify-between">
<div class="text-#000 text-16px ellipsis line-height-21px">张天赐 | 日出而作日落而息撒打算撒打算撒打决赛多久啊是世奥兰</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>
</div>
<div class="flex">
<div class="flex-shrink-0 mr-10px">
<img class="w-80px h-80px" src="@/static/images/ddfdcaer.png" alt="">
</div>
<div class="flex flex-col justify-between">
<div class="text-#000 text-16px ellipsis line-height-21px">张天赐 | 日出而作日落而息撒打算撒打算撒打决赛多久啊是世奥兰</div>
<div class="text-#575757 text-14px line-height-none ">起拍价RMB 1,000</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="text-#B58047 text-14px line-height-none">成交价RMB 10,000</div>
</div> </div>

View File

@ -14,7 +14,7 @@ const {userInfo}= authStore()
<template> <template>
<div class="text-#1A1A1A text-16px"> <div class="text-#1A1A1A text-16px">
<template v-if="type===0"> <template v-if="type===0">
<div class="flex mb-20px" > <div class="flex mb-20px">
<div class="mr-10px">姓名</div> <div class="mr-10px">姓名</div>
<div>{{userInfo.realName}}</div> <div>{{userInfo.realName}}</div>
</div> </div>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import MasonryWall from '@yeger/vue-masonry-wall'
const items = [
{
title: 'First',
description: 'The first item.',
},
{
title: 'Second',
description: 'The second item.',
},
]
</script>
<template>
<masonry-wall :items="items" :ssr-columns="2" :minColumns="2" :gap="16">
<template #default="{ item, index }">
<div :style="{ height: `${(index + 1) * 100}px` }">
<h1>{{ item.title }}</h1>
<span>{{ item.description }}</span>
</div>
</template>
</masonry-wall>
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,21 @@
:root:root {
--c-primary: #3554AF;
--c-primary-active: #3554AF;
--van-primary-color: var(--c-primary);
--van-cell-group-inset-padding: 0;
}
#__nuxt {
margin: 0;
padding: 0;
}
html {
background: var(--van-gray-1);
color-scheme: light;
}
html.dark {
background: #222;
color-scheme: dark;
}

View File

@ -3,9 +3,11 @@ export const authStore = createGlobalState(() => {
const token=useLocalStorage('token','') const token=useLocalStorage('token','')
const RefreshToken=useLocalStorage('RefreshToken','') const RefreshToken=useLocalStorage('RefreshToken','')
const userInfo=useLocalStorage('userInfo',{}) const userInfo=useLocalStorage('userInfo',{})
const fingerprint=useLocalStorage('fingerprint','')
return{ return{
userInfo, userInfo,
RefreshToken, RefreshToken,
token token,
fingerprint
} }
}) })

View File

@ -1,40 +1,115 @@
import {createGlobalState} from '@vueuse/core' import { createGlobalState } from '@vueuse/core'
import {artworkList, defaultDetail} from "~/api/goods/index.js"; import { ref } from 'vue'
import { artworkList, defaultDetail, artworkDetail } from "~/api/goods/index.js"
export const goodStore = createGlobalState(() => { export const goodStore = createGlobalState(() => {
// 状态定义
const actionDetails = ref({}) const actionDetails = ref({})
const fullLive = ref(false) const fullLive = ref(false)
const liveRef = ref(null); const liveRef = ref(null)
const currentItem=ref({}) const currentItem = ref({})
const myArtWorks=ref([]) const myArtWorks = ref([])
const pageRef = ref({ const pageRef = ref({
page: 0, page: 0,
pageSize: 5, pageSize: 5,
itemCount: 0 itemCount: 0
}) })
const artWorkDetail=ref(null) const artWorkDetail = ref(null)
const itemList = ref([]) const itemList = ref([])
const auctionDetail = 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 getAuctionDetail = async () => { const getAuctionDetail = async () => {
try {
loading.value = true
const res = await defaultDetail({}) const res = await defaultDetail({})
if (res.status === 0) { if (res.status === 0) {
auctionDetail.value = res.data auctionDetail.value = res.data
} }
} catch (err) {
console.error('获取拍卖详情错误:', err)
} finally {
loading.value = false
} }
const getArtworkList = async (page = pageRef.value) => {
return artworkList({auctionUuid: auctionDetail.value.uuid, ...page})
} }
// 获取艺术品列表
const getArtworkList = async (isRefresh = false) => {
if (isRefresh) {
resetPage()
}
try {
loading.value = true
const res = await artworkList({
auctionUuid: auctionDetail.value.uuid,
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 { return {
myArtWorks, finished: !newItems.length || newItems.length < pageRef.value.pageSize,
currentItem, items: newItems
artWorkDetail, }
liveRef, }
pageRef, return { finished: true, items: [] }
getArtworkList, } catch (err) {
auctionDetail, console.error('获取艺术品列表错误:', err)
getAuctionDetail, return { finished: true, items: [] }
} finally {
loading.value = false
}
}
// 获取艺术品详情
const getArtworkDetail = async (uuid) => {
try {
loading.value = true
const res = await artworkDetail({ uuid })
if (res.status === 0) {
artWorkDetail.value = res.data
}
} catch (err) {
console.error('获取艺术品详情错误:', err)
} finally {
loading.value = false
}
}
return {
// 状态
actionDetails, actionDetails,
fullLive,
liveRef,
currentItem,
myArtWorks,
pageRef,
artWorkDetail,
itemList, itemList,
fullLive auctionDetail,
loading,
error,
// 方法
getAuctionDetail,
getArtworkList,
getArtworkDetail,
resetPage
} }
}) })

44
app/utils/format.js Normal file
View File

@ -0,0 +1,44 @@
/**
* 格式化价格
* @param {number} price - 价格数值
* @param {string} currency - 货币符号默认为 ¥
* @returns {string} 格式化后的价格字符串
*/
export const formatPrice = (price, currency = '¥') => {
if (price == null || isNaN(price)) return `${currency}0`
// 将价格转换为数字
const numPrice = Number(price)
// 处理小数点,保留两位小数
const formattedPrice = numPrice.toFixed(2)
// 添加千位分隔符
const parts = formattedPrice.toString().split('.')
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return `${currency}${parts.join('.')}`
}
/**
* 格式化数字
* @param {number} num - 需要格式化的数字
* @returns {string} 格式化后的数字字符串
*/
export const formatNumber = (num) => {
if (num == null || isNaN(num)) return '0'
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
/**
* 格式化百分比
* @param {number} value - 需要格式化的值
* @param {number} decimals - 小数位数默认为 2
* @returns {string} 格式化后的百分比字符串
*/
export const formatPercent = (value, decimals = 2) => {
if (value == null || isNaN(value)) return '0%'
return `${(Number(value) * 100).toFixed(decimals)}%`
}

View File

@ -13,22 +13,10 @@ const publicConfig = Object.entries(process.env)
export default defineNuxtConfig({ export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
const indexPage = pages.findIndex(page => page.path === '/')
if (indexPage !== -1) {
pages.splice(indexPage, 1)
}
pages.push({
name: 'home',
path: '/',
file: '~/pages/home/index.vue'
})
}
},
modules: [ modules: [
'@vant/nuxt', '@vant/nuxt',
'@unocss/nuxt', '@unocss/nuxt',
'@nuxt/image',
'@nuxtjs/color-mode', '@nuxtjs/color-mode',
'@nuxtjs/i18n', '@nuxtjs/i18n',
], ],
@ -41,9 +29,7 @@ export default defineNuxtConfig({
css: [ css: [
'@unocss/reset/tailwind.css', '@unocss/reset/tailwind.css',
'./app/styles/vars.css', '@/static/styles/default-theme.css',
'./app/styles/global.css',
'./app/styles/default-theme.css',
], ],
postcss: { postcss: {

View File

@ -17,9 +17,11 @@
"start": "cross-env ENV_FILE=.env.prod nuxt start" "start": "cross-env ENV_FILE=.env.prod nuxt start"
}, },
"dependencies": { "dependencies": {
"@fingerprintjs/fingerprintjs": "^4.5.1",
"@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/i18n": "^9.1.1", "@nuxtjs/i18n": "^9.1.1",
"@vueuse/core": "^12.4.0", "@vueuse/core": "^12.4.0",
"@yeger/vue-masonry-wall": "^5.0.17",
"aliyun-aliplayer": "^2.28.5", "aliyun-aliplayer": "^2.28.5",
"axios": "^1.7.9", "axios": "^1.7.9",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
@ -32,6 +34,7 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/carbon": "^1.2.5", "@iconify-json/carbon": "^1.2.5",
"@nuxt/image": "^1.9.0",
"@unocss/nuxt": "0.65.2", "@unocss/nuxt": "0.65.2",
"@unocss/preset-rem-to-px": "0.65.2", "@unocss/preset-rem-to-px": "0.65.2",
"@vant/nuxt": "^1.0.6", "@vant/nuxt": "^1.0.6",

File diff suppressed because it is too large Load Diff