panhui vor 3 Jahren
Ursprung
Commit
110d2ef39b

BIN
src/assets/search.png


BIN
src/assets/tabbar_icon_02_pre.png


+ 52 - 8
src/components/ProductInfo.vue

@@ -1,15 +1,15 @@
 <template>
-    <div class="product-info">
-        <van-image width="104" height="104" fit="cover" :src="productImg" />
+    <div class="product-info" :class="{ product2: list }" @click="goDetail">
+        <van-image width="104" height="104" fit="cover" :src="getImg(info.pic)" />
         <div class="content">
-            <div class="text1 van-ellipsis">名称名称名称名称名称名称名称名称名称名称名称名称…</div>
+            <div class="text1 van-ellipsis">{{ info.name }}</div>
             <div class="badges">
-                <div class="badge" v-for="i in 3" :key="i">小标语</div>
+                <div class="badge">{{ category.name }}</div>
             </div>
             <div class="flex1"></div>
             <div class="datas">
                 <div class="data">
-                    <div class="val">10.00</div>
+                    <div class="val">{{ info.currentPrice }}</div>
                     <div class="name">当前价(元)</div>
                 </div>
                 <div class="data">
@@ -17,16 +17,48 @@
                     <div class="name">日化收益</div>
                 </div>
                 <div class="data">
-                    <div class="val">0.30</div>
+                    <div class="val">{{ info.premium || 0 }}</div>
                     <div class="name">明日可得(元)</div>
                 </div>
             </div>
+
         </div>
     </div>
 </template>
 
-<script setup>
-import productImg from '../assets/c1.png'
+<script lang="ts">
+import { defineComponent, computed } from 'vue'
+import { useRouter } from 'vue-router'
+export default defineComponent({
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        list: {
+            type: Boolean,
+            default: false
+        }
+    },
+    setup(props) {
+        const category = computed(() => {
+            return props.info.category || {}
+        })
+
+        const router = useRouter()
+        const goDetail = () => {
+            router.push({
+                path: '/productDetail',
+                query: {
+                    id: props.info.id
+                }
+            })
+        }
+        return { category, goDetail }
+    }
+})
 </script>
 
 <style lang="less" scoped>
@@ -48,6 +80,7 @@ import productImg from '../assets/c1.png'
         font-size: 14px;
         color: #1b1300;
         line-height: 24px;
+        font-weight: bold;
     }
 
     .badges {
@@ -98,4 +131,15 @@ import productImg from '../assets/c1.png'
         }
     }
 }
+
+.product2 {
+    .content {
+        .badges {
+            .badge {
+                color: #4078de;
+                background: #e9effb;
+            }
+        }
+    }
+}
 </style>

+ 3 - 0
src/main.js

@@ -33,6 +33,8 @@ import 'vant/lib/index.css'
 import './styles/main.less'
 import './styles/theme/variables.less'
 
+import common from './mixins/common'
+
 const app = createApp(App)
 app.use(i18n)
 app.use(createPinia())
@@ -41,6 +43,7 @@ app.use(router)
 app.use(http)
 app.use(Vant)
 app.use(ConfigProvider)
