卖家端多语言

This commit is contained in:
李志鹏
2026-05-04 11:18:56 +08:00
parent 596bc75b83
commit b3d9bce440
10 changed files with 262 additions and 93 deletions

View File

@@ -167,8 +167,7 @@ li {
padding: 0.6rem 0.5rem;
}
.ant-modal-mask {
background-color: #666666;
opacity: 0.5;
background-color: rgba(102, 102, 102, 0.5);
}
.select_block {
height: 4rem;
@@ -1251,8 +1250,8 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
border-color: #000 !important;
}
.ant-spin .ant-spin-dot {
width: 1.5em;
height: 1.5em;
width: 4.5rem;
height: 4.5rem;
}
.ant-spin-dot-item {
background-color: #000000 !important;

View File

@@ -172,8 +172,9 @@ input:focus{
}
}
.ant-modal-mask{
background-color: #666666;
opacity: .5;
background-color: rgba(102,102,102,0.5);
// background-color: #666666;
// opacity: .5;
}
.select_block{
height: 4rem;

View File

@@ -1762,5 +1762,74 @@ export default {
draftDesc: '您的商品已保存为草稿。\n您可以继续编辑或稍后在“我的商品”中发布。',
listingLive:'商品已上架',
publishDesc:'您的商品现已上架。\n买家可以浏览并购买您的设计。'
},
Seller: {
// Brand Profile
brandProfile: "店铺详情",
myListings: "我的商品",
myOrders: "我的订单",
settings: "设置",
brandProfileBgNullTip: "您的店铺横幅尚未设置。",
changeBrandBanner: "更改店铺横幅",
edit: "编辑",
cancel: "取消",
confirm: "确认",
saveChange: "保存更改",
brandProfileEditTip: "这些更改将会在您的“Stylish Parade”品牌页面上得到体现。",
storeName: "店铺名称",
storeNameDesc: "请输入您的店铺名称",
ownerName: "姓名",
ownerNameDesc: "请输入姓名",
email: "邮箱",
emailDesc: "请输入邮箱",
mobile: "手机号",
mobileDesc: "请输入手机号",
link: "链接{index}",
links: "投资组合/社交媒体链接",
storeDescription: "店铺描述",
storeDescriptionDesc: "请输入店铺描述",
storeDescriptionErr: "请输入店铺描述",
cropBrandBanner: "裁剪店铺横幅",
cropAvatar: "裁剪头像",
cropFrom: "裁剪来源:",
sketch: "线稿图",
mainProductImage: "产品主图",
cropPreview: "裁剪预览",
imageClipCoverTip: "将封面图片裁剪为正方形,保持比例。",
imageClipMainProductImageTip: "将产品主图裁剪为正方形,保持比例。",
imageClipSketchTip: "将线稿图裁剪为正方形,保持比例。",
imageClipApparelTip: "将服装线稿图裁剪为正方形,保持比例。",
// 我的订单
totalRevenue: "总销售额",
totalPurchases: "总购买量",
totalViews: "总浏览量",
allInvoice: "所有订单",
myOrdersTip: "所有已完成交易的摘要",
myOrdersSearchPlaceholder: "按项目名称或订单ID搜索",
orderId: "订单ID",
item: "项目",
price: "价格",
buyerUsername: "买家用户名",
date: "日期",
// 设置
notifications: "通知",
notificationsTitle: "新订单通知",
notificationsTip: "当有新订单时收到通知。",
unbound: "未绑定",
manage: "管理",
bindNow: "立即绑定",
paymentCurrency: "支付货币",
HKD: "HKD - 香港元",
fixed: "固定",
dataPrivacy: "数据与隐私",
dataPrivacyTitle: "版权许可",
dataPrivacyTip1: "每笔购买下载都会自动附带一份许可证证书。请查看适用于您商品列表的默认许可条款。",
dataPrivacyTip2: "此许可证由 Code-Create 颁发,购买时即具有法律约束力。它证明购买者有权按照以下条款使用所购买的设计资产。",
dataPrivacyTip3: "对于定制的许可安排,请<span onclick='{click}()'>联系我们</span>。",
downloadToView: "下载以查看",
stopSelling: "停止销售",
stopSellingTitle: "停用卖家账户",
stopSellingTip: "永久停用您的卖家账户。所有商品列表和发票记录将被删除。您以后可以重新注册为卖家,但您的先前销售数据无法恢复。",
deactivate: "停用卖家账户",
}
}

