Commit 5010e9d9de03d2426c2b46cc7ab63e3e0e7970b0

Authored by boyang
1 parent 9b642ad1

feat: title对应产品名,meta动态获取

components/ProductDetail.vue
@@ -2,9 +2,19 @@ @@ -2,9 +2,19 @@
2 <v-container> 2 <v-container>
3 <v-row class="mb-16 ma-0"> 3 <v-row class="mb-16 ma-0">
4 <v-col cols="12" sm="5"> 4 <v-col cols="12" sm="5">
5 - <v-carousel class="tw-float-left" height="450" v-model="slide" hide-delimiter-background>  
6 - <v-carousel-item cover v-for="(slide, i) in info.productimageliststore" :src="slide.url" :key="i"  
7 - :alt="info.name"> 5 + <v-carousel
  6 + class="tw-float-left"
  7 + height="450"
  8 + v-model="slide"
  9 + hide-delimiter-background
  10 + >
  11 + <v-carousel-item
  12 + cover
  13 + v-for="(slide, i) in info.productimageliststore"
  14 + :src="slide.url"
  15 + :key="i"
  16 + :alt="info.name"
  17 + >
8 </v-carousel-item> 18 </v-carousel-item>
9 </v-carousel> 19 </v-carousel>
10 </v-col> 20 </v-col>
@@ -14,42 +24,90 @@ @@ -14,42 +24,90 @@
14 </v-row> 24 </v-row>
15 <div class="tw-flex tw-flex-wrap"> 25 <div class="tw-flex tw-flex-wrap">
16 <div class="tw-w-1/2"> 26 <div class="tw-w-1/2">
17 - <span class="tw-leading-[10px] tw-m-[16px]"> Brand:{{ info.brandName }} </span> 27 + <span class="tw-leading-[10px] tw-m-[16px]">
  28 + Brand:{{ info.brandName }}
  29 + </span>
18 </div> 30 </div>
19 <div class="tw-w-1/2 tw-mb-[12px]"> 31 <div class="tw-w-1/2 tw-mb-[12px]">
20 - <span class="tw-leading-[10px] tw-m-[16px]">Product Model:{{ info.model }}</span> 32 + <span class="tw-leading-[10px] tw-m-[16px]"
  33 + >Product Model:{{ info.model }}</span
  34 + >
21 </div> 35 </div>
22 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename1"> 36 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename1">
23 - <span class="tw-leading-[10px] tw-m-[16px]">{{ info.basename1 }}:{{ info.basecore1 }}</span> 37 + <span class="tw-leading-[10px] tw-m-[16px]"
  38 + >{{ info.basename1 }}:{{ info.basecore1 }}</span
  39 + >
24 </div> 40 </div>
25 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename2"> 41 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename2">
26 - <span class="tw-leading-[10px] tw-m-[16px]">{{ info.basename2 }}:{{ info.basecore2 }}</span> 42 + <span class="tw-leading-[10px] tw-m-[16px]"
  43 + >{{ info.basename2 }}:{{ info.basecore2 }}</span
  44 + >
27 </div> 45 </div>
28 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename3"> 46 <div class="tw-w-1/2 tw-mb-[12px]" v-if="info.basename3">
29 - <span class="tw-leading-[10px] tw-m-[16px]">{{ info.basename3 }}:{{ info.basecore3 }}</span> 47 + <span class="tw-leading-[10px] tw-m-[16px]"
  48 + >{{ info.basename3 }}:{{ info.basecore3 }}</span
  49 + >
30 </div> 50 </div>
31 </div> 51 </div>
32 - <div class="tw-flex tw-flex-wrap" v-if="info.productAttributeList && info.productAttributeList.length > 0">  
33 - <div v-for="(attribute, index) in info.productAttributeList" :key="index" class="tw-w-1/2 tw-mb-[12px]">  
34 - <span class="tw-leading-[10px] tw-m-[16px]">{{ attribute.name }}:{{ attribute.value }}</span> 52 + <div
  53 + class="tw-flex tw-flex-wrap"
  54 + v-if="
  55 + info.productAttributeList && info.productAttributeList.length > 0
  56 + "
  57 + >
  58 + <div
  59 + v-for="(attribute, index) in info.productAttributeList"
  60 + :key="index"
  61 + class="tw-w-1/2 tw-mb-[12px]"
  62 + >
  63 + <span class="tw-leading-[10px] tw-m-[16px]"
  64 + >{{ attribute.name }}:{{ attribute.value }}</span
  65 + >