+app.mixin(common)
 
 // ionic components
 Object.keys(IonComponents).forEach(key => {

+ 23 - 0
src/mixins/common.js

@@ -0,0 +1,23 @@
+export default {
+    methods: {
+        getImg(imgs = '', type = '', size = 800) {
+            if (!imgs) {
+                imgs = ''
+            }
+            if (!(imgs instanceof Array)) {
+                imgs = imgs.split(',')
+            }
+
+            imgs = imgs.filter(item => {
+                return !!item
+            })
+            if (imgs.length > 0) {
+                let img = type ? imgs[0][type] : imgs[0]
+                // console.log(img);
+                return img + (/\.gif$/i.test(img) ? '' : `?x-oss-process=image/resize,h_${size},m_lfit`)
+            } else {
+                return ''
+            }
+        }
+    }
+}

+ 78 - 0
src/plugins/list.js

@@ -0,0 +1,78 @@
+import { ref } from 'vue'
+import { getCurrentInstance } from 'vue'
+
+function useList(url, beforeData = {}, httpType) {
+    const {
+        appContext: {
+            config: { globalProperties: global }
+        }
+    } = getCurrentInstance()
+
+    const empty = ref(false)
+    const loading = ref(false)
+    const finished = ref(false)
+    const page = ref(0)
+    const totalElements = ref(0)
+    const size = ref(20)
+    const list = ref([])
+    const getData = (isRefresh = false) => {
+        if (isRefresh) {
+            page.value = 0
+            list.value = []
+        }
+        loading.value = true
+        finished.value = false
+        empty.value = false
+
+        let data = { page: page.value, size: size.value, sort: 'createdAt,desc' }
+        if (beforeData) {
+            data = {
+                ...data,
+                ...beforeData
+            }
+        }
+
+        if (httpType === 'get') {
+            return global.$http.get(url, data, { body: 'json' }).then(res => {
+                if (res.first) {
+                    list.value = []
+                }
+                list.value = [...list.value, ...res.content]
+                empty.value = res.empty
+                loading.value = false
+                finished.value = res.last
+                if (!finished.value) {
+                    page.value = page.value + 1
+                }
+                totalElements.value = Number(res.totalElements)
+            })
+        } else {
+            return global.$http.post(url, data, { body: 'json' }).then(res => {
+                if (res.first) {
+                    list.value = []
+                }
+                list.value = [...list.value, ...res.content]
+                empty.value = res.empty
+                loading.value = false
+                finished.value = res.last
+                if (!finished.value) {
+                    page.value = page.value + 1
+                }
+                totalElements.value = Number(res.totalElements)
+            })
+        }
+    }
+
+    return {
+        empty,
+        loading,
+        finished,
+        page,
+        totalElements,
+        size,
+        list,
+        getData
+    }
+}
+
+export default useList

+ 10 - 0
src/router/index.js

@@ -62,6 +62,16 @@ const router = createRouter({
             path: '/balanceRecord',
             name: 'balanceRecord',
             component: () => import('@/views/BalanceRecordPage.vue')
+        },
+        {
+            path: '/productList',
+            name: 'productList',
+            component: () => import('@/views/ProductListPage.vue')
+        },
+        {
+            path: '/productDetail',
+            name: 'productDetail',
+            component: () => import('@/views/ProductDetailPage.vue')
         }
     ]
 })

+ 52 - 7
src/views/HomePage.vue

@@ -61,9 +61,25 @@
 
             <!-- 初级场 -->
 
-            <template v-for="(item, index) in categories" :key="index">
+            <template v-for="(item, index) in saleBatchs" :key="index">
                 <div class="level">
-                    <van-cell :title="item.name" value="全部" :border="false" is-link />
+                    <van-cell
+                        :title="item.name"
+                        value="全部"
+                        :to="{
+                            path: '/productList',
+                            query: {
+                                batchId: item.id
+                            }
+                        }"
+                        :border="false"
+                        is-link
+                    >
+                        <template #right-icon>
+                            <img src="../assets/icon_inter.png" alt="" class="right-icon" />
+                        </template>
+                    </van-cell>
+
                     <div class="first" v-if="index === 0">
                         <van-image
                             :src="item.icon || firstImg"
@@ -73,7 +89,7 @@
                             radius="4"
                         />
                         <div class="first-content">
-                            <product-info></product-info>
+                            <product-info :info="hotInfo"></product-info>
                         </div>
 
                         <div class="status hot">
@@ -151,16 +167,39 @@ function getBanner() {
         })
     })
 }
