Commit 70a1f94e251c21ee9f7cb97bd9018988393a79df

Authored by boyang
1 parent 0b26f917

feat: 开发手机端热销,prod2-35/36

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(&quot;/products&quot;); @@ -312,6 +467,143 @@ const href2 = ref(&quot;/products&quot;);
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&lt;BreadcrumbItem[]&gt;([ @@ -332,12 +624,12 @@ const items = ref&lt;BreadcrumbItem[]&gt;([
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(() =&gt; { @@ -424,42 +716,11 @@ watchEffect(() =&gt; {
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 () =&gt; { @@ -559,17 +563,17 @@ const loadHotProducts = async () =&gt; {
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 () =&gt; { @@ -167,41 +245,46 @@ const loadHotProducts = async () =&gt; {
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 = () =&gt; { @@ -216,10 +299,13 @@ const toggleRowRight = () =&gt; {
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 &quot;~/components/MobileProductDetail.vue&quot;; @@ -10,6 +10,7 @@ import MobileProductDetail from &quot;~/components/MobileProductDetail.vue&quot;;
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 &quot;vue&quot;; @@ -146,6 +214,7 @@ import { watchEffect, computed, ref } from &quot;vue&quot;;
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 () =&gt; { @@ -164,41 +241,46 @@ const loadHotProducts = async () =&gt; {
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 = () =&gt; { @@ -213,10 +295,13 @@ const toggleRowRight = () =&gt; {
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 () =&gt; { @@ -404,6 +489,9 @@ watchEffect(async () =&gt; {
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>