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',
|
||||
nameLengthError: 'Name length must be between {min} and {max} 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',
|
||||
emailFormatError: 'Please input the email again',
|
||||
pleaseInputPassword: 'Please input the password',
|
||||
@@ -355,7 +355,7 @@ export default {
|
||||
Offices: "Offices",
|
||||
JoinWithUs: "Join with Us",
|
||||
},
|
||||
addShoppingCart:{
|
||||
addShoppingCart: {
|
||||
title: 'Added to your Shopping Cart',
|
||||
statement: 'Digital Assets Only. No physical product included.',
|
||||
button: 'See Shopping Cart'
|
||||
@@ -375,5 +375,23 @@ export default {
|
||||
germany: 'Germany',
|
||||
australia: 'Australia',
|
||||
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: '请输入姓名',
|
||||
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
||||
passwordSpecial: '必须包含特殊符号',
|
||||
passwordCase: '大小写字母与数字混合组合',
|
||||
passwordCase: '字母和数字组合',
|
||||
pleaseInputEmail: '请输入邮箱',
|
||||
emailFormatError: '请输入正确的邮箱',
|
||||
pleaseInputPassword: '请输入密码',
|
||||
@@ -376,5 +376,23 @@ export default {
|
||||
AboutUs: '关于我们',
|
||||
Offices: '办公室',
|
||||
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 { fetchAllUnreadMessage } from '@/api/notification'
|
||||
import i18n from '@/lang/index'
|
||||
import myEvent from '@/utils/myEvent'
|
||||
|
||||
// 语言映射:后端格式 -> i18n 格式
|
||||
const backendToI18nLanguage: Record<string, string> = {
|
||||
@@ -19,6 +20,8 @@ let languageSynced = false
|
||||
* 1. 设置路由的meta属性为{ cache: true },表示需要缓存
|
||||
* 2. App.vue中使用RouteCache组件,通过路由的name来进行匹配
|
||||
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
|
||||
*
|
||||
* 需要登录路由: meta={ login:true }
|
||||
*/
|
||||
const router = createRouter({
|
||||
routes: [
|
||||
@@ -62,7 +65,8 @@ const router = createRouter({
|
||||
{
|
||||
path: '/shoppingCart', // 购物车
|
||||
name: 'shoppingCart',
|
||||
component: () => import('@/views/shoppingCart/index.vue')
|
||||
component: () => import('@/views/shoppingCart/index.vue'),
|
||||
meta: { login: true }
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
@@ -94,6 +98,9 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if(to.meta?.login && !useUserInfoStore().state.token) {
|
||||
myEvent.emit('openLoginDialog')
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
> section {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 50vw;
|
||||
}
|
||||
> section.bgw {
|
||||
position: relative;
|
||||
@@ -40,5 +41,8 @@
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
> .section-footer {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
> div {
|
||||
padding: 1rem;
|
||||
border: 0.1rem solid #979797;
|
||||
background-color: #fff;
|
||||
> img {
|
||||
width: 27.4rem;
|
||||
height: 34.6rem;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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="tip" v-html="$t('Home.DigitalItemsTip2')"></div>
|
||||
<div class="list">
|
||||
@@ -65,6 +65,7 @@
|
||||
> div {
|
||||
padding: 1rem;
|
||||
border: 0.1rem solid #979797;
|
||||
background-color: #fff;
|
||||
> img {
|
||||
width: 27.4rem;
|
||||
height: 34.6rem;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
</el-icon>
|
||||
<span>{{ $t('Login.passwordLengthError', { min: 6, max: 20 }) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<!-- <div>
|
||||
<el-icon>
|
||||
<CloseBold v-if="validateSpecial(value)" />
|
||||
<Select v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t('Login.passwordSpecial') }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div>
|
||||
<el-icon>
|
||||
<CloseBold v-if="validateCase(value)" />
|
||||
@@ -42,7 +42,7 @@
|
||||
color: #fff;
|
||||
font-size: 1.1rem;
|
||||
padding: 1.5rem;
|
||||
border-radius: 1.5rem;
|
||||
// border-radius: 1.5rem;
|
||||
line-height: normal;
|
||||
|
||||
> 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 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) => {
|
||||
var str = ''
|
||||
if (validateLength(value)) {
|
||||
str = t('Login.passwordLengthError', { min: 6, max: 20 })
|
||||
} else if (validateSpecial(value)) {
|
||||
str = t('Login.passwordSpecial')
|
||||
// } else if (validateSpecial(value)) {
|
||||
// str = t('Login.passwordSpecial')
|
||||
} else if (validateCase(value)) {
|
||||
str = t('Login.passwordCase')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="main-header" id="main-header">
|
||||
<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 class="center">
|
||||
<div
|
||||
@@ -14,7 +18,7 @@
|
||||
? activePath === v.path
|
||||
: new RegExp(`^${v.path}`).test(activePath)
|
||||
}"
|
||||
@click="onNavItemClick(v.path)"
|
||||
@click="onNavItemClick(v)"
|
||||
>
|
||||
<span>{{ $t(v.name) }}</span>
|
||||
</div>
|
||||
@@ -25,7 +29,7 @@
|
||||
v-for="v in navList2"
|
||||
:key="v.path"
|
||||
: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" />
|
||||
</div>
|
||||
@@ -129,10 +133,15 @@
|
||||
{
|
||||
icon: 'cart_0',
|
||||
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
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="pay">
|
||||
<div class="content">
|
||||
<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>
|
||||
<my-footer />
|
||||
</div>
|
||||
|
||||
@@ -2,57 +2,47 @@
|
||||
<div class="payment">
|
||||
<div class="header" @click="onBack">
|
||||
<span class="icon"><svg-icon name="back" size="30" /></span>
|
||||
<span class="text">Payment Details</span>
|
||||
<span class="text">{{ $t('Pay.PaymentDetails') }}</span>
|
||||
</div>
|
||||
<!-- 未支付 -->
|
||||
<template v-if="paymentStatus !== ORDER_STATUS.SUCCESS">
|
||||
<div class="paylist">
|
||||
<div class="item">
|
||||
<img src="@/assets/images/pay/stripe.png" alt="" />
|
||||
<span>Credit / Debit Card</span>
|
||||
<span>{{ $t('Pay.CreditDebitCard') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement">
|
||||
<el-checkbox v-model="agreement">
|
||||
<div class="text">
|
||||
I agree to the <span>Terms & Conditions</span> and
|
||||
<span>Privacy Policy</span>. All digital item sales are final and
|
||||
non-refundable.
|
||||
</div></el-checkbox
|
||||
>
|
||||
<div class="text" v-html="$t('Pay.AgreementText')"></div
|
||||
></el-checkbox>
|
||||
</div>
|
||||
<div class="title">
|
||||
<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>
|
||||
<template v-if="!query.paymentId">
|
||||
<div class="tip">
|
||||
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="tip">{{ $t('Pay.PayTip1') }}</div>
|
||||
<div class="buttons">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<span class="text" @click="onBack">Cancel</span>
|
||||
<span class="text" @click="onBack">{{ $t('Pay.Cancel') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 已支付,等待确认 -->
|
||||
<template v-if="query.paymentId">
|
||||
<div class="tip">
|
||||
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.
|
||||
<div class="tip">{{ $t('Pay.PayTip2') }}</div>
|
||||
<div class="buttons">
|
||||
<button custom="black" @click="getOrderStatus">
|
||||
{{ $t('Pay.IHaveCompletedPayment') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button custom="black" @click="getOrderStatus">I Have Completed payment</button>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<span class="text" @click="onBack">Back</span>
|
||||
<span class="text" @click="onBack">{{ $t('Pay.Back') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -60,11 +50,8 @@
|
||||
<template v-if="paymentStatus === ORDER_STATUS.SUCCESS">
|
||||
<div class="success">
|
||||
<img src="@/assets/images/pay/success.png" alt="" />
|
||||
<div class="title">Purchase Successful!</div>
|
||||
<div class="tip">
|
||||
Your digital items are now available and have been saved in Personal Center → My
|
||||
Wardrobe.
|
||||
</div>
|
||||
<div class="title">{{ $t('Pay.PurchaseSuccessful') }}</div>
|
||||
<div class="tip">{{ $t('Pay.PurchaseSuccessfulTip') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="progres" v-if="downloadInfo.status !== DOWNLOAD_STATUS.null">
|
||||
@@ -81,14 +68,16 @@
|
||||
</el-progress>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<span class="text" @click="onBack">Continue Shopping</span>
|
||||
<span class="text" @click="onBack">{{ $t('Pay.ContinueShopping') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span class="text">{{ $t('ShoppingCart.brands') }}</span>
|
||||
</div>
|
||||
<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>{{ v.children.length }}</span
|
||||
>{{ $t('ShoppingCart.item') }}</span
|
||||
@@ -51,6 +51,7 @@
|
||||
if (index === -1) {
|
||||
arr.push({
|
||||
brand: v.brand,
|
||||
id: v.sellerId,
|
||||
children: [v]
|
||||
})
|
||||
} else {
|
||||
@@ -68,6 +69,12 @@
|
||||
query: { list }
|
||||
})
|
||||
}
|
||||
const handleBrandClick = (id) => {
|
||||
router.push({
|
||||
name: 'brandDetail',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -124,6 +131,8 @@
|
||||
> .label {
|
||||
text-decoration: underline;
|
||||
color: #585858;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
> .value {
|
||||
color: #808080;
|
||||
|
||||
@@ -319,6 +319,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
> .text {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--sc-list-title-font-size, 4rem);
|
||||
@@ -420,7 +421,7 @@
|
||||
}
|
||||
}
|
||||
.sc-list:deep(.el-select) {
|
||||
width: 15rem;
|
||||
width: 18rem;
|
||||
--el-border-radius-base: 0;
|
||||
--el-select-input-color: rgba(0, 0, 0, 0.5);
|
||||
--el-select-input-font-size: 1rem;
|
||||
|
||||
@@ -241,63 +241,6 @@
|
||||
const selectedCount = computed(() => {
|
||||
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[] }) => {
|
||||
filters.categories = value.categories
|
||||
|
||||
@@ -11,41 +11,21 @@
|
||||
<section class="filter-group">
|
||||
<h3 class="filter-group__title">{{ t('Wardrobe.assets.categories') }}</h3>
|
||||
<div class="filter-group__line"></div>
|
||||
<div class="filter-group__options">
|
||||
<button
|
||||
v-for="option in categories"
|
||||
:key="option.value"
|
||||
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>
|
||||
<Checked
|
||||
:list="categoryOptions"
|
||||
:selected="selectedCategories"
|
||||
@change="handleCategoryChange"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="filter-group">
|
||||
<h3 class="filter-group__title">{{ t('Wardrobe.assets.gender') }}</h3>
|
||||
<div class="filter-group__line"></div>
|
||||
<div class="filter-group__options">
|
||||
<button
|
||||
v-for="option in genders"
|
||||
:key="option.value"
|
||||
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>
|
||||
<CheckedGender
|
||||
:list="genderOptions"
|
||||
:selected="selectedGenders"
|
||||
@change="handleGenderChange"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -55,6 +35,8 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { PropType } from 'vue'
|
||||
import Checked from '@/components/checked.vue'
|
||||
import CheckedGender from '@/components/checked-gender.vue'
|
||||
|
||||
interface FilterOption {
|
||||
label: string
|
||||
@@ -95,90 +77,64 @@
|
||||
props.genders.filter((option) => option.value !== 'all').map((option) => option.value)
|
||||
)
|
||||
|
||||
const allCategoriesSelected = computed(() => {
|
||||
return (
|
||||
// 为 Checked 组件准备的选项列表(不包含 'all')
|
||||
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 &&
|
||||
categoryValues.value.every((value) => props.filters.categories.includes(value))
|
||||
)
|
||||
|
||||
return allSelected ? ['all'] : props.filters.categories
|
||||
})
|
||||
|
||||
const allGendersSelected = computed(() => {
|
||||
return (
|
||||
const selectedGenders = computed(() => {
|
||||
const allSelected =
|
||||
props.filters.genders.length === genderValues.value.length &&
|
||||
genderValues.value.every((value) => props.filters.genders.includes(value))
|
||||
)
|
||||
|
||||
return allSelected ? ['all'] : props.filters.genders
|
||||
})
|
||||
|
||||
const currentFilters = computed<FilterState>(() => ({
|
||||
categories: [...props.filters.categories],
|
||||
genders: [...props.filters.genders]
|
||||
}))
|
||||
const handleCategoryChange = (selected: string[]) => {
|
||||
let categories: string[]
|
||||
|
||||
if (selected.includes('all') || selected.length === 0) {
|
||||
// 如果选择了 'all' 或者没有选择任何项,则选择所有分类
|
||||
categories = [...categoryValues.value]
|
||||
} else {
|
||||
categories = selected
|
||||
}
|
||||
|
||||
const updateFilters = (updated: Partial<FilterState>) => {
|
||||
emit('update:filters', {
|
||||
categories: updated.categories ?? currentFilters.value.categories,
|
||||
genders: updated.genders ?? currentFilters.value.genders
|
||||
categories,
|
||||
genders: props.filters.genders
|
||||
})
|
||||
}
|
||||
|
||||
const isCategoryActive = (value: string) => {
|
||||
if (value === 'all') {
|
||||
return allCategoriesSelected.value
|
||||
const handleGenderChange = (selected: string[]) => {
|
||||
let genders: string[]
|
||||
|
||||
if (selected.includes('all') || selected.length === 0) {
|
||||
// 如果选择了 'all' 或者没有选择任何项,则选择所有性别
|
||||
genders = [...genderValues.value]
|
||||
} else {
|
||||
genders = selected
|
||||
}
|
||||
|
||||
return props.filters.categories.includes(value)
|
||||
}
|
||||
|
||||
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]
|
||||
emit('update:filters', {
|
||||
categories: props.filters.categories,
|
||||
genders
|
||||
})
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
updateFilters({
|
||||
emit('update:filters', {
|
||||
categories: [...categoryValues.value],
|
||||
genders: [...genderValues.value]
|
||||
})
|
||||
@@ -241,60 +197,7 @@
|
||||
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