【前端】Vue项目:旅游App-(22)detail:房屋信息、房屋设施、插槽

本项目博客总结:【前端】Vue项目:旅游App-博客总结

目标

第一个框是房屋信息info,第二个框是房屋设施facility。
在这里插入图片描述

过程与代码

房屋信息部分info

目标是这样:

在这里插入图片描述
目标数据在整个页面数据的位置:

在这里插入图片描述
数据的对应:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因此,我们需要给组件传数据:

detailData.mainPart.topModule

detail.vue:

 <!-- 标题 -->
 <div class="info" v-if="detailData.mainPart">
     <detailInfo :house-info="detailData.mainPart.topModule"/>
 </div>

detail-info对数据的定义:

const props = defineProps({
    houseInfo: {
        type: Object,
        default: () => { }
    }
})

根据目标搭建结构:

<template>
    <div class="info">
        <div class="name">{{ props.houseInfo.houseName }}</div>
        
        <div class="tag">
            <template v-for="(item,index) in houseTags" :key="index">
                <!-- 注意,有的房屋详情可能没有tagText -->
                <span class="item1" v-if="item.tagText" :style="{ color:  item.tagText.color ,background:item.tagText.background.color }">{{ item.tagText.text }}</span>
                <span class="item1" v-else>
                    <img :src="item.tagPic" alt="">
                </span>
            </template>
        </div>

        <div class="comment item2">
            <div class="score">{{ commentBrief.overall }}</div>
            <div class="content">{{ commentBrief.commentBrief }}</div>
            <div class="more">{{ commentBrief.totalCount }}条评论></div>
        </div>

        <div class="location item2">
            <div class="content">{{ nearByPosition.address }}</div>
            <div class="more">地图 周边></div>
        </div>
    </div>
</template>

<script setup>
const props = defineProps({
    houseInfo: {
        type: Object,
        default: () => { }
    }
})

const houseTags = props.houseInfo.houseTags
const commentBrief=props.houseInfo.commentBrief
const nearByPosition=props.houseInfo.nearByPosition

</script>

<style lang="less" scoped>

</style>

效果:

在这里插入图片描述
加入样式:

.info {
    .name {
        color: #333;
        font-size: 20px;
        font-weight: 700;
        text-align: justify;
        margin-bottom: 6px;
        letter-spacing: 0.3px;
    }

    .tag {

        display: flex;
        flex-wrap: wrap;

        .item1 {
            height: 14px;
            margin: 0 3px 3px 0;
            padding: 2px 4px;
            font-size: 12px;

            img {
                height: 18px;
            }
        }
    }

    .item2{
        display: flex;
        align-items: center;
        justify-content: space-between;

        margin: 12px 0;
        padding: 8px 12px;
        background-color: #f5f7fa;


        .score{
            font-weight: 700;
        }
        .address{
            font-weight: 700;
        }
        .more{
            color: var(--primary-color);
        }

    }
}

效果:

在这里插入图片描述

相似结构的组件section

接下来要做的内容如下:

在这里插入图片描述
显然,它们的结构是相似的,可以把它们抽取为一个组件,中间不同的地方是插槽slot

此组件的结构和样式代码:

<template>
    <div class="section">
        <div class="header">
            <h2 class="title">{{ headerText }}</h2>
        </div>
        <div class="content">
            <slot>默认内容</slot>
        </div>
        <div class="footer" v-if="moreText.length">
            {{ moreText }}
            <van-icon name="arrow" />
        </div>
    </div>
</template>

<script setup>
defineProps({
    headerText: {
        Object: String,
        default: "默认标题"
    },
    moreText: {
        Object: String,
        default: ""
    }
})
</script>

<style lang="less" scoped>
.section {
    margin-top: 10px;
    padding: 0 16px;

    .header {
        height: 52px;
        line-height: 52px;

        h2 {
            color: #333;
            font-size: 20px;
            font-weight: 600;
        }
    }

    .footer{
        display: flex;
        justify-content: flex-end;
        align-items: center;

        padding: 0 16px;
        color: var(--primary-color);
        font-weight: 600;
    }
}
</style>

效果:

在这里插入图片描述

房屋设施facility

目标:

在这里插入图片描述
根据前面组件section代码可知,我们只需要写content的内容,至于header和footer,只需要传入数据即可。

分析一下数据:框起来的是我们要显示的数据。

在这里插入图片描述
在这里插入图片描述
具体显示情况:要显示houseFacilitys[facilitySort]中数据。

在这里插入图片描述

<!-- 内容 -->
<detailSection :header-text="'房屋设施'" :more-text="'全部房屋设施'">
    <!-- 插槽内容 -->
    <detailFacility :houseFacility="detailData.mainPart.dynamicModule.facilityModule.houseFacility" />
</detailSection>

把传给facility组件的对象打印出来:

