<template> <div> <!-- <v-breadcrumbs divider="/" dense style=" padding-top: 8px; font-size: 14px; display: flex; flex-wrap: wrap; align-items: center; " > <template v-for="(item, index) in items" :key="index"> <v-breadcrumbs-item :disabled="item.disabled" :href="item.href" :class="{ 'breadcrumb-last': index === items.length - 1, }" :style=" index === items.length - 1 ? 'color: black; font-weight: bold;' : 'color:black' " > {{ item.title }} </v-breadcrumbs-item> <span v-if="index < items.length - 1" class="breadcrumb-divider" style="margin: 0 4px; font-size: 14px; color: gray" >/</span > </template> </v-breadcrumbs> --> <v-breadcrumbs divider="/" dense style=" padding-top: 8px; font-size: 14px; display: flex; flex-wrap: wrap; align-items: center; " > <template v-for="(item, index) in items" :key="index"> <v-breadcrumbs-item :disabled="item.disabled" :href="item.href" style="color: #1e88e5" > {{ item.title }} </v-breadcrumbs-item> <span v-if="index < items.length - 1" class="breadcrumb-divider" style="margin: 0 4px; font-size: 14px; color: gray" >/</span > </template> </v-breadcrumbs> </div> <v-container class="ma-0 pa-0 bg-grey-lighten-5"> <v-carousel class="tw-float-left" height="450" v-model="slide" hide-delimiter-background > <v-carousel-item cover v-for="(slide, i) in info.productimageliststore" :src="slide.url" :key="i" :alt="info.name" > </v-carousel-item> </v-carousel> <h1 class="mb-3 bg-white mb-sm-10 text-h4 font-weight-medium pa-4"> {{ info.name }} </h1> <v-row class="mx-4 mt-0 mb-4 bg-white rounded-lg"> <v-col cols="6"> <div class="text-body-1 text-grey-darken-3">Brand:</div> <div class="text-h6">{{ info.brandName }}</div> </v-col> <v-col cols="6"> <div class="text-body-1 text-grey-darken-3">Product Model:</div> <div class="text-h6">{{ info.model }}</div> </v-col> <v-col cols="6" v-if="info.basename1"> <div class="text-body-1 text-grey-darken-3">{{ info.basename1 }}:</div> <div class="text-h6">{{ info.basecore1 }}</div> </v-col> <v-col cols="6" v-if="info.basename2"> <div class="text-body-1 text-grey-darken-3">{{ info.basename2 }}:</div> <div class="text-h6">{{ info.basecore2 }}</div> </v-col> <v-col cols="6" v-if="info.basename3"> <div class="text-body-1 text-grey-darken-3">{{ info.basename3 }}:</div> <div class="text-h6">{{ info.basecore3 }}</div> </v-col> </v-row> <div ref="ticketDiv" v-if="info.ticketTypes?.length" class="py-0 mx-4 rounded-lg tw-flex tw-flex-wrap tw-justify-around" > <!-- <v-sheet v-for="item in info.ticketTypes || []" :key="item.rank" rounded="lg" border class="tw-w-[40%] mb-4" > <div class="tw-bg-[#dcecfa] tw-h-[64px] !tw-leading-[64px] rounded-lg rounded-b-0 pa-2 tw-text-center text-h6 tw-overflow-hidden tw-text-ellipsis" > {{ item.typeName }} </div> <div class="tw-h-[82px] pa-2"> <div class="text-caption">Product Code</div> <div class="text-body-1 !tw-font-[500]"> {{ item.rank }} </div> </div> </v-sheet> --> <v-table density="comfortable" style="width: 100%" class="table1 tw-mt-[0px] tw-overflow-x-auto" v-if="info.ticketTypes?.length" > <thead> <tr class="bg-grey-lighten-3"> <th class="text-left headerBorder text-grey-darken-1">Name/Code</th> <th class="text-left headerBorder text-grey-darken-1"> Specification </th> <th v-if="info.priceShow !== undefined && info.priceShow" class="text-left headerBorder text-grey-darken-1" > Price </th> <!-- <th class="text-left headerBorder text-grey-darken-1">Actions</th> --> </tr> </thead> <tbody> <tr class="tr" v-for="item in info.ticketTypes || []" :key="item.rank" > <td class="td text-grey-darken-4 font-weight-medium"> {{ item.rank }} </td> <td class="td text-grey-darken-4 font-weight-medium"> {{ item.typeName }} </td> <td v-if="item.priceShow" class="td">$ {{ item.price }}</td> <!-- <td class="td"> <v-btn size="small" color="blue-darken-1" @click="router.push('/contact')" > Quote </v-btn> </td> --> </tr> </tbody> </v-table> <!-- <div v-for="item in info.ticketTypes || []" :key="item.rank"> </div> --> <!-- <v-btn style="margin-top: 6px" color="blue-darken-1" @click="router.push('/contact')" > Quotation Inquiry </v-btn> --> <!-- <div v-if="showBottomButton" ref="bottomButton" :class="['button-container', { fixed: isFixed }]" > --> <v-btn style="margin-top: 12px" color="blue-darken-1 tw-sticky" @click="router.push('/contact')" > Quotation Inquiry </v-btn> <!-- </div> --> </div> <!-- <v-dialog v-model="dialog" activator="parent" width="auto"> <v-card> Contact us Email: contact@canrd.com QQ: 3003597584 / 2902385824 </v-card> </v-dialog> --> <div class="tw-pb-[64px] ma-4 rounded-lg"> <v-tabs class="tabs" v-model="tab" style="margin-top: 25px; margin-bottom: 20px" color="white" bg-color="#eeeeee" slider-color="blue-lighten-1" selected-class="active" > <v-tab :value="1">Product Details</v-tab> <v-tab :value="2">Specification</v-tab> <!-- <v-tab :value="3">商品百科</v-tab> --> </v-tabs> <v-window v-model="tab"> <v-window-item key="1" :value="1"> <div v-if="info.advantage" class="tw-mb-[24px]"> <div class="py-2 pl-2 text-h6">Advantage</div> <v-divider class="tw-mb-[12px]"></v-divider> <div v-html="info.advantage"></div> </div> <div v-if="info.physicalproperty" class="tw-mb-[24px]"> <div class="py-2 pl-2 text-h6">Physical Property</div> <v-divider class="tw-mb-[12px]"></v-divider> <div v-html="clearSpanStyle(info.physicalproperty)"></div> </div> <div v-if="info.storage" class="tw-mb-[24px]"> <div class="py-2 pl-2 text-h6">Storage</div> <v-divider class="tw-mb-[12px]"></v-divider> <div v-html="clearSpanStyle(info.storage)"></div> </div> <div v-if="info.introduction" class="tw-mb-[24px]"> <div class="py-2 pl-2 text-h6">Introduction</div> <v-divider class="tw-mb-[12px]"></v-divider> <div v-html="clearSpanStyle(info.introduction)"></div> </div> <div v-if="info.description" class="tw-mb-[24px]"> <div class="py-2 pl-2 text-h6">Description</div> <v-divider class="tw-mb-[12px]"></v-divider> <div v-html="clearSpanStyle(info.description)"></div> </div> </v-window-item> <v-window-item key="2" :value="2"> <v-table density="compact" class="table2"> <tbody> <tr class="tr" v-for="item in info.productAttributeList || []" :key="item.name" > <td class="td tw-w-[350px]">{{ item.name }}</td> <td class="td">{{ item.value }}</td> </tr> </tbody> </v-table> </v-window-item> <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> </v-window> </div> <div class="tw-pb-[64px] ma-4 rounded-lg" style="padding-bottom: 30px"> <v-tabs class="tabs" v-model="tabProduct" color="white" bg-color="#eeeeee" slider-color="blue-lighten-1" selected-class="active" style="margin-bottom: 20px" > <v-tab :value="1">Best Sellers</v-tab> <v-tab :value="2">Related Products</v-tab> <!-- <v-tab :value="3">商品百科</v-tab> --> </v-tabs> <v-window v-model="tabProduct"> <v-window-item key="1" :value="1"> <div class="tw-text-center" v-if="hotLoading && isMobile()"> <v-progress-circular color="blue-lighten-2" indeterminate size="64" class="tw-m-auto" ></v-progress-circular> </div> <v-item-group multiple v-if="isMobile()"> <v-row v-if="!hotLoading"> <v-col v-for="(item, i) in recommendImagesHot" :key="i" cols="6" lg="3" md="4" sm="6" > <div v-if="item !== null"> <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl" > <!-- 设置 eager 属性,确保图片直接加载 --> <v-img :src="item[0].url" :alt="item[0].name" eager class="d-block" /> <v-card-text class="tw-text-left font-weight-medium title"> <h4 class="clamp-text">{{ item[0].name }}</h4> </v-card-text> </v-card> </div> </v-col> </v-row> <!-- <div v-if="!hotTotal" class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" > no data </div> --> </v-item-group> <v-row v-if="isMobile()"> <v-col> <v-pagination :size="isMobile() ? 'small' : 'default'" v-if="hotTotal" v-model="currentIndexHot" @update:modelValue="toggleRowMobileHot" :length="hotLength" rounded="0" class="tw-float-right tw-mt-[32px]" total-visible="5" ></v-pagination></v-col ></v-row> </v-window-item> <!-- best sellers --> <v-window-item key="2" :value="2"> <div class="tw-text-center" v-if="loading && isMobile()"> <v-progress-circular color="blue-lighten-2" indeterminate size="64" class="tw-m-auto" ></v-progress-circular> </div> <v-item-group multiple v-if="isMobile()"> <v-row v-if="!loading"> <v-col v-for="(item, i) in recommendImages" :key="i" cols="6" lg="3" md="4" sm="6" > <div v-if="item !== null"> <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl" > <!-- 设置 eager 属性,确保图片直接加载 --> <v-img :src="item[0].url" :alt="item[0].name" eager class="d-block" /> <v-card-text class="tw-text-left font-weight-medium title"> <h4 class="clamp-text">{{ item[0].name }}</h4> </v-card-text> </v-card> </div> </v-col> </v-row> <!-- <div v-if="!hotTotal" class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]" > no data </div> --> </v-item-group> <v-row v-if="isMobile()"> <v-col> <v-pagination :size="isMobile() ? 'small' : 'default'" v-if="total" v-model="currentIndex" @update:modelValue="toggleRowMobile" :length="length" rounded="0" class="tw-float-right tw-mt-[32px]" total-visible="5" ></v-pagination></v-col ></v-row> </v-window-item> <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> </v-window> </div> <div class="tw-pb-[64px] ma-4 rounded-lg"> <v-tabs class="tabs" v-model="tabJournal" color="white" bg-color="#eeeeee" slider-color="blue-lighten-1" selected-class="active" > <v-tab :value="1">Product-related Journals</v-tab> </v-tabs> <v-window v-model="tabJournal"> <v-window-item key="1" :value="1"> <v-list> <v-list-item v-for="item in info.journals" :key="item.id" @click="navigateToUrl(item.link)" @mouseenter="hoveredItem = item.id" @mouseleave="hoveredItem = null" > <v-list-item-title> <span :class="['title', { 'full-title': hoveredItem === item.id }]" > {{ item.title }} </span> </v-list-item-title> </v-list-item> </v-list> </v-window-item> </v-window> </div> <!-- Basic use --> <div class="social-share-container"> <SocialShare network="facebook" :styled="true" :label="true" :title="info.name" :url="currentUrl" style="color: #1e88e5; width: 140px" /> <SocialShare network="twitter" :styled="true" :label="true" :title="info.name" :url="currentUrl" style="color: #1e88e5; width: 140px" /> <SocialShare network="linkedin" :styled="true" :label="true" :title="info.name" :url="currentUrl" style="color: #1e88e5; width: 140px" /> </div> </v-container> </template> <script setup lang="ts"> import type { Product } from "~/type"; import { isMobile } from "~/utils"; import { defineProps, ref } from "vue"; import { useDialogStore } from "~/stores/dialog"; const dialogStore = useDialogStore(); const props = defineProps<{ info: Product; }>(); const info = props.info; // onMounted(() => { // dialogStore.updateDialog(true) // }) const tab = ref(); const tabProduct = ref(); const tabJournal = ref(); const slide = ref(0); const router = useRouter(); const href1 = ref("/products"); const href2 = ref("/products"); const routeQuery = useRouteQuery(); const productStore = useProductListStore(); const currentUrl = ref("https://www.canrud.com/products"); const hotLoading = ref(false); const loading = ref(false); const maxPage = ref(1); const maxPageRe = ref(1); const tabRecom = ref(); const recommendList = ref(); const recommendListHot = ref(); const recommendImages = ref(); const recommendImagesHot = ref(); const currentIndex = ref(1); const currentIndexHot = ref(1); const total = ref(3); const hotTotal = ref(10); const isOrNotMobile = isMobile(); const hoveredItem = ref(null); const clearSpanStyle = (htmlContent) => { if (!htmlContent) return ""; // 检查是否为空 // 创建一个 DOM 解析器 const parser = new DOMParser(); const doc = parser.parseFromString(htmlContent, "text/html"); // 清理 span 的 style 属性 const spans = doc.querySelectorAll("span"); spans.forEach((span) => { span.removeAttribute("style"); // 清空 style 属性 }); // 修改 img 的 style 属性,追加 width: 100% const imgs = doc.querySelectorAll("img"); imgs.forEach((img) => { const style = img.getAttribute("style") || ""; // 获取现有 style if (!style.includes("width: 100%")) { img.setAttribute("style", `${style} width: 100%;`.trim()); // 添加 width: 100% } }); // 返回处理后的 HTML return doc.body.innerHTML; }; const navigateToUrl = (url) => { window.open(url); // 在新标签页中打开链接 }; const loadProducts = async () => { const pageSize = 4; loading.value = true; let { data: resData } = await useAsyncData( "list", () => $fetch("/shop/product/list", { method: "GET", params: { pageNo: currentIndex.value, pageSize: pageSize, ids: info.relatedProductIds, }, }), { server: true, // 仅在服务器端获取数据 } ); total.value = resData.value.data.total; if (total.value > 50) { total.value = 50; } recommendList.value = resData.value.data.records; maxPageRe.value = resData.value.data.pages; // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { recommendImages.value = Array.from({ length: 4 }).map((_, index) => { const item = recommendList.value[index]; if (!item) { return null; } // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 if (typeof item.productimageliststore === "string") { try { item.productimageliststore = JSON.parse(item.productimageliststore); } catch (error) { item.productimageliststore = []; // 解析失败时,设置为空数组 } } const ree = (item.productimageliststore = item?.productimageliststore.map( (productItem: ProductImage) => ({ ...productItem, // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, name: item.name, productUrl: `https://www.canrud.com/products/detail/${item.id}`, }) )); return ree; }); loading.value = false; }; const loadHotProducts = async () => { const pageSizeHot = ref(5); if (isOrNotMobile) { pageSizeHot.value = 4; } hotLoading.value = true; let { data: hotProducts } = await useAsyncData( "hotProducts", () => $fetch("/shop/product/hotProducts", { method: "GET", params: { pageNo: currentIndexHot.value, pageSize: pageSizeHot.value, }, }), { server: true, // 仅在服务器端获取数据 } ); hotTotal.value = hotProducts.value.data.total; recommendListHot.value = hotProducts.value.data.records; maxPage.value = hotProducts.value.data.pages; // recommendImages.value = recommendList.value.slice(0, 10).map((item) => { recommendImagesHot.value = Array.from({ length: pageSizeHot.value }).map( (_, index) => { const item = recommendListHot.value[index]; if (!item) { return null; } // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析 if (typeof item.productimageliststore === "string") { try { item.productimageliststore = JSON.parse(item.productimageliststore); } catch (error) { item.productimageliststore = []; // 解析失败时,设置为空数组 } } const ree = (item.productimageliststore = item?.productimageliststore.map( (productItem: ProductImage) => ({ ...productItem, // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`, url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`, name: item.name, productUrl: `https://www.canrud.com/products/detail/${item.id}`, }) )); return ree; } ); hotLoading.value = false; }; const toggleRowMobile = (value: number) => { currentIndex.value = value; }; const toggleRowMobileHot = (value: number) => { currentIndexHot.value = value; }; watch(currentIndexHot, (newIndex) => { loadHotProducts(); // Call loadHotProducts when currentIndex changes }); watch(currentIndex, (newIndex) => { loadProducts(); // Call loadHotProducts when currentIndex changes }); // Initial load of hot products await loadHotProducts(); // Load hot products the first time await loadProducts(); // Load hot products the first time interface BreadcrumbItem { title: string; // 标题 disabled: boolean; // 是否禁用 href: string; // 链接地址 } // 示例数据 const items = ref<BreadcrumbItem[]>([ { title: "Products", disabled: false, href: "https://www.canrud.com/products", }, { title: "CATEGORY", disabled: false, href: href1.value, }, { title: "Not specified", disabled: false, href: href2.value, }, { title: "Not specified", disabled: false, href: "/products", }, ]); watchEffect(() => { currentUrl.value = "https://www.canrud.com/products/detail/" + info.id; if (info?.productCrumbsVO?.category1 && productStore.keyword) { items.value[1].title = info.productCrumbsVO.category1; items.value[1].href = "https://www.canrud.com/products?categories=" + info.productCrumbsVO.category1; href1.value = "https://www.canrud.com/products?categories=" + info.productCrumbsVO.category1; if (info?.productCrumbsVO?.category2) { items.value[2].title = info.productCrumbsVO.category2; href2.value = href1.value + "," + info.productCrumbsVO.category2; items.value[2].href = href1.value + "," + info.productCrumbsVO.category2; } if (info?.productCrumbsVO?.function) { // items.value.push({ // title: info.productCrumbsVO.function, // disabled: false, // href: href2.value + "&function=" + info.productCrumbsVO.function, // }); items.value[3].title = info.productCrumbsVO.function; items.value[3].href = href2.value + "&function=" + info.productCrumbsVO.function; } } else if (routeQuery.categories) { if (!routeQuery.categories.includes("Energy materials")) { routeQuery.updateFunction("Not specified"); } const categories = routeQuery.categories.split(","); const mainCategory = categories[0].trim(); // 取第一个值 const subCategoryName = ref( categories[1] ? categories[1].trim() : "Not specified" ); // 取第二个值(如果存在) if (subCategoryName.value == "Accessories & fixtures") { subCategoryName.value = encodeURIComponent("Accessories & fixtures"); items.value[2].title = "Accessories & fixtures"; } else { items.value[2].title = subCategoryName.value; } items.value[1].title = mainCategory; items.value[1].href = "https://www.canrud.com/products?categories=" + mainCategory; href1.value = "https://www.canrud.com/products?categories=" + mainCategory; // items.value[1].title = subCategoryName.value; href2.value = href1.value + "," + subCategoryName.value; items.value[2].href = href1.value + "," + subCategoryName.value; if (routeQuery?.selectedFunction) { // items.value.push({ // title: routeQuery.selectedFunction, // disabled: false, // href: href2.value + "&function=" + routeQuery.selectedFunction, // }); items.value[3].title = routeQuery.selectedFunction; items.value[3].href = href2.value + "&function=" + routeQuery.selectedFunction; // routeQuery.updateFunction(null); } } else if (info?.productCrumbsVO?.category1) { items.value[1].title = info.productCrumbsVO.category1; items.value[1].href = "https://www.canrud.com/products?categories=" + info.productCrumbsVO.category1; href1.value = "https://www.canrud.com/products?categories=" + info.productCrumbsVO.category1; if (info?.productCrumbsVO?.category2) { items.value[2].title = info.productCrumbsVO.category2; href2.value = href1.value + "," + info.productCrumbsVO.category2; items.value[2].href = href1.value + "," + info.productCrumbsVO.category2; } if (info?.productCrumbsVO?.function) { // items.value.push({ // title: info.productCrumbsVO.function, // disabled: false, // href: href2.value + "&function=" + info.productCrumbsVO.function, // }); items.value[3].title = info.productCrumbsVO.function; items.value[3].href = href2.value + "&function=" + info.productCrumbsVO.function; } } }); const length = computed(() => (total.value ? Math.ceil(total.value / 4) : 0)); const hotLength = computed(() => hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0 ); </script> <style lang="scss" scoped> // .tabs { // border-bottom: 2px solid #cbd9e4; // } // .active { // background-color: #fff; // } .tabs { border-bottom: 2px solid #1f88e5; } .active { background-color: #1086e8; } .tw-sticky { position: sticky; } // .button-container { // margin-top: 6px; // display: flex; // justify-content: center; // position: absolute; // width: 100%; // bottom: auto; /* 默认情况下不固定 */ // } // .button-container.fixed { // position: fixed; // bottom: 16px; /* 固定在页面底部 */ // left: 0; // z-index: 1000; // background-color: rgba(255, 255, 255, 0.9); // box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.1); // padding: 8px 0; // } .social-share-container { display: flex; flex-direction: row; /* 父容器横向排列 */ gap: 1rem; /* 设置每个分享项之间的间距 */ margin-left: 10px; margin-bottom: 10px; } .social-share-item { display: flex; flex-direction: row; /* 每个分享项横向排列 */ align-items: center; /* 垂直居中对齐 */ gap: 0.5rem; /* 标题与按钮之间的间距 */ } .social-share-title { font-size: 1rem; font-weight: bold; } .v-card { transition: all 0.3s ease-in-out; } .clamp-text { display: -webkit-box; /* 使用弹性盒子 */ -webkit-box-orient: vertical; /* 设置为垂直方向 */ -webkit-line-clamp: 3; /* 限制最多显示3行 */ overflow: hidden; /* 隐藏多余内容 */ text-overflow: ellipsis; /* 添加省略号 */ white-space: normal; /* 允许换行 */ line-height: 1.5em; /* 设置每行的高度 */ min-height: calc(3 * 1.5em); /* 确保最小高度为3行 */ } </style>