Commit a7b494e229505dd4eeb2a0e0e0f28d890faca4c9

Authored by 曾国涛
2 parents 1881dbad cd5cc258

Merge branch 'master' of http://39.108.227.113:8001/zhusen/canrud-nuxt-front

# Conflicts:
#	nuxt.config.ts

Too many changes to show.

To preserve performance only 13 of 18 files are displayed.

components/CategoryList.vue
... ... @@ -79,7 +79,9 @@ import { useProductListStore } from "@/stores/product_list";
79 79 import type { CategoryRootType } from "@/type";
80 80 import { computed } from "vue";
81 81 import { useRouter, useRoute } from "vue-router";
  82 +import { useRouteQuery } from "@/stores/route_query";
82 83  
  84 +const routeQuery = useRouteQuery();
83 85 const route = useRoute();
84 86 const router = useRouter();
85 87  
... ... @@ -91,13 +93,16 @@ watchEffect(async () => {
91 93 productStore.updateKeyword("");
92 94 const categories = route.query.categories.split(",");
93 95 const mainCategory = categories[0].trim(); // 取第一个值
94   - const subCategoryName = categories[1] ? categories[1].trim() : null; // 取第二个值(如果存在)
  96 + const subCategoryName = ref("Not specified");
  97 + subCategoryName.value = categories[1]
  98 + ? categories[1].trim()
  99 + : "Not specified"; // 取第二个值(如果存在)
95 100  
96 101 // 2. 更新选中的主分类
97 102 categoryStore.updateCategory(mainCategory);
98   -
  103 + routeQuery.updateCategories(mainCategory + "," + subCategoryName.value);
99 104 // 3. 如果有子分类名称,查找其对应的 ID
100   - if (subCategoryName) {
  105 + if (subCategoryName.value) {
101 106 const subCategoryList = computed(() => {
102 107 if (categoryStore.selectedCategory) {
103 108 const tmp = categoryStore.list.filter(
... ... @@ -111,7 +116,7 @@ watchEffect(async () => {
111 116  
112 117 // 4. 查找对应的子分类 ID
113 118 const foundFuncCategory = subCategoryList.value.find(
114   - (func) => func.name === subCategoryName
  119 + (func) => func.name === subCategoryName.value
115 120 );
116 121  
117 122 if (foundFuncCategory) {
... ... @@ -123,6 +128,7 @@ watchEffect(async () => {
123 128 // 5. 判断 query 中是否存在 function,并查找对应的 ID
124 129 if (route.query.function) {
125 130 const functionName = route.query.function.trim();
  131 + routeQuery.updateFunction(functionName);
126 132 const funcCategoryList = computed(() => {
127 133 if (categoryStore.selectedCategory) {
128 134 const tmp = categoryStore.list.filter(
... ... @@ -152,15 +158,30 @@ watchEffect(async () => {
152 158 // // 使用找到的 funcCategoryId
153 159 // categoryStore.updateFuncCategory(funcCategoryId);
154 160 // }
  161 + } else if (route.query.categories.includes("Energy materials")) {
  162 + const defaultCategory = categoryStore.list[1];
  163 + const defaultFuncCategory = defaultCategory.productFunctions[1];
  164 + if (defaultFuncCategory) {
  165 + categoryStore.updateFuncCategory(defaultFuncCategory.id);
  166 + }
  167 + router.push({
  168 + query: {
  169 + categories: route.query.categories,
  170 + function: defaultFuncCategory.name,
  171 + },
  172 + });
155 173 }
156   - } else if (Object.keys(route.query).length === 0) {
  174 + } else if (
  175 + Object.keys(route.query).length === 0 &&
  176 + !route.path.includes("/products/detail")
  177 + ) {
157 178 // 检查是否有默认的分类
158 179 const defaultCategory = categoryStore.list[0]; // 假设第一个分类是默认的
159 180  
160 181 if (defaultCategory) {
161 182 const defaultCategoryName = defaultCategory.categoryDisplayName;
162   - const defaultSubCategory = defaultCategory.list[0]; // 假设第一个子分类为默认子分类
163   - const defaultFuncCategory = defaultCategory.productFunctions[0]; // 假设第一个功能分类为默认功能分类
  183 + const defaultSubCategory = defaultCategory.list[1]; // 假设第一个子分类为默认子分类
  184 + const defaultFuncCategory = defaultCategory.productFunctions[1]; // 假设第一个功能分类为默认功能分类
164 185  
165 186 // 更新 store 和 URL
166 187 categoryStore.updateCategory(defaultCategoryName);
... ...
components/Footer.vue
... ... @@ -5,7 +5,9 @@
5 5 <v-col cols="12" lg="3" sm="12" md="6">
6 6 <b>Solution</b>
7 7 <p><router-link to="/equipment">Lab Device</router-link></p>
8   - <p><router-link to="/customize">Customized BatterTesting</router-link></p>
  8 + <p>
  9 + <router-link to="/customize">Customized BatterTesting</router-link>
  10 + </p>
9 11 <p><router-link to="/pack">Pack</router-link></p>
10 12 </v-col>
11 13 <v-col cols="12" lg="3" sm="12" md="6">
... ... @@ -16,6 +18,39 @@
16 18 <v-col cols="12" lg="3" sm="12" md="6">
17 19 <b>About</b>
18 20 <p><router-link to="/about">About us</router-link></p>
  21 + <p>
  22 + <a
  23 + href="https://www.linkedin.com/company/canrd?originalSubdomain=cn"
  24 + rel="noopener noreferrer"
  25 + >LinkedIn</a
  26 + >
  27 + </p>
  28 + <p>
  29 + <a
  30 + href="https://www.amazon.com/s?me=A3A2SQ086XUS66&marketplaceID=ATVPDKIKX0DER"
  31 + rel="noopener noreferrer"
  32 + >Amazon</a
  33 + >
  34 + </p>
  35 + <p>
  36 + <a
  37 + href="https://canrd.en.alibaba.com/company_profile.html?spm=a2700.galleryofferlist.normal_offer.d_companyName.262213a0fqshG2"
  38 + rel="noopener noreferrer"
  39 + >Alibaba</a
  40 + >
  41 + </p>
  42 + <p>
  43 + <a
  44 + href="https://www.youtube.com/@Canrd_Tech"
  45 + rel="noopener noreferrer"
  46 + >Youtube</a
  47 + >
  48 + </p>
  49 + <p>
  50 + <a href="https://x.com/canrdenerge?s=11" rel="noopener noreferrer"
  51 + >Twitter</a
  52 + >
  53 + </p>
19 54 </v-col>
20 55 <v-col cols="12" lg="3" sm="12" md="6">
21 56 <div class="tw-w-[250px] tw-float-left tw-mr-[8px]">
... ... @@ -24,14 +59,20 @@
24 59 <p>Phone: +86 19867737979</p>
25 60 <p>Wechat: contactcanrd</p>
26 61 </div>
27   - <img class="tw-float-left" src="/wechat.jpg" alt="canrud-wechat" width="80" />
  62 + <img
  63 + class="tw-float-left"
  64 + src="/wechat.jpg"
  65 + alt="canrud-wechat"
  66 + width="80"
  67 + />
28 68 </v-col>
29 69 </v-row>
30 70 </v-container>
31 71 </div>
32 72 </template>
33 73  
34   -<script setup lang="ts"></script>
  74 +<script setup lang="ts">
  75 +</script>
35 76  
36 77 <style>
37 78 b {
... ...
components/MobileCategoryList.vue
... ... @@ -3,7 +3,9 @@
3 3 class="pr-4 tw-h-[48px] tw-leading-[48px] border-b tw-flex tw-justify-between tw-items-center"
4 4 >
5 5 <span class="ml-4 tw-font-bold">{{ categoryStore.selectedCategory }}</span>
6   - <span class="ml-1 text-grey-darken-4 text-body-2" @click="drawerVis = !drawerVis"
  6 + <span
  7 + class="ml-1 text-grey-darken-4 text-body-2"
  8 + @click="drawerVis = !drawerVis"
7 9 >Filter <v-icon> mdi-filter-outline </v-icon></span
8 10 >
9 11 </div>
... ... @@ -16,7 +18,11 @@
16 18 touchless
17 19 class="!tw-h-[70%] bg-grey-lighten-4 tw-overflow-y-auto tw-overflow-x-hidden"
18 20 >
19   - <div class="mb-4 pa-2 tw-bg-[#fff]" v-for="(item, index) in categoryStore.list" :key="index">
  21 + <div
  22 + class="mb-4 pa-2 tw-bg-[#fff]"
  23 + v-for="(item, index) in categoryStore.list"
  24 + :key="index"
  25 + >
20 26 <div class="mb-4 tw-flex tw-items-center">
21 27 <div>
22 28 <v-img
... ... @@ -29,7 +35,9 @@
29 35 "
30 36 ></v-img>
31 37 </div>
32   - <strong class="tw-m-0 tw-inline">{{ item.categoryDisplayName }}</strong>
  38 + <strong class="tw-m-0 tw-inline">{{
  39 + item.categoryDisplayName
  40 + }}</strong>
33 41 </div>
34 42 <div class="tw-flex tw-flex-wrap tw-justify-between">
35 43 <template v-if="index !== 0">
... ... @@ -43,7 +51,7 @@
43 51 : ' bg-grey-lighten-4 ')
44 52 "
45 53 :key="i"
46   - @click="handleCategoryClick(item, k.id)"
  54 + @click="handleCategoryClick(item, k.id, k)"
47 55 >
48 56 {{ k.name }}
49 57 </div>
... ... @@ -59,7 +67,7 @@
59 67 ' tw-w-[48%] mb-4 py-3 px-2 tw-rounded-lg tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis'
60 68 "
61 69 :key="i"
62   - @click="handleCategoryClick(item, k.id)"
  70 + @click="handleCategoryClick(item, k.id, k)"
63 71 >
64 72 {{ k.name }}
65 73 </div>
... ... @@ -71,62 +79,259 @@
71 79 </template>
72 80  
73 81 <script setup lang="ts">
74   -import { useCategoryStore } from '@/stores/category'
75   -import { useProductListStore } from '@/stores/product_list'
76   -import type { CategoryRootType } from '@/type'
77   -import { computed, ref, watchEffect } from 'vue'
78   -import { useRouter } from 'vue-router'
  82 +import { useCategoryStore } from "@/stores/category";
  83 +import { useProductListStore } from "@/stores/product_list";
  84 +import type { CategoryRootType } from "@/type";
  85 +import { computed, ref, watchEffect } from "vue";
  86 +import { useRouter, useRoute } from "vue-router";
79 87  
80   -const router = useRouter()
  88 +const router = useRouter();
  89 +const route = useRoute();
81 90  
82   -const categoryStore = useCategoryStore()
83   -const productStore = useProductListStore()
  91 +const categoryStore = useCategoryStore();
  92 +const productStore = useProductListStore();
  93 +const routeQuery = useRouteQuery();
84 94  
85   -const drawerVis = ref(false)
  95 +const drawerVis = ref(false);
86 96  
87 97 const CATEGORY_IMG = [
88   - { normal: '/category/1.png', selected: '/category/1.1.png', name: 'Energy materials' },
89   - { normal: '/category/2.png', selected: '/category/2.1.png', name: 'Laboratory consumables' },
90   - { normal: '/category/3.png', selected: '/category/3.1.png', name: 'Low-dimensional materials' },
91   - { normal: '/category/4.png', selected: '/category/4.1.png', name: 'Equipment' }
92   -]
  98 + {
  99 + normal: "/category/1.png",
  100 + selected: "/category/1.1.png",
  101 + name: "Energy materials",
  102 + },
  103 + {
  104 + normal: "/category/2.png",
  105 + selected: "/category/2.1.png",
  106 + name: "Laboratory consumables",
  107 + },
  108 + {
  109 + normal: "/category/3.png",
  110 + selected: "/category/3.1.png",
  111 + name: "Low-dimensional materials",
  112 + },
  113 + {
  114 + normal: "/category/4.png",
  115 + selected: "/category/4.1.png",
  116 + name: "Equipment",
  117 + },
  118 +];
  119 +
  120 +watchEffect(async () => {
  121 + if (route.query.categories) {
  122 + // 1. 提取 query.category 的内容
  123 + productStore.updateKeyword("");
  124 + const categories = route.query.categories.split(",");
  125 + const mainCategory = categories[0].trim(); // 取第一个值
  126 + const subCategoryName = ref("");
  127 + subCategoryName.value = categories[1]
  128 + ? categories[1].trim()
  129 + : "Not specified"; // 取第二个值(如果存在)
  130 +
  131 + // 2. 更新选中的主分类
  132 + categoryStore.updateCategory(mainCategory);
  133 + routeQuery.updateCategories(mainCategory + "," + subCategoryName.value);
  134 + if (!route.query.categories.includes("Energy materials")) {
  135 + // 3. 如果有子分类名称,查找其对应的 ID
  136 + if (subCategoryName.value) {
  137 + // if (subCategoryName.value && mainCategory !== "Energy materials") {
  138 + const subCategoryList = computed(() => {
  139 + if (categoryStore.selectedCategory) {
  140 + const tmp = categoryStore.list.filter(
  141 + (item) =>
  142 + item.categoryDisplayName === categoryStore.selectedCategory
  143 + );
  144 + return tmp?.[0]?.list || [];
  145 + }
  146 + return [];
  147 + });
  148 +
  149 + // 4. 查找对应的子分类 ID
  150 + const foundFuncCategory = subCategoryList.value.find(
  151 + (func) => func.name === subCategoryName.value
  152 + );
  153 +
  154 + if (foundFuncCategory) {
  155 + const funcCategoryId = foundFuncCategory.id;
  156 + // 你可以在这里使用找到的 funcCategoryId
  157 + categoryStore.updateSubCategory(funcCategoryId);
  158 + }
  159 + }
  160 + } else {
  161 + // 3. 如果有子分类名称,查找其对应的 ID
  162 + if (subCategoryName.value) {
  163 + // if (subCategoryName.value && mainCategory !== "Energy materials") {
  164 + const subCategoryList = computed(() => {
  165 + if (categoryStore.selectedCategory) {
  166 + const tmp = categoryStore.list.filter(
  167 + (item) =>
  168 + item.categoryDisplayName === categoryStore.selectedCategory
  169 + );
  170 + return tmp?.[0]?.list || [];
  171 + }
  172 + return [];
  173 + });
  174 +
  175 + // 4. 查找对应的子分类 ID
  176 + const foundFuncCategory = subCategoryList.value.find(
  177 + (func) => func.name === subCategoryName.value
  178 + );
  179 +
  180 + if (foundFuncCategory) {
  181 + const funcCategoryId = foundFuncCategory.id;
  182 + // 你可以在这里使用找到的 funcCategoryId
  183 + categoryStore.updateSubCategory(funcCategoryId);
  184 + }
  185 + }
  186 + // 5. 判断 query 中是否存在 function,并查找对应的 ID
  187 + if (route.query.function) {
  188 + const functionName = route.query.function.trim();
  189 + routeQuery.updateFunction(functionName);
  190 + const funcCategoryList = computed(() => {
  191 + if (categoryStore.selectedCategory) {
  192 + const tmp = categoryStore.list.filter(
  193 + (item) =>
  194 + item.categoryDisplayName === categoryStore.selectedCategory
  195 + );
  196 + return tmp?.[0]?.productFunctions || [];
  197 + }
  198 + return [];
  199 + });
  200 + const foundFuncCategory = funcCategoryList.value.find(
  201 + (func) => func.name === functionName
  202 + );
  203 + console.log(foundFuncCategory, "functionName");
  204 +
  205 + if (foundFuncCategory) {
  206 + const funcCategoryId = foundFuncCategory.id;
  207 + if (typeof window !== "undefined") {
  208 + window.selectedFuncCategory = foundFuncCategory.id;
  209 + }
  210 + // 你可以在这里使用找到的 funcCategoryId
  211 + categoryStore.updateFuncCategory(funcCategoryId);
  212 + }
  213 + // // 6. 查找对应的功能分类 ID
  214 + // const foundFuncCategory = funcCategories.find(
  215 + // (func) => func.name === functionName
  216 + // );
  217 +
  218 + // if (foundFuncCategory) {
  219 + // const funcCategoryId = foundFuncCategory.id;
  220 + // // 使用找到的 funcCategoryId
  221 + // categoryStore.updateFuncCategory(funcCategoryId);
  222 + // }
  223 + } else if (route.query.categories.includes("Energy materials")) {
  224 + //没有function默认为第一个
  225 + const defaultCategory = categoryStore.list[0];
  226 + const defaultFuncCategory = defaultCategory.productFunctions[1];
  227 + if (defaultFuncCategory) {
  228 + categoryStore.updateFuncCategory(defaultFuncCategory.id);
  229 + }
  230 + }
  231 + }
  232 + } else if (
  233 + Object.keys(route.query).length === 0 &&
  234 + !route.path.includes("/products/detail")
  235 + ) {
  236 + // 检查是否有默认的分类
  237 + const defaultCategory = categoryStore.list[0]; // 假设第一个分类是默认的
  238 +
  239 + if (defaultCategory) {
  240 + const defaultCategoryName = defaultCategory.categoryDisplayName;
  241 + const defaultSubCategory = defaultCategory.list[1]; // 假设第一个子分类为默认子分类
  242 + const defaultFuncCategory = defaultCategory.productFunctions[1]; // 假设第一个功能分类为默认功能分类
  243 +
  244 + // 更新 store 和 URL
  245 + categoryStore.updateCategory(defaultCategoryName);
  246 + productStore.updatePageNo(1);
  247 +
  248 + if (defaultSubCategory) {
  249 + categoryStore.updateSubCategory(defaultSubCategory.id);
  250 +
  251 + // 如果有之前的值则使用之前的值,拼接新的子分类名
  252 + const updatedCategory =
  253 + defaultCategoryName + "," + defaultSubCategory.name;
  254 +
  255 + // 拼接设备类型到 URL
  256 + router.push({
  257 + query: {
  258 + categories: defaultCategoryName + "," + defaultSubCategory.name,
  259 + },
  260 + });
  261 + }
  262 +
  263 + if (defaultFuncCategory) {
  264 + categoryStore.updateFuncCategory(defaultFuncCategory.id);
  265 + // 拼接功能类型到 URL
  266 + router.push({
  267 + query: {
  268 + categories: defaultCategoryName + "," + defaultSubCategory.name,
  269 + function: defaultFuncCategory.name,
  270 + },
  271 + });
  272 + }
  273 + }
  274 + }
  275 +});
93 276  
94 277 const SEO = {
95   - 'Energy materials':
96   - 'Energy materials,Not specified,Battery accessories,Lithium-ion batteries,Capacitors,Sodium-ion batteries,Lithium-sulfur batteries,Potassium/magnesium/aluminum/zinc batteries,Air/fuel/solar,Analytical electrodes,Complete battery accessories',
97   - 'Laboratory consumables':
98   - 'Laboratory consumables,Not specified,Glass materials,Plastic materials,Metal materials,Ceramic materials,Paper film materials,Chemical materials,Tetrafluoro materials,Safety protection,Office supplies,Tools,Others',
99   - 'Low-dimensional materials':
100   - ',Low-dimensional materialsNot specified,Zero-dimensional carbon materials,One-dimensional carbon materials,Two-dimensional carbon materials,Three-dimensional carbon materials,Inorganic nanomaterials,Organic nanomaterials,Metal nanomaterials,Others',
  278 + "Energy materials":
  279 + "Energy materials,Not specified,Battery accessories,Lithium-ion batteries,Capacitors,Sodium-ion batteries,Lithium-sulfur batteries,Potassium/magnesium/aluminum/zinc batteries,Air/fuel/solar,Analytical electrodes,Complete battery accessories",
  280 + "Laboratory consumables":
  281 + "Laboratory consumables,Not specified,Glass materials,Plastic materials,Metal materials,Ceramic materials,Paper film materials,Chemical materials,Tetrafluoro materials,Safety protection,Office supplies,Tools,Others",
  282 + "Low-dimensional materials":
  283 + ",Low-dimensional materialsNot specified,Zero-dimensional carbon materials,One-dimensional carbon materials,Two-dimensional carbon materials,Three-dimensional carbon materials,Inorganic nanomaterials,Organic nanomaterials,Metal nanomaterials,Others",
101 284 Equipment:
102   - 'Equipment,Not specified,Equipment,Accessories & fixtures,Fuel cell manufacturing and testing equipment'
103   -}
  285 + "Equipment,Not specified,Equipment,Accessories & fixtures,Fuel cell manufacturing and testing equipment",
  286 +};
104 287  
105 288 const handleDrawerHide = () => {
106   - productStore.updatePageNo(1)
  289 + // productStore.updatePageNo(1);
107 290  
108   - router.push({ query: { category: categoryStore.selectedCategory } })
  291 + // router.push({
  292 + // query: {
  293 + // categories: categoryStore.selectedCategory,
  294 + // },
  295 + // });
109 296  
110   - const doc = document as any
111   - const head = doc.getElementsByTagName('head')
112   - const meta = doc.createElement('meta')
113   - document.title = SEO[categoryStore.selectedCategory as keyof typeof SEO]
  297 + const doc = document as any;
  298 + const head = doc.getElementsByTagName("head");
  299 + const meta = doc.createElement("meta");
  300 + document.title = SEO[categoryStore.selectedCategory as keyof typeof SEO];
114 301 doc
115 302 .querySelector('meta[name="keywords"]')
116   - .setAttribute('content', SEO[categoryStore.selectedCategory as keyof typeof SEO])
117   - head[0].appendChild(meta)
118   -}
  303 + .setAttribute(
  304 + "content",
  305 + SEO[categoryStore.selectedCategory as keyof typeof SEO]
  306 + );
  307 + head[0].appendChild(meta);
  308 +};
119 309  
120   -const handleCategoryClick = (item: CategoryRootType, id: string) => {
121   - categoryStore.updateCategory(item.categoryDisplayName)
  310 +const handleCategoryClick = (
  311 + item: CategoryRootType,
  312 + id: string,
  313 + subItem: any
  314 +) => {
  315 + categoryStore.updateCategory(item.categoryDisplayName);
122 316  
123   - if (item.categoryDisplayName === 'Energy materials') {
124   - categoryStore.updateFuncCategory(id)
125   - categoryStore.updateSubCategory(item.list[0].id)
  317 + if (item.categoryDisplayName === "Energy materials") {
  318 + categoryStore.updateFuncCategory(id);
  319 + categoryStore.updateSubCategory(item.list[0].id);
  320 + router.push({
  321 + query: {
  322 + categories: categoryStore.selectedCategory + "," + item.list[0].name,
  323 + function: subItem.name,
  324 + },
  325 + });
126 326 } else {
127   - categoryStore.updateSubCategory(id)
  327 + categoryStore.updateSubCategory(id);
  328 + router.push({
  329 + query: {
  330 + categories: categoryStore.selectedCategory + "," + subItem.name,
  331 + },
  332 + });
128 333 }
129   -}
  334 +};
130 335 </script>
131 336  
132 337 <style lang="less" scoped></style>
... ...
components/MobileHeader.vue
... ... @@ -5,37 +5,87 @@
5 5 <v-layout>
6 6 <v-app-bar scroll-behavior="elevate" color="white" density="default">
7 7 <v-app-bar-title v-if="!showSearch">
8   - <a href="/"><v-img src="/mobile/index-logo.png" alt="canrud" width="100" height="40" /></a>
  8 + <a href="/"
  9 + ><v-img
  10 + src="/mobile/index-logo.png"
  11 + alt="canrud"
  12 + width="100"
  13 + height="40"
  14 + /></a>
9 15 </v-app-bar-title>
10 16 <template v-slot:append>
11 17 <v-btn icon v-if="!showSearch">
12 18 <v-icon @click="showSearch = !showSearch">mdi-magnify</v-icon>
13 19 </v-btn>
14 20  
15   - <v-app-bar-nav-icon @click="drawer = !drawer" v-if="!drawer"></v-app-bar-nav-icon>
16   - <v-app-bar-nav-icon @click="drawer = !drawer" v-if="drawer" icon="mdi-window-close"></v-app-bar-nav-icon>
  21 + <v-app-bar-nav-icon
  22 + @click="drawer = !drawer"
  23 + v-if="!drawer"
  24 + ></v-app-bar-nav-icon>
  25 + <v-app-bar-nav-icon
  26 + @click="drawer = !drawer"
  27 + v-if="drawer"
  28 + icon="mdi-window-close"
  29 + ></v-app-bar-nav-icon>
17 30 </template>
18 31  
19 32 <div class="tw-h-[36px] tw-w-[300px] ml-8" v-if="showSearch">
20   - <v-text-field dense density="compact" height="24" label="Search keyword" hide-details="auto" variant="solo"
21   - append-inner-icon="mdi-close" @click:appendInner="handleClick" @keydown="handleKeyDown" v-model="input">
  33 + <v-text-field
  34 + dense
  35 + density="compact"
  36 + height="24"
  37 + label="Search keyword"
  38 + hide-details="auto"
  39 + variant="solo"
  40 + append-inner-icon="mdi-close"
  41 + @click:appendInner="handleClick"
  42 + @keydown="handleKeyDown"
  43 + v-model="input"
  44 + >
22 45 </v-text-field>
23 46 </div>
24 47 </v-app-bar>
25 48  
26   - <v-navigation-drawer width="512" v-model="drawer" location="right" temporary floating>
  49 + <v-navigation-drawer
  50 + width="512"
  51 + v-model="drawer"
  52 + location="right"
  53 + temporary
  54 + floating
  55 + >
27 56 <v-list density="compact" nav>
28   - <v-list-item to="/" title="Home" value="home" @click="handleTabClick" color="blue-darken-1">
  57 + <v-list-item
  58 + to="/"
  59 + title="Home"
  60 + value="home"
  61 + @click="handleTabClick"
  62 + color="blue-darken-1"
  63 + >
29 64 </v-list-item>
30 65 <v-divider></v-divider>
31   - <v-list-item to="/products" title="Products" value="Products" @click="handleTabClick"
32   - color="blue-darken-1"></v-list-item>
  66 + <v-list-item
  67 + to="/products"
  68 + title="Products"
  69 + value="Products"
  70 + @click="handleTabClick"
  71 + color="blue-darken-1"
  72 + ></v-list-item>
33 73 <v-divider></v-divider>
34   - <v-list-item to="/about" title="About" value="about" @click="handleTabClick"
35   - color="blue-darken-1"></v-list-item>
  74 + <v-list-item
  75 + to="/about"
  76 + title="About"
  77 + value="about"
  78 + @click="handleTabClick"
  79 + color="blue-darken-1"
  80 + ></v-list-item>
36 81 <v-divider></v-divider>
37   - <v-list-item to="/contact" title="Contact" value="contact" @click="handleTabClick"
38   - color="blue-darken-1"></v-list-item>
  82 + <v-list-item
  83 + to="/contact"
  84 + title="Contact"
  85 + value="contact"
  86 + @click="handleTabClick"
  87 + color="blue-darken-1"
  88 + ></v-list-item>
39 89 <v-divider></v-divider>
40 90 <v-list-item title="Blog" value="blog" color="blue-darken-1">
41 91 <a href="http://blog.canrud.com/" target="" title="Blog">Blog</a>
... ... @@ -73,66 +123,66 @@
73 123 </template>
74 124  
75 125 <script setup lang="ts">
76   -import { ref, watchEffect } from 'vue'
77   -import { useProductListStore } from '@/stores/product_list'
78   -import { useRouter } from 'vue-router'
79   -import { useDialogStore } from '@/stores/dialog'
80   -import { useCategoryStore } from '@/stores/category'
81   -import { useDisplay } from 'vuetify'
  126 +import { ref, watchEffect } from "vue";
  127 +import { useProductListStore } from "@/stores/product_list";
  128 +import { useRouter } from "vue-router";
  129 +import { useDialogStore } from "@/stores/dialog";
  130 +import { useCategoryStore } from "@/stores/category";
  131 +import { useDisplay } from "vuetify";
82 132  
83   -const drawer = ref(false)
84   -const showSearch = ref(false)
  133 +const drawer = ref(false);
  134 +const showSearch = ref(false);
85 135  
86   -const productStore = useProductListStore()
87   -const categoryStore = useCategoryStore()
  136 +const productStore = useProductListStore();
  137 +const categoryStore = useCategoryStore();
88 138  
89   -const input = ref()
  139 +const input = ref();
90 140  
91   -const router = useRouter()
  141 +const router = useRouter();
92 142  
93 143 const handleKeyDown = (e: any) => {
94 144 if (e.keyCode == 13) {
95   - categoryStore.updateDisplay(!input.value)
96   - productStore.updateKeyword(input.value)
97   - productStore.updatePageNo(1)
98   - router.push('/products')
  145 + categoryStore.updateDisplay(!input.value);
  146 + productStore.updateKeyword(input.value);
  147 + productStore.updatePageNo(1);
  148 + router.push("/products");
99 149 }
100   -}
  150 +};
101 151  
102 152 const handleClick = () => {
103   - showSearch.value = false
104   -}
  153 + showSearch.value = false;
  154 +};
105 155  
106 156 const handleTabClick = () => {
107   - categoryStore.updateDisplay(true)
108   - productStore.updateKeyword('')
109   -}
  157 + categoryStore.updateDisplay(true);
  158 + productStore.updateKeyword("");
  159 +};
110 160  
111 161 watchEffect(() => {
112   - input.value = productStore.keyword
113   -})
  162 + input.value = productStore.keyword;
  163 +});
114 164  
115 165 onMounted(() => {
116 166 // 获取url的参数
117   - const url = window.location.href
118   - const index = url.indexOf('?')
  167 + const url = window.location.href;
  168 + const index = url.indexOf("?");
119 169 if (index !== -1) {
120   - const params = url.slice(index + 1).split('&')
121   - const obj: any = {}
  170 + const params = url.slice(index + 1).split("&");
  171 + const obj: any = {};
122 172 params.forEach((item) => {
123   - const arr = item.split('=')
124   - obj[arr[0]] = arr[1]
125   - })
  173 + const arr = item.split("=");
  174 + obj[arr[0]] = arr[1];
  175 + });
126 176 // 获取dialog的状态
127 177 if (obj.flag) {
128   - dialog.updateDialog(true)
  178 + dialog.updateDialog(true);
129 179 }
130 180  
131 181 if (obj.keyword) {
132   - productStore.updateKeyword(obj.keyword)
  182 + productStore.updateKeyword(obj.keyword);
133 183 }
134 184 }
135   -})
  185 +});
136 186 </script>
137 187  
138 188 <style lang="scss" scoped>
... ...
components/MobileProductDetail.vue
1 1 <template>
  2 + <div>
  3 + <!-- <v-breadcrumbs
  4 + divider="/"
  5 + dense
  6 + style="
  7 + padding-top: 8px;
  8 + font-size: 14px;
  9 + display: flex;
  10 + flex-wrap: wrap;
  11 + align-items: center;
  12 + "
  13 + >
  14 + <template v-for="(item, index) in items" :key="index">
  15 + <v-breadcrumbs-item
  16 + :disabled="item.disabled"
  17 + :href="item.href"
  18 + :class="{
  19 + 'breadcrumb-last': index === items.length - 1,
  20 + }"
  21 + :style="
  22 + index === items.length - 1
  23 + ? 'color: black; font-weight: bold;'
  24 + : 'color:black'
  25 + "
  26 + >
  27 + {{ item.title }}
  28 + </v-breadcrumbs-item>
  29 + <span
  30 + v-if="index < items.length - 1"
  31 + class="breadcrumb-divider"
  32 + style="margin: 0 4px; font-size: 14px; color: gray"
  33 + >/</span
  34 + >
  35 + </template>
  36 + </v-breadcrumbs> -->
  37 + <v-breadcrumbs
  38 + divider="/"
  39 + dense
  40 + style="
  41 + padding-top: 8px;
  42 + font-size: 14px;
  43 + display: flex;
  44 + flex-wrap: wrap;
  45 + align-items: center;
  46 + "
  47 + >
  48 + <template v-for="(item, index) in items" :key="index">
  49 + <v-breadcrumbs-item
  50 + :disabled="item.disabled"
  51 + :href="item.href"
  52 + style="color: #1e88e5"
  53 + >
  54 + {{ item.title }}
  55 + </v-breadcrumbs-item>
  56 + <span
  57 + v-if="index < items.length - 1"
  58 + class="breadcrumb-divider"
  59 + style="margin: 0 4px; font-size: 14px; color: gray"
  60 + >/</span
  61 + >
  62 + </template>
  63 + </v-breadcrumbs>
  64 + </div>
  65 +
2 66 <v-container class="ma-0 pa-0 bg-grey-lighten-5">
3   - <v-carousel class="tw-float-left" height="450" v-model="slide" hide-delimiter-background>
4   - <v-carousel-item cover v-for="(slide, i) in info.productimageliststore" :src="slide.url" :key="i"
5   - :alt="info.name">
  67 + <v-carousel
  68 + class="tw-float-left"
  69 + height="450"
  70 + v-model="slide"
  71 + hide-delimiter-background
  72 + >
  73 + <v-carousel-item
  74 + cover
  75 + v-for="(slide, i) in info.productimageliststore"
  76 + :src="slide.url"
  77 + :key="i"
  78 + :alt="info.name"
  79 + >
6 80 </v-carousel-item>
7 81 </v-carousel>
8   - <div class="mb-3 bg-white mb-sm-10 text-h4 font-weight-medium pa-4">
  82 + <h1 class="mb-3 bg-white mb-sm-10 text-h4 font-weight-medium pa-4">
9 83 {{ info.name }}
10   - </div>
  84 + </h1>
11 85 <v-row class="mx-4 mt-0 mb-4 bg-white rounded-lg">
12 86 <v-col cols="6">
13 87 <div class="text-body-1 text-grey-darken-3">Brand:</div>
... ... @@ -30,10 +104,21 @@
30 104 <div class="text-h6">{{ info.basecore3 }}</div>
31 105 </v-col>
32 106 </v-row>
33   - <div v-if="info.ticketTypes?.length" class="py-4 mx-4 bg-white rounded-lg tw-flex tw-flex-wrap tw-justify-around">
34   - <v-sheet v-for="item in info.ticketTypes || []" :key="item.rank" rounded="lg" border class="tw-w-[40%] mb-4">
  107 + <div
  108 + ref="ticketDiv"
  109 + v-if="info.ticketTypes?.length"
  110 + class="py-0 mx-4 rounded-lg tw-flex tw-flex-wrap tw-justify-around"
  111 + >
  112 + <!-- <v-sheet
  113 + v-for="item in info.ticketTypes || []"
  114 + :key="item.rank"
  115 + rounded="lg"
  116 + border
  117 + class="tw-w-[40%] mb-4"
  118 + >
35 119 <div
36   - 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">
  120 + 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"
  121 + >
37 122 {{ item.typeName }}
38 123 </div>
39 124 <div class="tw-h-[82px] pa-2">
... ... @@ -42,17 +127,91 @@
42 127 {{ item.rank }}
43 128 </div>
44 129 </div>
45   - </v-sheet>
46   - <v-btn size="large" color="blue-darken-1" @click="router.push('/contact')">
  130 + </v-sheet> -->
  131 + <v-table
  132 + density="comfortable"
  133 + style="width: 100%"
  134 + class="table1 tw-mt-[0px] tw-overflow-x-auto"
  135 + v-if="info.ticketTypes?.length"
  136 + >
  137 + <thead>
  138 + <tr class="bg-grey-lighten-3">
  139 + <th class="text-left headerBorder text-grey-darken-1">Name/Code</th>
  140 + <th class="text-left headerBorder text-grey-darken-1">
  141 + Specification
  142 + </th>
  143 + <th
  144 + v-if="info.priceShow !== undefined && info.priceShow"
  145 + class="text-left headerBorder text-grey-darken-1"
  146 + >
  147 + Price
  148 + </th>
  149 + <!-- <th class="text-left headerBorder text-grey-darken-1">Actions</th> -->
  150 + </tr>
  151 + </thead>
  152 + <tbody>
  153 + <tr
  154 + class="tr"
  155 + v-for="item in info.ticketTypes || []"
  156 + :key="item.rank"
  157 + >
  158 + <td class="td text-grey-darken-4 font-weight-medium">
  159 + {{ item.rank }}
  160 + </td>
  161 + <td class="td text-grey-darken-4 font-weight-medium">
  162 + {{ item.typeName }}
  163 + </td>
  164 + <td v-if="item.priceShow" class="td">$ {{ item.price }}</td>
  165 + <!-- <td class="td">
  166 + <v-btn
  167 + size="small"
  168 + color="blue-darken-1"
  169 + @click="router.push('/contact')"
  170 + >
  171 + Quote
  172 + </v-btn>
  173 + </td> -->
  174 + </tr>
  175 + </tbody>
  176 + </v-table>
  177 + <!-- <div v-for="item in info.ticketTypes || []"
  178 + :key="item.rank">
47 179  
  180 + </div> -->
  181 + <!-- <v-btn
  182 + style="margin-top: 6px"
  183 + color="blue-darken-1"
  184 + @click="router.push('/contact')"
  185 + >
  186 + Quotation Inquiry
  187 + </v-btn> -->
  188 + <!-- <div
  189 + v-if="showBottomButton"
  190 + ref="bottomButton"
  191 + :class="['button-container', { fixed: isFixed }]"
  192 + > -->
  193 + <v-btn
  194 + style="margin-top: 12px"
  195 + color="blue-darken-1 tw-sticky"
  196 + @click="router.push('/contact')"
  197 + >
48 198 Quotation Inquiry
49 199 </v-btn>
  200 + <!-- </div> -->
50 201 </div>
51 202 <!-- <v-dialog v-model="dialog" activator="parent" width="auto">
52 203 <v-card> Contact us Email: contact@canrd.com QQ: 3003597584 / 2902385824 </v-card>
53 204 </v-dialog> -->
54 205 <div class="tw-pb-[64px] ma-4 rounded-lg">
55   - <v-tabs class="tabs" v-model="tab" bg-color="#fff" slider-color="#1d89e4" selected-class="active">
  206 + <v-tabs
  207 + class="tabs"
  208 + v-model="tab"
  209 + style="margin-top: 25px; margin-bottom: 20px"
  210 + color="white"
  211 + bg-color="#eeeeee"
  212 + slider-color="blue-lighten-1"
  213 + selected-class="active"
  214 + >
56 215 <v-tab :value="1">Product Details</v-tab>
57 216 <v-tab :value="2">Specification</v-tab>
58 217 <!-- <v-tab :value="3">商品百科</v-tab> -->
... ... @@ -69,7 +228,7 @@
69 228 <v-divider class="tw-mb-[12px]"></v-divider>
70 229 <div v-html="info.physicalproperty"></div>
71 230 </div>
72   - <div v-if="info.advantage" class="tw-mb-[24px]">
  231 + <div v-if="info.storage" class="tw-mb-[24px]">
73 232 <div class="py-2 pl-2 text-h6">Storage</div>
74 233 <v-divider class="tw-mb-[12px]"></v-divider>
75 234 <div v-html="info.storage"></div>
... ... @@ -79,7 +238,7 @@
79 238 <v-divider class="tw-mb-[12px]"></v-divider>
80 239 <div v-html="info.introduction"></div>
81 240 </div>
82   - <div v-if="info.advantage" class="tw-mb-[24px]">
  241 + <div v-if="info.description" class="tw-mb-[24px]">
83 242 <div class="py-2 pl-2 text-h6">Description</div>
84 243 <v-divider class="tw-mb-[12px]"></v-divider>
85 244 <div v-html="info.description"></div>
... ... @@ -88,8 +247,12 @@
88 247 <v-window-item key="2" :value="2">
89 248 <v-table density="compact" class="table2">
90 249 <tbody>
91   - <tr class="tr" v-for="item in info.productAttributeList || []" :key="item.name">
92   - <td class="td tw-w-[400px]">{{ item.name }}</td>
  250 + <tr
  251 + class="tr"
  252 + v-for="item in info.productAttributeList || []"
  253 + :key="item.name"
  254 + >
  255 + <td class="td tw-w-[350px]">{{ item.name }}</td>
93 256 <td class="td">{{ item.value }}</td>
94 257 </tr>
95 258 </tbody>
... ... @@ -98,33 +261,553 @@
98 261 <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
99 262 </v-window>
100 263 </div>
  264 + <div class="tw-pb-[64px] ma-4 rounded-lg" style="padding-bottom: 30px">
  265 + <v-tabs
  266 + class="tabs"
  267 + v-model="tabProduct"
  268 + color="white"
  269 + bg-color="#eeeeee"
  270 + slider-color="blue-lighten-1"
  271 + selected-class="active"
  272 + style="margin-bottom: 20px"
  273 + >
  274 + <v-tab :value="1">Best Sellers</v-tab>
  275 + <v-tab :value="2">Related Products</v-tab>
  276 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  277 + </v-tabs>
  278 + <v-window v-model="tabProduct">
  279 + <v-window-item key="1" :value="1">
  280 + <div class="tw-text-center" v-if="hotLoading && isMobile()">
  281 + <v-progress-circular
  282 + color="blue-lighten-2"
  283 + indeterminate
  284 + size="64"
  285 + class="tw-m-auto"
  286 + ></v-progress-circular>
  287 + </div>
  288 + <v-item-group multiple v-if="isMobile()">
  289 + <v-row v-if="!hotLoading">
  290 + <v-col
  291 + v-for="(item, i) in recommendImagesHot"
  292 + :key="i"
  293 + cols="6"
  294 + lg="3"
  295 + md="4"
  296 + sm="6"
  297 + >
  298 + <div v-if="item !== null">
  299 + <v-card
  300 + :elevation="4"
  301 + class="mx-auto"
  302 + :href="item[0].productUrl"
  303 + >
  304 + <!-- 设置 eager 属性,确保图片直接加载 -->
  305 + <v-img
  306 + :src="item[0].url"
  307 + :alt="item[0].name"
  308 + eager
  309 + class="d-block"
  310 + />
  311 + <v-card-text class="tw-text-left font-weight-medium title">
  312 + <h4>{{ item[0].name }}</h4>
  313 + </v-card-text>
  314 + </v-card>
  315 + </div>
  316 + </v-col>
  317 + </v-row>
  318 + <!-- <div
  319 + v-if="!hotTotal"
  320 + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]"
  321 + >
  322 + no data
  323 + </div> -->
  324 + </v-item-group>
  325 + <v-row v-if="isMobile()">
  326 + <v-col>
  327 + <v-pagination
  328 + :size="isMobile() ? 'small' : 'default'"
  329 + v-if="hotTotal"
  330 + v-model="currentIndexHot"
  331 + @update:modelValue="toggleRowMobileHot"
  332 + :length="hotLength"
  333 + rounded="0"
  334 + class="tw-float-right tw-mt-[32px]"
  335 + total-visible="5"
  336 + ></v-pagination></v-col
  337 + ></v-row>
  338 + </v-window-item>
  339 + <!-- best sellers -->
  340 + <v-window-item key="2" :value="2">
  341 + <div class="tw-text-center" v-if="loading && isMobile()">
  342 + <v-progress-circular
  343 + color="blue-lighten-2"
  344 + indeterminate
  345 + size="64"
  346 + class="tw-m-auto"
  347 + ></v-progress-circular>
  348 + </div>
  349 + <v-item-group multiple v-if="isMobile()">
  350 + <v-row v-if="!loading">
  351 + <v-col
  352 + v-for="(item, i) in recommendImages"
  353 + :key="i"
  354 + cols="6"
  355 + lg="3"
  356 + md="4"
  357 + sm="6"
  358 + >
  359 + <div v-if="item !== null">
  360 + <v-card
  361 + :elevation="4"
  362 + class="mx-auto"
  363 + :href="item[0].productUrl"
  364 + >
  365 + <!-- 设置 eager 属性,确保图片直接加载 -->
  366 + <v-img
  367 + :src="item[0].url"
  368 + :alt="item[0].name"
  369 + eager
  370 + class="d-block"
  371 + />
  372 + <v-card-text class="tw-text-left font-weight-medium title">
  373 + <h4>{{ item[0].name }}</h4>
  374 + </v-card-text>
  375 + </v-card>
  376 + </div>
  377 + </v-col>
  378 + </v-row>
  379 + <!-- <div
  380 + v-if="!hotTotal"
  381 + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]"
  382 + >
  383 + no data
  384 + </div> -->
  385 + </v-item-group>
  386 + <v-row v-if="isMobile()">
  387 + <v-col>
  388 + <v-pagination
  389 + :size="isMobile() ? 'small' : 'default'"
  390 + v-if="total"
  391 + v-model="currentIndex"
  392 + @update:modelValue="toggleRowMobile"
  393 + :length="length"
  394 + rounded="0"
  395 + class="tw-float-right tw-mt-[32px]"
  396 + total-visible="5"
  397 + ></v-pagination></v-col
  398 + ></v-row>
  399 + </v-window-item>
  400 + <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
  401 + </v-window>
  402 + </div>
  403 + <div class="tw-pb-[64px] ma-4 rounded-lg">
  404 + <v-tabs
  405 + class="tabs"
  406 + v-model="tabJournal"
  407 + color="white"
  408 + bg-color="#eeeeee"
  409 + slider-color="blue-lighten-1"
  410 + selected-class="active"
  411 + >
  412 + <v-tab :value="1">Product-related Journals</v-tab>
  413 + </v-tabs>
  414 + <v-window v-model="tabJournal">
  415 + <v-window-item key="1" :value="1">
  416 + <v-list>
  417 + <v-list-item
  418 + v-for="item in info.journals"
  419 + :key="item.id"
  420 + @click="navigateToUrl(item.link)"
  421 + @mouseenter="hoveredItem = item.id"
  422 + @mouseleave="hoveredItem = null"
  423 + >
  424 + <v-list-item-title>
  425 + <span
  426 + :class="['title', { 'full-title': hoveredItem === item.id }]"
  427 + >
  428 + {{ item.title }}
  429 + </span>
  430 + </v-list-item-title>
  431 + </v-list-item>
  432 + </v-list>
  433 + </v-window-item>
  434 + </v-window>
  435 + </div>
  436 + <!-- Basic use -->
  437 + <div class="social-share-container">
  438 + <SocialShare
  439 + network="facebook"
  440 + :styled="true"
  441 + :label="true"
  442 + :title="info.name"
  443 + :url="currentUrl"
  444 + style="color: #1e88e5; width: 140px"
  445 + />
  446 +
  447 + <SocialShare
  448 + network="twitter"
  449 + :styled="true"
  450 + :label="true"
  451 + :title="info.name"
  452 + :url="currentUrl"
  453 + style="color: #1e88e5; width: 140px"
  454 + />
  455 +
  456 + <SocialShare
  457 + network="linkedin"
  458 + :styled="true"
  459 + :label="true"
  460 + :title="info.name"
  461 + :url="currentUrl"
  462 + style="color: #1e88e5; width: 140px"
  463 + />
  464 + </div>
101 465 </v-container>
102 466 </template>
103 467  
104 468 <script setup lang="ts">
105   -import type { Product } from '~/type'
106   -import { onMounted, ref } from 'vue'
107   -import { useDialogStore } from '~/stores/dialog'
108   -const dialogStore = useDialogStore()
  469 +import type { Product } from "~/type";
  470 +import { isMobile } from "~/utils";
  471 +import { defineProps, ref } from "vue";
  472 +import { useDialogStore } from "~/stores/dialog";
  473 +const dialogStore = useDialogStore();
109 474  
110 475 const props = defineProps<{
111   - info: Product
112   -}>()
113   -const info = props.info
  476 + info: Product;
  477 +}>();
  478 +const info = props.info;
114 479 // onMounted(() => {
115 480 // dialogStore.updateDialog(true)
116 481 // })
117   -const tab = ref(0)
118   -const slide = ref(0)
119   -const router = useRouter()
  482 +const tab = ref();
  483 +const tabProduct = ref();
  484 +const tabJournal = ref();
  485 +const slide = ref(0);
  486 +const router = useRouter();
  487 +const href1 = ref("/products");
  488 +const href2 = ref("/products");
  489 +const routeQuery = useRouteQuery();
  490 +const productStore = useProductListStore();
  491 +const currentUrl = ref("https://www.canrud.com/products");
  492 +const hotLoading = ref(false);
  493 +const loading = ref(false);
  494 +const maxPage = ref(1);
  495 +const maxPageRe = ref(1);
  496 +const tabRecom = ref();
  497 +const recommendList = ref();
  498 +const recommendListHot = ref();
  499 +const recommendImages = ref();
  500 +const recommendImagesHot = ref();
  501 +const currentIndex = ref(1);
  502 +const currentIndexHot = ref(1);
  503 +const total = ref(3);
  504 +const hotTotal = ref(10);
  505 +const isOrNotMobile = isMobile();
  506 +const hoveredItem = ref(null);
  507 +
  508 +const navigateToUrl = (url) => {
  509 + window.open(url); // 在新标签页中打开链接
  510 +};
  511 +const loadProducts = async () => {
  512 + const pageSize = 4;
  513 + loading.value = true;
  514 +
  515 + let { data: resData } = await useAsyncData(
  516 + "list",
  517 + () =>
  518 + $fetch("/shop/product/list", {
  519 + method: "GET",
  520 + params: {
  521 + pageNo: currentIndex.value,
  522 + pageSize: pageSize,
  523 + ids: info.relatedProductIds,
  524 + },
  525 + }),
  526 + {
  527 + server: true, // 仅在服务器端获取数据
  528 + }
  529 + );
  530 + total.value = resData.value.data.total;
  531 + if (total.value > 50) {
  532 + total.value = 50;
  533 + }
  534 + recommendList.value = resData.value.data.records;
  535 + maxPageRe.value = resData.value.data.pages;
  536 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  537 + recommendImages.value = Array.from({ length: 4 }).map((_, index) => {
  538 + const item = recommendList.value[index];
  539 + if (!item) {
  540 + return null;
  541 + }
  542 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  543 + if (typeof item.productimageliststore === "string") {
  544 + try {
  545 + item.productimageliststore = JSON.parse(item.productimageliststore);
  546 + } catch (error) {
  547 + item.productimageliststore = []; // 解析失败时,设置为空数组
  548 + }
  549 + }
  550 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  551 + (productItem: ProductImage) => ({
  552 + ...productItem,
  553 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  554 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  555 + name: item.name,
  556 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  557 + })
  558 + ));
  559 + return ree;
  560 + });
  561 + loading.value = false;
  562 +};
  563 +const loadHotProducts = async () => {
  564 + const pageSizeHot = ref(5);
  565 + if (isOrNotMobile) {
  566 + pageSizeHot.value = 4;
  567 + }
  568 + hotLoading.value = true;
  569 +
  570 + let { data: hotProducts } = await useAsyncData(
  571 + "hotProducts",
  572 + () =>
  573 + $fetch("/shop/product/hotProducts", {
  574 + method: "GET",
  575 + params: {
  576 + pageNo: currentIndexHot.value,
  577 + pageSize: pageSizeHot.value,
  578 + },
  579 + }),
  580 + {
  581 + server: true, // 仅在服务器端获取数据
  582 + }
  583 + );
  584 + hotTotal.value = hotProducts.value.data.total;
  585 + recommendListHot.value = hotProducts.value.data.records;
  586 + maxPage.value = hotProducts.value.data.pages;
  587 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  588 + recommendImagesHot.value = Array.from({ length: pageSizeHot.value }).map(
  589 + (_, index) => {
  590 + const item = recommendListHot.value[index];
  591 + if (!item) {
  592 + return null;
  593 + }
  594 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  595 + if (typeof item.productimageliststore === "string") {
  596 + try {
  597 + item.productimageliststore = JSON.parse(item.productimageliststore);
  598 + } catch (error) {
  599 + item.productimageliststore = []; // 解析失败时,设置为空数组
  600 + }
  601 + }
  602 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  603 + (productItem: ProductImage) => ({
  604 + ...productItem,
  605 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  606 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  607 + name: item.name,
  608 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  609 + })
  610 + ));
  611 + return ree;
  612 + }
  613 + );
  614 + hotLoading.value = false;
  615 +};
  616 +
  617 +const toggleRowMobile = (value: number) => {
  618 + currentIndex.value = value;
  619 +};
  620 +const toggleRowMobileHot = (value: number) => {
  621 + currentIndexHot.value = value;
  622 +};
  623 +
  624 +watch(currentIndexHot, (newIndex) => {
  625 + loadHotProducts(); // Call loadHotProducts when currentIndex changes
  626 +});
  627 +watch(currentIndex, (newIndex) => {
  628 + loadProducts(); // Call loadHotProducts when currentIndex changes
  629 +});
  630 +// Initial load of hot products
  631 +await loadHotProducts(); // Load hot products the first time
  632 +await loadProducts(); // Load hot products the first time
  633 +
  634 +interface BreadcrumbItem {
  635 + title: string; // 标题
  636 + disabled: boolean; // 是否禁用
  637 + href: string; // 链接地址
  638 +}
  639 +
  640 +// 示例数据
  641 +const items = ref<BreadcrumbItem[]>([
  642 + {
  643 + title: "Products",
  644 + disabled: false,
  645 + href: "https://www.canrud.com/products",
  646 + },
  647 + {
  648 + title: "CATEGORY",
  649 + disabled: false,
  650 + href: href1.value,
  651 + },
  652 + {
  653 + title: "Not specified",
  654 + disabled: false,
  655 + href: href2.value,
  656 + },
  657 + {
  658 + title: "Not specified",
  659 + disabled: false,
  660 + href: "/products",
  661 + },
  662 +]);
  663 +watchEffect(() => {
  664 + currentUrl.value = "https://www.canrud.com/products/detail/" + info.id;
  665 + if (info?.productCrumbsVO?.category1 && productStore.keyword) {
  666 + items.value[1].title = info.productCrumbsVO.category1;
  667 + items.value[1].href =
  668 + "https://www.canrud.com/products?categories=" +
  669 + info.productCrumbsVO.category1;
  670 + href1.value =
  671 + "https://www.canrud.com/products?categories=" +
  672 + info.productCrumbsVO.category1;
  673 + if (info?.productCrumbsVO?.category2) {
  674 + items.value[2].title = info.productCrumbsVO.category2;
  675 + href2.value = href1.value + "," + info.productCrumbsVO.category2;
  676 + items.value[2].href = href1.value + "," + info.productCrumbsVO.category2;
  677 + }
  678 + if (info?.productCrumbsVO?.function) {
  679 + // items.value.push({
  680 + // title: info.productCrumbsVO.function,
  681 + // disabled: false,
  682 + // href: href2.value + "&function=" + info.productCrumbsVO.function,
  683 + // });
  684 + items.value[3].title = info.productCrumbsVO.function;
  685 + items.value[3].href =
  686 + href2.value + "&function=" + info.productCrumbsVO.function;
  687 + }
  688 + } else if (routeQuery.categories) {
  689 + if (!routeQuery.categories.includes("Energy materials")) {
  690 + routeQuery.updateFunction("Not specified");
  691 + }
  692 + const categories = routeQuery.categories.split(",");
  693 + const mainCategory = categories[0].trim(); // 取第一个值
  694 + const subCategoryName = ref(
  695 + categories[1] ? categories[1].trim() : "Not specified"
  696 + ); // 取第二个值(如果存在)
  697 + if (subCategoryName.value == "Accessories & fixtures") {
  698 + subCategoryName.value = encodeURIComponent("Accessories & fixtures");
  699 + items.value[2].title = "Accessories & fixtures";
  700 + } else {
  701 + items.value[2].title = subCategoryName.value;
  702 + }
  703 + items.value[1].title = mainCategory;
  704 + items.value[1].href =
  705 + "https://www.canrud.com/products?categories=" + mainCategory;
  706 + href1.value = "https://www.canrud.com/products?categories=" + mainCategory;
  707 + // items.value[1].title = subCategoryName.value;
  708 + href2.value = href1.value + "," + subCategoryName.value;
  709 + items.value[2].href = href1.value + "," + subCategoryName.value;
  710 + if (routeQuery?.selectedFunction) {
  711 + // items.value.push({
  712 + // title: routeQuery.selectedFunction,
  713 + // disabled: false,
  714 + // href: href2.value + "&function=" + routeQuery.selectedFunction,
  715 + // });
  716 + items.value[3].title = routeQuery.selectedFunction;
  717 + items.value[3].href =
  718 + href2.value + "&function=" + routeQuery.selectedFunction;
  719 + // routeQuery.updateFunction(null);
  720 + }
  721 + } else if (info?.productCrumbsVO?.category1) {
  722 + items.value[1].title = info.productCrumbsVO.category1;
  723 + items.value[1].href =
  724 + "https://www.canrud.com/products?categories=" +
  725 + info.productCrumbsVO.category1;
  726 + href1.value =
  727 + "https://www.canrud.com/products?categories=" +
  728 + info.productCrumbsVO.category1;
  729 + if (info?.productCrumbsVO?.category2) {
  730 + items.value[2].title = info.productCrumbsVO.category2;
  731 + href2.value = href1.value + "," + info.productCrumbsVO.category2;
  732 + items.value[2].href = href1.value + "," + info.productCrumbsVO.category2;
  733 + }
  734 + if (info?.productCrumbsVO?.function) {
  735 + // items.value.push({
  736 + // title: info.productCrumbsVO.function,
  737 + // disabled: false,
  738 + // href: href2.value + "&function=" + info.productCrumbsVO.function,
  739 + // });
  740 + items.value[3].title = info.productCrumbsVO.function;
  741 + items.value[3].href =
  742 + href2.value + "&function=" + info.productCrumbsVO.function;
  743 + }
  744 + }
  745 +});
  746 +const length = computed(() => (total.value ? Math.ceil(total.value / 4) : 0));
  747 +const hotLength = computed(() =>
  748 + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0
  749 +);
120 750 </script>
121 751  
122 752 <style lang="scss" scoped>
  753 +// .tabs {
  754 +// border-bottom: 2px solid #cbd9e4;
  755 +// }
  756 +
  757 +// .active {
  758 +// background-color: #fff;
  759 +// }
  760 +
123 761 .tabs {
124   - border-bottom: 2px solid #cbd9e4;
  762 + border-bottom: 2px solid #1f88e5;
125 763 }
126 764  
127 765 .active {
128   - background-color: #fff;
  766 + background-color: #1086e8;
  767 +}
  768 +
  769 +.tw-sticky {
  770 + position: sticky;
  771 +}
  772 +
  773 +// .button-container {
  774 +// margin-top: 6px;
  775 +// display: flex;
  776 +// justify-content: center;
  777 +// position: absolute;
  778 +// width: 100%;
  779 +// bottom: auto; /* 默认情况下不固定 */
  780 +// }
  781 +
  782 +// .button-container.fixed {
  783 +// position: fixed;
  784 +// bottom: 16px; /* 固定在页面底部 */
  785 +// left: 0;
  786 +// z-index: 1000;
  787 +// background-color: rgba(255, 255, 255, 0.9);
  788 +// box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.1);
  789 +// padding: 8px 0;
  790 +// }
  791 +.social-share-container {
  792 + display: flex;
  793 + flex-direction: row; /* 父容器横向排列 */
  794 + gap: 1rem; /* 设置每个分享项之间的间距 */
  795 + margin-left: 10px;
  796 + margin-bottom: 10px;
  797 +}
  798 +
  799 +.social-share-item {
  800 + display: flex;
  801 + flex-direction: row; /* 每个分享项横向排列 */
  802 + align-items: center; /* 垂直居中对齐 */
  803 + gap: 0.5rem; /* 标题与按钮之间的间距 */
  804 +}
  805 +
  806 +.social-share-title {
  807 + font-size: 1rem;
  808 + font-weight: bold;
  809 +}
  810 +.v-card {
  811 + transition: all 0.3s ease-in-out;
129 812 }
130 813 </style>
... ...
components/ProductDetail.vue
1 1 <template>
2 2 <v-container>
  3 + <div>
  4 + <!-- <v-breadcrumbs divider="/" style="padding-top: 0">
  5 + <template v-for="(item, index) in items" :key="index">
  6 + <v-breadcrumbs-item
  7 + :disabled="item.disabled"
  8 + :href="item.href"
  9 + :class="{
  10 + 'breadcrumb-disabled': item.disabled,
  11 + 'breadcrumb-active': !item.disabled,
  12 + 'breadcrumb-last': index === items.length - 1,
  13 + }"
  14 + :style="
  15 + index === items.length - 1
  16 + ? 'color: black;font-size:15px'
  17 + : 'font-size:15px;'
  18 + "
  19 + >
  20 + {{ item.title }}
  21 + </v-breadcrumbs-item>
  22 + <span v-if="index < items.length - 1" class="breadcrumb-divider"
  23 + >/</span
  24 + >
  25 + </template>
  26 + </v-breadcrumbs> -->
  27 + <v-breadcrumbs divider="/" style="padding-top: 0">
  28 + <template v-for="(item, index) in items" :key="index">
  29 + <v-breadcrumbs-item
  30 + :disabled="item.disabled"
  31 + :href="item.href"
  32 + :class="{
  33 + 'breadcrumb-disabled': item.disabled,
  34 + 'breadcrumb-active': !item.disabled,
  35 + }"
  36 + style="font-size: 14px"
  37 + >
  38 + {{ item.title }}
  39 + </v-breadcrumbs-item>
  40 + <!-- 添加分隔符,排除最后一个 item -->
  41 + <span
  42 + v-if="index < items.length - 1"
  43 + class="breadcrumb-divider"
  44 + style="color: gray"
  45 + >/</span
  46 + >
  47 + </template>
  48 + </v-breadcrumbs>
  49 + </div>