在这里插入图片描述
接下来就是根据数据搭建html结构:

<template>
    <div class="facility">
        <template v-for="(itemIndex, index) in facilitySort" :key="index">
            <div class="item1">
                <!-- 左侧title -->
                <div class="groupName">
                    <img :src="houseFacilitys[itemIndex].icon" alt="">
                    {{ houseFacilitys[itemIndex].groupName }}
                </div>
                <div class="nameList">
                    <!-- 右侧最多四个name -->
                    <template v-for="(item, indexx) in houseFacilitys[itemIndex].facilitys.slice(0, 4)" :key="indexx">
                        <van-icon name="passed" />{{ item.name }}
                    </template>
                </div>
            </div>
        </template>
    </div>
</template>

<script setup>
const props = defineProps({
    houseFacility: {
        type: Object,
        default: () => ({})
    }
})

const facilitySort = props.houseFacility.facilitySort
const houseFacilitys = props.houseFacility.houseFacilitys


</script>

<style lang="less" scoped>

</style>

效果:

在这里插入图片描述
注意:数据中右边的每个小item最多只有4个,因此要把要循环的数组先切割为只有4个的:

<template v-for="(item, indexx) in houseFacilitys[itemIndex].facilitys.slice(0,4)" :key="indexx">

加样式:

detail-facility:

.facility {
    background-color: #f7f9fb;
    color: #333;
    padding: 5px 16px 20px;
    border-radius: 6px;

    .item1 {
        display: flex;
        align-items: center;
        margin-top: 20px;

        .left {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 70px;

            .groupName {
                font-weight: 600;
                font-size: 11px;
                margin-top: 3px;
            }


            img {
                width: 20px;
                height: 20px;
            }
        }

        .nameList {
            display: grid;
            grid-template-columns: auto auto ;
            grid-template-rows: auto auto;
            margin-left: 17px;

            .item2 {
                width: 110px;
                height: 14px;

                margin-top: 7px;
            }
        }
    }
}

detail-section:

.section {
    margin-top: 10px;
    padding: 0 16px;

    .header {
        height: 52px;
        line-height: 52px;
        border-bottom: 1px solid  #DCDCDC;

        h2 {
            color: #333;
            font-size: 20px;
            font-weight: 600;
        }
    }

    .content{

        margin-top: 20px;
    }

    .footer{
        display: flex;
        justify-content: flex-end;
        align-items: center;

        padding: 15px 16px;
        color: var(--primary-color);
        font-weight: 600;
    }
}

效果:

在这里插入图片描述

效果

在这里插入图片描述

总代码

修改或添加的文件

在这里插入图片描述

detail-section

将有相似结构的内容抽取出来,不同的部分用插槽实现。

<template>
    <div class="section">
        <div class="header">
            <h2 class="title">{{ headerText }}</h2>
        </div>
        <div class="content">
            <slot>默认内容</slot>
        </div>
        <div class="footer" v-if="moreText.length">
            查看{{ moreText }}
            <van-icon name="arrow" />
        </div>
    </div>
</template>

<script setup>
defineProps({
    headerText: {
        Object: String,
        default: "默认标题"
    },
    moreText: {
        Object: String,
        default: ""
    }
})
</script>

<style lang="less" scoped>
.section {
    margin-top: 10px;
    padding: 0 16px;

    .header {
        height: 52px;
        line-height: 52px;
        border-bottom: 1px solid  #DCDCDC;

        h2 {
            color: #333;
            font-size: 20px;
            font-weight: 600;
        }
    }

    .content{

        margin-top: 20px;
    }

    .footer{
        display: flex;
        justify-content: flex-end;
        align-items: center;

        padding: 15px 16px;
        color: var(--primary-color);
        font-weight: 600;
    }
}
</style>

detail-facility

插槽:房屋设施 这一内容组件。

<template>
    <div class="facility">
        <template v-for="(itemIndex, index) in facilitySort" :key="index">
            <div class="item1">
                <!-- 左侧title -->
                <div class="left">
                    <img :src="houseFacilitys[itemIndex].icon" alt="">
                    <div class="groupName">{{ houseFacilitys[itemIndex].groupName }}</div>
                </div>
                <div class="nameList">
                    <!-- 右侧最多四个name -->
                    <template v-for="(item, indexx) in houseFacilitys[itemIndex].facilitys.slice(0, 4)" :key="indexx">
                        <div class="item2">
                            <van-icon name="passed" color="#17d2bc" />{{ item.name }}
                        </div>
                    </template>
                </div>
            </div>
        </template>
    </div>
</template>

<script setup>
const props = defineProps({
    houseFacility: {
        type: Object,
        default: () => ({})
    }
})

const facilitySort = props.houseFacility.facilitySort
const houseFacilitys = props.houseFacility.houseFacilitys


</script>

