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,7 +79,9 @@ import { useProductListStore } from "@/stores/product_list";
79 import type { CategoryRootType } from "@/type"; 79 import type { CategoryRootType } from "@/type";
80 import { computed } from "vue"; 80 import { computed } from "vue";
81 import { useRouter, useRoute } from "vue-router"; 81 import { useRouter, useRoute } from "vue-router";
  82 +import { useRouteQuery } from "@/stores/route_query";
82 83
  84 +const routeQuery = useRouteQuery();
83 const route = useRoute(); 85 const route = useRoute();
84 const router = useRouter(); 86 const router = useRouter();
85 87
@@ -91,13 +93,16 @@ watchEffect(async () => { @@ -91,13 +93,16 @@ watchEffect(async () => {
91 productStore.updateKeyword(""); 93 productStore.updateKeyword("");
92 const categories = route.query.categories.split(","); 94 const categories = route.query.categories.split(",");
93 const mainCategory = categories[0].trim(); // 取第一个值 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 // 2. 更新选中的主分类 101 // 2. 更新选中的主分类
97 categoryStore.updateCategory(mainCategory); 102 categoryStore.updateCategory(mainCategory);
98 - 103 + routeQuery.updateCategories(mainCategory + "," + subCategoryName.value);
99 // 3. 如果有子分类名称,查找其对应的 ID 104 // 3. 如果有子分类名称,查找其对应的 ID
100 - if (subCategoryName) { 105 + if (subCategoryName.value) {
101 const subCategoryList = computed(() => { 106 const subCategoryList = computed(() => {
102 if (categoryStore.selectedCategory) { 107 if (categoryStore.selectedCategory) {
103 const tmp = categoryStore.list.filter( 108 const tmp = categoryStore.list.filter(
@@ -111,7 +116,7 @@ watchEffect(async () => { @@ -111,7 +116,7 @@ watchEffect(async () => {
111 116
112 // 4. 查找对应的子分类 ID 117 // 4. 查找对应的子分类 ID
113 const foundFuncCategory = subCategoryList.value.find( 118 const foundFuncCategory = subCategoryList.value.find(
114 - (func) => func.name === subCategoryName 119 + (func) => func.name === subCategoryName.value
115 ); 120 );
116 121
117 if (foundFuncCategory) { 122 if (foundFuncCategory) {
@@ -123,6 +128,7 @@ watchEffect(async () => { @@ -123,6 +128,7 @@ watchEffect(async () => {
123 // 5. 判断 query 中是否存在 function,并查找对应的 ID 128 // 5. 判断 query 中是否存在 function,并查找对应的 ID
124 if (route.query.function) { 129 if (route.query.function) {
125 const functionName = route.query.function.trim(); 130 const functionName = route.query.function.trim();
  131 + routeQuery.updateFunction(functionName);
126 const funcCategoryList = computed(() => { 132 const funcCategoryList = computed(() => {
127 if (categoryStore.selectedCategory) { 133 if (categoryStore.selectedCategory) {
128 const tmp = categoryStore.list.filter( 134 const tmp = categoryStore.list.filter(
@@ -152,15 +158,30 @@ watchEffect(async () => { @@ -152,15 +158,30 @@ watchEffect(async () => {
152 // // 使用找到的 funcCategoryId 158 // // 使用找到的 funcCategoryId
153 // categoryStore.updateFuncCategory(funcCategoryId); 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 const defaultCategory = categoryStore.list[0]; // 假设第一个分类是默认的 179 const defaultCategory = categoryStore.list[0]; // 假设第一个分类是默认的
159 180
160 if (defaultCategory) { 181 if (defaultCategory) {
161 const defaultCategoryName = defaultCategory.categoryDisplayName; 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 // 更新 store 和 URL 186 // 更新 store 和 URL
166 categoryStore.updateCategory(defaultCategoryName); 187 categoryStore.updateCategory(defaultCategoryName);
components/Footer.vue
@@ -5,7 +5,9 @@ @@ -5,7 +5,9 @@
5 <v-col cols="12" lg="3" sm="12" md="6"> 5 <v-col cols="12" lg="3" sm="12" md="6">
6 <b>Solution</b> 6 <b>Solution</b>
7 <p><router-link to="/equipment">Lab Device</router-link></p> 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 <p><router-link to="/pack">Pack</router-link></p> 11 <p><router-link to="/pack">Pack</router-link></p>
10 </v-col> 12 </v-col>
11 <v-col cols="12" lg="3" sm="12" md="6"> 13 <v-col cols="12" lg="3" sm="12" md="6">
@@ -16,6 +18,39 @@ @@ -16,6 +18,39 @@
16 <v-col cols="12" lg="3" sm="12" md="6"> 18 <v-col cols="12" lg="3" sm="12" md="6">
17 <b>About</b> 19 <b>About</b>
18 <p><router-link to="/about">About us</router-link></p> 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 </v-col> 54 </v-col>
20 <v-col cols="12" lg="3" sm="12" md="6"> 55 <v-col cols="12" lg="3" sm="12" md="6">
21 <div class="tw-w-[250px] tw-float-left tw-mr-[8px]"> 56 <div class="tw-w-[250px] tw-float-left tw-mr-[8px]">
@@ -24,14 +59,20 @@ @@ -24,14 +59,20 @@
24 <p>Phone: +86 19867737979</p> 59 <p>Phone: +86 19867737979</p>
25 <p>Wechat: contactcanrd</p> 60 <p>Wechat: contactcanrd</p>
26 </div> 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 </v-col> 68 </v-col>
29 </v-row> 69 </v-row>
30 </v-container> 70 </v-container>
31 </div> 71 </div>
32 </template> 72 </template>
33 73
34 -<script setup lang="ts"></script> 74 +<script setup lang="ts">
  75 +</script>
35 76
36 <style> 77 <style>
37 b { 78 b {
components/MobileCategoryList.vue
@@ -3,7 +3,9 @@ @@ -3,7 +3,9 @@
3 class="pr-4 tw-h-[48px] tw-leading-[48px] border-b tw-flex tw-justify-between tw-items-center" 3 class="pr-4 tw-h-[48px] tw-leading-[48px] border-b tw-flex tw-justify-between tw-items-center"
4 > 4 >
5 <span class="ml-4 tw-font-bold">{{ categoryStore.selectedCategory }}</span> 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 >Filter <v-icon> mdi-filter-outline </v-icon></span 9 >Filter <v-icon> mdi-filter-outline </v-icon></span
8 > 10 >
9 </div> 11 </div>
@@ -16,7 +18,11 @@ @@ -16,7 +18,11 @@
16 touchless 18 touchless
17 class="!tw-h-[70%] bg-grey-lighten-4 tw-overflow-y-auto tw-overflow-x-hidden" 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 <div class="mb-4 tw-flex tw-items-center"> 26 <div class="mb-4 tw-flex tw-items-center">
21 <div> 27 <div>
22 <v-img 28 <v-img
@@ -29,7 +35,9 @@ @@ -29,7 +35,9 @@
29 " 35 "
30 ></v-img> 36 ></v-img>
31 </div> 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 </div> 41 </div>
34 <div class="tw-flex tw-flex-wrap tw-justify-between"> 42 <div class="tw-flex tw-flex-wrap tw-justify-between">
35 <template v-if="index !== 0"> 43 <template v-if="index !== 0">
@@ -43,7 +51,7 @@ @@ -43,7 +51,7 @@
43 : ' bg-grey-lighten-4 ') 51 : ' bg-grey-lighten-4 ')
44 " 52 "
45 :key="i" 53 :key="i"
46 - @click="handleCategoryClick(item, k.id)" 54 + @click="handleCategoryClick(item, k.id, k)"
47 > 55 >
48 {{ k.name }} 56 {{ k.name }}
49 </div> 57 </div>
@@ -59,7 +67,7 @@ @@ -59,7 +67,7 @@
59 ' tw-w-[48%] mb-4 py-3 px-2 tw-rounded-lg tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis' 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 :key="i" 69 :key="i"
62 - @click="handleCategoryClick(item, k.id)" 70 + @click="handleCategoryClick(item, k.id, k)"
63 > 71 >
64 {{ k.name }} 72 {{ k.name }}
65 </div> 73 </div>
@@ -71,62 +79,259 @@ @@ -71,62 +79,259 @@
71 </template> 79 </template>
72 80
73 <script setup lang="ts"> 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 const CATEGORY_IMG = [ 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 const SEO = { 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 Equipment: 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 const handleDrawerHide = () => { 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 doc 301 doc
115 .querySelector('meta[name="keywords"]') 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 } else { 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 </script> 335 </script>
131 336
132 <style lang="less" scoped></style> 337 <style lang="less" scoped></style>
components/MobileHeader.vue
@@ -5,37 +5,87 @@ @@ -5,37 +5,87 @@
5 <v-layout> 5 <v-layout>
6 <v-app-bar scroll-behavior="elevate" color="white" density="default"> 6 <v-app-bar scroll-behavior="elevate" color="white" density="default">
7 <v-app-bar-title v-if="!showSearch"> 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 </v-app-bar-title> 15 </v-app-bar-title>
10 <template v-slot:append> 16 <template v-slot:append>
11 <v-btn icon v-if="!showSearch"> 17 <v-btn icon v-if="!showSearch">
12 <v-icon @click="showSearch = !showSearch">mdi-magnify</v-icon> 18 <v-icon @click="showSearch = !showSearch">mdi-magnify</v-icon>
13 </v-btn> 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 </template> 30 </template>
18 31
19 <div class="tw-h-[36px] tw-w-[300px] ml-8" v-if="showSearch"> 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 </v-text-field> 45 </v-text-field>
23 </div> 46 </div>
24 </v-app-bar> 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 <v-list density="compact" nav> 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 </v-list-item> 64 </v-list-item>
30 <v-divider></v-divider> 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 <v-divider></v-divider> 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 <v-divider></v-divider> 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 <v-divider></v-divider> 89 <v-divider></v-divider>
40 <v-list-item title="Blog" value="blog" color="blue-darken-1"> 90 <v-list-item title="Blog" value="blog" color="blue-darken-1">
41 <a href="http://blog.canrud.com/" target="" title="Blog">Blog</a> 91 <a href="http://blog.canrud.com/" target="" title="Blog">Blog</a>
@@ -73,66 +123,66 @@ @@ -73,66 +123,66 @@
73 </template> 123 </template>
74 124
75 <script setup lang="ts"> 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 const handleKeyDown = (e: any) => { 143 const handleKeyDown = (e: any) => {
94 if (e.keyCode == 13) { 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 const handleClick = () => { 152 const handleClick = () => {
103 - showSearch.value = false  
104 -} 153 + showSearch.value = false;
  154 +};
105 155
106 const handleTabClick = () => { 156 const handleTabClick = () => {
107 - categoryStore.updateDisplay(true)  
108 - productStore.updateKeyword('')  
109 -} 157 + categoryStore.updateDisplay(true);
  158 + productStore.updateKeyword("");
  159 +};
110 160
111 watchEffect(() => { 161 watchEffect(() => {
112 - input.value = productStore.keyword  
113 -}) 162 + input.value = productStore.keyword;
  163 +});
114 164
115 onMounted(() => { 165 onMounted(() => {
116 // 获取url的参数 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 if (index !== -1) { 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 params.forEach((item) => { 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 // 获取dialog的状态 176 // 获取dialog的状态
127 if (obj.flag) { 177 if (obj.flag) {
128 - dialog.updateDialog(true) 178 + dialog.updateDialog(true);
129 } 179 }
130 180
131 if (obj.keyword) { 181 if (obj.keyword) {
132 - productStore.updateKeyword(obj.keyword) 182 + productStore.updateKeyword(obj.keyword);
133 } 183 }
134 } 184 }
135 -}) 185 +});
136 </script> 186 </script>
137 187
138 <style lang="scss" scoped> 188 <style lang="scss" scoped>
components/MobileProductDetail.vue
1 <template> 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 <v-container class="ma-0 pa-0 bg-grey-lighten-5"> 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 </v-carousel-item> 80 </v-carousel-item>
7 </v-carousel> 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 {{ info.name }} 83 {{ info.name }}
10 - </div> 84 + </h1>
11 <v-row class="mx-4 mt-0 mb-4 bg-white rounded-lg"> 85 <v-row class="mx-4 mt-0 mb-4 bg-white rounded-lg">
12 <v-col cols="6"> 86 <v-col cols="6">
13 <div class="text-body-1 text-grey-darken-3">Brand:</div> 87 <div class="text-body-1 text-grey-darken-3">Brand:</div>
@@ -30,10 +104,21 @@ @@ -30,10 +104,21 @@
30 <div class="text-h6">{{ info.basecore3 }}</div> 104 <div class="text-h6">{{ info.basecore3 }}</div>
31 </v-col> 105 </v-col>
32 </v-row> 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 <div 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 {{ item.typeName }} 122 {{ item.typeName }}
38 </div> 123 </div>
39 <div class="tw-h-[82px] pa-2"> 124 <div class="tw-h-[82px] pa-2">
@@ -42,17 +127,91 @@ @@ -42,17 +127,91 @@
42 {{ item.rank }} 127 {{ item.rank }}
43 </div> 128 </div>
44 </div> 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 Quotation Inquiry 198 Quotation Inquiry
49 </v-btn> 199 </v-btn>
  200 + <!-- </div> -->
50 </div> 201 </div>
51 <!-- <v-dialog v-model="dialog" activator="parent" width="auto"> 202 <!-- <v-dialog v-model="dialog" activator="parent" width="auto">
52 <v-card> Contact us Email: contact@canrd.com QQ: 3003597584 / 2902385824 </v-card> 203 <v-card> Contact us Email: contact@canrd.com QQ: 3003597584 / 2902385824 </v-card>
53 </v-dialog> --> 204 </v-dialog> -->
54 <div class="tw-pb-[64px] ma-4 rounded-lg"> 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 <v-tab :value="1">Product Details</v-tab> 215 <v-tab :value="1">Product Details</v-tab>
57 <v-tab :value="2">Specification</v-tab> 216 <v-tab :value="2">Specification</v-tab>
58 <!-- <v-tab :value="3">商品百科</v-tab> --> 217 <!-- <v-tab :value="3">商品百科</v-tab> -->
@@ -69,7 +228,7 @@ @@ -69,7 +228,7 @@
69 <v-divider class="tw-mb-[12px]"></v-divider> 228 <v-divider class="tw-mb-[12px]"></v-divider>
70 <div v-html="info.physicalproperty"></div> 229 <div v-html="info.physicalproperty"></div>
71 </div> 230 </div>
72 - <div v-if="info.advantage" class="tw-mb-[24px]"> 231 + <div v-if="info.storage" class="tw-mb-[24px]">
73 <div class="py-2 pl-2 text-h6">Storage</div> 232 <div class="py-2 pl-2 text-h6">Storage</div>
74 <v-divider class="tw-mb-[12px]"></v-divider> 233 <v-divider class="tw-mb-[12px]"></v-divider>
75 <div v-html="info.storage"></div> 234 <div v-html="info.storage"></div>
@@ -79,7 +238,7 @@ @@ -79,7 +238,7 @@
79 <v-divider class="tw-mb-[12px]"></v-divider> 238 <v-divider class="tw-mb-[12px]"></v-divider>
80 <div v-html="info.introduction"></div> 239 <div v-html="info.introduction"></div>
81 </div> 240 </div>
82 - <div v-if="info.advantage" class="tw-mb-[24px]"> 241 + <div v-if="info.description" class="tw-mb-[24px]">
83 <div class="py-2 pl-2 text-h6">Description</div> 242 <div class="py-2 pl-2 text-h6">Description</div>
84 <v-divider class="tw-mb-[12px]"></v-divider> 243 <v-divider class="tw-mb-[12px]"></v-divider>
85 <div v-html="info.description"></div> 244 <div v-html="info.description"></div>
@@ -88,8 +247,12 @@ @@ -88,8 +247,12 @@
88 <v-window-item key="2" :value="2"> 247 <v-window-item key="2" :value="2">
89 <v-table density="compact" class="table2"> 248 <v-table density="compact" class="table2">
90 <tbody> 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 <td class="td">{{ item.value }}</td> 256 <td class="td">{{ item.value }}</td>
94 </tr> 257 </tr>
95 </tbody> 258 </tbody>
@@ -98,33 +261,553 @@ @@ -98,33 +261,553 @@
98 <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> --> 261 <!-- <v-window-item key="3" :value="3"> 2 </v-window-item> -->
99 </v-window> 262 </v-window>
100 </div> 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 </v-container> 465 </v-container>
102 </template> 466 </template>
103 467
104 <script setup lang="ts"> 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 const props = defineProps<{ 475 const props = defineProps<{
111 - info: Product  
112 -}>()  
113 -const info = props.info 476 + info: Product;
  477 +}>();
  478 +const info = props.info;
114 // onMounted(() => { 479 // onMounted(() => {
115 // dialogStore.updateDialog(true) 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 </script> 750 </script>
121 751
122 <style lang="scss" scoped> 752 <style lang="scss" scoped>
  753 +// .tabs {
  754 +// border-bottom: 2px solid #cbd9e4;
  755 +// }
  756 +
  757 +// .active {
  758 +// background-color: #fff;
  759 +// }
  760 +
123 .tabs { 761 .tabs {
124 - border-bottom: 2px solid #cbd9e4; 762 + border-bottom: 2px solid #1f88e5;
125 } 763 }
126 764
127 .active { 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 </style> 813 </style>
components/ProductDetail.vue
1 <template> 1 <template>
2 <v-container> 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 <v-row class="mb-16 ma-0"> 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 <v-carousel 52 <v-carousel
6 - class="tw-float-left" 53 + class="tw-float-left tw-sticky tw-top-[16px]"
7 height="450" 54 height="450"
8 v-model="slide" 55 v-model="slide"
9 hide-delimiter-background 56 hide-delimiter-background
  57 + style="top: 16px"
10 > 58 >
11 <v-carousel-item 59 <v-carousel-item
12 cover 60 cover
@@ -18,33 +66,34 @@ @@ -18,33 +66,34 @@
18 </v-carousel-item> 66 </v-carousel-item>
19 </v-carousel> 67 </v-carousel>
20 </v-col> 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 <v-col>{{ info.name }}</v-col> 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 <div class="tw-flex tw-flex-wrap"> 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 Brand:{{ info.brandName }} 77 Brand:{{ info.brandName }}
29 </span> 78 </span>
30 </div> 79 </div>
31 <div class="tw-w-1/2 tw-mb-[12px]"> 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 >Product Model:{{ info.model }}</span 82 >Product Model:{{ info.model }}</span
34 > 83 >
35 </div> 84 </div>
36 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename1"> 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 >{{ info.basename1 }}:{{ info.basecore1 }}</span 87 >{{ info.basename1 }}:{{ info.basecore1 }}</span
39 > 88 >
40 </div> 89 </div>
41 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename2"> 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 >{{ info.basename2 }}:{{ info.basecore2 }}</span 92 >{{ info.basename2 }}:{{ info.basecore2 }}</span
44 > 93 >
45 </div> 94 </div>
46 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename3"> 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 >{{ info.basename3 }}:{{ info.basecore3 }}</span 97 >{{ info.basename3 }}:{{ info.basecore3 }}</span
49 > 98 >
50 </div> 99 </div>
@@ -60,15 +109,21 @@ @@ -60,15 +109,21 @@
60 :key="index" 109 :key="index"
61 class="tw-w-1/2 tw-mb-[12px]" 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 >{{ attribute.name }}:{{ attribute.value }}</span 114 >{{ attribute.name }}:{{ attribute.value }}</span
65 > 115 >
66 </div> 116 </div>
67 </div> 117 </div>
68 - <v-table 118 + <!-- <v-table
69 density="comfortable" 119 density="comfortable"
70 class="table1 tw-mt-[32px]" 120 class="table1 tw-mt-[32px]"
71 v-if="info.ticketTypes?.length" 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 <thead> 128 <thead>
74 <tr class="bg-grey-lighten-3"> 129 <tr class="bg-grey-lighten-3">
@@ -120,63 +175,326 @@ @@ -120,63 +175,326 @@
120 </v-dialog> --> 175 </v-dialog> -->
121 </v-col> 176 </v-col>
122 </v-row> 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 </div> 498 </div>
181 </v-container> 499 </v-container>
182 </template> 500 </template>
@@ -185,20 +503,291 @@ @@ -185,20 +503,291 @@
185 import type { Product, ProductImage } from "~/type"; 503 import type { Product, ProductImage } from "~/type";
186 import { onMounted, ref } from "vue"; 504 import { onMounted, ref } from "vue";
187 import { useDialogStore } from "~/stores/dialog"; 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 const dialogStore = useDialogStore(); 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 const props = defineProps<{ 560 const props = defineProps<{
191 info: Product; 561 info: Product;
192 }>(); 562 }>();
193 const info = props.info; 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 // onMounted(() => { 778 // onMounted(() => {
196 // dialogStore.updateDialog(true) 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 const tab = ref(0); 789 const tab = ref(0);
200 const slide = ref(0); 790 const slide = ref(0);
201 -const router = useRouter();  
202 </script> 791 </script>
203 792
204 <style lang="scss" scoped> 793 <style lang="scss" scoped>
@@ -206,10 +795,32 @@ const router = useRouter(); @@ -206,10 +795,32 @@ const router = useRouter();
206 border-top: 3px solid #1f88e5 !important; 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 .active { 824 .active {
214 background-color: #1086e8; 825 background-color: #1086e8;
215 } 826 }
@@ -231,4 +842,234 @@ th { @@ -231,4 +842,234 @@ th {
231 border-bottom: 1px solid #dcdcdc !important; 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 </style> 1075 </style>
deploy/prod2.sh
1 #!/bin/bash 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 TARGET_PATH="/root/web/canrud-outside-nuxt-front" 5 TARGET_PATH="/root/web/canrud-outside-nuxt-front"
6 DOCKERFILE_PATH="/root/web/canrud-outside-nuxt-front/canrud-nuxt-front" 6 DOCKERFILE_PATH="/root/web/canrud-outside-nuxt-front/canrud-nuxt-front"
7 IMAGE_NAME="canrud-outside-front" 7 IMAGE_NAME="canrud-outside-front"
nuxt.config.ts
@@ -20,7 +20,16 @@ export default defineNuxtConfig({ @@ -20,7 +20,16 @@ export default defineNuxtConfig({
20 // baseURL: 'http://47.89.254.121:8002/shop' || 'http://39.108.227.113:8002' // Exposed to the frontend as well. 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 vuetify: { 33 vuetify: {
25 moduleOptions: { 34 moduleOptions: {
26 /* module specific options */ 35 /* module specific options */
package-lock.json
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 "hasInstallScript": true, 8 "hasInstallScript": true,
9 "dependencies": { 9 "dependencies": {
10 "@pinia/nuxt": "^0.5.1", 10 "@pinia/nuxt": "^0.5.1",
  11 + "@stefanobartoletti/nuxt-social-share": "^1.2.0",
11 "lodash": "^4.17.21", 12 "lodash": "^4.17.21",
12 "nuxt": "^3.11.2", 13 "nuxt": "^3.11.2",
13 "vue": "^3.4.27", 14 "vue": "^3.4.27",
@@ -2440,6 +2441,15 @@ @@ -2440,6 +2441,15 @@
2440 "url": "https://github.com/sponsors/sindresorhus" 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 "node_modules/@tailwindcss/aspect-ratio": { 2453 "node_modules/@tailwindcss/aspect-ratio": {
2444 "version": "0.4.2", 2454 "version": "0.4.2",
2445 "resolved": "https://registry.npmmirror.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz", 2455 "resolved": "https://registry.npmmirror.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
package.json
@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 }, 11 },
12 "dependencies": { 12 "dependencies": {
13 "@pinia/nuxt": "^0.5.1", 13 "@pinia/nuxt": "^0.5.1",
  14 + "@stefanobartoletti/nuxt-social-share": "^1.2.0",
14 "lodash": "^4.17.21", 15 "lodash": "^4.17.21",
15 "nuxt": "^3.11.2", 16 "nuxt": "^3.11.2",
16 "vue": "^3.4.27", 17 "vue": "^3.4.27",
@@ -25,4 +26,4 @@ @@ -25,4 +26,4 @@
25 "sass": "^1.77.1", 26 "sass": "^1.77.1",
26 "tailwindcss": "^3.4.3" 27 "tailwindcss": "^3.4.3"
27 } 28 }
28 -}  
29 \ No newline at end of file 29 \ No newline at end of file
  30 +}
pages/index.vue
1 <template> 1 <template>
2 <v-rows class="tw-w-full"> 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 </v-carousel-item> 16 </v-carousel-item>
6 </v-carousel> 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 </v-carousel-item> 31 </v-carousel-item>
10 </v-carousel> 32 </v-carousel>
11 </v-rows> 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 <div class="tw-py-8 py-sm-16"> 155 <div class="tw-py-8 py-sm-16">
14 <v-container> 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 </v-container> 162 </v-container>
18 </div> 163 </div>
19 164
20 <!-- 设备 --> 165 <!-- 设备 -->
21 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16"> 166 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16">
22 <v-container> 167 <v-container>
23 - <MainTitleList title="Lab Device" listType="equipment" 168 + <MainTitleList
  169 + title="Lab Device"
  170 + listType="equipment"
24 :list="lab.list.map((item) => ({ ...item, href: '/products' }))" 171 :list="lab.list.map((item) => ({ ...item, href: '/products' }))"
25 desc="Self-built High-precision Machining Center with Powerful Design and Manufacturing Capabilities. " 172 desc="Self-built High-precision Machining Center with Powerful Design and Manufacturing Capabilities. "
26 - href="/equipment" /> 173 + href="/equipment"
  174 + />
27 </v-container> 175 </v-container>
28 </div> 176 </div>
29 177
30 <!-- Customized Battery --> 178 <!-- Customized Battery -->
31 <div class="tw-py-8 py-sm-16"> 179 <div class="tw-py-8 py-sm-16">
32 <v-container> 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 </v-container> 187 </v-container>
36 </div> 188 </div>
37 <!-- Testing --> 189 <!-- Testing -->
38 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16"> 190 <div class="bg-grey-lighten-5 tw-py-8 py-sm-16">
39 <v-container> 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 </v-container> 198 </v-container>
43 </div> 199 </div>
44 <!-- Pack --> 200 <!-- Pack -->
45 <div class="pt-8 pb-8 pt pt-sm-16 pb-sm-32"> 201 <div class="pt-8 pb-8 pt pt-sm-16 pb-sm-32">
46 <v-container> 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 </v-container> 209 </v-container>
50 </div> 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 </template> 274 </template>
52 275
53 <script setup lang="ts"> 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 onMounted(() => { 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 useHead({ 375 useHead({
63 - title: 'canrud', 376 + title: "canrud",
64 meta: [ 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 content: 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 name: "description", 389 name: "description",
74 content: 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 // useAsyncData(async ({ app }) => { 397 // useAsyncData(async ({ app }) => {
83 // console.log('%c [ app ]-70', 'font-size:13px; background:pink; color:#bf2c9f;', app) 398 // console.log('%c [ app ]-70', 'font-size:13px; background:pink; color:#bf2c9f;', app)
@@ -85,76 +400,308 @@ useHead({ @@ -85,76 +400,308 @@ useHead({
85 // app.head.meta = [ 400 // app.head.meta = [
86 // { 401 // {
87 // name: 'description', 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 const lab = computed( 410 const lab = computed(
96 () => 411 () =>
97 store?.list?.[3] || { 412 store?.list?.[3] || {
98 - categoryDisplayName: '',  
99 - list: [] 413 + categoryDisplayName: "",
  414 + list: [],
100 } 415 }
101 -) 416 +);
102 417
103 const banners = [ 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 const mobileBanners = [ 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 const materials = [ 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 const tests = [ 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 const batteries = [ 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 const packs = [ 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 </script> 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 <template> 1 <template>
2 - <MobileProductDetail v-if="isMobile()" :info="info" /> 2 + <MobileProductDetail v-if="isMobile()" :info="info" :res="resData" />
3 <ProductDetail v-else :info="info" /> 3 <ProductDetail v-else :info="info" />
4 </template> 4 </template>
5 5
@@ -10,8 +10,9 @@ import MobileProductDetail from &quot;~/components/MobileProductDetail.vue&quot;; @@ -10,8 +10,9 @@ import MobileProductDetail from &quot;~/components/MobileProductDetail.vue&quot;;
10 import type { Product, ProductImage } from "~/type"; 10 import type { Product, ProductImage } from "~/type";
11 import { useRoute, useRouter } from "vue-router"; 11 import { useRoute, useRouter } from "vue-router";
12 12
  13 +const productStore = useProductListStore();
13 const route = useRoute(); 14 const route = useRoute();
14 - 15 +const router = useRouter();
15 const info = ref<Partial<Product>>({ 16 const info = ref<Partial<Product>>({
16 productimageliststore: [], 17 productimageliststore: [],
17 productAttributeList: [], 18 productAttributeList: [],
@@ -44,32 +45,28 @@ watchEffect(() =&gt; { @@ -44,32 +45,28 @@ watchEffect(() =&gt; {
44 }, 45 },
45 { 46 {
46 name: "keywords", 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 name: "description", 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 const newData: Product = resData.value.data; 58 const newData: Product = resData.value.data;
62 -  
63 newData.productimageliststore = 59 newData.productimageliststore =
64 typeof newData.productimageliststore === "string" 60 typeof newData.productimageliststore === "string"
65 ? JSON.parse(newData.productimageliststore) || [] 61 ? JSON.parse(newData.productimageliststore) || []
66 : (newData.productimageliststore as ProductImage[]); 62 : (newData.productimageliststore as ProductImage[]);
67 -newData.productimageliststore = newData?.productimageliststore.map( 63 +newData.productimageliststore = newData?.productimageliststore?.map(
68 (item: ProductImage) => ({ 64 (item: ProductImage) => ({
69 ...item, 65 ...item,
70 // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}` 66 // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`
71 url: `/api/show/image?fileKey=${item.fileKey}&psize=p512`, 67 url: `/api/show/image?fileKey=${item.fileKey}&psize=p512`,
72 }) 68 })
73 ); 69 );
  70 +
74 info.value = { ...newData }; 71 info.value = { ...newData };
75 </script> 72 </script>
pages/products/index.vue
1 <template> 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 <CategoryList v-if="categoryStore.categoryVisible && !isMobile()" /> 3 <CategoryList v-if="categoryStore.categoryVisible && !isMobile()" />
4 <MobileCategoryList v-if="categoryStore.categoryVisible && isMobile()" /> 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 <v-container class=""> 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 <v-progress-circular 64 <v-progress-circular
8 color="blue-lighten-2" 65 color="blue-lighten-2"
9 indeterminate 66 indeterminate
@@ -60,11 +117,79 @@ @@ -60,11 +117,79 @@
60 <v-row> 117 <v-row>
61 <v-col> 118 <v-col>
62 <v-pagination 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 :size="isMobile() ? 'small' : 'default'" 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 rounded="0" 193 rounded="0"
69 class="tw-float-right tw-mt-[32px]" 194 class="tw-float-right tw-mt-[32px]"
70 total-visible="5" 195 total-visible="5"
@@ -75,6 +200,7 @@ @@ -75,6 +200,7 @@
75 </template> 200 </template>
76 201
77 <script setup lang="ts"> 202 <script setup lang="ts">
  203 +import type { ProductImage } from "~/type";
78 import { isMobile, isEqual } from "~/utils"; 204 import { isMobile, isEqual } from "~/utils";
79 import { useProductListStore } from "~/stores/product_list"; 205 import { useProductListStore } from "~/stores/product_list";
80 import { useCategoryStore } from "~/stores/category"; 206 import { useCategoryStore } from "~/stores/category";
@@ -85,10 +211,110 @@ import { watchEffect, computed, ref } from &quot;vue&quot;; @@ -85,10 +211,110 @@ import { watchEffect, computed, ref } from &quot;vue&quot;;
85 const productStore = useProductListStore(); 211 const productStore = useProductListStore();
86 const categoryStore = useCategoryStore(); 212 const categoryStore = useCategoryStore();
87 const loading = ref(false); 213 const loading = ref(false);
  214 +const hotLoading = ref(false);
88 const route = useRoute(); // 获取路由信息 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 useHead({ 316 useHead({
91 - title: "canrud", 317 + title: title.value || keywordTitle.value,
92 meta: [ 318 meta: [
93 { 319 {
94 name: "title", 320 name: "title",
@@ -107,9 +333,8 @@ useHead({ @@ -107,9 +333,8 @@ useHead({
107 }, 333 },
108 ], 334 ],
109 }); 335 });
110 - 336 +const firstIndex = ref(0);
111 const loadProducts = async () => { 337 const loadProducts = async () => {
112 - console.log(productStore, "5656productStore");  
113 let params: any = { 338 let params: any = {
114 pageNo: productStore.pageNo, 339 pageNo: productStore.pageNo,
115 pageSize: 20, 340 pageSize: 20,
@@ -123,35 +348,146 @@ const loadProducts = async () =&gt; { @@ -123,35 +348,146 @@ const loadProducts = async () =&gt; {
123 loading.value = false; 348 loading.value = false;
124 return; 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 params.productCategoryId = categoryStore.selectedSubCategory; 358 params.productCategoryId = categoryStore.selectedSubCategory;
128 // productCategoryId: '78b86c6e917841cf9a292234f2805e24', 359 // productCategoryId: '78b86c6e917841cf9a292234f2805e24',
129 -  
130 if (categoryStore.selectedFuncCategory) { 360 if (categoryStore.selectedFuncCategory) {
131 params.productFunctionId = categoryStore.selectedFuncCategory; 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 if ( 394 if (
135 categoryStore.selectedSubCategory && 395 categoryStore.selectedSubCategory &&
136 !isEqual(productStore.params, params) 396 !isEqual(productStore.params, params)
137 ) { 397 ) {
  398 + // console.log(route, "5656route");
138 productStore.updateParams(params); 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 await productStore.getList(params); 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 loading.value = false; 426 loading.value = false;
143 }; 427 };
144 - 428 +const isWindowAssigned = ref(false);
  429 +const selectedSubCategoryBak = ref();
  430 +const selectedFuncCategoryBak = ref();
145 watchEffect(async () => { 431 watchEffect(async () => {
146 if (route.query.keyword !== undefined) { 432 if (route.query.keyword !== undefined) {
147 productStore.keyword = route.query.keyword; 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 loadProducts(); 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 const length = computed(() => 485 const length = computed(() =>
153 productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0 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 </script> 491 </script>
156 492
157 <style scoped> 493 <style scoped>
@@ -163,4 +499,200 @@ const length = computed(() =&gt; @@ -163,4 +499,200 @@ const length = computed(() =&gt;
163 -webkit-line-clamp: 2; 499 -webkit-line-clamp: 2;
164 -webkit-box-orient: vertical; 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 </style> 698 </style>