3 50 <v-row class="mb-16 ma-0">
4   - <v-col cols="12" sm="5">
  51 + <v-col cols="12" sm="5" class="carousel-container">
5 52 <v-carousel
6   - class="tw-float-left"
  53 + class="tw-float-left tw-sticky tw-top-[16px]"
7 54 height="450"
8 55 v-model="slide"
9 56 hide-delimiter-background
  57 + style="top: 16px"
10 58 >
11 59 <v-carousel-item
12 60 cover
... ... @@ -18,33 +66,34 @@
18 66 </v-carousel-item>
19 67 </v-carousel>
20 68 </v-col>
21   - <v-col cols="12" sm="7">
22   - <v-row class="bg-white mb-sm-10 text-h4 font-weight-medium">
  69 + <v-col cols="12" sm="7" class="table-container">
  70 + <!-- <v-row class="bg-white mb-sm-10 text-h4 font-weight-medium">
23 71 <v-col>{{ info.name }}</v-col>
24   - </v-row>
  72 + </v-row> -->
  73 + <h1 class="tw-m-[12px] tw-mb-[36px]">{{ info.name }}</h1>
25 74 <div class="tw-flex tw-flex-wrap">
26   - <div class="tw-w-1/2">
27   - <span class="tw-leading-[10px] tw-m-[16px]">
  75 + <div class="tw-w-1/2 tw-mb-[12px]">
  76 + <span class="tw-leading-[10px] tw-m-[12px]">
