文章目录
- 目标
- 过程与代码
- content组件
- 请求数据:houseList
- request
- store
- 控制台输出
 
- 动态加载更多列表数据
- house-item组件
- 阶段1:数据传送
- 阶段2:对着目标写样式
- house-item-v9
- house-item-v9:debug
- house-item-v3
 
- 阶段3:总体效果
 
 
- 效果
- 总代码
- 修改或添加的文件
- house-item
- house-item-v9
- house-item-v3
 
- service的home.js
- store的home.js
- cpns的home-content
- main.js
 
- 参考
 
本项目博客总结:【前端】Vue项目:旅游App-博客总结
目标
完成热门精选的内容。
- 数据来源:网络请求123.207.32.32:1888/api/home/houselist?page=1
- 把它抽取成组件

过程与代码
content组件
我们将houseList的内容抽取到一个组件中,将其命名为home-content.vue。
<template>
    <div class="content">
        <h2>热门精选</h2>
        <div class="list">
        </div>
    </div>
</template>
<script setup>
</script>
<style lang="less" scoped>
.content {
    padding: 0 20px;
    h2 {
        font-size: 20px;
        font-weight: 700;
    }
}
</style>
请求数据:houseList
数据是分页的。
第1页:123.207.32.32:1888/api/home/houselist?page=1
 第2页:123.207.32.32:1888/api/home/houselist?page=2
 以此类推。
本项目已经多此实现对数据请求的功能,因此这里不再赘述。
数据输出:

request
get请求的参数为params,post请求的参数为data。
这里先写死:请求第一页数据。
export function getHouseList() {
    return HYRequest.get({
        url: '/home/houselist',
        params: {
            page: 1
        }
    })
}
store
// home.vue页面所有的进行网络请求和数据都封装到这里
import { defineStore } from "pinia";
import { getHotSuggest, getCategories, getHouseList } from '@/service'
const useHomeStore = defineStore('home', {
    state: () => {
        return {
            hotSuggest: [],
            categories: [],
            houseList: [],
        }
    },
    actions: {
        // 网络请求,由于返回一个promise,要异步async await
        async fetchHotSuggest() {
            const res = await getHotSuggest()
            this.hotSuggest = res.data
            // console.log(res)
        },
        async fetchCategories() {
            const res = await getCategories()
            this.categories = res.data
        },
        async fetchHouseList() {
            const res = await getHouseList()
            this.houseList = res.data
        }
    }
})
export default useHomeStore
控制台输出
数据请求成功。
 
动态加载更多列表数据
这里的数据一页只有20条,而我们也在代码中写死参数page=1。
 实际上,App中此模块的数据是在下拉过程中加载更多的数据。因此我们要动态地加载数据。
 这里要使用Array.push和...解构语法。
为什么要解构?
 答:网络请求得到的是一个数组,不解构会使store中的数据变成二维数组,这不是我们想要的。
如何动态地加载数据?
 我们可以先用一个按钮模拟这个功能,每次点击按钮就加载更多的数据,page的参数可以用currentPage来表示,每点击按钮令currentPage++。currentPage存在store中。
request:
export function getHouseList(currentPage) {
    return HYRequest.get({
        url: '/home/houselist',
        params: {
            page: currentPage
        }
    })
}
store:
// home.vue页面所有的进行网络请求和数据都封装到这里
import { defineStore } from "pinia";
import { getHotSuggest, getCategories, getHouseList } from '@/service'
const useHomeStore = defineStore('home', {
    state: () => {
        return {
            hotSuggest: [],
            categories: [],
            houseList: [],
            currentPage: 1,
        }
    },
    actions: {
        // 网络请求,由于返回一个promise,要异步async await
        async fetchHotSuggest() {
            const res = await getHotSuggest()
            this.hotSuggest = res.data
            // console.log(res)
        },
        async fetchCategories() {
            const res = await getCategories()
            this.categories = res.data
        },
        async fetchHouseList() {
            const res = await getHouseList(this.currentPage)
            this.currentPage++
            this.houseList.push(...res.data)
        }
    }
})
export default useHomeStore
按钮:
<button @click="moreList()">page++</button>
function moreList(){
    homeStore.fetchHouseList()
}
初始状态:说明只请求了第一页数据。

 点一次按钮:请求了第二页数据。
 
 注意store中currentPage的代码。它表示的是下一次要请求的页面。
