vue3搭建实战项目笔记四

发布于:2025-05-24 ⋅ 阅读:(18) ⋅ 点赞:(0)

5.1.动态绑定ref,销毁页面时拿到值为空

```javascript
  // 找到对应元素的根元素
  const getSectionRef = (value) => {
      console.log('value===', value);
      // 在页面卸载时,元素是空的为null,所以判断下是否为空,为空直接返回出去
      if(!value) return
    // 1.在滚动时,会引起dom的刷新,导致sectionEls会被重新执行,会有多个sectionEls
    // 2.使用v-memo,可以解决这个问题,缓存一个模板的子树,当数据变化时才会刷新
    // 3.value拿到的是组件实例对象,想要拿到组件对象的根元素,怎么拿到组件对象的根元素.$el
    const name = value.$el.getAttribute('name')
    names.push(name)
    sectionEls[name] = value.$el
  }
```

5.2. 页面滚动,显示正确的tabControl的标题

    1. 需求: 页面滚动,滚动到一定位置,显示正确的tabControl的索引(标题)

    2. 监听滚动的位置 scrollTop
      * scrollTop: 600

    3. 利用scrollTop去匹配正确的位置
      offsetTop
      [描述300, 设施500, 房东800, 评论1200, 须知1400, 周边1600]

      const values = [300, 500, 800, 1200, 1400, 1600]
    
      let index = values.length - 1
      for(let i = 0; i < values.length; i++) {
        const value = values[i]
        if (value > scrollTop) {
          index = i - 1
          break;
        }
      }
      console.log(index)
    
    1. 关键代码如下:
      <template>
        <div class="detail top-page" ref="detailRef">
          <van-nav-bar
            title="房屋详情"
            left-text="返回"
            left-arrow
            @click-left="onClickLeft"
          />
          <tab-control
            ref="tabControlRef"
            class="tabs"
            v-if="showTabControl"
            :titles="names"
            @tabItemClick="tabClick"
          />
          <!-- 内容部分 -->
          <div class="main" v-if="mainPart" v-memo="[mainPart]">
            <!-- 轮播组件 -->
            <detail-swipe :swipe-data="mainPart.topModule.housePicture.housePics" />
            <!-- 动态绑定Ref:在处理复杂组件结构和动态数据时通过动态绑定Ref,我们可以更灵活地访问和操作DOM元素或组件实例,实现更高效的交互和状态管理 -->
            <detail-infos name="描述" :ref="getSectionRef" :topInfos="mainPart.topModule"/>
            <detail-facility name="设施"  :ref="getSectionRef" :house-facility="mainPart.dynamicModule.facilityModule.houseFacility"/>
            <!-- :landload="mainPart.dynamicModule.landloadModule.houseLandload" -->
            <detail-landlord name="房东" :ref="getSectionRef" :landlord="mainPart.dynamicModule.landlordModule"/>
            <detail-comment name="评论" :ref="getSectionRef" :comment="mainPart.dynamicModule.commentModule"/>
            <detail-notice name="须知" :ref="getSectionRef" :order-rules="mainPart.dynamicModule.rulesModule.orderRules"/>
            <detail-map name="周边" :ref="getSectionRef" :position="mainPart.dynamicModule.positionModule" />
            <detail-intro :priceIntro="mainPart.introductionModule"/>
          </div>
          <div class="footer">
            <img src="@/assets/img/detail/icon_ensure.png" alt="">
            <div class="text">弘源旅途, 永无止境!</div>
          </div>
        </div>
      </template>
    
     <script></script>
      const detailRef = ref(null)
      const { scrollTop } = useScroll(detailRef)
    
      const sectionEls = ref({})
      const names = []
    
      // 找到对应元素的根元素
      const getSectionRef = (value) => {
          // 在页面卸载时,元素是空的为null,所以判断下是否为空,为空直接返回出去
          if(!value) return
        // 1.在滚动时,会引起dom的刷新,导致sectionEls会被重新执行,会有多个sectionEls
        // 2.使用v-memo,可以解决这个问题,缓存一个模板的子树,当数据变化时才会刷新
        // 3.value拿到的是组件实例对象,想要拿到组件对象的根元素,怎么拿到组件对象的根元素.$el
        const name = value.$el.getAttribute('name')
        names.push(name)
        sectionEls.value[name] = value.$el
      }
    
      watch(scrollTop, (newVal) => {
        // 1.获取所有滚动区域的offsetTop
        const els = Object.values(sectionEls.value)
        const values = els.map(el => el.offsetTop)
    
        console.log('values===2',values);
        // 2.根据newValue去匹配想要 的索引
        // index默认值为最后一个元素的索引
        let index = values.length -1
        for(let i = 0; i < values.length; i++) {
          if (values[i] > newVal + 44) {
            index = i - 1;
            break;
          }
        }
        if(index === -1) {
          index = 0
        }
        if(tabControlRef.value?.setCurrentIndex ===  index) {
          return
        }
        tabControlRef.value?.setCurrentIndex(index)
      })
    
    1. 点击时tabControl的标题是会挨个移动端最终点击的标题上
      - 4.1. 使用个变量记录当前的是否在点击
      - 4.2. 记录当前点击后滚动的距离,当监听scrollTop时,他的值等于点击后的滚动距离重置是否点击
    ```javascript
        // 当点击时,不需要去监听滚动挨个匹配位置
        // 当通过点击时,
        let isClick = false
        // 记录当前滚动的位置
        let currentDistance = -1
        // 当点击时滚动到对应位置
        const tabClick = (index) => {
          const key = Object.keys(sectionEls.value)[index]
          const el = sectionEls.value[key]
          let distance = el.offsetTop
          if(index !== 0) {
            // 滚动距离减去 44,因为tabControl组件的高度为44,会遮挡模块的标题
            distance = distance - 44
          }
          // 当点击时将isClick设置为true,将当前的滚动位置保存的currentDistance中
          isClick = true
          currentDistance = distance
          // scrollTo 使界面滚动到给定元素的指定位置
          detailRef.value.scrollTo({
            top: distance,
            behavior: 'smooth' // 滚动行为 smooth: 平滑滚动
          })
        }
    
    
        const tabControlRef = ref(null)
    
        // 页面滚动,滚动时匹配对应的tabControl的index
        watch(scrollTop, (newVal) => {
          // 当滚动到指定位置时,将isClick设置为false
          if(newVal === currentDistance) {
            isClick = false
          }
          // 当点击时,不需要去监听滚动挨个匹配位置
          if(isClick) return
    
          // 1.获取所有滚动区域的offsetTop
          const els = Object.values(sectionEls.value)
          const values = els.map(el => el.offsetTop)
    
          console.log('values===2',values);
          // 2.根据newValue去匹配想要 的索引
          // index默认值为最后一个元素的索引
          let index = values.length -1
          for(let i = 0; i < values.length; i++) {
            if (values[i] > newVal + 44) {
              index = i - 1;
              break;
            }
          }
          if(index === -1) {
            index = 0
          }
          if(tabControlRef.value?.setCurrentIndex ===  index) {
            return
          }
          tabControlRef.value?.setCurrentIndex(index)
        })
    ```
    