28 77 Brand:{{ info.brandName }}
29 78 </span>
30 79 </div>
31 80 <div class="tw-w-1/2 tw-mb-[12px]">
32   - <span class="tw-leading-[10px] tw-m-[16px]"
  81 + <span class="tw-leading-[10px] tw-m-[12px]"
33 82 >Product Model:{{ info.model }}</span
34 83 >
35 84 </div>
36 85 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename1">
37   - <span class="tw-leading-[10px] tw-m-[16px]"
  86 + <span class="tw-leading-[10px] tw-m-[12px]"
38 87 >{{ info.basename1 }}:{{ info.basecore1 }}</span
39 88 >
40 89 </div>
41 90 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename2">
42   - <span class="tw-leading-[10px] tw-m-[16px]"
  91 + <span class="tw-leading-[10px] tw-m-[12px]"
43 92 >{{ info.basename2 }}:{{ info.basecore2 }}</span
44 93 >
45 94 </div>
46 95 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename3">
47   - <span class="tw-leading-[10px] tw-m-[16px]"
  96 + <span class="tw-leading-[10px] tw-m-[12px]"
48 97 >{{ info.basename3 }}:{{ info.basecore3 }}</span
49 98 >
50 99 </div>
... ... @@ -60,15 +109,21 @@
60 109 :key="index"
61 110 class="tw-w-1/2 tw-mb-[12px]"
62 111 >
63   - <span class="tw-leading-[10px] tw-m-[16px]"
  112 + <span
  113 + class="tw-leading-normal tw-m-0 tw-block tw-max-w-full tw-break-words tw-ml-[12px]"
