文章目录
- 目标
- 过程与代码
- 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;