10. trip2

114 阅读6分钟

1. 详情页剩余部分

1.1 周边地图

image.png

地图使用步骤

  1. 首先在百度开放平台注册为开发者,获取AK
  2. 在html引入百度地图API文件
<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=您的密钥"></script>

//AK:ArMTS9ea6ivqfI5GKkh4p5AqDe7BlmMM
// <script type="text/javascript" src="https://api.map.baidu.com/getscript?v=3.0&type=webgl&ak=ArMTS9ea6ivqfI5GKkh4p5AqDe7BlmMM"></script>
  1. 初始化地图逻辑

首先创建地图实例,之后用一个Point坐标点和缩放级别来初始化地图

var map = new BMapGL.Map("container");          // 创建地图实例 
var point = new BMapGL.Point(116.404, 39.915);  // 创建点坐标 
map.centerAndZoom(point, 15);                 // 初始化地图,设置中心点坐标和地图级别

detail使用detail-map组件

import DetailMap from './components/detail_07-map.vue'

<!-- 位置周边 -->
<template v-if="mainPart?.dynamicModule?.positionModule">
  <detail-map :map="mainPart.dynamicModule.positionModule"></detail-map>
</template>
  • detail-map组件
<script setup>
import { ref, onMounted } from 'vue'
import detailSection from '@/components/detail-section/detail-section.vue';

const props = defineProps({
  map: {
    type: Object,
    default: () => ({})
  }
})
const mapRef = ref()

onMounted(() => {
  const map = new BMapGL.Map(mapRef.value); // 创建地图实例 
  const point = new BMapGL.Point(props.map.longitude, props.map.latitude);  // 创建点坐标 
  map.centerAndZoom(point, 15);  // 初始化地图,设置中心点坐标和地图级别
  const marker = new BMapGL.Marker(point)
  map.addOverlay(marker)
})

</script>

<template>
  <div class="map">
    <detail-section title="位置周边" more="查看更多周边信息">
      <div class="baidu" ref="mapRef">百度地图</div>
    </detail-section>
  </div>
</template>

<style lang="less" scoped>
.map {
  .baidu {
    height: 300px;
    border-radius: 6px;
  }
}
</style>

1.2 价格说明及结尾

image.png

  • detail页面引入
import DetailIntroduction from './components/detail_08-introduction.vue'

<!-- 价格说明 -->
<template v-if="mainPart">
  <detail-introduction :introduction="mainPart"></detail-introduction>
</template>
  • detail-introduction组件
<script setup>

defineProps({
  introduction: {
    type: Object,
    default: () => ({})
  }
})

</script>

<template>
  <div class="intro">
    <div class="top">
      <div class="title">{{ introduction.introductionModule.title }}</div>
      <div class="content">
        {{ introduction.introductionModule.introduction }}
      </div>
    </div>
  </div>
  <div class="footer">
    <div class="ensure">
      <img :src="introduction.ensureModule.icon" alt="">
    </div>
    <div class="ensure-bottom">
      <div class="business-license">{{ introduction.businessLicenseModuleTitle }}</div>
      <div class="info-txt">民宿预定频道由本平台提供服务</div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.intro {
  border-top: 10px solid #f2f3f4;
  background-color: #fff;
  padding: 16px 12px;

  .title {
    font-size: 14px;
    color: #333;
    line-height: 17px;
    font-weight: 600;
  }

  .content {
    font-size: 12px;
    color: #666;
    margin-top: 10px;
    line-height: 15px;
  }
}

.footer {
  background-color: #f2f3f4;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .ensure {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 130px;
    margin-top: 30px;
    padding: 10px 0;

    img {
      width: 100%;
    }
  }

  .ensure-bottom {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    color: #7688a7;
    line-height: 12px;
    margin-bottom: 18px;

    .business-license {
      margin-top: 4px;
      font-weight: 500;
      margin-bottom: 9px;
    }
  }
}
</style>

2. 详情页tabControl

2.1 useScroll的完善监听滚动

优化了document窗口滚动或元素滚动

import { onMounted, onUnmounted, ref } from "vue"
import { throttle } from 'underscore'

export default function useScroll(elRef) {
  let el = window

  const isReachBottom = ref(false)
  const scrollTop = ref(0)
  const scrollHeight = ref(0)
  const clientHeight = ref(0)

  // 节流
  const scrollListenerHandler = throttle(() => {
    console.log('正在进行滚动');
    if(el === window){
      clientHeight.value = document.documentElement.clientHeight
      scrollTop.value = document.documentElement.scrollTop
      scrollHeight.value = document.documentElement.scrollHeight
    } else {
      clientHeight.value = el.clientHeight
      scrollTop.value = el.scrollTop
      scrollHeight.value = el.scrollHeight
    }

    // 滚动到底部,自动加载更多
    // console.log("监听到滚动");
    if (Math.ceil(scrollTop.value + clientHeight.value) >= scrollHeight.value) {
      // 滚动到底部
      console.log("滚动到底部了");
      isReachBottom.value = true
    }
  }, 100)

  onMounted(() => {
    if(elRef) el = elRef.value
    el.addEventListener("scroll", scrollListenerHandler)
  })

  onUnmounted(() => {
    el.removeEventListener("scroll", scrollListenerHandler)
  })

  return { isReachBottom, scrollTop, scrollHeight, clientHeight }
}

2.2 实现点击后滚动到对应的位置

1.控制tabControl的显示隐藏
2.对要监听的组件实例的处理
3.自动设置title,以后想要新加tab,只需要在组件上加上属性name和ref即可
4.点击定位到具体位置
5.滚动中监听到达高度切换tab

  • detail页面
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { getDetailInfos } from '@/services'
import { ref, computed, reactive, onMounted } from 'vue';

import DetailSwipe from './components/detail_01-swipe.vue'
import DetailInfos from './components/detail_02-infos.vue'
import DetailFacility from './components/detail_03-facility.vue'
import DetailLandlord from './components/detail_04-landlord.vue'
import DetailComment from './components/detail_05-comment.vue'
import DetailNotice from './components/detail_06-notice.vue'
import DetailMap from './components/detail_07-map.vue'
import DetailIntroduction from './components/detail_08-introduction.vue'
import useScroll from '@/hooks/useScroll';

const router = useRouter()
const route = useRoute()

// 监听返回按钮的点击
const onClickLeft = () => {
  router.back()
}

// 发送网络请求
const houseId = route.params.id
const detailInfos = ref({})
const mainPart = computed(() => detailInfos.value?.mainPart)

getDetailInfos(houseId).then(res => {
  if (!res.data) {
    console.log("小主,数据请求超时,请重新刷新~~");
  }
  detailInfos.value = res.data
})

//=> tabControl的相关操作
// 1.控制tabControl的显示隐藏
const detailRef = ref()
const { scrollTop } = useScroll(detailRef)
const showTabControl = computed(() => {
  return scrollTop.value >= 300
})

// 2. 定义
const TabActive = ref(0)
const tabs = ref()

// const names = ["infos", "facility", "landlord", "comment", "notice", "map"]

// 3.对要监听的组件实例的处理
const sectionEles = ref({})
const getSectionRef = (value) => {
  // 点击返回,组件被卸载的时候也会执行这个方法,null的时候会报错
  if(!value) return;
  const name = value.$el.getAttribute("name")
  sectionEles.value[name] = value.$el
}

// 自动设置title,以后想要新加tab,只需要在组件上加上属性name和ref即可
// const titles = ["概述", "设施", "房东", "点评", "须知", "周边"]
const titles = computed(() => {
  return Object.keys(sectionEles.value)
})

// 4.点击定位到具体位置
const onClickTab = (value) => {
  const title = value?.title 
  let top = sectionEles.value[title]?.offsetTop
  if(title !== "概述") {
    top = top - 44
  }
  detailRef.value.scrollTo({
    top: top,
    behavior: "smooth"
  })
}

// 5.滚动中监听到达高度切换tab


</script>

<template>
  <div class="detail top-page" ref="detailRef">
    <!-- tabControl -->
    <div class="tabControl">
      <van-tabs 
        v-model:active="TabActive" 
        sticky 
        title-active-color="#ff9645"
        ref="tabs"
        v-if="showTabControl" 
        @click-tab="onClickTab"
      >
        <van-tab v-for="(item, index) in titles" :key="index" :title="item" ></van-tab>
      </van-tabs>
    </div>

    <!-- 导航栏 -->
    <van-nav-bar title="房屋详情" left-text="旅途" left-arrow @click-left="onClickLeft" />

    <div class="main" v-if="mainPart" v-memo="[mainPart]">
      <!-- 轮播图 -->
      <detail-swipe :swipe-data="mainPart.topModule.housePicture.housePics"></detail-swipe>
      <!-- 详情 -->
      <detail-infos name="概述" :ref="getSectionRef" :top-infos="mainPart.topModule"></detail-infos>
      <!-- 房屋设施 -->
      <detail-facility name="设施" :ref="getSectionRef"
        :house-facility="mainPart.dynamicModule.facilityModule.houseFacility"></detail-facility>
      <!-- 房东介绍 -->
      <detail-landlord name="房东" :ref="getSectionRef" :landlord="mainPart.dynamicModule.landlordModule"
        ref="landlordRef"></detail-landlord>
      <!-- 房客点评 -->
      <detail-comment name="点评" :ref="getSectionRef" :comment="mainPart.dynamicModule.commentModule"></detail-comment>
      <!-- 预定须知 -->
      <detail-notice name="须知" :ref="getSectionRef" :notice="mainPart.dynamicModule.rulesModule"></detail-notice>
      <!-- 位置周边 -->
      <detail-map name="周边" :ref="getSectionRef" :map="mainPart.dynamicModule.positionModule"></detail-map>
      <!-- 价格说明 -->
      <detail-introduction :introduction="mainPart"></detail-introduction>

    </div>
  </div>