5.3. 首页缓存,并记录滚动位置

    1. 在App页面使用keep-alive缓存首页
      <template>
        <div class="app">
          <!-- 在首页页面的时候希望缓存首页 -->
          <router-view v-slot="props">
          <!-- 通过name属性匹配缓存页面,组件的name属性 -->
            <keep-alive include="home">
              <component :is="props.Component"></component>
            </keep-alive>
          </router-view>
        </div>
      </template>
    
    1. 找到首页页面在script标签上添加name属性
      <script setup name="home">  </script>
      ```
    
    1. 首页的window滚动方式改为页面滚动
    • 3.1. 将home页面中的根元素改为可以滚动
        .home {
          height: 100vh;
          overflow-y: auto;
          box-sizing: border-box;
        }
      
    • 3.2. 设置ref, 并将ref元素传到useScroll中更改为页面滚动
        <template>
          <div class="home" ref="homeRef">
            ...
          </div>
        </template>
      
        
        <script setup name="home">
          import { ref, onMounted, watch } from 'vue'
      
          const homeRef = ref(null)
          // 拿到isReachBottom,然后监听isReachBottom为真的时候滚动到底部
          const { isReachBottom, scrollTop } = useScroll(homeRef)
          watch(isReachBottom, (newValue) => {
            if(newValue) {
              homeStore.fetchHouseListData().then(() => {
                // 拿到数据后重置isReachBottom为false
                isReachBottom.value = false
              })
            }
          })
        </script>
      
      
    1. 记录滚动位置
      // 跳转回原来的位置时,保留原来的位置
      onActivated(() => {
        homeRef.value?.scrollTo({
          top: scrollTop.value
        })
      })
    
    1. 完整代码如下:
      <template>
        <div class="home" ref="homeRef">
          <home-nav-bar/>
          <div class="banner">
            <img src="@/assets/img/home/banner.webp" alt="">
          </div>
          <home-search-box></home-search-box>
          <home-categories></home-categories>
    
          <div v-if="isShowSearchBar" class="search-bar">
            <searchBar :start-date="'03-27'" :end-date="'03-28'"/>
          </div>
          <home-content></home-content>
        </div>
      </template>
    
      <script setup name="home">
      import HomeNavBar from './cpns/home-nav-bar.vue'
      import homeSearchBox from './cpns/home-search-box.vue';
      // import hyRequest from '@/service/request/index.js'
      import useHomeStore from '@/stores/modules/home';
      import homeCategories from './cpns/home-categories.vue';
      import homeContent from './cpns/home-content.vue';
      import searchBar from '@/components/search-bar/search-bar.vue';
    
      import useScroll from '@/hooks/useScroll';
      import { watch, ref, computed, onActivated } from 'vue';
    
      // 发送网络请求
      const homeStore = useHomeStore()
      homeStore.fetchHotSuggestsData()
      homeStore.fetchCategoriesData()
      homeStore.fetchHouseListData()
    
    
    
      const homeRef = ref(null)
      // 拿到isReachBottom,然后监听isReachBottom为真的时候滚动到底部
      const { isReachBottom, scrollTop } = useScroll(homeRef)
    
      // 当监听到变化的时候去执行另外一个js代码,不用计算属性,而是用watch监听,在回调函数里面写自己的逻辑
      watch(isReachBottom, (newValue) => {
        if(newValue) {
          homeStore.fetchHouseListData().then(() => {
            // 拿到数据后重置isReachBottom为false
            isReachBottom.value = false
          })
        }
      })
    
    
      const isShowSearchBar = computed(() => {
        return scrollTop.value >= 360
      })
    
    
      // 跳转回原来的位置时,保留原来的位置
      onActivated(() => {
        homeRef.value?.scrollTo({
          top: scrollTop.value
        })
      })
    
      </script>
    
      <style lang="less" scoped>
      .home {
        height: 100vh;
        overflow-y: auto;
        box-sizing: border-box;
        padding-bottom: 60px;
        .banner{
          img {
            width: 100%;
          }
        }
    
        .search-bar {
          position: fixed;
          z-index: 9;
          top: 0;
          left: 0;
          right: 0;
          height: 45px;
          padding: 16px 16px 10px;
          background-color: #fff;
        }
    
      }
      </style>
    

5.4. 移动端禁止缩放,vite中将项目中所有px单位转为vw单位

    1. 移动端禁止缩放,在index.html中操作meta标签的content属性
     <!DOCTYPE html>
     <html lang="">
       <head>
         <meta charset="UTF-8">
         <link rel="icon" href="/favicon.ico">
         <!-- 禁止缩放页面 -->
         <!-- 
             width=device-width  设置页面的宽度与设备的屏幕宽度相同,确保网页在移动设备上能正确显示。
             initial-scale=1.0   设置网页在移动设备上的初始缩放比例
             maximum-scale=1.0   设置网页在移动设备上的最大缩放比例
             minimum-scale=1.0   设置网页在移动设备上的最小缩放比例
             user-scalable=no    禁用用户缩放功能
         -->
         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, 
         minimum-scale=1.0, user-scalable=no">
         <title>弘源旅途</title>
       </head>
       <body>
         <div id="app"></div>
         <script type="module" src="/src/main.js"></script>
         <script type="text/javascript" src="https://api.map.baidu.com/getscript?v=3.0&&type=webgl&ak=kMX8CLJhpvH1KA8q0cN5eidPalGwvjzi">
     </script>
       </body>
     </html>
    
    1. 自动将px单位转换成vw单位
    • 在这里插入图片描述

    • Vite/Webpack -> postcss工具 -> plugins -> postcss-px-to-viewport

    • Vant 默认使用 px 作为样式单位,如果需要使用 viewport 单位 (vw, vh, vmin, vmax),推荐使用
      postcss-px-to-viewport进行转换。

    • postcss-px-to-viewport 是一款 PostCSS 插件,用于将 px 单位转化为 vw/vh 单位。

    1. 安装postcss-px-to-viewport
      npm install postcss-px-to-viewport -D
    1. 根目录下建立postcss.config.cjs文件进行配置
        // postcss.config.cjs
        module.exports = {
          plugins: {
            'postcss-px-to-viewport': {
              viewportWidth: 375, // 设计稿的宽度
              selectorBlackList: ['favor'] // 设置黑名单,不转换的页面组件
            },
          },
        };
    
    1. postcss-px-to-viewport官方中文地址
  • 在这里插入图片描述

5.5. 项目的部署发布和访问的流程

    1. 首先打包项目: npm run build
    1. 打包后的文件会生成在dist文件夹中,将dist文件夹需要放到服务器里面,意味着服务有dist静态资源(index.html,css/,js/,img/
    1. 用户在浏览器输入域名(xxx.baidu.com)访问项目,然后经过域名解析,解析出来IP地址,通过IP地址找到服务器,服务器的话请求对应的资源
    1. 最先请求下来的是index.html, 把index.html下载下来,然后浏览器解析会index.html, 解析过程当中遇到谁加载谁(例如:遇到js加载js文件,遇到img加载img,遇到css加载css文件),然后把静态资源下载下来
    1. 最终渲染页面,用户就可以看到页面了(不管是PC端还是移动端)

5.6. 项目的两个问题:一个是keep-alive使用include, 一个是tab-bar组件销毁后导致activeIndex为0错误

    1. keep-alive使用include属性
      <router-view v-slot="props">
        <!-- 通过name属性匹配缓存页面,组件的name属性 -->
        <keep-alive include="home">
          <component :is="props.Component"></component>
        </keep-alive>
      </router-view>
    
    1. tab-bar组件销毁后导致activeIndex为0错误
    • 2.1. 产生原因:
      当点击message页面的时候,隐藏tabBar使用的是v-if会销毁掉dom,组件不存在了, 后面又把组件添加进来,相当于创建了一个新的tabBar,跟添加的时机有关系, 因为是新创建出来的,所以到时候watch这个监听还没来的及监听页面就已经跳过去了(watch监听可能不执行) 最后导致currentIndex值不对

    • 2.2. 解决方法:
      使用v-show解决
      html <tab-bar v-show="!route.meta.hideTabBar"/>

    • 2.3. 完整代码:

        ~ <template>
          <div class="app">
            <!-- 官方不推荐我们这样写,推荐插槽的方式来写 -->
            <!-- <keep-alive includes="home">
              <router-view></router-view>
            </keep-alive> -->
      
            <!-- 插槽的方式来写 -->
            <!-- 在首页页面的时候希望缓存首页 -->
            <router-view v-slot="props">
            <!-- 通过name属性匹配缓存页面,组件的name属性 -->
              <keep-alive include="home">
                <component :is="props.Component"></component>
              </keep-alive>
            </router-view>
            <!-- 
                当点击message页面的时候,隐藏tabBar使用的是v-if会销毁掉dom,组件不存在了,
                后面又把组件添加进来,相当于创建了一个新的tabBar,跟添加的时机有关系,
                因为是新创建出来的,所以到时候watch这个监听还没来的及监听页面就已经跳过去了(watch监听可能不执行)
                最后导致currentIndex值不对 
      
                使用v-show解决
                -->
            <!-- 根据路由元信息是否显示 tabBar -->
            <tab-bar v-show="!route.meta.hideTabBar"/>
            <loading />
          </div>
        </template>
      
        <script setup>
          import tabBar from '@/components/tab-bar/tab-bar.vue';
          import { useRoute } from 'vue-router';
          import Loading from '@/components/loading/loading.vue';
          // 当前活跃的路由对象
          const route = useRoute()
      
        </script>
      
        <style scoped>
      
        </style>
      

网站公告

今日签到

点亮在社区的每一天
去签到