feat(approval): 员工端审批中心入口与业务完成确认单页面 (#19)
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<view class="approval-page">
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'mine'" class="toolbar">
|
||||
<picker :range="statusLabels" :value="statusIndex" @change="onStatusChange">
|
||||
<view class="filter">{{ statusLabels[statusIndex] }}</view>
|
||||
</picker>
|
||||
<input
|
||||
v-model="keywords"
|
||||
class="search"
|
||||
type="text"
|
||||
placeholder="搜索单号或名称"
|
||||
confirm-type="search"
|
||||
@confirm="reload"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab !== 'mine'" class="placeholder">
|
||||
<text class="placeholder-title">{{ activeLabel }}功能建设中</text>
|
||||
<text class="placeholder-desc">当前版本先开放「我发起的」</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="list">
|
||||
<uni-card
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:border="false"
|
||||
:padding="'14px 16px'"
|
||||
class="item-card"
|
||||
@click="openDetail(item.id)"
|
||||
>
|
||||
<view class="item-head">
|
||||
<text class="item-title">{{ item.title || '未命名单据' }}</text>
|
||||
<text class="status" :class="'status-' + item.status">
|
||||
{{ item.statusName }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="label">单号</text>
|
||||
<text class="value">{{ item.bizNo || '—' }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="label">当前节点</text>
|
||||
<text class="value">{{ item.currentNodeName || '—' }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="label">提交时间</text>
|
||||
<text class="value">{{ formatTime(item.submitTime) }}</text>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<view v-if="!loading && list.length === 0" class="empty">
|
||||
<text>暂无单据</text>
|
||||
</view>
|
||||
<view v-if="loading" class="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view v-if="finished && list.length > 0" class="finished">
|
||||
<text>没有更多了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'mine'" class="fab" @click="createDraft">
|
||||
<text class="fab-text">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad, onPullDownRefresh, onReachBottom, onShow } from '@dcloudio/uni-app';
|
||||
import { pageMine } from '@/api/approval/instance';
|
||||
import type { ApprovalItem } from '@/api/approval/model';
|
||||
|
||||
type TabKey = 'todo' | 'mine' | 'done';
|
||||
|
||||
const tabs: Array<{ key: TabKey; label: string }> = [
|
||||
{ key: 'todo', label: '待办' },
|
||||
{ key: 'mine', label: '我发起的' },
|
||||
{ key: 'done', label: '已办' }
|
||||
];
|
||||
|
||||
const statusOptions = [
|
||||
{ value: '', label: '全部状态' },
|
||||
{ value: 'DRAFT', label: '未提交' },
|
||||
{ value: 'PENDING', label: '审批中' },
|
||||
{ value: 'REJECTED', label: '已驳回' },
|
||||
{ value: 'WITHDRAWN', label: '已撤回' },
|
||||
{ value: 'APPROVED', label: '审批完成' },
|
||||
{ value: 'ARCHIVED', label: '已归档' }
|
||||
];
|
||||
const statusLabels = statusOptions.map(option => option.label);
|
||||
|
||||
const activeTab = ref<TabKey>('mine');
|
||||
const activeLabel = computed(
|
||||
() => tabs.find(tab => tab.key === activeTab.value)?.label || ''
|
||||
);
|
||||
const statusIndex = ref(0);
|
||||
const keywords = ref('');
|
||||
const list = ref<ApprovalItem[]>([]);
|
||||
const page = ref(1);
|
||||
const limit = 10;
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
|
||||
onLoad(() => {
|
||||
load(true);
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (activeTab.value === 'mine') {
|
||||
load(true);
|
||||
}
|
||||
});
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
if (activeTab.value === 'mine') {
|
||||
await load(true);
|
||||
}
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
if (activeTab.value === 'mine' && !loading.value && !finished.value) {
|
||||
load(false);
|
||||
}
|
||||
});
|
||||
|
||||
function switchTab(key: TabKey) {
|
||||
if (activeTab.value === key) {
|
||||
return;
|
||||
}
|
||||
activeTab.value = key;
|
||||
if (key === 'mine' && list.value.length === 0) {
|
||||
load(true);
|
||||
}
|
||||
}
|
||||
|
||||
function onStatusChange(event: { detail: { value: string | number } }) {
|
||||
statusIndex.value = Number(event.detail.value);
|
||||
load(true);
|
||||
}
|
||||
|
||||
async function load(reset: boolean) {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
if (reset) {
|
||||
page.value = 1;
|
||||
finished.value = false;
|
||||
}
|
||||
const result = await pageMine({
|
||||
page: page.value,
|
||||
limit,
|
||||
keywords: keywords.value || undefined,
|
||||
status: statusOptions[statusIndex.value].value || undefined
|
||||
});
|
||||
total.value = Number(result.count) || 0;
|
||||
list.value = reset ? result.list || [] : [...list.value, ...(result.list || [])];
|
||||
finished.value = list.value.length >= total.value;
|
||||
if (!finished.value) {
|
||||
page.value += 1;
|
||||
}
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error?.message || '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reload() {
|
||||
load(true);
|
||||
}
|
||||
|
||||
function openDetail(id: number) {
|
||||
uni.navigateTo({ url: '/packageApproval/pages/detail?id=' + id });
|
||||
}
|
||||
|
||||
function createDraft() {
|
||||
uni.navigateTo({
|
||||
url: '/packageApproval/pages/form-completion?mode=create'
|
||||
});
|
||||
}
|
||||
|
||||
function formatTime(value?: string) {
|
||||
if (!value) {
|
||||
return '—';
|
||||
}
|
||||
return value.replace('T', ' ').substring(0, 19);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.approval-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6f8;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
padding: 12rpx 24rpx;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
border-bottom: 4rpx solid transparent;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #d81e06;
|
||||
font-weight: 600;
|
||||
border-bottom-color: #d81e06;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter {
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
background: #f2f3f5;
|
||||
border-radius: 12rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 26rpx;
|
||||
background: #f2f3f5;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
margin: 12rpx 16rpx;
|
||||
}
|
||||
|
||||
.item-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #f2f3f5;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.status-DRAFT {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.status-PENDING {
|
||||
background: #fff7e6;
|
||||
color: #d46b08;
|
||||
}
|
||||
|
||||
.status-REJECTED {
|
||||
background: #fff1f0;
|
||||
color: #cf1322;
|
||||
}
|
||||
|
||||
.status-WITHDRAWN {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.status-APPROVED {
|
||||
background: #f6ffed;
|
||||
color: #389e0d;
|
||||
}
|
||||
|
||||
.status-ARCHIVED {
|
||||
background: #e6f4ff;
|
||||
color: #0958d9;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 140rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 160rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty,
|
||||
.loading,
|
||||
.finished {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 80rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: #d81e06;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 20rpx rgba(216, 30, 6, 0.35);
|
||||
}
|
||||
|
||||
.fab-text {
|
||||
color: #ffffff;
|
||||
font-size: 56rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user