Commit 70a1f94e251c21ee9f7cb97bd9018988393a79df
1 parent
0b26f917
feat: 开发手机端热销,prod2-35/36
Showing
6 changed files
with
564 additions
and
100 deletions
components/MobileProductDetail.vue
@@ -259,6 +259,158 @@ | @@ -259,6 +259,158 @@ | ||
259 | <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> | 259 | <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> |
260 | </v-window> | 260 | </v-window> |
261 | </div> | 261 | </div> |
262 | + <div class="tw-pb-[64px] ma-4 rounded-lg" style="padding-bottom: 30px"> | ||
263 | + <v-tabs | ||
264 | + class="tabs" | ||
265 | + v-model="tabProduct" | ||
266 | + bg-color="#fff" | ||
267 | + slider-color="#1d89e4" | ||
268 | + selected-class="active" | ||
269 | + style="margin-bottom: 20px" | ||
270 | + > | ||
271 | + <v-tab :value="1">Related Products</v-tab> | ||
272 | + <v-tab :value="2">Best Sellers</v-tab> | ||
273 | + <!-- <v-tab :value="3">商品百科</v-tab> --> | ||
274 | + </v-tabs> | ||
275 | + <v-window v-model="tabProduct"> | ||
276 | + <v-window-item key="1" :value="1"> | ||
277 | + <div class="tw-text-center" v-if="loading && isMobile()"> | ||
278 | + <v-progress-circular | ||
279 | + color="blue-lighten-2" | ||
280 | + indeterminate | ||
281 | + size="64" | ||
282 | + class="tw-m-auto" | ||
283 | + ></v-progress-circular> | ||
284 | + </div> | ||
285 | + <v-item-group multiple v-if="isMobile()"> | ||
286 | + <v-row v-if="!loading"> | ||
287 | + <v-col | ||
288 | + v-for="(item, i) in recommendImages" | ||
289 | + :key="i" | ||
290 | + cols="6" | ||
291 | + lg="3" | ||
292 | + md="4" | ||
293 | + sm="6" | ||
294 | + > | ||
295 | + <div v-if="item !== null"> | ||
296 | + <v-card | ||
297 | + :elevation="4" | ||
298 | + class="mx-auto" | ||
299 | + :href="item[0].productUrl" | ||
300 | + > | ||
301 | + <!-- 设置 eager 属性,确保图片直接加载 --> | ||
302 | + <v-img | ||
303 | + :src="item[0].url" | ||
304 | + :alt="item[0].name" | ||
305 | + eager | ||
306 | + class="d-block" | ||
307 | + /> | ||
308 | + <v-card-text class="tw-text-left font-weight-medium title"> | ||
309 | + <h4>{{ item[0].name }}</h4> | ||
310 | + </v-card-text> | ||
311 | + </v-card> | ||
312 | + </div> | ||
313 | + </v-col> | ||
314 | + </v-row> | ||
315 | + <!-- <div | ||
316 | + v-if="!hotTotal" | ||
317 | + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" | ||
318 | + > | ||
319 | + no data | ||
320 | + </div> --> | ||
321 | + </v-item-group> | ||
322 | + <v-row v-if="isMobile()"> | ||
323 | + <v-col> | ||
324 | + <v-pagination | ||
325 | + :size="isMobile() ? 'small' : 'default'" | ||
326 | + v-if="total" | ||
327 | + v-model="currentIndex" | ||
328 | + @update:modelValue="toggleRowMobile" | ||
329 | + :length="length" | ||
330 | + rounded="0" | ||
331 | + class="tw-float-right tw-mt-[32px]" | ||
332 | + total-visible="5" | ||
333 | + ></v-pagination></v-col | ||
334 | + ></v-row> | ||
335 | + </v-window-item> | ||
336 | + <!-- best sellers --> | ||
337 | + <v-window-item key="2" :value="2"> | ||
338 | + <div class="tw-text-center" v-if="hotLoading && isMobile()"> | ||
339 | + <v-progress-circular | ||
340 | + color="blue-lighten-2" | ||
341 | + indeterminate | ||
342 | + size="64" | ||
343 | + class="tw-m-auto" | ||
344 | + ></v-progress-circular> | ||
345 | + </div> | ||
346 | + <v-item-group multiple v-if="isMobile()"> | ||
347 | + <v-row v-if="!hotLoading"> | ||
348 | + <v-col | ||
349 | + v-for="(item, i) in recommendImagesHot" | ||
350 | + :key="i" | ||
351 | + cols="6" | ||
352 | + lg="3" | ||
353 | + md="4" | ||
354 | + sm="6" | ||
355 | + > | ||
356 | + <div v-if="item !== null"> | ||
357 | + <v-card | ||
358 | + :elevation="4" | ||
359 | + class="mx-auto" | ||
360 | + :href="item[0].productUrl" | ||
361 | + > | ||
362 | + <!-- 设置 eager 属性,确保图片直接加载 --> | ||
363 | + <v-img | ||
364 | + :src="item[0].url" | ||
365 | + :alt="item[0].name" | ||
366 | + eager | ||
367 | + class="d-block" | ||
368 | + /> | ||
369 | + <v-card-text class="tw-text-left font-weight-medium title"> | ||
370 | + <h4>{{ item[0].name }}</h4> | ||
371 | + </v-card-text> | ||
372 | + </v-card> | ||
373 | + </div> | ||
374 | + </v-col> | ||
375 | + </v-row> | ||
376 | + <!-- <div | ||
377 | + v-if="!hotTotal" | ||
378 | + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" | ||
379 | + > | ||
380 | + no data | ||
381 | + </div> --> | ||
382 | + </v-item-group> | ||
383 | + <v-row v-if="isMobile()"> | ||
384 | + <v-col> | ||
385 | + <v-pagination | ||
386 | + :size="isMobile() ? 'small' : 'default'" | ||
387 | + v-if="hotTotal" | ||
388 | + v-model="currentIndexHot" | ||
389 | + @update:modelValue="toggleRowMobileHot" | ||
390 | + :length="hotLength" | ||
391 | + rounded="0" | ||
392 | + class="tw-float-right tw-mt-[32px]" | ||
393 | + total-visible="5" | ||
394 | + ></v-pagination></v-col | ||
395 | + ></v-row> | ||
396 | + </v-window-item> | ||
397 | + <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> | ||
398 | + </v-window> | ||
399 | + </div> | ||
400 | + <!-- <div class="tw-pb-[64px] ma-4 rounded-lg"> | ||
401 | + <v-tabs | ||
402 | + class="tabs" | ||
403 | + v-model="tabJournal" | ||
404 | + bg-color="#fff" | ||
405 | + slider-color="#1d89e4" | ||
406 | + selected-class="active" | ||
407 | + > | ||
408 | + <v-tab :value="1">JOURNAL RECOMMENDATION</v-tab> | ||
409 | + </v-tabs> | ||
410 | + <v-window v-model="tab"> | ||
411 | + <v-window-item key="1" :value="1"> </v-window-item> | ||
412 | + </v-window> | ||
413 | + </div> --> | ||
262 | <!-- Basic use --> | 414 | <!-- Basic use --> |
263 | <div class="social-share-container"> | 415 | <div class="social-share-container"> |
264 | <SocialShare | 416 | <SocialShare |
@@ -293,7 +445,8 @@ | @@ -293,7 +445,8 @@ | ||
293 | 445 | ||
294 | <script setup lang="ts"> | 446 | <script setup lang="ts"> |
295 | import type { Product } from "~/type"; | 447 | import type { Product } from "~/type"; |
296 | -import { ref } from "vue"; | 448 | +import { isMobile } from "~/utils"; |
449 | +import { defineProps, ref } from "vue"; | ||
297 | import { useDialogStore } from "~/stores/dialog"; | 450 | import { useDialogStore } from "~/stores/dialog"; |
298 | const dialogStore = useDialogStore(); | 451 | const dialogStore = useDialogStore(); |
299 | 452 | ||
@@ -305,6 +458,8 @@ const info = props.info; | @@ -305,6 +458,8 @@ const info = props.info; | ||
305 | // dialogStore.updateDialog(true) | 458 | // dialogStore.updateDialog(true) |
306 | // }) | 459 | // }) |
307 | const tab = ref(0); | 460 | const tab = ref(0); |
461 | +const tabProduct = ref(0); | ||
462 | +const tabJournal = ref(0); | ||
308 | const slide = ref(0); | 463 | const slide = ref(0); |
309 | const router = useRouter(); | 464 | const router = useRouter(); |
310 | const href1 = ref("/products"); | 465 | const href1 = ref("/products"); |
@@ -312,6 +467,143 @@ const href2 = ref("/products"); | @@ -312,6 +467,143 @@ const href2 = ref("/products"); | ||
312 | const routeQuery = useRouteQuery(); | 467 | const routeQuery = useRouteQuery(); |
313 | const productStore = useProductListStore(); | 468 | const productStore = useProductListStore(); |
314 | const currentUrl = ref("https://www.canrud.com/products"); | 469 | const currentUrl = ref("https://www.canrud.com/products"); |
470 | +const hotLoading = ref(false); | ||
471 | +const loading = ref(false); | ||
472 | +const maxPage = ref(1); | ||
473 | +const maxPageRe = ref(1); | ||
474 | +const tabRecom = ref(); | ||
475 | +const recommendList = ref(); | ||
476 | +const recommendListHot = ref(); | ||
477 | +const recommendImages = ref(); | ||
478 | +const recommendImagesHot = ref(); | ||
479 | +const currentIndex = ref(1); | ||
480 | +const currentIndexHot = ref(1); | ||
481 | +const total = ref(3); | ||
482 | +const hotTotal = ref(10); | ||
483 | +const isOrNotMobile = isMobile(); | ||
484 | + | ||
485 | +const loadProducts = async () => { | ||
486 | + const pageSize = 4; | ||
487 | + loading.value = true; | ||
488 | + | ||
489 | + let { data: resData } = await useAsyncData( | ||
490 | + "list", | ||
491 | + () => | ||
492 | + $fetch("/shop/product/list", { | ||
493 | + method: "GET", | ||
494 | + params: { | ||
495 | + pageNo: currentIndex.value, | ||
496 | + pageSize: pageSize, | ||
497 | + ids: info.relatedProductIds, | ||
498 | + }, | ||
499 | + }), | ||
500 | + { | ||
501 | + server: true, // 仅在服务器端获取数据 | ||
502 | + } | ||
503 | + ); | ||
504 | + total.value = resData.value.data.total; | ||
505 | + if (total.value > 30) { | ||
506 | + total.value = 30; | ||
507 | + } | ||
508 | + recommendList.value = resData.value.data.records; | ||
509 | + maxPageRe.value = resData.value.data.pages; | ||
510 | + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { | ||
511 | + recommendImages.value = Array.from({ length: 4 }).map((_, index) => { | ||
512 | + const item = recommendList.value[index]; | ||
513 | + if (!item) { | ||
514 | + return null; | ||
515 | + } | ||
516 | + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
517 | + if (typeof item.productimageliststore === "string") { | ||
518 | + try { | ||
519 | + item.productimageliststore = JSON.parse(item.productimageliststore); | ||
520 | + } catch (error) { | ||
521 | + item.productimageliststore = []; // 解析失败时,设置为空数组 | ||
522 | + } | ||
523 | + } | ||
524 | + const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
525 | + (productItem: ProductImage) => ({ | ||
526 | + ...productItem, | ||
527 | + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
528 | + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
529 | + name: item.name, | ||
530 | + productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
531 | + }) | ||
532 | + )); | ||
533 | + return ree; | ||
534 | + }); | ||
535 | + loading.value = false; | ||
536 | +}; | ||
537 | +const loadHotProducts = async () => { | ||
538 | + const pageSizeHot = ref(5); | ||
539 | + if (isOrNotMobile) { | ||
540 | + pageSizeHot.value = 4; | ||
541 | + } | ||
542 | + hotLoading.value = true; | ||
543 | + | ||
544 | + let { data: hotProducts } = await useAsyncData( | ||
545 | + "hotProducts", | ||
546 | + () => | ||
547 | + $fetch("/shop/product/hotProducts", { | ||
548 | + method: "GET", | ||
549 | + params: { | ||
550 | + pageNo: currentIndexHot.value, | ||
551 | + pageSize: pageSizeHot.value, | ||
552 | + }, | ||
553 | + }), | ||
554 | + { | ||
555 | + server: true, // 仅在服务器端获取数据 | ||
556 | + } | ||
557 | + ); | ||
558 | + hotTotal.value = hotProducts.value.data.total; | ||
559 | + recommendListHot.value = hotProducts.value.data.records; | ||
560 | + maxPage.value = hotProducts.value.data.pages; | ||
561 | + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { | ||
562 | + recommendImagesHot.value = Array.from({ length: pageSizeHot.value }).map( | ||
563 | + (_, index) => { | ||
564 | + const item = recommendListHot.value[index]; | ||
565 | + if (!item) { | ||
566 | + return null; | ||
567 | + } | ||
568 | + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
569 | + if (typeof item.productimageliststore === "string") { | ||
570 | + try { | ||
571 | + item.productimageliststore = JSON.parse(item.productimageliststore); | ||
572 | + } catch (error) { | ||
573 | + item.productimageliststore = []; // 解析失败时,设置为空数组 | ||
574 | + } | ||
575 | + } | ||
576 | + const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
577 | + (productItem: ProductImage) => ({ | ||
578 | + ...productItem, | ||
579 | + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
580 | + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
581 | + name: item.name, | ||
582 | + productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
583 | + }) | ||
584 | + )); | ||
585 | + return ree; | ||
586 | + } | ||
587 | + ); | ||
588 | + hotLoading.value = false; | ||
589 | +}; | ||
590 | + | ||
591 | +const toggleRowMobile = (value: number) => { | ||
592 | + currentIndex.value = value; | ||
593 | +}; | ||
594 | +const toggleRowMobileHot = (value: number) => { | ||
595 | + currentIndexHot.value = value; | ||
596 | +}; | ||
597 | + | ||
598 | +watch(currentIndexHot, (newIndex) => { | ||
599 | + loadHotProducts(); // Call loadHotProducts when currentIndex changes | ||
600 | +}); | ||
601 | +watch(currentIndex, (newIndex) => { | ||
602 | + loadProducts(); // Call loadHotProducts when currentIndex changes | ||
603 | +}); | ||
604 | +// Initial load of hot products | ||
605 | +await loadHotProducts(); // Load hot products the first time | ||
606 | +await loadProducts(); // Load hot products the first time | ||
315 | 607 | ||
316 | interface BreadcrumbItem { | 608 | interface BreadcrumbItem { |
317 | title: string; // 标题 | 609 | title: string; // 标题 |
@@ -332,12 +624,12 @@ const items = ref<BreadcrumbItem[]>([ | @@ -332,12 +624,12 @@ const items = ref<BreadcrumbItem[]>([ | ||
332 | href: href1.value, | 624 | href: href1.value, |
333 | }, | 625 | }, |
334 | { | 626 | { |
335 | - title: "DEVICE TYPE", | 627 | + title: "Not specified", |
336 | disabled: false, | 628 | disabled: false, |
337 | href: href2.value, | 629 | href: href2.value, |
338 | }, | 630 | }, |
339 | { | 631 | { |
340 | - title: "FUNCTION", | 632 | + title: "Not specified", |
341 | disabled: false, | 633 | disabled: false, |
342 | href: "/products", | 634 | href: "/products", |
343 | }, | 635 | }, |
@@ -424,42 +716,11 @@ watchEffect(() => { | @@ -424,42 +716,11 @@ watchEffect(() => { | ||
424 | href2.value + "&function=" + info.productCrumbsVO.function; | 716 | href2.value + "&function=" + info.productCrumbsVO.function; |
425 | } | 717 | } |
426 | } | 718 | } |
427 | - // recommendList.value = resData.value.data.records; | ||
428 | - | ||
429 | - // // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { | ||
430 | - // recommendImages.value = Array.from({ length: 10 }).map((_, index) => { | ||
431 | - // const item = recommendList.value[index]; | ||
432 | - // if (!item) { | ||
433 | - // return null; | ||
434 | - // } | ||
435 | - // // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
436 | - // if (typeof item.productimageliststore === "string") { | ||
437 | - // try { | ||
438 | - // item.productimageliststore = JSON.parse(item.productimageliststore); | ||
439 | - // } catch (error) { | ||
440 | - // item.productimageliststore = []; // 解析失败时,设置为空数组 | ||
441 | - // } | ||
442 | - // } | ||
443 | - // const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
444 | - // (productItem: ProductImage) => ({ | ||
445 | - // ...productItem, | ||
446 | - // // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
447 | - // url: `/api/show/image?fileKey=${productItem.fileKey}&psize=p512`, | ||
448 | - // name: item.name, | ||
449 | - // productUrl: `http://www.canrud.com/products/detail/${item.id}`, | ||
450 | - // }) | ||
451 | - // )); | ||
452 | - // return ree; | ||
453 | - // }); | ||
454 | }); | 719 | }); |
455 | -function getTitle(network) { | ||
456 | - const titles = { | ||
457 | - facebook: "Facebook", | ||
458 | - twitter: "Twitter", | ||
459 | - linkedin: "LinkedIn", | ||
460 | - }; | ||
461 | - return titles[network] || "Share"; | ||
462 | -} | 720 | +const length = computed(() => (total.value ? Math.ceil(total.value / 4) : 0)); |
721 | +const hotLength = computed(() => | ||
722 | + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0 | ||
723 | +); | ||
463 | </script> | 724 | </script> |
464 | 725 | ||
465 | <style lang="scss" scoped> | 726 | <style lang="scss" scoped> |
@@ -470,6 +731,13 @@ function getTitle(network) { | @@ -470,6 +731,13 @@ function getTitle(network) { | ||
470 | .active { | 731 | .active { |
471 | background-color: #fff; | 732 | background-color: #fff; |
472 | } | 733 | } |
734 | +// .tabs { | ||
735 | +// border-bottom: 2px solid #1f88e5; | ||
736 | +// } | ||
737 | + | ||
738 | +// .active { | ||
739 | +// background-color: #1086e8; | ||
740 | +// } | ||
473 | 741 | ||
474 | .tw-sticky { | 742 | .tw-sticky { |
475 | position: sticky; | 743 | position: sticky; |
@@ -512,4 +780,7 @@ function getTitle(network) { | @@ -512,4 +780,7 @@ function getTitle(network) { | ||
512 | font-size: 1rem; | 780 | font-size: 1rem; |
513 | font-weight: bold; | 781 | font-weight: bold; |
514 | } | 782 | } |
783 | +.v-card { | ||
784 | + transition: all 0.3s ease-in-out; | ||
785 | +} | ||
515 | </style> | 786 | </style> |
components/ProductDetail.vue
@@ -262,7 +262,7 @@ | @@ -262,7 +262,7 @@ | ||
262 | </div> | 262 | </div> |
263 | </div> | 263 | </div> |
264 | <div style="display: flex"> | 264 | <div style="display: flex"> |
265 | - <div class="tw-pb-[64px]" style="width: 70%; margin-right: 10px;"> | 265 | + <div class="tw-pb-[64px]" style="width: 70%; margin-right: 10px"> |
266 | <v-tabs | 266 | <v-tabs |
267 | class="tabs" | 267 | class="tabs" |
268 | v-model="tab" | 268 | v-model="tab" |
@@ -275,7 +275,11 @@ | @@ -275,7 +275,11 @@ | ||
275 | <v-tab :value="2">Specification</v-tab> | 275 | <v-tab :value="2">Specification</v-tab> |
276 | <!-- <v-tab :value="3">商品百科</v-tab> --> | 276 | <!-- <v-tab :value="3">商品百科</v-tab> --> |
277 | </v-tabs> | 277 | </v-tabs> |
278 | - <v-window v-model="tab" class="tw-p-[24px]" style="min-height: 500px; height: auto;" > | 278 | + <v-window |
279 | + v-model="tab" | ||
280 | + class="tw-p-[24px]" | ||
281 | + style="min-height: 500px; height: auto" | ||
282 | + > | ||
279 | <v-window-item key="1" :value="1"> | 283 | <v-window-item key="1" :value="1"> |
280 | <div v-if="info.advantage" class="tw-mb-[24px]"> | 284 | <div v-if="info.advantage" class="tw-mb-[24px]"> |
281 | <div class="text-h6">Advantage</div> | 285 | <div class="text-h6">Advantage</div> |
@@ -559,17 +563,17 @@ const loadHotProducts = async () => { | @@ -559,17 +563,17 @@ const loadHotProducts = async () => { | ||
559 | }); | 563 | }); |
560 | }; | 564 | }; |
561 | const toggleRowLeft = () => { | 565 | const toggleRowLeft = () => { |
562 | - if (currentIndex.value !== 1) { | ||
563 | - currentIndex.value--; | ||
564 | - } else if (currentIndex.value == 1) { | ||
565 | - currentIndex.value = maxPage.value; | 566 | + if (currentIndexHot.value !== 1) { |
567 | + currentIndexHot.value--; | ||
568 | + } else if (currentIndexHot.value == 1) { | ||
569 | + currentIndexHot.value = maxPage.value; | ||
566 | } | 570 | } |
567 | }; | 571 | }; |
568 | const toggleRowRight = () => { | 572 | const toggleRowRight = () => { |
569 | - if (currentIndex.value < maxPage.value) { | ||
570 | - currentIndex.value++; | ||
571 | - } else if (currentIndex.value == maxPage.value) { | ||
572 | - currentIndex.value = 1; | 573 | + if (currentIndexHot.value < maxPage.value) { |
574 | + currentIndexHot.value++; | ||
575 | + } else if (currentIndexHot.value == maxPage.value) { | ||
576 | + currentIndexHot.value = 1; | ||
573 | } | 577 | } |
574 | }; | 578 | }; |
575 | watch(currentIndexHot, (newIndex) => { | 579 | watch(currentIndexHot, (newIndex) => { |
deploy/prod2.sh
1 | #!/bin/bash | 1 | #!/bin/bash |
2 | # 变量定义 | 2 | # 变量定义 |
3 | -LAST_TAG="1.0.34" | ||
4 | -TAG="1.0.35" | 3 | +LAST_TAG="1.0.35" |
4 | +TAG="1.0.36" | ||
5 | TARGET_PATH="/root/web/canrud-outside-nuxt-front" | 5 | TARGET_PATH="/root/web/canrud-outside-nuxt-front" |
6 | DOCKERFILE_PATH="/root/web/canrud-outside-nuxt-front/canrud-nuxt-front" | 6 | DOCKERFILE_PATH="/root/web/canrud-outside-nuxt-front/canrud-nuxt-front" |
7 | IMAGE_NAME="canrud-outside-front" | 7 | IMAGE_NAME="canrud-outside-front" |
pages/index.vue
@@ -144,6 +144,76 @@ | @@ -144,6 +144,76 @@ | ||
144 | /> | 144 | /> |
145 | </div> | 145 | </div> |
146 | </div> | 146 | </div> |
147 | + <div style="padding-left: 16px; padding-right: 16px; padding-bottom: 30px"> | ||
148 | + <v-tabs | ||
149 | + class="tabs2" | ||
150 | + ref="tabs2" | ||
151 | + v-model="tabRecom" | ||
152 | + style="margin-top: 25px; margin-bottom: 20px; width: 100%" | ||
153 | + color="white" | ||
154 | + bg-color="#eeeeee" | ||
155 | + slider-color="blue-lighten-1" | ||
156 | + selected-class="active" | ||
157 | + v-if="isMobile()" | ||
158 | + > | ||
159 | + <v-tab :value="1">Best Sellers</v-tab> | ||
160 | + <!-- <v-tab :value="3">商品百科</v-tab> --> | ||
161 | + </v-tabs> | ||
162 | + <div class="tw-text-center" v-if="hotLoading && isMobile()"> | ||
163 | + <v-progress-circular | ||
164 | + color="blue-lighten-2" | ||
165 | + indeterminate | ||
166 | + size="64" | ||
167 | + class="tw-m-auto" | ||
168 | + ></v-progress-circular> | ||
169 | + </div> | ||
170 | + <v-item-group multiple v-if="isMobile()"> | ||
171 | + <v-row v-if="!hotLoading"> | ||
172 | + <v-col | ||
173 | + v-for="(item, i) in recommendImages" | ||
174 | + :key="i" | ||
175 | + cols="6" | ||
176 | + lg="3" | ||
177 | + md="4" | ||
178 | + sm="6" | ||
179 | + > | ||
180 | + <div v-if="item !== null"> | ||
181 | + <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl"> | ||
182 | + <!-- 设置 eager 属性,确保图片直接加载 --> | ||
183 | + <v-img | ||
184 | + :src="item[0].url" | ||
185 | + :alt="item[0].name" | ||
186 | + eager | ||
187 | + class="d-block" | ||
188 | + /> | ||
189 | + <v-card-text class="tw-text-left font-weight-medium title"> | ||
190 | + <h4>{{ item[0].name }}</h4> | ||
191 | + </v-card-text> | ||
192 | + </v-card> | ||
193 | + </div> | ||
194 | + </v-col> | ||
195 | + </v-row> | ||
196 | + <!-- <div | ||
197 | + v-if="!hotTotal" | ||
198 | + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" | ||
199 | + > | ||
200 | + no data | ||
201 | + </div> --> | ||
202 | + </v-item-group> | ||
203 | + <v-row v-if="isMobile()"> | ||
204 | + <v-col> | ||
205 | + <v-pagination | ||
206 | + :size="isMobile() ? 'small' : 'default'" | ||
207 | + v-if="hotTotal" | ||
208 | + v-model="currentIndex" | ||
209 | + @update:modelValue="toggleRowMobile" | ||
210 | + :length="hotLength" | ||
211 | + rounded="0" | ||
212 | + class="tw-float-right tw-mt-[32px]" | ||
213 | + total-visible="5" | ||
214 | + ></v-pagination></v-col | ||
215 | + ></v-row> | ||
216 | + </div> | ||
147 | </template> | 217 | </template> |
148 | 218 | ||
149 | <script setup lang="ts"> | 219 | <script setup lang="ts"> |
@@ -158,8 +228,16 @@ const tabRecom = ref(); | @@ -158,8 +228,16 @@ const tabRecom = ref(); | ||
158 | const recommendList = ref(); | 228 | const recommendList = ref(); |
159 | const recommendImages = ref(); | 229 | const recommendImages = ref(); |
160 | const currentIndex = ref(1); | 230 | const currentIndex = ref(1); |
161 | -const flag = ref(false); | 231 | +const hotLoading = ref(false); |
232 | +const hotTotal = ref(10); | ||
233 | +const isOrNotMobile = isMobile(); | ||
162 | const loadHotProducts = async () => { | 234 | const loadHotProducts = async () => { |
235 | + const pageSize = ref(5); | ||
236 | + if (isOrNotMobile) { | ||
237 | + pageSize.value = 4; | ||
238 | + } | ||
239 | + hotLoading.value = true; | ||
240 | + | ||
163 | let { data: hotProducts } = await useAsyncData( | 241 | let { data: hotProducts } = await useAsyncData( |
164 | "hotProducts", | 242 | "hotProducts", |
165 | () => | 243 | () => |
@@ -167,41 +245,46 @@ const loadHotProducts = async () => { | @@ -167,41 +245,46 @@ const loadHotProducts = async () => { | ||
167 | method: "GET", | 245 | method: "GET", |
168 | params: { | 246 | params: { |
169 | pageNo: currentIndex.value, | 247 | pageNo: currentIndex.value, |
170 | - pageSize: 5, | 248 | + pageSize: pageSize.value, |
171 | }, | 249 | }, |
172 | }), | 250 | }), |
173 | { | 251 | { |
174 | server: true, // 仅在服务器端获取数据 | 252 | server: true, // 仅在服务器端获取数据 |
175 | } | 253 | } |
176 | ); | 254 | ); |
255 | + hotTotal.value = hotProducts.value.data.total; | ||
177 | recommendList.value = hotProducts.value.data.records; | 256 | recommendList.value = hotProducts.value.data.records; |
178 | maxPage.value = hotProducts.value.data.pages; | 257 | maxPage.value = hotProducts.value.data.pages; |
179 | // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { | 258 | // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { |
180 | - recommendImages.value = Array.from({ length: 5 }).map((_, index) => { | ||
181 | - const item = recommendList.value[index]; | ||
182 | - if (!item) { | ||
183 | - return null; | ||
184 | - } | ||
185 | - // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
186 | - if (typeof item.productimageliststore === "string") { | ||
187 | - try { | ||
188 | - item.productimageliststore = JSON.parse(item.productimageliststore); | ||
189 | - } catch (error) { | ||
190 | - item.productimageliststore = []; // 解析失败时,设置为空数组 | 259 | + recommendImages.value = Array.from({ length: pageSize.value }).map( |
260 | + (_, index) => { | ||
261 | + const item = recommendList.value[index]; | ||
262 | + if (!item) { | ||
263 | + return null; | ||
191 | } | 264 | } |
265 | + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
266 | + if (typeof item.productimageliststore === "string") { | ||
267 | + try { | ||
268 | + item.productimageliststore = JSON.parse(item.productimageliststore); | ||
269 | + } catch (error) { | ||
270 | + item.productimageliststore = []; // 解析失败时,设置为空数组 | ||
271 | + } | ||
272 | + } | ||
273 | + const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
274 | + (productItem: ProductImage) => ({ | ||
275 | + ...productItem, | ||
276 | + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
277 | + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
278 | + name: item.name, | ||
279 | + productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
280 | + }) | ||
281 | + )); | ||
282 | + return ree; | ||
192 | } | 283 | } |
193 | - const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
194 | - (productItem: ProductImage) => ({ | ||
195 | - ...productItem, | ||
196 | - // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
197 | - url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
198 | - name: item.name, | ||
199 | - productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
200 | - }) | ||
201 | - )); | ||
202 | - return ree; | ||
203 | - }); | 284 | + ); |
285 | + hotLoading.value = false; | ||
204 | }; | 286 | }; |
287 | + | ||
205 | const toggleRowLeft = () => { | 288 | const toggleRowLeft = () => { |
206 | if (currentIndex.value !== 1) { | 289 | if (currentIndex.value !== 1) { |
207 | currentIndex.value--; | 290 | currentIndex.value--; |
@@ -216,10 +299,13 @@ const toggleRowRight = () => { | @@ -216,10 +299,13 @@ const toggleRowRight = () => { | ||
216 | currentIndex.value = 1; | 299 | currentIndex.value = 1; |
217 | } | 300 | } |
218 | }; | 301 | }; |
302 | +const toggleRowMobile = (value: number) => { | ||
303 | + currentIndex.value = value; | ||
304 | +}; | ||
305 | + | ||
219 | watch(currentIndex, (newIndex) => { | 306 | watch(currentIndex, (newIndex) => { |
220 | loadHotProducts(); // Call loadHotProducts when currentIndex changes | 307 | loadHotProducts(); // Call loadHotProducts when currentIndex changes |
221 | }); | 308 | }); |
222 | - | ||
223 | // Initial load of hot products | 309 | // Initial load of hot products |
224 | await loadHotProducts(); // Load hot products the first time | 310 | await loadHotProducts(); // Load hot products the first time |
225 | 311 | ||
@@ -354,6 +440,9 @@ const packs = [ | @@ -354,6 +440,9 @@ const packs = [ | ||
354 | href: "/pack", | 440 | href: "/pack", |
355 | }, | 441 | }, |
356 | ]; | 442 | ]; |
443 | +const hotLength = computed(() => | ||
444 | + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0 | ||
445 | +); | ||
357 | </script> | 446 | </script> |
358 | <style scoped> | 447 | <style scoped> |
359 | @media screen and (min-width: 1537px) { | 448 | @media screen and (min-width: 1537px) { |
@@ -499,4 +588,11 @@ button .recommendButton { | @@ -499,4 +588,11 @@ button .recommendButton { | ||
499 | .recommend-img-right:hover { | 588 | .recommend-img-right:hover { |
500 | cursor: pointer; | 589 | cursor: pointer; |
501 | } | 590 | } |
591 | +.image-grid { | ||
592 | + padding: 16px; | ||
593 | +} | ||
594 | + | ||
595 | +.v-card { | ||
596 | + transition: all 0.3s ease-in-out; | ||
597 | +} | ||
502 | </style> | 598 | </style> |
pages/products/detail/[id]/index.vue
1 | <template> | 1 | <template> |
2 | - <MobileProductDetail v-if="isMobile()" :info="info" /> | 2 | + <MobileProductDetail v-if="isMobile()" :info="info" :res="resData" /> |
3 | <ProductDetail v-else :info="info" /> | 3 | <ProductDetail v-else :info="info" /> |
4 | </template> | 4 | </template> |
5 | 5 | ||
@@ -10,6 +10,7 @@ import MobileProductDetail from "~/components/MobileProductDetail.vue"; | @@ -10,6 +10,7 @@ import MobileProductDetail from "~/components/MobileProductDetail.vue"; | ||
10 | import type { Product, ProductImage } from "~/type"; | 10 | import type { Product, ProductImage } from "~/type"; |
11 | import { useRoute, useRouter } from "vue-router"; | 11 | import { useRoute, useRouter } from "vue-router"; |
12 | 12 | ||
13 | +const productStore = useProductListStore(); | ||
13 | const route = useRoute(); | 14 | const route = useRoute(); |
14 | const router = useRouter(); | 15 | const router = useRouter(); |
15 | const info = ref<Partial<Product>>({ | 16 | const info = ref<Partial<Product>>({ |
pages/products/index.vue
@@ -130,6 +130,74 @@ | @@ -130,6 +130,74 @@ | ||
130 | /> | 130 | /> |
131 | </div> | 131 | </div> |
132 | </div> | 132 | </div> |
133 | + <v-tabs | ||
134 | + class="tabs2" | ||
135 | + ref="tabs2" | ||
136 | + v-model="tabRecom" | ||
137 | + style="margin-top: 25px; margin-bottom: 20px" | ||
138 | + color="white" | ||
139 | + bg-color="#eeeeee" | ||
140 | + slider-color="blue-lighten-1" | ||
141 | + selected-class="active" | ||
142 | + v-if="isMobile()" | ||
143 | + > | ||
144 | + <v-tab :value="1">Best Sellers</v-tab> | ||
145 | + <!-- <v-tab :value="3">商品百科</v-tab> --> | ||
146 | + </v-tabs> | ||
147 | + <div class="tw-text-center" v-if="hotLoading && isMobile()"> | ||
148 | + <v-progress-circular | ||
149 | + color="blue-lighten-2" | ||
150 | + indeterminate | ||
151 | + size="64" | ||
152 | + class="tw-m-auto" | ||
153 | + ></v-progress-circular> | ||
154 | + </div> | ||
155 | + <v-item-group multiple v-if="isMobile()"> | ||
156 | + <v-row v-if="!hotLoading"> | ||
157 | + <v-col | ||
158 | + v-for="(item, i) in recommendImages" | ||
159 | + :key="i" | ||
160 | + cols="6" | ||
161 | + lg="3" | ||
162 | + md="4" | ||
163 | + sm="6" | ||
164 | + > | ||
165 | + <div v-if="item !== null"> | ||
166 | + <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl"> | ||
167 | + <!-- 设置 eager 属性,确保图片直接加载 --> | ||
168 | + <v-img | ||
169 | + :src="item[0].url" | ||
170 | + :alt="item[0].name" | ||
171 | + eager | ||
172 | + class="d-block" | ||
173 | + /> | ||
174 | + <v-card-text class="tw-text-left font-weight-medium title"> | ||
175 | + <h4>{{ item[0].name }}</h4> | ||
176 | + </v-card-text> | ||
177 | + </v-card> | ||
178 | + </div> | ||
179 | + </v-col> | ||
180 | + </v-row> | ||
181 | + <!-- <div | ||
182 | + v-if="!hotTotal" | ||
183 | + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" | ||
184 | + > | ||
185 | + no data | ||
186 | + </div> --> | ||
187 | + </v-item-group> | ||
188 | + <v-row v-if="isMobile()"> | ||
189 | + <v-col> | ||
190 | + <v-pagination | ||
191 | + :size="isMobile() ? 'small' : 'default'" | ||
192 | + v-if="hotTotal" | ||
193 | + v-model="currentIndex" | ||
194 | + @update:modelValue="toggleRowMobile" | ||
195 | + :length="hotLength" | ||
196 | + rounded="0" | ||
197 | + class="tw-float-right tw-mt-[32px]" | ||
198 | + total-visible="5" | ||
199 | + ></v-pagination></v-col | ||
200 | + ></v-row> | ||
133 | </v-container> | 201 | </v-container> |
134 | </div> | 202 | </div> |
135 | </template> | 203 | </template> |
@@ -146,6 +214,7 @@ import { watchEffect, computed, ref } from "vue"; | @@ -146,6 +214,7 @@ import { watchEffect, computed, ref } from "vue"; | ||
146 | const productStore = useProductListStore(); | 214 | const productStore = useProductListStore(); |
147 | const categoryStore = useCategoryStore(); | 215 | const categoryStore = useCategoryStore(); |
148 | const loading = ref(false); | 216 | const loading = ref(false); |
217 | +const hotLoading = ref(false); | ||
149 | const route = useRoute(); // 获取路由信息 | 218 | const route = useRoute(); // 获取路由信息 |
150 | const router = useRouter(); // 获取路由信息 | 219 | const router = useRouter(); // 获取路由信息 |
151 | const title = ref(""); | 220 | const title = ref(""); |
@@ -155,8 +224,16 @@ const tabRecom = ref(); | @@ -155,8 +224,16 @@ const tabRecom = ref(); | ||
155 | const recommendList = ref(); | 224 | const recommendList = ref(); |
156 | const recommendImages = ref(); | 225 | const recommendImages = ref(); |
157 | const currentIndex = ref(1); | 226 | const currentIndex = ref(1); |
227 | +const hotTotal = ref(10); | ||
158 | const isOrNotMobile = isMobile(); | 228 | const isOrNotMobile = isMobile(); |
229 | + | ||
159 | const loadHotProducts = async () => { | 230 | const loadHotProducts = async () => { |
231 | + const pageSize = ref(5); | ||
232 | + if (isOrNotMobile) { | ||
233 | + pageSize.value = 4; | ||
234 | + } | ||
235 | + hotLoading.value = true; | ||
236 | + | ||
160 | let { data: hotProducts } = await useAsyncData( | 237 | let { data: hotProducts } = await useAsyncData( |
161 | "hotProducts", | 238 | "hotProducts", |
162 | () => | 239 | () => |
@@ -164,41 +241,46 @@ const loadHotProducts = async () => { | @@ -164,41 +241,46 @@ const loadHotProducts = async () => { | ||
164 | method: "GET", | 241 | method: "GET", |
165 | params: { | 242 | params: { |
166 | pageNo: currentIndex.value, | 243 | pageNo: currentIndex.value, |
167 | - pageSize: 5, | 244 | + pageSize: pageSize.value, |
168 | }, | 245 | }, |
169 | }), | 246 | }), |
170 | { | 247 | { |
171 | server: true, // 仅在服务器端获取数据 | 248 | server: true, // 仅在服务器端获取数据 |
172 | } | 249 | } |
173 | ); | 250 | ); |
251 | + hotTotal.value = hotProducts.value.data.total; | ||
174 | recommendList.value = hotProducts.value.data.records; | 252 | recommendList.value = hotProducts.value.data.records; |
175 | maxPage.value = hotProducts.value.data.pages; | 253 | maxPage.value = hotProducts.value.data.pages; |
176 | // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { | 254 | // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { |
177 | - recommendImages.value = Array.from({ length: 5 }).map((_, index) => { | ||
178 | - const item = recommendList.value[index]; | ||
179 | - if (!item) { | ||
180 | - return null; | ||
181 | - } | ||
182 | - // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
183 | - if (typeof item.productimageliststore === "string") { | ||
184 | - try { | ||
185 | - item.productimageliststore = JSON.parse(item.productimageliststore); | ||
186 | - } catch (error) { | ||
187 | - item.productimageliststore = []; // 解析失败时,设置为空数组 | 255 | + recommendImages.value = Array.from({ length: pageSize.value }).map( |
256 | + (_, index) => { | ||
257 | + const item = recommendList.value[index]; | ||
258 | + if (!item) { | ||
259 | + return null; | ||
188 | } | 260 | } |
261 | + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 | ||
262 | + if (typeof item.productimageliststore === "string") { | ||
263 | + try { | ||
264 | + item.productimageliststore = JSON.parse(item.productimageliststore); | ||
265 | + } catch (error) { | ||
266 | + item.productimageliststore = []; // 解析失败时,设置为空数组 | ||
267 | + } | ||
268 | + } | ||
269 | + const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
270 | + (productItem: ProductImage) => ({ | ||
271 | + ...productItem, | ||
272 | + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
273 | + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
274 | + name: item.name, | ||
275 | + productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
276 | + }) | ||
277 | + )); | ||
278 | + return ree; | ||
189 | } | 279 | } |
190 | - const ree = (item.productimageliststore = item?.productimageliststore.map( | ||
191 | - (productItem: ProductImage) => ({ | ||
192 | - ...productItem, | ||
193 | - // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, | ||
194 | - url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, | ||
195 | - name: item.name, | ||
196 | - productUrl: `https://www.canrud.com/products/detail/${item.id}`, | ||
197 | - }) | ||
198 | - )); | ||
199 | - return ree; | ||
200 | - }); | 280 | + ); |
281 | + hotLoading.value = false; | ||
201 | }; | 282 | }; |
283 | + | ||
202 | const toggleRowLeft = () => { | 284 | const toggleRowLeft = () => { |
203 | if (currentIndex.value !== 1) { | 285 | if (currentIndex.value !== 1) { |
204 | currentIndex.value--; | 286 | currentIndex.value--; |
@@ -213,10 +295,13 @@ const toggleRowRight = () => { | @@ -213,10 +295,13 @@ const toggleRowRight = () => { | ||
213 | currentIndex.value = 1; | 295 | currentIndex.value = 1; |
214 | } | 296 | } |
215 | }; | 297 | }; |
298 | +const toggleRowMobile = (value: number) => { | ||
299 | + currentIndex.value = value; | ||
300 | +}; | ||
301 | + | ||
216 | watch(currentIndex, (newIndex) => { | 302 | watch(currentIndex, (newIndex) => { |
217 | loadHotProducts(); // Call loadHotProducts when currentIndex changes | 303 | loadHotProducts(); // Call loadHotProducts when currentIndex changes |
218 | }); | 304 | }); |
219 | - | ||
220 | // Initial load of hot products | 305 | // Initial load of hot products |
221 | await loadHotProducts(); // Load hot products the first time | 306 | await loadHotProducts(); // Load hot products the first time |
222 | 307 | ||
@@ -404,6 +489,9 @@ watchEffect(async () => { | @@ -404,6 +489,9 @@ watchEffect(async () => { | ||
404 | const length = computed(() => | 489 | const length = computed(() => |
405 | productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0 | 490 | productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0 |
406 | ); | 491 | ); |
492 | +const hotLength = computed(() => | ||
493 | + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0 | ||
494 | +); | ||
407 | </script> | 495 | </script> |
408 | 496 | ||
409 | <style scoped> | 497 | <style scoped> |
@@ -529,4 +617,8 @@ button .recommendButton { | @@ -529,4 +617,8 @@ button .recommendButton { | ||
529 | .recommend-img-right:hover { | 617 | .recommend-img-right:hover { |
530 | cursor: pointer; | 618 | cursor: pointer; |
531 | } | 619 | } |
620 | + | ||
621 | +.v-card { | ||
622 | + transition: all 0.3s ease-in-out; | ||
623 | +} | ||
532 | </style> | 624 | </style> |