Files
im-uniapp/pages/workbench/hrm/approve/approve.vue
砂糖 05ff3391c7 feat(hrm): 新增拨款申请功能并修复仓储显示问题
- 添加拨款申请相关组件、API及页面逻辑
- 修复仓储模块列表显示边框和内容插槽问题
- 更新版本记录至5.2.3版本
2026-04-14 09:33:58 +08:00

613 lines
14 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="hrm-page">
<!-- 顶部统计栏 -->
<view class="summary-bar">
<view class="summary-left">
<view class="page-title">审批中心</view>
<view class="page-desc">集中查看与处理待办审批</view>
</view>
<view class="summary-right">
<view class="metric">
<view class="metric-value">{{ todoCount }}</view>
<view class="metric-label">待审批</view>
</view>
</view>
</view>
<!-- 筛选栏 -->
<view class="filter-bar">
<view style="display: flex; align-items: center; gap: 16rpx; flex: 1">
<span class="filter-label">申请类型</span>
<picker @change="handleBizTypeChange" :value="bizTypeIndex" :range="bizTypeList" range-key="label">
<view class="picker-text">{{ bizTypeList[bizTypeIndex].label }}</view>
</picker>
</view>
<button class="refresh-btn" size="mini" @click="loadTodoList">
刷新
</button>
</view>
<!-- 待审批列表 -->
<scroll-view class="approval-list" scroll-y>
<!-- 加载中 -->
<view v-if="loading" class="loading-container">
<uni-load-more type="loading" color="#409EFF"></uni-load-more>
</view>
<!-- 空数据 -->
<view v-else-if="todoList.length === 0" class="empty-container">
<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 todoList" :key="index" @click="goDetail(item)"
@longpress="showActionMenu(item)">
<!-- 申请类型标签 -->
<view class="item-tag" :class="getBizTypeTagType(item.bizType)">
{{ getBizTypeText(item.bizType) }}
</view>
<!-- 核心信息 -->
<view class="item-main">
<view class="applicant">
<uni-icons type="user" size="14" color="#8a8f99"></uni-icons>
{{ formatApplicant(item) }}
</view>
<view class="request-info">{{ formatRequestInfo(item) }}</view>
<!-- <view class="node-info">当前节点{{ formatNodeName(item) }}</view> -->
<view class="time-info">{{ formatDate(item.createTime) }}</view>
</view>
<!-- 状态标签 -->
<view class="status-tag" :class="statusType(item.status)">
{{ statusText(item.status) }}
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
listEmployee
} from '@/api/hrm';
import {
approveFlowTask,
listTodoFlowTask,
rejectFlowTask
} from '@/api/hrm/flow';
export default {
name: 'HrmApproval',
data() {
return {
// 员工列表
employees: [],
// 待审批列表
todoList: [],
loading: false,
todoCount: 0,
todayCount: 0,
// 筛选条件
query: {
bizType: ''
},
// 申请类型筛选器配置
bizTypeList: [{
label: '全部',
value: ''
},
{
label: '请假',
value: 'leave'
},
{
label: '出差',
value: 'travel'
},
{
label: '用印',
value: 'seal'
},
{
label: '报销',
value: 'reimburse'
},
{
label: '拨款',
value: 'appropriation'
}
],
bizTypeIndex: 0,
// 审批操作弹窗
actionDialog: {
visible: false,
title: '',
type: '', // approve/reject
task: null
},
actionForm: {
remark: ''
},
actionSubmitting: false
}
},
onLoad() {
this.loadEmployees();
},
onShow() {
this.loadTodoList();
},
methods: {
// 格式化员工信息展示
formatEmpLabel(emp) {
if (!emp) return '';
const name = emp.empName || emp.nickName || emp.userName || '';
const no = emp.empNo ? ` · ${emp.empNo}` : '';
const dept = emp.deptName ? ` · ${emp.deptName}` : '';
return `${name || '员工'}${no}${dept}`.trim();
},
// 格式化申请人信息
formatApplicant(task) {
if (!task.bizData) return '加载中...';
const empId = task.bizData.empId;
const emp = this.employees.find(e => String(e.empId) === String(empId));
if (emp) {
return this.formatEmpLabel(emp);
}
return empId ? `员工ID:${empId}` : '未指定';
},
// 格式化申请信息
formatRequestInfo(task) {
if (!task.bizData) return '加载中...';
const biz = task.bizData;
if (task.bizType === 'leave') {
return `${biz.leaveType || '请假'} · ${this.formatDuration(biz)}`;
} else if (task.bizType === 'travel') {
return `${biz.travelType || '出差'} · ${biz.destination || ''}`;
} else if (task.bizType === 'seal') {
return `${biz.sealType || '用印'} · ${biz.applyFileIds ? '已上传文件' : '未上传'}`;
} else if (task.bizType === 'reimburse') {
const amt = biz.totalAmount != null ? biz.totalAmount : 0;
return `${biz.reimburseType || '报销'} · 金额: ${amt}`;
} else if (task.bizType === 'appropriation') {
const amt = biz.amount != null ? biz.amount : 0;
return `${biz.appropriationType || '拨款'} · 金额: ${amt}`;
}
return '-';
},
// 格式化节点名称
formatNodeName(task) {
return `节点 #${task.nodeId}`;
},
// 获取申请类型文本
getBizTypeText(type) {
const map = {
leave: '请假',
travel: '出差',
seal: '用印',
reimburse: '报销',
appropriation: '拨款'
};
return map[type] || type || '-';
},
// 获取申请类型标签样式
getBizTypeTagType(type) {
const map = {
leave: 'primary',
travel: 'success',
seal: 'warning',
reimburse: 'danger',
appropriation: 'danger'
};
return map[type] || 'info';
},
// 获取状态文本
statusText(status) {
const map = {
pending: '待审批',
draft: '草稿',
approved: '已通过',
rejected: '已驳回'
};
return map[status] || status || '-';
},
// 获取状态标签样式
statusType(status) {
if (!status) return 'info';
const map = {
pending: 'warning',
draft: 'info',
approved: 'success',
rejected: 'danger'
};
return map[status] || 'info';
},
// 格式化日期
formatDate(val) {
if (!val) return '';
const d = new Date(val);
const p = n => (n < 10 ? `0${n}` : n);
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
},
// 格式化时长
formatDuration(biz) {
if (biz.hours) return `${biz.hours}h`;
if (biz.startTime && biz.endTime) {
const ms = new Date(biz.endTime).getTime() - new Date(biz.startTime).getTime();
if (ms > 0) return `${(ms / 3600000).toFixed(1)}h`;
}
return '-';
},
// 加载员工列表
loadEmployees() {
listEmployee({
pageNum: 1,
pageSize: 1000
}).then(res => {
this.employees = res.rows || res.data || [];
}).catch(() => {
this.employees = [];
});
},
// 加载待办列表
async loadTodoList() {
this.loading = true;
try {
const userId = this.$store?.getters.storeOaId;
console.log(this.$store?.getters.storeOaId)
if (!userId) {
uni.showToast({
title: '无法获取当前用户信息,请重新登录',
icon: 'error'
});
this.loading = false;
return;
}
const res = await listTodoFlowTask(userId);
let list = res.data || [];
// 前端过滤 bizType
if (this.query.bizType) {
list = list.filter(item => item.bizType === this.query.bizType);
}
this.todoList = list;
this.todoCount = list.length;
this.todayCount = 0; // 可根据实际需求实现今日处理数量统计
} catch (err) {
console.error('加载待办任务失败:', err);
uni.showToast({
title: '加载待办任务失败',
icon: 'error'
});
this.todoList = [];
this.todoCount = 0;
} finally {
this.loading = false;
}
},
// 跳转详情页
goDetail(task) {
if (!task || !task.bizId) {
uni.showToast({
title: '缺少bizId无法打开详情',
icon: 'none'
});
return;
}
if (!task || !task.bizType) {
uni.showToast({
title: '未知的申请类型',
icon: 'none'
});
return;
}
uni.navigateTo({
url: `/pages/workbench/hrm/detail/detail?bizId=${task.bizId}&bizType=${task.bizType}`
});
},
// 申请类型筛选变更
handleBizTypeChange(e) {
this.bizTypeIndex = e.detail.value;
this.query.bizType = this.bizTypeList[this.bizTypeIndex].value;
this.loadTodoList();
},
// 长按弹出操作菜单
showActionMenu(task) {
uni.showActionSheet({
itemList: ['查看详情', '审批通过', '审批驳回'],
success: (res) => {
switch (res.tapIndex) {
case 0: // 查看详情
this.goDetail(task);
break;
case 1: // 审批通过
approveFlowTask(task.taskId).then(() => {
uni.showToast({
title: '已通过审批',
icon: 'none'
})
this.loadTodoList()
})
break;
case 2: // 审批驳回
rejectFlowTask(task.taskId).then(() => {
uni.showToast({
title: '已驳回',
icon: 'none'
})
this.loadTodoList()
})
break;
}
},
fail: (res) => {
console.log('取消操作:', res);
}
});
},
// 提交审批操作
submitAction() {
if (!this.actionDialog.task) return;
this.actionSubmitting = true;
const {
task,
type
} = this.actionDialog;
const action = type === 'approve' ? approveFlowTask : rejectFlowTask;
action(task.taskId, {
remark: this.actionForm.remark
})
.then(() => {
uni.showToast({
title: type === 'approve' ? '审批通过' : '已驳回',
icon: 'success'
});
this.actionDialog.visible = false;
this.loadTodoList();
})
.catch(err => {
console.error('审批操作失败:', err);
uni.showToast({
title: '操作失败',
icon: 'error'
});
})
.finally(() => {
this.actionSubmitting = false;
this.actionForm.remark = '';
});
}
}
}
</script>
<style lang="scss" scoped>
.hrm-page {
min-height: 100vh;
background-color: #f8f9fb;
padding: 16rpx;
}
// 顶部统计栏
.summary-bar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16rpx;
padding: 20rpx;
margin-bottom: 16rpx;
border: 1px solid #e6e8ed;
border-radius: 10rpx;
background: #ffffff;
.summary-left {
.page-title {
font-size: 32rpx;
font-weight: 800;
color: #2b2f36;
line-height: 1.2;
}
.page-desc {
margin-top: 8rpx;
font-size: 24rpx;
color: #8a8f99;
}
}
.summary-right {
display: flex;
gap: 16rpx;
.metric {
min-width: 100rpx;
padding: 12rpx 16rpx;
border: 1px solid #e6e8ed;
border-radius: 10rpx;
background: #fcfdff;
text-align: center;
.metric-value {
font-size: 32rpx;
font-weight: 800;
color: #2b2f36;
line-height: 1.2;
}
.metric-label {
margin-top: 4rpx;
font-size: 24rpx;
color: #8a8f99;
}
}
}
}
// 筛选栏
.filter-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx;
background: #fff;
border-radius: 8rpx;
margin-bottom: 16rpx;
box-sizing: border-box;
.filter-label {
font-size: 28rpx;
color: #333;
}
.picker-text {
flex: 1;
font-size: 28rpx;
color: #666;
padding: 8rpx 16rpx;
border: 1px solid #e6e8ed;
border-radius: 6rpx;
}
.refresh-btn {
background-color: #409eff;
color: #fff;
border: none;
border-radius: 6rpx;
font-size: 24rpx;
padding: 8rpx 16rpx;
}
}
// 审批列表
.approval-list {
height: calc(100vh - 280rpx);
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
}
.empty-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 100rpx 0;
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #909399;
}
}
.list-item {
display: flex;
align-items: flex-start;
gap: 16rpx;
padding: 20rpx;
margin-bottom: 12rpx;
background: #fff;
border-radius: 10rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
.item-tag {
padding: 6rpx 12rpx;
border-radius: 4rpx;
font-size: 22rpx;
color: #fff;
&.primary {
background-color: #409eff;
}
&.success {
background-color: #67c23a;
}
&.warning {
background-color: #e6a23c;
}
&.danger {
background-color: #f56c6c;
}
&.info {
background-color: #909399;
}
}
.item-main {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
.applicant {
font-size: 28rpx;
color: #333;
display: flex;
align-items: center;
gap: 8rpx;
}
.request-info {
font-size: 26rpx;
color: #666;
}
.node-info {
font-size: 24rpx;
color: #888;
}
.time-info {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
}
.status-tag {
padding: 6rpx 12rpx;
border-radius: 4rpx;
font-size: 22rpx;
color: #fff;
&.warning {
background-color: #e6a23c;
}
&.info {
background-color: #909399;
}
&.success {
background-color: #67c23a;
}
&.danger {
background-color: #f56c6c;
}
}
}
}
// 审批意见输入框
.remark-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx;
border: 1px solid #e6e8ed;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
}
</style>