feat:系统UI优化初版
This commit is contained in:
@@ -149,15 +149,62 @@
|
||||
})
|
||||
}
|
||||
|
||||
function createListLoading(icon = "/assets/mobile/img/common/smart-union-loading.png") {
|
||||
return vant.Toast.loading({
|
||||
icon: icon,
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
loadingType: "spinner",
|
||||
duration: 0,
|
||||
className: "custom-list-loading"
|
||||
let h5GlobalLoading = null
|
||||
let h5PendingRequestCount = 0
|
||||
|
||||
function showH5GlobalLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg") {
|
||||
if (!h5GlobalLoading) {
|
||||
h5GlobalLoading = vant.Toast.loading({
|
||||
icon: icon,
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
loadingType: "spinner",
|
||||
duration: 0,
|
||||
className: "custom-list-loading"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function closeH5GlobalLoading() {
|
||||
if (h5PendingRequestCount > 0 || !h5GlobalLoading) return
|
||||
h5GlobalLoading.clear()
|
||||
h5GlobalLoading = null
|
||||
}
|
||||
|
||||
// 所有 H5 Axios 请求先展示 Loading,等待 200ms 后再真正发出请求。
|
||||
window.bindH5AxiosLoading = function(axiosService) {
|
||||
axiosService.interceptors.request.use((config) => {
|
||||
h5PendingRequestCount++
|
||||
showH5GlobalLoading()
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(config), 200)
|
||||
})
|
||||
}, (error) => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
axiosService.interceptors.response.use((response) => {
|
||||
h5PendingRequestCount = Math.max(0, h5PendingRequestCount - 1)
|
||||
closeH5GlobalLoading()
|
||||
return response
|
||||
}, (error) => {
|
||||
h5PendingRequestCount = Math.max(0, h5PendingRequestCount - 1)
|
||||
closeH5GlobalLoading()
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
function createListLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg") {
|
||||
showH5GlobalLoading(icon)
|
||||
|
||||
const closeLoading = function() {
|
||||
closeH5GlobalLoading()
|
||||
}
|
||||
|
||||
return {
|
||||
clear: closeLoading,
|
||||
close: closeLoading
|
||||
}
|
||||
}
|
||||
|
||||
function historyBack(func) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,43 +4,45 @@ const todo = {
|
||||
<van-nav-bar class="task-nav" placeholder fixed :border="false" style="background-color: #0879f9;">
|
||||
<template #title><span class="task-nav-title">待办中心</span></template>
|
||||
</van-nav-bar>
|
||||
<div class="task-hero">
|
||||
<div class="task-hero" ref="taskHero">
|
||||
<div class="task-statistics">
|
||||
<div class="statistic-item">
|
||||
<button type="button" class="statistic-item" @click="switchTab('todo')">
|
||||
<div class="statistic-icon statistic-icon-todo">
|
||||
<van-icon name="description"></van-icon>
|
||||
<van-icon name="description-o"></van-icon>
|
||||
</div>
|
||||
<div class="statistic-count">{{ todoCount }}</div>
|
||||
<div class="statistic-label">待办事项</div>
|
||||
</div>
|
||||
<div class="statistic-item">
|
||||
</button>
|
||||
<button type="button" class="statistic-item" @click="switchTab('done')">
|
||||
<div class="statistic-icon statistic-icon-done">
|
||||
<van-icon name="underway-o"></van-icon>
|
||||
</div>
|
||||
<div class="statistic-count">{{ doneCount }}</div>
|
||||
<div class="statistic-label">已办事项</div>
|
||||
</div>
|
||||
<div class="statistic-item">
|
||||
</button>
|
||||
<button type="button" class="statistic-item" @click="switchTab('started')">
|
||||
<div class="statistic-icon statistic-icon-started">
|
||||
<van-icon name="guide-o"></van-icon>
|
||||
</div>
|
||||
<div class="statistic-count">{{ startedCount }}</div>
|
||||
<div class="statistic-label">我发起的</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 标签页 -->
|
||||
<van-sticky offset-top="46px">
|
||||
<van-tabs v-model="activeTab" class="task-tabs" @change="handleTabChange" animated swipeable>
|
||||
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
|
||||
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
|
||||
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
|
||||
</van-tabs>
|
||||
<van-tabs v-model="activeTab" class="task-tabs task-tabs--hero-replacement"
|
||||
:style="{opacity: tabRevealProgress, pointerEvents: tabRevealProgress > 0.8 ? 'auto' : 'none'}"
|
||||
@change="handleTabChange" animated swipeable>
|
||||
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
|
||||
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
|
||||
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
|
||||
</van-tabs>
|
||||
<van-sticky :offset-top="46 + 44 * tabRevealProgress">
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="filter-section">
|
||||
<div>
|
||||
<div class="search-form">
|
||||
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"
|
||||
<van-search v-model="pageForm.searchKeyword" shape="round" placeholder="请输入搜索关键词"
|
||||
@search="doSearch"></van-search>
|
||||
</div>
|
||||
|
||||
@@ -67,34 +69,36 @@ const todo = {
|
||||
</van-sticky>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<table-list :api="'/flow/todoCenter/'+ activeTab" :page_form.sync="pageForm"
|
||||
ref="tableListRef"
|
||||
@ready="doSearch"
|
||||
title="variable.instanceName">
|
||||
<template #header="{row}">
|
||||
<div class="todo-item-header">
|
||||
<div class="todo-item-title">
|
||||
<span class="todo-title-icon"><van-icon name="description"></van-icon></span>
|
||||
<span class="todo-title-text">{{ row.variable?.instanceName || '' }}</span>
|
||||
<div class="task-list-offset" :style="{paddingTop: 44 * tabRevealProgress + 'px'}">
|
||||
<table-list :api="'/flow/todoCenter/'+ activeTab" :page_form.sync="pageForm"
|
||||
ref="tableListRef"
|
||||
@ready="doSearch"
|
||||
title="variable.instanceName">
|
||||
<template #header="{row}">
|
||||
<div class="todo-item-header">
|
||||
<div class="todo-item-title">
|
||||
<span class="todo-title-icon"><van-icon name="description-o"></van-icon></span>
|
||||
<span class="todo-title-text">{{ row.variable?.instanceName || '' }}</span>
|
||||
</div>
|
||||
<span class="todo-status">{{ getTaskStatus(row) }}</span>
|
||||
</div>
|
||||
<span class="todo-status">{{ getTaskStatus(row) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="任务节点">{{row.taskName}}</table-column>
|
||||
<table-column label="流程分类">{{row.categoryName}}</table-column>
|
||||
<table-column label="申请人">{{row.variable?.initiatorName || '未知'}}</table-column>
|
||||
<table-column label="状态" v-if="activeTab === 'started'">{{processStatusMap[row.state]?.text ||
|
||||
'未知状态'}}
|
||||
</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="任务节点">{{row.taskName}}</table-column>
|
||||
<table-column label="流程分类">{{row.categoryName}}</table-column>
|
||||
<table-column label="申请人">{{row.variable?.initiatorName || '未知'}}</table-column>
|
||||
<table-column label="状态" v-if="activeTab === 'started'">{{processStatusMap[row.state]?.text ||
|
||||
'未知状态'}}
|
||||
</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<span>查看详情</span>
|
||||
<van-icon name="arrow"></van-icon>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
@@ -126,6 +130,9 @@ const todo = {
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
heroVisible: true,
|
||||
tabRevealProgress: 0,
|
||||
scrollContainer: null,
|
||||
|
||||
processStatusMap: {
|
||||
10: {text: "进行中", class: "doing"},
|
||||
@@ -142,7 +149,32 @@ const todo = {
|
||||
this.initData();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => this.initHeroObserver())
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.scrollContainer) this.scrollContainer.removeEventListener("scroll", this.updateHeroVisibility)
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Hero 接近顶部导航栏时按剩余高度渐显分类标签。
|
||||
initHeroObserver() {
|
||||
this.scrollContainer = this.$el.closest(".page-wrapper") || document.scrollingElement
|
||||
this.scrollContainer.addEventListener("scroll", this.updateHeroVisibility, {passive: true})
|
||||
this.updateHeroVisibility()
|
||||
},
|
||||
|
||||
updateHeroVisibility() {
|
||||
const hero = this.$refs.taskHero
|
||||
const containerTop = this.scrollContainer ? this.scrollContainer.getBoundingClientRect().top : 0
|
||||
if (!hero) return
|
||||
const heroBottom = hero.getBoundingClientRect().bottom
|
||||
const revealProgress = (containerTop + 90 - heroBottom) / 44
|
||||
this.tabRevealProgress = Math.max(0, Math.min(1, revealProgress))
|
||||
this.heroVisible = this.tabRevealProgress < 1
|
||||
},
|
||||
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.listCategory()]);
|
||||
@@ -183,6 +215,30 @@ const todo = {
|
||||
this.activeTab = name;
|
||||
this.doSearch()
|
||||
this.getStatistics();
|
||||
this.keepTabsVisible()
|
||||
},
|
||||
|
||||
// 顶部统计按钮直接切换对应任务分类并刷新列表。
|
||||
switchTab(tabValue) {
|
||||
this.activeTab = tabValue
|
||||
this.doSearch()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 切换分类后停留在 Hero 刚好收起的位置,避免列表清空时页面回到顶部。
|
||||
keepTabsVisible() {
|
||||
this.heroVisible = false
|
||||
this.tabRevealProgress = 1
|
||||
this.$nextTick(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
const hero = this.$refs.taskHero
|
||||
const container = this.scrollContainer
|
||||
if (!hero || !container) return
|
||||
const containerTop = container.getBoundingClientRect().top
|
||||
const targetTop = container.scrollTop + hero.getBoundingClientRect().bottom - containerTop - 45
|
||||
container.scrollTop = Math.max(0, targetTop)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 搜索
|
||||
@@ -294,6 +350,14 @@ const todo = {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.statistic-item:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.statistic-icon {
|
||||
@@ -329,30 +393,59 @@ const todo = {
|
||||
|
||||
.statistic-label {
|
||||
color: #8d97a7;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
/* 标签页样式优化 */
|
||||
/deep/ .van-tabs__nav {
|
||||
/deep/ .task-tabs .van-tabs__wrap {
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
/deep/ .task-tabs .van-tabs__nav {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .van-tab {
|
||||
/deep/ .task-tabs .van-tab {
|
||||
position: relative;
|
||||
color: #7f8998;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s, font-size 0.2s;
|
||||
}
|
||||
|
||||
/deep/ .van-tab--active {
|
||||
/deep/ .task-tabs .van-tab:not(:last-child)::after {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 0;
|
||||
bottom: 13px;
|
||||
width: 1px;
|
||||
background: #edf1f6;
|
||||
content: "";
|
||||
transform: skewX(-18deg);
|
||||
}
|
||||
|
||||
/deep/ .task-tabs .van-tab--active {
|
||||
color: var(--color-primary, #1989fa);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/deep/ .van-tabs__line {
|
||||
width: 30px;
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
/deep/ .task-tabs .van-tabs__line {
|
||||
bottom: 7px;
|
||||
width: 24px;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-primary, #1989fa);
|
||||
}
|
||||
|
||||
/deep/ .task-tabs--hero-replacement {
|
||||
position: fixed;
|
||||
top: 46px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
transition: opacity 0.12s linear;
|
||||
}
|
||||
|
||||
/deep/ .filter-section {
|
||||
/*margin-bottom: 16px;*/
|
||||
@@ -360,7 +453,7 @@ const todo = {
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
padding: 8px 12px 4px;
|
||||
padding: 10px 12px;
|
||||
align-items: center;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
@@ -368,16 +461,20 @@ const todo = {
|
||||
.search-form .van-search{
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/deep/ .search-form .van-search__content {
|
||||
border-radius: 8px;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/deep/ .search-form .van-field__control {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/deep/ .van-sticky--fixed {
|
||||
background: #f5f7fb;
|
||||
}
|
||||
@@ -440,17 +537,23 @@ const todo = {
|
||||
margin: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
.task-list-offset {
|
||||
min-height: calc(100vh - 140px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container {
|
||||
margin-top: 8px;
|
||||
padding: 0 12px;
|
||||
min-height: calc(100vh - 140px);
|
||||
margin-top: 8px !important;
|
||||
padding: 0 12px !important;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
box-shadow: 0 2px 10px rgba(31, 68, 113, 0.07);
|
||||
margin-bottom: 10px !important;
|
||||
padding: 12px !important;
|
||||
border: 0 !important;
|
||||
border-radius: 9px !important;
|
||||
box-shadow: 0 2px 10px rgba(31, 68, 113, 0.07) !important;
|
||||
}
|
||||
|
||||
/deep/ .todo-item-header {
|
||||
@@ -498,37 +601,43 @@ const todo = {
|
||||
padding: 3px 7px;
|
||||
border-radius: 4px;
|
||||
color: #4b92f2;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
background: #edf5ff;
|
||||
}
|
||||
|
||||
/deep/ .table-column {
|
||||
padding: 3px 0;
|
||||
padding: 3px 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .table-column .label,
|
||||
/deep/ .table-column .value {
|
||||
color: #637083;
|
||||
font-size: 11px;
|
||||
line-height: 17px;
|
||||
color: #637083 !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 19px !important;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item .item-actions {
|
||||
margin-top: 8px;
|
||||
padding-top: 7px;
|
||||
border-top-color: #edf1f6;
|
||||
margin-top: 8px !important;
|
||||
padding-top: 7px !important;
|
||||
border-top-color: #edf1f6 !important;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item .action-btn {
|
||||
padding: 0;
|
||||
color: #2783ef;
|
||||
font-size: 11px;
|
||||
padding: 0 !important;
|
||||
color: #2783ef !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item .action-btn i {
|
||||
margin-right: 4px;
|
||||
/deep/ .table-list-container .table-list-item .action-btn .van-icon {
|
||||
margin-left: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/deep/ .van-list__finished-text {
|
||||
padding: 12px 0 16px;
|
||||
color: #a1a9b5;
|
||||
font-size: 13px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user