Commit 70a1f94e251c21ee9f7cb97bd9018988393a79df

Authored by boyang
1 parent 0b26f917

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

components/MobileProductDetail.vue
... ... @@ -259,6 +259,158 @@
259 259 <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
260 260 </v-window>
261 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 414 <!-- Basic use -->
263 415 <div class="social-share-container">
264 416 <SocialShare
... ... @@ -293,7 +445,8 @@
293 445  
294 446 <script setup lang="ts">
295 447 import type { Product } from "~/type";
296   -import { ref } from "vue";
  448 +import { isMobile } from "~/utils";
  449 +import { defineProps, ref } from "vue";
297 450 import { useDialogStore } from "~/stores/dialog";
298 451 const dialogStore = useDialogStore();
299 452  
... ... @@ -305,6 +458,8 @@ const info = props.info;
305 458 // dialogStore.updateDialog(true)
306 459 // })
307 460 const tab = ref(0);
  461 +const tabProduct = ref(0);
  462 +const tabJournal = ref(0);
308 463 const slide = ref(0);
309 464 const router = useRouter();
310 465 const href1 = ref("/products");
... ... @@ -312,6 +467,143 @@ const href2 = ref(&quot;/products&quot;);
312 467 const routeQuery = useRouteQuery();
313 468 const productStore = useProductListStore();
314 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 608 interface BreadcrumbItem {
317 609 title: string; // 标题
... ... @@ -332,12 +624,12 @@ const items = ref&lt;BreadcrumbItem[]&gt;([
332 624 href: href1.value,
333 625 },
334 626 {
335   - title: "DEVICE TYPE",
  627 + title: "Not specified",
336 628 disabled: false,
337 629 href: href2.value,
338 630 },
339 631 {
340   - title: "FUNCTION",
  632 + title: "Not specified",
341 633 disabled: false,
342 634 href: "/products",
343 635 },
... ... @@ -424,42 +716,11 @@ watchEffect(() =&gt; {
424 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 724 </script>
464 725  
465 726 <style lang="scss" scoped>
... ... @@ -470,6 +731,13 @@ function getTitle(network) {
470 731 .active {
471 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 742 .tw-sticky {
475 743 position: sticky;
... ... @@ -512,4 +780,7 @@ function getTitle(network) {
512 780 font-size: 1rem;
513 781 font-weight: bold;
514 782 }
  783 +.v-card {
  784 + transition: all 0.3s ease-in-out;
  785 +}
515 786 </style>
... ...
components/ProductDetail.vue
... ... @@ -262,7 +262,7 @@
262 262 </div>
263 263 </div>
264 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 266 <v-tabs
267 267 class="tabs"
268 268 v-model="tab"
... ... @@ -275,7 +275,11 @@
275 275 <v-tab :value="2">Specification</v-tab>
276 276 <!-- <v-tab :value="3">商品百科</v-tab> -->
277 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 283 <v-window-item key="1" :value="1">
280 284 <div v-if="info.advantage" class="tw-mb-[24px]">
281 285 <div class="text-h6">Advantage</div>
... ... @@ -559,17 +563,17 @@ const loadHotProducts = async () =&gt; {
559 563 });
560 564 };
561 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 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 579 watch(currentIndexHot, (newIndex) => {
... ...
deploy/prod2.sh
1 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 5 TARGET_PATH="/root/web/canrud-outside-nuxt-front"
6 6 DOCKERFILE_PATH="/root/web/canrud-outside-nuxt-front/canrud-nuxt-front"
7 7 IMAGE_NAME="canrud-outside-front"
... ...
pages/index.vue
... ... @@ -144,6 +144,76 @@
144 144 />
145 145 </div>
146 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 217 </template>
148 218  
149 219 <script setup lang="ts">
... ... @@ -158,8 +228,16 @@ const tabRecom = ref();
158 228 const recommendList = ref();
159 229 const recommendImages = ref();
160 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 234 const loadHotProducts = async () => {
  235 + const pageSize = ref(5);
  236 + if (isOrNotMobile) {
  237 + pageSize.value = 4;
  238 + }
  239 + hotLoading.value = true;
  240 +
163 241 let { data: hotProducts } = await useAsyncData(
164 242 "hotProducts",
165 243 () =>
... ... @@ -167,41 +245,46 @@ const loadHotProducts = async () =&gt; {
167 245 method: "GET",
168 246 params: {
169 247 pageNo: currentIndex.value,
170   - pageSize: 5,
  248 + pageSize: pageSize.value,
171 249 },
172 250 }),
173 251 {
174 252 server: true, // 仅在服务器端获取数据
175 253 }
176 254 );
  255 + hotTotal.value = hotProducts.value.data.total;
177 256 recommendList.value = hotProducts.value.data.records;
178 257 maxPage.value = hotProducts.value.data.pages;
179 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 288 const toggleRowLeft = () => {
206 289 if (currentIndex.value !== 1) {
207 290 currentIndex.value--;
... ... @@ -216,10 +299,13 @@ const toggleRowRight = () =&gt; {
216 299 currentIndex.value = 1;
217 300 }
218 301 };
  302 +const toggleRowMobile = (value: number) => {
  303 + currentIndex.value = value;
  304 +};
  305 +
219 306 watch(currentIndex, (newIndex) => {
220 307 loadHotProducts(); // Call loadHotProducts when currentIndex changes
221 308 });
222   -
223 309 // Initial load of hot products
224 310 await loadHotProducts(); // Load hot products the first time
225 311  
... ... @@ -354,6 +440,9 @@ const packs = [
354 440 href: "/pack",
355 441 },
356 442 ];
  443 +const hotLength = computed(() =>
  444 + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0
  445 +);
357 446 </script>
358 447 <style scoped>
359 448 @media screen and (min-width: 1537px) {
... ... @@ -499,4 +588,11 @@ button .recommendButton {
499 588 .recommend-img-right:hover {
500 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 598 </style>
... ...
pages/products/detail/[id]/index.vue
1 1 <template>
2   - <MobileProductDetail v-if="isMobile()" :info="info" />
  2 + <MobileProductDetail v-if="isMobile()" :info="info" :res="resData" />
3 3 <ProductDetail v-else :info="info" />
4 4 </template>
5 5  
... ... @@ -10,6 +10,7 @@ import MobileProductDetail from &quot;~/components/MobileProductDetail.vue&quot;;
10 10 import type { Product, ProductImage } from "~/type";
11 11 import { useRoute, useRouter } from "vue-router";
12 12  
  13 +const productStore = useProductListStore();
13 14 const route = useRoute();
14 15 const router = useRouter();
15 16 const info = ref<Partial<Product>>({
... ...
pages/products/index.vue
... ... @@ -130,6 +130,74 @@
130 130 />
131 131 </div>
132 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 201 </v-container>
134 202 </div>
135 203 </template>
... ... @@ -146,6 +214,7 @@ import { watchEffect, computed, ref } from &quot;vue&quot;;
146 214 const productStore = useProductListStore();
147 215 const categoryStore = useCategoryStore();
148 216 const loading = ref(false);
  217 +const hotLoading = ref(false);
149 218 const route = useRoute(); // 获取路由信息
150 219 const router = useRouter(); // 获取路由信息
151 220 const title = ref("");
... ... @@ -155,8 +224,16 @@ const tabRecom = ref();
155 224 const recommendList = ref();
156 225 const recommendImages = ref();
157 226 const currentIndex = ref(1);
  227 +const hotTotal = ref(10);
158 228 const isOrNotMobile = isMobile();
  229 +
159 230 const loadHotProducts = async () => {
  231 + const pageSize = ref(5);
  232 + if (isOrNotMobile) {
  233 + pageSize.value = 4;
  234 + }
  235 + hotLoading.value = true;
  236 +
160 237 let { data: hotProducts } = await useAsyncData(
161 238 "hotProducts",
162 239 () =>
... ... @@ -164,41 +241,46 @@ const loadHotProducts = async () =&gt; {
164 241 method: "GET",
165 242 params: {
166 243 pageNo: currentIndex.value,
167   - pageSize: 5,
  244 + pageSize: pageSize.value,
168 245 },
169 246 }),
170 247 {
171 248 server: true, // 仅在服务器端获取数据
172 249 }
173 250 );
  251 + hotTotal.value = hotProducts.value.data.total;
174 252 recommendList.value = hotProducts.value.data.records;
175 253 maxPage.value = hotProducts.value.data.pages;
176 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 284 const toggleRowLeft = () => {
203 285 if (currentIndex.value !== 1) {
204 286 currentIndex.value--;
... ... @@ -213,10 +295,13 @@ const toggleRowRight = () =&gt; {
213 295 currentIndex.value = 1;
214 296 }
215 297 };
  298 +const toggleRowMobile = (value: number) => {
  299 + currentIndex.value = value;
  300 +};
  301 +
216 302 watch(currentIndex, (newIndex) => {
217 303 loadHotProducts(); // Call loadHotProducts when currentIndex changes
218 304 });
219   -
220 305 // Initial load of hot products
221 306 await loadHotProducts(); // Load hot products the first time
222 307  
... ... @@ -404,6 +489,9 @@ watchEffect(async () =&gt; {
404 489 const length = computed(() =>
405 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 495 </script>
408 496  
409 497 <style scoped>
... ... @@ -529,4 +617,8 @@ button .recommendButton {
529 617 .recommend-img-right:hover {
530 618 cursor: pointer;
531 619 }
  620 +
  621 +.v-card {
  622 + transition: all 0.3s ease-in-out;
  623 +}
532 624 </style>
... ...