Leanote's Blog
I love Leanote!
Toggle navigation
Leanote's Blog
Home
Chrome
Git
Linux
Windows
Others
工具大全
VsCode
Expo
Html
JavaScript
Npm
Node
Mock
React-Native
React
TypeScript
小程序
插件
正则
Dva
Ant-Design-React
Umi
Vue
Vux
Ant-Design-Vue
Http
Java
flutter
开发小工具
About Me
Archives
Tags
vue2模仿外卖平台电梯效果
2024-04-18 03:36:42
5
0
0
admin
## 组件 ```vue // ElevatorScroll.vue <template> <div class="elevator-container" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" ref="elevatorContainerRef" > <div class="section" v-for="(item, index) in items" :key="item.id" :ref="`section-${index}`" > <slot :item="item.list" :index="index"></slot> </div> <div v-if="showHint && consistentDirection" :class="['scroll-hint', hintPosition]" :style="{ opacity: hintOpacity }">{{ hintMessage }} </div> </div> </template> <script> export default { props: { items: { type: Array, required: true, }, activeSectionDate: { type: Number, default: 0, }, }, data() { return { startY: 0, currentY: 0, hasMoved: false, // 用于记录是否发生了有效滑动 hintOpacity: 0, showHint: false, hintMessage: '', hintPosition: 'hint-bottom', initialDirection: null, // 初始滑动方向 consistentDirection: true, // 用户滑动方向是否一致 } }, watch: { activeSectionDate(nv, ov) { if (nv !== undefined) { this.init(nv) } }, }, activated() { this.init() }, methods: { init(sectionDate) { let date = sectionDate let behavior = undefined if (sectionDate === undefined) { date = this.activeSectionDate behavior = 'auto' } this.$nextTick(() => this.scrollToSection(date, behavior)) }, updateHint(direction) { const distanceMoved = Math.abs(this.startY - this.currentY) this.hintOpacity = Math.min(distanceMoved / 10, 0.8) if (direction === 'up') { if (this.activeSectionDate < this.items.length - 1) { this.hintMessage = '上滑查看下一天菜单' this.showHint = true } else { this.hintMessage = '' } this.hintPosition = 'hint-bottom' } else { if (this.activeSectionDate > 0) { this.hintMessage = '下滑查看上一天菜单' this.showHint = true } else { this.hintMessage = '' } this.hintPosition = 'hint-top' } }, handleTouchStart(event) { this.startY = event.touches[0].clientY this.hintOpacity = 0 this.initialDirection = null this.consistentDirection = true this.hasMoved = false }, handleTouchMove(event) { this.currentY = event.touches[0].clientY if (!this.hasMoved) { const moveThreshold = 5 if (Math.abs(this.startY - this.currentY) > moveThreshold) { this.hasMoved = true } const direction = this.startY > this.currentY ? 'up' : 'down' if (this.initialDirection === null) { this.initialDirection = direction } else if (this.initialDirection !== direction) { this.consistentDirection = false return } // 获取当前部分的元素 const activeSectionDateElement = this.$refs[`section-${ this.activeSectionDate }`][0] const atTop = activeSectionDateElement.scrollTop <= 0 const atBottom = activeSectionDateElement.scrollTop + activeSectionDateElement.clientHeight >= activeSectionDateElement.scrollHeight - 1 // 减去1作为容忍值 if ((direction === 'down' && atTop) || (direction === 'up' && atBottom)) { this.updateHint(direction) } else { this.showHint = false } } }, handleTouchEnd() { if (this.hasMoved && this.consistentDirection) { // 计算滑动距离 const distanceMoved = Math.abs(this.startY - this.currentY) const threshold = 50 if (distanceMoved > threshold) { const direction = this.startY > this.currentY ? 'up' : 'down' const activeSectionDateElement = this.$refs[`section-${ this.activeSectionDate }`][0] const atTop = activeSectionDateElement.scrollTop <= 0 const atBottom = activeSectionDateElement.scrollTop + activeSectionDateElement.clientHeight >= activeSectionDateElement.scrollHeight - 1 if ((direction === 'up' && atBottom && this.activeSectionDate < this.items.length - 1) || (direction === 'down' && atTop && this.activeSectionDate > 0)) { direction === 'up' ? this.moveToNextSection() : this.moveToPreviousSection() } } } this.showHint = false this.hasMoved = false }, moveToNextSection() { if (this.activeSectionDate < this.items.length - 1) { this.$emit('update:activeSectionDate', this.activeSectionDate + 1) } }, moveToPreviousSection() { if (this.activeSectionDate > 0) { this.$emit('update:activeSectionDate', this.activeSectionDate - 1) } }, scrollToSection(index, behavior = 'smooth') { const refs = this.$refs[`section-${ index }`] || [] const section = refs[0] if (section) { this.$refs.elevatorContainerRef.scrollTo({ top: section.offsetTop, behavior, }) } }, }, } </script> <style lang="scss" scoped> .elevator-container { width: 100%; height: 100%; overflow: hidden; } .section { height: 100%; box-sizing: border-box; overflow: hidden; overflow-y: auto; &::-webkit-scrollbar { width: 8px; height: 8px; } &::-webkit-scrollbar-thumb { border-radius: 6px; } } .scroll-hint { position: fixed; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: #fff; padding: 10px; border-radius: 5px; font-size: 14px; z-index: 1000; transition: opacity 0.3s ease; } .hint-top { top: 20px; } .hint-bottom { bottom: 20px; } </style> ``` ## 不需要左边电梯楼层提示的使用 ```vue <template> <ElevatorScroll :items="items"> <template v-slot:default="{ item, index }"> <div :style="{height: '1000px', backgroundColor: !index ? 'red': 'blue'}"> <h2>{{ item.label }}</h2> <p>{{ item.content }}</p> </div> </template> </ElevatorScroll> </template> <script> import ElevatorScroll from '@/components/ElevatorScroll.vue' export default { components: { ElevatorScroll, }, data() { return { items: [ { label: '电梯1', content: '这里是电梯1的内容...' }, { label: '电梯2', content: '这里是电梯2的内容...' }, ], } }, } </script> ``` ## 显示左边电梯楼层 ```vue // CurrentWeekMenu.vue <template> <div class="CurrentWeekMenu_root"> <div class="week_box" ref="weekBoxRef"> <div v-for="(item, index) in weekList" :key="item.id" class="week-item_box" :id="`week-item_${item.id}`" :class="activeSectionDate === index ? 'active' : ''" @click="scrollToSection(index)"> <div class="week-item_name">{{ item.name }}</div> <div class="week-item_date">{{ item.date }}</div> </div> </div> <div class="food-menu_box"> <ElevatorScroll :items="thisWeek" :activeSectionDate.sync="activeSectionDate" ref="elevatorScrollRef"> <template v-slot:default="{ item: weekItem, index: weekIndex }"> <div class="food-menu-group_box" :id="'food-menu-group_' + weekIndex"> <div class="food-menu-item_box" v-for="(item, index) in weekItem" :key="item.id" :id="item.id" @click="orderedChange(item)" :class="itemGroupClass(item, index, weekItem)"> <div class="food-menu-item_img"> <van-image width="2.13333rem" height="1.52rem" fit="cover" :src="item.imgUrl" > <template v-slot:loading> <van-loading type="spinner" size="20"/> </template> <template v-slot:error>暂无图片</template> </van-image> </div> <div class="food-menu-item_cotent"> <div class="food-menu-item_cotent_title">{{ buttonTitle(item) }}-{{ weekIndex }}</div> <div class="food-menu-item_cotent_desc">{{ item.mealName }}</div> </div> <div class="check-box"> <van-checkbox icon-size="22px" :value="item.ordered" checked-color="#27AFE9"/> </div> </div> </div> </template> </ElevatorScroll> </div> </div> </template> <script> import { getWeekList } from '../util' import throttle from 'lodash/throttle' import { mapState } from 'vuex' import { v4 as uuidv4 } from 'uuid' import ElevatorScroll from '@/components/ElevatorScroll.vue' import orderMixin from '@/pages/OrdeSystem/orderMixin' export default { name: 'CurrentWeekMenu', mixins: [orderMixin], components: { ElevatorScroll, }, data() { return { weekList: [], activeSectionDate: 0, thisWeek: [], diningLocation: null, } }, computed: { ...mapState('user', ['userno']), buttonTitle() { return function (item) { if (item.dayType === 1 && item.packageTypeView === 1) return '中餐A' if (item.dayType === 1 && item.packageTypeView === 2) return '中餐B' if (item.dayType === 2 && item.packageTypeView === 1) return '晚餐A' if (item.dayType === 2 && item.packageTypeView === 2) return '晚餐B' if (item.dayType === 3 && item.packageTypeView === 1) return '夜宵A' if (item.dayType === 3 && item.packageTypeView === 2) return '夜宵B' } }, itemGroupClass() { return function (item, index, list = []) { if (index < list.length - 1 && item.dayType !== list[index + 1].dayType) { return 'food-menu-item_group' } return '' } }, }, watch: { activeSectionDate(nv, ov) { if (nv !== ov && nv) { this.$nextTick(() => { const element = this.$refs.weekBoxRef.querySelector(`#week-item_${ nv }`) if (element) { const container = this.$refs.weekBoxRef // 计算元素顶部相对于容器顶部的距离 const elementTopRelativeToContainer = element.offsetTop - container.offsetTop // 计算元素中心相对于元素顶部的距离 const elementCenter = element.offsetHeight / 2 // 计算容器中心的位置 const containerCenter = container.clientHeight / 2 // 计算滚动位置:元素中心相对于容器顶部的距离 - 容器中心的位置 const scrollPosition = elementTopRelativeToContainer + elementCenter - containerCenter container.scrollTo({ top: scrollPosition, behavior: 'smooth', }) } }) } }, }, mounted() { this.weekList = getWeekList() }, methods: { orderedChange(record) { if (!this.orderLoading.includes(record.id)) { this.dialogEnter(!record.ordered, record) } }, toDetails(diningLocation) { if (diningLocation || diningLocation === 0) { this.diningLocation = diningLocation } this.init() }, async init() { this.$nextTick(() => { const elevatorScrollRef = this.$refs.elevatorScrollRef if (elevatorScrollRef) { elevatorScrollRef.init() } }) // 初始化请求 if (this.userno && this.userno !== 'null') { this.$root.openLoading() let data = { userId: this.userno, diningLocation: this.diningLocation, } let res = await this.$api.getMealWeek({ data }) if (res && res.code == 200) { const thisWeek = res.data && res.data.thisWeek && res.data.thisWeek.length ? res.data.thisWeek.map(item => { return { id: uuidv4(), ...item, list: (item.list || []).map(item => ({ ...item, ordered: item.dayPeopleStatus == 2 && item.packageType == item.packageTypeView, })), } }) : [] this.thisWeek = thisWeek } } }, scrollToSection(idx) { this.activeSectionDate = idx }, }, } </script> <style scoped lang="scss"> .CurrentWeekMenu_root { font-family: Inter, Inter; display: flex; height: 100%; box-sizing: border-box; .week_box { width: 144px; margin-right: 34px; height: 100%; overflow: hidden; overflow-y: auto; box-sizing: border-box; padding-top: 34px; .week-item_box { height: 108px; background: #F8F8F8; border-radius: 8px 8px 8px 8px; margin-bottom: 4px; display: flex; flex-direction: column; justify-content: center; color: #333333; &.active { background: #27AFE9; color: #fff; transition: background cubic-bezier(0.42, 0, 0.58, 1) .3s; .week-item_date { color: #fff; } } .week-item_name { height: 36px; font-size: 30px; line-height: 35px; margin-bottom: 2px; } .week-item_date { font-size: 24px; color: #666666; } } } .food-menu_box { flex: 1; height: 100%; box-sizing: border-box; /deep/ .section { padding-top: 34px; } .food-menu-group_box { .food-menu-item_box { margin-bottom: 36px; margin-right: 20px; display: flex; transition: opacity .2s; &.food-menu-item_group { &:not(:last-child) { padding-bottom: 46px; border-bottom: 2px solid #ECECEC; margin-bottom: 46px; } } &:active { opacity: 0.7; } .food-menu-item_img { margin-right: 22px; /deep/ .van-image { border-radius: 16px; overflow: hidden; .van-image__error { font-size: 28px; color: #FFFFFF; background: #6C6C6C; border-radius: 16px; } } } .food-menu-item_cotent { flex: 1; box-sizing: border-box; font-weight: 400; font-size: 28px; line-height: 33px; text-align: left; .food-menu-item_cotent_title { color: #999999; margin-bottom: 10px; } .food-menu-item_cotent_desc { color: #333333; } } .check-box { width: 60px; display: flex; align-items: center; justify-content: end; /deep/ .van-checkbox { height: 100%; .van-checkbox__icon { .van-icon { position: unset; width: 44px; height: 44px; } } } } } } } .food-menu_box, .week_box { &::-webkit-scrollbar { width: 8px; height: 8px; } &::-webkit-scrollbar-thumb { border-radius: 6px; } } } </style> ```
Pre:
Upload上传限制一张图片的时候会有抖动动画
Next:
联动滑动电梯
0
likes
5
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Submit
Sign in
to leave a comment.
No Leanote account?
Sign up now.
0
comments
More...
Table of content
No Leanote account? Sign up now.