64 114 >{{ attribute.name }}:{{ attribute.value }}</span
65 115 >
66 116 </div>
67 117 </div>
68   - <v-table
  118 + <!-- <v-table
69 119 density="comfortable"
70 120 class="table1 tw-mt-[32px]"
71 121 v-if="info.ticketTypes?.length"
  122 + > -->
  123 + <v-table
  124 + density="comfortable"
  125 + class="table1 tw-mt-[32px] tw-overflow-x-auto"
  126 + v-if="info.ticketTypes?.length"
72 127 >
73 128 <thead>
74 129 <tr class="bg-grey-lighten-3">
... ... @@ -120,63 +175,326 @@
120 175 </v-dialog> -->
121 176 </v-col>
122 177 </v-row>
123   - <div class="tw-pb-[64px]">
124   - <v-tabs
125   - class="tabs"
126   - v-model="tab"
127   - color="white"
128   - bg-color="#eeeeee"
129   - slider-color="blue-lighten-1"
130   - selected-class="active"
  178 + <v-tabs
  179 + class="tabs"
  180 + v-model="tabRecomHot"
  181 + style="margin-top: 25px"
  182 + color="white"
  183 + bg-color="#eeeeee"
  184 + slider-color="blue-lighten-1"
  185 + selected-class="active"
  186 + v-if="recommendImagesHot[0] !== null"
  187 + >
  188 + <v-tab :value="1">Best Sellers</v-tab>
  189 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  190 + </v-tabs>
  191 + <div id="image-container" v-if="recommendImagesHot[0] !== null">
  192 + <div class="recommend-left-box">
  193 + <v-img
  194 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/76c987e13a334be481a346c19c2284f3qy4tjnxps7.png"
  195 + alt="往左移"
  196 + class="recommend-img-left"
  197 + @click="toggleRowLeft"
  198 + />
  199 + </div>
  200 + <div class="image-row" id="row1">
  201 + <!-- <img
  202 + v-for="(imageObj, index) in recommendImages.slice(0, 5)"
  203 + :key="'row1-' + index"
  204 + :src="imageObj[0]?.url"
  205 + :alt="'Image ' + (index + 1)"
  206 + style="margin: 0 5px; width: 200px; height: 200px"
  207 + /> -->
  208 + <div
  209 + v-for="(imageObj, index) in recommendImagesHot"
  210 + :key="'row1-' + index"
  211 + class="imageTotal"
  212 + >
  213 + <a v-if="imageObj" :href="imageObj[0]?.productUrl" target="_blank">
  214 + <img
  215 + :src="imageObj[0]?.url"
  216 + :alt="'Image ' + (index + 1)"
  217 + class="item-imgHot"
  218 + />
  219 + <span class="image-name">
  220 + {{ imageObj[0]?.name }}
  221 + </span>
  222 + </a>
  223 + <div v-else style="width: 200px; height: 200px"></div>
  224 + </div>
  225 + </div>
  226 + <div class="recommend-right-box">
  227 + <v-img
  228 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/b5daa0a8f2f140f5b406e984c730a453iznzlekysg.png"
  229 + alt="往右移"
  230 + class="recommend-img-right"
  231 + @click="toggleRowRight"
  232 + />
  233 + </div>
  234 + </div>
  235 +
  236 + <div style="display: flex; width: 100%; box-sizing: border-box">
  237 + <div
  238 + class="tw-pb-[64px]"
  239 + style="
  240 + width: 70%;
  241 + margin-right: 10px;
  242 + display: flex;
  243 + flex-direction: column;
  244 + flex-wrap: wrap;
  245 + overflow: auto;
  246 + "
131 247 >
132   - <v-tab :value="1">Product Details</v-tab>
133   - <v-tab :value="2">Specification</v-tab>
134   - <!-- <v-tab :value="3">商品百科</v-tab> -->
135   - </v-tabs>
136   - <v-window v-model="tab" class="tw-p-[24px]">
137   - <v-window-item key="1" :value="1">
138   - <div v-if="info.advantage" class="tw-mb-[24px]">
139   - <div class="text-h6">Advantage</div>
140   - <v-divider class="tw-mb-[12px]"></v-divider>
141   - <div v-html="info.advantage"></div>
142   - </div>
143   - <div v-if="info.physicalproperty" class="tw-mb-[24px]">
144   - <div class="text-h6">Physical Property</div>
145   - <v-divider class="tw-mb-[12px]"></v-divider>
146   - <div v-html="info.physicalproperty"></div>
147   - </div>
148   - <div v-if="info.advantage" class="tw-mb-[24px]">
149   - <div class="text-h6">Storage</div>
150   - <v-divider class="tw-mb-[12px]"></v-divider>
151   - <div v-html="info.storage"></div>
152   - </div>
153   - <div v-if="info.introduction" class="tw-mb-[24px]">
154   - <div class="text-h6">Introduction</div>
155   - <v-divider class="tw-mb-[12px]"></v-divider>
156   - <div v-html="info.introduction"></div>
157   - </div>
158   - <div v-if="info.advantage" class="tw-mb-[24px]">
159   - <div class="text-h6">Description</div>
160   - <v-divider class="tw-mb-[12px]"></v-divider>
161   - <div v-html="info.description"></div>
162   - </div>
163   - </v-window-item>
164   - <v-window-item key="2" :value="2">
165   - <v-table density="compact" class="table2">
166   - <tbody>
167   - <tr
168   - class="tr"
169   - v-for="item in info.productAttributeList || []"
170   - :key="item.name"
  248 + <v-tabs
  249 + class="tabs"
  250 + v-model="tab"
  251 + color="white"
  252 + bg-color="#eeeeee"
  253 + slider-color="blue-lighten-1"
  254 + selected-class="active"
  255 + style="width: 100%"
  256 + >
  257 + <v-tab :value="1">Product Details</v-tab>
  258 + <v-tab :value="2">Specification</v-tab>
  259 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  260 + </v-tabs>
  261 + <v-window
  262 + v-model="tab"
  263 + class="tw-p-[24px]"
  264 + style="
  265 + min-height: 500px;
  266 + height: auto;
  267 + overflow-wrap: break-word;
  268 + word-wrap: break-word;
  269 + white-space: pre-wrap;
  270 + "
  271 + >
  272 + <v-window-item key="1" :value="1">
  273 + <!-- 修改位置 1: 使用 clearSpanStyle 方法清理 info.advantage -->
  274 + <div v-if="info.advantage" class="tw-mb-[24px]">
  275 + <div class="text-h6">Advantage</div>
  276 + <v-divider class="tw-mb-[12px]"></v-divider>
  277 + <div v-html="clearSpanStyle(info.advantage)"></div>
  278 + </div>
  279 + <!-- 修改位置 2: 使用 clearSpanStyle 方法清理 info.physicalproperty -->
  280 + <div v-if="info.physicalproperty" class="tw-mb-[24px]">
  281 + <div class="text-h6">Physical Property</div>
  282 + <v-divider class="tw-mb-[12px] html-content"></v-divider>
  283 + <div v-html="clearSpanStyle(info.physicalproperty)"></div>
  284 + </div>
  285 + <!-- 修改位置 3: 使用 clearSpanStyle 方法清理 info.storage -->
  286 + <div v-if="info.storage" class="tw-mb-[24px]">
  287 + <div class="text-h6">Storage</div>
  288 + <v-divider class="tw-mb-[12px]"></v-divider>
  289 + <div v-html="clearSpanStyle(info.storage)"></div>
  290 + </div>
  291 + <!-- 修改位置 4: 使用 clearSpanStyle 方法清理 info.introduction -->
  292 + <div v-if="info.introduction" class="tw-mb-[24px]">
  293 + <div class="text-h6">Introduction</div>
  294 + <v-divider class="tw-mb-[12px]"></v-divider>
  295 + <div v-html="clearSpanStyle(info.introduction)"></div>
  296 + </div>
  297 + <!-- 修改位置 5: 使用 clearSpanStyle 方法清理 info.description -->
  298 + <div v-if="info.description" class="tw-mb-[24px]">
  299 + <div class="text-h6">Description</div>
  300 + <v-divider class="tw-mb-[12px]"></v-divider>
  301 + <div v-html="clearSpanStyle(info.description)"></div>
  302 + </div>
  303 + </v-window-item>
  304 + <!-- <div v-if="info.advantage" class="tw-mb-[24px]">
  305 + <div class="text-h6">Advantage</div>
  306 + <v-divider class="tw-mb-[12px]"></v-divider>
  307 + <div v-html="info.advantage"></div>
  308 + </div>
  309 + <div v-if="info.physicalproperty" class="tw-mb-[24px]">
  310 + <div class="text-h6">Physical Property</div>
  311 + <v-divider class="tw-mb-[12px] html-content"></v-divider>
  312 + <div
  313 + v-html="info.physicalproperty"
  314 + style="
  315 + overflow-wrap: break-word;
  316 + word-wrap: break-word;
  317 + white-space: pre-wrap;
  318 + line-height: 1.5;
  319 + "
  320 + ></div>
  321 + </div>
  322 + <div v-if="info.storage" class="tw-mb-[24px]">
  323 + <div class="text-h6">Storage</div>
  324 + <v-divider class="tw-mb-[12px]"></v-divider>
  325 + <div v-html="info.storage"></div>
  326 + </div>
  327 + <div v-if="info.introduction" class="tw-mb-[24px]">
  328 + <div class="text-h6">Introduction</div>
  329 + <v-divider class="tw-mb-[12px]"></v-divider>
  330 + <div v-html="info.introduction"></div>
  331 + </div>
  332 + <div v-if="info.description" class="tw-mb-[24px]">
  333 + <div class="text-h6">Description</div>
  334 + <v-divider class="tw-mb-[12px]"></v-divider>
  335 + <div v-html="info.description"></div>
  336 + </div>
  337 + </v-window-item> -->
  338 + <v-window-item key="2" :value="2">
  339 + <v-table density="compact" class="table2">
  340 + <tbody>
  341 + <tr
  342 + class="tr"
  343 + v-for="item in info.productAttributeList || []"
  344 + :key="item.name"
  345 + >
  346 + <td class="td tw-w-[400px]">{{ item.name }}</td>
  347 + <td class="td">{{ item.value }}</td>
  348 + </tr>
  349 + </tbody>
  350 + </v-table>
  351 + </v-window-item>
  352 + <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
  353 + </v-window>
  354 + </div>
  355 + <div style="width: 30%">
  356 + <v-tabs
  357 + v-model="tabPeriodical"
  358 + color="white"
  359 + bg-color="#eeeeee"
  360 + slider-color="blue-lighten-1"
  361 + selected-class="active"
  362 + grow
  363 + >
  364 + <v-tab value="one">Product-related Journals</v-tab>
  365 + </v-tabs>
  366 + <v-list>
  367 + <v-list-item
  368 + v-for="item in info.journals"
  369 + :key="item.id"
  370 + @click="navigateToUrl(item.link)"
  371 + @mouseenter="hoveredItem = item.id"
  372 + @mouseleave="hoveredItem = null"
  373 + >
  374 + <v-list-item-title>
  375 + <span
  376 + :class="['title', { 'full-title': hoveredItem === item.id }]"