-const categories = ref([])
-function getCategory() {
+
+const saleBatchs = ref([])
+const hotInfo = ref({})
+function getSaleBatch() {
     global.$http.post('saleBatch/all', {}, { body: 'json' }).then(res => {
-        categories.value = res.content
+        saleBatchs.value = res.content
+        if (!res.empty) {
+            global.$http
+                .post(
+                    'product/all',
+                    {
+                        query: {
+                            batchId: res.content[0].id,
+                            status: 'IN_STOCK',
+                            enabled: true
+                        },
+                        size: 1,
+                        sort: 'id,desc'
+                    },
+                    { body: 'json' }
+                )
+                .then(res => {
+                    if (!res.empty) {
+                        hotInfo.value = res.content[0]
+                    }
+                })
+        }
     })
 }
 
 onMounted(() => {
     getBanner()
-    getCategory()
+    getSaleBatch()
 })
 </script>
 
@@ -237,10 +276,16 @@ onMounted(() => {
     --van-cell-background-color: var(--bg2);
     --van-cell-horizontal-padding: 20px;
     background: var(--bg2);
+    .f();
     .van-cell__title {
         font-size: 16px;
         font-weight: bold;
     }
+
+    .right-icon {
+        width: 24px;
+        height: 24px;
+    }
 }
 .first {
     margin: 4px 20px 14px;

+ 3 - 3
src/views/MinePage.vue

@@ -181,7 +181,7 @@ const user = computed(() => {
         display: block;
     }
 
-    /deep/.van-grid {
+    :deep(.van-grid) {
         --van-grid-item-text-color: #686868;
         --van-grid-item-content-padding: 9px 8px;
 
@@ -203,7 +203,7 @@ const user = computed(() => {
             --van-grid-item-content-background: #f8faff;
         }
 
-        /deep/.van-grid {
+        :deep(.van-grid) {
             --van-grid-item-text-color: #000;
             --van-grid-item-text-font-size: 15px;
             .van-grid-item__text {
@@ -217,7 +217,7 @@ const user = computed(() => {
     margin-top: 14px;
 }
 
-/deep/.menu {
+:deep(.menu) {
     --van-cell-line-height: 28px;
     --van-cell-text-color: #000;
     --van-cell-font-size: 14px;

+ 213 - 0
src/views/ProductDetailPage.vue

@@ -0,0 +1,213 @@
+<template>
+    <ion-page class="product">
+        <ion-content>
+            <swiper class="bannerSwiper">
+                <swiper-slide v-for="(item, index) in info.pic" :key="index">
+                    <van-image :src="item" fit="cover" height="100vw" width="100vw" />
+                </swiper-slide>
+            </swiper>
+            <div class="info">
+                <div class="card">
+                    <div class="title">
+                        {{ info.name }}
+                    </div>
+                    <div class="badges">
+                        <div class="badge">{{ category.name }}</div>
+                    </div>
+                    <div class="datas">
+                        <div class="data">
+                            <div class="val">{{ info.currentPrice }}</div>
+                            <div class="name">当前价(元)</div>
+                        </div>
+                        <div class="data">
+                            <div class="val">3%</div>
+                            <div class="name">日化收益</div>
+                        </div>
+                        <div class="data">
+                            <div class="val">{{ info.premium || 0 }}</div>
+                            <div class="name">明日可得(元)</div>
+                        </div>
+                    </div>
+
+                    <div class="creator" v-if="user.id">
+                        <div class="text1">产品持有人:</div>
+                        <van-image width="20" height="20" radius="4" :src="user.avatar"></van-image>
+                        <div class="text2">{{ user.nickname }}</div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="detail">
+                <div class="card">
+                    <div class="detail-title">商品详情</div>
+                    <div class="detail-content">
+                        <van-image
+                            v-for="(item, index) in info.pic"
+                            :key="index"
+                            :src="item"
+                            radius="4"
+                            fit="scale-down"
+                            width="100%"
+                        />
+                    </div>
+                </div>
+            </div>
+        </ion-content>
+        <ion-footer v-if="info.status === 'IN_STOCK'">
+            <ion-toolbar>
+                <div class="footer">
+                    <van-button type="primary" block>立即购买</van-button>
+                </div>
+            </ion-toolbar>
+        </ion-footer>
+    </ion-page>
+</template>
+
+<script setup>
+import { Swiper, SwiperSlide } from 'swiper/vue'
+import 'swiper/swiper.min.css'
+import { ref, getCurrentInstance, onMounted, computed } from 'vue'
+import { useRoute } from 'vue-router'
+
+const {
+    appContext: {
+        config: { globalProperties: global }
+    }
+} = getCurrentInstance()
+const route = useRoute()
+
+const info = ref({
+    pic: []
+})
+
+const category = computed(() => {
+    return info.value.category || {}
+})
+const user = computed(() => {
+    return info.value.user || {}
+})
+
+function getDetail() {
+    global.$http.get('/product/get/' + route.query.id).then(res => {
+        info.value = res
+    })
+}
+
+onMounted(() => {
+    getDetail()
+})
+</script>
+
+<style lang="less" scoped>
+.info {
+    transform: translateY(-20px);
+    position: relative;
+    z-index: 2;
+
+    .title {
+        font-size: 20px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 28px;
+        margin-bottom: 6px;
+    }
+
+    .badges {
+        .f();
+        .badge {
+            font-size: 10px;
+            background: #e9effb;
+            color: #4078de;
+            line-height: 16px;
+            padding: 0 2px;
+        }
+    }
+
+    .datas {
+        .f();
+        .data {
+            width: 33%;
+            .f-col();
+            align-items: center;
+            margin-top: 16px;
+            .val {
+                font-size: 20px;
+                font-weight: bold;
+                color: #000000;
+                line-height: 24px;
+            }
+            .name {
+                font-size: 12px;
+                color: rgba(0, 0, 0, 0.6);
+                line-height: 17px;
+            }
+
+            &:nth-child(1) {
+                align-items: flex-start;
+                .val {
+                    color: var(--red);
+                }
+            }
+
+            &:nth-child(2) {
+                .val {
+                    color: var(--green);
+                }
+            }
+        }
+    }
+
+    .creator {
+        .f();
+        background: #f5f7fa;
+        border-radius: 4px;
+        padding: 7px 12px;
+        margin-top: 16px;
+
+        .text1 {
+            font-size: 12px;
+            color: #000000;
+            line-height: 17px;
+        }
+
+        .text2 {
+            font-size: 12px;
+            font-weight: bold;
+            color: #000000;
+            line-height: 17px;
+        }
+
+        .van-image {
+            margin: 0 6px;
+        }
+    }
+}
+.detail {
+    transform: translateY(-10px);
+}
+.bannerSwiper {
+    position: relative;
+    z-index: 1;
+}
+.card {
+    background: #ffffff;
+    border-radius: 16px;
+    padding: 16px 16px 20px;
+}
+
+.detail {
+    .detail-title {
+        font-size: 16px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 24px;
+        padding-bottom: 16px;
+    }
+}
+
+.footer {
+    padding: 6px 24px;
+    --van-button-border-width: 0;
+    --van-button-primary-background: linear-gradient(135deg, #d700ff 0%, #3e22ff 100%);
+}
+</style>

+ 137 - 0
src/views/ProductListPage.vue

@@ -0,0 +1,137 @@
+<template>
+    <ion-page>
+        <ion-header>
+            <ion-toolbar>
+                <ion-buttons slot="start">
+                    <div class="header-title">{{ batchInfo.name }}</div>
+                </ion-buttons>
+                <ion-buttons slot="end">
+                    <ion-button>
+                        <img class="search" src="../assets/search.png" alt="" />
+                    </ion-button>
+                </ion-buttons>
+            </ion-toolbar>
+        </ion-header>
+        <div class="status hot" v-if="status === '抢购中'">
+            <img src="../assets/info_icon_qianggouzhong.png" alt="" />
+            <span>火爆抢购中…</span>
+        </div>
+        <div class="status" v-else-if="status">
+            <img src="../assets/info_icon_shijian.png" alt="" />
+            <span>{{ status }}</span>
+        </div>
+        <ion-content>
+            <van-list class="list" v-model:loading="loading" :finished="finished" finished-text="" @load="getData">
+                <div class="product" v-for="(item, index) in list" :key="index">
+                    <product-info list v-model:info="list[index]"></product-info>
+                </div>
+            </van-list>
+        </ion-content>
+    </ion-page>
+</template>
+
+<script setup>
+import productInfo from '../components/ProductInfo.vue'
+import { ref, getCurrentInstance, onMounted, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import useList from '../plugins/list'
+import { isAfter, isBefore, parse, getYear, getMonth, getDate } from 'date-fns'
+
+const route = useRoute()
+const {
+    appContext: {
+        config: { globalProperties: global }
+    }
+} = getCurrentInstance()
+
+const batchInfo = ref({})
+
+let beforeData = {
+    query: {
+        batchId: route.query.batchId,
+        status: 'IN_STOCK',
+        enabled: true
+    },
+    sort: 'id,desc'
+}
+const { empty, loading, finished, list, getData } = useList('/product/all', beforeData)
+function getBatch() {
+    global.$http.get('/saleBatch/get/' + route.query.batchId).then(res => {
+        batchInfo.value = res
+    })
+}
+
+const status = computed(() => {
+    let date1 = getYear(new Date()) + '/' + (getMonth(new Date()) + 1) + '/' + getDate(new Date())
+    console.log(date1)
+    if (batchInfo.value) {
+        if (
+            isAfter(new Date(), new Date(date1 + ' ' + batchInfo.value.saleStart)) &&
+            isBefore(new Date(), new Date(date1 + ' ' + batchInfo.value.saleEnd))
+        ) {
+            return '抢购中'
+        } else {
+            return batchInfo.value.saleStart + ' 开抢'
+        }
+    }
+    return ''
+})
+
+onMounted(() => {
+    getData(true)
+    getBatch()
+})
+</script>
+
+<style lang="less" scoped>
+:deep(.in-toolbar) {
+    --padding-start: 16px;
+    --padding-end: 8px;
+    --background: #fff;
+    --ion-toolbar-border-color: #fff;
+    position: relative;
+}
+
+.header-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #000000;
+    line-height: 24px;
+}
+
+.search {
+    width: 24px;
+    height: 24px;
+    display: block;
+}
+
+.list {
+    padding: 12px 0;
+    .product {
+        margin: 0 16px 12px;
+        background: #ffffff;
+        border-radius: 4px;
+    }
+}
+
+.status {
+    .f();
+    justify-content: center;
+    background: var(--green);
+    height: 28px;
+    img {
+        width: 18px;
+        height: 18px;
+    }
+    span {
+        font-size: 12px;
+        color: #ffffff;
+        line-height: 22px;
+        margin-left: 6px;
+    }
+
+    &.hot {
+        background: #ed1e31;
+    }
+}
+</style>

+ 0 - 27
src/views/Tab1Page.vue

@@ -1,27 +0,0 @@
-<template>
-    <ion-page>
-        <ion-header>
-            <ion-toolbar>
-                <ion-title>Tab 1</ion-title>
-            </ion-toolbar>
-        </ion-header>
-        <ion-content :fullscreen="true">
-            <ion-header collapse="condense">
-                <ion-toolbar>
-                    <ion-title size="large">Tab 1</ion-title>
-                </ion-toolbar>
-            </ion-header>
-
-            <ion-button @click="navigate">{{ $t('login') }}</ion-button>
-        </ion-content>
-    </ion-page>
-</template>
-
-<script setup>
-import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/vue'
-import { useRouter } from 'vue-router'
-const router = useRouter()
-function navigate() {
-    router.push({ name: 'login' })
-}
-</script>

+ 0 - 28
src/views/Tab3Page.vue

@@ -1,28 +0,0 @@
-<template>
-    <ion-page>
-        <ion-header>
-            <ion-toolbar>
-                <ion-title>Tab 3</ion-title>
-            </ion-toolbar>
-        </ion-header>
-        <ion-content :fullscreen="true">
-            <ion-header collapse="condense">
-                <ion-toolbar>
-                    <ion-title size="large">Tab 3</ion-title>
-                </ion-toolbar>
-            </ion-header>
-            <IonToggle :checked="isDark" @ionChange="change"></IonToggle>
-        </ion-content>
-    </ion-page>
-</template>
-
-<script setup>
-import { defineComponent } from 'vue'
-import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonToggle } from '@ionic/vue'
-import { useDark, useToggle } from '@vueuse/core'
-const isDark = useDark()
-const toggleDark = useToggle(isDark)
-const change = e => {
-    toggleDark(e.detail.checked)
-}
-</script>