From 093a1e9afbec7011cb8008708c203f9dfb57f040 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 28 Oct 2026 13:22:25 +0000 Subject: [PATCH] =?UTF-8?q?feat(project):=20=E7=A7=BB=E5=8A=A8=E7=AB=AF=20?= =?UTF-8?q?App=20=E5=86=85=E9=A1=B9=E7=9B=AE=E5=88=97=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=88=87=E6=8D=A2=E5=A2=9E=E5=8A=A0=E5=8F=B3?= =?UTF-8?q?=E4=BE=A7=E6=BB=91=E5=85=A5/=E6=BB=91=E5=87=BA=E5=8A=A8?= =?UTF-8?q?=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 列表与详情为同一路由页面,路由级动画不会触发,改在项目页内部用 transition 处理: - 滑入期间保持列表与底部选项卡,结束后再隐藏,避免下层页面高度跳变 - 动画期间详情固定覆盖于列表及选项卡之上,按实际位置换算 top - 返回时等滑出动画结束再清空当前项目数据 --- resources/assets/js/pages/manage.vue | 9 ++ resources/assets/js/pages/manage/project.vue | 87 ++++++++++++++++++- resources/assets/sass/pages/page-project.scss | 12 +++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue index d688b9822..926247192 100644 --- a/resources/assets/js/pages/manage.vue +++ b/resources/assets/js/pages/manage.vue @@ -1119,6 +1119,8 @@ export default { this.releaseViewCover() this.viewLeavePath = from.path const isSubPage = route => /^manage-(setting|calendar|file)/.test(route.name || '') + const isProjectList = route => route.name === 'manage-project' && !/^\d+$/.test(route.params.projectId) + const isProjectPanel = route => route.name === 'manage-project' && /^\d+$/.test(route.params.projectId) if (this.$isEEUIApp && this.windowPortrait) { if (from.name === 'manage-application' && isSubPage(to)) { // 划入动画结束后再隐藏底部选项卡 @@ -1132,6 +1134,13 @@ export default { this.viewTransition = {enter: 'manage-view-stay', leave: 'mobile-dialog-leave-active manage-view-top'} return } + if (isProjectList(from) && isProjectPanel(to)) { + // 项目列表与详情为同一页面组件,划入动画由项目页处理并负责释放 tabbarHold + this.tabbarHold = true + this.tabbarTransition = 'none' + this.viewTransition = {enter: '', leave: ''} + return + } } this.tabbarHold = false this.tabbarTransition = 'mobile-slide' diff --git a/resources/assets/js/pages/manage/project.vue b/resources/assets/js/pages/manage/project.vue index 32785058e..a505701fb 100644 --- a/resources/assets/js/pages/manage/project.vue +++ b/resources/assets/js/pages/manage/project.vue @@ -1,7 +1,17 @@ @@ -12,6 +22,13 @@ import ProjectList from "./components/ProjectList"; export default { components: {ProjectList, ProjectPanel}, + data() { + return { + panelTransition: {enter: '', leave: ''}, + listStay: false, // 详情划入期间保持列表显示 + } + }, + deactivated() { this.$store.dispatch("forgetTaskCompleteTemp", true); }, @@ -19,6 +36,15 @@ export default { computed: { ...mapState(['cacheProjects', 'wsOpenNum']), + /** + * 划出类名须在详情渲染时即确定(Vue 离开动画读取的是元素上一次渲染时的类名) + * 非返回列表的离开(切到其它页面)此时页面已被 keep-alive 移出,动画不可见 + * @returns {string} + */ + panelLeaveClass() { + return this.$isEEUIApp && this.windowPortrait ? 'mobile-dialog-leave-active' : '' + }, + projectId() { const {projectId} = this.$route.params; return parseInt(/^\d+$/.test(projectId) ? projectId : 0); @@ -26,6 +52,11 @@ export default { }, watch: { + // 须在 projectId 之前定义:先确定动画再处理项目数据 + '$route'(to, from) { + this.updatePanelTransition(to, from) + }, + projectId: { handler() { this.getProjectData(); @@ -43,9 +74,59 @@ export default { }, methods: { - getProjectData() { + /** + * 移动端项目列表与详情切换动画(同一页面组件,路由动画不会触发) + * @param to + * @param from + */ + updatePanelTransition(to, from) { + const isList = route => route.name === 'manage-project' && !/^\d+$/.test(route.params.projectId) + const isPanel = route => route.name === 'manage-project' && /^\d+$/.test(route.params.projectId) + this.listStay = false + this.panelTransition = {enter: '', leave: ''} + if (this.$isEEUIApp && this.windowPortrait) { + if (isList(from) && isPanel(to)) { + this.listStay = true + this.panelTransition.enter = 'mobile-dialog-enter-active' + } else if (isPanel(from) && isList(to)) { + this.panelTransition.leave = 'mobile-dialog-leave-active' + } + } + }, + + onPanelCover(el) { + if (this.panelTransition.enter || this.panelTransition.leave) { + // 动画期间详情固定覆盖于列表及底部选项卡之上 + // fixed 的参照可能不是视口(祖先 will-change: transform),按实际位置换算 top + el.setAttribute('data-panel-cover', '') + el.style.top = `${this.$el.getBoundingClientRect().top - el.getBoundingClientRect().top}px` + } + }, + + releasePanelCover(el) { + el.style.top = '' + el.removeAttribute('data-panel-cover') + }, + + onPanelEntered(el) { + this.releasePanelCover(el) + this.listStay = false + this.$store.state.mobileTabbarHold = false + }, + + onPanelLeft(el) { + this.releasePanelCover(el) if (this.projectId <= 0) { this.$store.state.projectId = 0; + } + }, + + getProjectData() { + if (this.projectId <= 0) { + // 详情划出期间保留项目数据,动画结束后再清除 + if (!this.panelTransition.leave) { + this.$store.state.projectId = 0; + } return; } const projectId = this.projectId; diff --git a/resources/assets/sass/pages/page-project.scss b/resources/assets/sass/pages/page-project.scss index 2103f2b13..bcfd4138c 100644 --- a/resources/assets/sass/pages/page-project.scss +++ b/resources/assets/sass/pages/page-project.scss @@ -7,6 +7,18 @@ width: 0; height: 100%; background-color: #fafafa; + // 移动端列表与详情切换动画期间(top 由脚本按页面位置换算) + &[data-panel-cover] { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: auto; + height: auto; + min-height: 0; + z-index: 100; + } } }