</template>

<style lang="less" scoped>
.tabControl {
  .van-tabs {
    position: fixed;
    z-index: 20;
  }

  :deep(.van-tabs__wrap) {
    width: 415px;
  }
}

.detail {
  position: relative;
}
</style>

2.3 页面滚动匹配tabControl索引

/* 
5.页面滚动,滚动到一定的位置时,显示正确的tabControl的索引(标题)
  + 监听滚动的位置 scrollTop
  + 利用scrollTop去匹配正确的位置
    + 变化的scrollTop,newValue 在数组中匹配第一个大于的值,找到跳出循环
    + 找到的index -1即可
    + 最后一个匹配不到,可以默认索引为最后一个
    + 第一个索引为-1,可以不考虑,因为在第一次的时候scrollTop还没有变化,依然是最初设置的0
*/
watch(scrollTop, (newValue) => {
  // 1.获取所有区域的offsetTop数组
  const elem = Object.values(sectionEles.value)
  const offsetValues = elem.map(elem => elem.offsetTop)
  
  // 2.根据newValue去匹配想要的索引
  // 最后一个匹配不到,可以默认索引为最后一个
  let index = offsetValues.length -1
  for(let i = 0; i < offsetValues.length; i++){
    let curValue = offsetValues[i]
    if(curValue > newValue + 44) {
      index = i -1
      break
    }
  }
  // 如果拿到的是组件实例,可以调用子组件的方法和属性,可以修改默认index
  TabActive.value = index
})

2.4 索引匹配算法-随机数在数组中的索引

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="btn">随机数</button>

  <script>
    // 1.定义的数组
    const values = [100, 300, 500, 800, 1000]

    // 2.按钮的点击
    // 获取元素
    const btnEl = document.querySelector('.btn')
    btnEl.onclick = function () {
      let currentValue = Math.floor(Math.random() * 1500)
      matchIndex(currentValue)
    }

    // 3.随机currentValue匹配索引
    function matchIndex(currentValue) {
      let index = values.length - 1
      for (let i = 0; i < values.length; i++) {
        const value = values[i]
        if (value > currentValue) {
          index = i - 1
          break
        }
      }
      if (index === -1) return
      console.log("value:", currentValue, "index:", index)
    }

    /*
    匹配400在values中是哪一个位置
       + [300, 500] 最后算法找到500的索引,我们需要的是300的索引,所以index-1即可
    let currentValue = 400
    let index = values.length - 1
    for(let i = 0; i < values.length; i++) {
      const value = values[i]
      if (value > currentValue) {
        index = i - 1
        break
      }
    }
    console.log(index);
    */
  </script>
</body>

</html>

2.5 点击tabs跳动bug处理及tabControl的动态图

1.gif

// 4.点击定位到具体位置
let isClick = false
let currentTop = -1
const onClickTab = (value) => {
  const title = value?.title 
  let top = sectionEles.value[title]?.offsetTop
  top = title === "概述" ? top -34 : top -44

  // 点击时设置为true
  isClick = true
  // 点击时记录要滚动的位置
  currentTop = top

  detailRef.value.scrollTo({
    top: top,
    behavior: "smooth"
  })
}

/* 
5.页面滚动,滚动到一定的位置时,显示正确的tabControl的索引(标题)
  + 监听滚动的位置 scrollTop
  + 利用scrollTop去匹配正确的位置
    + 变化的scrollTop,newValue 在数组中匹配第一个大于的值,找到跳出循环
    + 找到的index -1即可
    + 最后一个匹配不到,可以默认索引为最后一个
    + 第一个索引为-1,可以不考虑,因为在第一次的时候scrollTop还没有变化,依然是最初设置的0
*/

watch(scrollTop, (newValue) => {
  // 如果点击滚动到了目标位置,改变标志,继续执行滚动逻辑
  if(Math.round(newValue) === currentTop){
    isClick = false
  }
  
  // 如果是点击操作,下面的滚动逻辑不执行 解决点击跳动问题
  if(isClick) return
  
  // 1.获取所有区域的offsetTop数组
  const elem = Object.values(sectionEles.value)
  const offsetValues = elem.map(elem => elem.offsetTop)

  // 2.根据newValue去匹配想要的索引
  // 最后一个匹配不到,可以默认索引为最后一个
  let index = offsetValues.length -1
  let curValue = null
  for(let i = 0; i < offsetValues.length; i++){
    curValue = offsetValues[i]
    if(curValue > newValue + 44) {
      index = i -1
      break
    }
  }
  // 如果拿到的是组件实例,可以调用子组件的方法和属性,可以修改默认index
  // 如果已经等于了就不再次赋值
  // if(TabActive.value === index) return
  TabActive.value = index
})

2.6 详情页actionBar

image.png

  • detail页面使用组件
import ActionBar from './components/detail_09-actionbar.vue'

<div class="actionBar" v-if="detailInfos.currentHouse">
  <action-bar :action-bar="detailInfos.currentHouse"></action-bar>
</div>
  • action-bar
<script setup>
import { computed } from 'vue';
const props = defineProps({
  actionBar: {
    type: Object,
    default: () => ({})
  }
})

const discountNum = computed(() => {
  const prod = props.actionBar.productPrice.replace("¥", "")
  const final = props.actionBar.finalPrice
  return prod - final
})
</script>

<template>
  <div class="action-bar">
    <div class="bottom-price">
      <div class="bottom-price-show">
        <div class="chat">
          <img src="@/assets/img/detail/icon_chat.png" alt="">
          <div class="txt">聊天</div>
        </div>
        <div class="count">
          <div class="price-main">
            <div class="final">¥{{ actionBar.finalPrice }}</div>
            <div class="final-right">
              <div class="priceMark">{{ actionBar.priceMark }}</div>
              <div class="product-price">{{ actionBar.productPrice }}</div>
            </div>
          </div>
          <div class="discount">
            <div class="discount-num">已减{{ discountNum }}元</div>
            <div class="txt">明细<van-icon name="arrow-down" /></div>
          </div>
        </div>
      </div>
      <div class="bottom-price-buttom">
        <img src="@/assets/img/detail/icon_order.png" alt="">
        <div class="txt">预订当前房源</div>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.action-bar {
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 101;

  .bottom-price {
    width: 100vw;
    height: 60px;
    background-color: #fff;
    border-radius: 6px 6px 0 0;
    display: flex;
    justify-content: space-between;
    align-items: center;

    .bottom-price-show {
      display: flex;
      flex: 1;

      .chat {
        display: flex;
        align-items: center;
        justify-content: center;
        flex-direction: column;
        height: 42px;
        border-right: 1px solid #e9e9e9;
        padding: 10px 15px;
        font-size: 10px;
        color: #333;

        img {
          width: 17px;
          height: 17px;
          margin-bottom: 6px;
        }
      }

      .count {
        display: flex;
        flex-direction: column;
        justify-content: center;
        flex: 1;
        padding-left: 12px;
        color: #666;
        font-size: 10px;

        .price-main {
          display: flex;
          align-items: flex-end;

          .final {
            font-size: 16px;
            font-weight: 600;
            color: var(--primary-color);
            margin-right: 5px;
          }

          .final-right {
            display: flex;
            font-size: 10px;
            line-height: 17px;

            .product-price {
              margin-left: 5px;
              text-decoration: line-through;
            }
          }
        }

        .discount {
          display: flex;

          .discount-num {
            height: 15px;
            display: inline-block;
            background: rgba(255, 150, 69, .1);
            border-radius: 2px;
            padding: 2px;
            font-size: 10px;
            color: var(--primary-color);
            margin-top: 2px;
            white-space: nowrap;
            margin-right: 10px;
          }

          .txt {
            margin-top: 4px;
            font-size: 11px;
            color: var(--primary-color);
          }
        }
      }
    }

    .bottom-price-buttom {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 150px;
      padding: 20px 0;
      color: #fff;
      font-size: 18px;
      background: var(--theme-linear-gradient);

      img {
        width: 14px;
        height: 18px;
        vertical-align: middle;
        margin-right: 5px;
      }
    }
  }
}
</style>
  • detail-introducton底部样式修改
.ensure-bottom {
  margin-bottom: 90px;
}

3.切换页面的问题

3.1 切换页面的keep-alive操作

这种写法会报错
<keep-alive includes="home">
    <router-view></router-view>
</keep-alive>

采用以下做法
1.给要缓存的组件加name属性
<router-view v-slot="props">
    <keep-alive include="home">
        <component :is="props.Component"></component>
    </keep-alive>
</router-view>

