1. 颜色渐变
使用 background-image
background-image: linear-gradient(90deg, #fa8c1d, #fcaf3f);
效果:
路由携带参数:
2. 设置横向滚动
CSS3 overflow-x 属性
如果它溢出了元素的内容区-剪辑div元素的左/右边缘内容:
语法:overflow-x: visible|hidden|scroll|auto|no-display|no-content;
| 值 | 描述 |
|---|---|
| visible | 不裁剪内容,可能会显示在内容框之外。 |
| hidden | 裁剪内容 - 不提供滚动机制。 |
| scroll | 裁剪内容 - 提供滚动机制。 |
| auto | 如果溢出框,则应该提供滚动机制。 |
| no-display | 如果内容不适合内容框,则删除整个框。 |
| no-content | 如果内容不适合内容框,则隐藏整个内容。 |
3. flex-shrink
flex-shrink 属性指定了 flex 元素的收缩规则,默认值是1。在flex 元素的默认宽度之和大于容器的宽度时候,元素会发生收缩,其收缩的大小的依据是 flex-shrink 值。
4.隐藏横向的滚动条
5. 多页数据如何请求
5.1 如何监听页面滚动到底部
- 页面什么时候会发生滚动?
- windows窗口滚动
- 还是元素:
overflow-y:auto
document.documentElement.scrollTop文档滚动距离- 当文档滚动距离加上客户端高度大于等于可滚动的距离,页面发生滚动
clientHeight+scrollTop>=scrollHeight。
// 监听window创建的滚动
window.addEventListener("scroll", () => {
// 当前客户端的高度
const clientHeight = document.documentElement.clientHeight;
// 文档滚动距离
const scrollTop = document.documentElement.scrollTop;
// 文档可以滚动的区域高度
const scrollHeight = document.documentElement.scrollHeight;
console.log(clientHeight,scrollTop, scrollHeight);
})
- 最终实现思路
5. 优化请求,封装hooks,多个页面需要用到滚动请求数据,所以需要抽取出来。
- 监听window创建的滚动
- 当我们离开页面时,需要移除监听
- 别的页面也进行类似的监听,会重复编写代码
思路一:传入回调函数
import { onMounted, onUnmounted, onActivated } from 'vue'
export default function useScroll(reachBottomCallBack) {
// 监听window创建的滚动
// 1. 当我们离开页面时,需要移除监听
// 2. 别的页面也进行类似的监听,会重复编写代码
const scrollListenerHandler = () => {
// 当前客户端的高度
const clientHeight = document.documentElement.clientHeight;
// 文档滚动距离
const scrollTop = document.documentElement.scrollTop;
// 文档可以滚动的区域高度
const scrollHeight = document.documentElement.scrollHeight;
if (clientHeight + Math.ceil(scrollTop) >= scrollHeight) {
console.log("滚动到底部了");
if (reachBottomCallBack) {
reachBottomCallBack();
}
}
}
// 挂载时,添加监听
onMounted(() => {
window.addEventListener("scroll", scrollListenerHandler)
});
// 活跃时
onActivated(() => {
window.addEventListener("scroll", scrollListenerHandler)
})
// 卸载时,移除监听
onUnmounted(() => {
window.removeEventListener("scroll", scrollListenerHandler)
})
}
思路二:监控变量,更好的管理hooks内部的状态。
6. 页面文档滚动距离,显示搜索栏
思路一:监听文档的滚动
思路二:计算属性
// 直接使用计算属性,大于等于100为true
// 计算属性,依赖于响应式的数据,数据一旦发生变化,就会重新计算。
const isShowBar = computed(() => {
return scrollTop.value >= 100
})
7. 页面函数执行频率太高,防抖,节流
- 防抖:在执行事件时,又触发了这个事件,把这个事件不断地向后延后。如果一直有执行,则会一直向后延后。
- 节流:在单位的时间之内,执行函数的时间非常频繁,但是在规定的时间里面,只执行函数一次。
7.1 underscore使用第三方库
- 安装
npm install underscore
- 引入使用
8. tree shaking
-
像webpack,vite,只会把你引入的包做一个打包,没有引入的不会做一个打包。
-
Tree Shaking 指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。
-
官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)
-
在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
9. 监听路由改变,找到对应的索引
10.加载过渡动画
思路:
- 将loading组件放在app.vue中
- loading组件使用v-if,通过判断store中isloading的状态,进行判断
- loading的状态是通过各个组件发送网络请求的时候进行更改
- 在每次发送网络请求的时候,都需要将isloading的状态设置为true,结束的时候,设置为false。
- 思路一(不推荐,容易造成逻辑混乱)
- 思路二:
11. 监听单个item的点击
- 给组件绑定点击事件,传递data的值。逻辑如下图所示
2. 写组件,配置路由,传递参数
12 .集成百度地图
- 登录百度地图开放平台,填写资料,进行开发者认证。
- 认证成功后,显示下面的界面。
- 创建应用
4. 类型选择
5. 白名单
6. 提交创建成功
7. 添加API文件
<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=您的密钥">
</script>
- 代码思路
实现效果:
完整代码:
<template>
<div class="map">
<detail-section title="位置周边" more-text="查看更多周边信息">
<div class="baidu" ref="mapRef"></div>
</detail-section>
</div>
</template>
<script setup>
import DetailSection from "@/components/detail-section/detail-section.vue";
import { onMounted, ref } from "vue";
const props = defineProps({
position: {
type: Object,
default: () => ({})
}
})
const mapRef = ref()
// 放在onMounted里面确保生命,容器元素存在
onMounted(() => {
const map = new BMapGL.Map(mapRef.value); // 创建地图实例
const point = new BMapGL.Point(props.position.longitude, props.position.latitude); // 创建点坐标,经纬度
map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和15表示地图级别
const marker = new BMapGL.Marker(point); // 创建标注
map.addOverlay(marker); // 将标注添加到地图中
})
</script>
<style lang="less" scoped>
.baidu {
height: 300px;
}
</style>
13. 顶部tab的集成,页面元素的滚动
界面滚动到一定位置显示导航
实现思路:根据当前滚动的距离来控制tab的显示和隐藏。
hooks useScroll函数:
import { onMounted, onUnmounted, onActivated, ref } from 'vue'
import { throttle } from 'underscore'
export default function useScroll(elRef) {
let el = window;//设置默认为window
// 通过变量判断是否达到底部
const clientHeight = ref(0)
const isReachBottom = ref(false);
const scrollTop = ref(0);
const scrollHeight = ref(0)
// 监听window创建的滚动
// 1. 当我们离开页面时,需要移除监听
// 2. 别的页面也进行类似的监听,会重复编写代码
// 函数执行频繁,采用防抖,节流
const scrollListenerHandler = throttle(() => {
// 当前滚动的是浏览器
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( clientHeight.value, scrollTop.value, scrollHeight.value);
}
if (clientHeight.value + Math.ceil(scrollTop.value) >= scrollHeight.value) {
console.log("滚动到底部了");
isReachBottom.value = true;
}
}, 100)//间隔100毫秒
// 挂载时,添加监听
onMounted(() => {
if (elRef) {
el = elRef.value;
}
el.addEventListener("scroll", scrollListenerHandler)
});
// 活跃时
onActivated(() => {
el.addEventListener("scroll", scrollListenerHandler)
})
// 卸载时,移除监听
onUnmounted(() => {
el.removeEventListener("scroll", scrollListenerHandler)
})
// 将变量返回出去
return { isReachBottom, scrollTop, scrollHeight };
}
14. tab点击到达对应位置
实现效果
14.1方法一:
注意问题:页面每次滚动的时候,会引起页面的刷新,使用v-memo性能优化,数据变化的的时候,页面才会刷新。
实现思路:
实现代码如下:
子组件:
<template>
<div class="tab-control">
<template v-for="(item, index) in titles" :key="index">
<div class="tab-control-item" :class="{ active: currentIndex == index }" @click="itemClick(index)">
<!-- 2.子组件的值传递给父组件 -->
<slot :itemsss="item" abc="cba">
<!-- 1.如果要将item传递到外部元素所在,使用作用插槽-->
<span> {{ item }}</span>
</slot>
</div>
</template>
</div>
</template>
<script>
export default {
emits: ['itemClick'],
props: {
titles: {
type: Array,
default: () => []
}
},
data() {
return {
currentIndex: 0,
}
},
methods: {
itemClick(index) {
// console.log(index);
this.currentIndex = index;
this.$emit("itemClick", index);
}
}
}
</script>
<style scoped>
.tab-control {
display: flex;
height: 44px;
line-height: 44px;
text-align: center;
}
.tab-control-item {
flex: 1;
}
.tab-control-item.active {
color: var(--primary-color);
font-weight: 700;
}
.tab-control-item.active span {
border-bottom: 2px solid #ff9854;
padding: 8px;
}
</style>
父组件:
<template>
<div class="detail hidetabbar" ref="detailRef">
<tab-control class="tabs" v-if="showTabControl" :titles="['描述', '设施', '房东', '评论', '须知', '地图']"
@itemClick="tabClick" />
<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 :ref="getSectionRef" :top-infos="mainPart.topModule"></detail-infos>
<detail-facility :ref="getSectionRef"
:house-facility="mainPart.dynamicModule.facilityModule.houseFacility"></detail-facility>
<detail-landord :ref="getSectionRef" name="房东"
:landlord="mainPart.dynamicModule.landlordModule"></detail-landord>
<detail-comment :ref="getSectionRef" name="评论" :comment="mainPart.dynamicModule.commentModule" />
<detail-notice :ref="getSectionRef" name="须知" :order-rules="mainPart.dynamicModule.rulesModule.orderRules" />
<detail-map :ref="getSectionRef" :position="mainPart.dynamicModule.positionModule"></detail-map>
<DetilInfo :price-intro="mainPart.introductionModule"></DetilInfo>
</div>
<div class="footer">
<img src="@/assets/img/detail/icon_ensure.png" alt="">
<div class="text">探寻自己的旅途</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router'
import { getDetailInfo } from '@/service/index'
import useScroll from '@/hooks/useScroll'
import TabControl from '@/components/tab-control/tab-control.vue'
import DetailSwipe from './cpns/detail-01-swipe.vue'
import DetailInfos from './cpns/detail-02-infos.vue';
import DetailFacility from './cpns/detail-03-facility.vue';
import DetailLandord from './cpns/detail-04-landord.vue';
import DetailComment from './cpns/detail-05-comment.vue';
import DetailNotice from './cpns/detail-06-notice.vue';
import DetailMap from './cpns/detail-07-map.vue';
import DetilInfo from './cpns/detail-08-intro.vue';
const router = useRouter()
const route = useRoute();
const houseId = route.params.id
// 这里采用在组件中发送网络请求
const detailInfos = ref({})
const mainPart = computed(() => detailInfos.value.mainPart)
getDetailInfo(houseId).then(res => {
detailInfos.value = res.data;
})
const sectionEls = [];
// ref绑定函数,每次绑定就会执行函数
const getSectionRef = (value) => {
console.log("----");
sectionEls.push(value?.$el)
// console.log(value.$el);
// const name = value.$el.getAttribute("name")
// sectionEls.value[name] = value.$el
}
// 监听返回按钮的点击
const onClickLeft = () => {
router.back();
}
// tabControl相关的操作,showTabControl写成计算属性
const detailRef = ref();
const { scrollTop } = useScroll(detailRef)
const showTabControl = computed(() => {
return scrollTop.value >= 300;
});
const tabClick = (index) => {
console.log("detailRef", detailRef.value.scrollTo);
let instance = sectionEls[index].offsetTop;
if (index != 0) {
instance = instance - 44;
}
detailRef.value.scrollTo({
top: instance,
behavior: 'smooth',//平滑滚动
})
}
</script>
<style lang="less" scoped>
.tabs {
position: fixed;
background-color: #fff;
z-index: 9;
left: 0;
right: 0;
top: 0;
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 120px;
img {
width: 123px;
}
.text {
margin-top: 12px;
font-size: 12px;
color: #7688a7;
}
}
</style>
14.2 方法二
- 创建一个对象,用来存储点击元素和 跳转对应元素的对象
- 动态绑定ref,动态获取点击时的组件元素。
实现思路:
ps:Object.keys()的作用
let person = {name:"张三",age:25,address:"深圳",getName:function(){}}
Object.keys(person) // ["name", "age", "address","getName"]