mirror of
https://github.com/kuaifan/dootask.git
synced 2026-10-29 22:10:42 +00:00
perf(task): 移动端打开任务详情时先播放打开动画再挂载内容,挂载前显示任务头部、标题与属性骨架占位
This commit is contained in:
parent
2556c53fbc
commit
9d07f88e8c
@ -8,7 +8,73 @@
|
||||
:fullscreen="windowPortrait"
|
||||
:beforeClose="onBeforeClose"
|
||||
class-name="common-task-modal">
|
||||
<TaskDetail ref="taskDetail" :task-id="taskId" :open-task="taskData" modalMode/>
|
||||
<TaskDetail v-if="contentReady" ref="taskDetail" :task-id="taskId" :open-task="taskData" modalMode/>
|
||||
<!--内容挂载前的占位(头部结构与 TaskDetail 一致 + 骨架),避免打开时整页空白-->
|
||||
<div v-else-if="show" class="task-detail task-detail-placeholder" :class="{completed: taskData.complete_at}">
|
||||
<div class="task-info">
|
||||
<div class="head">
|
||||
<div class="task-menu-icon icon">
|
||||
<Icon v-if="taskData.complete_at" class="completed" type="md-checkmark-circle"/>
|
||||
<Icon v-else class="uncomplete" type="md-radio-button-off"/>
|
||||
</div>
|
||||
<div
|
||||
v-if="taskData.flow_item_name"
|
||||
class="flow"
|
||||
:style="$A.generateColorVarStyle(taskData.flow_item_color, [10], 'flow-item-custom-color')">
|
||||
<span :class="taskData.flow_item_status">{{taskData.flow_item_name}}</span>
|
||||
</div>
|
||||
<div v-if="taskData.archived_at" class="flow">
|
||||
<span class="archived">{{$L('已归档')}}</span>
|
||||
</div>
|
||||
<div class="nav">
|
||||
<p v-if="projectName"><span>{{projectName}}</span></p>
|
||||
<p v-if="columnName"><span>{{columnName}}</span></p>
|
||||
<p v-if="taskData.id"><span>{{taskData.id}}</span></p>
|
||||
</div>
|
||||
<div class="function">
|
||||
<div class="menu">
|
||||
<div class="task-menu-icon">
|
||||
<Icon type="ios-more"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroller">
|
||||
<div class="title">
|
||||
<div class="placeholder-title">{{taskData.name}}</div>
|
||||
</div>
|
||||
<div class="placeholder-skeleton">
|
||||
<div class="skeleton-desc">
|
||||
<div class="skeleton-line" style="width:92%"></div>
|
||||
<div class="skeleton-line" style="width:58%"></div>
|
||||
</div>
|
||||
<div class="skeleton-row">
|
||||
<div class="skeleton-label" style="width:56px"></div>
|
||||
<div class="skeleton-tag"></div>
|
||||
</div>
|
||||
<div class="skeleton-row">
|
||||
<div class="skeleton-label" style="width:44px"></div>
|
||||
<div class="skeleton-avatar"></div>
|
||||
</div>
|
||||
<div class="skeleton-row">
|
||||
<div class="skeleton-label" style="width:64px"></div>
|
||||
<div class="skeleton-avatar"></div>
|
||||
<div class="skeleton-avatar"></div>
|
||||
</div>
|
||||
<div class="skeleton-row multi">
|
||||
<div class="skeleton-label" style="width:58px"></div>
|
||||
<div class="skeleton-values">
|
||||
<div class="skeleton-line" style="width:180px"></div>
|
||||
<div class="skeleton-line" style="width:84px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="skeleton-add">
|
||||
<div class="skeleton-line" style="width:62%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@ -21,14 +87,44 @@ export default {
|
||||
name: "TaskModal",
|
||||
components: {TaskDetail},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contentReady: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['taskId']),
|
||||
...mapState(['taskId', 'cacheProjects', 'cacheColumns']),
|
||||
...mapGetters(['taskData']),
|
||||
|
||||
show() {
|
||||
return this.taskId > 0
|
||||
},
|
||||
|
||||
projectName() {
|
||||
const {project_id, project_name} = this.taskData
|
||||
if (!project_id) {
|
||||
return ''
|
||||
}
|
||||
if (project_name) {
|
||||
return project_name
|
||||
}
|
||||
const project = this.cacheProjects.find(({id}) => id == project_id)
|
||||
return project ? project.name : ''
|
||||
},
|
||||
|
||||
columnName() {
|
||||
const {column_id, column_name} = this.taskData
|
||||
if (!column_id) {
|
||||
return ''
|
||||
}
|
||||
if (column_name) {
|
||||
return column_name
|
||||
}
|
||||
const column = this.cacheColumns.find(({id}) => id == column_id)
|
||||
return column ? column.name : ''
|
||||
},
|
||||
|
||||
styles() {
|
||||
return this.windowPortrait ? {
|
||||
width: '100%',
|
||||
@ -42,6 +138,17 @@ export default {
|
||||
watch: {
|
||||
show(v) {
|
||||
$A.eeuiAppSetScrollDisabled(v && this.windowPortrait)
|
||||
if (v) {
|
||||
if (!this.windowPortrait) {
|
||||
this.contentReady = true
|
||||
return
|
||||
}
|
||||
// 任务详情渲染较重,等打开动画开始(交由合成线程执行)后再挂载,避免动画首帧被阻塞
|
||||
this.contentReady = false
|
||||
requestAnimationFrame(_ => requestAnimationFrame(_ => {
|
||||
this.contentReady = this.show
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -61,7 +168,7 @@ export default {
|
||||
},
|
||||
checkUpdate() {
|
||||
if (this.show) {
|
||||
this.$refs.taskDetail.checkUpdate(true);
|
||||
this.$refs.taskDetail?.checkUpdate(true);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1187,3 +1187,98 @@ body.window-portrait {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 任务详情挂载前的占位与骨架(见 TaskModal.vue)
|
||||
.task-detail-placeholder {
|
||||
.task-info {
|
||||
.scroller {
|
||||
flex: 1;
|
||||
padding: 0 5px;
|
||||
.title {
|
||||
.placeholder-title {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
padding: 5px 1px;
|
||||
line-height: 1.4;
|
||||
color: #606266;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.placeholder-skeleton {
|
||||
// 尺寸与 TaskDetail 真实布局对齐(描述每行 24px、属性行 36px + 10px 间距),加载完成时不跳动
|
||||
padding: 10px 1px 0;
|
||||
animation: task-skeleton-animation 1.4s ease-in-out infinite;
|
||||
.skeleton-line,
|
||||
.skeleton-label,
|
||||
.skeleton-tag,
|
||||
.skeleton-avatar {
|
||||
flex-shrink: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.skeleton-line,
|
||||
.skeleton-label {
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.skeleton-desc {
|
||||
// 每行占 24px,与描述行高一致
|
||||
margin-bottom: 12px;
|
||||
.skeleton-line {
|
||||
margin: 6px 0;
|
||||
}
|
||||
}
|
||||
.skeleton-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding-left: 107px;
|
||||
position: relative;
|
||||
& + .skeleton-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.skeleton-label {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 0;
|
||||
}
|
||||
.skeleton-tag {
|
||||
width: 72px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.skeleton-avatar {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&.multi {
|
||||
height: 54px;
|
||||
align-items: flex-start;
|
||||
.skeleton-values {
|
||||
padding-top: 11px;
|
||||
.skeleton-line + .skeleton-line {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes task-skeleton-animation {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user