Files
klp-oa/klp-ui/src/views/wms/hrm/todo/index.vue
砂糖 5b6286326b feat(HRM): 添加附件显示组件并优化表单逻辑
添加FileList组件用于显示附件列表
在请假和外出申请详情页中显示附件
优化审批部门选择逻辑,仅在新增时显示
修复请假申请编辑时的审批类型校验问题
2026-03-11 16:48:44 +08:00

505 lines
16 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>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧筛选区域 - 自定义样式替代el-tabs -->
<el-col :span="4">
<div class="filter-container">
<!-- <h4>筛选条件</h4> -->
<!-- 申请类型自定义选项卡替代el-tabs -->
<!-- <div class="custom-tabs">
<div class="custom-tabs-header">申请类型</div>
<div class="custom-tabs-nav">
<div
class="custom-tabs-item"
:class="{ active: queryParams.applyType === undefined || queryParams.applyType === '' }"
@click="handleTabClick('applyType', '')"
>
全部
</div>
<div
class="custom-tabs-item"
:class="{ active: queryParams.applyType === 'leave' }"
@click="handleTabClick('applyType', 'leave')"
>
请假
</div>
<div
class="custom-tabs-item"
:class="{ active: queryParams.applyType === 'out' }"
@click="handleTabClick('applyType', 'out')"
>
外出
</div>
</div>
</div> -->
<!-- 审批状态自定义选项卡替代el-tabs -->
<div class="custom-tabs">
<div class="custom-tabs-header">审批状态</div>
<div class="custom-tabs-nav">
<div
class="custom-tabs-item"
:class="{ active: queryParams.taskStatus === undefined || queryParams.taskStatus === '' }"
@click="handleTabClick('taskStatus', '')"
>
全部
</div>
<div
class="custom-tabs-item"
:class="{ active: queryParams.taskStatus === 'pending' }"
@click="handleTabClick('taskStatus', 'pending')"
>
待审批
</div>
<div
class="custom-tabs-item"
:class="{ active: queryParams.taskStatus === 'approved' }"
@click="handleTabClick('taskStatus', 'approved')"
>
已同意
</div>
<div
class="custom-tabs-item"
:class="{ active: queryParams.taskStatus === 'rejected' }"
@click="handleTabClick('taskStatus', 'rejected')"
>
已驳回
</div>
</div>
</div>
<!-- 重置按钮 -->
<!-- <el-button type="text" @click="resetQuery" style="width: 100%">重置筛选</el-button> -->
</div>
</el-col>
<!-- 右侧表格区域修复el-table多余的total属性 -->
<el-col :span="20">
<el-table
v-loading="loading"
:data="todoList"
border
stripe
style="width: 100%"
@selection-change="handleSelectionChange"
>
<!-- 通用列 -->
<el-table-column prop="applicantName" label="申请人" align="center" />
<el-table-column prop="createBy" label="发起人" align="center" />
<el-table-column prop="applyType" label="申请类型" align="center">
<template slot-scope="scope">
{{ getApplyTypeText(scope.row.applyType) }}
</template>
</el-table-column>
<!-- <el-table-column prop="applicantDeptName" label="审批部门" align="center" show-overflow-tooltip/> -->
<el-table-column prop="startTime" label="开始时间" align="center">
<template slot-scope="scope">
{{ scope.row.startTime ? formatTime(scope.row.startTime) : '-' }}
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" align="center">
<template slot-scope="scope">
{{ scope.row.endTime ? formatTime(scope.row.endTime) : '-' }}
</template>
</el-table-column>
<el-table-column prop="taskStatus" label="整体状态" align="center">
<template slot-scope="scope">
<el-tag :type="getInstStatusTagType(scope.row.approvalStatus)">
{{ getInstStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="taskStatus" label="审批状态" align="center">
<template slot-scope="scope">
<el-tag :type="getStatusTagType(scope.row.taskStatus)">
{{ getStatusText(scope.row.taskStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="240">
<template slot-scope="scope">
<el-button
size="small"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>
详情
</el-button>
<el-button
v-if="scope.row.taskStatus === 'pending'"
size="small"
icon="el-icon-check"
@click="handleApprove(scope.row)"
:loading="buttonLoading"
>
同意
</el-button>
<el-button
v-if="scope.row.taskStatus === 'pending'"
size="small"
icon="el-icon-close"
@click="handleReject(scope.row)"
:loading="buttonLoading"
>
驳回
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="queryParams.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 20px; text-align: right"
>
</el-pagination>
</el-col>
</el-row>
<!-- 详情弹窗无变化 -->
<el-dialog
title="申请详情"
:visible.sync="detailDialogVisible"
width="600px"
destroy-on-close
>
<div v-if="currentDetail.applyType === 'leave'" class="detail-content">
<el-descriptions :column="1" border>
<el-descriptions-item label="请假标题">{{ currentDetail.leaveTitle || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假类型">{{ currentDetail.leaveType || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请人">{{ currentDetail.applicantName || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请部门">{{ currentDetail.applicantDeptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假开始时间">{{ formatTime(currentDetail.startTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假结束时间">{{ formatTime(currentDetail.endTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假班次">{{ currentDetail.leaveShift || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假天数">{{ currentDetail.leaveDays || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假原因">{{ currentDetail.leaveReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ currentDetail.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="附件">
<FileList :ossIds="currentDetail.attachmentUrls" />
</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="currentDetail.applyType === 'out'" class="detail-content">
<el-descriptions :column="1" border>
<el-descriptions-item label="外出类型">{{ currentDetail.outType || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请人">{{ currentDetail.applicantName || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请部门">{{ currentDetail.applicantDeptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="外出开始时间">{{ formatTime(currentDetail.startTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="外出结束时间">{{ formatTime(currentDetail.endTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="外出时长(小时)">{{ currentDetail.outHours || '-' }}</el-descriptions-item>
<el-descriptions-item label="外出地点">{{ currentDetail.outPlace || '-' }}</el-descriptions-item>
<el-descriptions-item label="外出原因">{{ currentDetail.outReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ currentDetail.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="附件">
<FileList :ossIds="currentDetail.attachmentUrls" />
</el-descriptions-item>
</el-descriptions>
</div>
<template v-slot:footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { listApprovalTask, rejectApprovalTask, resolveApprovalTask } from '@/api/wms/approvalTask'
import FileList from '@/components/FileList/index.vue'
export default {
name: 'TodoList',
components: {
FileList,
},
data() {
return {
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
approveStatus: '', // 审批状态pending(待审批)、approved(已审批)、rejected(已驳回)、withdrawn(已撤回)
applyType: '', // 申请类型leave(请假)、out(外出),空为全部
},
// 列表数据
todoList: [],
total: 0,
loading: false,
buttonLoading: false,
// 详情弹窗相关
detailDialogVisible: false,
currentDetail: {},
}
},
// 新增从Vuex获取nickName
computed: {
nickName() {
return this.$store.getters.nickName
},
userId() {
return this.$store.getters.id
}
},
created() {
// 页面加载时查询列表
this.getTodoList()
},
methods: {
// 查询待办列表 - 核心修改新增approveName参数
getTodoList() {
this.loading = true
// 拼接请求参数新增approveName
const requestParams = {
...this.queryParams,
approverId: this.userId,
}
listApprovalTask(requestParams)
.then((res) => {
this.todoList = res.rows.map((item) => {
return {
approvalStatus: item.approval.approvalStatus,
taskStatus: item.task.taskStatus,
applyId: item.approval.applyId,
approvalId: item.approval.approvalId,
applyType: item.approval.applyType,
approverName: item.approval.approverName,
taskId: item.task.taskId,
...item.detail, // 合并请假/外出的详情字段
}
})
this.total = res.total
this.loading = false
})
.catch((err) => {
this.loading = false
this.$message.error('查询待办列表失败:' + err.message)
})
},
// 自定义tab点击事件替代原el-tabs的tab-click
handleTabClick(field, value) {
this.queryParams[field] = value
this.handleQuery()
},
// 处理筛选查询
handleQuery() {
this.queryParams.pageNum = 1 // 筛选时重置页码
this.getTodoList()
},
// 重置筛选条件
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
approveStatus: '',
applyType: '',
}
this.getTodoList()
},
// 分页-每页条数变更
handleSizeChange(val) {
this.queryParams.pageSize = val
this.getTodoList()
},
// 分页-当前页变更
handleCurrentChange(val) {
this.queryParams.pageNum = val
this.getTodoList()
},
// 查看详情
handleDetail(row) {
this.currentDetail = { ...row } // 深拷贝避免原数据被修改
this.detailDialogVisible = true
},
// 审批通过
handleApprove(row) {
this.$confirm('确定要审批通过该申请吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
this.buttonLoading = true
try {
const approvalTime = this.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
// 这里替换为你的审批接口调用
console.log(row)
await resolveApprovalTask(row.taskId)
this.$message.success('审批通过成功!')
this.getTodoList() // 重新查询列表
} catch (err) {
this.$message.error('审批通过失败:' + err.message)
} finally {
this.buttonLoading = false
}
})
},
// 驳回申请
handleReject(row) {
this.$confirm('确定要驳回该申请吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
required: true,
}).then(async ({ value }) => {
this.buttonLoading = true
try {
console.log(row)
const approvalTime = this.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
// 这里替换为你的驳回接口调用
await rejectApprovalTask(row.taskId)
console.log(row.taskId)
this.$message.success('驳回成功!')
this.getTodoList() // 重新查询列表
} catch (err) {
this.$message.error('驳回失败:' + err.message)
} finally {
this.buttonLoading = false
}
})
},
// 格式化时间(如果项目没有内置工具,可使用此方法)
formatTime(time) {
if (!time) return '-'
return this.parseTime(time, '{y}-{m}-{d} {h}')
},
// 获取审批状态对应的标签类型
getInstStatusTagType(status) {
const typeMap = {
'待审批': 'warning',
'已同意': 'success',
'已驳回': 'danger',
'已撤销': 'info',
}
return typeMap[status] || 'default'
},
// 获取审批状态的中文文本
getInstStatusText(status) {
const textMap = {
'待审批': '待审批',
'已同意': '已同意',
'已驳回': '已驳回',
'已撤销': '已撤销',
}
return textMap[status] || '未知状态'
},
// 获取审批状态对应的标签类型
getStatusTagType(status) {
const typeMap = {
'pending': 'warning',
'approved': 'success',
'rejected': 'danger',
'withdrawn': 'info',
}
return typeMap[status] || 'default'
},
// 获取审批状态的中文文本
getStatusText(status) {
const textMap = {
'pending': '待审批',
'approved': '已同意',
'rejected': '已驳回',
'withdrawn': '已撤销',
}
return textMap[status] || '未知状态'
},
getApplyTypeText(type) {
const textMap = {
'leave': '请假',
'out': '外出',
}
return textMap[type] || '未知类型'
},
// 表格多选事件(可选,根据需求使用)
handleSelectionChange(val) {
// 可用于批量操作
console.log('选中的数据:', val)
},
},
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.filter-container {
background: #f5f7fa;
/* padding: 20px; */
border-radius: 4px;
height: fit-content;
}
/* 自定义tabs样式 */
.custom-tabs {
margin-bottom: 20px;
width: 100%;
}
.custom-tabs-header {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
font-weight: 500;
}
.custom-tabs-nav {
display: flex;
flex-direction: column;
gap: 4px;
width: 100%;
}
.custom-tabs-item {
padding: 10px 15px;
text-align: center;
border-radius: 2px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
background: #ffffff;
border: 1px solid #e4e7ed;
}
.custom-tabs-item:hover {
background: #ecf5ff;
border-color: #c6e2ff;
}
.custom-tabs-item.active {
background: #667996;
color: #ffffff;
border-color: #667996;
}
.detail-content {
margin-top: 10px;
}
.el-descriptions {
--el-descriptions-item-label-color: #606266;
--el-descriptions-item-content-color: #303133;
}
</style>