3.2切换页面自动发送网络请求的问题

因为监听window窗口滚动到底部发送网络请求加载更多,会造成切换页面自动监听到底部并发送网络请求 可以给home元素y方向滚动,添加ref

const homeRef = ref()
const { isReachBottom, scrollTop } = useScroll(homeRef)

<div class="home" ref="homeRef"></div>

.home {
  // 让元素滚动
  height: 100vh;
  overflow-y: auto;
  padding-bottom: 60px;
  box-sizing: border-box;
}

3.3 设置视口和px to vw浏览器适配

  • 视口设置
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
  • 浏览器适配
yarn add postcss-px-to-viewport -D

postcss.config.js

module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      viewportWidth: 375
    }
  }
}

4.编写价格和人数区域点击显示弹出层

image.png
<script setup>
import { ref } from 'vue'
import useMain from '@/stores/modules/main';

const mainStore = useMain()
// 价格
const showPriceBottom = ref(false)
const priceRange = ref({
  '¥100以下': [0, 100],
  '¥100-200': [100, 200],
  '¥200-300': [200, 300],
  '¥300-400': [300, 400],
  '¥400-600': [400, 600],
  '¥600-1000': [600, 1000],
  '¥1000-2000': [1000, 2000],
  '¥2000以上': [2000, 2000]
})
const sliderValue = ref([0, 2000])

const priceClick = () => {
  showPriceBottom.value = true
}
const onChange = (value) => {
  console.log(value);
}

// 人数
const showPeopleBottom = ref(false)
const showIcon = ref(10)
const peopleOptions = [
  { text: '1人', value: 0 },
  { text: '2人', value: 1 },
  { text: '3人', value: 2 },
  { text: '4人', value: 3 },
  { text: '5人', value: 4 },
  { text: '6人', value: 5 },
  { text: '7人', value: 6 },
  { text: '8人', value: 7 },
  { text: '9人', value: 8 },
  { text: '10+人', value: 9 },
  { text: '不限人数', value: 10 }
]
const peopleClick = () => {
  showPeopleBottom.value = true
}
const peopleItemClick = (index) => {
  showIcon.value = index
}
</script>

<template>
  <div class="item price-counter bottom-gray-line">
    <div class="start" @click="priceClick">价格不限</div>
    <div class="end" @click="peopleClick">人数不限
    </div>
  </div>
  <van-popup v-model:show="showPriceBottom" round position="bottom" closeable :style="{ height: '60%' }">
    <div class="card">
      <div class="title">价格</div>
      <div class="select">
        <div class="tip">价格区间 ¥0-不限</div>
        <van-slider v-model="sliderValue" bar-height="4px" active-color="#ff9645" range max="2000" @change="onChange" />
        <div class="range">
          <template v-for="(value, key, index) in priceRange" :key="index">
            <div class="item">{{ key }}</div>
          </template>
        </div>
      </div>
      <div class="price-btn">
        <van-button type="primary" class="clear">清空</van-button>
        <van-button type="primary" class="confirm">确认</van-button>
      </div>
    </div>
  </van-popup>
  <van-popup v-model:show="showPeopleBottom" :overlay="false" position="bottom" closeable close-icon-position="top-left"
    :style="{ height: '100%' }">
    <div class="people">
      <div class="title">选择入住人数</div>
      <template v-for="(item, index) in peopleOptions" :key="item.value">
        <div class="item" @click="peopleItemClick(index)">
          <div class="text">{{ item.text }}</div>
          <div class="icon" v-show="index === showIcon">
            <van-icon name="success" color="#ff9645" />
          </div>
        </div>
      </template>
    </div>
  </van-popup>
</template>

<style lang="less" scoped>
.price-counter {
  .start {
    border-right: 1px solid var(--line-color);
  }
}

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  color: #999;

  .start {
    flex: 1;
    display: flex;
    height: 44px;
    align-items: center;
  }

  .end {
    min-width: 30%;
    padding-left: 20px;
  }

  .date {
    display: flex;
    flex-direction: column;

    .tip {
      font-size: 12px;
      color: #999;
    }

    .time {
      margin-top: 3px;
      color: #333;
      font-size: 15px;
      font-weight: 500;
    }
  }
}

.card {
  padding: 20px;
  height: 90%;
  overflow-y: hidden;
  background-color: #f2f3f4;

  .title {
    color: #333;
    font-weight: 600;
    margin-bottom: 10px;
  }

  .--van-popup-close-icon-color {
    color: rgb(153, 153, 153);
    font-size: 18px;
  }

  .select {
    background-color: #fff;
    padding: 10px;
    height: 220px;
    border-radius: 9px;
    font-size: 10px;
    color: #666;

    .van-slider {
      position: relative;
      top: 0px;
      left: 15px;
      margin-top: 30px;
      width: 90%;
    }

    .range {
      display: flex;
      flex-wrap: wrap;
      margin-top: 18px;

      .item {
        height: 23px;
        padding: 5px 10px;
        background-color: #f8f8f8;
        margin-right: 20px;
        margin-top: 15px;
        font-size: 12px;
        border-radius: 15px;
        color: #666;
      }
    }
  }

  .van-button {
    border: none;
    border-radius: 20px;
  }

  .price-btn {
    margin-top: 30px;

    .clear {
      height: 40px;
      padding: 0 25px;
      background-color: #fff;
      color: #333;
      font-size: 15px;
      text-align: center;
      line-height: 40px;
    }

    .confirm {
      min-width: 240px;
      margin-left: 10px;
      background: var(--primary-color);
      font-weight: 500;
      color: #fff;
    }
  }
}

.people {
  height: 100vh;
  background-color: #fff;

  .--van-popup-close-icon-color {
    width: 11px;
    height: 11px;
    color: #333;
  }

  .title {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    font-size: 15px;
  }

  .item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f6f6f6;
    color: #333;
  }
}
</style>

5.价格部分功能完善

  • 点击显示弹出框
  • 点击下面按钮或者拖动滑块,头部自动显示价格
  • 点击清空,头部显示 ¥0-不限
  • 点击确定,把数据保存到store,弹出框隐藏
  • 价格回显
  • 右边可以清空选择的价格

image.png

<script setup>
import { ref, computed } from 'vue'
import useMain from '@/stores/modules/main';
import { storeToRefs } from 'pinia';

const mainStore = useMain()

// store管理数据
const { sPrice, ePrice } = storeToRefs(mainStore)
// 价格
// 定义
const showPriceBottom = ref(false)
const priceRange = ref({
  '¥100以下': [0, 100],
  '¥100-200': [100, 200],
  '¥200-300': [200, 300],
  '¥300-400': [300, 400],
  '¥400-600': [400, 600],
  '¥600-1000': [600, 1000],
  '¥1000-2000': [1000, 2000],
  '¥2000以上': [2000, 2000]
})
// 滑块初始值
const sliderValue = ref([0, 2000])
// 点击后存储值
const changeValue = ref([])
// const sPrice = ref(0)
// const ePrice = ref(0)

// 点击后弹出框显示
const priceClick = () => {
  showPriceBottom.value = true
}


// 把值给changeValue保存
// 1.点击item
const priceItemClick = (value) => {
  sliderValue.value = value
  changeValue.value = value
}
// 2.手动拖动滑块的值
const onChange = (value) => {
  changeValue.value = value
}

// card price动态设置
const getCardPrice = () => {
  let len = changeValue.value.length
  // 第一次进来数组没有长度  设置值是价格不限
  if (len === 0 || changeValue.value === '价格不限') {
    return `¥0-不限`
  }

  return `¥${changeValue.value[0]}-¥${changeValue.value[1]}`
}

// 清空
const clear = () => {
  sliderValue.value = [0, 2000]
  changeValue.value = `价格不限`
}
// 确认
const confirm = () => {
  // 复原store数据
  if (changeValue.value == '价格不限') {
    sPrice.value = 0
    ePrice.value = 0
    showPriceBottom.value = false
    return
  }
  // 给store设置数据
  sPrice.value = changeValue.value[0]
  ePrice.value = changeValue.value[1]
  showPriceBottom.value = false
}
// 判断是否已经选择有用数据
const isSelected = computed(() => {
  return changeValue.value.length !== 0 && changeValue.value !== '价格不限'
})

// 清空选择的价格
const isClear = ref(false)
const clearSelect = () => {
  sliderValue.value = [0, 2000]
  changeValue.value.splice(0, 2)
  sPrice.value = 0
  ePrice.value = 0
  isClear.value = true
}


// 人数
const showPeopleBottom = ref(false)
const showIcon = ref(10)
const peopleOptions = [
  { text: '1人', value: 0 },
  { text: '2人', value: 1 },
  { text: '3人', value: 2 },
  { text: '4人', value: 3 },
  { text: '5人', value: 4 },
  { text: '6人', value: 5 },
  { text: '7人', value: 6 },
  { text: '8人', value: 7 },
  { text: '9人', value: 8 },
  { text: '10+人', value: 9 },
  { text: '不限人数', value: 10 }
]
const peopleClick = () => {
  showPeopleBottom.value = true
}
const peopleItemClick = (index) => {
  showIcon.value = index
}
</script>