35 </div> 66 </div>
36 </div> 67 </div>
37 - <v-table density="comfortable" class="table1 tw-mt-[32px]" v-if="info.ticketTypes?.length"> 68 + <v-table
  69 + density="comfortable"
  70 + class="table1 tw-mt-[32px]"
  71 + v-if="info.ticketTypes?.length"
  72 + >
38 <thead> 73 <thead>
39 <tr class="bg-grey-lighten-3"> 74 <tr class="bg-grey-lighten-3">
40 - <th class="text-left headerBorder text-grey-darken-1">Product Name / Code</th>  
41 - <th class="text-left headerBorder text-grey-darken-1">Specification and model</th>  
42 - <th v-if="info.priceShow!==undefined && info.priceShow" class="text-left headerBorder text-grey-darken-1">Price</th> 75 + <th class="text-left headerBorder text-grey-darken-1">
  76 + Product Name / Code
  77 + </th>
  78 + <th class="text-left headerBorder text-grey-darken-1">
  79 + Specification and model
  80 + </th>
  81 + <th
  82 + v-if="info.priceShow !== undefined && info.priceShow"
  83 + class="text-left headerBorder text-grey-darken-1"
  84 + >
  85 + Price
  86 + </th>
43 <th class="text-left headerBorder text-grey-darken-1">Actions</th> 87 <th class="text-left headerBorder text-grey-darken-1">Actions</th>
44 </tr> 88 </tr>
45 </thead> 89 </thead>
46 <tbody> 90 <tbody>
47 - <tr class="tr" v-for="item in info.ticketTypes || []" :key="item.rank">  
48 - <td class="td text-grey-darken-4 font-weight-medium">{{ item.rank }}</td>  
49 - <td class="td text-grey-darken-4 font-weight-medium">{{ item.typeName }}</td>  
50 - <td v-if="item.priceShow" class="td">{{ item.price+" "+item.priceUnit }}</td> 91 + <tr
  92 + class="tr"
  93 + v-for="item in info.ticketTypes || []"
  94 + :key="item.rank"
  95 + >
  96 + <td class="td text-grey-darken-4 font-weight-medium">
  97 + {{ item.rank }}
  98 + </td>
  99 + <td class="td text-grey-darken-4 font-weight-medium">
  100 + {{ item.typeName }}
  101 + </td>
  102 + <td v-if="item.priceShow" class="td">
  103 + {{ item.price + " " + item.priceUnit }}
  104 + </td>
51 <td class="td"> 105 <td class="td">
52 - <v-btn size="small" color="blue-darken-1" @click="router.push('/contact')"> 106 + <v-btn
  107 + size="small"
  108 + color="blue-darken-1"
  109 + @click="router.push('/contact')"
  110 + >
53 <!-- Quotation Inquiry --> 111 <!-- Quotation Inquiry -->
54 Quote 112 Quote
55 </v-btn> 113 </v-btn>
@@ -63,8 +121,14 @@ @@ -63,8 +121,14 @@
63 </v-col> 121 </v-col>
64 </v-row> 122 </v-row>
65 <div class="tw-pb-[64px]"> 123 <div class="tw-pb-[64px]">
66 - <v-tabs class="tabs" v-model="tab" color="white" bg-color="#eeeeee" slider-color="blue-lighten-1"  
67 - selected-class="active"> 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"
  131 + >