View File

@@ -1813,5 +1813,77 @@ export default {
draftDesc: 'Your listing has been saved as a draft. \nYou can continue editing or publish it later from My Listings.',
listingLive:'Listing Live',
publishDesc:'Your listing is now live on the marketplace.\nBuyers can discover and purchase your design.'
},
Seller: {
brandProfile: "Brand Profile",
myListings: "My Listings",
myOrders: "My Orders",
settings: "Settings",
// Brand Profile
brandProfileBgNullTip: "Your brand banner has not been set up yet.",
changeBrandBanner: "Change Brand Banner",
edit: "Edit",
cancel: "Cancel",
confirm: "Confirm",
saveChange: "Save Change",
brandProfileEditTip: "Changes will be reflected on your Stylish Parade brand page.",
storeName: "Store Name",
storeNameDesc: "Enter your store name",
ownerName: "Owners Full Name",
ownerNameDesc: "Enter store owner's full name",
email: "Email",
emailDesc: "Enter email",
mobile: "Phone Number",
mobileDesc: "Enter phone number",
link: "Link {index}",
links: "Portfoilo/Social Media Links",
storeDescription: "Store Description",
storeDescriptionDesc: "Briefly describe your design style and store features...",
storeDescriptionErr: "Please enter store description",
cropBrandBanner: "Crop Brand Banner",
cropAvatar: "Crop Avatar",
cropFrom: "Crop from:",
sketch: "Sketch",
mainProductImage: "Main Product Image",
cropPreview: "Crop Preview",
imageClipCoverTip: "Align crown to top, mid-thigh to bottom for best results.",
imageClipMainProductImageTip: "Align crown to top, foot base to bottom for best results.",
imageClipSketchTip: "Align crown to top, foot base to bottom for best results.",
imageClipApparelTip: "Trim whitespace and center your apparel sketch.",
// 我的订单
totalRevenue: "Total Revenue",
totalPurchases: "Total Purchases",
totalViews: "Total Views",
allInvoice: "All Invoice",
myOrdersTip: "A summary of all completed transactions.",
myOrdersSearchPlaceholder: "Search by item name or order ID",
orderId: "Order ID",
item: "Item",
price: "Price",
buyerUsername: "Buyer Username",
date: "Date",
// 设置
notifications: "Notifications",
notificationsTitle: "New order notification",
notificationsTip: "Receive an inbox message when a new order is placed.",
payout: "Payout",
payoutTitle: "Payment Providers",
payoutTip: "Select how you want to receive payments.",
unbound: "Unbound",
manage: "Manage",
bindNow: "Bind Now",
paymentCurrency: "Payment Currency",
HKD: "HKD - Hong Kong Dollar",
fixed: "Fixed",
dataPrivacy: "Data & Privacy",
dataPrivacyTitle: "Copyright licence",
dataPrivacyTip1: "A licence certificate is automatically included with every purchase download. View the default licensing terms applied to your listings.",
dataPrivacyTip2: "BThis licence is issued by Code-Create and is legally binding upon purchase. It certifies the buyer's right to use the purchased design asset in accordance with the terms below.",
dataPrivacyTip3: "For custom licensing arrangements, <span onclick='{click}()'>contact us</span>.",
downloadToView: "Download to View",
stopSelling: "Stop Selling",
stopSellingTitle: "Deactivate seller account",
stopSellingTip: "Permanently deactivate your seller account. All listings and invoice records will be deleted. You may re-register as a seller in the future, but your previous sales data cannot be recovered.",
deactivate: "Deactivate",
}
}

View File