<template>
  <div class="item price-counter bottom-gray-line">
    <div class="start" :class="{ selected: isSelected }">
      <div class="startTxt" @click="priceClick">{{ mainStore.getPriceStart }}</div>
      <van-icon name="close" v-if="isSelected" @click="clearSelect" />
    </div>
    <div class="end" @click="peopleClick">人数不限
    </div>
  </div>
  <van-popup v-model:show="showPriceBottom" round position="bottom" closeable :style="{ height: '60%' }">
    <div class="card">
      <div class="title">价格</div>
      <div class="select">
        <div class="tip">价格区间 {{ getCardPrice() }}</div>
        <van-slider v-model="sliderValue" bar-height="4px" active-color="#ff9645" range max="2000" @change="onChange" />
        <div class="range">
          <template v-for="(value, key, index) in priceRange" :key="index">
            <div class="item" @click="priceItemClick(value)">{{ key }}</div>
          </template>
        </div>
      </div>
      <div class="price-btn">
        <van-button type="primary" class="clear" @click="clear">清空</van-button>
        <van-button type="primary" class="confirm" @click="confirm">确认</van-button>
      </div>
    </div>
  </van-popup>
  <van-popup v-model:show="showPeopleBottom" :overlay="false" position="bottom" closeable close-icon-position="top-left"
    :style="{ height: '100%' }">
    <div class="people">
      <div class="title">选择入住人数</div>
      <template v-for="(item, index) in peopleOptions" :key="item.value">
        <div class="item" @click="peopleItemClick(index)">
          <div class="text">{{ item.text }}</div>
          <div class="icon" v-show="index === showIcon">
            <van-icon name="success" color="#ff9645" />
          </div>
        </div>
      </template>
    </div>
  </van-popup>
</template>

<style lang="less" scoped>
.price-counter {
  .start {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-right: 1px solid var(--line-color);

    &.selected {
      color: #333;
    }

    .startTxt {
      width: 160px;
      height: 100%;
      line-height: 45px;
    }

    .van-icon {
      position: relative;
      top: 1px;
      right: 15px;
    }
  }
}

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  color: #999;

  .start {
    flex: 1;
    display: flex;
    height: 44px;
    align-items: center;
  }

  .end {
    min-width: 30%;
    padding-left: 20px;
  }

  .date {
    display: flex;
    flex-direction: column;

    .tip {
      font-size: 12px;
      color: #999;
    }

    .time {
      margin-top: 3px;
      color: #333;
      font-size: 15px;
      font-weight: 500;
    }
  }
}

.card {
  padding: 20px;
  height: 90%;
  overflow-y: hidden;
  background-color: #f2f3f4;

  .title {
    color: #333;
    font-weight: 600;
    margin-bottom: 10px;
  }

  .--van-popup-close-icon-color {
    color: rgb(153, 153, 153);
    font-size: 18px;
  }

  .select {
    background-color: #fff;
    padding: 10px;
    height: 220px;
    border-radius: 9px;
    font-size: 10px;
    color: #666;

    .van-slider {
      position: relative;
      top: 0px;
      left: 15px;
      margin-top: 30px;
      width: 90%;
    }

    .range {
      display: flex;
      flex-wrap: wrap;
      margin-top: 18px;

      .item {
        height: 23px;
        padding: 5px 10px;
        background-color: #f8f8f8;
        margin-right: 20px;
        margin-top: 15px;
        font-size: 12px;
        border-radius: 15px;
        color: #666;
      }
    }
  }

  .van-button {
    border: none;
    border-radius: 20px;
  }

  .price-btn {
    margin-top: 30px;

    .clear {
      height: 40px;
      padding: 0 25px;
      background-color: #fff;
      color: #333;
      font-size: 15px;
      text-align: center;
      line-height: 40px;
    }

    .confirm {
      min-width: 240px;
      margin-left: 10px;
      background: var(--primary-color);
      font-weight: 500;
      color: #fff;
    }
  }
}

.people {
  height: 100vh;
  background-color: #fff;

  .--van-popup-close-icon-color {
    width: 11px;
    height: 11px;
    color: #333;
  }

  .title {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    font-size: 15px;
  }

  .item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f6f6f6;
    color: #333;
  }
}
</style> 

6.选择人数功能实现,完成了价格人数模块编写

image.png

<script setup>
import { ref, computed } from 'vue'
import useMain from '@/stores/modules/main';
import { storeToRefs } from 'pinia';

const mainStore = useMain()

// store管理数据
const { sPrice, ePrice, selectPeople } = storeToRefs(mainStore)
// 价格
// 定义
const showPriceBottom = ref(false)
const priceRange = ref({
  '¥100以下': [0, 100],
  '¥100-200': [100, 200],
  '¥200-300': [200, 300],
  '¥300-400': [300, 400],
  '¥400-600': [400, 600],
  '¥600-1000': [600, 1000],
  '¥1000-2000': [1000, 2000],
  '¥2000以上': [2000, 2000]
})
// 滑块初始值
const sliderValue = ref([0, 2000])
// 点击后存储值
const changeValue = ref([])
// const sPrice = ref(0)
// const ePrice = ref(0)

// 点击后弹出框显示
const priceClick = () => {
  showPriceBottom.value = true
}


// 把值给changeValue保存
// 1.点击item
const priceItemClick = (value) => {
  sliderValue.value = value
  changeValue.value = value
}
// 2.手动拖动滑块的值
const onChange = (value) => {
  changeValue.value = value
}

// card price动态设置
const getCardPrice = () => {
  let len = changeValue.value.length
  // 第一次进来数组没有长度  设置值是价格不限
  if (len === 0 || changeValue.value === '价格不限') {
    return `¥0-不限`
  }

  return `¥${changeValue.value[0]}-¥${changeValue.value[1]}`
}

// 清空
const clear = () => {
  sliderValue.value = [0, 2000]
  changeValue.value = `价格不限`
}
// 确认
const confirm = () => {
  // 复原store数据
  if (changeValue.value == '价格不限') {
    sPrice.value = 0
    ePrice.value = 0
    showPriceBottom.value = false
    return
  }
  // 给store设置数据
  sPrice.value = changeValue.value[0]
  ePrice.value = changeValue.value[1]
  showPriceBottom.value = false
}
// 判断是否已经选择有用数据
const isSelected = computed(() => {
  return changeValue.value.length !== 0 && changeValue.value !== '价格不限'
})

// 清空选择的价格
const isClear = ref(false)
const clearSelect = () => {
  sliderValue.value = [0, 2000]
  changeValue.value.splice(0, 2)
  sPrice.value = 0
  ePrice.value = 0
  isClear.value = true
}


// 人数
const showPeopleBottom = ref(false)
const showIcon = ref(10)
const peopleOptions = [
  { text: '1人', value: 0 },
  { text: '2人', value: 1 },
  { text: '3人', value: 2 },
  { text: '4人', value: 3 },
  { text: '5人', value: 4 },
  { text: '6人', value: 5 },
  { text: '7人', value: 6 },
  { text: '8人', value: 7 },
  { text: '9人', value: 8 },
  { text: '10+人', value: 9 },
  { text: '人数不限', value: 10 }
]
const peopleClick = () => {
  showPeopleBottom.value = true
}
const peopleItemClick = (index, num) => {
  showIcon.value = index
  selectPeople.value = num
  showPeopleBottom.value = false
}
const isPeopleSelect = computed(() => selectPeople.value !== '人数不限')
const clearPeopleSelect = () => {
  selectPeople.value = '人数不限'
}
</script>

<template>
  <div class="item price-counter bottom-gray-line">
    <div class="start" :class="{ selected: isSelected }">
      <div class="startTxt" @click="priceClick">{{ mainStore.getPriceStart }}</div>
      <van-icon name="close" v-if="isSelected" @click="clearSelect" />
    </div>
    <div class="end" :class="{ active: isPeopleSelect }">
      <div class="endTxt" @click="peopleClick">{{ selectPeople }}</div>
      <van-icon name="close" v-if="isPeopleSelect" @click="clearPeopleSelect" />
    </div>
  </div>
  <van-popup v-model:show="showPriceBottom" round position="bottom" closeable :style="{ height: '60%' }">
    <div class="card">
      <div class="title">价格</div>
      <div class="select">
        <div class="tip">价格区间 {{ getCardPrice() }}</div>
        <van-slider v-model="sliderValue" bar-height="4px" active-color="#ff9645" range max="2000" @change="onChange" />
        <div class="range">
          <template v-for="(value, key, index) in priceRange" :key="index">
            <div class="item" @click="priceItemClick(value)">{{ key }}</div>
          </template>
        </div>
      </div>
      <div class="price-btn">
        <van-button type="primary" class="clear" @click="clear">清空</van-button>
        <van-button type="primary" class="confirm" @click="confirm">确认</van-button>
      </div>
    </div>
  </van-popup>
  <van-popup v-model:show="showPeopleBottom" :overlay="false" position="bottom" closeable close-icon-position="top-left"
    :style="{ height: '100%' }">
    <div class="people">
      <div class="title">选择入住人数</div>
      <template v-for="(item, index) in peopleOptions" :key="item.value">
        <div class="item" @click="peopleItemClick(index, item.text)">
          <div class="text">{{ item.text }}</div>
          <div class="icon" v-show="index === showIcon">
            <van-icon name="success" color="#ff9645" />
          </div>
        </div>
      </template>
    </div>
  </van-popup>