171 377 >
172   - <td class="td tw-w-[400px]">{{ item.name }}</td>
173   - <td class="td">{{ item.value }}</td>
174   - </tr>
175   - </tbody>
176   - </v-table>
177   - </v-window-item>
178   - <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
179   - </v-window>
  378 + {{ item.title }}
  379 + </span>
  380 + </v-list-item-title>
  381 + </v-list-item>
  382 + </v-list>
  383 + </div>
  384 + </div>
  385 + <v-tabs
  386 + class="tabs"
  387 + v-model="tabRecom"
  388 + color="white"
  389 + bg-color="#eeeeee"
  390 + slider-color="blue-lighten-1"
  391 + selected-class="active"
  392 + v-if="recommendImages[0] !== null"
  393 + >
  394 + <v-tab :value="1">Related Products</v-tab>
  395 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  396 + </v-tabs>
  397 + <div id="image-container" v-if="recommendImages[0] !== null">
  398 + <div class="recommend-left-box">
  399 + <v-img
  400 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/76c987e13a334be481a346c19c2284f3qy4tjnxps7.png"
  401 + alt="往左移"
  402 + class="recommend-img-left"
  403 + @click="toggleRow"
  404 + />
  405 + </div>
  406 + <div v-if="currentIndex === 0" class="image-row" id="row1">
  407 + <!-- <img
  408 + v-for="(imageObj, index) in recommendImages.slice(0, 5)"
  409 + :key="'row1-' + index"
  410 + :src="imageObj[0]?.url"
  411 + :alt="'Image ' + (index + 1)"
  412 + style="margin: 0 5px; width: 200px; height: 200px"
  413 + /> -->
  414 + <div
  415 + v-for="(imageObj, index) in recommendImages.slice(0, 5)"
  416 + :key="'row1-' + index"
  417 + class="imageTotal"
  418 + >
  419 + <a v-if="imageObj" :href="imageObj[0]?.productUrl" target="_blank">
  420 + <img
  421 + :src="imageObj[0]?.url"
  422 + :alt="'Image ' + (index + 1)"
  423 + class="item-imgHot"
  424 + />
  425 + <span class="image-name">
  426 + {{ imageObj[0]?.name }}
  427 + </span>
  428 + </a>
  429 + <div v-else style="width: 200px; height: 200px"></div>
  430 + </div>
  431 + </div>
  432 + <div v-else class="image-row" id="row2">
  433 + <!-- <img
  434 + v-for="(imageObj, index) in recommendImages.slice(5, 10)"
  435 + :key="'row2-' + index"
  436 + :src="imageObj[0]?.url"
  437 + :alt="'Image ' + (index + 6)"
  438 + style="margin: 0 5px; width: 200px; height: 200px"
  439 + /> -->
  440 + <div
  441 + v-for="(imageObj, index) in recommendImages.slice(5, 10)"
  442 + :key="'row2-' + index"
  443 + class="imageTotal"
  444 + >
  445 + <a v-if="imageObj" :href="imageObj[0]?.productUrl" target="_blank">
  446 + <img
  447 + :src="imageObj[0]?.url"
  448 + :alt="'Image ' + (index + 6)"
  449 + class="item-imgHot"
  450 + />
  451 + <span class="image-name">
  452 + {{ imageObj[0]?.name }}
  453 + </span>
  454 + </a>
  455 + <div
  456 + v-else
  457 + style="width: 200px; height: 200px; margin-right: 10px"
  458 + ></div>
  459 + </div>
  460 + </div>
  461 + <div class="recommend-right-box">
  462 + <v-img
  463 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/b5daa0a8f2f140f5b406e984c730a453iznzlekysg.png"
  464 + alt="往右移"
  465 + class="recommend-img-right"
  466 + @click="toggleRow"
  467 + />
  468 + </div>
  469 + </div>
  470 +
  471 + <div class="social-share-container">
  472 + <SocialShare
  473 + network="facebook"
  474 + :styled="true"
  475 + :label="true"
  476 + :title="info.name"
  477 + :url="currentUrl"
  478 + style="color: #1e88e5; width: 140px"
  479 + />
  480 +
  481 + <SocialShare
  482 + network="twitter"
  483 + :styled="true"
  484 + :label="true"
  485 + :title="info.name"
  486 + :url="currentUrl"
  487 + style="color: #1e88e5; width: 140px"
  488 + />
  489 +
  490 + <SocialShare
  491 + network="linkedin"
  492 + :styled="true"
  493 + :label="true"
  494 + :title="info.name"
  495 + :url="currentUrl"
  496 + style="color: #1e88e5; width: 140px"
  497 + />
180 498 </div>
181 499 </v-container>
182 500 </template>
... ... @@ -185,20 +503,291 @@
185 503 import type { Product, ProductImage } from "~/type";
186 504 import { onMounted, ref } from "vue";
187 505 import { useDialogStore } from "~/stores/dialog";
  506 +import { useRouter, useRoute } from "vue-router";
  507 +import { useRouteQuery } from "@/stores/route_query";
  508 +
  509 +const recommendList = ref();
  510 +const recommendImages = ref();
  511 +const selectedItem = ref();
  512 +const tabRecom = ref(0);
  513 +const tabRecommend = ref(0);
  514 +const tabPeriodical = ref(0);
  515 +const route = useRoute();
  516 +const router = useRouter();
  517 +const routeQuery = useRouteQuery();
188 518 const dialogStore = useDialogStore();
  519 +const href1 = ref("/products");
  520 +const href2 = ref("/products");
  521 +const idHref = ref("/products");
  522 +const hoveredItem = ref(null);
  523 +const productStore = useProductListStore();
  524 +const maxPage = ref(1);
  525 +const tabRecomHot = ref();
  526 +const recommendListHot = ref();
  527 +const recommendImagesHot = ref();
  528 +const currentIndexHot = ref(1);
  529 +const currentUrl = ref("https://www.canrud.com/products");
  530 +// 定义单个 item 的接口
  531 +interface BreadcrumbItem {
  532 + title: string; // 标题
  533 + disabled: boolean; // 是否禁用
  534 + href: string; // 链接地址
  535 +}
  536 +// 示例数据
  537 +const items = ref<BreadcrumbItem[]>([
  538 + {
  539 + title: "Products",
  540 + disabled: false,
  541 + href: "https://www.canrud.com/products",
  542 + },
  543 + {
  544 + title: "CATEGORY",
  545 + disabled: false,
  546 + href: href1.value,
  547 + },
  548 + {
  549 + title: "Not specified",
  550 + disabled: false,
  551 + href: href2.value,
  552 + },
  553 + {
  554 + title: "Not specified",
  555 + disabled: false,
  556 + href: "/products",
  557 + },
  558 +]);
189 559  
190 560 const props = defineProps<{
191 561 info: Product;
192 562 }>();
193 563 const info = props.info;
  564 +let { data: resData } = await useAsyncData(
  565 + "list",
  566 + () =>
  567 + $fetch("/shop/product/list", {
  568 + method: "GET",
  569 + params: {
  570 + pageNo: 1,
  571 + pageSize: 20,
  572 + ids: info.relatedProductIds,
  573 + },
  574 + }),
  575 + {
  576 + server: true, // 仅在服务器端获取数据
  577 + }
  578 +);
  579 +const loadHotProducts = async () => {
  580 + let { data: hotProducts } = await useAsyncData(
  581 + "hotProducts",
  582 + () =>
  583 + $fetch("/shop/product/hotProducts", {
  584 + method: "GET",
  585 + params: {
  586 + pageNo: currentIndexHot.value,
  587 + pageSize: 5,
  588 + },
  589 + }),
  590 + {
  591 + server: true, // 仅在服务器端获取数据
  592 + }
  593 + );
  594 + recommendListHot.value = hotProducts.value.data.records;
  595 + maxPage.value = hotProducts.value.data.pages;
  596 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  597 + recommendImagesHot.value = Array.from({ length: 5 }).map((_, index) => {
  598 + const item = recommendListHot.value[index];
  599 + if (!item) {
  600 + return null;
  601 + }
  602 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  603 + if (typeof item.productimageliststore === "string") {
  604 + try {
  605 + item.productimageliststore = JSON.parse(item.productimageliststore);
  606 + } catch (error) {
  607 + item.productimageliststore = []; // 解析失败时,设置为空数组
  608 + }
  609 + }
  610 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  611 + (productItem: ProductImage) => ({
  612 + ...productItem,
  613 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  614 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  615 + name: item.name,
  616 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  617 + })
  618 + ));
  619 + return ree;
  620 + });
  621 +};
  622 +const clearSpanStyle = (htmlContent) => {
  623 + if (!htmlContent) return ""; // 检查是否为空
  624 + // 创建一个 DOM 解析器
  625 + const parser = new DOMParser();
  626 + const doc = parser.parseFromString(htmlContent, "text/html");
  627 +
  628 + // 清理 span 的 style 属性
  629 + const spans = doc.querySelectorAll("span");
  630 + spans.forEach((span) => {
  631 + span.removeAttribute("style"); // 清空 style 属性
  632 + });
  633 +
  634 + // 修改 img 的 style 属性,追加 width: 100%
  635 + const imgs = doc.querySelectorAll("img");
  636 + imgs.forEach((img) => {
  637 + const style = img.getAttribute("style") || ""; // 获取现有 style
  638 + if (!style.includes("width: 100%")) {
  639 + img.setAttribute("style", `${style} width: 100%;`.trim()); // 添加 width: 100%
  640 + }
  641 + });
  642 +
  643 + // 返回处理后的 HTML
  644 + return doc.body.innerHTML;
  645 +};
  646 +const toggleRowLeft = () => {
  647 + if (currentIndexHot.value !== 1) {
  648 + currentIndexHot.value--;
  649 + } else if (currentIndexHot.value == 1) {
  650 + currentIndexHot.value = maxPage.value;
  651 + }
  652 +};
  653 +const toggleRowRight = () => {
  654 + if (currentIndexHot.value < maxPage.value) {
  655 + currentIndexHot.value++;
  656 + } else if (currentIndexHot.value == maxPage.value) {
  657 + currentIndexHot.value = 1;
  658 + }
  659 +};
  660 +watch(currentIndexHot, (newIndex) => {
  661 + loadHotProducts(); // Call loadHotProducts when currentIndex changes
  662 +});
  663 +
  664 +// Initial load of hot products
  665 +await loadHotProducts(); // Load hot products the first time
  666 +
  667 +watchEffect(() => {
  668 + console.log(info, "5656info");
  669 + currentUrl.value = "https://www.canrud.com/products/detail/" + info.id;
  670 + if (info?.productCrumbsVO?.category1 && productStore.keyword) {
  671 + items.value[1].title = info.productCrumbsVO.category1;
  672 + items.value[1].href =
  673 + "https://www.canrud.com/products?categories=" +
  674 + info.productCrumbsVO.category1;
  675 + href1.value =
  676 + "https://www.canrud.com/products?categories=" +
  677 + info.productCrumbsVO.category1;
  678 + if (info?.productCrumbsVO?.category2) {
  679 + items.value[2].title = info.productCrumbsVO.category2;
  680 + href2.value = href1.value + "," + info.productCrumbsVO.category2;
  681 + items.value[2].href = href1.value + "," + info.productCrumbsVO.category2;
  682 + }
  683 + if (info?.productCrumbsVO?.function) {
  684 + // items.value.push({
  685 + // title: info.productCrumbsVO.function,
  686 + // disabled: false,
  687 + // href: href2.value + "&function=" + info.productCrumbsVO.function,
  688 + // });
  689 + items.value[3].title = info.productCrumbsVO.function;
  690 + items.value[3].href =
  691 + href2.value + "&function=" + info.productCrumbsVO.function;
  692 + }
  693 + } else if (routeQuery.categories) {
  694 + if (!routeQuery.categories.includes("Energy materials")) {
  695 + routeQuery.updateFunction("Not specified");
  696 + }
  697 + const categories = routeQuery.categories.split(",");
  698 + const mainCategory = categories[0].trim(); // 取第一个值
  699 + const subCategoryName = ref(
  700 + categories[1] ? categories[1].trim() : "Not specified"
  701 + ); // 取第二个值(如果存在)
  702 + if (subCategoryName.value == "Accessories & fixtures") {
  703 + subCategoryName.value = encodeURIComponent("Accessories & fixtures");
  704 + items.value[2].title = "Accessories & fixtures";
  705 + } else {
  706 + items.value[2].title = subCategoryName.value;
  707 + }
  708 + items.value[1].title = mainCategory;
  709 + items.value[1].href =
  710 + "https://www.canrud.com/products?categories=" + mainCategory;
  711 + href1.value = "https://www.canrud.com/products?categories=" + mainCategory;
  712 + // items.value[1].title = subCategoryName.value;
  713 + href2.value = href1.value + "," + subCategoryName.value;
  714 + items.value[2].href = href1.value + "," + subCategoryName.value;
  715 + if (routeQuery?.selectedFunction) {
  716 + // items.value.push({
  717 + // title: routeQuery.selectedFunction,
  718 + // disabled: false,
  719 + // href: href2.value + "&function=" + routeQuery.selectedFunction,
  720 + // });
  721 + items.value[3].title = routeQuery.selectedFunction;
  722 + items.value[3].href =
  723 + href2.value + "&function=" + routeQuery.selectedFunction;
  724 + // routeQuery.updateFunction(null);
  725 + }
  726 + } else if (info?.productCrumbsVO?.category1) {
  727 + items.value[1].title = info.productCrumbsVO.category1;
  728 + items.value[1].href =
  729 + "https://www.canrud.com/products?categories=" +
  730 + info.productCrumbsVO.category1;
  731 + href1.value =
  732 + "https://www.canrud.com/products?categories=" +
  733 + info.productCrumbsVO.category1;
  734 + if (info?.productCrumbsVO?.category2) {
  735 + items.value[2].title = info.productCrumbsVO.category2;
  736 + href2.value = href1.value + "," + info.productCrumbsVO.category2;
  737 + items.value[2].href = href1.value + "," + info.productCrumbsVO.category2;
  738 + }
  739 + if (info?.productCrumbsVO?.function) {
  740 + // items.value.push({
  741 + // title: info.productCrumbsVO.function,
  742 + // disabled: false,
  743 + // href: href2.value + "&function=" + info.productCrumbsVO.function,
  744 + // });
  745 + items.value[3].title = info.productCrumbsVO.function;
  746 + items.value[3].href =
  747 + href2.value + "&function=" + info.productCrumbsVO.function;
  748 + }
  749 + }
  750 + recommendList.value = resData.value.data.records;
194 751  
  752 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  753 + recommendImages.value = Array.from({ length: 10 }).map((_, index) => {
  754 + const item = recommendList.value[index];
  755 + if (!item) {
  756 + return null;
  757 + }
  758 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  759 + if (typeof item.productimageliststore === "string") {
  760 + try {
  761 + item.productimageliststore = JSON.parse(item.productimageliststore);
  762 + } catch (error) {
  763 + item.productimageliststore = []; // 解析失败时,设置为空数组
  764 + }
  765 + }
  766 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  767 + (productItem: ProductImage) => ({
  768 + ...productItem,
  769 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  770 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  771 + name: item.name,
  772 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  773 + })
  774 + ));
  775 + return ree;
  776 + });
  777 +});
195 778 // onMounted(() => {
196 779 // dialogStore.updateDialog(true)
197 780 // })
  781 +const currentIndex = ref(0);
  782 +const navigateToUrl = (url) => {
  783 + window.open(url); // 在新标签页中打开链接
  784 +};
  785 +const toggleRow = () => {
  786 + currentIndex.value = currentIndex.value === 0 ? 1 : 0;
  787 +};
198 788  
199 789 const tab = ref(0);
200 790 const slide = ref(0);
201   -const router = useRouter();
202 791 </script>
203 792  
204 793 <style lang="scss" scoped>
... ... @@ -206,10 +795,32 @@ const router = useRouter();
206 795 border-top: 3px solid #1f88e5 !important;
207 796 }
208 797  
209   -.tabs {
210   - border-bottom: 2px solid #1f88e5;
  798 +@media screen and (min-width: 1537px) {
  799 + .tabs {
  800 + border-bottom: 2px solid #1f88e5;
  801 + margin: 10px auto 20px;
  802 + width: 100%;
  803 + }
  804 +}
  805 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  806 + .tabs {
  807 + border-bottom: 2px solid #1f88e5;
  808 + margin: 10px auto 40px;
  809 + width: 100%;
  810 + }
  811 +}
  812 +@media screen and (max-width: 1280px) {
  813 + .tabs {
  814 + border-bottom: 2px solid #1f88e5;
  815 + margin: 10px auto 40px;
  816 + width: 100%;
  817 + }
211 818 }
212 819  
  820 +// .tabs {
  821 +// border-bottom: 2px solid #1f88e5;
  822 +// }
  823 +
213 824 .active {
214 825 background-color: #1086e8;
215 826 }
... ... @@ -231,4 +842,234 @@ th {
231 842 border-bottom: 1px solid #dcdcdc !important;
232 843 }
233 844 }
  845 +
  846 +.tw-sticky {
  847 + position: sticky;
  848 +}
  849 +
  850 +.carousel-container {
  851 + position: relative;
  852 +}
  853 +
  854 +.table-container {
  855 + overflow-x: auto; /* 防止表格超出页面宽度 */
  856 +}
  857 +
  858 +.table1 {
  859 + width: 100%;
  860 + min-width: 600px; /* 设置表格最小宽度 */
  861 +}
  862 +
  863 +.tr {
  864 + border-bottom: 1px solid #e0e0e0; /* 表格行样式 */
  865 +}
  866 +
  867 +.headerBorder {
  868 + border-bottom: 2px solid #ccc; /* 表头边框 */
  869 +}
  870 +
  871 +.text-grey-darken-4 {
  872 + color: #333; /* 表格文字颜色 */
  873 +}
  874 +.breadcrumb-disabled {
  875 + color: black;
  876 + pointer-events: none; /* 禁用点击 */
  877 + text-decoration: none; /* 移除链接样式 */
  878 +}
  879 +
  880 +.breadcrumb-active {
  881 + color: #1e88e5;
  882 +}
  883 +
  884 +@media screen and (min-width: 1537px) {
  885 + #image-container {
  886 + display: flex;
  887 + align-items: center;
  888 + justify-content: center;
  889 + height: 320px;
  890 + margin: 10px auto 80px;
  891 + width: 100%;
  892 + }
  893 +}
  894 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  895 + #image-container {
  896 + display: flex;
  897 + align-items: center;
  898 + justify-content: center;
  899 + height: 320px;
  900 + margin: 10px auto 0px;
  901 + width: 80%;
  902 + padding: 0;
  903 + }
  904 +}
  905 +@media screen and (max-width: 1280px) {
  906 + #image-container {
  907 + display: flex;
  908 + align-items: center;
  909 + justify-content: center;
  910 + height: 320px;
  911 + margin: 10px auto 0px;
  912 + width: 80%;
  913 + padding: 0;
  914 + }
  915 +}
  916 +.image-row {
  917 + display: flex;
  918 + height: 245px;
  919 +}
  920 +@media screen and (min-width: 1537px) {
  921 + .imageTotal {
  922 + display: inline-block;
  923 + margin: 0 5px;
  924 + text-align: center;
  925 + width: 300px;
  926 + }
  927 +}
  928 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  929 + .imageTotal {
  930 + display: inline-block;
  931 + margin: 0 5px;
  932 + text-align: center;
  933 + width: 200px;
  934 + }
  935 +}
  936 +@media screen and (max-width: 1280px) {
  937 + .imageTotal {
  938 + display: inline-block;
  939 + margin: 0 5px;
  940 + text-align: center;
  941 + width: 200px;
  942 + }
  943 +}
  944 +@media screen and (min-width: 1537px) {
  945 + .image-row img {
  946 + width: 200px;
  947 + height: 200px;
  948 + }
  949 +}
  950 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  951 + .image-row img {
  952 + width: 140px;
  953 + height: 140px;
  954 + }
  955 +}
  956 +@media screen and (max-width: 1280px) {
  957 + .image-row img {
  958 + width: 160px;
  959 + height: 160px;
  960 + }
  961 +}
  962 +@media screen and (min-width: 1537px) {
  963 + .image-name {
  964 + display: -webkit-box; /* Enables multi-line text handling */
  965 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  966 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  967 + overflow: hidden; /* Hides the overflowed text */
  968 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  969 + margin-top: 5px;
  970 + font-size: 16px;
  971 + width: 180px;
  972 + color: #555;
  973 + text-align: center; /* Centers the text horizontally */
  974 + margin-left: 50px;
  975 + }
  976 +}
  977 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  978 + .image-name {
  979 + display: -webkit-box; /* Enables multi-line text handling */
  980 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  981 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  982 + overflow: hidden; /* Hides the overflowed text */
  983 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  984 + margin-top: 5px;
  985 + font-size: 16px;
  986 + width: 180px;
  987 + color: #555;
  988 + text-align: center; /* Centers the text horizontally */
  989 + margin-left: 10px;
  990 + }
  991 +}
  992 +@media screen and (max-width: 1280px) {
  993 + .image-name {
  994 + display: -webkit-box; /* Enables multi-line text handling */
  995 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  996 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  997 + overflow: hidden; /* Hides the overflowed text */
  998 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  999 + margin-top: 5px;
  1000 + font-size: 16px;
  1001 + width: 180px;
  1002 + color: #555;
  1003 + text-align: center; /* Centers the text horizontally */
  1004 + margin-left: 20px;
  1005 + }
  1006 +}
  1007 +
  1008 +button .recommendButton {
  1009 + margin: 0 0px;
  1010 + cursor: pointer;
  1011 +}
  1012 +
  1013 +.recommend-left-box {
  1014 +}
  1015 +
  1016 +.recommend-img-left {
  1017 + width: 26px;
  1018 + height: 27px;
  1019 + margin-right: 30px;
  1020 + margin-bottom: 50px;
  1021 +}
  1022 +
  1023 +.recommend-img-left:hover {
  1024 + cursor: pointer;
  1025 +}
  1026 +.recommend-right-box {
  1027 +}
  1028 +
  1029 +.recommend-img-right {
  1030 + width: 26px;
  1031 + height: 27px;
  1032 + margin-left: 30px;
  1033 + margin-bottom: 50px;
  1034 +}
  1035 +
  1036 +.recommend-img-right:hover {
  1037 + cursor: pointer;
  1038 +}
  1039 +
  1040 +.social-share-container {
  1041 + display: flex;
  1042 + flex-direction: row; /* 父容器横向排列 */
  1043 + gap: 1rem; /* 设置每个分享项之间的间距 */
  1044 + margin-top: 40px;
  1045 +}
  1046 +
  1047 +.social-share-item {
  1048 + display: flex;
  1049 + flex-direction: row; /* 每个分享项横向排列 */
  1050 + align-items: center; /* 垂直居中对齐 */
  1051 + gap: 0.5rem; /* 标题与按钮之间的间距 */
  1052 +}
  1053 +
  1054 +.social-share-title {
  1055 + font-size: 1rem;
  1056 + font-weight: bold;
  1057 +}
  1058 +
  1059 +.title {
  1060 + font-size: 15px; /* 设置字体大小为24px */
  1061 + color: black; /* 设置字体颜色为黑色 */
  1062 + display: -webkit-box; /* 使用盒模型显示多行文本 */
  1063 + -webkit-line-clamp: 2; /* 限制为两行 */
  1064 + -webkit-box-orient: vertical; /* 盒子方向为垂直 */
  1065 + overflow: hidden; /* 超过部分隐藏 */
  1066 + transition: max-height 0.3s ease; /* 过渡效果 */
  1067 +}
  1068 +
  1069 +.full-title {
  1070 + display: block; /* 当悬浮时显示为块级元素 */
  1071 + color: #1e88e5;
  1072 + white-space: normal; /* 允许换行 */
  1073 + overflow: visible; /* 显示全部内容 */
  1074 +}