@@ -2,10 +2,10 @@
<div class="brand-info">
<a-form :model="formData" :rules="isEdit ? formRules : {}" layout="vertical" ref="formRef">
<div class="form-group">
<a-form-item label="Store Name" name="shopName">
<a-form-item :label="$t('Seller.storeName')" name="shopName">
<a-input
v-model:value="formData.shopName"
placeholder="Enter the store name"
:placeholder="$t('Seller.storeNameDesc')"
:maxlength="80"
:readonly="!isEdit"
/>
@@ -13,34 +13,34 @@
>{{ formData.shopName.length }}/80</span
>
</a-form-item>
<a-form-item label="Owners Full Name" name="ownerName">
<a-form-item :label="$t('Seller.ownerName')" name="ownerName">
<a-input
v-model:value="formData.ownerName"
placeholder="Enter store owner's full name"
:placeholder="$t('Seller.ownerNameDesc')"
:readonly="!isEdit"
/>
</a-form-item>
</div>
<div class="form-group">
<a-form-item label="Email" name="email">
<a-form-item :label="$t('Seller.email')" name="email">
<a-input
type="email"
v-model:value="formData.email"
placeholder="Enter email"
:placeholder="$t('Seller.emailDesc')"
:readonly="!isEdit"
/>
</a-form-item>
<a-form-item label="Phone Number" name="mobile">
<a-form-item :label="$t('Seller.mobile')" name="mobile">
<a-input
type="tel"
v-model:value="formData.mobile"
placeholder="Enter phone number"
:placeholder="$t('Seller.mobileDesc')"
:readonly="!isEdit"
/>
</a-form-item>
</div>
<div class="form-group">
<a-form-item label="Portfoilo/Social Media Links">
<a-form-item :label="$t('Seller.links')">
<a-input
placeholder="https://"
v-for="(v, i) in formData.socialLinks"
@@ -48,7 +48,7 @@
v-model:value="formData.socialLinks[i]"
:readonly="!isEdit"
>
<template #prefix>Link {{ i + 1 }}</template>
<template #prefix>{{ $t("Seller.link", { index: i + 1 }) }}</template>
</a-input>
<a-input
placeholder="https://"
@@ -63,10 +63,10 @@
</template>
</a-input>
</a-form-item>
<a-form-item label="Store Description" name="description">
<a-form-item :label="$t('Seller.storeDescription')" name="description">
<a-textarea
v-model:value="formData.description"
placeholder="Briefly describe your design style and store features..."
:placeholder="$t('Seller.storeDescriptionDesc')"
:maxlength="500"
:readonly="!isEdit"
/>
@@ -83,6 +83,9 @@
import { ref, reactive, watch } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const store = useStore()
const designerInfo = computed(() => store.state.seller.designerInfo)
const route = useRoute()
@@ -94,11 +97,11 @@
}
})
const formRules = {
shopName: [{ required: true, message: "Enter the store name" }],
ownerName: [{ required: true, message: "Enter store owner's full name" }],
email: [{ required: true, message: "Enter email" }],
mobile: [{ required: true, message: "Enter phone number" }],
description: [{ required: true, message: "Enter store description" }]
shopName: [{ required: true, message: t("Seller.storeNameDesc") }],
ownerName: [{ required: true, message: t("Seller.ownerNameDesc") }],
email: [{ required: true, message: t("Seller.emailDesc") }],
mobile: [{ required: true, message: t("Seller.mobileDesc") }],
description: [{ required: true, message: t("Seller.storeDescriptionErr") }]
}
const formRef = ref(null)

View File