</template>

<style lang="less" scoped>
.price-counter {
  .start {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-right: 1px solid var(--line-color);

    &.selected {
      color: #333;
    }

    .startTxt {
      width: 160px;
      height: 100%;
      line-height: 45px;
    }

    .van-icon {
      position: relative;
      top: 1px;
      right: 15px;
    }
  }

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

    &.active {
      color: #333;
    }

    .endTxt {
      width: 80%;
    }
  }
}

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  color: #999;

  .start {
    flex: 1;
    display: flex;
    height: 44px;
    align-items: center;
  }

  .end {
    min-width: 30%;
    padding-left: 20px;
  }

  .date {
    display: flex;
    flex-direction: column;

    .tip {
      font-size: 12px;
      color: #999;
    }

    .time {
      margin-top: 3px;
      color: #333;
      font-size: 15px;
      font-weight: 500;
    }
  }
}

.card {
  padding: 20px;
  height: 90%;
  overflow-y: hidden;
  background-color: #f2f3f4;

  .title {
    color: #333;
    font-weight: 600;
    margin-bottom: 10px;
  }

  .--van-popup-close-icon-color {
    color: rgb(153, 153, 153);
    font-size: 18px;
  }

  .select {
    background-color: #fff;
    padding: 10px;
    height: 220px;
    border-radius: 9px;
    font-size: 10px;
    color: #666;

    .van-slider {
      position: relative;
      top: 0px;
      left: 15px;
      margin-top: 30px;
      width: 90%;
    }

    .range {
      display: flex;
      flex-wrap: wrap;
      margin-top: 18px;

      .item {
        height: 23px;
        padding: 5px 10px;
        background-color: #f8f8f8;
        margin-right: 20px;
        margin-top: 15px;
        font-size: 12px;
        border-radius: 15px;
        color: #666;
      }
    }
  }

  .van-button {
    border: none;
    border-radius: 20px;
  }

  .price-btn {
    margin-top: 30px;

    .clear {
      height: 40px;
      padding: 0 25px;
      background-color: #fff;
      color: #333;
      font-size: 15px;
      text-align: center;
      line-height: 40px;
    }

    .confirm {
      min-width: 240px;
      margin-left: 10px;
      background: var(--primary-color);
      font-weight: 500;
      color: #fff;
    }
  }
}

.people {
  height: 100vh;
  background-color: #fff;

  .--van-popup-close-icon-color {
    width: 11px;
    height: 11px;
    color: #333;
  }

  .title {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    font-size: 15px;
  }

  .item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f6f6f6;
    color: #333;
  }
}
</style> 

7.点击search-keyword和search-bar组件跳转到keyword-suggests页面

image.png

import { useRouter } from 'vue-router';

const router = useRouter()

//给组件添加事件即可
const keywordClick = () => {
  router.push('/keyword-suggests')
}

8.keyword-suggests页面的展示

image.png

8.1 配置路由表

{
  path: "/keyword-suggests",
  component: () => import("@/views/keyword-suggests/keyword-suggests.vue")
}

8.2 管理api 和 store请求数据

  • 封装api
// search.js

// 测试用的配置,需要在vite.config.js中配置代理
const TSET_URL = "http://localhost:5173/api";
const _apitsp = "1657957399020";
const _fasTraceId = "1657957399023KiYJenCT_6Th0R8QmkS3QDMD3wNhFaJsfn97x";

// 发起网络请求
export function getGuessulike() {
  return hyRequest.post({
    url:
      TSET_URL +
      `/bnbapp-node-h5/h5/suggest/guessulike/bnb?_apitsp=${_apitsp}&_fasTraceId=${_fasTraceId}
    `,
    method: "post",
    data: { cityId: 45, keywordTryRecommend: 2 },
  });
}

  • store请求数据 keyword-suggests.js
import { getGuessulike, keywordsearchsuggest} from "@/services";
import { defineStore } from "pinia";

const usekeywordsuggests = defineStore("keywordsuggests", {
  state: () => ({
    keywordsuggests: [],
    isDownIdx: '',
    type: '',
    suggestName: ''
  }),
  actions: {
    //页面调用即可发送请求
    async fetchKeywordsuggestsData(){
      const res = await getGuessulike()
      this.keywordsuggests = res.data
    },
  }
})

export default usekeywordsuggests

8.3 keyword-suggests页面

<script setup>
import { ref } from 'vue'
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router';

import searchCategory from './components/search-category.vue';
import SearchContent from './components/search-content.vue'

const router = useRouter()
const keywordsuggestsStore = usekeywordsuggests()
const { keywordsuggests } = storeToRefs(keywordsuggestsStore)

// 发送网络请求
keywordsuggestsStore.fetchKeywordsuggestsData()

// 1.搜索框的功能
const searchValue = ref("")

// 点击取消返回上一个页面
const cancelClick = () => {
  router.back()
}

</script>

<template>
  <div class="keyword-suggests top-page">
    <!-- 搜索框 -->
    <van-search v-model="searchValue" placeholder="搜索广州的景点/地标/房源" shape="round" show-action
      @cancel="cancelClick"></van-search>
    <!-- 滚动区域 -->
    <div class="search-result-panel">
      <template v-for="(item, index) in keywordsuggests.groups" :key="index">
        <search-category :category="item" :index="index"></search-category>
        <search-content :content="item" :index="index"></search-content>
      </template>
    </div>
  </div>
</template>

<style lang="less" scoped>
.van-search {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 9;
}

.keyword-suggests {
  height: 100vh;
  overflow-y: auto;
  margin-top: 54px;
}
</style>

8.4 两个组件

  • search-category
<script setup>
import { ref } from 'vue';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';

const props = defineProps({
  category: {
    type: Object,
    default: () => ({})
  },
  index: {
    type: Number,
    default: 0
  }
})

const keywordsuggestsStore = usekeywordsuggests()
const { isDownIdx, type } = storeToRefs(keywordsuggestsStore)

const isToggle = ref(true)

const moreClick = () => {
  isToggle.value = !isToggle.value
  if (!isToggle.value) {
    isDownIdx.value = props.index
    type.value = 'show'
  } else {
    type.value = 'hidden'
  }
}
</script>

<template>
  <div class="search-category">
    <span class="name">{{ category.name }}</span>
    <div class="more" @click="moreClick">
      <template v-if="isToggle">
        <div class="down">展开</div>
        <van-icon name="arrow-down" />
      </template>
      <template v-else>
        <div class="down">收起</div>
        <van-icon name="arrow-up" />
      </template>
    </div>
  </div>
</template>

<style lang="less" scoped>
.search-category {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  margin-top: 20px;

  .name {
    font-weight: 500;
    font-size: 16px;
    color: #333;

    &::before {
      position: relative;
      top: 1px;
      content: "";
      display: inline-block;
      width: 3px;
      height: 15px;
      margin-right: 10px;
      border-radius: 4px;
      background-color: #ff9645;
      box-shadow: 4px 0 10px 0 rgb(255 150 69 / 30%);
    }
  }

  .more {
    display: flex;
    align-items: center;

    .down {
      font-weight: 500;
      font-size: 10px;
      color: #666;
      line-height: 20px;
      height: 20;
      margin-right: 5px;
    }
  }
}
</style>
  • search-content
<script setup>
import { computed } from 'vue'
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';

const props = defineProps({
  content: {
    type: Object,
    default: () => ({})
  },
  index: {
    type: Number,
    default: 0
  }
})

const keywordsuggestsStore = usekeywordsuggests()
const { isDownIdx, type, suggestName } = storeToRefs(keywordsuggestsStore)
const isToggle = computed(() => {
  return isDownIdx.value === props.index && type.value === 'show'
})
</script>

<template>
  <div class="content" :class="{ toggle: isToggle }">
    <template v-for="(item, index) in content.suggests" :key="index">
      <div class="search-suggests" :class="{ active: content.name == '热门搜索' }">
        <div class="item">{{ item.name }}</div>
      </div>
    </template>
  </div>
</template>

<style lang="less" scoped>
.content {
  padding: 11px 10px 0 20px;
  margin-bottom: 16px;
  max-height: 68px;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;

  &.toggle {
    max-height: 168px;
  }

  .search-suggests {
    margin-left: 10px;
    margin-bottom: 10px;
    font-size: 12px;
    border-radius: 14px;
    padding: 5px 12px;
    background-color: rgb(240, 243, 246);

    &.active {
      background-color: #fff4ec;
    }
  }
}
</style>

9.首页和keyword-suggests页面跳转到search页面

image.png

  • keyword-suggests页面
// 点击跳转到search页面
const itemClick = (clickName) => {
  suggestName.value = clickName
  router.push({
    path: '/search',
    query: {
      startDate: startDate.value,
      endDate: endDate.value,
      cityName: currentCity.value.cityName,
      clickName: clickName,
      selectPeople: selectPeople.value
    }
  })
}
  • 首页hot-suggest
const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

const router = useRouter()
const itemClick = (itemTxt) => {
  suggestName.value = itemTxt
  router.push({
    path: "/search",
    query: {
      clickName: itemTxt
    }
  })
}
  • search-btn
