소스 검색

feat: 新增产品标签功能和多语言支持

本次提交主要进行了以下修改:
1. 在 `content.config.ts` 中为产品和类别集合新增了 `tags` 字段,以支持产品标签功能。
2. 在 `i18n/locales` 目录下的多语言文件中新增了 `all` 标签的翻译,确保多语言环境下的标签显示一致。
3. 在 `pages/products/index.vue` 中实现了标签选择功能,用户可以通过标签筛选产品,提升了用户体验。
4. 更新了相关的路由逻辑,以支持标签的选择和清除。

这些改动旨在增强产品页面的可用性和用户交互体验,同时提升多语言支持的完整性。
master
lizhuang 1 개월 전
부모
커밋
2679beeb56
5개의 변경된 파일89개의 추가작업 그리고 9개의 파일을 삭제
  1. 2
    0
      content.config.ts
  2. 1
    0
      i18n/locales/en.ts
  3. 1
    0
      i18n/locales/ja.ts
  4. 1
    0
      i18n/locales/zh.ts
  5. 84
    9
      pages/products/index.vue

+ 2
- 0
content.config.ts 파일 보기

@@ -11,6 +11,7 @@ const categoryCollection = defineCollection({
summary: z.string(),
capacities: z.array(z.string()),
sort: z.number(),
tags: z.array(z.string()),
})
})

@@ -25,6 +26,7 @@ const productCollection = defineCollection({
categoryId: z.string(), // 关联类别的ID
usage: z.array(z.string()),
series: z.array(z.string()),
tag: z.array(z.string()),
image: z.string(),
gallery: z.array(z.string()),
summary: z.string(),

+ 1
- 0
i18n/locales/en.ts 파일 보기

@@ -72,6 +72,7 @@ export default {
privacy: "Privacy Policy",
terms: "Terms of Use",
},
all: "All",
},
},
home: {

+ 1
- 0
i18n/locales/ja.ts 파일 보기

@@ -66,6 +66,7 @@ export default {
privacy: "プライバシーポリシー",
terms: "利用規約",
},
all: "全部",
},
},
home: {

+ 1
- 0
i18n/locales/zh.ts 파일 보기

@@ -67,6 +67,7 @@ export default {
description: "了解 Hanye 如何收集、使用和保护您的个人信息",
},
},
all: "全部",
},
home: {
title: "Hanye 官网",

+ 84
- 9
pages/products/index.vue 파일 보기

@@ -73,6 +73,7 @@
v-if="selectedCategory"
@click="clearCategory"
class="flex items-center gap-1 px-1.5 py-0.5 sm:px-2 sm:py-1 md:px-2.5 md:py-1.5 lg:px-3 lg:py-2 text-xs sm:text-sm md:text-base text-white/60 hover:text-white bg-zinc-800/50 hover:bg-zinc-700/50 rounded-lg transition-all duration-300 active:scale-95"
v-show="false"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -174,6 +175,34 @@
{{ category }}
</div>

<div
v-if="getCategoryTags(category)?.length > 0 && selectedCategory"
class="flex flex-wrap gap-4"
>
<span
@click="selectTag('all')"
class="text-white text-sm md:text-lg font-normal px-4 py-2 bg-zinc-800/50 rounded-lg cursor-pointer hover:bg-zinc-700/50 transition-all duration-300"
:class="{
'!bg-cyan-400 text-zinc-900 font-medium':
selectedTag === 'all',
}"
>
{{ t("common.all") }}
</span>
<span
v-for="tag in getCategoryTags(category)"
:key="tag"
@click="selectTag(tag)"
class="text-white text-sm md:text-lg font-normal px-4 py-2 bg-zinc-800/50 rounded-lg cursor-pointer hover:bg-zinc-700/50 transition-all duration-300"
:class="{
'!bg-cyan-400 text-zinc-900 font-medium':
selectedTag === tag,
}"
>
{{ tag }}
</span>
</div>

<!-- 添加系列分组 -->
<template
v-for="[seriesName, seriesProducts] in Array.from(
@@ -188,7 +217,9 @@
(c) => c.title === category
);
return (
categoryObj && p.categoryId === categoryObj.id
categoryObj &&
p.categoryId === categoryObj.id &&
(selectedTag === 'all' || p.tag === selectedTag)
);
})
"
@@ -240,7 +271,9 @@
);
return (
categoryObj &&
p.categoryId === categoryObj.id
p.categoryId === categoryObj.id &&
(selectedTag === 'all' ||
p.tag === selectedTag)
);
})"
:key="product.id"
@@ -332,7 +365,8 @@
return (
categoryObj &&
p.categoryId === categoryObj.id &&
(!p.series || p.series.length === 0)
(!p.series || p.series.length === 0) &&
(selectedTag === 'all' || p.tag === selectedTag)
);
}).length > 0
"
@@ -348,7 +382,8 @@
return (
categoryObj &&
p.categoryId === categoryObj.id &&
(!p.series || p.series.length === 0)
(!p.series || p.series.length === 0) &&
(selectedTag === 'all' || p.tag === selectedTag)
);
})"
:key="product.id"
@@ -472,6 +507,7 @@ interface Product {
summary: string;
gallery: string[];
sort: number;
tag: string;
}