234 1075 </style>
... ...
deploy/prod2.sh
1 1 #!/bin/bash
2 2 # 变量定义
3   -LAST_TAG="1.0.15"
4   -TAG="1.0.16"
  3 +LAST_TAG="1.0.40"
  4 +TAG="1.0.41"
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"
... ...
nuxt.config.ts
... ... @@ -20,7 +20,16 @@ export default defineNuxtConfig({
20 20 // baseURL: 'http://47.89.254.121:8002/shop' || 'http://39.108.227.113:8002' // Exposed to the frontend as well.
21 21 // }
22 22 // },
23   - modules: ["vuetify-nuxt-module", "@pinia/nuxt", "@nuxtjs/i18n"],
  23 + modules: [
  24 + "vuetify-nuxt-module",
  25 + "@pinia/nuxt",
  26 + "@nuxtjs/i18n",
  27 + "@stefanobartoletti/nuxt-social-share",
  28 + ],
  29 + // optional configuration, should be added manually
  30 + socialShare: {
  31 + // module options
  32 + },
24 33 vuetify: {
25 34 moduleOptions: {
26 35 /* module specific options */
... ...
package-lock.json
... ... @@ -8,6 +8,7 @@
8 8 "hasInstallScript": true,
9 9 "dependencies": {
10 10 "@pinia/nuxt": "^0.5.1",
  11 + "@stefanobartoletti/nuxt-social-share": "^1.2.0",
11 12 "lodash": "^4.17.21",
12 13 "nuxt": "^3.11.2",
13 14 "vue": "^3.4.27",
... ... @@ -2440,6 +2441,15 @@
2440 2441 "url": "https://github.com/sponsors/sindresorhus"
2441 2442 }
2442 2443 },
  2444 + "node_modules/@stefanobartoletti/nuxt-social-share": {
  2445 + "version": "1.2.0",
  2446 + "resolved": "https://registry.npmmirror.com/@stefanobartoletti/nuxt-social-share/-/nuxt-social-share-1.2.0.tgz",
  2447 + "integrity": "sha512-iehssGh971g49CFU3A9leUpAgrLMb9gPFIO7OORyh6ghclc6HNr7QaGnOXihAM0VTJRIqiL3f3AEMiAvwT79Kg==",
  2448 + "dependencies": {
  2449 + "@nuxt/kit": "^3.13.2",
  2450 + "defu": "^6.1.4"
  2451 + }
  2452 + },
2443 2453 "node_modules/@tailwindcss/aspect-ratio": {
2444 2454 "version": "0.4.2",
2445 2455 "resolved": "https://registry.npmmirror.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
... ...
package.json
... ... @@ -11,6 +11,7 @@
11 11 },
12 12 "dependencies": {
13 13 "@pinia/nuxt": "^0.5.1",
  14 + "@stefanobartoletti/nuxt-social-share": "^1.2.0",
14 15 "lodash": "^4.17.21",
15 16 "nuxt": "^3.11.2",
16 17 "vue": "^3.4.27",
... ... @@ -25,4 +26,4 @@
25 26 "sass": "^1.77.1",
26 27 "tailwindcss": "^3.4.3"
27 28 }
28   -}
29 29 \ No newline at end of file
  30 +}
... ...
pages/index.vue
1 1 <template>
2 2 <v-rows class="tw-w-full">
3   - <v-carousel hide-delimiter-background show-arrows="hover" height="auto" v-if="!isMobile()">
4   - <v-carousel-item v-for="banner in banners" :src="banner" cover alt="canrud" :key="banner">
  3 + <v-carousel
  4 + hide-delimiter-background
  5 + show-arrows="hover"
  6 + height="auto"
  7 + v-if="!isMobile()"
  8 + >
  9 + <v-carousel-item
  10 + v-for="banner in banners"
  11 + :src="banner"
  12 + cover
  13 + alt="canrud"
  14 + :key="banner"
  15 + >
5 16 </v-carousel-item>
6 17 </v-carousel>
7   - <v-carousel hide-delimiter-background show-arrows="hover" height="auto" v-if="isMobile()">
8   - <v-carousel-item v-for="banner in mobileBanners" :src="banner" cover alt="canrud" :key="banner">
  18 + <v-carousel
  19 + hide-delimiter-background
  20 + show-arrows="hover"
  21 + height="auto"
  22 + v-if="isMobile()"
  23 + >
  24 + <v-carousel-item
  25 + v-for="banner in mobileBanners"
  26 + :src="banner"
  27 + cover
  28 + alt="canrud"
  29 + :key="banner"
  30 + >
9 31 </v-carousel-item>
10 32 </v-carousel>
11 33 </v-rows>
  34 + <v-tabs
  35 + class="tabs"
  36 + v-model="tabRecom"
  37 + color="white"
  38 + bg-color="#eeeeee"
  39 + style="margin-top: 40px"
  40 + slider-color="blue-lighten-1"
  41 + selected-class="active"
  42 + v-if="recommendImages[0] !== null && !isMobile()"
  43 + >
  44 + <v-tab :value="1">Best Sellers</v-tab>
  45 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  46 + </v-tabs>
  47 + <div id="image-container" v-if="recommendImages[0] !== null && !isMobile()">
  48 + <div class="recommend-left-box">
  49 + <v-img
  50 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/76c987e13a334be481a346c19c2284f3qy4tjnxps7.png"
  51 + alt="往左移"
  52 + class="recommend-img-left"
  53 + @click="toggleRowLeft"
  54 + />
  55 + </div>
  56 + <div class="image-row" id="row1">
  57 + <!-- <img
  58 + v-for="(imageObj, index) in recommendImages.slice(0, 5)"
  59 + :key="'row1-' + index"
  60 + :src="imageObj[0]?.url"
  61 + :alt="'Image ' + (index + 1)"
  62 + style="margin: 0 5px; width: 200px; height: 200px"
  63 + /> -->
  64 + <div
  65 + v-for="(imageObj, index) in recommendImages"
  66 + :key="'row1-' + index"
  67 + class="imageTotal"
  68 + >
  69 + <a v-if="imageObj" :href="imageObj[0]?.productUrl" target="_blank">
  70 + <img
  71 + :src="imageObj[0]?.url"
  72 + :alt="'Image ' + (index + 1)"
  73 + class="item-imgHot"
  74 + />
  75 + <span class="image-name">
  76 + {{ imageObj[0]?.name }}
  77 + </span>
  78 + </a>
  79 + <div v-else style="width: 200px; height: 200px"></div>
  80 + </div>
  81 + </div>
  82 + <div class="recommend-right-box">
  83 + <v-img
  84 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/b5daa0a8f2f140f5b406e984c730a453iznzlekysg.png"
  85 + alt="往右移"
  86 + class="recommend-img-right"
  87 + @click="toggleRowRight"
  88 + />
  89 + </div>
  90 + </div>
  91 + <div style="padding-left: 16px; padding-right: 16px">
  92 + <v-tabs
  93 + class="tabs2"
  94 + ref="tabs2"
  95 + v-model="tabRecom"
  96 + style="margin-top: 25px; margin-bottom: 20px; width: 100%"
  97 + color="white"
  98 + bg-color="#eeeeee"
  99 + slider-color="blue-lighten-1"
  100 + selected-class="active"
  101 + v-if="isMobile()"
  102 + >
  103 + <v-tab :value="1">Best Sellers</v-tab>
  104 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  105 + </v-tabs>
  106 + <div class="tw-text-center" v-if="hotLoading && isMobile()">
  107 + <v-progress-circular
  108 + color="blue-lighten-2"
  109 + indeterminate
  110 + size="64"
  111 + class="tw-m-auto"
  112 + ></v-progress-circular>
  113 + </div>
  114 + <v-item-group multiple v-if="isMobile()">
  115 + <v-row v-if="!hotLoading">
  116 + <v-col
  117 + v-for="(item, i) in recommendImages"
  118 + :key="i"
  119 + cols="6"
  120 + lg="3"
  121 + md="4"
  122 + sm="6"
  123 + >
  124 + <div v-if="item !== null">
  125 + <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl">
  126 + <v-img
  127 + :src="item[0].url"
  128 + :alt="item[0].name"
  129 + eager
  130 + class="d-block"
  131 + />
  132 + <v-card-text class="tw-text-left font-weight-medium title">
  133 + <h4>{{ item[0].name }}</h4>
  134 + </v-card-text>
  135 + </v-card>
  136 + </div>
  137 + </v-col>
  138 + </v-row>
  139 + </v-item-group>
  140 + <v-row v-if="isMobile()">
  141 + <v-col>
  142 + <v-pagination
  143 + :size="isMobile() ? 'small' : 'default'"
  144 + v-if="hotTotal"
  145 + v-model="currentIndex"
  146 + @update:modelValue="toggleRowMobile"
  147 + :length="hotLength"
  148 + rounded="0"
  149 + class="tw-float-right tw-mt-[32px]"
  150 + total-visible="5"
  151 + ></v-pagination></v-col
  152 + ></v-row>
  153 + </div>
12 154 <!-- 能源材料 -->
13 155 <div class="tw-py-8 py-sm-16">
14 156 <v-container>
15   - <MainTitleListOdd title="Material Reagents" :list="materials"
16   - desc="Leading global provider of energy storage research materials and providing other professional/universal experimental materials. " />
  157 + <MainTitleListOdd
  158 + title="Material Reagents"
  159 + :list="materials"
  160 + desc="Leading global provider of energy storage research materials and providing other professional/universal experimental materials. "
  161 + />
17 162 </v-container>
18 163 </div>
19 164  
20 165 <!-- 设备 -->
21 166 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16">
22 167 <v-container>
23   - <MainTitleList title="Lab Device" listType="equipment"
  168 + <MainTitleList
  169 + title="Lab Device"
  170 + listType="equipment"
24 171 :list="lab.list.map((item) => ({ ...item, href: '/products' }))"
25 172 desc="Self-built High-precision Machining Center with Powerful Design and Manufacturing Capabilities. "
26   - href="/equipment" />
  173 + href="/equipment"
  174 + />
27 175 </v-container>
28 176 </div>
29 177  
30 178 <!-- Customized Battery -->
31 179 <div class="tw-py-8 py-sm-16">
32 180 <v-container>
33   - <MainTitleList :title="('Customized Battery')" :list="batteries" href="/customize"
34   - desc="200mAh~10Ah, Winding/Stacking, Unfilled/Filled Electrolyte Cells, Three-Electrode, and More. " />
  181 + <MainTitleList
  182 + :title="'Customized Battery'"
  183 + :list="batteries"
  184 + href="/customize"
  185 + desc="200mAh~10Ah, Winding/Stacking, Unfilled/Filled Electrolyte Cells, Three-Electrode, and More. "
  186 + />
35 187 </v-container>
36 188 </div>
37 189 <!-- Testing -->
38 190 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16">
39 191 <v-container>
40   - <MainTitleList title="Testing" :list="tests" href="/test"
41   - desc="Self built testing center and signed strategic cooperation with ATL, Tsinghua and other units. " />
  192 + <MainTitleList
  193 + title="Testing"
  194 + :list="tests"
  195 + href="/test"
  196 + desc="Self built testing center and signed strategic cooperation with ATL, Tsinghua and other units. "
  197 + />
42 198 </v-container>
43 199 </div>
44 200 <!-- Pack -->
45 201 <div class="pt-8 pb-8 pt pt-sm-16 pb-sm-32">
46 202 <v-container>
47   - <MainTitleList title="Pack" href="/pack" :list="packs"
48   - desc="Focusing on energy materials/new energy storage systems/modules and other fields, mastering advanced technologies to provide high-quality services. " />
  203 + <MainTitleList
  204 + title="Pack"
  205 + href="/pack"
  206 + :list="packs"
  207 + desc="Focusing on energy materials/new energy storage systems/modules and other fields, mastering advanced technologies to provide high-quality services. "
  208 + />
49 209 </v-container>
50 210 </div>
  211 + <div style="padding-left: 16px; padding-right: 16px; padding-bottom: 30px">
  212 + <v-tabs
  213 + class="tabs2"
  214 + ref="tabs2"
  215 + v-model="tabRecom"
  216 + style="margin-top: 25px; margin-bottom: 20px; width: 100%"
  217 + color="white"
  218 + bg-color="#eeeeee"
  219 + slider-color="blue-lighten-1"
  220 + selected-class="active"
  221 + v-if="isMobile()"
  222 + >
  223 + <v-tab :value="1">Best Sellers</v-tab>
  224 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  225 + </v-tabs>
  226 + <div class="tw-text-center" v-if="hotLoading && isMobile()">
  227 + <v-progress-circular
  228 + color="blue-lighten-2"
  229 + indeterminate
  230 + size="64"
  231 + class="tw-m-auto"
  232 + ></v-progress-circular>
  233 + </div>
  234 + <v-item-group multiple v-if="isMobile()">
  235 + <v-row v-if="!hotLoading">
  236 + <v-col
  237 + v-for="(item, i) in recommendImages"
  238 + :key="i"
  239 + cols="6"
  240 + lg="3"
  241 + md="4"
  242 + sm="6"
  243 + >
  244 + <div v-if="item !== null">
  245 + <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl">
  246 + <v-img
  247 + :src="item[0].url"
  248 + :alt="item[0].name"
  249 + eager
  250 + class="d-block"
  251 + />
  252 + <v-card-text class="tw-text-left font-weight-medium title">
  253 + <h4>{{ item[0].name }}</h4>
  254 + </v-card-text>
  255 + </v-card>
  256 + </div>
  257 + </v-col>
  258 + </v-row>
  259 + </v-item-group>
  260 + <v-row v-if="isMobile()">
  261 + <v-col>
  262 + <v-pagination
  263 + :size="isMobile() ? 'small' : 'default'"
  264 + v-if="hotTotal"
  265 + v-model="currentIndex"
  266 + @update:modelValue="toggleRowMobile"
  267 + :length="hotLength"
  268 + rounded="0"
  269 + class="tw-float-right tw-mt-[32px]"
  270 + total-visible="5"
  271 + ></v-pagination></v-col
  272 + ></v-row>
  273 + </div>
51 274 </template>
52 275  
53 276 <script setup lang="ts">
54   -import MainTitleList from '../components/MainTitleList.vue'
55   -import MainTitleListOdd from '../components/MainTitleListOdd.vue'
56   -import { useCategoryStore } from '../stores/category'
57   -import { computed } from 'vue'
58   -import { isMobile } from '../utils'
  277 +import MainTitleList from "../components/MainTitleList.vue";
  278 +import MainTitleListOdd from "../components/MainTitleListOdd.vue";
  279 +import { useCategoryStore } from "../stores/category";
  280 +import { computed, onMounted, reactive, ref } from "vue";
  281 +import { isMobile } from "../utils";
  282 +
  283 +const maxPage = ref(1);
  284 +const tabRecom = ref();
  285 +const recommendList = ref();
  286 +const recommendImages = ref();
  287 +const currentIndex = ref(1);
  288 +const hotLoading = ref(false);
  289 +const hotTotal = ref(10);
  290 +const isOrNotMobile = isMobile();
  291 +const loadHotProducts = async () => {
  292 + const pageSize = ref(5);
  293 + if (isOrNotMobile) {
  294 + pageSize.value = 4;
  295 + }
  296 + hotLoading.value = true;
  297 + let { data: hotProducts } = await useAsyncData(
  298 + "hotProducts",
  299 + () =>
  300 + $fetch("/shop/product/hotProducts", {
  301 + method: "GET",
  302 + params: {
  303 + pageNo: currentIndex.value,
  304 + pageSize: pageSize.value,
  305 + },
  306 + }),
  307 + {
  308 + server: true, // 仅在服务器端获取数据
  309 + }
  310 + );
  311 + hotTotal.value = hotProducts.value.data.total;
  312 + recommendList.value = hotProducts.value.data.records;
  313 + maxPage.value = hotProducts.value.data.pages;
  314 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  315 + recommendImages.value = Array.from({ length: pageSize.value }).map(
  316 + (_, index) => {
  317 + const item = recommendList.value[index];
  318 + if (!item) {
  319 + return null;
  320 + }
  321 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  322 + if (typeof item.productimageliststore === "string") {
  323 + try {
  324 + item.productimageliststore = JSON.parse(item.productimageliststore);
  325 + } catch (error) {
  326 + item.productimageliststore = []; // 解析失败时,设置为空数组
  327 + }
  328 + }
  329 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  330 + (productItem: ProductImage) => ({
  331 + ...productItem,
  332 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  333 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  334 + name: item.name,
  335 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  336 + })
  337 + ));
  338 + return ree;
  339 + }
  340 + );
  341 + hotLoading.value = false;
  342 +};
  343 +
  344 +const toggleRowLeft = () => {
  345 + if (currentIndex.value !== 1) {
  346 + currentIndex.value--;
  347 + } else if (currentIndex.value == 1) {
  348 + currentIndex.value = maxPage.value;
  349 + }
  350 +};
  351 +const toggleRowRight = () => {
  352 + if (currentIndex.value < maxPage.value) {
  353 + currentIndex.value++;
  354 + } else if (currentIndex.value == maxPage.value) {
  355 + currentIndex.value = 1;
  356 + }
  357 +};
  358 +const toggleRowMobile = (value: number) => {
  359 + currentIndex.value = value;
  360 +};
  361 +
  362 +watch(currentIndex, (newIndex) => {
  363 + loadHotProducts(); // Call loadHotProducts when currentIndex changes
  364 +});
  365 +// Initial load of hot products
  366 +await loadHotProducts(); // Load hot products the first time
  367 +