<script setup>
import useCity from '@/stores/modules/city';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import useMain from '@/stores/modules/main';
import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router';

const mainStore = useMain()
const { startDate, endDate, sPrice, ePrice, selectPeople } = storeToRefs(mainStore)

const cityStore = useCity()
const { currentCity } = storeToRefs(cityStore)

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

// 搜索并且跳转到search页面
const router = useRouter()
const searchBtnClick = () => {
  router.push({
    path: "/search",
    query: {
      startDate: startDate.value,
      endDate: endDate.value,
      cityName: currentCity.value.cityName,
      clickName: suggestName.value,
      sPrice: sPrice.value,
      ePrice: ePrice.value,
      selectPeople: selectPeople.value
    }
  })
}
</script>

<template>
  <div class="item search-btn" @click="searchBtnClick">
    <div class="btn">开始搜索</div>
  </div>
</template>

<style lang="less" scoped>
.search-btn {
  .btn {
    width: 442px;
    height: 38px;
    max-height: 50px;
    font-weight: 500;
    font-size: 18px;
    line-height: 38px;
    text-align: center;
    border-radius: 20px;
    color: #fff;
    background-image: var(--theme-linear-gradient);
  }
}

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  color: #999;

  .start {
    flex: 1;
    display: flex;
    height: 44px;
    align-items: center;
  }

  .end {
    min-width: 30%;
    padding-left: 20px;
  }

  .date {
    display: flex;
    flex-direction: column;

    .tip {
      font-size: 12px;
      color: #999;
    }

    .time {
      margin-top: 3px;
      color: #333;
      font-size: 15px;
      font-weight: 500;
    }
  }
}
</style>

10.点击热门区域回显到home页面

image.png

image.png

<script setup>
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
import { useRouter } from 'vue-router';

const router = useRouter()

const keywordClick = () => {
  router.push('/keyword-suggests')
}

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

const getsuggestName = computed(() => {
  if (suggestName.value === '') return `关键字/位置/民宿名`
  return suggestName.value
})

const clearSuggestName = () => {
  suggestName.value = ''
}
</script>

<template>
  <div class="item keyword bottom-gray-line" :class="{ active: suggestName !== '' }">
    <div class="txt" @click="keywordClick">{{ getsuggestName }}</div>
    <van-icon name="close" v-if="suggestName !== ''" @click="clearSuggestName" />
  </div>
</template>

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

  &.active {
    color: #333;
  }

  .txt {
    width: 90%;
  }

  .van-icon {
    // margin-left: 10px;
  }
}

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  color: #999;

  .start {
    flex: 1;
    display: flex;
    height: 44px;
    align-items: center;
  }

  .end {
    min-width: 30%;
    padding-left: 20px;
  }

  .date {
    display: flex;
    flex-direction: column;

    .tip {
      font-size: 12px;
      color: #999;
    }

    .time {
      margin-top: 3px;
      color: #333;
      font-size: 15px;
      font-weight: 500;
    }
  }
}
</style>

11.search页面开发

11.1 进一步强化search-bar组件

  1. 增加当前城市
  2. 搜索按钮等的样式修改
<script setup>
import useMain from '@/stores/modules/main';
import { storeToRefs } from 'pinia';
import { formatMonthDay } from '@/utils/format_data'
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';

const props = defineProps({
  isSearch: {
    type: Boolean,
    default: false
  },
  city: {
    type: String,
    default: ''
  },
  title: {
    type: String,
    default: ''
  }
})

const mainStore = useMain()
const { startDate, endDate } = storeToRefs(mainStore)
const startDateStr = computed(() => formatMonthDay(startDate.value, "MM.DD"))
const endDateStr = computed(() => formatMonthDay(endDate.value, "MM.DD"))

const router = useRouter()

const keywordClick = () => {
  router.push('/keyword-suggests')
}

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

const getsuggestName = computed(() => {
  if (suggestName.value === '') return props.title
  return suggestName.value
})

const clearSuggestName = () => {
  suggestName.value = ''
}

const isShowRightIcon = computed(() => {
  if (props.isSearch) return false
  return suggestName.value === ''
})
</script>

<template>
  <div class="search">
    <div class="position" v-if="isSearch">
      <slot class="current-city">{{ city }}</slot>
    </div>
    <div class="select-time">
      <div class="start">
        <div class="name"></div>
        <div class="date">{{ startDateStr }}</div>
      </div>
      <div class="end">
        <div class="name"></div>
        <div class="date">{{ endDateStr }}</div>
        <i class="icon-search-arrow"></i>
      </div>
    </div>
    <div class="search-inner" :class="{ active: suggestName !== '' }">
      <div class="keyword" @click="keywordClick" :style="{ width: !isShowRightIcon ? '100%' : '' }">{{ getsuggestName }}
      </div>
      <van-icon name="close" v-if="suggestName !== ''" @click="clearSuggestName" />
    </div>
    <div class="right-icon" v-if="isShowRightIcon">
      <i class="icon-search"></i>
    </div>
  </div>
</template>

<style lang="less" scoped>
.search {
  position: relative;
  display: flex;
  height: 40px;
  border-radius: 6px;
  background-color: #f2f4f6;
  color: #999;

  .position {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 5px 0 10px;
    font-size: 14px;
    font-weight: 500;
    color: #333;
    border-right: 1px solid #fff;
  }

  .select-time {
    display: flex;
    flex-direction: column;
    border-right: 1px solid #fff;
    align-items: flex-start;
    justify-content: center;
    font-size: 10px;
    padding: 5px 0 5px 5px;

    .start,
    .end {
      display: flex;
      margin: 0 2px;
      font-weight: 500;
      justify-content: center;
      align-items: flex-end;

      .date {
        margin: 0 2px;
        color: #333;
      }
    }

    .end {
      .icon-search-arrow {
        position: relative;
        top: -2px;
        display: inline-block;
        width: 5px;
        height: 5px;
        background-image: url(@/assets/img/home/home-sprite.png);
        background-size: 207px 192px;
        background-position: -201px -157px;
      }
    }
  }

  .search-inner {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 10px;
    flex: 1;
    font-size: 14px;

    &.active {
      color: #333;
      width: 157px;
    }

    .keyword {
      width: 90%;
      font-size: 12px;
      font-weight: 500;
    }
  }

  .right-icon {
    .icon-search {
      position: absolute;
      right: 5px;
      top: 10px;
      width: 24px;
      height: 24px;
      background-image: url(@/assets/img/home/home-sprite.png);
      background-size: 207px 192px;
      background-position: -29px -151px;
    }
  }
}
</style>

11.2 实现导航栏

image.png

<script setup>
import SearchBar from '@/components/search-bar/index.vue'
import { useRoute, useRouter } from 'vue-router';

const route = useRoute()
const router = useRouter()

const city = route.query.cityName

const back = () => {
  router.back()
}
</script>

<template>
  <div class="search top-page">
    <!-- 导航栏 -->
    <div class="nav">
      <div class="go-back" @click="back">
        <van-icon name="arrow-left" color="#ff9645" :size="25" />
      </div>
      <div class="search-bar">
        <search-bar title="搜索广州的景点、地标、房源" :is-search="true" :city="city" />
      </div>
      <div class="right">
        <i class="right-icon"></i>
      </div>
    </div>
  </div>
</template>

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

  // padding: 0 20px;
  .nav {
    position: fixed;
    top: 0;
    left: 0;
    width: 335px;
    background-color: #fff;
    padding: 5px 20px;
    z-index: 9;
    display: flex;
    align-items: center;
    justify-content: center;

    .go-back {
      position: absolute;
      top: 12px;
      left: 10px;
    }

    .right {
      position: absolute;
      top: 7px;
      right: 6px;
      line-height: 13px;
      padding: 10px;

      .right-icon {
        display: inline-block;
        width: 20px;
        height: 15px;
        background-image: url(@/assets/img/home/home-sprite.png);
        background-size: 207px 192px;
        background-position: -166px -104px;
      }
    }

  }
}
</style>

11.3 实现下拉菜单位置的第一项数据展示

image.png

<script setup>
import { useRoute } from 'vue-router';
import NavBar from './component/nav-bar.vue'
import useSearch from '@/stores/modules/search'
import { storeToRefs } from 'pinia';
import { ref, computed } from 'vue';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';

// 发送网络请求
const searchStore = useSearch()
searchStore.fetchSearchtopData()

const route = useRoute()
//=> nav
// 获取路由信息
const city = route.query.cityName
const title = `搜索${city}的景点、地标、房源`
// let clickName = route.query.clickName


//=> top
const { searchConditions } = storeToRefs(searchStore)

const positionRef = ref();
const sortRef = ref()
const filterRef = ref()

const activeIndex = ref(0)
const rightActiveIndex = ref()
const items = [
  { text: '热门推荐' },
  { text: '观光景点' },
  { text: '商圈' },
  { text: '行政区' },
  { text: '机场/车站' },
  { text: '高校' },
  { text: '医院' },
]
const positionData = computed(() => {
  return searchConditions.value[0]?.subGroups
})

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

let clickName = suggestName.value

