feat(i18n): 优化国际化文案并添加通用拍卖术语- 更新了多个组件中的国际化文案,使其更加统一和准确

- 在 zh-CN.json 中添加了"common"字段,用于存储通用拍卖术语
- 调整了部分组件的布局和样式,以更好地展示国际化文案
This commit is contained in:
xingyy 2025-02-12 16:50:52 +08:00
parent b927267c6a
commit 226057ce8f
13 changed files with 269 additions and 60 deletions

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
import { useAppHeaderRouteNames as routeWhiteList } from '@/config' import { useAppHeaderRouteNames as routeWhiteList } from '@/config'
import { liveStore } from "@/stores/live/index.js"; import { liveStore } from "@/stores/live/index.js";
import {goodStore} from "~/stores/goods/index.js";
const { fullLive } = liveStore() const { fullLive } = liveStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const {auctionDetail} = goodStore();
function onBack() { function onBack() {
if (fullLive.value){ if (fullLive.value){
fullLive.value=false fullLive.value=false
@ -40,11 +42,9 @@ const showLeftArrow = computed(() => route.name && routeWhiteList.includes(route
placeholder clickable fixed placeholder clickable fixed
@click-left="onBack" @click-left="onBack"
> >
<template #title v-if="route.meta.i18n==='menu.goods'"> <template #title v-if="route.meta.i18n==='menu.home'">
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<div class="text-#000000 text-17px mb-5px font-600">{{ title }}</div> <div class="text-#000000 text-17px mb-5px font-600">{{ auctionDetail.title }}</div>
<div class="text-#939393 text-10px line-height-none font-100">{{subTitle}}</div>
</div> </div>
</template> </template>
</VanNavBar> </VanNavBar>

View File

@ -0,0 +1,199 @@
<script setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
const props = defineProps({
initialPosition: {
type: Object,
default: () => ({ x: 0, y: 0 })
},
stick: {
type: String,
default: null,
validator: (value) => ['left', 'right', 'top', 'bottom', null].includes(value)
},
dragMode: {
type: String,
default: 'free',
validator: (value) => ['free', 'horizontal', 'vertical'].includes(value)
},
stickyDistance: {
type: Number,
default: 20
},
margin: {
type: Number,
default: 0
},
draggable: {
type: Boolean,
default: true
},
zIndex: {
type: Number,
default: 1000
}
})
const emit = defineEmits(['update:position'])
const containerRef = ref(null)
const position = ref(props.initialPosition)
const isDragging = ref(false)
const startPos = ref({ x: 0, y: 0 })
const windowSize = ref({ width: 0, height: 0 })
const elementSize = ref({ width: 0, height: 0 })
const transformStyle = computed(() => ({
transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`,
zIndex: props.zIndex,
position: 'fixed',
top: '0',
left: '0', //
right: 'auto',
bottom: 'auto'
}))
const updateSizes = () => {
if (typeof window !== 'undefined') {
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
if (containerRef.value) {
elementSize.value = {
width: containerRef.value.offsetWidth,
height: containerRef.value.offsetHeight
}
}
}
}
const handleStick = (pos) => {
if (!props.stick) return pos
const { width, height } = elementSize.value
const { stickyDistance, margin } = props
const maxX = windowSize.value.width - width - margin
const maxY = windowSize.value.height - height - margin
const newPos = { ...pos }
switch (props.stick) {
case 'left':
if (pos.x < stickyDistance) newPos.x = margin
break
case 'right':
if (maxX - pos.x < stickyDistance) newPos.x = maxX
break
case 'top':
if (pos.y < stickyDistance) newPos.y = margin
break
case 'bottom':
if (maxY - pos.y < stickyDistance) newPos.y = maxY
break
}
return newPos
}
const constrainPosition = (pos) => {
const { width, height } = elementSize.value
const { margin } = props
const maxX = windowSize.value.width - width - margin
const maxY = windowSize.value.height - height - margin
return {
x: Math.min(Math.max(pos.x, margin), maxX),
y: Math.min(Math.max(pos.y, margin), maxY)
}
}
const handleStart = (event) => {
if (!props.draggable) return
const e = event.touches ? event.touches[0] : event
isDragging.value = true
startPos.value = {
x: e.clientX - position.value.x,
y: e.clientY - position.value.y
}
}
const handleMove = (event) => {
if (!isDragging.value) return
const e = event.touches ? event.touches[0] : event
let newPos = {
x: e.clientX - startPos.value.x,
y: e.clientY - startPos.value.y
}
if (props.dragMode === 'horizontal') {
newPos.y = position.value.y
} else if (props.dragMode === 'vertical') {
newPos.x = position.value.x
}
newPos = handleStick(newPos)
newPos = constrainPosition(newPos)
position.value = newPos
emit('update:position', newPos)
event.preventDefault()
}
const handleEnd = () => {
isDragging.value = false
}
onMounted(() => {
updateSizes()
window.addEventListener('resize', updateSizes)
document.addEventListener('mousemove', handleMove)
document.addEventListener('mouseup', handleEnd)
document.addEventListener('touchmove', handleMove, { passive: false })
document.addEventListener('touchend', handleEnd)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updateSizes)
document.removeEventListener('mousemove', handleMove)
document.removeEventListener('mouseup', handleEnd)
document.removeEventListener('touchmove', handleMove)
document.removeEventListener('touchend', handleEnd)
})
watch(() => props.initialPosition, (newPos) => {
position.value = newPos
}, {deep: true})
</script>
<template>
<div
ref="containerRef"
class="drag-window"
:class="{ 'dragging': isDragging }"
:style="transformStyle"
@mousedown="handleStart"
@touchstart="handleStart"
>
<slot></slot>
</div>
</template>
<style scoped>
.drag-window {
position: fixed;
touch-action: none;
user-select: none;
-webkit-user-select: none;
will-change: transform;
transition: transform 0.2s ease;
}
.drag-window.dragging {
transition: none;
cursor: move;
}
</style>

View File

@ -20,29 +20,29 @@ const props = defineProps({
<div class="text-[#000] text-[16px] mb-[12px]">{{detailInfo?.artworkTitle}}</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]">{{$t('detail.text1')}}</div>
<div>{{detailInfo?.artwork?.artistName??'-'}}</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">{{$t('detail.text2')}}</div>
<div>{{detailInfo?.artwork?.ruler??'-'}}</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">{{$t('detail.text3')}}*{{$t('detail.text4')}}</div>
<div>{{detailInfo?.artwork?.length}}*{{detailInfo?.artwork?.width}}cm</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">{{$t('detail.text5')}}</div>
<div>{{detailInfo?.artwork?.abstract??'-'}}</div> <div>{{detailInfo?.artwork?.abstract??'-'}}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="flex px-[16px] bg-#fff h-[36px] items-center mb-6px"> <div class="flex px-[16px] bg-#fff h-[36px] items-center mb-6px">
<div class="text-[#575757] text-[14px]">起拍价</div> <div class="text-[#575757] text-[14px]">{{$t('detail.text6')}}</div>
<div class="text-#575757 text-14px font-bold">RMB 1,000</div> <div class="text-#575757 text-14px font-bold">RMB 1,000</div>
</div> </div>
<div class="px-[16px] bg-#fff pt-12px pb-18px"> <div class="px-[16px] bg-#fff pt-12px pb-18px">
<div class="text-[#575757] text-[14px] mb-4px">竞价表</div> <div class="text-[#575757] text-[14px] mb-4px">{{$t('detail.text7')}}</div>
<div v-if="detailInfo?.priceRuleType!=='diy'"> <div v-if="detailInfo?.priceRuleType!=='diy'">
<xImage :src="detailInfo?.priceRuleImage" alt=""/> <xImage :src="detailInfo?.priceRuleImage" alt=""/>
</div> </div>

View File

@ -5,7 +5,6 @@ import countryCode from './data/index.js';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
definePageMeta({ definePageMeta({
title: '国家地区',
i18n: 'countryRegion.title', i18n: 'countryRegion.title',
}) })
const router = useRouter() const router = useRouter()

View File

@ -52,12 +52,11 @@ const openShow = async (item) => {
<div class="px-[16px] pt-[16px]"> <div class="px-[16px] pt-[16px]">
<van-pull-refresh <van-pull-refresh
v-model="localState.refreshing" v-model="localState.refreshing"
:success-text="$t('refresh_show')"
:success-duration="700" :success-duration="700"
@refresh="onRefresh" @refresh="onRefresh"
> >
<template #success> <template #success>
<van-icon name="success" /> <span>{{ $t('refresh_show') }}</span> <van-icon name="success" /> <span>{{ $t('home.refresh_show') }}</span>
</template> </template>
<van-list <van-list
v-model:loading="storeLoading" v-model:loading="storeLoading"

View File

@ -5,12 +5,13 @@ import ItemList from './components/ItemList/index.vue'
import Cescribe from './components/Cescribe/index.vue' import Cescribe from './components/Cescribe/index.vue'
import {message} from '@/components/x-message/useMessage.js' import {message} from '@/components/x-message/useMessage.js'
import {liveStore} from "~/stores/live/index.js"; import {liveStore} from "~/stores/live/index.js";
const {getAuctionDetail,auctionDetail} = goodStore();
const {fullLive}= liveStore() const {getAuctionDetail, auctionDetail} = goodStore();
const {fullLive} = liveStore()
const changeLive = () => { const changeLive = () => {
fullLive.value = true; fullLive.value = true;
}; };
if (!auctionDetail.value.uuid){ if (!auctionDetail.value.uuid) {
await getAuctionDetail() await getAuctionDetail()
} }
@ -18,7 +19,15 @@ if (!auctionDetail.value.uuid){
<template> <template>
<div class="flex-grow-1"> <div class="flex-grow-1">
<client-only> <client-only>
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/> <div class="relative" @click="changeLive">
<liveRoom :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/>
<div class="absolute h-188px w-screen pt-36px flex flex-col text-#fff top-0 left-0 items-center">
<div class="text-18px mb-5px">{{ auctionDetail.title }}</div>
<div class="text-12px mb-54px">{{ $t('home.text1') }}<van-icon name="arrow" /></div>
<div><span>-</span> <span class="text-12px mx-5px">{{auctionDetail.totalNum}}{{ auctionDetail.totalNum }}{{ $t('common.items') }}{{ $t('common.auction') }}</span> <span>-</span></div>
<div class="text-12px">{{auctionDetail.startDate}} {{$t('home.text2')}}</div>
</div>
</div>
</client-only> </client-only>
<div v-if="!fullLive" class="bg-#fff"> <div v-if="!fullLive" class="bg-#fff">
<van-tabs sticky animated> <van-tabs sticky animated>
@ -42,6 +51,7 @@ if (!auctionDetail.value.uuid){
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
:deep(.van-swipe__indicator) { :deep(.van-swipe__indicator) {
width: 8px; width: 8px;
height: 8px; height: 8px;

View File

@ -2,7 +2,6 @@
import Home from './home/index.vue' import Home from './home/index.vue'
definePageMeta({ definePageMeta({
layout: 'default', layout: 'default',
title: '主页',
i18n: 'menu.home', i18n: 'menu.home',
}) })
</script> </script>

View File

@ -77,7 +77,7 @@ watch(()=>props.show,(newValue)=>{
<x-popup :show="show" @update:show="close"> <x-popup :show="show" @update:show="close">
<template #title> <template #title>
<div class="text-#000 text-16px">{{ $t('home.tab1')}}</div> <div class="text-#000 text-16px">{{ $t('home.tab1')}}</div>
<div class="text-#939393 text-16px ml-14px">{{ $t('home.total') }}{{ pageRef.itemCount }}{{ $t('home.lots_num') }}</div> <div class="text-#939393 text-16px ml-14px">{{ $t('live_room.total') }}{{ pageRef.itemCount }}{{ $t('live_room.lots_num') }}</div>
</template> </template>
<div> <div>
<van-pull-refresh <van-pull-refresh

View File

@ -10,8 +10,7 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const { locale } = useI18n() const { locale } = useI18n()
definePageMeta({ definePageMeta({
title: '登录', i18n: 'login.title'
i18n: 'login.title',
}) })
const loadingRef=ref({ const loadingRef=ref({
loading1:false, loading1:false,

View File

@ -87,6 +87,9 @@ const onRefresh = async () => {
:success-text="$t('home.refresh_show')" :success-text="$t('home.refresh_show')"
:success-duration="700" :success-duration="700"
@refresh="onRefresh"> @refresh="onRefresh">
<template #success>
<van-icon name="success" /> <span>{{ $t('home.refresh_show') }}</span>
</template>
<van-list <van-list
:finished-text="$t('home.finished_text')" :finished-text="$t('home.finished_text')"
> >

View File

@ -1,24 +0,0 @@
<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>

View File

@ -6,10 +6,11 @@ import {message} from "~/components/x-message/useMessage.js";
import { WebSocketClient } from '@/utils/websocket' import { WebSocketClient } from '@/utils/websocket'
import {logSendlog} from "~/api/goods/index.js"; import {logSendlog} from "~/api/goods/index.js";
import CryptoJS from "crypto-js"; import CryptoJS from "crypto-js";
import {useI18n} from 'vue-i18n'
export const liveStore = createGlobalState(() => { export const liveStore = createGlobalState(() => {
const {auctionDetail} = goodStore(); const {auctionDetail} = goodStore();
const { token } = authStore() const { token } = authStore()
const t=useI18n().t
const fullLive = ref(false) const fullLive = ref(false)
const quoteStatus = ref(false) const quoteStatus = ref(false)
const show = ref(false) const show = ref(false)
@ -127,7 +128,7 @@ export const liveStore = createGlobalState(() => {
if (data.data?.tip?.tipType === 'falling'){ if (data.data?.tip?.tipType === 'falling'){
message.warning({ message.warning({
title: { title: {
text: '即将落槌', text: t('live_room.text1'),
color: '#F09F1F', color: '#F09F1F',
align: 'center', align: 'center',
}, },
@ -139,13 +140,13 @@ export const liveStore = createGlobalState(() => {
}else if (data.data?.tip?.tipType === 'othersBid'){ }else if (data.data?.tip?.tipType === 'othersBid'){
message.error({ message.error({
title: { title: {
text: '已有人出价', text: t('live_room.text2'),
color: '#CF3050', color: '#CF3050',
align: 'center', align: 'center',
}, },
icon:false, icon:false,
subTitle:{ subTitle:{
text:'更新后再出价', text:t('live_room.text3'),
color: '#939393', color: '#939393',
align: 'center', align: 'center',
}, },
@ -157,13 +158,13 @@ export const liveStore = createGlobalState(() => {
}else if (data.data?.tip?.tipType === 'successBid'){ }else if (data.data?.tip?.tipType === 'successBid'){
message.success({ message.success({
title: { title: {
text: '恭喜您,竞拍成功', text: t('live_room.text4'),
color: '#18A058', color: '#18A058',
align: 'center', align: 'center',
}, },
icon:false, icon:false,
subTitle:{ subTitle:{
text:'请缴款', text:t('live_room.text5'),
color: '#939393', color: '#939393',
align: 'center', align: 'center',
}, },
@ -175,14 +176,14 @@ export const liveStore = createGlobalState(() => {
}else if (data.data?.tip?.tipType === 'artworkOver'){ }else if (data.data?.tip?.tipType === 'artworkOver'){
message.success({ message.success({
title: { title: {
text: '本拍品已结束', text: t('live_room.text6'),
color: '#575757', color: '#575757',
align: 'center', align: 'center',
}, },
icon:false, icon:false,
subTitle:{ subTitle:{
text:'请期待下个拍品', text:t('live_room.text7'),
color: '#939393', color: '#939393',
align: 'center', align: 'center',
}, },
@ -196,13 +197,13 @@ export const liveStore = createGlobalState(() => {
}else if (data.data?.tip?.tipType === 'failBid'){ }else if (data.data?.tip?.tipType === 'failBid'){
message.error({ message.error({
title: { title: {
text: '很遗憾,竞拍失败', text: t('live_room.text8'),
color: '#CF3050', color: '#CF3050',
align: 'center', align: 'center',
}, },
icon:false, icon:false,
subTitle:{ subTitle:{
text:'竞拍结束', text: t('live_room.text9'),
color: '#939393', color: '#939393',
align: 'center', align: 'center',
}, },
@ -218,10 +219,9 @@ export const liveStore = createGlobalState(() => {
}else if (data.data?.wsType==='over'){ }else if (data.data?.wsType==='over'){
message.success({ message.success({
title: { title: {
text: '竞拍结束,谢谢参与', text: t('live_room.text10'),
color: '#575757', color: '#575757',
align: 'center', align: 'center',
}, },
icon:false, icon:false,
style: { style: {

View File

@ -5,7 +5,7 @@
"appKeyWords": "泰丰,泰丰文化,豐和,京都,拍卖会" "appKeyWords": "泰丰,泰丰文化,豐和,京都,拍卖会"
}, },
"menu": { "menu": {
"home": "主页", "home": "京都拍卖会",
"profile": "我的", "profile": "我的",
"darkMode": "🌗 暗黑模式", "darkMode": "🌗 暗黑模式",
"language": "📚 语言", "language": "📚 语言",
@ -28,6 +28,10 @@
"hasSendTo": "已发送验证码至", "hasSendTo": "已发送验证码至",
"reSend": "重新发送" "reSend": "重新发送"
}, },
"common": {
"items": "件",
"auction": "拍品"
},
"profile": { "profile": {
"name": "姓名", "name": "姓名",
"phone": "手机号" "phone": "手机号"
@ -71,6 +75,15 @@
"confirm": "确定", "confirm": "确定",
"success_mess": "提交成功" "success_mess": "提交成功"
}, },
"detail": {
"text1": "作者",
"text2": "总平尺数",
"text3": "长",
"text4": "宽",
"text5": "画作简介",
"text6": "起拍价",
"text7": "竞价表",
},
"art_detail_page": { "art_detail_page": {
"button": "去支付", "button": "去支付",
"prompt_title": "恭喜您", "prompt_title": "恭喜您",
@ -85,7 +98,9 @@
"start_price": "起拍价", "start_price": "起拍价",
"close_price": "成交价", "close_price": "成交价",
"my_lots": "我的拍品", "my_lots": "我的拍品",
"go_home": "去首页" "go_home": "去首页",
"text1": "点击进入直播间",
"text2": "北京时间"
}, },
"live_room": { "live_room": {
"error_mess": "直播内容获取失败,是否刷新页面重新获取", "error_mess": "直播内容获取失败,是否刷新页面重新获取",
@ -111,7 +126,17 @@
"total": "共", "total": "共",
"lots_num": "个拍品", "lots_num": "个拍品",
"cast": "投屏中", "cast": "投屏中",
"wait_update": "等待更新" "wait_update": "等待更新",
"text1": "即将落槌",
"text2": "已有人出价",
"text3": "更新后再出价",
"text4": "恭喜您,竞拍成功",
"text5": "请缴款",
"text6": "本拍品已结束",
"text7": "请期待下个拍品",
"text8": "很遗憾,竞拍失败",
"text9": "竞拍结束",
"text10": "竞拍结束,谢谢参与",
}, },
"personal": { "personal": {
"title": "请填写个人相关信息", "title": "请填写个人相关信息",