Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front
This commit is contained in:
@@ -18,7 +18,7 @@ export default {
|
|||||||
pleaseInputName: 'Please input the name',
|
pleaseInputName: 'Please input the name',
|
||||||
nameLengthError: 'Name length must be between {min} and {max} characters',
|
nameLengthError: 'Name length must be between {min} and {max} characters',
|
||||||
passwordSpecial: 'Must contain special characters',
|
passwordSpecial: 'Must contain special characters',
|
||||||
passwordCase: 'Mix of uppercase, lowercase and numbers',
|
passwordCase: 'A combination of numbers and letters',
|
||||||
pleaseInputEmail: 'Please input the email',
|
pleaseInputEmail: 'Please input the email',
|
||||||
emailFormatError: 'Please input the email again',
|
emailFormatError: 'Please input the email again',
|
||||||
pleaseInputPassword: 'Please input the password',
|
pleaseInputPassword: 'Please input the password',
|
||||||
@@ -355,7 +355,7 @@ export default {
|
|||||||
Offices: "Offices",
|
Offices: "Offices",
|
||||||
JoinWithUs: "Join with Us",
|
JoinWithUs: "Join with Us",
|
||||||
},
|
},
|
||||||
addShoppingCart:{
|
addShoppingCart: {
|
||||||
title: 'Added to your Shopping Cart',
|
title: 'Added to your Shopping Cart',
|
||||||
statement: 'Digital Assets Only. No physical product included.',
|
statement: 'Digital Assets Only. No physical product included.',
|
||||||
button: 'See Shopping Cart'
|
button: 'See Shopping Cart'
|
||||||
@@ -375,5 +375,23 @@ export default {
|
|||||||
germany: 'Germany',
|
germany: 'Germany',
|
||||||
australia: 'Australia',
|
australia: 'Australia',
|
||||||
canada: 'Canada'
|
canada: 'Canada'
|
||||||
|
},
|
||||||
|
Pay: {
|
||||||
|
OrderSummary: 'Order Summary',
|
||||||
|
PaymentDetails: 'Payment Details',
|
||||||
|
CreditDebitCard: 'Credit / Debit Card',
|
||||||
|
AgreementText: 'I agree to the <span onclick="{onTermsClick}">Terms & Conditions</span> and <span onclick="{onPrivacyClick}">Privacy Policy</span>. All digital item sales are final and non-refundable.',
|
||||||
|
PayWithStripe: 'Pay with Stripe',
|
||||||
|
PayWith: 'Pay with',
|
||||||
|
Cancel: 'Cancel',
|
||||||
|
IHaveCompletedPayment: 'I Have Completed payment',
|
||||||
|
Back: 'Back',
|
||||||
|
PayTip1: "You'll be redirected to a Stripe popup to log in and confirm. No card details are shared with Stylish Parade — Stripe handles all payment security.",
|
||||||
|
PayTip2: "Please keep the window open until the payment is completed. If you are to open the payment window, please check your browser settings to see if pop-ups are being blocked. Points may be delayed after successful payment. Please wait 1-3 minutes and click the credits refresh button.",
|
||||||
|
PurchaseSuccessful: "Purchase Successful",
|
||||||
|
PurchaseSuccessfulTip: "Your digital items are now available and have been saved in Personal Center → My Wardrobe.",
|
||||||
|
DownloadAllAssets: "download all Assets",
|
||||||
|
ExportInvoice: "Export Invoice",
|
||||||
|
ContinueShopping: "Continue Shopping"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default {
|
|||||||
pleaseInputName: '请输入姓名',
|
pleaseInputName: '请输入姓名',
|
||||||
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
||||||
passwordSpecial: '必须包含特殊符号',
|
passwordSpecial: '必须包含特殊符号',
|
||||||
passwordCase: '大小写字母与数字混合组合',
|
passwordCase: '字母和数字组合',
|
||||||
pleaseInputEmail: '请输入邮箱',
|
pleaseInputEmail: '请输入邮箱',
|
||||||
emailFormatError: '请输入正确的邮箱',
|
emailFormatError: '请输入正确的邮箱',
|
||||||
pleaseInputPassword: '请输入密码',
|
pleaseInputPassword: '请输入密码',
|
||||||
@@ -376,5 +376,23 @@ export default {
|
|||||||
AboutUs: '关于我们',
|
AboutUs: '关于我们',
|
||||||
Offices: '办公室',
|
Offices: '办公室',
|
||||||
JoinWithUs: '加入我们'
|
JoinWithUs: '加入我们'
|
||||||
|
},
|
||||||
|
Pay: {
|
||||||
|
OrderSummary: '订单信息',
|
||||||
|
PaymentDetails: '支付详情',
|
||||||
|
CreditDebitCard: '信用卡/借记卡',
|
||||||
|
AgreementText: '我同意 <span onclick="{onTermsClick}">使用条款与条件</span> 和 <span onclick="{onPrivacyClick}">隐私政策</span>。所有数字资产销售均为最终销售,不可退款。',
|
||||||
|
PayWithStripe: '使用 Stripe 支付',
|
||||||
|
PayWith: '支付',
|
||||||
|
Cancel: '取消',
|
||||||
|
IHaveCompletedPayment: '我已完成支付',
|
||||||
|
Back: '返回',
|
||||||
|
PayTip1: "您将被重定向到 Stripe 弹窗以登录并确认支付。您的信用卡信息不会被 Stylish Parade 收集。Stripe 处理所有支付安全。",
|
||||||
|
PayTip2: "请保持窗口打开,直到支付完成。如果您打开支付窗口,请检查浏览器设置以查看是否已阻止弹窗。支付完成后,积分可能会有延迟。请等待 1-3 分钟并点击积分刷新按钮。",
|
||||||
|
PurchaseSuccessful: "购买成功",
|
||||||
|
PurchaseSuccessfulTip: "您的数字资产已保存在个人中心的我的衣橱中。",
|
||||||
|
DownloadAllAssets: "下载所有资产",
|
||||||
|
ExportInvoice: "导出发票",
|
||||||
|
ContinueShopping: "继续购物"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useGlobalStore } from '@/stores/global'
|
|||||||
import { getUserLanguage } from '@/api/user'
|
import { getUserLanguage } from '@/api/user'
|
||||||
import { fetchAllUnreadMessage } from '@/api/notification'
|
import { fetchAllUnreadMessage } from '@/api/notification'
|
||||||
import i18n from '@/lang/index'
|
import i18n from '@/lang/index'
|
||||||
|
import myEvent from '@/utils/myEvent'
|
||||||
|
|
||||||
// 语言映射:后端格式 -> i18n 格式
|
// 语言映射:后端格式 -> i18n 格式
|
||||||
const backendToI18nLanguage: Record<string, string> = {
|
const backendToI18nLanguage: Record<string, string> = {
|
||||||
@@ -19,6 +20,8 @@ let languageSynced = false
|
|||||||
* 1. 设置路由的meta属性为{ cache: true },表示需要缓存
|
* 1. 设置路由的meta属性为{ cache: true },表示需要缓存
|
||||||
* 2. App.vue中使用RouteCache组件,通过路由的name来进行匹配
|
* 2. App.vue中使用RouteCache组件,通过路由的name来进行匹配
|
||||||
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
|
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
|
||||||
|
*
|
||||||
|
* 需要登录路由: meta={ login:true }
|
||||||
*/
|
*/
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routes: [
|
routes: [
|
||||||
@@ -62,7 +65,8 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/shoppingCart', // 购物车
|
path: '/shoppingCart', // 购物车
|
||||||
name: 'shoppingCart',
|
name: 'shoppingCart',
|
||||||
component: () => import('@/views/shoppingCart/index.vue')
|
component: () => import('@/views/shoppingCart/index.vue'),
|
||||||
|
meta: { login: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
@@ -94,6 +98,9 @@ const router = createRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
|
if(to.meta?.login && !useUserInfoStore().state.token) {
|
||||||
|
myEvent.emit('openLoginDialog')
|
||||||
|
}
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
> section {
|
> section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
min-height: 50vw;
|
||||||
}
|
}
|
||||||
> section.bgw {
|
> section.bgw {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -40,5 +41,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> .section-footer {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
> div {
|
> div {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 0.1rem solid #979797;
|
border: 0.1rem solid #979797;
|
||||||
|
background-color: #fff;
|
||||||
> img {
|
> img {
|
||||||
width: 27.4rem;
|
width: 27.4rem;
|
||||||
height: 34.6rem;
|
height: 34.6rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="digital-items2 bgw">
|
<section class="digital-items2 bgw">
|
||||||
<img src="@/assets/images/home/digital-items-1.jpg" class="bg" />
|
<img src="@/assets/images/home/digital-items-2.jpg" class="bg" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="tip" v-html="$t('Home.DigitalItemsTip2')"></div>
|
<div class="tip" v-html="$t('Home.DigitalItemsTip2')"></div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
> div {
|
> div {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 0.1rem solid #979797;
|
border: 0.1rem solid #979797;
|
||||||
|
background-color: #fff;
|
||||||
> img {
|
> img {
|
||||||
width: 27.4rem;
|
width: 27.4rem;
|
||||||
height: 34.6rem;
|
height: 34.6rem;
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
<span>{{ $t('Login.passwordLengthError', { min: 6, max: 20 }) }}</span>
|
<span>{{ $t('Login.passwordLengthError', { min: 6, max: 20 }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<!-- <div>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<CloseBold v-if="validateSpecial(value)" />
|
<CloseBold v-if="validateSpecial(value)" />
|
||||||
<Select v-else />
|
<Select v-else />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>{{ $t('Login.passwordSpecial') }}</span>
|
<span>{{ $t('Login.passwordSpecial') }}</span>
|
||||||
</div>
|
</div> -->
|
||||||
<div>
|
<div>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<CloseBold v-if="validateCase(value)" />
|
<CloseBold v-if="validateCase(value)" />
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: 1.5rem;
|
// border-radius: 1.5rem;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ export const validateEmail = (rule, value, callback) => {
|
|||||||
export const validateLength = (v, min = 6, max = 20) => (v.length < 6 || v.length > 20);
|
export const validateLength = (v, min = 6, max = 20) => (v.length < 6 || v.length > 20);
|
||||||
//检查特殊字符
|
//检查特殊字符
|
||||||
export const validateSpecial = (v) => (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(v));
|
export const validateSpecial = (v) => (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(v));
|
||||||
//检查大小写字母和数字
|
//检查字母和数字
|
||||||
export const validateCase = (v) => (!/[a-z]/.test(v) || !/[A-Z]/.test(v) || !/\d/.test(v));
|
export const validateCase = (v) => (!/[A-z]/.test(v) || !/\d/.test(v));
|
||||||
// 检查密码
|
// 检查密码
|
||||||
export const validatePass = (rule, value, callback) => {
|
export const validatePass = (rule, value, callback) => {
|
||||||
var str = ''
|
var str = ''
|
||||||
if (validateLength(value)) {
|
if (validateLength(value)) {
|
||||||
str = t('Login.passwordLengthError', { min: 6, max: 20 })
|
str = t('Login.passwordLengthError', { min: 6, max: 20 })
|
||||||
} else if (validateSpecial(value)) {
|
// } else if (validateSpecial(value)) {
|
||||||
str = t('Login.passwordSpecial')
|
// str = t('Login.passwordSpecial')
|
||||||
} else if (validateCase(value)) {
|
} else if (validateCase(value)) {
|
||||||
str = t('Login.passwordCase')
|
str = t('Login.passwordCase')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-header" id="main-header">
|
<div class="main-header" id="main-header">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img class="logo" src="@/assets/images/logo.png" @click="onNavItemClick('/')" />
|
<img
|
||||||
|
class="logo"
|
||||||
|
src="@/assets/images/logo.png"
|
||||||
|
@click="onNavItemClick({ path: '/' })"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div
|
<div
|
||||||
@@ -14,7 +18,7 @@
|
|||||||
? activePath === v.path
|
? activePath === v.path
|
||||||
: new RegExp(`^${v.path}`).test(activePath)
|
: new RegExp(`^${v.path}`).test(activePath)
|
||||||
}"
|
}"
|
||||||
@click="onNavItemClick(v.path)"
|
@click="onNavItemClick(v)"
|
||||||
>
|
>
|
||||||
<span>{{ $t(v.name) }}</span>
|
<span>{{ $t(v.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,7 +29,7 @@
|
|||||||
v-for="v in navList2"
|
v-for="v in navList2"
|
||||||
:key="v.path"
|
:key="v.path"
|
||||||
:class="{ active: new RegExp(`^${v.path}`).test(activePath) }"
|
:class="{ active: new RegExp(`^${v.path}`).test(activePath) }"
|
||||||
@click="onNavItemClick(v.path)"
|
@click="onNavItemClick(v)"
|
||||||
>
|
>
|
||||||
<svg-icon :name="activePath === v.path ? v.active_icon : v.icon" size="22" />
|
<svg-icon :name="activePath === v.path ? v.active_icon : v.icon" size="22" />
|
||||||
</div>
|
</div>
|
||||||
@@ -129,10 +133,15 @@
|
|||||||
{
|
{
|
||||||
icon: 'cart_0',
|
icon: 'cart_0',
|
||||||
active_icon: 'cart_1',
|
active_icon: 'cart_1',
|
||||||
path: '/shoppingCart'
|
path: '/shoppingCart',
|
||||||
|
login: true
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const onNavItemClick = (path: string) => {
|
const onNavItemClick = (v: any) => {
|
||||||
|
const path = v.path
|
||||||
|
if (v.login && !userInfoStore.state.token) {
|
||||||
|
return onLogin()
|
||||||
|
}
|
||||||
if (path === activePath.value) return
|
if (path === activePath.value) return
|
||||||
router.push(path)
|
router.push(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="pay">
|
<div class="pay">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<payment :ids="ids" />
|
<payment :ids="ids" />
|
||||||
<sc-list title="Order Summary" is-view is-mini :list="list" />
|
<sc-list :title="$t('Pay.OrderSummary')" is-view is-mini :list="list" />
|
||||||
</div>
|
</div>
|
||||||
<my-footer />
|
<my-footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,57 +2,47 @@
|
|||||||
<div class="payment">
|
<div class="payment">
|
||||||
<div class="header" @click="onBack">
|
<div class="header" @click="onBack">
|
||||||
<span class="icon"><svg-icon name="back" size="30" /></span>
|
<span class="icon"><svg-icon name="back" size="30" /></span>
|
||||||
<span class="text">Payment Details</span>
|
<span class="text">{{ $t('Pay.PaymentDetails') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 未支付 -->
|
<!-- 未支付 -->
|
||||||
<template v-if="paymentStatus !== ORDER_STATUS.SUCCESS">
|
<template v-if="paymentStatus !== ORDER_STATUS.SUCCESS">
|
||||||
<div class="paylist">
|
<div class="paylist">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<img src="@/assets/images/pay/stripe.png" alt="" />
|
<img src="@/assets/images/pay/stripe.png" alt="" />
|
||||||
<span>Credit / Debit Card</span>
|
<span>{{ $t('Pay.CreditDebitCard') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="agreement">
|
<div class="agreement">
|
||||||
<el-checkbox v-model="agreement">
|
<el-checkbox v-model="agreement">
|
||||||
<div class="text">
|
<div class="text" v-html="$t('Pay.AgreementText')"></div
|
||||||
I agree to the <span>Terms & Conditions</span> and
|
></el-checkbox>
|
||||||
<span>Privacy Policy</span>. All digital item sales are final and
|
|
||||||
non-refundable.
|
|
||||||
</div></el-checkbox
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="icon"><svg-icon name="card" size="20" /></span>
|
<span class="icon"><svg-icon name="card" size="20" /></span>
|
||||||
<span class="text">Pay with Paypal</span>
|
<span class="text">{{ $t('Pay.PayWithStripe') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!query.paymentId">
|
<template v-if="!query.paymentId">
|
||||||
<div class="tip">
|
<div class="tip">{{ $t('Pay.PayTip1') }}</div>
|
||||||
You'll be redirected to a Paypal popup to log in and confirm. No card details
|
|
||||||
are shared with Stylish Parade — PayPal handles all payment security.
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button custom="black" @click="onPayWith">
|
<button custom="black" @click="onPayWith">
|
||||||
<span class="text">Pay with</span>
|
<span class="text">{{ $t('Pay.PayWith') }}</span>
|
||||||
<span class="icon pay"><svg-icon name="pay-stripe" /></span>
|
<span class="icon pay"><svg-icon name="pay-stripe" /></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<span class="text" @click="onBack">Cancel</span>
|
<span class="text" @click="onBack">{{ $t('Pay.Cancel') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 已支付,等待确认 -->
|
<!-- 已支付,等待确认 -->
|
||||||
<template v-if="query.paymentId">
|
<template v-if="query.paymentId">
|
||||||
<div class="tip">
|
<div class="tip">{{ $t('Pay.PayTip2') }}</div>
|
||||||
Please keep the window open until the payment is completed. If you are to open
|
<div class="buttons">
|
||||||
the payment window, please check your browser settings to see if pop-ups are
|
<button custom="black" @click="getOrderStatus">
|
||||||
being blocked. Points may be delayed after successful payment. Please wait 1-3
|
{{ $t('Pay.IHaveCompletedPayment') }}
|
||||||
minutes and click the credits refresh button.
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button custom="black" @click="getOrderStatus">I Have Completed payment</button>
|
<span class="text" @click="onBack">{{ $t('Pay.Back') }}</span>
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<span class="text" @click="onBack">Back</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,11 +50,8 @@
|
|||||||
<template v-if="paymentStatus === ORDER_STATUS.SUCCESS">
|
<template v-if="paymentStatus === ORDER_STATUS.SUCCESS">
|
||||||
<div class="success">
|
<div class="success">
|
||||||
<img src="@/assets/images/pay/success.png" alt="" />
|
<img src="@/assets/images/pay/success.png" alt="" />
|
||||||
<div class="title">Purchase Successful!</div>
|
<div class="title">{{ $t('Pay.PurchaseSuccessful') }}</div>
|
||||||
<div class="tip">
|
<div class="tip">{{ $t('Pay.PurchaseSuccessfulTip') }}</div>
|
||||||
Your digital items are now available and have been saved in Personal Center → My
|
|
||||||
Wardrobe.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progres" v-if="downloadInfo.status !== DOWNLOAD_STATUS.null">
|
<div class="progres" v-if="downloadInfo.status !== DOWNLOAD_STATUS.null">
|
||||||
@@ -81,14 +68,16 @@
|
|||||||
</el-progress>
|
</el-progress>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button custom="black" @click="handleDownloadAllAssets">download all Assets</button>
|
<button custom="black" @click="handleDownloadAllAssets">
|
||||||
|
{{ $t('Pay.DownloadAllAssets') }}
|
||||||
|
</button>
|
||||||
<button custom="black-box">
|
<button custom="black-box">
|
||||||
<span class="icon"><svg-icon name="order-file" size="18" /></span>
|
<span class="icon"><svg-icon name="order-file" size="18" /></span>
|
||||||
<span class="text">Export Invoice</span>
|
<span class="text">{{ $t('Pay.ExportInvoice') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<span class="text" @click="onBack">Continue Shopping</span>
|
<span class="text" @click="onBack">{{ $t('Pay.ContinueShopping') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<span class="text">{{ $t('ShoppingCart.brands') }}</span>
|
<span class="text">{{ $t('ShoppingCart.brands') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="brands-item" v-for="v in brandsList" :key="v.brand">
|
<div class="brands-item" v-for="v in brandsList" :key="v.brand">
|
||||||
<span class="label">{{ v.brand }}</span>
|
<span class="label" @click="handleBrandClick(v.id)">{{ v.brand }}</span>
|
||||||
<span class="value"
|
<span class="value"
|
||||||
><span>{{ v.children.length }}</span
|
><span>{{ v.children.length }}</span
|
||||||
>{{ $t('ShoppingCart.item') }}</span
|
>{{ $t('ShoppingCart.item') }}</span
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
arr.push({
|
arr.push({
|
||||||
brand: v.brand,
|
brand: v.brand,
|
||||||
|
id: v.sellerId,
|
||||||
children: [v]
|
children: [v]
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -68,6 +69,12 @@
|
|||||||
query: { list }
|
query: { list }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const handleBrandClick = (id) => {
|
||||||
|
router.push({
|
||||||
|
name: 'brandDetail',
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -124,6 +131,8 @@
|
|||||||
> .label {
|
> .label {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: #585858;
|
color: #585858;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
> .value {
|
> .value {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
|
|||||||
@@ -319,6 +319,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
> .text {
|
> .text {
|
||||||
font-family: KaiseiOpti-Bold;
|
font-family: KaiseiOpti-Bold;
|
||||||
font-size: var(--sc-list-title-font-size, 4rem);
|
font-size: var(--sc-list-title-font-size, 4rem);
|
||||||
@@ -420,7 +421,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sc-list:deep(.el-select) {
|
.sc-list:deep(.el-select) {
|
||||||
width: 15rem;
|
width: 18rem;
|
||||||
--el-border-radius-base: 0;
|
--el-border-radius-base: 0;
|
||||||
--el-select-input-color: rgba(0, 0, 0, 0.5);
|
--el-select-input-color: rgba(0, 0, 0, 0.5);
|
||||||
--el-select-input-font-size: 1rem;
|
--el-select-input-font-size: 1rem;
|
||||||
|
|||||||
@@ -241,63 +241,6 @@
|
|||||||
const selectedCount = computed(() => {
|
const selectedCount = computed(() => {
|
||||||
return dataList.value.filter((el) => el.checked === true).length
|
return dataList.value.filter((el) => el.checked === true).length
|
||||||
})
|
})
|
||||||
const allCategoriesSelected = computed(() => {
|
|
||||||
return (
|
|
||||||
filters.categories.length === categoryValues.value.length &&
|
|
||||||
categoryValues.value.every((value) => filters.categories.includes(value))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const allGendersSelected = computed(() => {
|
|
||||||
return (
|
|
||||||
filters.genders.length === genderValues.value.length &&
|
|
||||||
genderValues.value.every((value) => filters.genders.includes(value))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isCategoryActive = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
return allCategoriesSelected.value
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.categories.includes(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleCategory = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
filters.categories = allCategoriesSelected.value ? [] : [...categoryValues.value]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.categories.includes(value)) {
|
|
||||||
filters.categories = filters.categories.filter((item) => item !== value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filters.categories = [...filters.categories, value]
|
|
||||||
}
|
|
||||||
|
|
||||||
const isGenderActive = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
return allGendersSelected.value
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.genders.includes(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleGender = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
filters.genders = allGendersSelected.value ? [] : [...genderValues.value]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.genders.includes(value)) {
|
|
||||||
filters.genders = filters.genders.filter((item) => item !== value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filters.genders = [...filters.genders, value]
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateFilters = (value: { categories: string[]; genders: string[] }) => {
|
const updateFilters = (value: { categories: string[]; genders: string[] }) => {
|
||||||
filters.categories = value.categories
|
filters.categories = value.categories
|
||||||
|
|||||||
@@ -11,41 +11,21 @@
|
|||||||
<section class="filter-group">
|
<section class="filter-group">
|
||||||
<h3 class="filter-group__title">{{ t('Wardrobe.assets.categories') }}</h3>
|
<h3 class="filter-group__title">{{ t('Wardrobe.assets.categories') }}</h3>
|
||||||
<div class="filter-group__line"></div>
|
<div class="filter-group__line"></div>
|
||||||
<div class="filter-group__options">
|
<Checked
|
||||||
<button
|
:list="categoryOptions"
|
||||||
v-for="option in categories"
|
:selected="selectedCategories"
|
||||||
:key="option.value"
|
@change="handleCategoryChange"
|
||||||
class="filter-option"
|
/>
|
||||||
type="button"
|
|
||||||
:class="{ 'is-active': isCategoryActive(option.value) }"
|
|
||||||
@click="toggleCategory(option.value)"
|
|
||||||
>
|
|
||||||
<span class="filter-option__box">
|
|
||||||
<span class="filter-option__tick"></span>
|
|
||||||
</span>
|
|
||||||
<span class="filter-option__label">{{ option.label }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="filter-group">
|
<section class="filter-group">
|
||||||
<h3 class="filter-group__title">{{ t('Wardrobe.assets.gender') }}</h3>
|
<h3 class="filter-group__title">{{ t('Wardrobe.assets.gender') }}</h3>
|
||||||
<div class="filter-group__line"></div>
|
<div class="filter-group__line"></div>
|
||||||
<div class="filter-group__options">
|
<CheckedGender
|
||||||
<button
|
:list="genderOptions"
|
||||||
v-for="option in genders"
|
:selected="selectedGenders"
|
||||||
:key="option.value"
|
@change="handleGenderChange"
|
||||||
class="filter-option"
|
/>
|
||||||
type="button"
|
|
||||||
:class="{ 'is-active': isGenderActive(option.value) }"
|
|
||||||
@click="toggleGender(option.value)"
|
|
||||||
>
|
|
||||||
<span class="filter-option__box">
|
|
||||||
<span class="filter-option__tick"></span>
|
|
||||||
</span>
|
|
||||||
<span class="filter-option__label">{{ option.label }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -55,6 +35,8 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
import Checked from '@/components/checked.vue'
|
||||||
|
import CheckedGender from '@/components/checked-gender.vue'
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
label: string
|
label: string
|
||||||
@@ -95,90 +77,64 @@
|
|||||||
props.genders.filter((option) => option.value !== 'all').map((option) => option.value)
|
props.genders.filter((option) => option.value !== 'all').map((option) => option.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
const allCategoriesSelected = computed(() => {
|
// 为 Checked 组件准备的选项列表(不包含 'all')
|
||||||
return (
|
const categoryOptions = computed(() =>
|
||||||
|
props.categories.filter((option) => option.value !== 'all')
|
||||||
|
)
|
||||||
|
|
||||||
|
const genderOptions = computed(() => props.genders.filter((option) => option.value !== 'all'))
|
||||||
|
|
||||||
|
// 转换当前选中状态为组件需要的格式
|
||||||
|
const selectedCategories = computed(() => {
|
||||||
|
const allSelected =
|
||||||
props.filters.categories.length === categoryValues.value.length &&
|
props.filters.categories.length === categoryValues.value.length &&
|
||||||
categoryValues.value.every((value) => props.filters.categories.includes(value))
|
categoryValues.value.every((value) => props.filters.categories.includes(value))
|
||||||
)
|
|
||||||
|
return allSelected ? ['all'] : props.filters.categories
|
||||||
})
|
})
|
||||||
|
|
||||||
const allGendersSelected = computed(() => {
|
const selectedGenders = computed(() => {
|
||||||
return (
|
const allSelected =
|
||||||
props.filters.genders.length === genderValues.value.length &&
|
props.filters.genders.length === genderValues.value.length &&
|
||||||
genderValues.value.every((value) => props.filters.genders.includes(value))
|
genderValues.value.every((value) => props.filters.genders.includes(value))
|
||||||
)
|
|
||||||
|
return allSelected ? ['all'] : props.filters.genders
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentFilters = computed<FilterState>(() => ({
|
const handleCategoryChange = (selected: string[]) => {
|
||||||
categories: [...props.filters.categories],
|
let categories: string[]
|
||||||
genders: [...props.filters.genders]
|
|
||||||
}))
|
if (selected.includes('all') || selected.length === 0) {
|
||||||
|
// 如果选择了 'all' 或者没有选择任何项,则选择所有分类
|
||||||
|
categories = [...categoryValues.value]
|
||||||
|
} else {
|
||||||
|
categories = selected
|
||||||
|
}
|
||||||
|
|
||||||
const updateFilters = (updated: Partial<FilterState>) => {
|
|
||||||
emit('update:filters', {
|
emit('update:filters', {
|
||||||
categories: updated.categories ?? currentFilters.value.categories,
|
categories,
|
||||||
genders: updated.genders ?? currentFilters.value.genders
|
genders: props.filters.genders
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCategoryActive = (value: string) => {
|
const handleGenderChange = (selected: string[]) => {
|
||||||
if (value === 'all') {
|
let genders: string[]
|
||||||
return allCategoriesSelected.value
|
|
||||||
|
if (selected.includes('all') || selected.length === 0) {
|
||||||
|
// 如果选择了 'all' 或者没有选择任何项,则选择所有性别
|
||||||
|
genders = [...genderValues.value]
|
||||||
|
} else {
|
||||||
|
genders = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.filters.categories.includes(value)
|
emit('update:filters', {
|
||||||
}
|
categories: props.filters.categories,
|
||||||
|
genders
|
||||||
const toggleCategory = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
updateFilters({
|
|
||||||
categories: allCategoriesSelected.value ? [] : [...categoryValues.value]
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.filters.categories.includes(value)) {
|
|
||||||
updateFilters({
|
|
||||||
categories: props.filters.categories.filter((item) => item !== value)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFilters({
|
|
||||||
categories: [...props.filters.categories, value]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const isGenderActive = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
return allGendersSelected.value
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.filters.genders.includes(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleGender = (value: string) => {
|
|
||||||
if (value === 'all') {
|
|
||||||
updateFilters({
|
|
||||||
genders: allGendersSelected.value ? [] : [...genderValues.value]
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.filters.genders.includes(value)) {
|
|
||||||
updateFilters({
|
|
||||||
genders: props.filters.genders.filter((item) => item !== value)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFilters({
|
|
||||||
genders: [...props.filters.genders, value]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
updateFilters({
|
emit('update:filters', {
|
||||||
categories: [...categoryValues.value],
|
categories: [...categoryValues.value],
|
||||||
genders: [...genderValues.value]
|
genders: [...genderValues.value]
|
||||||
})
|
})
|
||||||
@@ -241,60 +197,7 @@
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group__options {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.2rem;
|
|
||||||
|
|
||||||
.filter-option {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.2rem;
|
|
||||||
width: fit-content;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
font-family: 'KaiseiOpti-Regular';
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: #6e665d;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
> .filter-option__box {
|
|
||||||
width: 1.6rem;
|
|
||||||
height: 1.6rem;
|
|
||||||
border: 0.1rem solid var(--wardrobe-border-dark);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #ffffff;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.filter-option__tick {
|
|
||||||
width: 0.9rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-left: 0.18rem solid #ffffff;
|
|
||||||
border-bottom: 0.18rem solid #ffffff;
|
|
||||||
transform: rotate(-45deg) translateY(-0.05rem);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
color: var(--wardrobe-text-main);
|
|
||||||
|
|
||||||
.filter-option__box {
|
|
||||||
border-color: var(--wardrobe-text-main);
|
|
||||||
background: var(--wardrobe-text-main);
|
|
||||||
|
|
||||||
.filter-option__tick {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user