根据F12中的网络也可以得知请求的数据:

house-item组件
显然house-item可能会在项目中多个地方使用,因此我们要把它单独抽为对应组件:

 注意,数据中discoveryContentType表示不同的展示列表数据的方式。上图左为9,右为3.
思路:
- 在house-content判断discoveryContentType,为3则引入组件house-item-v3,为9则引入组件house-item-v9
- 在house-item中定义props:item
- 在house-content中传入数据house-item
阶段1:数据传送
数据的传送情况如下:

 当前效果:

 对应代码:
home-content:
<template>
    <div class="content">
        <h2>热门精选</h2>
        <div class="list">
            <template v-for="(item, index) in houseList" :key="item.data.houseId">
                <houseItemV9 v-if="item.discoveryContentType === 9" :item="item.data"></houseItemV9>
                <houseItemV3 v-else-if="item.discoveryContentType === 3" :item="item.data"></houseItemV3>
            </template>
        </div>
    </div>
</template>
<script setup>
import { storeToRefs } from "pinia";
import useHomeStore from "../../../store/modules/home";
import houseItemV9 from "../../../components/house-item/house-item-v9.vue";
import houseItemV3 from "../../../components/house-item/house-item-v3.vue";
const homeStore = useHomeStore()
homeStore.fetchHouseList()
const { houseList } = storeToRefs(homeStore)
console.log(houseList)
</script>
<style lang="less" scoped>
.content {
    padding: 0 20px;
    h2 {
        font-size: 20px;
        font-weight: 700;
    }
}
</style>
house-item-v9:
<template>
    <div class="house-item">
        <h2>v9 {{item.houseName}}</h2>
    </div>
</template>
<script setup>
defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
</script>
<style lang="less" scoped>
</style>
house-item-v3:
<template>
    <div class="house-item">
        <h2>v3 {{item.houseName}}</h2>
    </div>
</template>
<script setup>
defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
</script>
<style lang="less" scoped>
</style>
阶段2:对着目标写样式
评分使用vant库。Rate 评分 - Vant 4 (gitee.io)
要显示小数。

house-item-v9
效果:

 代码:
