Files
yunzerwebsiteallinone/uniapp/pages/approval/verify.vue
T
2026-10-16 22:40:02 +08:00

310 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 顶部:搜索(系统导航栏已带返回按钮 + 标题) -->
<view class="toolbar">
<view class="search-box">
<FaIcon name="magnifying-glass" color="#94a3b8" :size="16" />
<input
class="search-input"
v-model="keyword"
placeholder="搜索单据标题"
confirm-type="search"
@confirm="onSearch"
/>
<text v-if="keyword" class="search-clear" @tap="clearKeyword">清空</text>
</view>
</view>
<view class="stat-bar">
<text class="stat-text">共 {{ total }} 条待办</text>
</view>
<!-- 待办列表 -->
<scroll-view class="list-scroll" scroll-y :show-scrollbar="false" @scrolltolower="loadMore">
<view v-if="loading && !list.length" class="state-box">
<text class="state-text">加载中...</text>
</view>
<view v-else-if="!list.length" class="state-box">
<u-empty mode="list" text="暂无待我审批的任务" />
</view>
<block v-else>
<view
v-for="task in list"
:key="task.id"
class="todo-card"
hover-class="card-active"
@tap="openDetail(task.instance)"
>
<view class="card-top">
<text class="card-title">{{ task.instance && task.instance.biz_title ? task.instance.biz_title : '(无标题)' }}</text>
<u-tag
:text="task.instance && task.instance.status_label || ''"
:type="task.instance && task.instance.status_tag || 'info'"
size="mini"
/>
</view>
<view class="card-meta">
<text class="meta-text">节点:{{ task.node_name || '-' }}</text>
<text class="meta-text">发起人:{{ task.instance && task.instance.submit_user_name || '-' }}</text>
</view>
<view class="card-meta">
<text class="meta-text">单据:{{ task.instance && task.instance.biz_type || '-' }}</text>
<text class="meta-text">提交:{{ task.instance && (task.instance.submit_time || task.instance.create_time) || '-' }}</text>
</view>
<view class="card-meta" v-if="task.deadline">
<text class="meta-text">截止:{{ task.deadline }}</text>
</view>
<!-- card-actions 不接管 tap,只把"查看详情"按钮的 tap 单独拦在按钮层,
这样 card 主体其它区域的 tap 仍能正常冒泡到 .todo-card 触发跳转 -->
<view class="card-actions">
<view class="op-btn op-view" hover-class="btn-active" @tap.stop="openDetail(task.instance)">查看详情</view>
</view>
</view>
<view v-if="finished" class="load-done">
<text>{{ list.length ? '没有更多了' : '' }}</text>
</view>
<view v-else class="more-tip" @tap="loadMore">
<text>加载更多</text>
</view>
</block>
<view class="bottom-space" />
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import {
fetchApprovalTodoList
} from '@/api/approval.js'
import { isLoggedIn } from '@/utils/auth.js'
const PAGE_SIZE = 20
const list = ref([])
const total = ref(0)
const page = ref(1)
const loading = ref(false)
const loadingMore = ref(false)
const finished = ref(false)
const keyword = ref('')
onLoad(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
refresh()
})
onShow(() => {
if (isLoggedIn() && list.value.length === 0) refresh()
})
async function refresh() {
page.value = 1
finished.value = false
list.value = []
await loadPage()
}
async function loadMore() {
if (finished.value || loadingMore.value) return
loadingMore.value = true
page.value += 1
await loadPage()
}
async function loadPage() {
loading.value = true
try {
const data = await fetchApprovalTodoList({
page: page.value,
pageSize: PAGE_SIZE,
keyword: keyword.value.trim()
})
const rows = (data.list || []).filter(t => {
if (!keyword.value) return true
const title = t.instance && t.instance.biz_title ? String(t.instance.biz_title) : ''
return title.toLowerCase().includes(keyword.value.toLowerCase())
})
list.value = page.value === 1 ? rows : [...list.value, ...rows]
total.value = data.total || 0
finished.value = list.value.length >= total.value
} catch (e) {
if (page.value > 1) page.value -= 1
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
loadingMore.value = false
}
}
function onSearch() { refresh() }
function clearKeyword() {
keyword.value = ''
refresh()
}
function openDetail(instance) {
if (!instance || !instance.id) {
uni.showToast({ title: '单据数据异常', icon: 'none' })
return
}
uni.navigateTo({ url: `/pages/approval/detail?id=${instance.id}` })
}
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.page {
min-height: 100vh;
background: $color-bg-page;
display: flex;
flex-direction: column;
}
/* ====== 顶部搜索 ====== */
.toolbar {
flex-shrink: 0;
padding: 20rpx $page-padding-x;
background: $color-card;
box-shadow: $shadow-header;
}
.search-box {
display: flex;
align-items: center;
gap: 12rpx;
height: 72rpx;
padding: 0 24rpx;
background: $color-bg-page;
border-radius: $radius-full;
box-shadow: $shadow-card;
.search-input {
flex: 1;
font-size: 26rpx;
color: $color-text;
}
.search-clear {
font-size: 24rpx;
color: $color-text-muted;
}
}
/* ====== 计数条 ====== */
.stat-bar {
flex-shrink: 0;
padding: 16rpx $page-padding-x;
background: $color-card;
border-bottom: 1rpx solid $color-divider;
}
.stat-text {
font-size: 26rpx;
color: $color-text-secondary;
}
/* ====== 列表 ====== */
.list-scroll {
flex: 1;
height: 0;
}
.todo-card {
@include card;
margin: 16rpx $page-padding-x 0;
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.card-active { opacity: 0.85; }
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
}
.card-title {
flex: 1;
min-width: 0;
font-size: 30rpx;
font-weight: 600;
color: $color-text;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-meta {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.meta-text {
font-size: 24rpx;
color: $color-text-muted;
}
.card-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
padding-top: 14rpx;
border-top: 1rpx solid $color-divider;
margin-top: 4rpx;
}
.op-btn {
padding: 14rpx 36rpx;
border-radius: $radius-md;
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
.op-view {
background: $color-primary;
}
.btn-active { opacity: 0.85; }
.more-tip {
padding: 12rpx 0 4rpx;
text-align: center;
font-size: 24rpx;
color: $color-primary;
}
.load-done {
padding: 12rpx 0 4rpx;
text-align: center;
font-size: 24rpx;
color: $color-text-muted;
}
.state-box {
display: flex;
justify-content: center;
padding: 140rpx 0;
}
.state-text {
font-size: 28rpx;
color: $color-text-muted;
}
.bottom-space {
height: 40rpx;
}
</style>