interface Category {
@@ -482,6 +518,7 @@ interface Category {
summary: string;
capacities: string[];
sort: number;
tags: string[];
}

// 分页配置
@@ -505,6 +542,12 @@ const categories = ref<string[]>([]);
const usages = ref<string[]>([]);
const selectedCategory = ref(route.query.category?.toString() || "");
const selectedUsage = ref(route.query.usage?.toString() || "");
const selectedTag = ref(route.query.tag?.toString() || "all");

const getCategoryTags = (category: string) => {
const categoryObj = allCategories.value.find((c) => c.title === category);
return categoryObj?.tags || [];
};

// 使用缓存优化数据加载
const { data: productData } = await useAsyncData("products", async () => {
@@ -534,6 +577,7 @@ const { data: productData } = await useAsyncData("products", async () => {
summary: meta.summary || "",
gallery: meta.gallery || [],
sort: meta.sort || 0,
tag: meta.tag || "",
};
})
.sort((a, b) => a.sort - b.sort);
@@ -569,6 +613,7 @@ const { data: categoryData } = await useAsyncData("categories", async () => {
summary: meta.summary || "",
capacities: meta.capacities || [],
sort: meta.sort || 0,
tags: meta.tags || [],
};
})
.sort((a, b) => a.sort - b.sort);
@@ -658,9 +703,22 @@ watchEffect(() => {
* 处理分类筛选
*/
function handleCategoryFilter(category: string) {
selectedCategory.value = selectedCategory.value === category ? "" : category;
// 不能取消选择
selectedCategory.value = category;
// 切换分类时,重置tag选择为'all'
selectedTag.value = "all";
currentPage.value = 1; // 重置页码
filteredProducts.value = paginatedProducts.value;
// 更新路由,移除tag参数
router.push({
query: {
...route.query,
category: category,
tag: undefined, // 清除tag参数
page: currentPage.value > 1 ? currentPage.value.toString() : undefined
}
});
}

/**
@@ -680,13 +738,15 @@ function handlePageChange(page: number) {
filteredProducts.value = paginatedProducts.value;
}

// 监听分类变化
// 监听分类变化 - 注释掉这个监听,防止干扰直接路由更新
/*
watch(selectedCategory, (newValue) => {
debouncedRouterPush({
...route.query,
category: newValue || undefined,
});
});
*/

// 监听用途变化
watch(selectedUsage, (newValue) => {
@@ -702,6 +762,7 @@ watch(
(newQuery) => {
selectedCategory.value = newQuery.category?.toString() || "";
selectedUsage.value = newQuery.usage?.toString() || "";
selectedTag.value = newQuery.tag?.toString() || "all";
const page = parseInt(newQuery.page?.toString() || "1");
if (page !== currentPage.value) {
currentPage.value = page;
@@ -714,6 +775,7 @@ watch(
const resetFilters = () => {
selectedCategory.value = "";
selectedUsage.value = "";
selectedTag.value = "all";
currentPage.value = 1;
debouncedRouterPush({});
};
@@ -766,8 +828,8 @@ const handleImageError = (event: Event, product: Product) => {
}
};

// 清除分类筛选
const clearCategory = () => {
// 清除分类筛选 (已经隐藏按钮)
function clearCategory() {
selectedCategory.value = "";
currentPage.value = 1;
filteredProducts.value = paginatedProducts.value;
@@ -775,7 +837,7 @@ const clearCategory = () => {
...route.query,
category: undefined,
});
};
}

// 清除用途筛选
const clearUsage = () => {
@@ -788,6 +850,19 @@ const clearUsage = () => {
});
};

// 选择标签进行筛选
function selectTag(tag: string) {
selectedTag.value = tag;

// 更新路由查询参数
router.push({
query: {
...route.query,
tag: tag === "all" ? undefined : tag
}
});
}

// 组件卸载时清理资源
onBeforeUnmount(() => {
allProducts.value = [];

Loading…
취소
저장