feat: 服装分类

This commit is contained in:
2026-05-21 13:44:57 +08:00
parent 5476a1f69d
commit 81b907562e
8 changed files with 321 additions and 61 deletions

View File

@@ -3,12 +3,14 @@
<aside class="wardrobe-assets__filters">
<div class="filters-card">
<div class="filters-card__heading">
<h2 class="filters-card__title">Filters</h2>
<button class="filters-card__clear" type="button" @click="clearFilters">Clear</button>
<h2 class="filters-card__title">{{ t('Wardrobe.assets.filters') }}</h2>
<button class="filters-card__clear" type="button" @click="clearFilters">
{{ t('Wardrobe.assets.clear') }}
</button>
</div>
<section class="filter-group">
<h3 class="filter-group__title">Categories</h3>
<h3 class="filter-group__title">{{ t('Wardrobe.assets.categories') }}</h3>
<div class="filter-group__line"></div>
<div class="filter-group__options">
<button
@@ -28,7 +30,7 @@
</section>
<section class="filter-group">
<h3 class="filter-group__title">Gender</h3>
<h3 class="filter-group__title">{{ t('Wardrobe.assets.gender') }}</h3>
<div class="filter-group__line"></div>
<div class="filter-group__options">
<button
@@ -57,10 +59,16 @@
<div class="assets-toolbar__selection">
<div class="select-count flex align-center">
<img src="@/assets/images/wardrobe/select.png" />
<span class="assets-toolbar__count">{{ selectedCount }} Selected</span>
<span class="assets-toolbar__count">
{{ t('Wardrobe.assets.selectedCount', { count: selectedCount }) }}
</span>
</div>
<div class="assets-toolbar__link" @click="handleSelectAll(true)">
{{ t('Wardrobe.assets.selectAll') }}
</div>
<div class="assets-toolbar__link" @click="handleSelectAll(false)">
{{ t('Wardrobe.assets.deselectAll') }}
</div>
<div class="assets-toolbar__link" @click="handleSelectAll(true)">Select All</div>
<div class="assets-toolbar__link" @click="handleSelectAll(false)">Deselect All</div>
</div>
<div class="assets-toolbar__actions">
@@ -70,7 +78,7 @@
@click="handleDownloadSelected"
>
<SvgIcon name="downloadBtn" color="#fff" />
<span>Download Selected</span>
<span>{{ t('Wardrobe.assets.downloadSelected') }}</span>
</div>
</div>
</div>
@@ -106,7 +114,9 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useClothesCategories } from '@/utils/ClothesCategory'
import img from '@/assets/images/collectionStory/Rectangle.png'
import Empty from './Empty.vue'
@@ -116,26 +126,26 @@ interface FilterOption {
}
const router = useRouter()
const { t } = useI18n({ useScope: 'global' })
const clothesCategories = useClothesCategories()
const categories: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Outerwear', value: 'outerwear' },
{ label: 'Dress', value: 'dress' },
{ label: 'Trousers', value: 'trousers' },
{ label: 'Blouse', value: 'blouse' },
{ label: 'Skirt', value: 'skirt' },
{ label: 'Accessories', value: 'accessories' }
]
const categories = computed<FilterOption[]>(() => [
{ label: t('Wardrobe.common.all'), value: 'all' },
...clothesCategories.value.map((option) => ({
label: option.label,
value: option.value
}))
])
const genders: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' }
]
const genders = computed<FilterOption[]>(() => [
{ label: t('Wardrobe.common.all'), value: 'all' },
{ label: t('Wardrobe.assets.genders.male'), value: 'male' },
{ label: t('Wardrobe.assets.genders.female'), value: 'female' }
])
const categoryValues = categories
.filter((option) => option.value !== 'all')
.map((option) => option.value)
const categoryValues = computed(() =>
categories.value.filter((option) => option.value !== 'all').map((option) => option.value)
)
const filters = reactive({
categories: ['skirt'] as string[],
@@ -209,8 +219,8 @@ const selectedCount = computed(() => {
})
const allCategoriesSelected = computed(() => {
return (
filters.categories.length === categoryValues.length &&
categoryValues.every((value) => filters.categories.includes(value))
filters.categories.length === categoryValues.value.length &&
categoryValues.value.every((value) => filters.categories.includes(value))
)
})
@@ -224,7 +234,7 @@ const isCategoryActive = (value: string) => {
const toggleCategory = (value: string) => {
if (value === 'all') {
filters.categories = allCategoriesSelected.value ? [] : [...categoryValues]
filters.categories = allCategoriesSelected.value ? [] : [...categoryValues.value]
return
}
@@ -241,7 +251,7 @@ const setGender = (value: string) => {
}
const clearFilters = () => {
filters.categories = [...categoryValues]
filters.categories = [...categoryValues.value]
filters.gender = 'all'
}
@@ -456,9 +466,9 @@ onUnmounted(() => {
font-family: 'KaiseiOpti-Regular';
.select-count {
column-gap: 1.2rem;
img{
img {
width: 2.4rem;
height: 2.4rem ;
height: 2.4rem;
}
.assets-toolbar__count {
position: relative;
@@ -544,7 +554,6 @@ onUnmounted(() => {
}
}
}
}
}
</style>

View File

@@ -1,4 +1,8 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n({ useScope: 'global' })
const emit = defineEmits<{
(event: 'explore'): void
}>()
@@ -8,12 +12,12 @@ const emit = defineEmits<{
<div class="wardrobe-empty flex flex-col flex-center">
<img src="@/assets/images/wardrobe/empty-wardrobe.png" class="wardrobe-empty__image" alt="" />
<h2 class="wardrobe-empty__title">Nothing in Wardrobe yet</h2>
<h2 class="wardrobe-empty__title">{{ t('Wardrobe.empty.title') }}</h2>
<p class="wardrobe-empty__description">
Explore the digital item and add pieces to your collection.
{{ t('Wardrobe.empty.description') }}
</p>
<button class="wardrobe-empty__button" type="button" @click="emit('explore')">
Explore Digital Items
{{ t('Wardrobe.empty.action') }}
</button>
</div>
</template>

View File

@@ -33,17 +33,17 @@
:style="{ backgroundColor: item.color }"
></span>
<span v-if="getExtraCount(order)" class="order-card__extra">
+{{ getExtraCount(order) }} more
{{ t('Wardrobe.orders.moreItems', { count: getExtraCount(order) }) }}
</span>
</div>
<div class="order-card__status" :class="`is-${order.status}`">
{{ order.status.toUpperCase() }}
{{ getStatusBadgeLabel(order.status) }}
</div>
<div class="order-card__amount">
<span>${{ order.amount }}</span>
<small>HKD</small>
<small>{{ t('Wardrobe.common.currencyHkd') }}</small>
</div>
<button
@@ -52,9 +52,7 @@
:class="{ 'is-primary': order.status === 'unpaid' }"
>
<svg-icon v-if="order.status === 'paid'" name="Invoice" size="20" color="#232323" />
<span v-if="order.status === 'paid'">Invoice</span>
<span v-else-if="order.status === 'unpaid'">Complete Payment</span>
<span v-else>Buy Again</span>
<span>{{ getOrderActionLabel(order.status) }}</span>
</button>
</div>
@@ -77,6 +75,7 @@
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import ScItem from '@/views/shoppingCart/sc-item.vue'
type OrderStatus = 'all' | 'paid' | 'unpaid' | 'cancelled'
@@ -109,13 +108,14 @@ interface OrderRecord {
}
const placeholderImage = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
const { t } = useI18n({ useScope: 'global' })
const statusOptions: StatusOption[] = [
{ key: 'all', label: 'All' },
{ key: 'paid', label: 'Paid' },
{ key: 'unpaid', label: 'Unpaid' },
{ key: 'cancelled', label: 'Canceled' }
]
const statusOptions = computed<StatusOption[]>(() => [
{ key: 'all', label: t('Wardrobe.orders.statuses.all') },
{ key: 'paid', label: t('Wardrobe.orders.statuses.paid') },
{ key: 'unpaid', label: t('Wardrobe.orders.statuses.unpaid') },
{ key: 'cancelled', label: t('Wardrobe.orders.statuses.cancelled') }
])
const createOrderItem = (
id: number,
@@ -201,6 +201,16 @@ const toggleOrder = (orderId: string) => {
const getExtraCount = (order: OrderRecord) => {
return Math.max(order.items.length - 2, 0)
}
const getStatusBadgeLabel = (status: ActualOrderStatus) => {
return t(`Wardrobe.orders.statusBadges.${status}`)
}
const getOrderActionLabel = (status: ActualOrderStatus) => {
if (status === 'paid') return t('Wardrobe.orders.actions.invoice')
if (status === 'unpaid') return t('Wardrobe.orders.actions.completePayment')
return t('Wardrobe.orders.actions.buyAgain')
}
</script>
<style lang="less" scoped>

View File

@@ -1,13 +1,13 @@
<template>
<div class="wardrobe-page">
<div class="wardrobe-hero flex flex-col flex-center">
<div class="wardrobe-hero__title">My Wardrobe</div>
<div class="wardrobe-hero__subtitle">Your digital pieces, all in one place</div>
<div class="wardrobe-hero__title">{{ t('Wardrobe.title') }}</div>
<div class="wardrobe-hero__subtitle">{{ t('Wardrobe.subtitle') }}</div>
</div>
<div class="wardrobe-shell">
<div class="wardrobe-tabs">
<div class="wardrobe-tabs__nav" role="tablist" aria-label="Wardrobe tabs">
<div class="wardrobe-tabs__nav" role="tablist" :aria-label="t('Wardrobe.tabs.ariaLabel')">
<button
v-for="tab in tabs"
:key="tab.key"
@@ -23,8 +23,8 @@
</div>
<div v-if="activeTab === 'assets'" class="wardrobe-tabs__sort">
<div class="wardrobe-tabs__sort-label">Sort by</div>
<el-select v-model="activeSort" placeholder="Select">
<div class="wardrobe-tabs__sort-label">{{ t('Wardrobe.sort.label') }}</div>
<el-select v-model="activeSort" :placeholder="t('Wardrobe.sort.placeholder')">
<el-option
v-for="option in sortOptions"
:key="option.value"
@@ -42,6 +42,7 @@
</template>
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import Assets from './Assets.vue'
import Orders from './Orders.vue'
@@ -57,31 +58,33 @@ interface SortOption {
value: number
}
const tabs: TabItem[] = [
const { t } = useI18n({ useScope: 'global' })
const tabs = computed<TabItem[]>(() => [
{
key: 'assets',
label: 'Assets'
label: t('Wardrobe.tabs.assets')
},
{
key: 'orders',
label: 'Orders'
label: t('Wardrobe.tabs.orders')
}
]
])
const sortOptions: SortOption[] = [
const sortOptions = computed<SortOption[]>(() => [
{
label: 'Default',
label: t('Wardrobe.sort.default'),
value: 0
},
{
label: 'Date Added',
label: t('Wardrobe.sort.dateAdded'),
value: 1
},
{
label: 'Selected First',
label: t('Wardrobe.sort.selectedFirst'),
value: 2
}
]
])
const activeTab = shallowRef<WardrobeTab>('assets')
const activeSort = shallowRef(1)