【前端】vue阶段案例:组件化-房源展示

阶段案例系列:

案例链接
【前端】vue阶段案例:购物车https://blog.csdn.net/karshey/article/details/127473654
【前端】vue阶段案例:父子组件通信-tabControl栏https://blog.csdn.net/karshey/article/details/127480941
【前端】vue阶段案例:组件化-房源展示https://blog.csdn.net/karshey/article/details/127520175
【前端】vue阶段案例:vue-router使用流程https://blog.csdn.net/karshey/article/details/127554171

目标

在这里插入图片描述

完成如上页面,要组件化

代码

0.准备工作

0.1 数据

以json的形式给出:

{
  "title": "高分好评房源",
  "subtitle": "来看看这些颇受房客好评的房源吧",
  "list": [
    {
      "id": "47773281",
      "picture_url": "https://z1.muscache.cn/im/pictures/miso/Hosting-47773281/original/de5df68f-8582-4ee6-82c4-a52443d9e83b.jpeg?aki_policy=large",
      "verify_info": {
        "messages": [
          "整套公寓型住宅",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "价格真实 实图拍摄 整套单独使用每客消毒 高清投影【方糖】人民北路地铁|龙湖上城|火车北站|密码锁|",
      "price": 158,
      "price_format": "¥158",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 1282,
      "reviews": [
        {
          "comments": "还不错",
          "created_at": "2022-06-21T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/762d2557-cdd5-427f-bcc8-26d61d885ab9.jpg?aki_policy=x_medium",
          "review_id": 653751197167823900
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.6816,
      "lng": 104.07032,
      "image_url": "/springdiscount/8ee76f60e2777c6c01195b318ace9656.jpg"
    },
    {
      "id": "54376288",
      "picture_url": "https://z1.muscache.cn/im/pictures/miso/Hosting-54376288/original/71d12acc-ee2c-4721-8809-8a2b4666bb01.jpeg?aki_policy=large",
      "verify_info": {
        "messages": [
          "整套公寓型住宅",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "漫漫 | 杨桃 轻奢一居室/地铁口/近春熙路太古里/4米9挑高/全景落地窗",
      "price": 201,
      "price_format": "¥201",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 76,
      "reviews": [
        {
          "comments": "总体好评,房间的搭配是更讨女性喜欢的,比较强的ins风,东西比较齐全,比如洗衣机和冰箱都是有的,然后去太古里不是特别近需要骑车,另外门窗很薄。",
          "created_at": "2022-06-22T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/986322dc-5205-445f-8274-ebd71ee748ed.jpg?aki_policy=x_medium",
          "review_id": 654502854910423900
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.672022,
      "lng": 104.088898,
      "image_url": "/springdiscount/2f461f589f4a0b7784ef1c35875837a6.jpg"
    },
    {
      "id": "45817721",
      "picture_url": "https://z1.muscache.cn/im/pictures/miso/Hosting-45817721/original/80f99830-f104-4404-86fd-1af856ac9b73.jpeg?aki_policy=large",
      "verify_info": {
        "messages": [
          "独立房间",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "【可月租】【网红美食街区】/近春熙路/宽窄巷子/熊猫基地//【轻奢大床房】",
      "price": 150,
      "price_format": "¥150",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 507,
      "reviews": [
        {
          "comments": "挺好的",
          "created_at": "2022-06-09T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/7b8fb8de-6437-4d10-bc37-d878fa084c5d.jpg?aki_policy=x_medium",
          "review_id": 645148062181591700
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.68856,
      "lng": 104.09887,
      "image_url": "/springdiscount/9b88e80dbe461f427cbe97c1e6265bb9.jpg"
    },
    {
      "id": "47434782",
      "picture_url": "https://z1.muscache.cn/im/pictures/miso/Hosting-47434782/original/8e550632-acec-4478-a641-7ecc0062b1db.jpeg?aki_policy=large",
      "verify_info": {
        "messages": [
          "整套公寓型住宅",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "可月租!品质大床!高空观景露台/步行地铁站/白天免费停车/直达春熙路/近建设巷小吃街",
      "price": 146,
      "price_format": "¥146",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 1184,
      "reviews": [
        {
          "comments": "在永立住过几家公寓,这一家老板很热情,房间在35楼,晚上住高层很安静,大早上外面有点施工的声音,关上窗户基本可以隔绝。房间很整洁,从洗衣机 热水器 花洒还有洗手台的购置看出来老板比较用心。靠窗有桌子,但房间内没有沙发,不过也对得起这个价格了。楼下有一个小商业体,有许多餐馆和麦当劳,便利店也有开到很晚的。最近的地铁站是李家沱3号线,去7号线驷马桥路途稍微远一些。",
          "created_at": "2022-06-08T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/f0e1a670-a96c-46a7-a995-126e317b77f6.jpg?aki_policy=x_medium",
          "review_id": 644366154235822300
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.69036,
      "lng": 104.10039,
      "image_url": "/springdiscount/f5dd50f6ad6c307f11f30f3d77521079.jpg"
    },
    {
      "id": "40356486",
      "picture_url": "https://z1.muscache.cn/im/pictures/e4aa2fdd-70f5-43dc-90ee-8a6d1d739d84.jpg?aki_policy=large",
      "verify_info": {
        "messages": [
          "整套公寓型住宅",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "「精致mini房」楼下商场|地铁直达近春熙路太古里|建设巷小吃街|白天免费停车|可开发票|行李寄存",
      "price": 146,
      "price_format": "¥146",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 1139,
      "reviews": [
        {
          "comments": "位置比较好 楼下全是吃的 性价比很高 建议老板可以加装个避光窗帘要不早上光太强了还有就是电视换个系统 电影感觉好少哈哈 别的都还不错 下次还会来",
          "created_at": "2022-06-18T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/fe5bb0d1-4a85-4bb7-b417-3b1ec2c296c0.jpg?aki_policy=x_medium",
          "review_id": 651640404764253600
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.68856,
      "lng": 104.09887,
      "image_url": "/springdiscount/b85ac5ad3658912e6d9d66aafee87f92.jpg"
    },
    {
      "id": "45098754",
      "picture_url": "https://z1.muscache.cn/im/pictures/6e739539-940e-456f-a10d-0e8526feb943.jpg?aki_policy=large",
      "verify_info": {
        "messages": [
          "整套公寓",
          "1室1卫1床"
        ],
        "text_color": "#767676"
      },
      "name": "【住.颜23】免清洁费/下楼就是太古里春熙路/高空浴缸/落地窗带阳台/百寸极米投影双地铁/其他点头像",
      "price": 212,
      "price_format": "¥212",
      "star_rating": 5,
      "star_rating_color": "#008489",
      "reviews_count": 156,
      "reviews": [
        {
          "comments": "位置很便利,房间简洁干净,但浴缸热水用不起。",
          "created_at": "2022-06-05T00:00:00Z",
          "is_translated": false,
          "localized_date": "2022年6月",
          "reviewer_image_url": "https://a0.muscache.com/im/pictures/user/c7e52930-6432-4310-9de1-0cacfcb56b14.jpg?aki_policy=x_medium",
          "review_id": 642180681003801200
        }
      ],
      "bottom_info": {
        "content": "超赞房东",
        "content_color": "#767676",
        "font_size": "10",
        "visibility": "LIST_VIEW"
      },
      "lat": 30.65122,
      "lng": 104.08556,
      "image_url": "/springdiscount/872952d67cc83e0135faea56e6587a32.jpg"
    }
  ]
}
0.2 reset.css

一些如h1、h2、h3的会有自己的margin和padding,我们把它清除一下:

body,h1,h2,h3{
    margin: 0;
    padding: 0;
}
0.3 main.js
import { createApp } from 'vue'

import App from './案例copy/App.vue'
import './assert/css/reset.css'
createApp(App).mount('#app')

剩下的在App.vue里开发。

1.AreaHeader

1.1 结构
<template>
    <div class="app">
        <div class="area-header">
            <h3 class="title">{{ HighScore.title }}</h3>
            <div class="subtitle">{{ HighScore.subtitle }}</div>
        </div>
    </div>
</template>

<script setup>
import HighScore from '../data/high_score.json'
</script>
1.2 样式
<style lang="less" scoped>
.app {
    width: 1032px;
    padding: 40px;
    margin: 0 auto;

    .area-header{
        height: 84px;

        .title{
            font-size: 22px;
        }

        .subtitle{
            margin-top: 20px;
            font-size: 16px;
        }
    }

}
</style>
1.3 抽取到组件

App.vue:

<template>
    <div class="app">
        <!-- 1.AreaHeader 传入数据-->
        <area-header :title="HighScore.title" :subtitle="HighScore.subtitle"></area-header>
    </div>
</template>

<script setup>
import HighScore from '../data/high_score.json'
import AreaHeader from './components/AreaHeader.vue';
</script>

<style lang="less" scoped>
.app {
    width: 1032px;
    padding: 40px;
    margin: 0 auto;   
}
</style>

组件AreaHeader.vue:

<template>
    <div class="area-header">
        <!-- 这里直接用title数据 -->
        <h3 class="title">{{ title }}</h3>
        <div class="subtitle">{{ subtitle }}</div>
    </div>
</template>

<script setup>
// 定义父组件传入的数据
defineProps({
    title: {
        type: String,
        default: "默认title"
    },
    subtitle: {
        type: String,
        default: "默认subtitle"
    }
})
</script>

<style lang="less" scoped>
.area-header {
    height: 84px;

    .title {
        font-size: 22px;
    }

    .subtitle {
        margin-top: 20px;
        font-size: 16px;
    }
}
</style>

效果:抽取组件前后效果没变。
在这里插入图片描述

2.RoomItem

2.1 结构
<!-- 2.RoomList -->
<div class="roomlist">
   <!-- RoomItem 要用循环显示-->
   <template v-for="item in HighScore.list" :key="item.id">
       <div class="room-item">
           <div class="room-inner">
               <div class="cover">
                   <img :src="item.picture_url" alt="">
               </div>
               <div class="info">
                   <div class="title">{{`todo`}}</div>
                   <div class="name">{{item.name}}</div>
                   <div class="price">{{item.price}}</div>
                   <div class="bottom">{{`todo`}}</div>
               </div>
           </div>
       </div>
   </template>
</div>

在这里插入图片描述

2.2 样式
.room-list {
   // 弹性布局
   display: flex;
   flex-wrap: wrap;

   // 没有-8的话标题和下面内容靠左不会对齐
   margin: 30px -8px 0;

   .room-item {
       width: 33.333333%;

       .room-inner {
           margin: 0 8px 12px;
           color: #484848;
           font-size: 800;

           img {
               width: 100%;
               // 外边框圆角
               border-radius: 3px;
           }

           .info {
               .title {
                   margin-top: 8px;
                   font-size: 12px;
               }

               .name {
                   margin-top: 3px;
                   font-size: 16px;

                   // 2行:超出部分省略...
                   overflow: hidden;
                   text-overflow: ellipsis;
                   display: -webkit-box;
                   -webkit-line-clamp: 2;
                   -webkit-box-orient: vertical;
               }

               .price {
                   margin: 3px 0;
                   font-size: 14px;
                   font-weight: 400;
               }
           }
       }
   }
}

在这里插入图片描述

2.3 抽取到组件

App.vue:

<!-- 2.RoomList -->
<div class="room-list">
    <!-- RoomItem 要用循环显示-->
    <template v-for="item in HighScore.list" :key="item.id">
        <room-item :item="item"></room-item>
    </template>
</div>

RoomItem.vue:

<template>
    <div class="room-item">
        <div class="room-inner">
            <div class="cover">
                <img :src="item.picture_url" alt="">
            </div>
            <div class="info">
                <div class="title">{{ `todo` }}</div>
                <div class="name">{{ item.name }}</div>
                <div class="price">{{ item.price }}</div>
                <div class="bottom">{{ `todo` }}</div>
            </div>
        </div>
    </div>
</template>

<script setup>
// 父组件传来的数据
defineProps({
    item:{
        type:Object,
        default:()=>{}
    }
})
</script>

<style lang="less" scoped>
.room-item {
    width: 33.333333%;

    .room-inner {
        margin: 0 8px 12px;
        color: #484848;
        font-size: 800;

        img {
            width: 100%;
            // 外边框圆角
            border-radius: 3px;
        }

        .info {
            .title {
                margin-top: 8px;
                font-size: 12px;
            }

            .name {
                margin-top: 3px;
                font-size: 16px;

                // 2行:超出部分省略...
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }

            .price {
                margin: 3px 0;
                font-size: 14px;
                font-weight: 400;
            }
        }
    }
}
</style>
2.4 完善title

目标:显示数据中的title,把verify_info中的messages连起来展示,颜色为text_color,要对空值进行处理。

js:把数据拼接

// title
const itemTitle=computed(()=>{
    return{
        title:props.item?.verify_info?.messages.join(" ")??'',
        color:props.item?.verify_info?.text_color??'#767676'
    }
})

html和css:插值语法和动态绑定style

<div class="title" 
:style="{color:itemTitle.color}">
{{ itemTitle.title }}
</div>

效果:
在这里插入图片描述

2.5 完善price

目标:规范化显示:¥price / 天

js:

// price
function priceFormat(price){
    return '¥'+price+' / 天'
}

html:

<div class="price">{{ priceFormat(item.price) }}</div>
2.6 完善buttom

json数据中的buttom信息如下:

"bottom_info": {
	 "content": "超赞房东",
	 "content_color": "#767676",
	 "font_size": "10",
	 "visibility": "LIST_VIEW"
},

目标:显示content,style是content_color和font_size,对空值处理(虽然没有空值,但严谨起见,我们还是要处理一下)。

js:显然要用计算属性。

// buttom
const bottomInfo=computed(()=>{
    return{
        content:props.item?.bottom_info?.content??'',
        style:{
            color:props.item?.bottom_info?.content_color??'#767676',
            fontSize:props.item.bottom_info.font_size+'px'
        }
    }
})

html和css:插值语法和动态绑定style

<div class="bottom" :style="bottomInfo.style">{{ bottomInfo.content }}</div>

效果:
在这里插入图片描述

总代码

App.vue

<template>
    <div class="app">
        <!-- 1.AreaHeader 传入数据-->
        <area-header :title="HighScore.title" :subtitle="HighScore.subtitle"></area-header>

        <!-- 2.RoomList -->
        <div class="room-list">
            <!-- RoomItem 要用循环显示-->
            <template v-for="item in HighScore.list" :key="item.id">
                <room-item :item="item"></room-item>
            </template>
        </div>
    </div>
</template>

<script setup>
import HighScore from '../data/high_score.json'
import AreaHeader from './components/AreaHeader.vue';
import RoomItem from './components/RoomItem.vue'
</script>

<style lang="less" scoped>
.app {
    width: 1032px;
    padding: 40px;
    margin: 0 auto;

    .room-list {
        // 弹性布局
        display: flex;
        flex-wrap: wrap;

        // 没有-8的话标题和下面内容靠左不会对齐
        margin: 30px -8px 0;
    }
}
</style>

AreaHeader.vue

<template>
    <div class="area-header">
        <!-- 这里直接用title数据 -->
        <h3 class="title">{{ title }}</h3>
        <div class="subtitle">{{ subtitle }}</div>
    </div>
</template>

<script setup>
// 定义父组件传入的数据
defineProps({
    title: {
        type: String,
        default: "默认title"
    },
    subtitle: {
        type: String,
        default: "默认subtitle"
    }
})
</script>

<style lang="less" scoped>
.area-header {
    height: 84px;

    .title {
        font-size: 22px;
    }

    .subtitle {
        margin-top: 20px;
        font-size: 16px;
    }
}
</style>

Roomitem.vue

<template>
    <div class="room-item">
        <div class="room-inner">
            <div class="cover">
                <img :src="item.picture_url" alt="">
            </div>
            <div class="info">
                <div class="title" :style="{color:itemTitle.color}">{{ itemTitle.title }}</div>
                <div class="name">{{ item.name }}</div>
                <div class="price">{{ priceFormat(item.price) }}</div>
                <div class="bottom" :style="bottomInfo.style">{{ bottomInfo.content }}</div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { computed } from "@vue/runtime-core"

// 父组件传来的数据:因为后面会用到item,所以要定义一个props,否则取不到item
const props= defineProps({
    item:{
        type:Object,
        default:()=>{}
    }
})

// title
const itemTitle=computed(()=>{
    return{
        title:props.item?.verify_info?.messages.join(" ")??'',
        color:props.item?.verify_info?.text_color??'#767676'
    }
})

// price
function priceFormat(price){
    return '¥'+price+' / 天'
}

// buttom
const bottomInfo=computed(()=>{
    return{
        content:props.item?.bottom_info?.content??'',
        style:{
            color:props.item?.bottom_info?.content_color??'#767676',
            fontSize:props.item.bottom_info.font_size+'px'
        }
    }
})
</script>

<style lang="less" scoped>
.room-item {
    width: 33.333333%;

    .room-inner {
        margin: 0 8px 12px;
        color: #484848;
        font-weight: 800;

        img {
            width: 100%;
            // 外边框圆角
            border-radius: 3px;
        }

        .info {
            .title {
                margin-top: 8px;
                font-size: 12px;
            }

            .name {
                margin-top: 3px;
                font-size: 16px;

                // 2行:超出部分省略...
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }

            .price {
                margin: 3px 0;
                font-size: 14px;
                font-weight: 400;
            }
        }
    }
}
</style>

参考

CSS3属性之text-overflow:ellipsis详解_Topepy的博客-CSDN博客_text-overflow:ellipsis
ES6中js的运算符(?.、?:、? ?、? ?=、)_小靳小盆友i.的博客-CSDN博客_es6中?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

karshey

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

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

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

打赏作者

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

抵扣说明:

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

余额充值