Compare commits
32 Commits
fc3e833605
...
107966dabc
Author | SHA1 | Date | |
---|---|---|---|
|
107966dabc | ||
|
88d1dea0d2 | ||
|
6fc63427de | ||
|
8cb35f4f1b | ||
|
579c6df57b | ||
|
0d1342414b | ||
|
fbac90a177 | ||
|
44b593966e | ||
|
7ae4899e17 | ||
|
8b144a270c | ||
|
d8b880cf47 | ||
|
88e8170755 | ||
|
0d087c0bc3 | ||
|
f4df2d0a78 | ||
|
f80f9c1651 | ||
|
e541d0b21d | ||
|
3b8bd623c0 | ||
|
fb8a72b47a | ||
|
a423a7f801 | ||
|
e6fdd0354a | ||
|
3225d91ecb | ||
|
a36a98c576 | ||
|
d3cb4d55b4 | ||
|
331b4a73b2 | ||
|
c74ba7bcb3 | ||
|
c445901806 | ||
|
8f38870c33 | ||
|
05cd427430 | ||
|
63e24791f2 | ||
|
635bca0fb6 | ||
|
6daf34856e | ||
|
59269a7547 |
@ -1,23 +1,25 @@
|
||||
import { getHttp } from '~/api/http.js'
|
||||
import { request } from '@/api/http.js'
|
||||
|
||||
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',
|
||||
body: data,
|
||||
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',
|
||||
body: data,
|
||||
data
|
||||
})
|
||||
}
|
||||
export async function userUpdate(data) {
|
||||
const http = getHttp()
|
||||
return await http('/api/v1/m/user/update', {
|
||||
return await request( {
|
||||
url:'/api/v1/m/user/update',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
data
|
||||
})
|
||||
}
|
@ -1,30 +1,32 @@
|
||||
import { getHttp } from '~/api/http.js'
|
||||
import { request } from '@/api/http.js'
|
||||
|
||||
export async function artworkList(data) {
|
||||
const http = getHttp()
|
||||
return await http('/api/v1/m/auction/default/artwork/list', {
|
||||
return await request( {
|
||||
url:'/api/v1/m/auction/default/artwork/list',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
data
|
||||
})
|
||||
}
|
||||
export async function defaultDetail(data) {
|
||||
const http = getHttp()
|
||||
return await http('/api/v1/m/auction/default/detail', {
|
||||
return await request ({
|
||||
url:'/api/v1/m/auction/default/detail',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
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',
|
||||
body: data,
|
||||
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',
|
||||
body: data,
|
||||
data
|
||||
})
|
||||
}
|
137
app/api/http.js
137
app/api/http.js
@ -1,52 +1,109 @@
|
||||
|
||||
import { useRuntimeConfig } from '#app'
|
||||
import { ofetch } from 'ofetch'
|
||||
import {useRuntimeConfig} from '#app'
|
||||
import {ofetch} from 'ofetch'
|
||||
import {message} from '@/components/x-message/useMessage.js'
|
||||
import {authStore} from "@/stores/auth/index.js";
|
||||
let httpStatusErrorHandler
|
||||
import {authStore} from "@/stores/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
|
||||
if (http) return http
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.NUXT_PUBLIC_API_BASE
|
||||
const {token}= authStore()
|
||||
const { token } = authStore()
|
||||
const router = useRouter()
|
||||
http = ofetch.create({
|
||||
|
||||
const defaultOptions = {
|
||||
baseURL,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
async onRequest({ options }) {
|
||||
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)
|
||||
},
|
||||
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('/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) {
|
||||
@ -59,3 +116,13 @@ export function getHttp() {
|
||||
}
|
||||
return http
|
||||
}
|
||||
|
||||
// 导出请求工具函数
|
||||
export async function request({url,...options}) {
|
||||
const http = getHttp()
|
||||
try {
|
||||
return await http(url, {...options,body:options.data})
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
13
app/app.vue
13
app/app.vue
@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {message} from '@/components/x-message/useMessage.js'
|
||||
// message.success('success')
|
||||
useHead({
|
||||
title: useI18n().t('appSetting.appName'),
|
||||
meta: [
|
||||
{ name: 'description', content: useI18n().t('appSetting.appDescription') },
|
||||
{ name: 'keywords', content: useI18n().t('appSetting.appKeyWords') },
|
||||
{name: 'description', content: useI18n().t('appSetting.appDescription')},
|
||||
{name: 'keywords', content: useI18n().t('appSetting.appKeyWords')},
|
||||
],
|
||||
})
|
||||
const color = useColorMode()
|
||||
@ -44,11 +44,11 @@ provide('slideDirection', slideDirection)
|
||||
<template>
|
||||
<VanConfigProvider :theme="mode">
|
||||
<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>
|
||||
<NuxtPage :transition="{
|
||||
name: slideDirection
|
||||
}" />
|
||||
}"/>
|
||||
</NuxtLayout>
|
||||
</VanConfigProvider>
|
||||
</template>
|
||||
@ -78,7 +78,8 @@ provide('slideDirection', slideDirection)
|
||||
.slide-right-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
:root{
|
||||
|
||||
:root {
|
||||
--safe-area-inset-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
</style>
|
@ -1,9 +1,8 @@
|
||||
<script setup>
|
||||
import { useAppFooterRouteNames as names } from '~/config/index.js'
|
||||
import MyIcon from "~/components/icons/MyIcon.vue";
|
||||
import HomeIcon from "~/components/icons/HomeIcon.vue";
|
||||
import { useAppFooterRouteNames as names } from '@/config/index.js'
|
||||
import MyIcon from "@/components/icons/MyIcon.vue";
|
||||
import HomeIcon from "@/components/icons/HomeIcon.vue";
|
||||
const route = useRoute()
|
||||
|
||||
const active = ref(0)
|
||||
const show = computed(() => {
|
||||
if (route.name && names.includes(route.name))
|
||||
@ -13,7 +12,6 @@ const show = computed(() => {
|
||||
const initData=()=>{
|
||||
active.value=route.path==='/profile'?1:0
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
initData()
|
||||
})
|
||||
@ -21,7 +19,6 @@ onMounted(()=>{
|
||||
|
||||
<template>
|
||||
<div v-if="show">
|
||||
|
||||
<van-tabbar v-model="active" route placeholder fixed>
|
||||
<van-tabbar-item replace to="/">
|
||||
<span>{{ $t('tabbar.home') }}</span>
|
||||
|
@ -30,7 +30,9 @@ const subTitle = computed(() => {
|
||||
return route.meta.subTitle ? t(route.meta.subTitle) : ''
|
||||
})
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
@ -1,48 +1,39 @@
|
||||
<script setup>
|
||||
import { ImagePreview } from 'vant';
|
||||
const images = [
|
||||
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/637d95b4-2ae9-4a74-bd60-a3a9d2ca2ca0.png',
|
||||
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/f7b65e23-ce21-41b4-8e58-9e6dc6950727.png',
|
||||
'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/41eceb23-d168-4049-ae8e-48c5328b192f.png',
|
||||
];
|
||||
const clickSwipe=(index)=>{
|
||||
showImagePreview({
|
||||
images: images,
|
||||
startPosition:index,
|
||||
})
|
||||
}
|
||||
import { showImagePreview } from 'vant';
|
||||
|
||||
import xImage from '@/components/x-image/index.vue'
|
||||
const props = defineProps({
|
||||
detailInfo: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<van-swipe style="height: 188px" indicator-color="#B4B4B4" lazy-render >
|
||||
<van-swipe-item v-for="(image,index) in images" :key="image" @click="clickSwipe(index)">
|
||||
<van-image
|
||||
fit="contain"
|
||||
width="100%"
|
||||
:height="188"
|
||||
:src="image"
|
||||
/>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div class="flex justify-center">
|
||||
<xImage class="h-188px" :src="detailInfo?.artwork?.hdPic"></xImage>
|
||||
</div>
|
||||
<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="flex mb-[4px]">
|
||||
<div class="w-[70px]">作者:</div>
|
||||
<div>张天赐</div>
|
||||
<div>{{detailInfo?.artwork?.artistName??'-'}}</div>
|
||||
</div>
|
||||
<div class="flex mb-[4px]">
|
||||
<div class="w-[70px] flex-shrink-0">总平尺数:</div>
|
||||
<div>—</div>
|
||||
<div>{{detailInfo?.artwork?.ruler??'-'}}</div>
|
||||
</div>
|
||||
<div class="flex mb-[4px]">
|
||||
<div class="w-[70px] flex-shrink-0">长*宽:</div>
|
||||
<div>100*200cm</div>
|
||||
<div>{{detailInfo?.artwork?.length}}*{{detailInfo?.artwork?.width}}cm</div>
|
||||
</div>
|
||||
<div class="flex mb-[4px]">
|
||||
<div class="w-[70px] flex-shrink-0">画作简介:</div>
|
||||
<div>是打卡时间到卡上即可点击卡拉斯科健康就阿斯科利打卡时间到卡萨带手机的啊科技是打卡时</div>
|
||||
<div>{{detailInfo?.artwork?.abstract??'-'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,7 @@
|
||||
<script setup>
|
||||
/*
|
||||
* 此组件的目的是使用该组件包裹的内容具有按压状态效果
|
||||
* */
|
||||
import {ref, defineEmits} from "vue";
|
||||
|
||||
const emit = defineEmits(["click"]);
|
56
app/components/x-image/index.vue
Normal file
56
app/components/x-image/index.vue
Normal 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>
|
54
app/components/x-popup/index.vue
Normal file
54
app/components/x-popup/index.vue
Normal 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>
|
@ -1,3 +1,3 @@
|
||||
|
||||
export const useAppFooterRouteNames= ['home', 'profile']
|
||||
export const useAppHeaderRouteNames= ['home', 'profile','login']
|
||||
export const useAppFooterRouteNames= ['index', 'profile']
|
||||
export const useAppHeaderRouteNames= ['index', 'profile','login','collectCode-login','collectCode-mine']
|
||||
|
211
app/pages/collectCode/login/index.vue
Normal file
211
app/pages/collectCode/login/index.vue
Normal 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>
|
55
app/pages/collectCode/mine/index.vue
Normal file
55
app/pages/collectCode/mine/index.vue
Normal 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>
|
@ -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>
|
24
app/pages/home/components/DetailPopup/index.vue
Normal file
24
app/pages/home/components/DetailPopup/index.vue
Normal 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>
|
@ -1,90 +1,116 @@
|
||||
<script setup>
|
||||
import Column from "~/pages/home/components/Column/index.vue";
|
||||
import {goodStore} from "~/stores/goods";
|
||||
import {artworkDetail, artworkList} from "~/api/goods";
|
||||
import {useRect} from "@vant/use";
|
||||
const {itemList, pageRef,auctionDetail,liveRef,artWorkDetail,currentItem} = goodStore();
|
||||
const loading = ref(false);
|
||||
const showHeight = ref('');
|
||||
const show = ref(false);
|
||||
const loading1 = ref(false);
|
||||
const finished = ref(false);
|
||||
const getArtworkList=async ()=>{
|
||||
const res= await artworkList({auctionUuid: auctionDetail.value.uuid,...pageRef.value})
|
||||
if (res.status===0){
|
||||
if (Array.isArray(res.data.data)&&res.data.data?.length>0){
|
||||
itemList.value.push(...res.data.data)
|
||||
}
|
||||
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)
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRect } from "@vant/use"
|
||||
import { goodStore } from "@/stores/goods"
|
||||
import DetailPopup from '../DetailPopup/index.vue'
|
||||
import MasonryWall from '@yeger/vue-masonry-wall'
|
||||
const {
|
||||
itemList,
|
||||
pageRef,
|
||||
auctionDetail,
|
||||
liveRef,
|
||||
artWorkDetail,
|
||||
currentItem,
|
||||
loading: storeLoading,
|
||||
getArtworkList,
|
||||
getArtworkDetail
|
||||
} = goodStore()
|
||||
|
||||
const localState = ref({
|
||||
finished: false,
|
||||
refreshing: false,
|
||||
showDetail: false,
|
||||
showHeight: ''
|
||||
})
|
||||
const openShow = (item,index) => {
|
||||
currentItem.value=item
|
||||
getDetail()
|
||||
const rect = useRect(liveRef.value.$el);
|
||||
showHeight.value = rect.height;
|
||||
nextTick(() => {
|
||||
show.value = true;
|
||||
});
|
||||
};
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
pageRef.value.page++
|
||||
const { finished } = await getArtworkList()
|
||||
localState.value.finished = finished
|
||||
}
|
||||
|
||||
// 刷新
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="px-[16px] pt-[16px]">
|
||||
<van-pull-refresh v-model="loading1" success-text="刷新成功" @refresh="refreshData">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了"
|
||||
@load="loadData">
|
||||
<van-pull-refresh
|
||||
v-model="localState.refreshing"
|
||||
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]">
|
||||
<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>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-action-sheet teleport="#__nuxt" :round="false" v-model:show="show" title="拍品详情">
|
||||
<div class="content bg-[#F0F0F0]" :style="`height: calc(100vh - ${showHeight + 85}px)`">
|
||||
<itemDetail/>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
<DetailPopup v-model:show="localState.showDetail"></DetailPopup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
@ -1,25 +1,19 @@
|
||||
<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 ItemList from './components/ItemList/index.vue'
|
||||
import Cescribe from './components/Cescribe/index.vue'
|
||||
import {artworkList} from "~/api/goods/index.js";
|
||||
const {fullLive,getAuctionDetail,auctionDetail,itemList,pageRef,liveRef} = goodStore();
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
i18n: 'menu.home',
|
||||
})
|
||||
const {fullLive, liveRef} = goodStore();
|
||||
const changeLive = () => {
|
||||
fullLive.value = true;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-grow-1">
|
||||
<client-only>
|
||||
<LiveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']" ref="liveRef" :fullLive="fullLive"/>
|
||||
</client-only>
|
||||
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']" ref="liveRef"
|
||||
:fullLive="fullLive"/>
|
||||
</client-only>
|
||||
<div v-show="!fullLive" class="bg-#fff">
|
||||
<van-tabs sticky animated>
|
||||
<van-tab title="拍品列表">
|
||||
@ -33,12 +27,14 @@ const changeLive = () => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
:root:root {
|
||||
--van-action-sheet-header-height: 39px;
|
||||
}
|
||||
</style>
|
||||
<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) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@ -59,7 +55,7 @@ const changeLive = () => {
|
||||
.changeLive {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
transition: height 0.5s ease, transform 0.5s ease;
|
||||
transition: height 0.4s ease, transform 0.4s ease;
|
||||
}
|
||||
|
||||
.changeLive.collapsed {
|
||||
|
11
app/pages/index.vue
Normal file
11
app/pages/index.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import Home from './home/index.vue'
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '主页',
|
||||
i18n: 'menu.home',
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<Home/>
|
||||
</template>
|
@ -1,24 +1,31 @@
|
||||
<script setup>
|
||||
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 PressableButton from './PressableButton.vue'
|
||||
const { quoteStatus, changeStatus,show,show1 } = liveStore();
|
||||
|
||||
import lockClosed from "@/static/images/lockdfd@2x.png";
|
||||
import lockOpen from "@/static/images/lock4@2x.png";
|
||||
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();
|
||||
const {pageRef} = goodStore();
|
||||
const showTang=ref(false)
|
||||
const openOne=()=>{
|
||||
showTang.value=true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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>拍品</div>
|
||||
<div>(1/188)</div>
|
||||
<div>(1/{{pageRef.itemCount??0}})</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="mb-1">
|
||||
<img
|
||||
@ -31,14 +38,15 @@ const { quoteStatus, changeStatus,show,show1 } = liveStore();
|
||||
{{ quoteStatus ? '关闭出价' : '开启出价' }}
|
||||
</div>
|
||||
</div>
|
||||
</PressableButton>
|
||||
</x-button>
|
||||
<!-- 支付 -->
|
||||
<PressableButton @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">
|
||||
<x-button @click="show = true">
|
||||
<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-12px">5,000</div>
|
||||
<div class="text-10px">去支付</div>
|
||||
</div>
|
||||
</PressableButton>
|
||||
</x-button>
|
||||
|
||||
</div>
|
||||
</template>
|
61
app/pages/liveRoom/components/SideButton/tangPopup.vue
Normal file
61
app/pages/liveRoom/components/SideButton/tangPopup.vue
Normal 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>
|
@ -2,13 +2,12 @@
|
||||
import {ref, onMounted, onBeforeUnmount} 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 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 PressableButton from '~/pages/LiveRoom/components/SideButton/PressableButton.vue'
|
||||
|
||||
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'
|
||||
const player = ref(null)
|
||||
const {quoteStatus, changeStatus, show, playerId, show1} = liveStore()
|
||||
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="text-16px text-#FFB25F font-600">
|
||||
当前价: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 class="text-16px text-#fff font-600">
|
||||
下口价: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>
|
||||
<PressableButton>
|
||||
<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>
|
||||
</PressableButton>
|
||||
</x-button>
|
||||
<broadcast></broadcast>
|
||||
</div>
|
||||
<paymentInput v-model:show="show"/>
|
@ -158,7 +158,7 @@ const goLogin =async () => {
|
||||
<div />
|
||||
</div>
|
||||
<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 v-else type="primary" color="#D3D3D3" block style="height: 48px">{{ $t('login.getCode')
|
||||
}}</van-button>
|
||||
|
@ -1,17 +1,36 @@
|
||||
<script setup>
|
||||
import {userArtworks} from "~/api/goods/index.js";
|
||||
import {authStore} from "~/stores/auth/index.js";
|
||||
|
||||
import xImage from '@/components/x-image/index.vue'
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '我的',
|
||||
i18n: 'menu.profile',
|
||||
})
|
||||
const myList=ref([])
|
||||
const showMyList=ref([])
|
||||
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 res=await userArtworks({})
|
||||
if (res.status===0){
|
||||
|
||||
myList.value=res.data.data
|
||||
showMyList.value=groupAndSortByDate(myList.value)
|
||||
}
|
||||
}
|
||||
initData()
|
||||
@ -28,55 +47,22 @@ initData()
|
||||
<div class="text-#575757 text-14px">{{userInfo.telNum}}</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="text-#000 text-16px border-b-3 border-b-#2B53AC h-36px">我的拍品</div>
|
||||
</div>
|
||||
<van-pull-refresh>
|
||||
<van-pull-refresh @refresh="initData">
|
||||
<van-list
|
||||
finished-text="没有更多了"
|
||||
>
|
||||
<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 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-shrink-0 mr-10px rounded-4px overflow-hidden">
|
||||
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" 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-[#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-#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>
|
||||
|
@ -14,7 +14,7 @@ const {userInfo}= authStore()
|
||||
<template>
|
||||
<div class="text-#1A1A1A text-16px">
|
||||
<template v-if="type===0">
|
||||
<div class="flex mb-20px" >
|
||||
<div class="flex mb-20px">
|
||||
<div class="mr-10px">姓名:</div>
|
||||
<div>{{userInfo.realName}}</div>
|
||||
</div>
|
||||
|
24
app/pages/test/index.client.vue
Normal file
24
app/pages/test/index.client.vue
Normal 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>
|
BIN
app/static/images/logout.png
Normal file
BIN
app/static/images/logout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
app/static/images/qrcodetext.png
Normal file
BIN
app/static/images/qrcodetext.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
21
app/static/styles/default-theme.css
Normal file
21
app/static/styles/default-theme.css
Normal 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;
|
||||
}
|
@ -3,9 +3,11 @@ export const authStore = createGlobalState(() => {
|
||||
const token=useLocalStorage('token','')
|
||||
const RefreshToken=useLocalStorage('RefreshToken','')
|
||||
const userInfo=useLocalStorage('userInfo',{})
|
||||
const fingerprint=useLocalStorage('fingerprint','')
|
||||
return{
|
||||
userInfo,
|
||||
RefreshToken,
|
||||
token
|
||||
token,
|
||||
fingerprint
|
||||
}
|
||||
})
|
@ -1,40 +1,115 @@
|
||||
import {createGlobalState} from '@vueuse/core'
|
||||
import {artworkList, defaultDetail} from "~/api/goods/index.js";
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { artworkList, defaultDetail, artworkDetail } from "~/api/goods/index.js"
|
||||
|
||||
export const goodStore = createGlobalState(() => {
|
||||
// 状态定义
|
||||
const actionDetails = ref({})
|
||||
const fullLive = ref(false)
|
||||
const liveRef = ref(null);
|
||||
const currentItem=ref({})
|
||||
const myArtWorks=ref([])
|
||||
const liveRef = ref(null)
|
||||
const currentItem = ref({})
|
||||
const myArtWorks = ref([])
|
||||
const pageRef = ref({
|
||||
page: 0,
|
||||
pageSize: 5,
|
||||
itemCount: 0
|
||||
})
|
||||
const artWorkDetail=ref(null)
|
||||
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 getAuctionDetail = async () => {
|
||||
const res = await defaultDetail({})
|
||||
if (res.status === 0) {
|
||||
auctionDetail.value = res.data
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await defaultDetail({})
|
||||
if (res.status === 0) {
|
||||
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 {
|
||||
finished: !newItems.length || newItems.length < pageRef.value.pageSize,
|
||||
items: newItems
|
||||
}
|
||||
}
|
||||
return { finished: true, items: [] }
|
||||
} catch (err) {
|
||||
console.error('获取艺术品列表错误:', err)
|
||||
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 {
|
||||
myArtWorks,
|
||||
currentItem,
|
||||
artWorkDetail,
|
||||
liveRef,
|
||||
pageRef,
|
||||
getArtworkList,
|
||||
auctionDetail,
|
||||
getAuctionDetail,
|
||||
// 状态
|
||||
actionDetails,
|
||||
fullLive,
|
||||
liveRef,
|
||||
currentItem,
|
||||
myArtWorks,
|
||||
pageRef,
|
||||
artWorkDetail,
|
||||
itemList,
|
||||
fullLive
|
||||
auctionDetail,
|
||||
loading,
|
||||
error,
|
||||
// 方法
|
||||
getAuctionDetail,
|
||||
getArtworkList,
|
||||
getArtworkDetail,
|
||||
resetPage
|
||||
}
|
||||
})
|
44
app/utils/format.js
Normal file
44
app/utils/format.js
Normal 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)}%`
|
||||
}
|
@ -5,30 +5,18 @@ import { currentLocales } from './i18n/i18n'
|
||||
const envFile = process.env.ENV_FILE || '.env.test'
|
||||
dotenv.config({ path: `./env/${envFile}` })
|
||||
const publicConfig = Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith('NUXT_PUBLIC_'))
|
||||
.reduce((config, [key, value]) => {
|
||||
config[key] = value
|
||||
return config
|
||||
}, {})
|
||||
.filter(([key]) => key.startsWith('NUXT_PUBLIC_'))
|
||||
.reduce((config, [key, value]) => {
|
||||
config[key] = value
|
||||
return config
|
||||
}, {})
|
||||
|
||||
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: [
|
||||
'@vant/nuxt',
|
||||
'@unocss/nuxt',
|
||||
'@nuxt/image',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
@ -41,9 +29,7 @@ export default defineNuxtConfig({
|
||||
|
||||
css: [
|
||||
'@unocss/reset/tailwind.css',
|
||||
'./app/styles/vars.css',
|
||||
'./app/styles/global.css',
|
||||
'./app/styles/default-theme.css',
|
||||
'@/static/styles/default-theme.css',
|
||||
],
|
||||
|
||||
postcss: {
|
||||
|
@ -17,9 +17,11 @@
|
||||
"start": "cross-env ENV_FILE=.env.prod nuxt start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fingerprintjs/fingerprintjs": "^4.5.1",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/i18n": "^9.1.1",
|
||||
"@vueuse/core": "^12.4.0",
|
||||
"@yeger/vue-masonry-wall": "^5.0.17",
|
||||
"aliyun-aliplayer": "^2.28.5",
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
@ -32,6 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.2.5",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@unocss/nuxt": "0.65.2",
|
||||
"@unocss/preset-rem-to-px": "0.65.2",
|
||||
"@vant/nuxt": "^1.0.6",
|
||||
|
636
pnpm-lock.yaml
636
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user