feat(新增加班申请功能): 新增申请功能,可在工作台进行审核。
fix(dict_data): 在字典数据新增、编辑时可以操作颜色类型字段(color_type)。
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, markRaw, onMounted, ref, watch } from 'vue';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import {
|
||||
fetchApproveOvertimeApplication,
|
||||
fetchGetOvertimeApplicationApprovalPage,
|
||||
fetchRejectOvertimeApplication
|
||||
} from '@/service/api';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
|
||||
import OvertimeApplicationActionDialog from '@/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue';
|
||||
import OvertimeApplicationDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue';
|
||||
import OvertimeApplicationStatusLogDialog from '@/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue';
|
||||
import {
|
||||
type WorkbenchTodoDeadlineFilter,
|
||||
type WorkbenchTodoItem,
|
||||
@@ -15,8 +23,14 @@ import {
|
||||
} from '../homepage';
|
||||
import { workbenchTodoMock } from '../mock';
|
||||
import WorkbenchModuleCard from './workbench-module-card.vue';
|
||||
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
|
||||
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
|
||||
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
|
||||
import IconMdiHistory from '~icons/mdi/history';
|
||||
|
||||
type SortKey = 'created' | 'priority' | 'deadline';
|
||||
type OvertimeApprovalActionType = 'approve' | 'reject';
|
||||
type ApprovalBizType = 'overtime_application';
|
||||
|
||||
defineOptions({ name: 'WorkbenchTodoPanel' });
|
||||
|
||||
@@ -38,6 +52,7 @@ const PAGE_SIZE = 5;
|
||||
|
||||
const activeTab = ref<WorkbenchTodoMainTab>('all');
|
||||
const activeDeadlineFilter = ref<WorkbenchTodoDeadlineFilter>(null);
|
||||
const activeApprovalBizType = ref<ApprovalBizType>('overtime_application');
|
||||
const activeSort = ref<SortKey>('deadline');
|
||||
const currentPage = ref(1);
|
||||
|
||||
@@ -66,9 +81,33 @@ const deadlineFilters: Array<{ key: Exclude<WorkbenchTodoDeadlineFilter, null>;
|
||||
{ key: 'week', label: '本周到期' }
|
||||
];
|
||||
|
||||
const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [
|
||||
{ key: 'overtime_application', label: '加班申请' }
|
||||
];
|
||||
|
||||
const allItems = computed(() => buildWorkbenchTodoItems(workbenchTodoMock));
|
||||
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
|
||||
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
|
||||
const mergedItems = computed(() => {
|
||||
const mockItems = allItems.value.filter(item => item.category !== 'approval');
|
||||
|
||||
return [...mockItems, ...overtimeApprovalItems.value];
|
||||
});
|
||||
|
||||
const addDialogVisible = ref(false);
|
||||
const overtimeDetailVisible = ref(false);
|
||||
const overtimeStatusLogVisible = ref(false);
|
||||
const overtimeActionVisible = ref(false);
|
||||
const overtimeActionSubmitting = ref(false);
|
||||
const currentOvertimeApplication = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
|
||||
const currentOvertimeActionType = ref<OvertimeApprovalActionType>('approve');
|
||||
|
||||
const OVERTIME_APPROVAL_ACTION_ICONS = {
|
||||
detail: markRaw(IconMdiEyeOutline),
|
||||
approve: markRaw(IconMdiCheckCircleOutline),
|
||||
reject: markRaw(IconMdiCloseCircleOutline),
|
||||
statusLog: markRaw(IconMdiHistory)
|
||||
};
|
||||
|
||||
function handleOpenAdd() {
|
||||
addDialogVisible.value = true;
|
||||
@@ -81,13 +120,13 @@ function handleAddSubmitted() {
|
||||
|
||||
const tabCounts = computed(() => {
|
||||
const counts: Record<WorkbenchTodoMainTab, number> = {
|
||||
all: allItems.value.length,
|
||||
all: mergedItems.value.length,
|
||||
task: 0,
|
||||
ticket: 0,
|
||||
personal: 0,
|
||||
approval: 0
|
||||
};
|
||||
allItems.value.forEach(item => {
|
||||
mergedItems.value.forEach(item => {
|
||||
counts[item.category] += 1;
|
||||
});
|
||||
return counts;
|
||||
@@ -101,7 +140,7 @@ const tabOverdueCount = computed(() => {
|
||||
personal: 0,
|
||||
approval: 0
|
||||
};
|
||||
allItems.value.forEach(item => {
|
||||
mergedItems.value.forEach(item => {
|
||||
if (!isWorkbenchTodoOverdue(item)) return;
|
||||
map.all += 1;
|
||||
map[item.category] += 1;
|
||||
@@ -109,9 +148,29 @@ const tabOverdueCount = computed(() => {
|
||||
return map;
|
||||
});
|
||||
|
||||
const itemsInTab = computed(() => filterWorkbenchTodoItemsByCategory(allItems.value, activeTab.value));
|
||||
const itemsInTab = computed(() => filterWorkbenchTodoItemsByCategory(mergedItems.value, activeTab.value));
|
||||
|
||||
const filteredItems = computed(() => filterWorkbenchTodoItemsByDeadline(itemsInTab.value, activeDeadlineFilter.value));
|
||||
const filteredItems = computed(() => {
|
||||
if (activeTab.value === 'approval') {
|
||||
return itemsInTab.value.filter(item => item.approvalBizType === activeApprovalBizType.value);
|
||||
}
|
||||
|
||||
return filterWorkbenchTodoItemsByDeadline(itemsInTab.value, activeDeadlineFilter.value);
|
||||
});
|
||||
|
||||
const approvalBizTabCounts = computed(() => {
|
||||
const counts: Record<ApprovalBizType, number> = {
|
||||
overtime_application: 0
|
||||
};
|
||||
|
||||
itemsInTab.value.forEach(item => {
|
||||
if (item.approvalBizType === 'overtime_application') {
|
||||
counts.overtime_application += 1;
|
||||
}
|
||||
});
|
||||
|
||||
return counts;
|
||||
});
|
||||
|
||||
const sortedItems = computed(() => {
|
||||
const base = filteredItems.value;
|
||||
@@ -155,20 +214,121 @@ function handleSelectDeadlineFilter(key: Exclude<WorkbenchTodoDeadlineFilter, nu
|
||||
activeDeadlineFilter.value = activeDeadlineFilter.value === key ? null : key;
|
||||
}
|
||||
|
||||
function handleSelectApprovalBizType(key: ApprovalBizType) {
|
||||
activeApprovalBizType.value = key;
|
||||
}
|
||||
|
||||
function handleSelectSort(key: SortKey) {
|
||||
activeSort.value = key;
|
||||
}
|
||||
|
||||
function handleClickItem(item: WorkbenchTodoItem) {
|
||||
if (item.approvalBizType === 'overtime_application') {
|
||||
openOvertimeDetail(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.routeKey) return;
|
||||
routerPushByKey(item.routeKey as RouteKey);
|
||||
}
|
||||
|
||||
function findOvertimeApprovalRow(item: WorkbenchTodoItem) {
|
||||
if (!item.approvalBizId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return overtimeApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
|
||||
}
|
||||
|
||||
function openOvertimeDetail(item: WorkbenchTodoItem) {
|
||||
const row = findOvertimeApprovalRow(item);
|
||||
if (!row) return;
|
||||
|
||||
currentOvertimeApplication.value = row;
|
||||
overtimeDetailVisible.value = true;
|
||||
}
|
||||
|
||||
function openOvertimeStatusLog(item: WorkbenchTodoItem) {
|
||||
const row = findOvertimeApprovalRow(item);
|
||||
if (!row) return;
|
||||
|
||||
currentOvertimeApplication.value = row;
|
||||
overtimeStatusLogVisible.value = true;
|
||||
}
|
||||
|
||||
function openOvertimeAction(item: WorkbenchTodoItem, actionType: OvertimeApprovalActionType) {
|
||||
const row = findOvertimeApprovalRow(item);
|
||||
if (!row) return;
|
||||
|
||||
currentOvertimeApplication.value = row;
|
||||
currentOvertimeActionType.value = actionType;
|
||||
overtimeActionVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleOvertimeActionSubmit(reason: string | null) {
|
||||
if (!currentOvertimeApplication.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
overtimeActionSubmitting.value = true;
|
||||
const result =
|
||||
currentOvertimeActionType.value === 'approve'
|
||||
? await fetchApproveOvertimeApplication(currentOvertimeApplication.value.id, { reason })
|
||||
: await fetchRejectOvertimeApplication(currentOvertimeApplication.value.id, { reason });
|
||||
overtimeActionSubmitting.value = false;
|
||||
|
||||
if (result.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
overtimeActionVisible.value = false;
|
||||
overtimeDetailVisible.value = false;
|
||||
window.$message?.success(currentOvertimeActionType.value === 'approve' ? '加班申请已通过' : '加班申请已退回');
|
||||
await loadOvertimeApprovalItems();
|
||||
}
|
||||
|
||||
async function loadOvertimeApprovalItems() {
|
||||
const { error, data } = await fetchGetOvertimeApplicationApprovalPage({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
statusCode: 'pending',
|
||||
keyword: undefined,
|
||||
applicantName: undefined,
|
||||
approverId: undefined,
|
||||
approverName: undefined,
|
||||
overtimeDate: undefined,
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
if (error || !data) {
|
||||
overtimeApprovalRows.value = [];
|
||||
overtimeApprovalItems.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
overtimeApprovalRows.value = data.list;
|
||||
overtimeApprovalItems.value = buildWorkbenchTodoItems(
|
||||
data.list.map(item => ({
|
||||
id: `overtime-application-${item.id}`,
|
||||
category: 'approval',
|
||||
title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)} 月加班 ${item.overtimeDuration} 申请待审批`,
|
||||
createdTime: item.submitTime || item.createTime,
|
||||
deadline: item.submitTime || item.createTime,
|
||||
source: `加班申请 · ${item.applicantName}`,
|
||||
priority: 'mid',
|
||||
approvalBizType: 'overtime_application',
|
||||
approvalBizId: item.id
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
if (isWorkbenchTodoOverdue(item)) return 'workbench-todo__deadline--rose';
|
||||
if (item.remainingDays === 0) return 'workbench-todo__deadline--amber';
|
||||
return 'workbench-todo__deadline--slate';
|
||||
}
|
||||
|
||||
onMounted(loadOvertimeApprovalItems);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -222,7 +382,19 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
{{ filter.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
<div v-else class="workbench-todo__filters-left">
|
||||
<button
|
||||
v-for="tab in approvalBizTabs"
|
||||
:key="tab.key"
|
||||
type="button"
|
||||
class="workbench-todo__filter"
|
||||
:class="{ 'workbench-todo__filter--active': activeApprovalBizType === tab.key }"
|
||||
@click="handleSelectApprovalBizType(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
<span class="workbench-todo__filter-count">{{ approvalBizTabCounts[tab.key] }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ElDropdown trigger="click" placement="bottom-end" @command="handleSelectSort">
|
||||
<span class="workbench-todo__sort">
|
||||
@@ -250,7 +422,7 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
v-for="item in pagedItems"
|
||||
:key="item.id"
|
||||
class="workbench-todo__item"
|
||||
:class="{ 'workbench-todo__item--clickable': Boolean(item.routeKey) }"
|
||||
:class="{ 'workbench-todo__item--clickable': Boolean(item.routeKey || item.approvalBizType) }"
|
||||
@click="handleClickItem(item)"
|
||||
>
|
||||
<div class="workbench-todo__leading">
|
||||
@@ -268,6 +440,38 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
</div>
|
||||
|
||||
<div class="workbench-todo__trailing">
|
||||
<div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
|
||||
<ElTooltip content="详情">
|
||||
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
|
||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.detail" class="text-15px" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="通过">
|
||||
<ElButton
|
||||
link
|
||||
type="success"
|
||||
class="workbench-todo__action-btn"
|
||||
@click="openOvertimeAction(item, 'approve')"
|
||||
>
|
||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.approve" class="text-15px" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="退回">
|
||||
<ElButton
|
||||
link
|
||||
type="danger"
|
||||
class="workbench-todo__action-btn"
|
||||
@click="openOvertimeAction(item, 'reject')"
|
||||
>
|
||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.reject" class="text-15px" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="状态日志">
|
||||
<ElButton link type="info" class="workbench-todo__action-btn" @click="openOvertimeStatusLog(item)">
|
||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.statusLog" class="text-15px" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<span class="workbench-todo__deadline" :class="getDeadlineToneClass(item)">
|
||||
{{ item.deadlineLabel }}
|
||||
</span>
|
||||
@@ -295,6 +499,18 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
:row-data="null"
|
||||
@submitted="handleAddSubmitted"
|
||||
/>
|
||||
|
||||
<OvertimeApplicationDetailDialog v-model:visible="overtimeDetailVisible" :row-data="currentOvertimeApplication" />
|
||||
<OvertimeApplicationStatusLogDialog
|
||||
v-model:visible="overtimeStatusLogVisible"
|
||||
:row-data="currentOvertimeApplication"
|
||||
/>
|
||||
<OvertimeApplicationActionDialog
|
||||
v-model:visible="overtimeActionVisible"
|
||||
:action-type="currentOvertimeActionType"
|
||||
:loading="overtimeActionSubmitting"
|
||||
@submit="handleOvertimeActionSubmit"
|
||||
/>
|
||||
</WorkbenchModuleCard>
|
||||
</template>
|
||||
|
||||
@@ -468,6 +684,12 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
color: rgb(190 18 60 / 96%);
|
||||
}
|
||||
|
||||
.workbench-todo__filter-count {
|
||||
margin-left: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.workbench-todo__content {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
@@ -596,6 +818,25 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
||||
.workbench-todo__trailing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.workbench-todo__actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workbench-todo__actions :deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:deep(.workbench-todo__action-btn) {
|
||||
min-width: auto;
|
||||
height: auto;
|
||||
padding: 3px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workbench-todo__deadline {
|
||||
|
||||
Reference in New Issue
Block a user