59 368 onMounted(() => {
60   - console.log('%c [ onMounted ]-10', 'font-size:13px; background:pink; color:#bf2c9f;', 111)
61   -})
  369 + console.log(
  370 + "%c [ onMounted ]-10",
  371 + "font-size:13px; background:pink; color:#bf2c9f;",
  372 + 111
  373 + );
  374 +});
62 375 useHead({
63   - title: 'canrud',
  376 + title: "canrud",
64 377 meta: [
65 378 {
66   - name: 'title',
67   - content: "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at contact@canrd.com or call +86 19867737979 to explore our innovative offerings. Together, let's shape a greener, brighter world!"
68   - }, {
69   - name: 'keywords',
  379 + name: "title",
  380 + content:
  381 + "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at contact@canrd.com or call +86 19867737979 to explore our innovative offerings. Together, let's shape a greener, brighter world!",
  382 + },
  383 + {
  384 + name: "keywords",
70 385 content:
71   - '科路得,canrd,canrud,Energy Storage Research,Lithium Batteries Research,Material Reagents,Lab Device,Customized Battery,Testing,Pack',
72   - }, {
  386 + "科路得,canrd,canrud,Energy Storage Research,Lithium Batteries Research,Material Reagents,Lab Device,Customized Battery,Testing,Pack",
  387 + },
  388 + {
73 389 name: "description",
74 390 content:
75   - "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at contact@canrd.com or call +86 19867737979 to explore our innovative offerings. Together, let's shape a greener, brighter world!"
76   - }],
77   - link: [
78   - { rel: 'preload', href: '/banner/banner1.jpg', as: 'image' }
79   - ]
80   -})
  391 + "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at contact@canrd.com or call +86 19867737979 to explore our innovative offerings. Together, let's shape a greener, brighter world!",
  392 + },
  393 + ],
  394 + link: [{ rel: "preload", href: "/banner/banner1.jpg", as: "image" }],
  395 +});
81 396  
82 397 // useAsyncData(async ({ app }) => {
83 398 // console.log('%c [ app ]-70', 'font-size:13px; background:pink; color:#bf2c9f;', app)
... ... @@ -85,76 +400,308 @@ useHead({
85 400 // app.head.meta = [
86 401 // {
87 402 // name: 'description',
88   -// content: "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at
  403 +// content: "科路得,助您科研之路势在必得。Canrd aims to be the world's leading one-stop service provider in new energy research. With a dedication to excellence, we offer Material Reagents, Lab Devices, Customized Batteries, Testing, and Advanced Packaging for energy materials and storage systems. We master advanced technologies to provide high-quality solutions. Our team's quick responses ensure tailored and professional services to meet your unique needs. Contact us at
89 404 // }]
90 405 // })
91   -console.log(11)
  406 +console.log(11);
92 407  
93   -const store = useCategoryStore()
  408 +const store = useCategoryStore();
94 409  
95 410 const lab = computed(
96 411 () =>
97 412 store?.list?.[3] || {
98   - categoryDisplayName: '',
99   - list: []
  413 + categoryDisplayName: "",
  414 + list: [],
100 415 }
101   -)
  416 +);
102 417  
103 418 const banners = [
104   - '/banner/banner1.jpg',
105   - '/banner/banner2.jpg',
106   - '/banner/banner3.jpg',
107   - '/banner/banner5.jpg'
108   -]
  419 + "/banner/banner1.jpg",
  420 + "/banner/banner2.jpg",
  421 + "/banner/banner3.jpg",
  422 + "/banner/banner5.jpg",
  423 +];
109 424  
110 425 const mobileBanners = [
111   - '/mobile/banner-index1.png',
112   - '/mobile/banner-index2.png',
113   - '/mobile/banner-index3.png'
114   -]
  426 + "/mobile/banner-index1.png",
  427 + "/mobile/banner-index2.png",
  428 + "/mobile/banner-index3.png",
  429 +];
115 430  
116 431 const materials = [
117   - { name: 'Energy materials', imageUrl: '/home/1.jpg', href: '/products' },
  432 + { name: "Energy materials", imageUrl: "/home/1.jpg", href: "/products" },
118 433 {
119   - name: 'Laboratory consumables',
120   - imageUrl: '/home/2-Universal-consumables.png',
121   - href: '/products'
  434 + name: "Laboratory consumables",
  435 + imageUrl: "/home/2-Universal-consumables.png",
  436 + href: "/products",
122 437 },
123 438 {
124   - name: 'Low-dimensional materials',
125   - imageUrl: '/home/3-Low-dimensional-materials.png',
126   - href: '/products'
127   - }
128   -]
  439 + name: "Low-dimensional materials",
  440 + imageUrl: "/home/3-Low-dimensional-materials.png",
  441 + href: "/products",
  442 + },
  443 +];
129 444  
130 445 const tests = [
131 446 {
132   - name: 'Electrochemical performance',
133   - imageUrl: '/home/8_Electrochemical_performance.svg',
134   - href: '/test'
  447 + name: "Electrochemical performance",
  448 + imageUrl: "/home/8_Electrochemical_performance.svg",
  449 + href: "/test",
  450 + },
  451 + {
  452 + name: "Reliability testing",
  453 + imageUrl: "/home/9 Reliability testing.svg",
  454 + href: "/test",
  455 + },
  456 + {
  457 + name: "Material testing",
  458 + imageUrl: "/home/10 Material testing.svg",
  459 + href: "/test",
135 460 },
136   - { name: 'Reliability testing', imageUrl: '/home/9 Reliability testing.svg', href: '/test' },
137   - { name: 'Material testing', imageUrl: '/home/10 Material testing.svg', href: '/test' },
138   - { name: 'Calibration', imageUrl: '/home/11 Calibration.svg', href: '/test' }
139   -]
  461 + { name: "Calibration", imageUrl: "/home/11 Calibration.svg", href: "/test" },
  462 +];
140 463 const batteries = [
141   - { name: 'Material evaluation', imageUrl: '/home/4-Material-evaluation.png', href: '/customize' },
142   - { name: 'R&D foundry', imageUrl: '/home/5-R&D-foundry.png', href: '/customize' },
143   - { name: 'Chemical system', imageUrl: '/home/6-Chemical-system.png', href: '/customize' },
144 464 {
145   - name: 'Semi product customization',
146   - imageUrl: '/home/7-Semi-product-customization.png',
147   - href: '/customize'
148   - }
149   -]
  465 + name: "Material evaluation",
  466 + imageUrl: "/home/4-Material-evaluation.png",
  467 + href: "/customize",
  468 + },
  469 + {
  470 + name: "R&D foundry",
  471 + imageUrl: "/home/5-R&D-foundry.png",
  472 + href: "/customize",
  473 + },
  474 + {
  475 + name: "Chemical system",
  476 + imageUrl: "/home/6-Chemical-system.png",
  477 + href: "/customize",
  478 + },
  479 + {
  480 + name: "Semi product customization",
  481 + imageUrl: "/home/7-Semi-product-customization.png",
  482 + href: "/customize",
  483 + },
  484 +];
150 485 const packs = [
151   - { name: 'Power bank', imageUrl: '/home/12-power-bank.png', href: '/pack' },
152   - { name: 'Energy storage', imageUrl: '/home/13-Energy-storage.png', href: '/pack' },
153   - { name: 'power tool', imageUrl: '/home/3-powertool.png', href: '/pack' },
  486 + { name: "Power bank", imageUrl: "/home/12-power-bank.png", href: "/pack" },
154 487 {
155   - name: 'portable energy storage',
156   - imageUrl: '/home/4-portableenergystorage.png',
157   - href: '/pack'
158   - }
159   -]
  488 + name: "Energy storage",
  489 + imageUrl: "/home/13-Energy-storage.png",
  490 + href: "/pack",
  491 + },
  492 + { name: "power tool", imageUrl: "/home/3-powertool.png", href: "/pack" },
  493 + {
  494 + name: "portable energy storage",
  495 + imageUrl: "/home/4-portableenergystorage.png",
  496 + href: "/pack",
  497 + },
  498 +];
  499 +const hotLength = computed(() =>
  500 + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0
  501 +);
160 502 </script>
  503 +<style scoped>
  504 +@media screen and (min-width: 1537px) {
  505 + .tabs {
  506 + border-bottom: 2px solid #1f88e5;
  507 + margin: 10px auto 100px;
  508 + width: 85%;
  509 + }
  510 +}
  511 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  512 + .tabs {
  513 + border-bottom: 2px solid #1f88e5;
  514 + margin: 10px auto 20px;
  515 + width: 80%;
  516 + }
  517 +}
  518 +@media screen and (max-width: 1280px) {
  519 + .tabs {
  520 + border-bottom: 2px solid #1f88e5;
  521 + margin: 10px auto 0px;
  522 + width: 85%;
  523 + }
  524 +}
  525 +
  526 +.active {
  527 + background-color: #1086e8;
  528 +}
  529 +
  530 +/* #image-container {
  531 + display: flex;
  532 + align-items: center;
  533 + justify-content: center;
  534 + height: 320px;
  535 + margin: 10px auto 50px;
  536 + width: 80%;
  537 +} */
  538 +
  539 +@media screen and (min-width: 1537px) {
  540 + #image-container {
  541 + display: flex;
  542 + align-items: center;
  543 + justify-content: center;
  544 + height: 320px;
  545 + margin: 10px auto 50px;
  546 + width: 80%;
  547 + }
  548 +}
  549 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  550 + #image-container {
  551 + display: flex;
  552 + align-items: center;
  553 + justify-content: center;
  554 + height: 240px;
  555 + margin: 10px auto 0px;
  556 + width: 80%;
  557 + }
  558 +}
  559 +@media screen and (max-width: 1280px) {
  560 + #image-container {
  561 + display: flex;
  562 + align-items: center;
  563 + justify-content: center;
  564 + height: 260px;
  565 + margin: 10px auto 0px;
  566 + width: 90%;
  567 + }
  568 +}
  569 +.image-row {
  570 + display: flex;
  571 + height: 305px;
  572 +}
  573 +@media screen and (min-width: 1537px) {
  574 + .image-row {
  575 + display: flex;
  576 + height: 305px;
  577 + }
  578 +}
  579 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  580 + .image-row {
  581 + display: flex;
  582 + height: 245px;
  583 + }
  584 +}
  585 +@media screen and (max-width: 1280px) {
  586 + .image-row {
  587 + display: flex;
  588 + height: 220px;
  589 + }
  590 +}
  591 +@media screen and (min-width: 1537px) {
  592 + .imageTotal {
  593 + display: inline-block;
  594 + margin: 0 5px;
  595 + text-align: center;
  596 + width: 290px;
  597 + }
  598 +}
  599 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  600 + .imageTotal {
  601 + display: inline-block;
  602 + margin: 0 5px;
  603 + text-align: center;
  604 + width: 210px;
  605 + }
  606 +}
  607 +@media screen and (min-width: 1537px) {
  608 + .image-row img {
  609 + width: 240px;
  610 + height: 240px;
  611 + }
  612 +}
  613 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  614 + .image-row img {
  615 + width: 140px;
  616 + height: 140px;
  617 + }
  618 +}
  619 +@media screen and (max-width: 1280px) {
  620 + .image-row img {
  621 + width: 160px;
  622 + height: 160px;
  623 + margin-left: 10px;
  624 + }
  625 +}
  626 +@media screen and (min-width: 1537px) {
  627 + .image-name {
  628 + display: -webkit-box; /* Enables multi-line text handling */
  629 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  630 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  631 + overflow: hidden; /* Hides the overflowed text */
  632 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  633 + margin-top: 5px;
  634 + font-size: 16px;
  635 + width: 180px;
  636 + color: #555;
  637 + text-align: center; /* Centers the text horizontally */
  638 + margin-left: 50px;
  639 + }
  640 +}
  641 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  642 + .image-name {
  643 + display: -webkit-box; /* Enables multi-line text handling */
  644 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  645 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  646 + overflow: hidden; /* Hides the overflowed text */
  647 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  648 + margin-top: 5px;
  649 + font-size: 16px;
  650 + width: 180px;
  651 + color: #555;
  652 + text-align: center; /* Centers the text horizontally */
  653 + margin-left: 10px;
  654 + }
  655 +}
  656 +@media screen and (max-width: 1280px) {
  657 + .image-name {
  658 + display: -webkit-box; /* Enables multi-line text handling */
  659 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  660 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  661 + overflow: hidden; /* Hides the overflowed text */
  662 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  663 + margin-top: 5px;
  664 + font-size: 16px;
  665 + width: 180px;
  666 + color: #555;
  667 + text-align: center; /* Centers the text horizontally */
  668 + margin-left: 5px;
  669 + }
  670 +}
  671 +button .recommendButton {
  672 + margin: 0 0px;
  673 + cursor: pointer;
  674 +}
  675 +
  676 +.recommend-left-box {
  677 +}
  678 +
  679 +.recommend-img-left {
  680 + width: 26px;
  681 + height: 27px;
  682 + margin-right: 30px;
  683 +}
  684 +
  685 +.recommend-img-left:hover {
  686 + cursor: pointer;
  687 +}
  688 +.recommend-right-box {
  689 +}
  690 +
  691 +.recommend-img-right {
  692 + width: 26px;
  693 + height: 27px;
  694 + margin-left: 30px;
  695 +}
  696 +
  697 +.recommend-img-right:hover {
  698 + cursor: pointer;
  699 +}
  700 +.image-grid {
  701 + padding: 16px;
  702 +}
  703 +
  704 +.v-card {
  705 + transition: all 0.3s ease-in-out;
  706 +}
  707 +</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,8 +10,9 @@ 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>>({
16 17 productimageliststore: [],
17 18 productAttributeList: [],
... ... @@ -44,32 +45,28 @@ watchEffect(() =&gt; {
44 45 },
45 46 {
46 47 name: "keywords",
47   - content:
48   - info.value.name ||
49   - "科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines",
  48 + content: info.value.name || info.value.metakeywords,
50 49 },
51 50 {
52 51 name: "description",
53   - content:
54   - info.value.name ||
55   - "科路得,助您科研之路势在必得。Equipment Business: With our self-built high-precision machining center, we possess robust design and manufacturing capabilities. We offer comprehensive equipment supply, production line planning, and construction services, including battery assembly lines, pouch cell testing lines, and more. Our aim is to provide complete equipment solutions that cater to the diverse needs of our clients. Expect top-quality equipment and professional services that will help you stand out in a fiercely competitive market!",
  52 + content: info.value.metadescription || info.value.name,
56 53 },
57 54 ],
58 55 });
59 56 });
60 57  
61 58 const newData: Product = resData.value.data;
62   -
63 59 newData.productimageliststore =
64 60 typeof newData.productimageliststore === "string"
65 61 ? JSON.parse(newData.productimageliststore) || []
66 62 : (newData.productimageliststore as ProductImage[]);
67   -newData.productimageliststore = newData?.productimageliststore.map(
  63 +newData.productimageliststore = newData?.productimageliststore?.map(
68 64 (item: ProductImage) => ({
69 65 ...item,
70 66 // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`
71 67 url: `/api/show/image?fileKey=${item.fileKey}&psize=p512`,
72 68 })
73 69 );
  70 +
74 71 info.value = { ...newData };
75 72 </script>
... ...
pages/products/index.vue
1 1 <template>
2   - <div class="tw-m-auto tw-pb-[64px]">
  2 + <div class="tw-m-auto tw-pb-[64px]" style="padding-bottom: 0px">
3 3 <CategoryList v-if="categoryStore.categoryVisible && !isMobile()" />
4 4 <MobileCategoryList v-if="categoryStore.categoryVisible && isMobile()" />
  5 + <v-tabs
  6 + class="tabs"
  7 + v-model="tabRecom"
  8 + style="margin-top: 25px"
  9 + color="white"
  10 + bg-color="#eeeeee"
  11 + slider-color="blue-lighten-1"
  12 + selected-class="active"
  13 + v-if="recommendImages[0] !== null && !isMobile()"
  14 + >
  15 + <v-tab :value="1">Best Sellers</v-tab>
  16 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  17 + </v-tabs>
  18 + <div id="image-container" v-if="recommendImages[0] !== null && !isMobile()">
  19 + <div class="recommend-left-box">
  20 + <v-img
  21 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/76c987e13a334be481a346c19c2284f3qy4tjnxps7.png"
  22 + alt="往左移"
  23 + class="recommend-img-left"
  24 + @click="toggleRowLeft"
  25 + />
  26 + </div>
  27 + <div class="image-row" id="row1">
  28 + <!-- <img
  29 + v-for="(imageObj, index) in recommendImages.slice(0, 5)"
  30 + :key="'row1-' + index"
  31 + :src="imageObj[0]?.url"
  32 + :alt="'Image ' + (index + 1)"
  33 + style="margin: 0 5px; width: 200px; height: 200px"
  34 + /> -->
  35 + <div
  36 + v-for="(imageObj, index) in recommendImages"
  37 + :key="'row1-' + index"
  38 + class="imageTotal"
  39 + >
  40 + <a v-if="imageObj" :href="imageObj[0]?.productUrl" target="_blank">
  41 + <img
  42 + :src="imageObj[0]?.url"
  43 + :alt="'Image ' + (index + 1)"
  44 + class="item-imgHot"
  45 + />
  46 + <span class="image-name">
  47 + {{ imageObj[0]?.name }}
  48 + </span>
  49 + </a>
  50 + <div v-else style="width: 200px; height: 200px"></div>
  51 + </div>
  52 + </div>
  53 + <div class="recommend-right-box">
  54 + <v-img
  55 + src="https://m-canrd.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2024/09/14/b5daa0a8f2f140f5b406e984c730a453iznzlekysg.png"
  56 + alt="往右移"
  57 + class="recommend-img-right"
  58 + @click="toggleRowRight"
  59 + />
  60 + </div>
  61 + </div>
5 62 <v-container class="">
6   - <div class="tw-text-center" v-if="loading">
  63 + <div class="tw-text-center tw-mt-[32px]" v-if="loading">
7 64 <v-progress-circular
8 65 color="blue-lighten-2"
9 66 indeterminate
... ... @@ -60,11 +117,79 @@
60 117 <v-row>
61 118 <v-col>
62 119 <v-pagination
  120 + :size="isMobile() ? 'small' : 'default'"
  121 + v-if="productStore.total"
  122 + v-model="productStore.pageNo"
  123 + @update:modelValue="productStore.updatePageNo"
  124 + :length="length"
  125 + rounded="0"
  126 + class="tw-float-right tw-mt-[32px]"
  127 + total-visible="5"
  128 + ></v-pagination></v-col
  129 + ></v-row>
  130 + <v-tabs
  131 + class="tabs2"
  132 + ref="tabs2"
  133 + v-model="tabRecom"
  134 + style="margin-top: 25px; margin-bottom: 20px"
  135 + color="white"
  136 + bg-color="#eeeeee"
  137 + slider-color="blue-lighten-1"
  138 + selected-class="active"
  139 + v-if="isMobile()"
  140 + >
  141 + <v-tab :value="1">Best Sellers</v-tab>
  142 + <!-- <v-tab :value="3">商品百科</v-tab> -->
  143 + </v-tabs>
  144 + <div class="tw-text-center" v-if="hotLoading && isMobile()">
  145 + <v-progress-circular
  146 + color="blue-lighten-2"
  147 + indeterminate
  148 + size="64"
  149 + class="tw-m-auto"
  150 + ></v-progress-circular>
  151 + </div>
  152 + <v-item-group multiple v-if="isMobile()">
  153 + <v-row v-if="!hotLoading">
  154 + <v-col
  155 + v-for="(item, i) in recommendImages"
  156 + :key="i"
  157 + cols="6"
  158 + lg="3"
  159 + md="4"
  160 + sm="6"
  161 + >
  162 + <div v-if="item !== null">
  163 + <v-card :elevation="4" class="mx-auto" :href="item[0].productUrl">
  164 + <!-- 设置 eager 属性,确保图片直接加载 -->
  165 + <v-img
  166 + :src="item[0].url"
  167 + :alt="item[0].name"
  168 + eager
  169 + class="d-block"
  170 + />
  171 + <v-card-text class="tw-text-left font-weight-medium title">
  172 + <h4>{{ item[0].name }}</h4>
  173 + </v-card-text>
  174 + </v-card>
  175 + </div>
  176 + </v-col>
  177 + </v-row>
  178 + <!-- <div
  179 + v-if="!hotTotal"
  180 + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]"
  181 + >
  182 + no data
  183 + </div> -->
  184 + </v-item-group>
  185 + <v-row v-if="isMobile()">
  186 + <v-col>
  187 + <v-pagination