68 <v-tab :value="1">Product Details</v-tab> 132 <v-tab :value="1">Product Details</v-tab>
69 <v-tab :value="2">Specification</v-tab> 133 <v-tab :value="2">Specification</v-tab>
70 <!-- <v-tab :value="3">商品百科</v-tab> --> 134 <!-- <v-tab :value="3">商品百科</v-tab> -->
@@ -100,7 +164,11 @@ @@ -100,7 +164,11 @@
100 <v-window-item key="2" :value="2"> 164 <v-window-item key="2" :value="2">
101 <v-table density="compact" class="table2"> 165 <v-table density="compact" class="table2">
102 <tbody> 166 <tbody>
103 - <tr class="tr" v-for="item in info.productAttributeList || []" :key="item.name"> 167 + <tr
  168 + class="tr"
  169 + v-for="item in info.productAttributeList || []"
  170 + :key="item.name"
  171 + >
104 <td class="td tw-w-[400px]">{{ item.name }}</td> 172 <td class="td tw-w-[400px]">{{ item.name }}</td>
105 <td class="td">{{ item.value }}</td> 173 <td class="td">{{ item.value }}</td>
106 </tr> 174 </tr>
@@ -114,23 +182,23 @@ @@ -114,23 +182,23 @@
114 </template> 182 </template>
115 183
116 <script setup lang="ts"> 184 <script setup lang="ts">
117 -import type { Product, ProductImage } from '~/type'  
118 -import { onMounted, ref } from 'vue'  
119 -import { useDialogStore } from '~/stores/dialog'  
120 -const dialogStore = useDialogStore() 185 +import type { Product, ProductImage } from "~/type";
  186 +import { onMounted, ref } from "vue";
  187 +import { useDialogStore } from "~/stores/dialog";
  188 +const dialogStore = useDialogStore();
121 189
122 const props = defineProps<{ 190 const props = defineProps<{
123 - info: Product  
124 -}>()  
125 -const info = props.info 191 + info: Product;
  192 +}>();
  193 +const info = props.info;
126 194
127 // onMounted(() => { 195 // onMounted(() => {
128 // dialogStore.updateDialog(true) 196 // dialogStore.updateDialog(true)
129 // }) 197 // })
130 198
131 -const tab = ref(0)  
132 -const slide = ref(0)  
133 -const router = useRouter() 199 +const tab = ref(0);
  200 +const slide = ref(0);
  201 +const router = useRouter();
