feat:系统UI优化初版

This commit is contained in:
2026-08-17 17:10:33 +08:00
parent 4cf4e45ea2
commit bc783a4beb
11 changed files with 1774 additions and 513 deletions
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;
}
`
}