63 188 :size="isMobile() ? 'small' : 'default'"
64   - v-if="productStore.total"
65   - v-model="productStore.pageNo"
66   - @update:modelValue="productStore.updatePageNo"
67   - :length="length"
  189 + v-if="hotTotal"
  190 + v-model="currentIndex"
  191 + @update:modelValue="toggleRowMobile"
  192 + :length="hotLength"
68 193 rounded="0"
69 194 class="tw-float-right tw-mt-[32px]"
70 195 total-visible="5"
... ... @@ -75,6 +200,7 @@
75 200 </template>
76 201  
77 202 <script setup lang="ts">
  203 +import type { ProductImage } from "~/type";
78 204 import { isMobile, isEqual } from "~/utils";
79 205 import { useProductListStore } from "~/stores/product_list";
80 206 import { useCategoryStore } from "~/stores/category";
... ... @@ -85,10 +211,110 @@ import { watchEffect, computed, ref } from &quot;vue&quot;;
85 211 const productStore = useProductListStore();
86 212 const categoryStore = useCategoryStore();
87 213 const loading = ref(false);
  214 +const hotLoading = ref(false);
88 215 const route = useRoute(); // 获取路由信息
  216 +const router = useRouter(); // 获取路由信息
  217 +const title = ref("");
  218 +const keywordTitle = ref("");
  219 +const maxPage = ref(1);
  220 +const tabRecom = ref();
  221 +const recommendList = ref();
  222 +const recommendImages = ref();
  223 +const currentIndex = ref(1);
  224 +const hotTotal = ref(10);
  225 +const isOrNotMobile = isMobile();
  226 +
  227 +const loadHotProducts = async () => {
  228 + const pageSize = ref(5);
  229 + if (isOrNotMobile) {
  230 + pageSize.value = 4;
  231 + }
  232 + hotLoading.value = true;
  233 + let { data: hotProducts } = await useAsyncData(
  234 + "hotProducts",
  235 + () =>
  236 + $fetch("/shop/product/hotProducts", {
  237 + method: "GET",
  238 + params: {
  239 + pageNo: currentIndex.value,
  240 + pageSize: pageSize.value,
  241 + },
  242 + }),
  243 + {
  244 + server: true, // 仅在服务器端获取数据
  245 + }
  246 + );
  247 + hotTotal.value = hotProducts.value.data.total;
  248 + recommendList.value = hotProducts.value.data.records;
  249 + maxPage.value = hotProducts.value.data.pages;
  250 + // recommendImages.value = recommendList.value.slice(0, 10).map((item) => {
  251 + recommendImages.value = Array.from({ length: pageSize.value }).map(
  252 + (_, index) => {
  253 + const item = recommendList.value[index];
  254 + if (!item) {
  255 + return null;
  256 + }
  257 + // 检查 productimageliststore 是否为字符串格式,如果是,则尝试解析
  258 + if (typeof item.productimageliststore === "string") {
  259 + try {
  260 + item.productimageliststore = JSON.parse(item.productimageliststore);
  261 + } catch (error) {
  262 + item.productimageliststore = []; // 解析失败时,设置为空数组
  263 + }
  264 + }
  265 + const ree = (item.productimageliststore = item?.productimageliststore.map(
  266 + (productItem: ProductImage) => ({
  267 + ...productItem,
  268 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`,
  269 + url: `https://www.canrud.com/api/show/image?fileKey=${productItem.fileKey}&psize=p256`,
  270 + name: item.name,
  271 + productUrl: `https://www.canrud.com/products/detail/${item.id}`,
  272 + })
  273 + ));
  274 + return ree;
  275 + }
  276 + );
  277 + hotLoading.value = false;
  278 +};
  279 +
  280 +const toggleRowLeft = () => {
  281 + if (currentIndex.value !== 1) {
  282 + currentIndex.value--;
  283 + } else if (currentIndex.value == 1) {
  284 + currentIndex.value = maxPage.value;
  285 + }
  286 +};
  287 +const toggleRowRight = () => {
  288 + if (currentIndex.value < maxPage.value) {
  289 + currentIndex.value++;
  290 + } else if (currentIndex.value == maxPage.value) {
  291 + currentIndex.value = 1;
  292 + }
  293 +};
  294 +const toggleRowMobile = (value: number) => {
  295 + currentIndex.value = value;
  296 +};
  297 +
  298 +watch(currentIndex, (newIndex) => {
  299 + loadHotProducts(); // Call loadHotProducts when currentIndex changes
  300 +});
  301 +// Initial load of hot products
  302 +await loadHotProducts(); // Load hot products the first time
  303 +
  304 +watchEffect(() => {
  305 + // 遍历数组
  306 + if (router.currentRoute.value.query.categories) {
  307 + title.value = `${router.currentRoute.value.query.categories}`;
  308 + } else if (router.currentRoute.value.query.function) {
  309 + title.value += `,${router.currentRoute.value.query.function}`;
  310 + }
  311 + if (router.currentRoute.value.query.keyword) {
  312 + keywordTitle.value = `${router.currentRoute.value.query.keyword}`;
  313 + }
  314 +});
89 315  
90 316 useHead({
91   - title: "canrud",
  317 + title: title.value || keywordTitle.value,
92 318 meta: [
93 319 {
94 320 name: "title",
... ... @@ -107,9 +333,8 @@ useHead({
107 333 },
108 334 ],
109 335 });
110   -
  336 +const firstIndex = ref(0);
111 337 const loadProducts = async () => {
112   - console.log(productStore, "5656productStore");
113 338 let params: any = {
114 339 pageNo: productStore.pageNo,
115 340 pageSize: 20,
... ... @@ -123,35 +348,146 @@ const loadProducts = async () =&gt; {
123 348 loading.value = false;
124 349 return;
125 350 }
  351 + const categories = ref();
  352 + const mainCategory = ref();
  353 + if (router.currentRoute.value.query.categories) {
  354 + categories.value = router.currentRoute.value.query.categories.split(",");
  355 + mainCategory.value = categories.value[0].trim(); // 取第一个值
  356 + }
126 357  
127 358 params.productCategoryId = categoryStore.selectedSubCategory;
128 359 // productCategoryId: '78b86c6e917841cf9a292234f2805e24',
129   -
130 360 if (categoryStore.selectedFuncCategory) {
131 361 params.productFunctionId = categoryStore.selectedFuncCategory;
  362 + if (firstIndex.value == 1) {
  363 + params.productFunctionId = selectedFuncCategoryBak.value;
  364 + firstIndex.value += 1;
  365 + if (
  366 + params.productFunctionId == "" &&
  367 + selectedFuncCategoryBak.value == ""
  368 + ) {
  369 + categoryStore.selectedFuncCategory = selectedFuncCategoryBak.value;
  370 + params.productFunctionId = categoryStore.selectedFuncCategory;
  371 + // categoryStore.selectedFuncCategory = selectedFuncCategoryBak.value;
  372 + }
  373 + console.log(
  374 + params.productFunctionId,
  375 + selectedFuncCategoryBak.value,
  376 + categoryStore.selectedFuncCategory
  377 + );
  378 + }
  379 + // const savedSubCategory = localStorage.getItem("selectedFuncCategory2");
  380 + // console.log(savedSubCategory, "5656savedSubCategory");
  381 + // localStorage.setItem("selectedFuncCategory3", "2");
  382 + } else if (mainCategory.value == "Energy materials") {
  383 + params.productCategoryId = categoryStore.selectedSubCategory;
132 384 }
133 385  
  386 + console.log(
  387 + // params,
  388 + // productStore?.params,
  389 + // categoryStore.selectedSubCategory,
  390 + // categoryStore.selectedFuncCategory,
  391 + window.selectedSubCategory,
  392 + window.selectedFuncCategory
  393 + );
134 394 if (
135 395 categoryStore.selectedSubCategory &&
136 396 !isEqual(productStore.params, params)
137 397 ) {
  398 + // console.log(route, "5656route");
138 399 productStore.updateParams(params);
  400 + const categories = ref("Laboratory consumables,Others");
  401 + const mainCategory = categories.value[0].trim(); // 取第一个值
  402 + // console.log(categoryStore.selectedSubCategory, "5656mobi");
139 403  
140 404 await productStore.getList(params);
141 405 }
  406 + // if (
  407 + // categoryStore.selectedFuncCategory &&
  408 + // !isEqual(productStore.params, params)
  409 + // ) {
  410 + // console.log(
  411 + // categoryStore.selectedFuncCategory,
  412 + // "5656categoryStore.selectedFuncCategoryss"
  413 + // );
  414 + // productStore.updateParams(params);
  415 + // await productStore.getList(params);
  416 + // }
  417 + if (firstIndex.value === 0 && categoryStore.selectedFuncCategory) {
  418 + // console.log(route, "5656route");
  419 + productStore.updateParams(params);
  420 + // const categories = ref("Laboratory consumables,Others");
  421 + // const mainCategory = categories.value[0].trim(); // 取第一个值
  422 + // console.log(categoryStore.selectedFuncCategory, "5656mobi");
  423 + await productStore.getList(params);
  424 + firstIndex.value = 1;
  425 + }
142 426 loading.value = false;
143 427 };
144   -
  428 +const isWindowAssigned = ref(false);
  429 +const selectedSubCategoryBak = ref();
  430 +const selectedFuncCategoryBak = ref();
145 431 watchEffect(async () => {
146 432 if (route.query.keyword !== undefined) {
147 433 productStore.keyword = route.query.keyword;
148 434 }
  435 + // console.log(categoryStore.selectedSubCategory, "5656index-categoryStore");
  436 + if (typeof window !== "undefined" && !isWindowAssigned.value) {
  437 + window.selectedSubCategory = categoryStore.selectedSubCategory;
  438 + window.selectedFuncCategory = categoryStore.selectedFuncCategory;
  439 + selectedSubCategoryBak.value = categoryStore.selectedSubCategory;
  440 + selectedFuncCategoryBak.value = categoryStore.selectedFuncCategory;
  441 + isWindowAssigned.value = true;
  442 + }
149 443 loadProducts();
  444 + // console.log(categoryStore.selectedSubCategory, "5656index-categoryStore");
150 445 });
  446 +// onMounted(() => {
  447 +// // window.selectedSubCategory = categoryStore.selectedSubCategory;
  448 +// // window.selectedFuncCategory = categoryStore.selectedFuncCategory;
  449 +// if (route.query.function) {
  450 +// const functionName = route.query.function.trim();
  451 +// const funcCategoryList = computed(() => {
  452 +// if (categoryStore.selectedCategory) {
  453 +// const tmp = categoryStore.list.filter(
  454 +// (item) => item.categoryDisplayName === categoryStore.selectedCategory
  455 +// );
  456 +// return tmp?.[0]?.productFunctions || [];
  457 +// }
  458 +// return [];
  459 +// });
  460 +// const foundFuncCategory = funcCategoryList.value.find(
  461 +// (func) => func.name === functionName
  462 +// );
  463 +
  464 +// if (foundFuncCategory) {
  465 +// const funcCategoryId = foundFuncCategory.id;
  466 +// if (typeof localStorage !== "undefined") {
  467 +// localStorage.setItem("selectedFuncCategory2", funcCategoryId);
  468 +// localStorage.setItem("selectedFuncCategory3", "1");
  469 +// }
  470 +// // 你可以在这里使用找到的 funcCategoryId
  471 +// }
  472 +// // // 6. 查找对应的功能分类 ID
  473 +// // const foundFuncCategory = funcCategories.find(
  474 +// // (func) => func.name === functionName
  475 +// // );
  476 +
  477 +// // if (foundFuncCategory) {
  478 +// // const funcCategoryId = foundFuncCategory.id;
  479 +// // // 使用找到的 funcCategoryId
  480 +// // categoryStore.updateFuncCategory(funcCategoryId);
  481 +// // }
  482 +// }
  483 +// });
151 484  
152 485 const length = computed(() =>
153 486 productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0
154 487 );
  488 +const hotLength = computed(() =>
  489 + hotTotal.value ? Math.ceil(hotTotal.value / 4) : 0
  490 +);
155 491 </script>
156 492  
157 493 <style scoped>
... ... @@ -163,4 +499,200 @@ const length = computed(() =&gt;
163 499 -webkit-line-clamp: 2;
164 500 -webkit-box-orient: vertical;
165 501 }
  502 +
  503 +@media screen and (min-width: 1537px) {
  504 + .tabs {
  505 + border-bottom: 2px solid #1f88e5;
  506 + margin: 10px auto 20px;
  507 + width: 93%;
  508 + }
  509 +}
  510 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  511 + .tabs {
  512 + border-bottom: 2px solid #1f88e5;
  513 + margin: 10px auto 40px;
  514 + width: 78%;
  515 + }
  516 +}
  517 +@media screen and (max-width: 1280px) {
  518 + .tabs {
  519 + border-bottom: 2px solid #1f88e5;
  520 + margin: 10px auto 40px;
  521 + width: 92%;
  522 + }
  523 +}
  524 +/* .tabs {
  525 + border-bottom: 2px solid #1f88e5;
  526 +} */
  527 +
  528 +.active {
  529 + background-color: #1086e8;
  530 +}
  531 +
  532 +@media screen and (min-width: 1537px) {
  533 + #image-container {
  534 + display: flex;
  535 + align-items: center;
  536 + justify-content: center;
  537 + height: 320px;
  538 + margin: 10px auto 50px;
  539 + width: 80%;
  540 + }
  541 +}
  542 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  543 + #image-container {
  544 + display: flex;
  545 + align-items: center;
  546 + justify-content: center;
  547 + height: 260px;
  548 + margin: 10px auto 0px;
  549 + width: 80%;
  550 + padding: 0;
  551 + }
  552 +}
  553 +@media screen and (max-width: 1280px) {
  554 + #image-container {
  555 + display: flex;
  556 + align-items: center;
  557 + justify-content: center;
  558 + height: 260px;
  559 + margin: 10px auto 0px;
  560 + width: 80%;
  561 + padding: 0;
  562 + }
  563 +}
  564 +.image-row {
  565 + display: flex;
  566 + height: 245px;
  567 +}
  568 +@media screen and (min-width: 1537px) {
  569 + .imageTotal {
  570 + display: inline-block;
  571 + margin: 0 5px;
  572 + text-align: center;
  573 + width: 300px;
  574 + }
  575 +}
  576 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  577 + .imageTotal {
  578 + display: inline-block;
  579 + margin: 0 5px;
  580 + text-align: center;
  581 + width: 200px;
  582 + }
  583 +}
  584 +@media screen and (max-width: 1280px) {
  585 + .imageTotal {
  586 + display: inline-block;
  587 + margin: 0 5px;
  588 + text-align: center;
  589 + width: 200px;
  590 + }
  591 +}
  592 +@media screen and (min-width: 1537px) {
  593 + .image-row img {
  594 + width: 240px;
  595 + height: 240px;
  596 + }
  597 +}
  598 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  599 + .image-row img {
  600 + width: 140px;
  601 + height: 140px;
  602 + }
  603 +}
  604 +@media screen and (max-width: 1280px) {
  605 + .image-row img {
  606 + width: 120px;
  607 + height: 120px;
  608 + }
  609 +}
  610 +@media screen and (min-width: 1537px) {
  611 + .image-name {
  612 + display: -webkit-box; /* Enables multi-line text handling */
  613 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  614 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  615 + overflow: hidden; /* Hides the overflowed text */
  616 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  617 + margin-top: 5px;
  618 + font-size: 16px;
  619 + width: 180px;
  620 + color: #555;
  621 + text-align: center; /* Centers the text horizontally */
  622 + margin-left: 50px;
  623 + }
  624 +}
  625 +@media screen and (max-width: 1536px) and (min-width: 1281px) {
  626 + .image-name {
  627 + display: -webkit-box; /* Enables multi-line text handling */
  628 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  629 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  630 + overflow: hidden; /* Hides the overflowed text */
  631 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  632 + margin-top: 5px;
  633 + font-size: 16px;
  634 + width: 180px;
  635 + color: #555;
  636 + text-align: center; /* Centers the text horizontally */
  637 + margin-left: 10px;
  638 + }
  639 +}
  640 +@media screen and (max-width: 1280px) {
  641 + .image-name {
  642 + display: -webkit-box; /* Enables multi-line text handling */
  643 + -webkit-box-orient: vertical; /* Defines the vertical orientation of the box */
  644 + -webkit-line-clamp: 3; /* Limits the text to 3 lines */
  645 + overflow: hidden; /* Hides the overflowed text */
  646 + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */
  647 + margin-top: 5px;
  648 + font-size: 16px;
  649 + width: 180px;
  650 + color: #555;
  651 + text-align: center; /* Centers the text horizontally */
  652 + margin-left: 10px;
  653 + }
  654 +}
  655 +
  656 +button .recommendButton {
  657 + margin: 0 0px;
  658 + cursor: pointer;
  659 +}
  660 +
  661 +.recommend-left-box {
  662 +}
  663 +
  664 +@media screen and (max-width: 1280px) {
  665 + .recommend-img-left {
  666 + width: 26px;
  667 + height: 27px;
  668 + margin-bottom: 60px;
  669 + }
  670 +}
  671 +.recommend-img-left {
  672 + width: 26px;
  673 + height: 27px;
  674 + margin-right: 30px;
  675 + margin-bottom: 60px;
  676 +}
  677 +
  678 +.recommend-img-left:hover {
  679 + cursor: pointer;
  680 +}
  681 +.recommend-right-box {
  682 +}
  683 +
  684 +.recommend-img-right {
  685 + width: 26px;
  686 + height: 27px;
  687 + margin-left: 30px;
  688 + margin-bottom: 60px;
  689 +}
  690 +
  691 +.recommend-img-right:hover {
  692 + cursor: pointer;
  693 +}
  694 +
  695 +.v-card {
  696 + transition: all 0.3s ease-in-out;
  697 +}
166 698 </style>
... ...