<style lang="less" scoped>
.facility {
    background-color: #f7f9fb;
    color: #333;
    padding: 5px 16px 20px;
    border-radius: 6px;

    .item1 {
        display: flex;
        align-items: center;
        margin-top: 20px;

        .left {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 70px;

            .groupName {
                font-weight: 600;
                font-size: 11px;
                margin-top: 3px;
            }


            img {
                width: 20px;
                height: 20px;
            }
        }

        .nameList {
            display: grid;
            grid-template-columns: auto auto ;
            grid-template-rows: auto auto;
            margin-left: 17px;

            .item2 {
                width: 110px;
                height: 14px;

                margin-top: 7px;
            }
        }
    }
}
</style>

detail-info

房屋信息。

<template>
    <div class="info">
        <div class="name">{{ props.houseInfo.houseName }}</div>

        <div class="tag">
            <template v-for="(item, index) in houseTags" :key="index">
                <!-- 注意,有的房屋详情可能没有tagText -->
                <span class="item1" v-if="item.tagText"
                    :style="{ color: item.tagText.color, background: item.tagText.background.color }">{{
                        item.tagText.text
                    }}</span>
                <span class="item1" v-else>
                    <img :src="item.tagPic" alt="">
                </span>
            </template>
        </div>

        <div class="comment item2">
            <div class="score">{{ commentBrief.overall }}</div>
            <div class="content">{{ commentBrief.scoreTitle }}   "{{ commentBrief.commentBrief }}"</div>
            <div class="more">{{ commentBrief.totalCount }}条评论<van-icon name="arrow" /></div>
        </div>

        <div class="location item2">
            <div class="address">{{ nearByPosition.address }}</div>
            <div class="more">地图 周边<van-icon name="arrow" /></div>
        </div>
    </div>
</template>

<script setup>
const props = defineProps({
    houseInfo: {
        type: Object,
        default: () => { }
    }
})

const houseTags = props.houseInfo.houseTags
const commentBrief = props.houseInfo.commentBrief
const nearByPosition = props.houseInfo.nearByPosition

</script>

<style lang="less" scoped>
.info {
    .name {
        color: #333;
        font-size: 20px;
        font-weight: 700;
        text-align: justify;
        margin-bottom: 6px;
        letter-spacing: 0.3px;
    }

    .tag {

        display: flex;
        flex-wrap: wrap;

        .item1 {
            height: 14px;
            margin: 0 3px 3px 0;
            padding: 2px 4px;
            font-size: 12px;

            img {
                height: 18px;
            }
        }
    }

    .item2{
        display: flex;
        align-items: center;
        justify-content: space-between;

        margin: 12px 0;
        padding: 8px 12px;
        background-color: #f5f7fa;


        .score{
            font-weight: 700;
        }
        .address{
            font-weight: 700;
        }
        .more{
            color: var(--primary-color);
        }

    }


}
</style>

detail

整个detail页面。

<template>
    <div class="detail top-page">
        <!-- 返回上级的导航栏 -->
        <van-nav-bar title="房屋详情" left-text="旅途" left-arrow @click-left="onClickLeft" />


        <div class="main" v-if="detailData.mainPart">
            <!-- 轮播图 -->
            <detailSwipe :swipe-data="detailData.mainPart.topModule.housePicture.housePics" />
            <!-- 标题 -->
            <div class="info">
                <detailInfo :house-info="detailData.mainPart.topModule" />
            </div>
            <!-- 内容 -->
            <detailSection :header-text="'房屋设施'" :more-text="'全部房屋设施'">
                <!-- 插槽内容 -->
                <detailFacility :houseFacility="detailData.mainPart.dynamicModule.facilityModule.houseFacility" />
            </detailSection>
        </div>

    </div>
</template>

<script setup>
import useDetailStore from '@/store/modules/detail';
import detailSwipe from '../detail/cpns/detail-swipe.vue'
import detailInfo from './cpns/detail-info.vue';
import detailSection from '@/components/detail-section/detail-section.vue';
import detailFacility from './cpns/detail-facility.vue';

import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';

// const
const detailStore = useDetailStore()
const route = useRoute()

// 返回导航栏
const onClickLeft = () => history.back();

// houseId
const houseId = route.params.id

// store
detailStore.fetchDetailData(houseId)
const { detailData } = storeToRefs(detailStore)
</script>

<style lang="less" scoped>
.detail {

    .info {
        margin: 9px;
    }
}
</style>

参考

vue 静态/动态绑定style的几种方式【Vue】
CSS text-align 属性 (w3school.com.cn)
CSS 文字间距 (w3school.com.cn)
Flex 布局语法教程 | 菜鸟教程 (runoob.com)
彻底搞懂slot插槽,图文详解 - 简书 (jianshu.com)
CSS 网格布局 | 菜鸟教程 (runoob.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值