mirror of
https://github.com/kuaifan/dootask.git
synced 2026-10-29 22:10:42 +00:00
feat(mobile): App 竖屏从应用进入设置/日历/文件改为右侧划入、返回划出,并沉浸底部安全区
- 划入期间保留底部选项卡、划入页置顶覆盖,避免高度跳动与结束时闪帧 - 返回应用页时恢复其滚动位置
This commit is contained in:
parent
8ded349077
commit
9fce053b93
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="app" class="app-view" :class="{'immersive-status': immersiveStatus}">
|
||||
<div id="app" class="app-view" :class="{'immersive-status': immersiveStatus, 'immersive-navigation': immersiveNavigation}">
|
||||
<!--顶部状态栏-->
|
||||
<div class="child-status-bar"></div>
|
||||
|
||||
@ -79,6 +79,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 沉浸底部安全区:隐藏占位,页面内容延伸到底部安全区
|
||||
&.immersive-navigation {
|
||||
.child-navigation-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.child-navigation-bar {
|
||||
flex-shrink: 0;
|
||||
height: var(--navigation-bar-height);
|
||||
@ -163,7 +170,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['aiVisible']),
|
||||
|
||||
...mapState(['ws', 'themeConf', 'windowOrientation', 'safeAreaSize', 'isFirstPage', 'mobileTabbar', 'themeName']),
|
||||
...mapState(['ws', 'themeConf', 'windowOrientation', 'safeAreaSize', 'isFirstPage', 'mobileTabbar', 'mobileTabbarHold', 'themeName']),
|
||||
|
||||
statusColor({routeName, windowLandscape}) {
|
||||
if (!routeName) {
|
||||
@ -186,6 +193,14 @@ export default {
|
||||
return windowPortrait && routeName === 'manage-dashboard'
|
||||
},
|
||||
|
||||
/**
|
||||
* 移动端设置/日历/文件使用沉浸底部安全区(划入动画期间保持占位,避免下层页面高度跳变)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
immersiveNavigation({routeName, windowPortrait, mobileTabbarHold}) {
|
||||
return windowPortrait && !mobileTabbarHold && /^manage-(setting|calendar|file)/.test(routeName || '')
|
||||
},
|
||||
|
||||
navigationColor({routeName, windowLandscape, mobileTabbar}) {
|
||||
if (!routeName) {
|
||||
return null
|
||||
|
||||
@ -296,9 +296,19 @@
|
||||
|
||||
<div class="manage-box-main" :role="routeName">
|
||||
<div class="manage-status-bar"><span></span></div>
|
||||
<keep-alive>
|
||||
<router-view class="manage-box-view" @on-click="onTabbarClick"></router-view>
|
||||
</keep-alive>
|
||||
<transition
|
||||
:enter-active-class="viewTransition.enter"
|
||||
:leave-active-class="viewTransition.leave"
|
||||
@before-leave="onViewBeforeLeave"
|
||||
@enter="onViewEnter"
|
||||
@after-enter="tabbarHold = false"
|
||||
@enter-cancelled="tabbarHold = false"
|
||||
@after-leave="releaseViewCover"
|
||||
@leave-cancelled="releaseViewCover">
|
||||
<keep-alive>
|
||||
<router-view class="manage-box-view" @on-click="onTabbarClick"></router-view>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<div class="manage-navigation-bar"><span></span></div>
|
||||
</div>
|
||||
|
||||
@ -458,8 +468,8 @@
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--移动端选项卡-->
|
||||
<transition name="mobile-slide">
|
||||
<MobileTabbar v-if="mobileTabbar" @on-click="onTabbarClick"/>
|
||||
<transition :name="tabbarTransition">
|
||||
<MobileTabbar v-if="mobileTabbar || tabbarHold" @on-click="onTabbarClick"/>
|
||||
</transition>
|
||||
|
||||
<!--应用详情-->
|
||||
@ -529,6 +539,10 @@ export default {
|
||||
return {
|
||||
loadIng: 0,
|
||||
|
||||
viewTransition: {enter: '', leave: ''},
|
||||
tabbarTransition: 'mobile-slide',
|
||||
viewCoverEl: null,
|
||||
|
||||
mateName: /macintosh|mac os x/i.test(navigator.userAgent) ? '⌘' : 'Ctrl',
|
||||
|
||||
addShow: false,
|
||||
@ -633,6 +647,16 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 划入动画期间保持Tabbar(存于 store,App.vue 据此延后沉浸底部安全区)
|
||||
tabbarHold: {
|
||||
get() {
|
||||
return this.$store.state.mobileTabbarHold
|
||||
},
|
||||
set(val) {
|
||||
this.$store.state.mobileTabbarHold = val
|
||||
}
|
||||
},
|
||||
|
||||
...mapState([
|
||||
'userInfo',
|
||||
'userIsAdmin',
|
||||
@ -692,9 +716,9 @@ export default {
|
||||
* @param userId
|
||||
* @returns {{"show-tabbar", "not-logged": boolean}}
|
||||
*/
|
||||
pageClass({mobileTabbar, userId}) {
|
||||
pageClass({mobileTabbar, tabbarHold, userId}) {
|
||||
return {
|
||||
'show-tabbar': mobileTabbar,
|
||||
'show-tabbar': mobileTabbar || tabbarHold,
|
||||
'not-logged': userId <= 0
|
||||
}
|
||||
},
|
||||
@ -942,8 +966,9 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route' () {
|
||||
'$route' (to, from) {
|
||||
this.chackPass();
|
||||
this.updateViewTransition(to, from);
|
||||
},
|
||||
|
||||
userIsAdmin: {
|
||||
@ -1084,6 +1109,82 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* App 竖屏下「应用 → 设置/日历/文件」右侧划入,返回时划出(与设置子页面效果一致)
|
||||
* @param to
|
||||
* @param from
|
||||
*/
|
||||
updateViewTransition(to, from) {
|
||||
this.releaseViewCover()
|
||||
const isSubPage = route => /^manage-(setting|calendar|file)/.test(route.name || '')
|
||||
if (this.$isEEUIApp && this.windowPortrait) {
|
||||
if (from.name === 'manage-application' && isSubPage(to)) {
|
||||
// 划入动画结束后再隐藏底部选项卡
|
||||
this.tabbarHold = true
|
||||
this.tabbarTransition = 'none'
|
||||
this.viewTransition = {enter: 'mobile-dialog-enter-active', leave: 'manage-view-stay'}
|
||||
return
|
||||
}
|
||||
if (isSubPage(from) && to.name === 'manage-application') {
|
||||
this.tabbarTransition = 'mobile-slide'
|
||||
this.viewTransition = {enter: 'manage-view-stay', leave: 'mobile-dialog-leave-active manage-view-top'}
|
||||
return
|
||||
}
|
||||
}
|
||||
this.tabbarHold = false
|
||||
this.tabbarTransition = 'mobile-slide'
|
||||
this.viewTransition = {enter: '', leave: ''}
|
||||
},
|
||||
|
||||
/**
|
||||
* 停留在下层的页面(应用)离开前记录滚动位置(keep-alive 移出 DOM 后会丢失)
|
||||
* @param el
|
||||
*/
|
||||
onViewBeforeLeave(el) {
|
||||
if (this.viewTransition.leave !== 'manage-view-stay') {
|
||||
return
|
||||
}
|
||||
el.__scrollSnapshot = [el, ...el.querySelectorAll('*')]
|
||||
.filter(item => item.scrollTop > 0 || item.scrollLeft > 0)
|
||||
.map(item => ({item, top: item.scrollTop, left: item.scrollLeft}))
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面重新进入时恢复滚动位置
|
||||
* @param el
|
||||
*/
|
||||
onViewEnter(el) {
|
||||
if (this.viewTransition.leave === 'manage-view-stay') {
|
||||
// 划入页面保持置顶直到下层页面移除,避免划入先结束时下层页面透出闪一帧
|
||||
el.setAttribute('data-view-cover', '')
|
||||
this.viewCoverEl = el
|
||||
}
|
||||
const snapshot = el.__scrollSnapshot
|
||||
if (!snapshot) {
|
||||
return
|
||||
}
|
||||
delete el.__scrollSnapshot
|
||||
if (this.viewTransition.enter !== 'manage-view-stay') {
|
||||
return
|
||||
}
|
||||
snapshot.forEach(({item, top, left}) => {
|
||||
if (el.contains(item)) {
|
||||
item.scrollTop = top
|
||||
item.scrollLeft = left
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除划入页面的置顶
|
||||
*/
|
||||
releaseViewCover() {
|
||||
if (this.viewCoverEl) {
|
||||
this.viewCoverEl.removeAttribute('data-view-cover')
|
||||
this.viewCoverEl = null
|
||||
}
|
||||
},
|
||||
|
||||
transformEmojiToHtml,
|
||||
// 插件/微应用菜单角标 {count, dot}
|
||||
microBadge(menu) {
|
||||
|
||||
2
resources/assets/js/store/state.js
vendored
2
resources/assets/js/store/state.js
vendored
@ -69,6 +69,8 @@ export default {
|
||||
|
||||
// 显示移动端Tabbar
|
||||
mobileTabbar: false,
|
||||
// 页面划入动画期间保持Tabbar(见 manage.vue updateViewTransition)
|
||||
mobileTabbarHold: false,
|
||||
|
||||
// 请求时间
|
||||
callAt: [],
|
||||
|
||||
20
resources/assets/sass/components/mobile.scss
vendored
20
resources/assets/sass/components/mobile.scss
vendored
@ -305,6 +305,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 页面切换:划入/划出的页面置顶,另一页面原地停留至动画结束
|
||||
.manage-view-top,
|
||||
[data-view-cover] {
|
||||
z-index: 1;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.manage-view-stay {
|
||||
animation: manage-view-stay-animation 0.3s;
|
||||
}
|
||||
|
||||
@keyframes manage-view-stay-animation {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 录音发送(缩放)
|
||||
.mobile-send-enter-active {
|
||||
animation: mobile-send-enter-animation 0.3s forwards;
|
||||
|
||||
4
resources/assets/sass/pages/page-file.scss
vendored
4
resources/assets/sass/pages/page-file.scss
vendored
@ -826,6 +826,8 @@ body.window-portrait {
|
||||
}
|
||||
.file-drag {
|
||||
.file-list {
|
||||
// 沉浸底部安全区(见 App.vue immersiveNavigation)
|
||||
padding-bottom: calc(20px + var(--navigation-bar-height));
|
||||
> ul {
|
||||
grid-template-columns: repeat(auto-fill, 80px);
|
||||
grid-gap: 4px;
|
||||
@ -847,7 +849,7 @@ body.window-portrait {
|
||||
}
|
||||
}
|
||||
.file-table {
|
||||
margin: 16px 0 0;
|
||||
margin: 16px 0 var(--navigation-bar-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
resources/assets/sass/pages/page-manage.scss
vendored
11
resources/assets/sass/pages/page-manage.scss
vendored
@ -622,6 +622,13 @@ body.window-portrait {
|
||||
&.show-tabbar {
|
||||
.manage-box-main {
|
||||
margin-bottom: 60px;
|
||||
// 页面切换动画中,划入/划出的页面覆盖底部选项卡及安全区,避免高度跳变
|
||||
.manage-view-top,
|
||||
[data-view-cover] {
|
||||
bottom: calc(-60px - var(--navigation-bar-height));
|
||||
height: auto; // 页面自身 height: 100%(如 .page-file)会使 bottom 失效
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
.mobile-tabbar {
|
||||
display: flex;
|
||||
@ -629,6 +636,10 @@ body.window-portrait {
|
||||
@media (height <= 400px) {
|
||||
.manage-box-main {
|
||||
margin-bottom: 0;
|
||||
.manage-view-top,
|
||||
[data-view-cover] {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.mobile-tabbar {
|
||||
display: none;
|
||||
|
||||
@ -554,7 +554,8 @@ body.window-portrait {
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
> ul {
|
||||
padding: 0 0 20px;
|
||||
// 沉浸底部安全区(见 App.vue immersiveNavigation)
|
||||
padding: 0 0 calc(20px + var(--navigation-bar-height));
|
||||
> li {
|
||||
position: relative;
|
||||
padding: 0 32px;
|
||||
@ -598,6 +599,9 @@ body.window-portrait {
|
||||
.setting-content-title {
|
||||
display: none;
|
||||
}
|
||||
.setting-content-view {
|
||||
padding-bottom: var(--navigation-bar-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user