Commit 5010e9d9de03d2426c2b46cc7ab63e3e0e7970b0
1 parent
9b642ad1
feat: title对应产品名,meta动态获取
Showing
5 changed files
with
315 additions
and
208 deletions
components/ProductDetail.vue
... | ... | @@ -2,9 +2,19 @@ |
2 | 2 | <v-container> |
3 | 3 | <v-row class="mb-16 ma-0"> |
4 | 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 | 18 | </v-carousel-item> |
9 | 19 | </v-carousel> |
10 | 20 | </v-col> |
... | ... | @@ -14,42 +24,90 @@ |
14 | 24 | </v-row> |
15 | 25 | <div class="tw-flex tw-flex-wrap"> |
16 | 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 | 30 | </div> |
19 | 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 | 35 | </div> |
22 | 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 | 40 | </div> |
25 | 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 | 45 | </div> |
28 | 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 | 50 | </div> |
31 | 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 | 66 | </div> |
36 | 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 | 73 | <thead> |
39 | 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 | 87 | <th class="text-left headerBorder text-grey-darken-1">Actions</th> |
44 | 88 | </tr> |
45 | 89 | </thead> |
46 | 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 | 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 | 111 | <!-- Quotation Inquiry --> |
54 | 112 | Quote |
55 | 113 | </v-btn> |
... | ... | @@ -63,8 +121,14 @@ |
63 | 121 | </v-col> |
64 | 122 | </v-row> |
65 | 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 | 132 | <v-tab :value="1">Product Details</v-tab> |
69 | 133 | <v-tab :value="2">Specification</v-tab> |
70 | 134 | <!-- <v-tab :value="3">商品百科</v-tab> --> |
... | ... | @@ -100,7 +164,11 @@ |
100 | 164 | <v-window-item key="2" :value="2"> |
101 | 165 | <v-table density="compact" class="table2"> |
102 | 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 | 172 | <td class="td tw-w-[400px]">{{ item.name }}</td> |
105 | 173 | <td class="td">{{ item.value }}</td> |
106 | 174 | </tr> |
... | ... | @@ -114,23 +182,23 @@ |
114 | 182 | </template> |
115 | 183 | |
116 | 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 | 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 | 195 | // onMounted(() => { |
128 | 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 | 202 | </script> |
135 | 203 | |
136 | 204 | <style lang="scss" scoped> | ... | ... |
nuxt.config.ts
1 | 1 | // https://nuxt.com/docs/api/configuration/nuxt-config |
2 | 2 | // Nuxt config file |
3 | -import { defineNuxtConfig } from 'nuxt/config' | |
3 | +import { defineNuxtConfig } from "nuxt/config"; | |
4 | 4 | |
5 | 5 | export default defineNuxtConfig({ |
6 | 6 | app: { |
7 | 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 | 12 | postcss: { |
15 | 13 | plugins: { |
16 | 14 | tailwindcss: {}, |
... | ... | @@ -22,45 +20,40 @@ export default defineNuxtConfig({ |
22 | 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 | 24 | vuetify: { |
31 | 25 | moduleOptions: { |
32 | 26 | /* module specific options */ |
33 | 27 | }, |
34 | 28 | vuetifyOptions: { |
35 | 29 | /* vuetify options */ |
36 | - } | |
30 | + }, | |
37 | 31 | }, |
38 | - nitro:{ | |
32 | + nitro: { | |
39 | 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 | 37 | // target: process.env.BASE_URL || 'http://39.108.227.113:8002/shop', // 目标接口域名 |
44 | 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 | 46 | // proxy: process.env.BASE_URL || 'http://39.108.227.113:8002/shop/**' |
53 | - } | |
54 | - } | |
47 | + }, | |
48 | + }, | |
55 | 49 | }, |
56 | 50 | i18n: { |
57 | 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 | 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 | 4 | </template> |
5 | 5 | |
6 | 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 | 15 | const info = ref<Partial<Product>>({ |
16 | 16 | productimageliststore: [], |
17 | 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 | 76 | </script> | ... | ... |
pages/products/index.vue
... | ... | @@ -4,14 +4,31 @@ |
4 | 4 | <MobileCategoryList v-if="categoryStore.categoryVisible && isMobile()" /> |
5 | 5 | <v-container class=""> |
6 | 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 | 13 | </div> |
9 | 14 | <v-item-group multiple> |
10 | 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 | 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 | 32 | <v-img :src="item.imgList[0].url" :alt="item.name"> |
16 | 33 | <!-- <v-expand-transition> |
17 | 34 | <div |
... | ... | @@ -24,100 +41,113 @@ |
24 | 41 | </v-expand-transition> --> |
25 | 42 | </v-img> |
26 | 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 | 45 | </v-card-text> |
31 | 46 | </v-card> |
32 | 47 | </v-hover> |
33 | 48 | </v-col> |
34 | 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 | 54 | no data |
37 | 55 | </div> |
38 | 56 | </v-item-group> |
39 | 57 | <v-row> |
40 | 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 | 70 | </v-container> |
45 | 71 | </div> |
46 | 72 | </template> |
47 | 73 | |
48 | 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 | 108 | const loadProducts = async () => { |
80 | 109 | let params: any = { |
81 | 110 | pageNo: productStore.pageNo, |
82 | - pageSize: 20 | |
83 | - } | |
111 | + pageSize: 20, | |
112 | + }; | |
84 | 113 | |
85 | - loading.value = true | |
114 | + loading.value = true; | |
86 | 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 | 124 | // productCategoryId: '78b86c6e917841cf9a292234f2805e24', |
96 | 125 | |
97 | 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 | 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 | 145 | loadProducts(); |
116 | -}) | |
146 | +}); | |
117 | 147 | |
118 | 148 | const length = computed(() => |
119 | 149 | productStore.total ? Math.ceil(productStore.total / productStore.pageSize) : 0 |
120 | -) | |
150 | +); | |
121 | 151 | </script> |
122 | 152 | |
123 | 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 | 15 | const getList = async (params: ProductListQuery) => { |
16 | 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 | 28 | list.value = |
27 | 29 | (data.value?.data?.records || []).map((record: Product) => ({ |
28 | 30 | ...record, |
29 | 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 | 42 | const updatePageNo = (value: number) => { |
41 | - pageNo.value = value | |
42 | - } | |
43 | + pageNo.value = value; | |
44 | + }; | |
43 | 45 | |
44 | 46 | const updateCategory = (value: string) => { |
45 | - productCategoryId.value = value | |
46 | - } | |
47 | + productCategoryId.value = value; | |
48 | + }; | |
47 | 49 | |
48 | 50 | const updateParams = (value: any) => { |
49 | - params.value = value | |
50 | - } | |
51 | + params.value = value; | |
52 | + }; | |
51 | 53 | |
52 | 54 | const updateKeyword = (value: string) => { |
53 | - keyword.value = value | |
54 | - } | |
55 | + keyword.value = value; | |
56 | + }; | |
55 | 57 | |
56 | 58 | return { |
57 | 59 | pageNo, |
... | ... | @@ -64,6 +66,6 @@ export const useProductListStore = defineStore('productList', () => { |
64 | 66 | updateCategory, |
65 | 67 | updatePageNo, |
66 | 68 | updateParams, |
67 | - updateKeyword | |
68 | - } | |
69 | -}) | |
69 | + updateKeyword, | |
70 | + }; | |
71 | +}); | ... | ... |