fix(工作报告): 修复工作报告存在的若干问题。
feat(加班申请): 支持批量审批。
This commit is contained in:
@@ -6,6 +6,8 @@ import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
|
||||
import { RDMS_REQ_PRIORITY_DICT_CODE } from '@/constants/dict';
|
||||
import {
|
||||
fetchApproveOvertimeApplication,
|
||||
fetchBatchApproveOvertimeApplication,
|
||||
fetchBatchRejectOvertimeApplication,
|
||||
fetchChangePersonalItemStatus,
|
||||
fetchChangeProjectTaskStatus,
|
||||
fetchGetMonthlyReportApprovalPage,
|
||||
@@ -28,13 +30,15 @@ import PersonalItemDetailDialog from '@/views/personal-center/my-item/modules/pe
|
||||
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
|
||||
import PersonalItemStatusActionDialog from '@/views/personal-center/my-item/modules/personal-item-status-action-dialog.vue';
|
||||
import OvertimeApplicationActionDialog from '@/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue';
|
||||
import OvertimeApplicationBatchDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-batch-detail-dialog.vue';
|
||||
import OvertimeApplicationDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue';
|
||||
import WorkReportPrototypePageDialog from '@/views/personal-center/work-report/shared/components/prototype-page-dialog.vue';
|
||||
import {
|
||||
WORK_REPORT_TYPE_LABEL,
|
||||
type WorkReportRow,
|
||||
type WorkReportType,
|
||||
formatPeriod
|
||||
formatPeriod,
|
||||
formatWeeklyPeriodLabel
|
||||
} from '@/views/personal-center/work-report/shared/types';
|
||||
import {
|
||||
type WorkbenchTodoDeadlineFilter,
|
||||
@@ -169,10 +173,19 @@ const overtimeActionVisible = ref(false);
|
||||
const overtimeActionSubmitting = ref(false);
|
||||
const currentOvertimeApplication = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
|
||||
const currentOvertimeActionType = ref<OvertimeApprovalActionType>('approve');
|
||||
const batchDetailVisible = ref(false);
|
||||
const batchActionVisible = ref(false);
|
||||
const batchSubmitting = ref(false);
|
||||
const workReportDetailVisible = ref(false);
|
||||
const currentWorkReport = ref<WorkReportRow | null>(null);
|
||||
const currentWorkReportType = ref<WorkReportType>('weekly');
|
||||
|
||||
// 批量审批选中状态(存原始加班申请 id,避免映射转换)
|
||||
const selectedOvertimeIds = ref<Set<string>>(new Set());
|
||||
|
||||
// 批量审批是否为当前操作来源(区分单条审批和批量审批的 submit 回调)
|
||||
const isBatchActionMode = ref(false);
|
||||
|
||||
const OVERTIME_APPROVAL_ACTION_ICONS = {
|
||||
detail: markRaw(IconMdiEyeOutline)
|
||||
};
|
||||
@@ -680,6 +693,97 @@ async function loadPersonalTodoItems() {
|
||||
);
|
||||
}
|
||||
|
||||
// 批量审批选中状态管理
|
||||
function isOvertimeItemSelected(item: WorkbenchTodoItem) {
|
||||
return item.approvalBizId ? selectedOvertimeIds.value.has(item.approvalBizId) : false;
|
||||
}
|
||||
|
||||
function toggleOvertimeItem(item: WorkbenchTodoItem, checked: boolean) {
|
||||
if (!item.approvalBizId) return;
|
||||
if (checked) {
|
||||
selectedOvertimeIds.value.add(item.approvalBizId);
|
||||
} else {
|
||||
selectedOvertimeIds.value.delete(item.approvalBizId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAllOvertimeItems(checked: boolean) {
|
||||
if (checked) {
|
||||
overtimeApprovalItems.value.forEach(item => {
|
||||
if (item.approvalBizId) selectedOvertimeIds.value.add(item.approvalBizId);
|
||||
});
|
||||
} else {
|
||||
selectedOvertimeIds.value.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function clearOvertimeSelection() {
|
||||
selectedOvertimeIds.value.clear();
|
||||
}
|
||||
|
||||
// 切换审批子页签时清空选中
|
||||
watch(activeApprovalBizType, () => {
|
||||
clearOvertimeSelection();
|
||||
});
|
||||
|
||||
// 当前页加班申请是否全部选中(用于全选复选框状态)
|
||||
const allOvertimeItemsSelected = computed(() => {
|
||||
const items = overtimeApprovalItems.value;
|
||||
if (items.length === 0) return false;
|
||||
return items.every(item => item.approvalBizId && selectedOvertimeIds.value.has(item.approvalBizId));
|
||||
});
|
||||
|
||||
// 当前页是否有部分选中
|
||||
const someOvertimeItemsSelected = computed(() => {
|
||||
return overtimeApprovalItems.value.some(
|
||||
item => item.approvalBizId && selectedOvertimeIds.value.has(item.approvalBizId)
|
||||
);
|
||||
});
|
||||
|
||||
// 批量审批选中的 id 数组(用于传给批量详情弹窗)
|
||||
const batchSelectedIds = computed(() => Array.from(selectedOvertimeIds.value));
|
||||
|
||||
// 打开批量审批详情弹窗
|
||||
function handleBatchReview() {
|
||||
batchDetailVisible.value = true;
|
||||
}
|
||||
|
||||
// 批量详情弹窗中点击"通过"或"退回"
|
||||
function openBatchActionDialog(actionType: OvertimeApprovalActionType) {
|
||||
currentOvertimeActionType.value = actionType;
|
||||
isBatchActionMode.value = true;
|
||||
batchActionVisible.value = true;
|
||||
}
|
||||
|
||||
// 批量审批提交(对所有选中项执行批量 API)
|
||||
async function handleBatchActionSubmit(reason: string | null) {
|
||||
const ids = Array.from(selectedOvertimeIds.value);
|
||||
if (ids.length === 0) return;
|
||||
|
||||
const fn =
|
||||
currentOvertimeActionType.value === 'approve'
|
||||
? fetchBatchApproveOvertimeApplication
|
||||
: fetchBatchRejectOvertimeApplication;
|
||||
|
||||
batchSubmitting.value = true;
|
||||
const { error, data } = await fn({ ids, reason });
|
||||
batchSubmitting.value = false;
|
||||
|
||||
if (error || !data) return;
|
||||
|
||||
batchActionVisible.value = false;
|
||||
batchDetailVisible.value = false;
|
||||
clearOvertimeSelection();
|
||||
|
||||
if (data.failCount > 0) {
|
||||
window.$message?.warning(`成功 ${data.successCount} 条,失败 ${data.failCount} 条`);
|
||||
} else {
|
||||
window.$message?.success(`已批量处理 ${data.successCount} 条`);
|
||||
}
|
||||
|
||||
await loadOvertimeApprovalItems();
|
||||
}
|
||||
|
||||
async function loadOvertimeApprovalItems() {
|
||||
const { error, data } = await fetchGetOvertimeApplicationApprovalPage({
|
||||
pageNo: 1,
|
||||
@@ -725,7 +829,7 @@ function buildWorkReportApprovalItems<T extends WorkReportRow>(
|
||||
rows.map(item => ({
|
||||
id: `${bizType}-${item.id}`,
|
||||
category: 'approval',
|
||||
title: `${reportTypeLabel} · ${formatPeriod(item)} 待审批`,
|
||||
title: `${reportTypeLabel} · ${bizType === 'weekly' ? formatWeeklyPeriodLabel(item) : formatPeriod(item)} 待审批`,
|
||||
createdTime: item.submitTime || item.createTime || '',
|
||||
deadline: item.submitTime || item.createTime || null,
|
||||
source: `${reportTypeLabel} · ${'projectName' in item ? item.projectName : item.reporterName}`,
|
||||
@@ -882,9 +986,60 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<div class="workbench-todo__content">
|
||||
<!-- 批量操作栏(仅加班申请待审批时显示) -->
|
||||
<div
|
||||
v-if="
|
||||
activeTab === 'approval' &&
|
||||
activeApprovalBizType === 'overtime_application' &&
|
||||
overtimeApprovalItems.length > 0
|
||||
"
|
||||
class="workbench-todo__batch-bar"
|
||||
:class="{ 'workbench-todo__batch-bar--active': selectedOvertimeIds.size > 0 }"
|
||||
>
|
||||
<div class="workbench-todo__batch-bar-left">
|
||||
<ElCheckbox
|
||||
:model-value="allOvertimeItemsSelected"
|
||||
:indeterminate="someOvertimeItemsSelected && !allOvertimeItemsSelected"
|
||||
@change="val => toggleSelectAllOvertimeItems(Boolean(val))"
|
||||
@click.stop
|
||||
>
|
||||
全选
|
||||
</ElCheckbox>
|
||||
<span v-if="selectedOvertimeIds.size > 0" class="workbench-todo__batch-bar-count">
|
||||
已选择 {{ selectedOvertimeIds.size }} 项
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="selectedOvertimeIds.size > 0" class="workbench-todo__batch-bar-right">
|
||||
<ElButton size="small" type="primary" :loading="batchSubmitting" @click.stop="handleBatchReview">
|
||||
批量审批
|
||||
</ElButton>
|
||||
<ElButton size="small" link @click.stop="clearOvertimeSelection">取消选择</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="pagedItems.length" class="workbench-todo__list">
|
||||
<article v-for="item in pagedItems" :key="item.id" class="workbench-todo__item">
|
||||
<article
|
||||
v-for="item in pagedItems"
|
||||
:key="item.id"
|
||||
class="workbench-todo__item"
|
||||
:class="{
|
||||
'workbench-todo__item--clickable': Boolean(item.routeKey || item.approvalBizType),
|
||||
'workbench-todo__item--selected': isOvertimeItemSelected(item)
|
||||
}"
|
||||
@click="handleClickItem(item)"
|
||||
>
|
||||
<div class="workbench-todo__leading">
|
||||
<!-- 加班申请待审批时显示复选框 -->
|
||||
<ElCheckbox
|
||||
v-if="
|
||||
activeTab === 'approval' &&
|
||||
activeApprovalBizType === 'overtime_application' &&
|
||||
item.approvalBizType === 'overtime_application'
|
||||
"
|
||||
:model-value="isOvertimeItemSelected(item)"
|
||||
@change="val => toggleOvertimeItem(item, Boolean(val))"
|
||||
@click.stop
|
||||
/>
|
||||
<span class="workbench-todo__category" :class="`workbench-todo__category--${item.categoryTone}`">
|
||||
{{ item.categoryLabel }}
|
||||
</span>
|
||||
@@ -1052,6 +1207,7 @@ onMounted(async () => {
|
||||
:row-data="currentOvertimeApplication"
|
||||
show-approval-actions
|
||||
:action-loading="overtimeActionSubmitting"
|
||||
append-to-body
|
||||
@approve="openCurrentOvertimeAction('approve')"
|
||||
@reject="openCurrentOvertimeAction('reject')"
|
||||
/>
|
||||
@@ -1059,9 +1215,30 @@ onMounted(async () => {
|
||||
v-model:visible="overtimeActionVisible"
|
||||
:action-type="currentOvertimeActionType"
|
||||
:loading="overtimeActionSubmitting"
|
||||
append-to-body
|
||||
@submit="handleOvertimeActionSubmit"
|
||||
/>
|
||||
|
||||
<!-- 批量审批详情弹窗(左右箭头切换 + 通过/退回按钮) -->
|
||||
<OvertimeApplicationBatchDetailDialog
|
||||
v-model:visible="batchDetailVisible"
|
||||
:selected-ids="batchSelectedIds"
|
||||
:rows="overtimeApprovalRows"
|
||||
:action-loading="batchSubmitting"
|
||||
append-to-body
|
||||
@approve="openBatchActionDialog('approve')"
|
||||
@reject="openBatchActionDialog('reject')"
|
||||
/>
|
||||
|
||||
<!-- 批量审批意见/退回原因对话框 -->
|
||||
<OvertimeApplicationActionDialog
|
||||
v-model:visible="batchActionVisible"
|
||||
:action-type="currentOvertimeActionType"
|
||||
:loading="batchSubmitting"
|
||||
append-to-body
|
||||
@submit="handleBatchActionSubmit"
|
||||
/>
|
||||
|
||||
<WorkReportPrototypePageDialog
|
||||
v-model:visible="workReportDetailVisible"
|
||||
mode="detail"
|
||||
@@ -1261,6 +1438,44 @@ onMounted(async () => {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* 批量操作栏 */
|
||||
.workbench-todo__batch-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 14px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid rgb(226 232 240 / 90%);
|
||||
border-radius: 12px;
|
||||
background-color: rgb(248 250 252 / 96%);
|
||||
font-size: 13px;
|
||||
color: rgb(71 85 105 / 94%);
|
||||
transition: all 160ms ease;
|
||||
}
|
||||
|
||||
.workbench-todo__batch-bar--active {
|
||||
border-color: rgb(14 116 144 / 40%);
|
||||
background-color: rgb(240 253 250 / 80%);
|
||||
color: rgb(14 116 144 / 96%);
|
||||
}
|
||||
|
||||
.workbench-todo__batch-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workbench-todo__batch-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.workbench-todo__batch-bar-count {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.workbench-todo__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1281,6 +1496,20 @@ onMounted(async () => {
|
||||
background-color 160ms ease;
|
||||
}
|
||||
|
||||
.workbench-todo__item--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workbench-todo__item--clickable:hover {
|
||||
border-color: rgb(14 116 144 / 60%);
|
||||
background-color: rgb(240 253 250 / 84%);
|
||||
}
|
||||
|
||||
.workbench-todo__item--selected {
|
||||
border-color: rgb(14 116 144 / 60%);
|
||||
background-color: rgb(240 253 250 / 90%);
|
||||
}
|
||||
|
||||
.workbench-todo__leading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user