Files
im-uniapp/pages/hrm/approve/approve.vue

632 lines
14 KiB
Vue
Raw Normal View History

<template>
<view class="hrm-page">
<!-- 顶部统计栏 -->
<view class="summary-bar">
<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 { getUserProfile } from '@/api/oa/user.js';
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,
currentOaUserId: ''
}
},
onShow() {
this.fetchUserProfile().then(() => {
this.loadEmployees();
this.loadTodoList();
});
},
methods: {
2026-04-14 10:25:13 +08:00
// 格式化员工信息展示不展示各类ID
formatEmpLabel(emp) {
if (!emp) return '';
const name = emp.empName || emp.nickName || emp.userName || '';
const dept = emp.deptName ? ` · ${emp.deptName}` : '';
2026-04-14 10:25:13 +08:00
return `${name || '员工'}${dept}`.trim();
},
async fetchUserProfile() {
2026-04-14 10:25:13 +08:00
try {
const response = await getUserProfile();
const user = response?.data?.user || {};
const roles = user.roles?.map(item => item.roleKey) || [];
this.$store.commit('oa/SET_STATE', user);
this.currentOaUserId = user.userId || '';
2026-04-14 10:25:13 +08:00
this.roleGroup = roles;
return user;
} catch (error) {
console.error('获取用户个人信息失败:', error);
this.roleGroup = [];
return null;
}
},
2026-04-14 10:25:13 +08:00
// 格式化申请人信息优先展示姓名不展示ID
formatApplicant(task) {
2026-04-14 10:25:13 +08:00
if (!task) return '未指定';
const biz = task.bizData || {};
const directName =
biz.empName ||
biz.applicantName ||
biz.userName ||
biz.nickName ||
task.empName ||
task.applicantName ||
task.userName ||
task.nickName ||
'';
if (directName) {
return directName;
}
const empId = biz.empId || task.empId;
const emp = this.employees.find(e => String(e.empId) === String(empId));
if (emp) {
return this.formatEmpLabel(emp);
}
2026-04-14 10:25:13 +08:00
return '未指定';
},
// 格式化申请信息
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 = [];
});
},
2026-04-14 10:25:13 +08:00
// 获取当前用户ID优先 store其次接口返回
resolveCurrentUserId(profileUser) {
const storeId = this.$store?.getters?.storeOaId || this.$store?.state?.oa?.id;
if (storeId) return storeId;
return profileUser?.userId || profileUser?.id || profileUser?.empId || '';
},
// 加载待办列表
async loadTodoList() {
this.loading = true;
try {
// const profileUser = await this.fetchUserProfile();
// const userId = this.resolveCurrentUserId(profileUser);
// if (!userId) {
// this.todoList = [];
// this.todoCount = 0;
// uni.showToast({
// title: '当前登录信息未就绪,请稍后重试',
// icon: 'none'
// });
// return;
// }
const res = await listTodoFlowTask(this.currentOaUserId);
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;
2026-04-14 10:25:13 +08:00
this.todayCount = 0;
} catch (err) {
2026-04-14 10:25:13 +08:00
console.error('加载待办任务失败:', err);
this.todoList = [];
this.todoCount = 0;
2026-04-14 10:25:13 +08:00
uni.showToast({
title: '加载待办任务失败',
icon: 'none'
});
} 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-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>