<template>
    <div class="house-item">
        <div class="house-inner">
            <div class="image">
                <img :src="item.image.url" alt="">
            </div>
            <div class="info">
                <div class="summary">{{ item.summaryText }}</div>
                <div class="name">{{ item.houseName }}</div>
                <div class="tips">
                    <div class="stars" >
                        <van-rate v-model="starValue" readonly allow-half size="12px"/>
                    </div>
                    <div class="price">
                        ¥{{ item.finalPrice }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
const starValue = ref(props.item.commentScore);
</script>
<style lang="less" scoped>
.house-item {
    // 每个item宽度占屏幕一半
    width: 50%;
    margin-top: 20px;
    .house-inner {
        // 信息在img上:子绝父相
        position: relative;
        margin: 5px;
        .image {
            img {
                width: 100%;
                border-radius: 6px;
            }
        }
        .info {
            position: absolute;
            bottom: 0;
            padding: 8px 10px;
            color: #fff;
            .summary {
                font-size: 12px;
            }
            .name {
                // 超过2行就省略号省略...
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
                margin: 5px 0;
            }
            .tips{
                display: flex;
                justify-content: space-between;
            }
        }
    }
}
</style>
house-item-v9:debug
出现了一个问题:4.8分只有4颗星。
控制台报错:

 大致意思:modelValue要的是Number而不是String。
查看Vue插件的starValue:是字符串。

 因此我们要把它改成Number。
const starValue = ref(Number(props.item.commentScore));
house-item-v3
icon的引入:Icon 图标 - Vant 4 (gitee.io)

 效果:

 代码:
<template>
    <div class="house-item">
        <div class="house-inner">
            <div class="image">
                <img :src="item.image.url" alt="">
            </div>
            <div class="info">
                <div class="location">
                    <van-icon name="location" color="#808080" />
                    {{ item.location }}
                </div>
                <div class="name">{{ item.houseName }}</div>
                <div class="summary">{{ item.summaryText }}</div>
                <div class="price">
                    <div class="cheap">¥{{ item.finalPrice }}</div>
                    <div class="exp">{{ item.productPrice }}</div>
                    <div class="sale">立减¥{{ item.productPrice - item.finalPrice }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
</script>
<style lang="less" scoped>
.house-item {
    width: 50%;
    .house-inner {
        margin: 5px;
        .image {
            img {
                width: 100%;
                border-radius: 6px;
            }
        }
        .info {
            padding: 15px 8px 0;
            .location {
                color: #000;
            }
            .name {
                color: #333;
                margin: 5px 0;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }
            .summary {
                color: #666;
                font-size: 12px;
            }
            .price {
                display: flex;
                justify-content: left;
                align-items: center;
                margin: 10px 0;
                .cheap {
                    color: var(--primary-color);
                }
                .exp {
                    color: #999;
                    text-decoration: line-through;
                    font-size: 12px;
                    margin: 0 5px;
                }
                .sale {
                    font-size: 12px;
                    color: #fff;
                    border-radius: 10px;
                    padding: 0 3px;
                    background-image: linear-gradient(270deg, #f66, #ff9f9f);
                }
            }
        }
    }
}
</style>
阶段3:总体效果
当前:

 css改为:
.list{
        margin-top: 20px;
        display: flex;
        flex-wrap: wrap;
    }
效果:

效果

总代码
修改或添加的文件

 
house-item
展示house信息的列表,分为两种,标号分别为9和3.
house-item-v9
标号为9的展示house信息的组件。
<template>
    <div class="house-item">
        <div class="house-inner">
            <div class="image">
                <img :src="item.image.url" alt="">
            </div>
            <div class="info">
                <div class="summary">{{ item.summaryText }}</div>
                <div class="name">{{ item.houseName }}</div>
                <div class="tips">
                    <div class="stars" >
                        <van-rate v-model="starValue" readonly allow-half size="12px"/>
                    </div>
                    <div class="price">
                        ¥{{ item.finalPrice }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
const starValue = ref(Number(props.item.commentScore));
</script>
<style lang="less" scoped>
.house-item {
    // 每个item宽度占屏幕一半
    width: 50%;
    .house-inner {
        // 信息在img上:子绝父相
        position: relative;
        margin: 5px;
        .image {
            img {
                width: 100%;
                border-radius: 6px;
            }
        }
        .info {
            position: absolute;
            bottom: 0;
            padding: 8px 10px;
            color: #fff;
            .summary {
                font-size: 12px;
            }
            .name {
                // 超过2行就省略号省略...
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
                margin: 5px 0;
            }
            .tips{
                display: flex;
                justify-content: space-between;
            }
        }
    }
}
</style>
house-item-v3
标号为3的展示house信息的组件。
<template>
    <div class="house-item">
        <div class="house-inner">
            <div class="image">
                <img :src="item.image.url" alt="">
            </div>
            <div class="info">
                <div class="location">
                    <van-icon name="location" color="#808080" />
                    {{ item.location }}
                </div>
                <div class="name">{{ item.houseName }}</div>
                <div class="summary">{{ item.summaryText }}</div>
                <div class="price">
                    <div class="cheap">¥{{ item.finalPrice }}</div>
                    <div class="exp">{{ item.productPrice }}</div>
                    <div class="sale">立减¥{{ item.productPrice - item.finalPrice }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
defineProps({
    item: {
        type: Object,
        default: () => ({})
    }
})
</script>
<style lang="less" scoped>
.house-item {
    width: 50%;
    .house-inner {
        margin: 5px;
        .image {
            img {
                width: 100%;
                border-radius: 6px;
            }
        }
        .info {
            padding: 15px 8px 0;
            .location {
                color: #000;
            }
            .name {
                color: #333;
                margin: 5px 0;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }
            .summary {
                color: #666;
                font-size: 12px;
            }
            .price {
                display: flex;
                justify-content: left;
                align-items: center;
                margin: 10px 0;
                .cheap {
                    color: var(--primary-color);
                }
                .exp {
                    color: #999;
                    text-decoration: line-through;
                    font-size: 12px;
                    margin: 0 5px;
                }
                .sale {
                    font-size: 12px;
                    color: #fff;
                    border-radius: 10px;
                    padding: 0 3px;
                    background-image: linear-gradient(270deg, #f66, #ff9f9f);
                }
            }
        }
    }
}
</style>
service的home.js
home页面网络请求数据的代码。
// 此文件保存所有home页面的网络请求
import HYRequest from '@/service/request'
export function getHotSuggest() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url: '/home/hotSuggests'
    })
}
export function getCategories() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url: '/home/categories'
    })
}
export function getHouseList(currentPage) {
    return HYRequest.get({
        url: '/home/houselist',
        params: {
            page: currentPage
        }
    })
}
store的home.js
管理home页面网络请求的数据的代码。
// home.vue页面所有的进行网络请求和数据都封装到这里
import { defineStore } from "pinia";
import { getHotSuggest, getCategories, getHouseList } from '@/service'
const useHomeStore = defineStore('home', {
    state: () => {
        return {
            hotSuggest: [],
            categories: [],
            houseList: [],
            currentPage: 1,
        }
    },
    actions: {
        // 网络请求,由于返回一个promise,要异步async await
        async fetchHotSuggest() {
            const res = await getHotSuggest()
            this.hotSuggest = res.data
            // console.log(res)
        },
        async fetchCategories() {
            const res = await getCategories()
            this.categories = res.data
        },
        async fetchHouseList() {
            const res = await getHouseList(this.currentPage)
            this.currentPage++
            this.houseList.push(...res.data)
        }
    }
})
export default useHomeStore
cpns的home-content
抽取了展示house信息的列表。
<template>
    <div class="content">
        <h2>热门精选</h2>
        <div class="list">
            <template v-for="(item, index) in houseList" :key="item.data.houseId">
                <houseItemV9 v-if="item.discoveryContentType === 9" :item="item.data"></houseItemV9>
                <houseItemV3 v-else-if="item.discoveryContentType === 3" :item="item.data"></houseItemV3>
            </template>
        </div>
    </div>
</template>
<script setup>
import { storeToRefs } from "pinia";
import useHomeStore from "../../../store/modules/home";
import houseItemV9 from "../../../components/house-item/house-item-v9.vue";
import houseItemV3 from "../../../components/house-item/house-item-v3.vue";
const homeStore = useHomeStore()
homeStore.fetchHouseList()
const { houseList } = storeToRefs(homeStore)
console.log(houseList)
</script>
<style lang="less" scoped>
.content {
    padding: 0 20px;
    h2 {
        font-size: 20px;
        font-weight: 700;
    }
    .list{
        margin-top: 20px;
        display: flex;
        flex-wrap: wrap;
    }
}
</style>
main.js
引入了vant的Rate和Icon。
参考
HTML文字超过两行以后 就用省略号显示代替 - 简书 (jianshu.com)
/*超过2行就省略*/
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;



