// 计算clickName
const getclickName = computed(() => {
  if (suggestName.value === '') {
    return clickName = route.query.clickName
  }
  return clickName = suggestName.value
})

const near = ["1km内", "2km内", "3km内", "4km内", "5km内"]
const curNear = ref()
const nearClick = (index) => {
  curNear.value = index
}

// 点击选中并改变上面的初始值
const rightClick = (index, label) => {
  rightActiveIndex.value = index
  // 修改公共的值
  suggestName.value = label
  // 修改下拉菜单的值
  clickName = label
}

</script>

<template>
  <div class="search top-page">
    <!-- 导航栏 -->
    <nav-bar :title="title" :city="city" />

    <div class="top">
      <div class="top-condition">
        <van-dropdown-menu active-color="#ff9645">
          <van-dropdown-item :title="getclickName" ref="positionRef">
            <div class="content">
              <div class="near">
                <div class="title">{{ clickName }}的附近</div>
                <div class="text">
                  <template v-for="(item, index) in near" :key="index">
                    <div class="item" @click="nearClick(index)" :class="{ active: curNear === index }">{{ item }}</div>
                  </template>
                </div>
              </div>
              <van-tree-select v-model:main-active-index="activeIndex" height="95vw" :items="items">
                <template #content>
                  <!-- <van-image v-if="activeIndex === 0" src="https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg" />
                        <van-image v-if="activeIndex === 1" src="https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg" /> -->
                  <template v-for="(item, index) in positionData" :key="index">
                    <div class="right">
                      <template v-for="(iten, idx) in item.items" :key="idx">
                        <div class="right-content" @click="rightClick(idx, iten.label)"
                          :class="{ active: rightActiveIndex === idx }">
                          <div class="sub" :class="{ subactive: rightActiveIndex === idx }">{{ iten.label }}</div>
                          <div class="sup">{{ iten.percentageUser }}</div>
                        </div>
                      </template>
                    </div>
                  </template>
                </template>
              </van-tree-select>
            </div>
          </van-dropdown-item>

          <van-dropdown-item title="排序" ref="sortRef">
            1
          </van-dropdown-item>

          <van-dropdown-item title="筛选" ref="filterRef">
            1
          </van-dropdown-item>

        </van-dropdown-menu>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.search {
  height: 100vh;
  overflow-y: auto;

  .top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px 0 25px;
    margin-top: 50px;
    height: 44px;

    .top-condition {
      width: 100%;

      :deep(.van-dropdown-menu__bar) {
        display: flex;
        justify-content: space-between;
        box-shadow: none;
      }

      :deep(.van-dropdown-menu__title) {
        color: #333;
        font-size: 13px;
        font-weight: 500;
      }

      .content {

        .near {
          padding: 0 10px;
          color: #666;
          background: linear-gradient(#f2f3f4, #fff);
          padding-bottom: 10px;
          border-bottom: 1px solid #eee;

          .title {
            font-size: 14px;
            color: #333;
            line-height: 20px;
            margin-bottom: 10px;
            padding-top: 10px;
          }

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


            .item {
              padding: 5px 10px;
              background-color: #f2f3f4;
              border-radius: 15px;

              &.active {
                color: #ff9645;
                border: 1px solid #ff9645;
                background-color: #fffcf5;
                font-weight: 500;
              }
            }
          }
        }

        .right {
          margin: 0 20px 0 12px;

          .right-content {
            display: flex;
            align-items: flex-start;
            justify-content: center;
            flex-direction: column;
            height: 55px;
            padding: 0 10px 0 10px;
            font-size: 14px;

            &.active {
              font-weight: 500;
              background-color: #fffcf5;
            }

            .sub {
              color: #666;

              &.subactive {
                color: #ff9645;
              }
            }

            .sup {
              line-height: 20px;
              font-size: 11px;
              color: #999;
            }
          }
        }
      }
    }
  }
}
</style>

11.4 划分组件

  • search页面
<script setup>
import { useRoute } from 'vue-router';
import useSearch from '@/stores/modules/search'

import NavBar from './component/nav-bar.vue'
import DropdownSelect from './component/dropdown-select.vue'

// 发送网络请求
const searchStore = useSearch()
searchStore.fetchSearchtopData()

const route = useRoute()

// 获取路由信息
const city = route.query.cityName
const title = `搜索${city}的景点、地标、房源`

</script>

<template>
  <div class="search top-page">
    <!-- 导航栏 -->
    <nav-bar :title="title" :city="city" />
    <!-- select section -->
    <dropdown-select></dropdown-select>
  </div>
</template>

<style lang="less" scoped>
.search {
  height: 100vh;
  overflow-y: auto;
}
</style>
  • dropdown-select组件
<script setup>
import TopCondition from '@/components/top-condition/index.vue'
</script>

<template>
  <div class="top">
    <top-condition />
  </div>
</template>

<style lang="less" scoped>
.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px 0 25px;
  margin-top: 50px;
  height: 44px;
}
</style>
  • top-condition-content公共组件
<script setup>
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';
import { ref } from 'vue'

defineProps({
  activeIndex: {
    type: Number,
    default: 0
  },
  itemData: {
    type: Array,
    default: () => ([])
  }
})

const rightActiveIndex = ref()
const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

// let clickName = suggestName.value

// 点击选中并改变上面的初始值
const rightClick = (index, label) => {
  rightActiveIndex.value = index
  // 修改公共的值
  suggestName.value = label
  // 修改下拉菜单的值
  clickName = label
  // 点击后隐藏下拉框
  positionRef.value.toggle()
}
</script>

<template>
  <template v-if="activeIndex === activeIndex" v-for="(item, index) in itemData" :key="index">
    <div class="right">
      <template v-for="(iten, idx) in item.items" :key="idx">
        <div class="right-content" @click="rightClick(idx, iten.label)" :class="{ active: rightActiveIndex === idx }">
          <div class="sub" :class="{ subactive: rightActiveIndex === idx }">{{ iten.label }}</div>
          <div class="sup">{{ iten.percentageUser }}</div>
        </div>
      </template>
    </div>
  </template>
</template>

<style lang="less" scoped>
.right {
  margin: 0 20px 0 12px;

  .right-content {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    flex-direction: column;
    height: 55px;
    padding: 0 10px 0 10px;
    font-size: 14px;

    &.active {
      font-weight: 500;
      background-color: #fffcf5;
    }

    .sub {
      color: #666;

      &.subactive {
        color: #ff9645;
      }
    }

    .sup {
      line-height: 20px;
      font-size: 11px;
      color: #999;
    }
  }
}
</style>
  • top-condition公共组件
<script setup>
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia';
import { ref, computed } from 'vue';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import useSearch from '@/stores/modules/search';
import near from '@/components/near/index.vue';
import TopConditionContent from '@/components/top-condition-content/index.vue'

const searchStore = useSearch()
const { searchConditions } = storeToRefs(searchStore)

const positionRef = ref()
const sortRef = ref()
const filterRef = ref()

const activeIndex = ref(0)

const items = [
  { text: '热门推荐' },
  { text: '观光景点' },
  { text: '商圈' },
  { text: '行政区' },
  { text: '机场/车站' },
  { text: '高校' },
  { text: '医院' },
]
const positionData = computed(() => {
  return searchConditions.value[0]?.subGroups
})

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

let clickName = suggestName.value

// 计算clickName
const route = useRoute()
const getclickName = computed(() => {
  if (suggestName.value === '') {
    return clickName = route.query.clickName
  }
  return clickName = suggestName.value
})


</script>

<template>
  <div class="top-condition">
    <van-dropdown-menu active-color="#ff9645">
      <van-dropdown-item :title="getclickName" ref="positionRef">
        <div class="content">
          <near :click-name="clickName" />

          <van-tree-select v-model:main-active-index="activeIndex" height="95vw" :items="items">
            <template #content>
                <top-condition-content :active-index="0" :item-data="positionData"/>
            </template>
          </van-tree-select>
        </div>
      </van-dropdown-item>

      <van-dropdown-item title="排序" ref="sortRef">
        1
      </van-dropdown-item>

      <van-dropdown-item title="筛选" ref="filterRef">
        1
      </van-dropdown-item>

    </van-dropdown-menu>
  </div>
</template>

<style lang="less" scoped>
.top-condition {
  width: 100%;

  :deep(.van-dropdown-menu__bar) {
    display: flex;
    justify-content: space-between;
    box-shadow: none;
  }

  :deep(.van-dropdown-menu__title) {
    color: #333;
    font-size: 13px;
    font-weight: 500;
  }
}
</style>

11.5 完成位置功能,复用top-condition-content组件

image.png

  • top-condition
<script setup>
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia';
import { ref, computed } from 'vue';
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import useSearch from '@/stores/modules/search';
import near from '@/components/near/index.vue';
import TopConditionContent from '@/components/top-condition-content/index.vue'

const searchStore = useSearch()
const { searchConditions } = storeToRefs(searchStore)

const positionRef = ref()
const positionData = computed(() => {
  return searchConditions.value[0]?.subGroups
})