134 </script> 202 </script>
135 203
136 <style lang="scss" scoped> 204 <style lang="scss" scoped>
nuxt.config.ts
1 // https://nuxt.com/docs/api/configuration/nuxt-config 1 // https://nuxt.com/docs/api/configuration/nuxt-config
2 // Nuxt config file 2 // Nuxt config file
3 -import { defineNuxtConfig } from 'nuxt/config' 3 +import { defineNuxtConfig } from "nuxt/config";
4 4
5 export default defineNuxtConfig({ 5 export default defineNuxtConfig({
6 app: { 6 app: {
7 head: { 7 head: {
8 - link: [  
9 - { rel: 'icon', type: 'image/x-icon', href: '/fav.ico' }  
10 - ]  
11 - } 8 + link: [{ rel: "icon", type: "image/x-icon", href: "/fav.ico" }],
  9 + },
12 }, 10 },
13 - css: ['~/assets/css/main.css'], 11 + css: ["~/assets/css/main.css"],
14 postcss: { 12 postcss: {
15 plugins: { 13 plugins: {
16 tailwindcss: {}, 14 tailwindcss: {},
@@ -22,45 +20,40 @@ export default defineNuxtConfig({ @@ -22,45 +20,40 @@ export default defineNuxtConfig({
22 // 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.
23 // } 21 // }
24 // }, 22 // },
25 - modules: [  
26 - 'vuetify-nuxt-module',  
27 - '@pinia/nuxt',  
28 - '@nuxtjs/i18n',  
29 - ], 23 + modules: ["vuetify-nuxt-module", "@pinia/nuxt", "@nuxtjs/i18n"],
30 vuetify: { 24 vuetify: {
31 moduleOptions: { 25 moduleOptions: {
32 /* module specific options */ 26 /* module specific options */
33 }, 27 },
34 vuetifyOptions: { 28 vuetifyOptions: {
35 /* vuetify options */ 29 /* vuetify options */
36 - } 30 + },
37 }, 31 },
38 - nitro:{ 32 + nitro: {
39 devProxy: { 33 devProxy: {
40 - '/shop': {  
41 - target: 'http://47.89.254.121:8002/shop', // 线上代理地址  
42 - // target: 'http://127.0.0.1:8002/shop/**', 34 + "/shop": {
  35 + // target: "http://47.89.254.121:8002/shop", // 线上代理地址
  36 + target: "http://127.0.0.1:8002/shop/**",
43 // target: process.env.BASE_URL || 'http://39.108.227.113:8002/shop', // 目标接口域名 37 // target: process.env.BASE_URL || 'http://39.108.227.113:8002/shop', // 目标接口域名
44 changeOrigin: true, // 表示是否跨域 38 changeOrigin: true, // 表示是否跨域
45 - } 39 + },
46 }, 40 },
47 - // 该配置用于服务端请求转发  
48 - routeRules: {  
49 - '/shop/**': {  
50 - proxy: 'http://47.89.254.121:8002/shop/**'  
51 - // proxy: 'http://127.0.0.1:8002/shop/**' 41 + // 该配置用于服务端请求转发
  42 + routeRules: {
  43 + "/shop/**": {
  44 + // proxy: "http://47.89.254.121:8002/shop/**",
  45 + proxy: "http://127.0.0.1:8002/shop/**",
52 // proxy: process.env.BASE_URL || 'http://39.108.227.113:8002/shop/**' 46 // proxy: process.env.BASE_URL || 'http://39.108.227.113:8002/shop/**'
53 - }  
54 - } 47 + },
  48 + },
55 }, 49 },
56 i18n: { 50 i18n: {
57 locales: [ 51 locales: [
58 - { code: 'en', iso: 'en-US', file: 'en.json' },  
59 - { code: 'zh', iso: 'zh-CN', file: 'zh.json' } 52 + { code: "en", iso: "en-US", file: "en.json" },
  53 + { code: "zh", iso: "zh-CN", file: "zh.json" },
60 ], 54 ],
61 lazy: true, 55 lazy: true,
62 - langDir: 'locales/',  
63 - defaultLocale: 'zh', 56 + langDir: "locales/",
  57 + defaultLocale: "zh",
64 }, 58 },
65 -})  
66 - 59 +});
pages/products/detail/[id]/index.vue
@@ -4,59 +4,73 @@ @@ -4,59 +4,73 @@
4 </template> 4 </template>
5 5
6 <script setup lang="ts"> 6 <script setup lang="ts">
7 -import { onMounted } from 'vue'  
8 -import ProductDetail from '~/components/ProductDetail.vue'  
9 -import MobileProductDetail from '~/components/MobileProductDetail.vue'  
10 -import type { Product, ProductImage } from '~/type'  
11 -import { useRoute } from 'vue-router' 7 +import { onMounted } from "vue";
  8 +import ProductDetail from "~/components/ProductDetail.vue";
  9 +import MobileProductDetail from "~/components/MobileProductDetail.vue";
  10 +import type { Product, ProductImage } from "~/type";
  11 +import { useRoute, useRouter } from "vue-router";
12 12
13 -const route = useRoute() 13 +const route = useRoute();
14 14
15 const info = ref<Partial<Product>>({ 15 const info = ref<Partial<Product>>({
16 productimageliststore: [], 16 productimageliststore: [],
17 productAttributeList: [], 17 productAttributeList: [],
18 - name: '',  
19 - ticketTypes: []  
20 -}) 18 + name: "",
  19 + ticketTypes: [],
  20 +});
21 21
22 -let { data: resData } = await useAsyncData('detail', () => $fetch('/shop/product/detail', {  
23 - method: 'GET',  
24 - params: {  
25 - id: route.params.id 22 +let { data: resData } = await useAsyncData(
  23 + "detail",
  24 + () =>
  25 + $fetch("/shop/product/detail", {
  26 + method: "GET",
  27 + params: {
  28 + id: route.params.id,
  29 + },
  30 + }),
  31 + {
  32 + server: true, // 仅在服务器端获取数据
26 } 33 }
27 -}), {  
28 - server: true // 仅在服务器端获取数据  
29 -}) 34 +);
  35 +watchEffect(() => {
  36 + console.log(info.value, "5656infovalue2/resData.value.", resData.value);
  37 + useHead({
  38 + title: info.value.name || "canrud",
  39 + meta: [
  40 + {
  41 + name: "title",
  42 + content:
  43 + info.value.name ||
  44 + "科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines",
  45 + },
  46 + {
  47 + name: "keywords",
  48 + content:
  49 + info.value.metakeywords ||
  50 + "科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines",
  51 + },
  52 + {
  53 + name: "description",
  54 + content:
  55 + info.value.metadescription ||
  56 + "科路得,助您科研之路势在必得。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!",
  57 + },
  58 + ],
  59 + });
  60 +});
30 61
31 -useHead({  
32 - title: info.name || 'canrud',  
33 - meta: [  
34 - {  
35 - name: 'title',  
36 - content:  
37 - '科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines',  
38 - }, {  
39 - name: 'keywords',  
40 - content:  
41 - '科路得,Equipment,High-precision,Machining center,Design,Manufacturing capabilities,Equipment supply,Production line planning,Construction services,Battery assembly lines,Pouch cell testing lines',  
42 - }, {  
43 - name: 'description',  
44 - content:  
45 - '科路得,助您科研之路势在必得。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!'  
46 - },  
47 - ],  
48 -})  
49 -  
50 -const newData: Product = resData.value.data  
51 -  
52 -newData.productimageliststore = typeof newData.productimageliststore === 'string' ?  
53 - (JSON.parse(newData.productimageliststore) || []) :  
54 - newData.productimageliststore as ProductImage[]  
55 -newData.productimageliststore = newData?.productimageliststore.map((item: ProductImage) => ({  
56 - ...item,  
57 - // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`  
58 - url: `/api/show/image?fileKey=${item.fileKey}`  
59 -}))  
60 -info.value = { ...newData } 62 +const newData: Product = resData.value.data;
61 63
  64 +newData.productimageliststore =
  65 + typeof newData.productimageliststore === "string"
  66 + ? JSON.parse(newData.productimageliststore) || []
  67 + : (newData.productimageliststore as ProductImage[]);
  68 +newData.productimageliststore = newData?.productimageliststore.map(
  69 + (item: ProductImage) => ({
  70 + ...item,
  71 + // url: `http://112.74.45.244:8100/api/show/image?fileKey=${item.fileKey}`
  72 + url: `/api/show/image?fileKey=${item.fileKey}&psize=p256`,
  73 + })
  74 +);
  75 +info.value = { ...newData };
62 </script> 76 </script>
pages/products/index.vue
@@ -4,14 +4,31 @@ @@ -4,14 +4,31 @@
4 <MobileCategoryList v-if="categoryStore.categoryVisible && isMobile()" /> 4 <MobileCategoryList v-if="categoryStore.categoryVisible && isMobile()" />
5 <v-container class=""> 5 <v-container class="">
6 <div class="tw-text-center" v-if="loading"> 6 <div class="tw-text-center" v-if="loading">
7 - <v-progress-circular color="blue-lighten-2" indeterminate size="64" class="tw-m-auto"></v-progress-circular> 7 + <v-progress-circular
  8 + color="blue-lighten-2"
  9 + indeterminate
  10 + size="64"
  11 + class="tw-m-auto"
  12 + ></v-progress-circular>
8 </div> 13 </div>
9 <v-item-group multiple> 14 <v-item-group multiple>
10 <v-row v-if="!loading"> 15 <v-row v-if="!loading">
11 - <v-col v-for="(item, i) in productStore.list" :key="i" cols="6" lg="3" md="4" sm="6"> 16 + <v-col
  17 + v-for="(item, i) in productStore.list"
  18 + :key="i"
  19 + cols="6"
  20 + lg="3"
  21 + md="4"
  22 + sm="6"
  23 + >
12 <v-hover v-slot="{ isHovering, props }" open-delay="200"> 24 <v-hover v-slot="{ isHovering, props }" open-delay="200">
13 - <v-card :elevation="isHovering ? 16 : 2" :class="{ 'on-hover': isHovering }" class="mx-auto"  
14 - v-bind="props" :to="`/products/detail/${item.id}`"> 25 + <v-card
  26 + :elevation="isHovering ? 16 : 2"
  27 + :class="{ 'on-hover': isHovering }"
  28 + class="mx-auto"
  29 + v-bind="props"
  30 + :to="`/products/detail/${item.id}`"
  31 + >
15 <v-img :src="item.imgList[0].url" :alt="item.name"> 32 <v-img :src="item.imgList[0].url" :alt="item.name">
16 <!-- <v-expand-transition> 33 <!-- <v-expand-transition>
17 <div 34 <div
@@ -24,100 +41,113 @@ @@ -24,100 +41,113 @@
24 </v-expand-transition> --> 41 </v-expand-transition> -->
25 </v-img> 42 </v-img>
26 <v-card-text class="tw-text-left font-weight-medium title"> 43 <v-card-text class="tw-text-left font-weight-medium title">
27 - <h4>{{  
28 - item.name  
29 - }}</h4> 44 + <h4>{{ item.name }}</h4>
30 </v-card-text> 45 </v-card-text>
31 </v-card> 46 </v-card>
32 </v-hover> 47 </v-hover>
33 </v-col> 48 </v-col>
34 </v-row> 49 </v-row>
35 - <div v-if="!productStore.total && !loading" class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]"> 50 + <div
  51 + v-if="!productStore.total && !loading"
  52 + class="text-medium-emphasis text-body-1 tw-text-center tw-m-[64px]"
  53 + >
36 no data 54 no data
37 </div> 55 </div>
38 </v-item-group> 56 </v-item-group>
39 <v-row> 57 <v-row>
40 <v-col> 58 <v-col>
41 - <v-pagination :size="isMobile() ? 'small' : 'default'" v-if="productStore.total" v-model="productStore.pageNo"  
42 - @update:modelValue="productStore.updatePageNo" :length="length" rounded="0"  
43 - class="tw-float-right tw-mt-[32px]" total-visible="5"></v-pagination></v-col></v-row> 59 + <v-pagination
  60 + :size="isMobile() ? 'small' : 'default'"
  61 + v-if="productStore.total"
  62 + v-model="productStore.pageNo"
  63 + @update:modelValue="productStore.updatePageNo"
  64 + :length="length"
  65 + rounded="0"
  66 + class="tw-float-right tw-mt-[32px]"
  67 + total-visible="5"
  68 + ></v-pagination></v-col
  69 + ></v-row>
44 </v-container> 70 </v-container>
45 </div> 71 </div>
46 </template> 72 </template>
47 73
48 <script setup lang="ts"> 74 <script setup lang="ts">
49 -import { isMobile, isEqual } from '~/utils'  
50 -import { useProductListStore } from '~/stores/product_list'  
51 -import { useCategoryStore } from '~/stores/category'  
52 -import CategoryList from '~/components/CategoryList.vue'  
53 -import MobileCategoryList from '~/components/MobileCategoryList.vue'  
54 -import { watchEffect, computed, ref } from 'vue' 75 +import { isMobile, isEqual } from "~/utils";
  76 +import { useProductListStore } from "~/stores/product_list";
  77 +import { useCategoryStore } from "~/stores/category";
  78 +import CategoryList from "~/components/CategoryList.vue";
  79 +import MobileCategoryList from "~/components/MobileCategoryList.vue";
  80 +import { watchEffect, computed, ref } from "vue";
55 81
56 -const productStore = useProductListStore()  
57 -const categoryStore = useCategoryStore()  
58 -const loading = ref(false)  
59 -const route = useRoute() // 获取路由信息  
60 -useHead({  
61 - title: 'canrud',  
62 - meta: [{  
63 - name: 'title',  
64 - 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!"  
65 - }, {  
66 - name: 'keywords',  
67 - content:  
68 - '科路得,canrd,canrud,Energy Storage Research,Lithium Batteries Research,Material Reagents,Lab Device,Customized Battery,Testing,Pack',  
69 - },  
70 - {  
71 - name: 'description',  
72 - content:  
73 - '科路得,助您科研之路势在必得。We offer a wide range of research materials and related equipment, technical services, and battery material performance evaluation in areas such as pouch cells, lithium-ion batteries, supercapacitors, lithium-sulfur batteries, fuel cells, lithium-air batteries, and sodium-ion batteries.'  
74 - }  
75 - ]  
76 -}) 82 +const productStore = useProductListStore();
  83 +const categoryStore = useCategoryStore();
  84 +const loading = ref(false);
  85 +const route = useRoute(); // 获取路由信息
77 86
  87 +useHead({
  88 + title: "canrud",
  89 + meta: [
  90 + {
  91 + name: "title",
  92 + content:
  93 + "科路得,助您科研之路势在必得。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!",
  94 + },
  95 + {
  96 + name: "keywords",
  97 + content:
  98 + "科路得,canrd,canrud,Energy Storage Research,Lithium Batteries Research,Material Reagents,Lab Device,Customized Battery,Testing,Pack",
  99 + },
  100 + {
  101 + name: "description",
  102 + content:
  103 + "科路得,助您科研之路势在必得。We offer a wide range of research materials and related equipment, technical services, and battery material performance evaluation in areas such as pouch cells, lithium-ion batteries, supercapacitors, lithium-sulfur batteries, fuel cells, lithium-air batteries, and sodium-ion batteries.",
  104 + },
  105 + ],
  106 +});
78 107
79 const loadProducts = async () => { 108 const loadProducts = async () => {
80 let params: any = { 109 let params: any = {
81 pageNo: productStore.pageNo, 110 pageNo: productStore.pageNo,
82 - pageSize: 20  
83 - } 111 + pageSize: 20,
  112 + };
84 113
85 - loading.value = true 114 + loading.value = true;
86 if (productStore.keyword && !isEqual(productStore.params, params)) { 115 if (productStore.keyword && !isEqual(productStore.params, params)) {
87 - params.keyword = productStore.keyword  
88 - productStore.updateParams(params)  
89 - await productStore.getList(params)  
90 - loading.value = false  
91 - return 116 + params.keyword = productStore.keyword;
  117 + productStore.updateParams(params);
  118 + await productStore.getList(params);
  119 + loading.value = false;
  120 + return;
92 } 121 }
93 122
94 - params.productCategoryId = categoryStore.selectedSubCategory 123 + params.productCategoryId = categoryStore.selectedSubCategory;
95 // productCategoryId: '78b86c6e917841cf9a292234f2805e24', 124 // productCategoryId: '78b86c6e917841cf9a292234f2805e24',
96 125
97 if (categoryStore.selectedFuncCategory) { 126 if (categoryStore.selectedFuncCategory) {
98 - params.productFunctionId = categoryStore.selectedFuncCategory 127 + params.productFunctionId = categoryStore.selectedFuncCategory;
99 } 128 }
100 129
101 - if (categoryStore.selectedSubCategory && !isEqual(productStore.params, params)) {  
102 - productStore.updateParams(params) 130 + if (
  131 + categoryStore.selectedSubCategory &&
  132 + !isEqual(productStore.params, params)
  133 + ) {
  134 + productStore.updateParams(params);
103 135
104 - await productStore.getList(params) 136 + await productStore.getList(params);
105 } 137 }
106 - loading.value = false  
107 -}  
108 -  
109 - 138 + loading.value = false;
  139 +};
110 140
111 watchEffect(async () => { 141 watchEffect(async () => {
112 - if(route.query.keyword !== undefined){  
113 - productStore.keyword = route.query.keyword 142 + if (route.query.keyword !== undefined) {
  143 + productStore.keyword = route.query.keyword;
114 } 144 }
115 loadProducts(); 145 loadProducts();
116 -}) 146 +});
117 147
118 const length = computed(() => 148 const length = computed(() =>
119 productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0 149 productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0
120 -) 150 +);
121 </script> 151 </script>
122 152
123 <style scoped> 153 <style scoped>
stores/product_list.ts
1 -import { getProductList } from './../service'  
2 -import { ref, watchEffect } from 'vue'  
3 -import { defineStore } from 'pinia'  
4 -import type { Product, ProductImage, ProductListQuery } from '@/type' 1 +import { getProductList } from "./../service";
  2 +import { ref, watchEffect } from "vue";
  3 +import { defineStore } from "pinia";
  4 +import type { Product, ProductImage, ProductListQuery } from "@/type";
5 5
6 -export const useProductListStore = defineStore('productList', () => {  
7 - const list = ref<Product[]>([])  
8 - const productCategoryId = ref('') // 选中的类别  
9 - const keyword = ref()  
10 - const pageNo = ref(1)  
11 - const pageSize = ref(20)  
12 - const total = ref(0)  
13 - const params = ref() 6 +export const useProductListStore = defineStore("productList", () => {
  7 + const list = ref<Product[]>([]);
  8 + const productCategoryId = ref(""); // 选中的类别
  9 + const keyword = ref();
  10 + const pageNo = ref(1);
  11 + const pageSize = ref(20);
  12 + const total = ref(0);
  13 + const params = ref();
14 14
15 const getList = async (params: ProductListQuery) => { 15 const getList = async (params: ProductListQuery) => {
16 if (params.productCategoryId || params.keyword) { 16 if (params.productCategoryId || params.keyword) {
17 - const { data } = await useAsyncData('product', () => $fetch('/shop/product/list', {  
18 - method: 'GET',  
19 - params: {  
20 - ...params,  
21 - pageNo: pageNo.value,  
22 - pageSize: pageSize.value  
23 - }  
24 - }));  
25 - 17 + const { data } = await useAsyncData("product", () =>
  18 + $fetch("/shop/product/list", {
  19 + method: "GET",
  20 + params: {
  21 + ...params,
  22 + pageNo: pageNo.value,
  23 + pageSize: pageSize.value,
  24 + },
  25 + })
  26 + );
  27 +
26 list.value = 28 list.value =
27 (data.value?.data?.records || []).map((record: Product) => ({ 29 (data.value?.data?.records || []).map((record: Product) => ({
28 ...record, 30 ...record,
29 // http://112.74.45.244:8100/api/show/image?fileKey=ac82abea34243b7f7a56e5c3ca03f3a9 31 // http://112.74.45.244:8100/api/show/image?fileKey=ac82abea34243b7f7a56e5c3ca03f3a9
30 - imgList: JSON.parse(record.productimageliststore as unknown as string).map(  
31 - (item: ProductImage) => ({  
32 - url: `/api/show/image?fileKey=${item.fileKey}&psize=p256`  
33 - })  
34 - )  
35 - })) || []  
36 - total.value = data.value?.data?.total || 0 32 + imgList: JSON.parse(
  33 + record.productimageliststore as unknown as string
  34 + ).map((item: ProductImage) => ({
  35 + url: `/api/show/image?fileKey=${item.fileKey}&psize=p256`,
  36 + })),
  37 + })) || [];
  38 + total.value = data.value?.data?.total || 0;
37 } 39 }
38 - } 40 + };
39 41
40 const updatePageNo = (value: number) => { 42 const updatePageNo = (value: number) => {
41 - pageNo.value = value  
42 - } 43 + pageNo.value = value;
  44 + };
43 45
44 const updateCategory = (value: string) => { 46 const updateCategory = (value: string) => {
45 - productCategoryId.value = value  
46 - } 47 + productCategoryId.value = value;
  48 + };
47 49
48 const updateParams = (value: any) => { 50 const updateParams = (value: any) => {
49 - params.value = value  
50 - } 51 + params.value = value;
  52 + };
51 53
52 const updateKeyword = (value: string) => { 54 const updateKeyword = (value: string) => {
53 - keyword.value = value  
54 - } 55 + keyword.value = value;
  56 + };
55 57
56 return { 58 return {
57 pageNo, 59 pageNo,
@@ -64,6 +66,6 @@ export const useProductListStore = defineStore(&#39;productList&#39;, () =&gt; { @@ -64,6 +66,6 @@ export const useProductListStore = defineStore(&#39;productList&#39;, () =&gt; {
64 updateCategory, 66 updateCategory,
65 updatePageNo, 67 updatePageNo,
66 updateParams, 68 updateParams,
67 - updateKeyword  
68 - }  
69 -}) 69 + updateKeyword,
  70 + };
  71 +});