@@ -16,28 +16,28 @@
<div class="title">{{ data.title }}</div>
<div class="right flex">
<div v-if="coverOrigin.length > 1" class="origin-container flex align-center">
<span>Crop from: </span>
<span>{{ $t("Seller.cropFrom") }}</span>
<div class="origin-select flex align-center">
<div
class="origin-item sketch"
:class="{ selected: currentOrigin === 'sketch' }"
@click="handleChangeOrigin('sketch')"
>
Sketch
{{ $t("Seller.sketch") }}
</div>
<div
class="origin-item product"
:class="{ selected: currentOrigin === 'mainProducImage' }"
@click="handleChangeOrigin('mainProductImage')"
>
Main product image
{{ $t("Seller.mainProductImage") }}
</div>
</div>
</div>
<div class="submit" v-if="!data.isPreview" @click="onSubmit">
<svg-icon name="seller-dui" size="24" />
</div>
<button @click="onCancel">Cancel</button>
<button @click="onCancel">{{ $t("Seller.cancel") }}</button>
</div>
</div>
<div class="content" :class="{ 'is-product': data.isProduct }">
@@ -64,7 +64,7 @@
>
<div class="title">
<span class="icon"><svg-icon name="seller-preview" size="24" /></span>
<span class="label">Crop Preview</span>
<span class="label">{{ $t("Seller.cropPreview") }}</span>
</div>
<div class="preview-image">
<img :src="data.preview_url" />
@@ -81,6 +81,10 @@
<script setup>
import { ref, reactive, computed } from "vue"
import ImageClip from "./image-clip.vue"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const props = defineProps({
type: {
@@ -95,16 +99,16 @@ const props = defineProps({
const tips = computed(() => {
if (props.type === "cover") {
return "Align crown to top, mid-thigh to bottom for best results."
return t("Seller.imageClipCoverTip")
}
if (props.type === "mainProductImage") {
return "Align crown to top, foot base to bottom for best results."
return t("Seller.imageClipMainProductImageTip")
}
if (props.type === "sketch") {
return "Align crown to top, foot base to bottom for best results."
return t("Seller.imageClipSketchTip")
}
if (props.type === "apparel") {
return "Trim whitespace and center your apparel sketch."
return t("Seller.imageClipApparelTip")
}
})

View File

@@ -5,9 +5,9 @@
<img v-if="banner" :src="banner" />
<div v-else class="null">
<span class="icon"><svg-icon name="seller-picture" size="60" /></span>
<span class="tip">Your brand banner has not been set up yet.</span>
<span class="tip">{{ $t("Seller.brandProfileBgNullTip") }}</span>
</div>
<button @click="onChangeBanner">Change Brand Banner</button>
<button @click="onChangeBanner">{{ $t("Seller.changeBrandBanner") }}</button>
</div>
<!-- 头像 -->
<div class="avatar">
@@ -25,14 +25,14 @@
<div class="and-profile-footer">
<template v-if="isEdit">
<div class="btns">
<button class="cancel" @click="onCancel()">Cancel</button>
<button class="submit" @click="onSubmit()">Save Change</button>
<button class="cancel" @click="onCancel()">{{ $t("Seller.cancel") }}</button>
<button class="submit" @click="onSubmit()">{{ $t("Seller.saveChange") }}</button>
</div>
<p class="tip">Changes will be reflected on your Stylish Parade brand page.</p>
<p class="tip">{{ $t("Seller.brandProfileEditTip") }}</p>
</template>
<template v-else>
<div class="btns">
<button class="edit" @click="onEdit">Edit</button>
<button class="edit" @click="onEdit">{{ $t("Seller.edit") }}</button>
</div>
<p class="tip">&nbsp;</p>
</template>
@@ -46,6 +46,8 @@
import BrandInfo from "./brand-info.vue"
import ImageClipDialog from "./image-clip-dialog.vue"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const store = useStore()
store.dispatch("seller/get_designerInfo")
const designerInfo = computed(() => store.state.seller.designerInfo)
@@ -90,7 +92,7 @@
onSubmit({ brandBanner: res })
store.commit("set_loading", false)
},
{ ratio: [40, 7], isPreview: false, title: "Crop Brand Banner" }
{ ratio: [40, 7], isPreview: false, title: t("Seller.cropBrandBanner") }
)
})
}
@@ -104,7 +106,7 @@
onSubmit({ avatar: res })
store.commit("set_loading", false)
},
{ ratio: [1, 1], isPreview: true, title: "Crop Avatar" }
{ ratio: [1, 1], isPreview: true, title: t("Seller.cropAvatar") }
)
})
}

View File

