• 【Vue3.0移动端项目--旅游网】-- 首页分类和热门精选展示


    多一些不为什么的坚持🤳

    贤蛋 🥚大眼萌 ,一名很普通但不想普通的程序媛🙊

    📝本文章收录于专栏:Vue3.0移动端项目-旅游网

    第一篇:【Vue3.0移动端项目–旅游网】–项目初始化搭建

    第二篇:【Vue3.0移动端项目–旅游网】–配置tabBar &首页搭建

    第三篇:【Vue3.0移动端项目–旅游网】-- 城市页面搭建

    第四篇:【Vue3.0移动端项目–旅游网】-- 首页日期和热门推荐处理

    🧲 新建 home 分支

    通过 Git 管理项目,养成良好的开发习惯,可以创建分支。最后开发完可以合并分支

    1. 创建新分支并且跳转到改分支上
    git checkout -b home02
    
    • 1
    1. 查看分支
    git branch
    
    • 1

    image-20220821211341095

    🍭 搜索按钮

    1. 搜索按钮及样式

    image-20220821214202131

    2. 点击搜索按钮跳转到搜索页面

    image-20220821214253736

    image-20220821214237143

    效果:

    image-20220821214416139

    🎯 展示分类

    1. 发送请求

    image-20220822093013733

    2. 分类数据存入 Store 中

    image-20220822093109992

    3. 展示数据渲染数据

    这里封装一个单独的组件 home-categories

    下面是组件完整代码:

    <template>
      <div class="categories">
        <template
          v-for="(item,index) in categories"
          :key="index"
        >
          <div class="item">
            <img :src="item.pictureUrl" />
            <div class="text">{{item.title}}</div>
          </div>
        </template>
      </div>
    </template>
    
    <script setup>
    import { useHomeStore } from '@/stores/modules/home';
    import { storeToRefs } from 'pinia';
    
    const homeStore = useHomeStore()
    const { categories } = storeToRefs(homeStore)
    </script>
     
    <style lang="less" scoped>
    .categories {
      display: flex;
      overflow-x: auto;
      height: 88px;
      padding: 0 10px;
      margin-top: 8px;
      &::-webkit-scrollbar {
        display: none;
      }
      .item {
        flex-shrink: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        width: 70px;
        text-align: center;
        img {
          width: 44px;
          height: 44px;
        }
        .text {
          font-size: 12px;
          margin-top: 8px;
        }
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    效果:

    image-20220822093328443

    🍳 展示热门精选

    1. 发送请求

    image-20220822105830573

    2. 分类数据存入Store中

    image-20220822105908080

    3. 页面渲染(布局处理)

    image-20220822110217304

    下面分别是组件v9和v3代码

    //component/house-item-v9
    <template>
      <div class="house-item-v9">
        <div class="item-inner">
          <div class="cover">
            <img :src="itemData.image.url">
          </div>
          <div class="info">
            <div class="summary">{{itemData.summaryText}}</div>
            <div class="name">{{itemData.houseName}}</div>
            <div class="price">
              <van-rate
                :model-value="itemScore"
                color="#fff"
                :size="15"
                readonly
                allow-half
              />
              <div class="new">{{itemData.finalPrice}}</div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { computed } from "@vue/runtime-core"
    
    const props = defineProps({
      itemData: {
        type: Object,
        default: () => ({})
      }
    })
    const itemScore = computed(() => {
      return Number(props.itemData.commentScore)
    })
    </script>
     
    <style lang="less" scoped>
    .house-item-v9 {
      width: 50%;
      .item-inner {
        position: relative;
        margin: 5px;
        background-color: #fff;
        border-radius: 6px;
        overflow: hidden;
    
        .cover {
          img {
            width: 100%;
          }
        }
        .info {
          position: absolute;
          bottom: 0;
          padding: 8px 10px;
          color: #fff;
    
          .summary {
            font-size: 12px;
          }
          .name {
            margin: 5px 0;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
          }
          .price {
            display: flex;
            justify-content: space-between;
            margin-top: 10px;
          }
        }
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    //component/house-item-v3
    <template>
      <div class="house-item-v3">
        <div class="item-inner">
          <div class="cover">
            <img
              :src="itemData?.image?.url"
              alt=""
            >
          </div>
          <div class="info">
            <div class="location">
              <img
                src="@/assets/img/home/location.png"
                alt=""
              >
              <span>{{ itemData.location }}</span>
            </div>
            <div class="name">{{ itemData.houseName }}</div>
            <div class="summary">{{ itemData.summaryText }}</div>
            <div class="price">
              <div class="new"> ¥ {{ itemData.finalPrice }}</div>
              <div class="old"> ¥ {{ itemData.productPrice }}</div>
              <div
                class="tip"
                v-if="itemData.priceTipBadge"
              >
                {{ itemData.priceTipBadge.text }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    
    defineProps({
      itemData: {
        type: Object,
        default: () => ({})
      }
    })
    
    </script>
    
    <style lang="less" scoped>
    .house-item-v3 {
      width: 50%;
    
      .item-inner {
        margin: 5px;
        background: #fff;
        border-radius: 6px;
        overflow: hidden;
    
        .cover {
          img {
            width: 100%;
          }
        }
    
        .info {
          padding: 8px 10px;
          color: #666;
          font-size: 12px;
        }
    
        .location {
          display: flex;
          align-items: center;
          img {
            width: 12px;
            height: 12px;
          }
    
          .text {
            margin-left: 2px;
            font-size: 12px;
            color: #666;
          }
        }
    
        .name {
          margin: 5px 0;
          font-size: 14px;
          color: #333;
    
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
        }
    
        .price {
          display: flex;
          align-items: flex-start;
    
          margin: 8px 0;
          .new {
            color: #ff9645;
            font-size: 14px;
          }
    
          .old {
            margin: 0 3px;
            color: #999;
            font-size: 12px;
            text-decoration: line-through;
          }
    
          .tip {
            background-image: linear-gradient(270deg, #f66, #ff9f9f);
            color: #fff;
            padding: 0 6px;
            border-radius: 8px;
          }
        }
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    效果:

    image-20220822110913272

    5. 滚动到底部 加载更多(再次请求数据)

    监听页面的滚动,当滚动到底部的时候,发送请求数据,渲染页面,离开页面时,需要移除监听

    这里我们封装一个 hooks

    因为监听页面滚动,滚动频率过高会频繁触发hooks,这里引入 underscore 库中的节流函数

    安装underscore

    npm install underscore
    
    • 1

    相关概念

    scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数

    clientHeight 属性(只读)元素的像素高度

    • 如果当前元素没有滚动条,那么 clinetHeight = scrollHeight

    • 如果当前元素存在滚动条,那么scrollHeight > clientHeight

      scrollHeight = scrollTop + clientHeight

    image-20220822115150176
    // hooks/useScroll.js
    import { onMounted, onUnmounted, ref } from "vue"
    import { throttle } from "underscore"
    export default function useScroll() {
      // 用变量判断是否到达底部
      const isReachBottom = ref(false)
    
      // scrollHeight = scrollTop + clientHeight
      const scrollHeight = ref(0)
      const clientHeight = ref(0)
      const scrolltop = ref(0)
    
      // 节流处理
      const scrollListenerHandler = throttle(() => {
        clientHeight.value = document.documentElement.clientHeight
        scrillTop.value = document.documentElement.scrollTop
        scrollHeight.value = document.documentElement.scrollHeight
        if (clientHeight.value + scrollTop.value >= scrollHeight.value) {
          // 已经滚到底部了
          console.log("滚动到底部了")
          isReachBottom.value = true
        }
      }, 100)
      onMounted(() => {
        window.addEventListener("scroll", scrollListenerHandler)
      })
    
      onUnmounted(() => {
        window.removeEventListener("scroll", scrollListenerHandler)
      })
      return {
        isReachBottom,
        scrollHeight,
        clientHeight,
        scrolltop
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    image-20220822151048880

    🍚 存储日期信息

    1. 将日期信息存入 mainStore

    image-20220822155416290

    2. 修改之前展示的日期信息

    image-20220822155600796

    🥩 搜索框展示

    1. 控制搜索框显示

    image-20220822155843172

    2. 搜索框布局

    <template>
      <div class="search">
        <div class="select-time">
          <div class="item start">
            <div class="name"></div>
            <div class="date">{{ startDateStr }}</div>
          </div>
          <div class="item end">
            <div class="name"></div>
            <div class="date">{{ endDateStr }}</div>
          </div>
        </div>
        <div class="content">
          <div class="keyword">关键字/位置/民宿</div>
        </div>
        <div class="right">
          <i class="icon-search"></i>
        </div>
      </div>
    </template>
    
    <script setup>
    import { storeToRefs } from "pinia";
    import { computed } from "@vue/runtime-core";
    
    import { formatMonthDay } from "@/utils/format-time";
    import { useMainStore } from "@/stores/modules/main";
    
    const mainStore = useMainStore()
    const { startDate, endDate } = storeToRefs(mainStore)
    const startDateStr = computed(() => formatMonthDay(startDate.value, "MM.DD"))
    const endDateStr = computed(() => formatMonthDay(endDate.value, "MM.DD"))
    
    </script>
     
    <style lang="less" scoped>
    .search {
      display: flex;
      flex-direction: row;
      align-items: center;
    
      height: 45px;
      line-height: 45px;
    
      padding: 0 10px;
      font-size: 14px;
      color: #999;
    
      border-radius: 6px;
      background-color: #f2f4f6;
    
      .select-time {
        display: flex;
        flex-direction: column;
    
        .item {
          display: flex;
          flex-direction: row;
          align-items: center;
          line-height: normal;
          font-size: 10px;
    
          .name {
            font-size: 10px;
          }
          .date {
            position: relative;
            color: #333;
            margin: 0 10px 0 3px;
          }
        }
      }
      .end .date::after {
        content: ' ';
        width: 0;
        height: 0;
        border: 4px solid #666;
        border-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #666;
        -webkit-border-radius: 3px;
        border-radius: 3px;
        -webkit-transform: rotate(45deg);
        -ms-transform: rotate(45deg);
        transform: rotate(45deg);
        position: absolute;
        bottom: 0px;
        right: -12px;
      }
    }
    .content {
      position: relative;
      flex: 1;
      padding: 0 6px;
      text-align: left;
      border-left: 1px solid #fff;
    
      .keyword {
        max-width: 155px;
        font-size: 12px;
      }
    }
    .right {
      display: flex;
      align-items: center;
    
      .icon-search {
        width: 24px;
        height: 24px;
        display: inline-block;
    
        background-image: url(../../assets/img/home/home-sprite.png);
        background-position: -29px -151px;
        background-size: 207px 192px;
      }
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    效果:

    image-20220822172019670

    🧬 Git 管理和代码托管(github)

    1. 添加到暂存区
    git add .
    
    • 1
    1. 添加到仓库
    git commit -m "home02分支"
    
    • 1
    1. 推送代码
    git push -u origin home02
    
    • 1
    1. 将本地的tabbar 分支 合并到主分支上master (注意要先切换在主分支上)
    git checkout mater
    
    • 1
    1. 分支合并
    git merge home02
    
    • 1
    1. 更新远程仓库 master 分支
    git push
    
    • 1
    1. 删除tabbar分支
    git branch -d home02
    
    • 1
    image-20220822172456073

    补充:

    网络数据请求地址数据

    项目github 地址:https://github.com/fdfd-0313/cz-trip.git

    喜欢就一键三连吧!

  • 相关阅读:
    EM算法推导小记
    HTTP/1.1协议中的响应报文
    Django(21):使用Celery任务框架
    资料 - hawei EulerOS的主页入口
    Win:将一般用户添加到 Local Admins 组中
    R语言生物群落(生态)数据统计分析与绘图
    虾皮二面:既然有 HTTP 协议,为什么还要有 RPC?
    Typescript类功能混合(mixin)使用,将多个类中功能合并到一个对象
    Java:代理模式详解
    JavaWeb知识梳理(后端部分)
  • 原文地址:https://blog.csdn.net/weixin_47980825/article/details/126470396