const activeIndex = ref(0)
const activeInnerIndex = ref(0)
const items = [
  { text: '热门推荐' },
  { text: '观光景点' },
  { text: '商圈' },
  { text: '行政区' },
  { text: '地铁线路' },
  { text: '机场/车站' },
  { text: '高校' },
  { text: '医院' },
]
const subwayItems = [
  { text: '飞机场' },
  { text: '火车站' },
  { text: '汽车站' },
]

const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

let clickName = suggestName.value

// 计算clickName
const route = useRoute()
const getclickName = computed(() => {
  if (suggestName.value === '') {
    if (route.query.clickName === '') {
      return '位置'
    }
    return clickName = route.query.clickName
  }
  return clickName = suggestName.value
})

const sortRef = ref()
const filterRef = ref()
// const active = ref(0);
</script>

<template>
  <div class="top-condition">
    <van-dropdown-menu active-color="#ff9645">
      <van-dropdown-item :title="getclickName" ref="positionRef">
        <div class="content">
          <near :click-name="clickName" />

          <van-tree-select v-model:main-active-index="activeIndex" height="100vw" :items="items">
            <template #content>
              <top-condition-content :active-index="activeIndex === 0" :item-data="positionData[0].items"
                :position-ref="positionRef" />
              <top-condition-content :active-index="activeIndex === 1" :item-data="positionData[1].items"
                :position-ref="positionRef" />
              <top-condition-content :active-index="activeIndex === 2" :item-data="positionData[2].items"
                :position-ref="positionRef" />
              <top-condition-content :active-index="activeIndex === 3" :item-data="positionData[3].items"
                :position-ref="positionRef" />
              <top-condition-content :active-index="activeIndex === 4" :item-data="positionData[4].subGroups"
                :position-ref="positionRef" />

              <van-tree-select v-model:main-active-index="activeInnerIndex" height="100vw" :items="subwayItems"
                v-if="activeIndex === 5">
                <template #content>
                  <top-condition-content :active-index="activeInnerIndex === 0"
                    :item-data="positionData[5].subGroups[0].items" :position-ref="positionRef" />

                  <top-condition-content :active-index="activeInnerIndex === 1"
                    :item-data="positionData[5].subGroups[1].items" :position-ref="positionRef" />

                  <top-condition-content :active-index="activeInnerIndex === 2"
                    :item-data="positionData[5].subGroups[2].items" :position-ref="positionRef" />
                </template>
              </van-tree-select>

              <top-condition-content :active-index="activeIndex === 6" :item-data="positionData[6].items"
                :position-ref="positionRef" />
              <top-condition-content :active-index="activeIndex === 7" :item-data="positionData[7].items"
                :position-ref="positionRef" />

            </template>
          </van-tree-select>
        </div>
      </van-dropdown-item>

      <van-dropdown-item title="排序" ref="sortRef">
        1
      </van-dropdown-item>

      <van-dropdown-item title="筛选" ref="filterRef">
        1
      </van-dropdown-item>

    </van-dropdown-menu>
  </div>
</template>

<style lang="less" scoped>
.top-condition {
  width: 100%;

  :deep(.van-dropdown-menu__bar) {
    display: flex;
    justify-content: space-between;
    box-shadow: none;
  }

  :deep(.van-dropdown-menu__title) {
    color: #333;
    font-size: 13px;
    font-weight: 500;
  }
}
</style>
  • top-condition-content
<script setup>
import usekeywordsuggests from '@/stores/modules/keyword-suggests';
import { storeToRefs } from 'pinia';
import { ref } from 'vue'

const props = defineProps({
  activeIndex: {
    type: Boolean,
    default: true
  },
  itemData: {
    type: Array,
    default: () => ([])
  },
  clickName: {
    type: String,
    default: ''
  },
  positionRef: {
    type: Object,
    default: () => ({})
  }
})

const rightActiveIndex = ref()
const keywordsuggestsStore = usekeywordsuggests()
const { suggestName } = storeToRefs(keywordsuggestsStore)

let clickName = suggestName.value

// 点击选中并改变上面的初始值
const rightClick = (index, label) => {
  rightActiveIndex.value = index
  // 修改公共的值
  suggestName.value = label
  // 修改下拉菜单的值
  // props.clickName = label
  clickName = label
  // 点击后隐藏下拉框
  props.positionRef.toggle()
}
</script>

<template>
  <div class="right">
    <template v-for="(item, index) in itemData" :key="index">
      <div class="right-content" @click="rightClick(index, item.label)" :class="{ active: rightActiveIndex === index }"
        v-if="activeIndex">
        <div class="sub" :class="{ subactive: rightActiveIndex === index }">{{ item.label }}</div>
        <div class="sup" v-if="item.percentageUser">{{ item.percentageUser }}</div>
      </div>
    </template>
  </div>
</template>

<style lang="less" scoped>
.right {
  margin: 0 20px 0 12px;

  .right-content {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    flex-direction: column;
    height: 55px;
    padding: 0 10px 0 10px;
    font-size: 14px;

    &.active {
      font-weight: 500;
      background-color: #fffcf5;
    }

    .sub {
      color: #666;

      &.subactive {
        color: #ff9645;
      }
    }

    .sup {
      line-height: 20px;
      font-size: 11px;
      color: #999;
    }
  }
}
</style>

11.6 提取组件

把位置的代码提取组件

  • top-position-treeselect
<script setup>
import useSearch from '@/stores/modules/search';
import { storeToRefs } from 'pinia';
import { ref, computed } from 'vue';
import TopConditionContent from '@/components/top-condition-content/index.vue'

defineProps({
  positionRef: {
    type: Object,
    default: () => ({})
  }
})

const searchStore = useSearch()
const { searchConditions } = storeToRefs(searchStore)

const positionData = computed(() => {
  return searchConditions.value[0]?.subGroups
})

const activeIndex = ref(0)
const activeInnerIndex = ref(0)
const items = [
  { text: '热门推荐' },
  { text: '观光景点' },
  { text: '商圈' },
  { text: '行政区' },
  { text: '地铁线路' },
  { text: '机场/车站' },
  { text: '高校' },
  { text: '医院' },
]
const subwayItems = [
  { text: '飞机场' },
  { text: '火车站' },
  { text: '汽车站' },
]
</script>

<template>
  <van-tree-select v-model:main-active-index="activeIndex" height="100vw" :items="items">
    <template #content>
      <top-condition-content :active-index="activeIndex === 0" :item-data="positionData[0].items"
        :position-ref="positionRef" />
      <top-condition-content :active-index="activeIndex === 1" :item-data="positionData[1].items"
        :position-ref="positionRef" />
      <top-condition-content :active-index="activeIndex === 2" :item-data="positionData[2].items"
        :position-ref="positionRef" />
      <top-condition-content :active-index="activeIndex === 3" :item-data="positionData[3].items"
        :position-ref="positionRef" />
      <top-condition-content :active-index="activeIndex === 4" :item-data="positionData[4].subGroups"
        :position-ref="positionRef" />

      <van-tree-select v-model:main-active-index="activeInnerIndex" height="100vw" :items="subwayItems"
        v-if="activeIndex === 5">
        <template #content>
          <top-condition-content :active-index="activeInnerIndex === 0" :item-data="positionData[5].subGroups[0].items"
            :position-ref="positionRef" />

          <top-condition-content :active-index="activeInnerIndex === 1" :item-data="positionData[5].subGroups[1].items"
            :position-ref="positionRef" />

          <top-condition-content :active-index="activeInnerIndex === 2" :item-data="positionData[5].subGroups[2].items"
            :position-ref="positionRef" />
        </template>
      </van-tree-select>

      <top-condition-content :active-index="activeIndex === 6" :item-data="positionData[6].items"
        :position-ref="positionRef" />
      <top-condition-content :active-index="activeIndex === 7" :item-data="positionData[7].items"
        :position-ref="positionRef" />

    </template>
  </van-tree-select>
</template>

<style lang="less" scoped></style>
  • top-condition使用组件
import TopPositionTreeselect from '@/components/top-position-treeselect.vue/index.vue'

<top-position-treeselect :position-ref="positionRef"/>

11.7 排序

  • top-sort
<script setup>
import { ref } from 'vue'
const props = defineProps({
  itemData: {
    type: Array,
    default: () => ([])
  },
})

const sortRef = ref()
const curIndex = ref(0)
const title = ref('欢迎度排序')
const sortItemClick = (label, index) => {
  title.value = label
  curIndex.value = index
  sortRef.value.toggle()
}
</script>

<template>
  <van-dropdown-item :title="title" ref="sortRef">
    <div class="sort-section">
      <template v-for="(item, index) in itemData">
        <div class="item" @click="sortItemClick(item.label, index)" :class="{ active: curIndex === index }">
          <div class="text">{{ item.label }}</div>
          <van-icon name="success" />
        </div>
      </template>
    </div>
  </van-dropdown-item>
</template>

<style lang="less" scoped>
.sort-section {
  padding: 0 10px;
  background-color: #fff;

  .item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 50px;
    padding: 0 20px;
    font-size: 14px;
    color: #666;

    &.active {
      background-color: rgba(255, 176, 0, 0.04);
      color: #ff9645;
    }
  }
}
</style>
  • top-condition使用
import TopSort from '@/components/top-sort/index.vue'

<top-sort :item-data="searchConditions[1]?.items" />

11.8 筛选