增强办公,新增审批,缺少电子签章功能

This commit is contained in:
2026-04-22 13:10:58 +08:00
parent 3db80e5ff7
commit 244dc8a056
6 changed files with 287 additions and 43 deletions

View File

@@ -65,7 +65,7 @@
<view class="sub-tabs sub-tabs--underline">
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'all' }" @click="switchMyApplyStatusTab('all')">全部</view>
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'running' }" @click="switchMyApplyStatusTab('running')">审批中</view>
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'approved' }" @click="switchMyApplyStatusTab('approved')">已通过</view>
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'approved' }" @click="switchMyApplyStatusTab('approved')">出差状态</view>
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'rejected' }" @click="switchMyApplyStatusTab('rejected')">已驳回</view>
</view>
@@ -89,7 +89,7 @@
<uni-icons type="empty" size="60" color="#909399"></uni-icons>
<view class="empty-text">暂无我的申请</view>
</view>
<view v-else class="list-item" v-for="(item, index) in myApplyList" :key="item.instId || item.id || index" @click="goDetail(item)">
<view v-else class="list-item my-apply-item" v-for="(item, index) in myApplyList" :key="item.instId || item.id || index" @click="goDetail(item)">
<view class="item-tag" :class="getBizTypeTagType(item.bizType)">{{ getBizTypeText(item.bizType) }}</view>
<view class="item-main">
<view class="applicant">
@@ -99,9 +99,9 @@
<view class="request-info">{{ formatMyApplyInfo(item) }}</view>
<view class="time-info">{{ formatDate(item.createTime || item.applyTime || item.startTime) }}</view>
</view>
<view class="right-section">
<view class="status-tag" :class="statusType(item.status)">{{ myApplyStatusText(item) }}</view>
<view v-if="item.bizType === 'travel' && (item.status === 'approved' || item.status === 'finished') && !item.actualEndTime && !item.endTime && !item.realEndTime" class="early-end-btn" @click.stop="goTravelEarlyEnd(item)">提前结束</view>
<view class="my-apply-actions">
<view v-if="canShowMyApplyEarlyEnd(item)" class="my-apply-early-end" @click.stop="goTravelEarlyEnd(item)">提前结束</view>
<view class="status-tag" :class="myApplyStatusClass(item)">{{ myApplyStatusText(item) }}</view>
</view>
</view>
</scroll-view>
@@ -126,6 +126,7 @@
import { getUserProfile } from '@/api/oa/user.js';
import { approveFlowTask, listMyFlowInstance, listTodoFlowTask, listDoneFlowTask, rejectFlowTask } from '@/api/hrm/flow';
import { earlyEndTravelReq } from '@/api/hrm/travel';
import { listCc } from '@/api/hrm/cc';
export default {
name: 'HrmApproval',
@@ -180,9 +181,9 @@ export default {
computed: {
currentList() {
if (this.activeTopTab === 'myApply') return this.myApplyList;
if (this.activeApprovalTab === 'todo') return this.todoList;
if (this.activeApprovalTab === 'done') return this.doneList;
return this.ccList;
if (this.activeApprovalTab === 'todo') return this.todoList.filter(item => item.bizType !== 'seal');
if (this.activeApprovalTab === 'done') return this.doneList.filter(item => item.bizType !== 'seal');
return this.ccList.filter(item => item.bizType !== 'seal');
},
emptyText() {
if (this.activeTopTab === 'myApply') return '暂无我的申请';
@@ -286,8 +287,8 @@ export default {
async loadCcList() {
this.loading = true;
try {
const res = await listMyFlowInstance({ pageNum: 1, pageSize: 200 });
let list = (res.rows || res.data || []).filter(item => item.ccFlag === 1 || item.isCc === 1 || item.readFlag === 0);
const res = await listCc({ ccUserId: this.currentOaUserId, pageNum: 1, pageSize: 200 });
let list = res.rows || res.data || [];
if (this.query.bizType) list = list.filter(item => item.bizType === this.query.bizType);
this.ccList = list;
this.summary.cc = list.length;
@@ -356,17 +357,22 @@ export default {
if (this.myApplyStatusTab === 'rejected') return ['rejected', 'reject'].includes(s);
return true;
},
myApplyStatusText(item) {
if (item?.bizType === 'travel') {
return item?.actualEndTime ? '已结束' : '出差中';
}
return this.statusText(item?.status);
},
myApplyStatusClass(item) {
if (item?.bizType === 'travel') {
return item?.actualEndTime ? 'travel-ended' : 'travel-running';
}
return this.statusType(item?.status);
},
statusText(status) {
const map = { pending: '待审批', draft: '草稿', approved: '已通过', rejected: '已驳回', running: '审批中', finished: '已完成', revoked: '已撤销' };
return map[status] || status || '-';
},
myApplyStatusText(item) {
if (item.bizType === 'travel' && (item.status === 'approved' || item.status === 'finished')) {
const endTime = item.actualEndTime || item.endTime || item.realEndTime;
return endTime ? '已结束' : '出差中';
}
return this.statusText(item.status);
},
statusType(status) {
const map = { pending: 'warning', running: 'warning', draft: 'info', approved: 'success', rejected: 'danger', finished: 'success', revoked: 'danger' };
return map[status] || 'info';
@@ -408,6 +414,10 @@ export default {
},
goDetail(task) {
if (!task) return;
if (task.bizType === 'seal') {
uni.showToast({ title: '用印审批暂时关闭', icon: 'none' });
return;
}
const bizId = task.bizId || task.instId;
const bizType = task.bizType;
if (!bizType || !bizId) {
@@ -426,10 +436,49 @@ export default {
};
uni.navigateTo({ url: map[type] || '/pages/workbench/hrm/apply/apply' });
},
canShowMyApplyEarlyEnd(item) {
return item?.bizType === 'travel' && item?.actualEndTime == null;
},
goTravelEarlyEnd(task) {
if (!task || task.bizType !== 'travel') {
uni.showToast({ title: '仅出差申请支持提前结束', icon: 'none' });
return;
}
const bizId = task.bizId || task.instId;
if (!bizId) {
uni.showToast({ title: '缺少出差单编号', icon: 'none' });
return;
}
uni.showModal({
title: '提前结束出差',
content: '确定要提前结束该出差吗?',
success: (res) => {
if (!res.confirm) return;
earlyEndTravelReq(bizId)
.then(() => {
uni.showToast({ title: '提前结束成功', icon: 'none' });
this.refreshCurrentList();
})
.catch((err) => {
console.error('提前结束失败:', err);
uni.showToast({ title: '提前结束失败', icon: 'none' });
});
}
});
},
goTravelEarlyEnd() {
uni.showToast({ title: '请进入出差详情后发起提前结束', icon: 'none' });
},
showActionMenu(task) {
if (task?.bizType === 'seal') {
this.goDetail(task);
return;
}
if (this.activeApprovalTab === 'cc' || this.activeApprovalTab === 'done') {
this.goDetail(task);
return;
}
if (this.activeApprovalTab !== 'todo') {
this.goDetail(task);
return;
@@ -706,11 +755,68 @@ export default {
.item-tag,
.status-tag {
padding: 0;
border-radius: 0;
padding: 4rpx 14rpx;
border-radius: 999rpx;
font-size: 22rpx;
line-height: 1.4;
background: #f3f4f6;
color: #6b7280;
background: transparent !important;
}
.status-tag.primary {
background: rgba(22, 119, 255, 0.12);
color: #1677ff;
}
.status-tag.success {
background: rgba(34, 197, 94, 0.12);
color: #16a34a;
}
.status-tag.warning,
.travel-running {
background: rgba(245, 158, 11, 0.12);
color: #d97706;
}
.status-tag.danger {
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
.status-tag.info,
.travel-ended {
background: #f3f4f6;
color: #9ca3af;
}
.travel-running {
color: #f59e0b;
}
.travel-ended {
color: #9ca3af;
}
.my-apply-actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
justify-content: flex-end;
flex-shrink: 0;
margin-left: 12rpx;
}
.my-apply-early-end {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: #1677ff;
color: #fff;
font-size: 22rpx;
line-height: 1;
white-space: nowrap;
box-shadow: 0 6rpx 14rpx rgba(22, 119, 255, 0.22);
}
.item-main {
@@ -732,25 +838,6 @@ export default {
.request-info { font-size: 26rpx; color: #6b7280; }
.time-info { font-size: 24rpx; color: #9ca3af; }
.early-end-btn {
flex: 0 0 auto;
padding: 6rpx 16rpx;
font-size: 22rpx;
color: #1677ff;
background: #eef4ff;
border-radius: 6rpx;
border: 1rpx solid #1677ff;
}
.right-section {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
gap: 8rpx;
padding-right: 8rpx;
}
.apply-panel {
padding-top: 16rpx;
}