@@ -11,8 +11,8 @@
</div>
<div class="filter-box">
<div class="left">
<div class="title">All Invoice</div>
<div class="tip">A summary of all completed transactions.</div>
<div class="title">{{ t("Seller.allInvoice") }}</div>
<div class="tip">{{ t("Seller.myOrdersTip") }}</div>
</div>
<div class="right">
<div class="input">
@@ -22,7 +22,7 @@
<input
type="text"
v-model="nameOrId"
placeholder="Search by item name or order ID"
:placeholder="t('Seller.myOrdersSearchPlaceholder')"
@keydown.enter.prevent="getList(true)"
/>
</div>
@@ -30,11 +30,11 @@
</div>
<div class="table">
<div class="header">
<div class="order-id">Order ID</div>
<div class="item">Item</div>
<div class="price">Price</div>
<div class="buyer-username">Buyer Username</div>
<div class="date">Date</div>
<div class="order-id">{{ t("Seller.orderId") }}</div>
<div class="item">{{ t("Seller.item") }}</div>
<div class="price">{{ t("Seller.price") }}</div>
<div class="buyer-username">{{ t("Seller.buyerUsername") }}</div>
<div class="date">{{ t("Seller.date") }}</div>
</div>
<div class="body">
<div class="item" v-for="v in list" :key="v.orderId">
@@ -79,6 +79,8 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from "vue"
import { Https } from "@/tool/https"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const totals_obj = ref({
totalRevenue: "--",
totalPurchases: "--",
@@ -87,17 +89,17 @@
const totals = computed(() => [
{
icon: "seller-qiandaizi",
title: "Total Revenue",
title: t("Seller.totalRevenue"),
value: `HK$ ${totals_obj.value.totalRevenue}`
},
{
icon: "seller-gouwudai",
title: "Total Purchases",
title: t("Seller.totalPurchases"),
value: totals_obj.value.totalPurchases
},
{
icon: "seller-eye",
title: "Total Views",
title: t("Seller.totalViews"),
value: totals_obj.value.totalViews
}
])

View File

@@ -2,11 +2,11 @@
<div class="settings-index">
<div>
<div class="notification">
<div class="header">Notifications</div>
<div class="header">{{ $t("Seller.notifications") }}</div>
<div class="content">
<div class="left">
<div class="title">New order notification</div>
<div class="tip">Receive an inbox message when a new order is placed.</div>
<div class="title">{{ $t("Seller.notificationsTitle") }}</div>
<div class="tip">{{ $t("Seller.notificationsTip") }}</div>
</div>
<div class="right">
<a-switch v-model:checked="checked" />
@@ -14,29 +14,29 @@
</div>
</div>
<div class="payout">
<div class="header">Payout</div>
<div class="header">{{ $t("Seller.payout") }}</div>
<div class="content">
<div class="header">
<div class="title">Payment Providers</div>
<div class="tip">Select how you want to receive payments.</div>
<div class="title">{{ $t("Seller.payoutTitle") }}</div>
<div class="tip">{{ $t("Seller.payoutTip") }}</div>
</div>
<div class="pay-item" v-for="v in payList" :key="v.type">
<div class="left">
<img :src="v.icon" />
<div class="value">{{ v.value || "Unbound" }}</div>
<div class="value">{{ v.value || $t("Seller.unbound") }}</div>
</div>
<div class="right">
<button v-if="v.value" class="manage">Manage</button>
<button v-else class="bind-now">Bind Now</button>
<button v-if="v.value" class="manage">{{ $t("Seller.manage") }}</button>
<button v-else class="bind-now">{{ $t("Seller.bindNow") }}</button>
</div>
</div>
<div class="footer">
<div class="left">
<div class="title">Payment Currency</div>
<div class="tip">HKD - Hong Kong Dollar</div>
<div class="title">{{ $t("Seller.paymentCurrency") }}</div>
<div class="tip">{{ $t("Seller.HKD") }}</div>
</div>
<div class="right">
<button>Fixed</button>
<button>{{ $t("Seller.fixed") }}</button>
</div>
</div>
</div>
@@ -44,40 +44,32 @@
</div>
<div>
<div class="data-privacy">
<div class="header">Data & Privacy</div>
<div class="header">{{ $t("Seller.dataPrivacy") }}</div>
<div class="content">
<div class="title">Copyright licence</div>
<div class="tip">
A licence certificate is automatically included with every purchase
download. View the default licensing terms applied to your listings.
</div>
<div class="tip">
This licence is issued by Code-Create and is legally binding upon purchase.
It certifies the buyer's right to use the purchased design asset in
accordance with the terms below.
</div>
<div class="tip">
For custom licensing arrangements, <span>contact us</span>.
</div>
<div class="title">{{ $t("Seller.dataPrivacyTitle") }}</div>
<div class="tip">{{ $t("Seller.dataPrivacyTip1") }}</div>
<div class="tip">{{ $t("Seller.dataPrivacyTip2") }}</div>
<div
class="tip"
v-html="
$t('Seller.dataPrivacyTip3', { click: 'onSellerSettingsContactUs' })
"
></div>
<div class="btns">
<button>
<button @click="onDownloadDataPrivacy">
<span class="icon"><svg-icon name="seller-download" size="14" /></span>
<span class="label">Download to View</span>
<span class="label">{{ $t("Seller.downloadToView") }}</span>
</button>
</div>
</div>
</div>
<div class="stop">
<div class="header">Stop Selling</div>
<div class="header">{{ $t("Seller.stopSelling") }}</div>
<div class="content">
<div class="title">Deactivate seller account</div>
<div class="tip">
Permanently deactivate your seller account. All listings and invoice records
will be deleted. You may re-register as a seller in the future, but your
previous sales data cannot be recovered.
</div>
<div class="title">{{ $t("Seller.stopSellingTitle") }}</div>
<div class="tip">{{ $t("Seller.stopSellingTip") }}</div>
<div class="btns">
<button>Deactivate</button>
<button @click="onStopSelling">{{ $t("Seller.deactivate") }}</button>
</div>
</div>
</div>
@@ -87,10 +79,14 @@
<script setup>
import { ref } from "vue"
import { Modal } from "ant-design-vue"
import paypal from "@/assets/images/seller/setting/paypal.png"
import stripe from "@/assets/images/seller/setting/stripe.png"
import alipayHk from "@/assets/images/seller/setting/alipay-hk.png"
import alipayChinese from "@/assets/images/seller/setting/alipay-chinese.png"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const checked = ref(true)
const payList = ref([
{
@@ -114,6 +110,24 @@
value: "123123"
}
])
window.onSellerSettingsContactUs = () => {
console.log("contact us")
}
const onDownloadDataPrivacy = () => {
console.log("download data privacy")
}
const onStopSelling = () => {
Modal.confirm({
title: t("Seller.stopSellingTitle"),
content: t("Seller.stopSellingTip"),
okText: t("Seller.confirm"),
cancelText: t("Seller.cancel"),
centered: true,
onOk() {
console.log("stop selling")
}
})
}
</script>
<style scoped lang="less">
.settings-index {
@@ -281,7 +295,7 @@
font-size: 1.4rem;
color: #999;
margin-bottom: 3rem;
> span {
&:deep(*) {
cursor: pointer;
text-decoration: underline;
color: #0080ed;

View File

@@ -28,6 +28,9 @@
import toolTipBox from "./toolTipBox.vue"
import myEvent from "@/tool/myEvents.js"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
const { t } = useI18n()
const props = defineProps({
cachedRoutes: {
type: Array,
@@ -35,7 +38,7 @@
}
})
const store = useStore()
const isSeller = computed(() => store.state.seller.isSeller)
const isSeller = computed(() => store.state.seller.isSeller)
// store.dispatch("seller/get_designerInfo")
const route = useRoute()
const router = useRouter()
@@ -43,22 +46,22 @@
const list = ref([
{
icon: "seller-brandProfile",
layer: "Brand Profile",
layer: t("Seller.brandProfile"),
path: "/home/seller/brandProfile"
},
{
icon: "seller-myListings",
layer: "My Listings",
layer: t("Seller.myListings"),
path: "/home/seller/myListings"
},
{
icon: "seller-myOrders",
layer: "My Orders",
layer: t("Seller.myOrders"),
path: "/home/seller/myOrders"
},
{
icon: "seller-settings",
layer: "Settings",
layer: t("Seller.settings"),
path: "/home/seller/settings"
}
])