Files
im-uniapp/pages/workbench/hrm/cc/cc.vue
砂糖 d1eb3d61cf feat(审批): 新增审批中心页面及功能优化
- 新增审批中心页面,包含待审批任务列表展示和筛选功能
- 优化各审批类型详情页,增加项目信息展示和弹窗查看功能
- 在报销、用印、请假、出差详情页添加项目信息展示区域和详情弹窗
- 调整消息中心CC列表,增加未读状态红点标识
- 更新tabbar配置,新增审批入口图标和路由
- 优化部分页面交互细节和样式调整
2026-02-05 15:52:09 +08:00

588 lines
13 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="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)">
<!-- 申请类型标签 -->
<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>
{{ item.bizTitle }}
</view>
<view class="request-info">{{ item.createBy }}</view>
<!-- <view class="node-info">当前节点{{ formatNodeName(item) }}</view> -->
<view class="time-info">{{ formatDate(item.createTime) }}</view>
</view>
<!-- 状态标签 -->
<view v-if="!item.readFlag" class="status-tag" style="border-radius: 50%; background-color: red; width: 20rpx; height: 20rpx; padding: 0;">
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { listCc, readCc } from '@/api/hrm/cc';
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'
}
],
bizTypeIndex: 0,
// 审批操作弹窗
actionDialog: {
visible: false,
title: '',
type: '', // approve/reject
task: null
},
actionForm: {
remark: ''
},
actionSubmitting: false
}
},
onLoad() {
this.loadEmployees();
},
onShow() {
this.loadTodoList();
},
computed: {
currentUserId() {
return this.$store.getters.storeOaId
}
},
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}`;
}
return '-';
},
// 格式化节点名称
formatNodeName(task) {
return `节点 #${task.nodeId}`;
},
// 获取申请类型文本
getBizTypeText(type) {
const map = {
leave: '请假',
travel: '出差',
seal: '用印',
reimburse: '报销'
};
return map[type] || type || '-';
},
// 获取申请类型标签样式
getBizTypeTagType(type) {
const map = {
leave: 'primary',
travel: 'success',
seal: 'warning',
reimburse: '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 listCc({ ccUserId: this.currentUserId });
let list = res.rows || [];
// 前端过滤 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;
}
readCc(task.ccId)
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>