Commit fa35f0cf933797c75441e375cf7846441bed539c
1 parent
877cab94
feat: 开发面包屑导航
Showing
6 changed files
with
236 additions
and
24 deletions
components/CategoryList.vue
... | ... | @@ -79,7 +79,9 @@ import { useProductListStore } from "@/stores/product_list"; |
79 | 79 | import type { CategoryRootType } from "@/type"; |
80 | 80 | import { computed } from "vue"; |
81 | 81 | import { useRouter, useRoute } from "vue-router"; |
82 | +import { useRouteQuery } from "@/stores/route_query"; | |
82 | 83 | |
84 | +const routeQuery = useRouteQuery(); | |
83 | 85 | const route = useRoute(); |
84 | 86 | const router = useRouter(); |
85 | 87 | |
... | ... | @@ -91,11 +93,13 @@ watchEffect(async () => { |
91 | 93 | productStore.updateKeyword(""); |
92 | 94 | const categories = route.query.categories.split(","); |
93 | 95 | const mainCategory = categories[0].trim(); // 取第一个值 |
94 | - const subCategoryName = categories[1] ? categories[1].trim() : null; // 取第二个值(如果存在) | |
96 | + const subCategoryName = categories[1] | |
97 | + ? categories[1].trim() | |
98 | + : "Not specified"; // 取第二个值(如果存在) | |
95 | 99 | |
96 | 100 | // 2. 更新选中的主分类 |
97 | 101 | categoryStore.updateCategory(mainCategory); |
98 | - | |
102 | + routeQuery.updateCategories(mainCategory + "," + subCategoryName); | |
99 | 103 | // 3. 如果有子分类名称,查找其对应的 ID |
100 | 104 | if (subCategoryName) { |
101 | 105 | const subCategoryList = computed(() => { |
... | ... | @@ -123,6 +127,7 @@ watchEffect(async () => { |
123 | 127 | // 5. 判断 query 中是否存在 function,并查找对应的 ID |
124 | 128 | if (route.query.function) { |
125 | 129 | const functionName = route.query.function.trim(); |
130 | + routeQuery.updateFunction(functionName); | |
126 | 131 | const funcCategoryList = computed(() => { |
127 | 132 | if (categoryStore.selectedCategory) { |
128 | 133 | const tmp = categoryStore.list.filter( |
... | ... | @@ -152,8 +157,23 @@ watchEffect(async () => { |
152 | 157 | // // 使用找到的 funcCategoryId |
153 | 158 | // categoryStore.updateFuncCategory(funcCategoryId); |
154 | 159 | // } |
160 | + } else if (route.query.categories.includes("Energy materials")) { | |
161 | + const defaultCategory = categoryStore.list[0]; | |
162 | + const defaultFuncCategory = defaultCategory.productFunctions[0]; | |
163 | + if (defaultFuncCategory) { | |
164 | + categoryStore.updateFuncCategory(defaultFuncCategory.id); | |
165 | + } | |
166 | + router.push({ | |
167 | + query: { | |
168 | + categories: route.query.categories, | |
169 | + function: defaultFuncCategory.name, | |
170 | + }, | |
171 | + }); | |
155 | 172 | } |
156 | - } else if (Object.keys(route.query).length === 0) { | |
173 | + } else if ( | |
174 | + Object.keys(route.query).length === 0 && | |
175 | + !route.path.includes("/products/detail") | |
176 | + ) { | |
157 | 177 | // 检查是否有默认的分类 |
158 | 178 | const defaultCategory = categoryStore.list[0]; // 假设第一个分类是默认的 |
159 | 179 | ... | ... |
components/ProductDetail.vue
1 | 1 | <template> |
2 | 2 | <v-container> |
3 | + <div> | |
4 | + <v-breadcrumbs divider="/" style="padding-top: 0"> | |
5 | + <template v-for="(item, index) in items" :key="index"> | |
6 | + <v-breadcrumbs-item | |
7 | + :disabled="item.disabled" | |
8 | + :href="item.href" | |
9 | + :class="{ | |
10 | + 'breadcrumb-disabled': item.disabled, | |
11 | + 'breadcrumb-active': !item.disabled, | |
12 | + }" | |
13 | + > | |
14 | + {{ item.title }} | |
15 | + </v-breadcrumbs-item> | |
16 | + | |
17 | + <!-- 添加分隔符,排除最后一个 item --> | |
18 | + <span v-if="index < items.length - 1" class="breadcrumb-divider" | |
19 | + >/</span | |
20 | + > | |
21 | + </template> | |
22 | + <span class="breadcrumb-divider">/</span> | |
23 | + <span style="margin-left: 5px">{{ info.name }}</span> | |
24 | + </v-breadcrumbs> | |
25 | + </div> | |
3 | 26 | <v-row class="mb-16 ma-0"> |
4 | - <v-col cols="12" sm="5"> | |
27 | + <v-col cols="12" sm="5" class="carousel-container"> | |
5 | 28 | <v-carousel |
6 | - class="tw-float-left" | |
29 | + class="tw-float-left tw-sticky tw-top-[16px]" | |
7 | 30 | height="450" |
8 | 31 | v-model="slide" |
9 | 32 | hide-delimiter-background |
33 | + style="top: 16px" | |
10 | 34 | > |
11 | 35 | <v-carousel-item |
12 | 36 | cover |
... | ... | @@ -18,10 +42,11 @@ |
18 | 42 | </v-carousel-item> |
19 | 43 | </v-carousel> |
20 | 44 | </v-col> |
21 | - <v-col cols="12" sm="7"> | |
22 | - <v-row class="bg-white mb-sm-10 text-h4 font-weight-medium"> | |
45 | + <v-col cols="12" sm="7" class="table-container"> | |
46 | + <!-- <v-row class="bg-white mb-sm-10 text-h4 font-weight-medium"> | |
23 | 47 | <v-col>{{ info.name }}</v-col> |
24 | - </v-row> | |
48 | + </v-row> --> | |
49 | + <h1>{{ info.name }}</h1> | |
25 | 50 | <div class="tw-flex tw-flex-wrap"> |
26 | 51 | <div class="tw-w-1/2"> |
27 | 52 | <span class="tw-leading-[10px] tw-m-[16px]"> |
... | ... | @@ -65,10 +90,15 @@ |
65 | 90 | > |
66 | 91 | </div> |
67 | 92 | </div> |
68 | - <v-table | |
93 | + <!-- <v-table | |
69 | 94 | density="comfortable" |
70 | 95 | class="table1 tw-mt-[32px]" |
71 | 96 | v-if="info.ticketTypes?.length" |
97 | + > --> | |
98 | + <v-table | |
99 | + density="comfortable" | |
100 | + class="table1 tw-mt-[32px] tw-overflow-x-auto" | |
101 | + v-if="info.ticketTypes?.length" | |
72 | 102 | > |
73 | 103 | <thead> |
74 | 104 | <tr class="bg-grey-lighten-3"> |
... | ... | @@ -145,7 +175,7 @@ |
145 | 175 | <v-divider class="tw-mb-[12px]"></v-divider> |
146 | 176 | <div v-html="info.physicalproperty"></div> |
147 | 177 | </div> |
148 | - <div v-if="info.advantage" class="tw-mb-[24px]"> | |
178 | + <div v-if="info.storage" class="tw-mb-[24px]"> | |
149 | 179 | <div class="text-h6">Storage</div> |
150 | 180 | <v-divider class="tw-mb-[12px]"></v-divider> |
151 | 181 | <div v-html="info.storage"></div> |
... | ... | @@ -155,7 +185,7 @@ |
155 | 185 | <v-divider class="tw-mb-[12px]"></v-divider> |
156 | 186 | <div v-html="info.introduction"></div> |
157 | 187 | </div> |
158 | - <div v-if="info.advantage" class="tw-mb-[24px]"> | |
188 | + <div v-if="info.description" class="tw-mb-[24px]"> | |
159 | 189 | <div class="text-h6">Description</div> |
160 | 190 | <v-divider class="tw-mb-[12px]"></v-divider> |
161 | 191 | <div v-html="info.description"></div> |
... | ... | @@ -185,20 +215,116 @@ |
185 | 215 | import type { Product, ProductImage } from "~/type"; |
186 | 216 | import { onMounted, ref } from "vue"; |
187 | 217 | import { useDialogStore } from "~/stores/dialog"; |
218 | +import { useRouter, useRoute } from "vue-router"; | |
219 | +import { useRouteQuery } from "@/stores/route_query"; | |
220 | + | |
221 | +const route = useRoute(); | |
222 | +const router = useRouter(); | |
223 | +const routeQuery = useRouteQuery(); | |
188 | 224 | const dialogStore = useDialogStore(); |
225 | +const href1 = ref("/products"); | |
226 | +const href2 = ref("/products"); | |
227 | +// 定义单个 item 的接口 | |
228 | +interface BreadcrumbItem { | |
229 | + title: string; // 标题 | |
230 | + disabled: boolean; // 是否禁用 | |
231 | + href: string; // 链接地址 | |
232 | +} | |
233 | + | |
234 | +// 示例数据 | |
235 | +const items = ref<BreadcrumbItem[]>([ | |
236 | + { | |
237 | + title: "Products", | |
238 | + disabled: false, | |
239 | + href: "/products", | |
240 | + }, | |
241 | + { | |
242 | + title: "CATEGORY", | |
243 | + disabled: false, | |
244 | + href: href1.value, | |
245 | + }, | |
246 | + { | |
247 | + title: "DEVICE TYPE", | |
248 | + disabled: false, | |
249 | + href: href2.value, | |
250 | + }, | |
251 | + { | |
252 | + title: "FUNCTION", | |
253 | + disabled: false, | |
254 | + href: "/products", | |
255 | + }, | |
256 | + // { | |
257 | + // title: "TITLE", | |
258 | + // disabled: false, | |
259 | + // href: "", | |
260 | + // }, | |
261 | +]); | |
189 | 262 | |
190 | 263 | const props = defineProps<{ |
191 | 264 | info: Product; |
192 | 265 | }>(); |
193 | 266 | const info = props.info; |
194 | - | |
267 | +watchEffect(() => { | |
268 | + if (routeQuery.categories) { | |
269 | + if (!routeQuery.categories.includes("Energy materials")) { | |
270 | + routeQuery.updateFunction("Not specified"); | |
271 | + } | |
272 | + const categories = routeQuery.categories.split(","); | |
273 | + const mainCategory = categories[0].trim(); // 取第一个值 | |
274 | + const subCategoryName = ref( | |
275 | + categories[1] ? categories[1].trim() : "Not specified" | |
276 | + ); // 取第二个值(如果存在) | |
277 | + if (subCategoryName.value == "Accessories & fixtures") { | |
278 | + subCategoryName.value = encodeURIComponent("Accessories & fixtures"); | |
279 | + items.value[2].title = "Accessories & fixtures"; | |
280 | + } else { | |
281 | + items.value[2].title = subCategoryName.value; | |
282 | + } | |
283 | + items.value[1].title = mainCategory; | |
284 | + items.value[1].href = href1.value + "?categories=" + mainCategory; | |
285 | + href1.value = href1.value + "?categories=" + mainCategory; | |
286 | + // items.value[1].title = subCategoryName.value; | |
287 | + href2.value = href1.value + "," + subCategoryName.value; | |
288 | + items.value[2].href = href1.value + "," + subCategoryName.value; | |
289 | + if (routeQuery?.selectedFunction) { | |
290 | + // items.value.push({ | |
291 | + // title: routeQuery.selectedFunction, | |
292 | + // disabled: false, | |
293 | + // href: href2.value + "&function=" + routeQuery.selectedFunction, | |
294 | + // }); | |
295 | + items.value[3].title = routeQuery.selectedFunction; | |
296 | + items.value[3].href = | |
297 | + href2.value + "&function=" + routeQuery.selectedFunction; | |
298 | + // routeQuery.updateFunction(null); | |
299 | + } | |
300 | + } else if (info?.productCrumbsVO?.category1) { | |
301 | + items.value[1].title = info.productCrumbsVO.category1; | |
302 | + items.value[1].href = | |
303 | + href1.value + "?categories=" + info.productCrumbsVO.category1; | |
304 | + href1.value = href1.value + "?categories=" + info.productCrumbsVO.category1; | |
305 | + if (info?.productCrumbsVO?.category2) { | |
306 | + items.value[2].title = info.productCrumbsVO.category2; | |
307 | + href2.value = href1.value + "," + info.productCrumbsVO.category2; | |
308 | + items.value[2].href = href1.value + "," + info.productCrumbsVO.category2; | |
309 | + } | |
310 | + if (info?.productCrumbsVO?.function) { | |
311 | + // items.value.push({ | |
312 | + // title: info.productCrumbsVO.function, | |
313 | + // disabled: false, | |
314 | + // href: href2.value + "&function=" + info.productCrumbsVO.function, | |
315 | + // }); | |
316 | + items.value[3].title = info.productCrumbsVO.function; | |
317 | + items.value[3].href = | |
318 | + href2.value + "&function=" + info.productCrumbsVO.function; | |
319 | + } | |
320 | + } | |
321 | +}); | |
195 | 322 | // onMounted(() => { |
196 | 323 | // dialogStore.updateDialog(true) |
197 | 324 | // }) |
198 | 325 | |
199 | 326 | const tab = ref(0); |
200 | 327 | const slide = ref(0); |
201 | -const router = useRouter(); | |
202 | 328 | </script> |
203 | 329 | |
204 | 330 | <style lang="scss" scoped> |
... | ... | @@ -231,4 +357,42 @@ th { |
231 | 357 | border-bottom: 1px solid #dcdcdc !important; |
232 | 358 | } |
233 | 359 | } |
360 | + | |
361 | +.tw-sticky { | |
362 | + position: sticky; | |
363 | +} | |
364 | + | |
365 | +.carousel-container { | |
366 | + position: relative; | |
367 | +} | |
368 | + | |
369 | +.table-container { | |
370 | + overflow-x: auto; /* 防止表格超出页面宽度 */ | |
371 | +} | |
372 | + | |
373 | +.table1 { | |
374 | + width: 100%; | |
375 | + min-width: 600px; /* 设置表格最小宽度 */ | |
376 | +} | |
377 | + | |
378 | +.tr { | |
379 | + border-bottom: 1px solid #e0e0e0; /* 表格行样式 */ | |
380 | +} | |
381 | + | |
382 | +.headerBorder { | |
383 | + border-bottom: 2px solid #ccc; /* 表头边框 */ | |
384 | +} | |
385 | + | |
386 | +.text-grey-darken-4 { | |
387 | + color: #333; /* 表格文字颜色 */ | |
388 | +} | |
389 | +.breadcrumb-disabled { | |
390 | + color: black; | |
391 | + pointer-events: none; /* 禁用点击 */ | |
392 | + text-decoration: none; /* 移除链接样式 */ | |
393 | +} | |
394 | + | |
395 | +.breadcrumb-active { | |
396 | + color: gray; | |
397 | +} | |
234 | 398 | </style> | ... | ... |
pages/products/detail/[id]/index.vue
... | ... | @@ -11,7 +11,7 @@ import type { Product, ProductImage } from "~/type"; |
11 | 11 | import { useRoute, useRouter } from "vue-router"; |
12 | 12 | |
13 | 13 | const route = useRoute(); |
14 | - | |
14 | +const router = useRouter(); | |
15 | 15 | const info = ref<Partial<Product>>({ |
16 | 16 | productimageliststore: [], |
17 | 17 | productAttributeList: [], |
... | ... | @@ -44,22 +44,17 @@ watchEffect(() => { |
44 | 44 | }, |
45 | 45 | { |
46 | 46 | name: "keywords", |
47 | - content: | |
48 | - info.value.name || | |
49 | - "科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines", | |
47 | + content: info.value.name || info.value.metakeywords, | |
50 | 48 | }, |
51 | 49 | { |
52 | 50 | name: "description", |
53 | - content: | |
54 | - info.value.name || | |
55 | - "科路得,助您科研之路势在必得。Equipment Business: With our self-built high-precision machining center, we possess robust design and manufacturing capabilities. We offer comprehensive equipment supply, production line planning, and construction services, including battery assembly lines, pouch cell testing lines, and more. Our aim is to provide complete equipment solutions that cater to the diverse needs of our clients. Expect top-quality equipment and professional services that will help you stand out in a fiercely competitive market!", | |
51 | + content: info.value.metadescription || info.value.name, | |
56 | 52 | }, |
57 | 53 | ], |
58 | 54 | }); |
59 | 55 | }); |
60 | 56 | |
61 | 57 | const newData: Product = resData.value.data; |
62 | - | |
63 | 58 | newData.productimageliststore = |
64 | 59 | typeof newData.productimageliststore === "string" |
65 | 60 | ? JSON.parse(newData.productimageliststore) || [] | ... | ... |
pages/products/index.vue
... | ... | @@ -86,9 +86,21 @@ const productStore = useProductListStore(); |
86 | 86 | const categoryStore = useCategoryStore(); |
87 | 87 | const loading = ref(false); |
88 | 88 | const route = useRoute(); // 获取路由信息 |
89 | +const router = useRouter(); // 获取路由信息 | |
90 | +const title = ref(""); | |
91 | +watchEffect(() => { | |
92 | + // 遍历数组 | |
93 | + if (router.currentRoute.value.query.categories) { | |
94 | + title.value = `${router.currentRoute.value.query.categories}`; | |
95 | + } | |
96 | + if (router.currentRoute.value.query.function) { | |
97 | + title.value += `,${router.currentRoute.value.query.function}`; | |
98 | + } | |
99 | + // document.title = title.value; | |
100 | +}); | |
89 | 101 | |
90 | 102 | useHead({ |
91 | - title: "canrud", | |
103 | + title: title.value, | |
92 | 104 | meta: [ |
93 | 105 | { |
94 | 106 | name: "title", |
... | ... | @@ -109,7 +121,6 @@ useHead({ |
109 | 121 | }); |
110 | 122 | |
111 | 123 | const loadProducts = async () => { |
112 | - console.log(productStore, "5656productStore"); | |
113 | 124 | let params: any = { |
114 | 125 | pageNo: productStore.pageNo, |
115 | 126 | pageSize: 20, | ... | ... |
stores/product_list.ts
... | ... | @@ -32,7 +32,7 @@ export const useProductListStore = defineStore("productList", () => { |
32 | 32 | imgList: JSON.parse( |
33 | 33 | record.productimageliststore as unknown as string |
34 | 34 | ).map((item: ProductImage) => ({ |
35 | - url: `/api/show/image?fileKey=${item.fileKey}&psize=p512`, | |
35 | + url: `/api/show/image?fileKey=${item.fileKey}&psize=p256`, | |
36 | 36 | })), |
37 | 37 | })) || []; |
38 | 38 | total.value = data.value?.data?.total || 0; | ... | ... |
stores/route_query.ts
0 → 100644
1 | +import { ref } from "vue"; | |
2 | +import { defineStore } from "pinia"; | |
3 | + | |
4 | +export const useRouteQuery = defineStore("routeQuery", () => { | |
5 | + const categories = ref(); | |
6 | + const selectedFunction = ref(); | |
7 | + | |
8 | + const updateCategories = (value: string) => { | |
9 | + categories.value = value; | |
10 | + }; | |
11 | + | |
12 | + const updateFunction = (value: string | null) => { | |
13 | + selectedFunction.value = value; | |
14 | + }; | |
15 | + | |
16 | + return { | |
17 | + categories, | |
18 | + selectedFunction, | |
19 | + updateCategories, | |
20 | + updateFunction, | |
21 | + }; | |
22 | +}); | ... | ... |