feat(oa/erp): 新增报表页面并优化任务管理功能

1.  重构临时任务API接口,拆分提交与列表查询方法,重命名方法名提升可读性
2.  完善临时任务相关类型定义,新增字段适配业务需求
3.  新增临时任务统计报表页面,支持多维度数据分析与导出
4.  优化主列表页面:调整搜索栏布局、新增业务状态筛选、重构表格列配置、增加编辑权限校验
5.  新增项目选择弹窗组件集成,优化表单重置逻辑
dev
zch 3 days ago
parent 843de4dc64
commit 57b270df36

@ -40,7 +40,15 @@ export const delTempTask = (tempTaskId: string | number | Array<string | number>
}); });
}; };
export const submitTempTask = (data: TempTaskForm): AxiosPromise<TempTaskVO> => { export const getTempTaskList = (query?: TempTaskQuery): AxiosPromise<TempTaskVO[]> => {
return request({
url: '/oa/erp/tempTask/getErpTempTaskList',
method: 'get',
params: query
});
};
export const tempTaskSubmitAndFlowStart = (data: TempTaskForm): AxiosPromise<TempTaskVO> => {
return request({ return request({
url: '/oa/erp/tempTask/submit', url: '/oa/erp/tempTask/submit',
method: 'post', method: 'post',
@ -56,14 +64,14 @@ export const closeTempTask = (data: TempTaskForm) => {
}); });
}; };
export const listTempTaskChanges = (tempTaskId: string | number): AxiosPromise<TempTaskChangeVO[]> => { export const listTempTaskChange = (tempTaskId: string | number): AxiosPromise<TempTaskChangeVO[]> => {
return request({ return request({
url: `/oa/erp/tempTask/${tempTaskId}/changes`, url: `/oa/erp/tempTask/${tempTaskId}/changes`,
method: 'get' method: 'get'
}); });
}; };
export const saveTempTaskChange = (data: TempTaskChangeForm) => { export const addTempTaskChange = (data: TempTaskChangeForm) => {
return request({ return request({
url: '/oa/erp/tempTask/change', url: '/oa/erp/tempTask/change',
method: 'post', method: 'post',
@ -78,11 +86,3 @@ export const approveTempTaskChange = (data: TempTaskChangeForm) => {
data data
}); });
}; };
export const getErpTempTaskList = (query?: TempTaskQuery): AxiosPromise<TempTaskVO[]> => {
return request({
url: '/oa/erp/tempTask/getErpTempTaskList',
method: 'get',
params: query
});
};

@ -1,11 +1,11 @@
export interface TempTaskVO { export interface TempTaskVO {
tempTaskId: string | number; tempTaskId: string | number;
tempTaskCode?: string; tempTaskCode: string;
taskTitle?: string; taskTitle?: string;
taskDesc?: string; taskDesc: string;
priority?: string; priority?: string;
urgentReason?: string; urgentReason?: string;
requireTime?: string; requireTime: string;
confirmFinishTime?: string; confirmFinishTime?: string;
actualStartTime?: string; actualStartTime?: string;
actualFinishTime?: string; actualFinishTime?: string;
@ -87,6 +87,8 @@ export interface TempTaskForm extends BaseEntity {
ccUserIds?: string; ccUserIds?: string;
ossId?: string; ossId?: string;
remark?: string; remark?: string;
changeCount?: number;
pendingChangeCount?: number;
flowCode?: string; flowCode?: string;
variables?: Record<string, unknown>; variables?: Record<string, unknown>;
bizExt?: Record<string, unknown>; bizExt?: Record<string, unknown>;
@ -98,16 +100,21 @@ export interface TempTaskQuery extends PageQuery {
taskDesc?: string; taskDesc?: string;
priority?: string; priority?: string;
projectId?: string | number; projectId?: string | number;
projectCode?: string;
projectName?: string; projectName?: string;
requesterId?: string | number; requesterId?: string | number;
requesterName?: string; requesterName?: string;
requestDeptId?: string | number;
requestDeptName?: string;
dispatcherId?: string | number; dispatcherId?: string | number;
dispatcherName?: string; dispatcherName?: string;
softwareLeaderId?: string | number;
softwareLeaderName?: string;
assigneeId?: string | number; assigneeId?: string | number;
assigneeName?: string; assigneeName?: string;
finishResult?: string; finishResult?: string;
terminateReason?: string;
relatedTaskId?: string | number;
relatedTaskCode?: string;
relateReason?: string;
taskStatus?: string; taskStatus?: string;
flowStatus?: string; flowStatus?: string;
params?: Record<string, unknown>; params?: Record<string, unknown>;
@ -117,8 +124,8 @@ export interface TempTaskChangeVO {
changeId: string | number; changeId: string | number;
tempTaskId: string | number; tempTaskId: string | number;
tempTaskCode?: string; tempTaskCode?: string;
changeType?: string; changeType: string;
changeReason?: string; changeReason: string;
beforeContent?: string; beforeContent?: string;
afterContent?: string; afterContent?: string;
workloadEffect?: string; workloadEffect?: string;
@ -146,6 +153,12 @@ export interface TempTaskChangeForm extends BaseEntity {
workloadEffect?: string; workloadEffect?: string;
timeEffect?: string; timeEffect?: string;
scopeEffect?: string; scopeEffect?: string;
changeUserId?: string | number;
changeUserName?: string;
approveResult?: string; approveResult?: string;
approverId?: string | number;
approverName?: string;
approveComment?: string; approveComment?: string;
approveTime?: string;
flowStatus?: string;
} }

File diff suppressed because it is too large Load Diff

@ -15,6 +15,13 @@
<el-option v-for="dict in temp_task_priority" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-option v-for="dict in temp_task_priority" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="项目" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请选择项目" clearable readonly>
<template #suffix>
<el-icon class="cursor-pointer" @click="openProjectSelect"><Search /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="发起人" prop="requesterName"> <el-form-item label="发起人" prop="requesterName">
<el-input v-model="queryParams.requesterName" placeholder="请输入发起人" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.requesterName" placeholder="请输入发起人" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -24,8 +31,10 @@
<el-form-item label="执行人" prop="assigneeName"> <el-form-item label="执行人" prop="assigneeName">
<el-input v-model="queryParams.assigneeName" placeholder="请输入执行人" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.assigneeName" placeholder="请输入执行人" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item label="项目" prop="projectName"> <el-form-item label="业务状态" prop="taskStatus">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" /> <el-select v-model="queryParams.taskStatus" placeholder="请选择业务状态" clearable>
<el-option v-for="dict in temp_task_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="完成结果" prop="finishResult"> <el-form-item label="完成结果" prop="finishResult">
<el-select v-model="queryParams.finishResult" placeholder="请选择完成结果" clearable> <el-select v-model="queryParams.finishResult" placeholder="请选择完成结果" clearable>
@ -43,93 +52,109 @@
<el-card shadow="never"> <el-card shadow="never">
<template #header> <template #header>
<div class="toolbar-row"> <el-row :gutter="10" class="mb8">
<div> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['oa:erp:tempTask:add']"></el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['oa:erp:tempTask:add']"></el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa:erp:tempTask:edit']" </el-col>
>修改</el-button <el-col :span="1.5">
> <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa:erp:tempTask:edit']">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['oa:erp:tempTask:remove']" 修改
>删除</el-button </el-button>
> </el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['oa:erp:tempTask:remove']">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['oa:erp:tempTask:export']"></el-button> <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['oa:erp:tempTask:export']"></el-button>
</div> </el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" /> <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</div> </el-row>
</template> </template>
<el-tabs v-model="activeTab" class="mb-2" @tab-change="handleTabChange"> <el-tabs v-model="activeTab" class="mb-2" @tab-change="handleTabChange">
<el-tab-pane label="全部" name="all" />
<el-tab-pane label="草稿" name="draft" /> <el-tab-pane label="草稿" name="draft" />
<el-tab-pane label="在办/未关闭" name="running" /> <el-tab-pane label="在办/未关闭" name="running" />
<el-tab-pane label="已结束" name="finished" /> <el-tab-pane label="已结束" name="finished" />
<el-tab-pane label="全部" name="all" />
</el-tabs> </el-tabs>
<el-table v-loading="loading" border :data="tempTaskList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" border :data="tempTaskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="45" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" /> <el-table-column type="index" label="序号" width="70" align="center" v-if="columns[0].visible" />
<el-table-column label="任务编号" prop="tempTaskCode" min-width="150" show-overflow-tooltip v-if="columns[0].visible" /> <el-table-column label="任务编号" prop="tempTaskCode" min-width="150" align="center" v-if="columns[1].visible" />
<el-table-column label="任务描述" prop="taskDesc" min-width="220" show-overflow-tooltip v-if="columns[1].visible" /> <el-table-column label="任务标题" prop="taskTitle" min-width="160" show-overflow-tooltip align="center" v-if="columns[2].visible" />
<el-table-column label="优先级" prop="priority" width="90" align="center" v-if="columns[2].visible"> <el-table-column label="任务描述" prop="taskDesc" min-width="220" show-overflow-tooltip align="center" v-if="columns[3].visible" />
<el-table-column label="优先级" prop="priority" width="90" align="center" v-if="columns[4].visible">
<template #default="scope"> <template #default="scope">
<dict-tag :options="temp_task_priority" :value="scope.row.priority" /> <dict-tag :options="temp_task_priority" :value="scope.row.priority" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="发起人" prop="requesterName" width="110" align="center" v-if="columns[3].visible" /> <el-table-column label="需求时间" prop="requireTime" width="150" align="center" v-if="columns[5].visible">
<el-table-column label="发起部门" prop="requestDeptName" min-width="130" show-overflow-tooltip v-if="columns[4].visible" />
<el-table-column label="指派人" prop="dispatcherName" width="110" align="center" v-if="columns[5].visible" />
<el-table-column label="执行人" prop="assigneeName" width="110" align="center" v-if="columns[6].visible" />
<el-table-column label="项目" prop="projectName" min-width="160" show-overflow-tooltip v-if="columns[7].visible" />
<el-table-column label="需求时间" prop="requireTime" width="160" align="center" v-if="columns[8].visible">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.requireTime, '{y}-{m}-{d} {h}:{i}') }}</span> <span>{{ parseTime(scope.row.requireTime, '{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="确认完成" prop="confirmFinishTime" width="160" align="center" v-if="columns[9].visible"> <el-table-column label="确认完成时间" prop="confirmFinishTime" width="150" align="center" v-if="columns[6].visible">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.confirmFinishTime, '{y}-{m}-{d} {h}:{i}') }}</span> <span>{{ parseTime(scope.row.confirmFinishTime, '{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="预计工时" prop="estimateWorkload" width="100" align="right" v-if="columns[10].visible" /> <el-table-column label="项目编号" prop="projectCode" width="140" align="center" v-if="columns[7].visible" />
<el-table-column label="实际工时" prop="actualWorkload" width="100" align="right" v-if="columns[11].visible" /> <el-table-column label="项目名称" prop="projectName" min-width="170" show-overflow-tooltip align="center" v-if="columns[8].visible" />
<el-table-column label="业务状态" prop="taskStatus" width="100" align="center" v-if="columns[12].visible"> <el-table-column label="发起人" prop="requesterName" width="100" align="center" v-if="columns[9].visible" />
<el-table-column label="发起部门" prop="requestDeptName" width="130" align="center" v-if="columns[10].visible" />
<el-table-column label="指派人" prop="dispatcherName" width="100" align="center" v-if="columns[11].visible" />
<el-table-column label="软件部领导" prop="softwareLeaderName" width="120" align="center" v-if="columns[12].visible" />
<el-table-column label="主执行人" prop="assigneeName" width="110" align="center" v-if="columns[13].visible" />
<el-table-column label="预计工时" prop="estimateWorkload" width="100" align="center" v-if="columns[14].visible" />
<el-table-column label="实际工时" prop="actualWorkload" width="100" align="center" v-if="columns[15].visible" />
<el-table-column label="业务状态" prop="taskStatus" width="100" align="center" v-if="columns[16].visible">
<template #default="scope"> <template #default="scope">
<dict-tag :options="temp_task_status" :value="scope.row.taskStatus" /> <dict-tag :options="temp_task_status" :value="scope.row.taskStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="流程状态" prop="flowStatus" width="110" align="center" v-if="columns[13].visible"> <el-table-column label="流程状态" prop="flowStatus" width="100" align="center" v-if="columns[17].visible">
<template #default="scope"> <template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus" /> <dict-tag :options="wf_business_status" :value="scope.row.flowStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="完成结果" prop="finishResult" width="130" align="center" v-if="columns[14].visible"> <el-table-column label="完成结果" prop="finishResult" width="130" align="center" v-if="columns[18].visible">
<template #default="scope"> <template #default="scope">
<dict-tag :options="temp_task_finish_result" :value="scope.row.finishResult" /> <dict-tag :options="temp_task_finish_result" :value="scope.row.finishResult" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="变更" width="90" align="center" v-if="columns[15].visible"> <el-table-column label="待审批变更" prop="pendingChangeCount" width="110" align="center" v-if="columns[19].visible" />
<el-table-column label="创建时间" prop="createTime" width="160" align="center" v-if="columns[20].visible">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.pendingChangeCount > 0 ? 'warning' : 'info'"> <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
{{ scope.row.pendingChangeCount || 0 }}/{{ scope.row.changeCount || 0 }}
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" fixed="right" width="220" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" fixed="right" width="190" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="查看" placement="top"> <el-tooltip content="查看" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" /> <el-button link type="primary" icon="View" @click="handleView(scope.row)" />
</el-tooltip> </el-tooltip>
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['oa:erp:tempTask:edit']" /> <el-button
v-if="canEdit(scope.row)"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['oa:erp:tempTask:edit']"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="提交" placement="top"> <el-tooltip content="删除" placement="top">
<el-button link type="success" icon="Promotion" @click="handleSubmit(scope.row)" v-hasPermi="['oa:erp:tempTask:submit']" /> <el-button
</el-tooltip> v-if="canEdit(scope.row)"
<el-tooltip content="变更" placement="top"> link
<el-button link type="warning" icon="RefreshRight" @click="handleChange(scope.row)" v-hasPermi="['oa:erp:tempTask:change']" /> type="primary"
</el-tooltip> icon="Delete"
<el-tooltip content="关闭" placement="top"> @click="handleDelete(scope.row)"
<el-button link type="danger" icon="CircleClose" @click="handleClose(scope.row)" v-hasPermi="['oa:erp:tempTask:close']" /> v-hasPermi="['oa:erp:tempTask:remove']"
/>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@ -137,17 +162,30 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card> </el-card>
<ProjectSelect ref="projectSelectRef" :multiple="false" @confirm-call-back="handleProjectSelect" />
</div> </div>
</template> </template>
<script setup name="TempTask" lang="ts"> <script setup name="TempTask" lang="ts">
import { delTempTask, listTempTask, submitTempTask } from '@/api/oa/erp/tempTask'; import { delTempTask, listTempTask } from '@/api/oa/erp/tempTask';
import { TempTaskQuery, TempTaskVO } from '@/api/oa/erp/tempTask/types'; import type { TempTaskQuery, TempTaskVO } from '@/api/oa/erp/tempTask/types';
import { FlowCodeEnum } from '@/enums/OAEnum'; import ProjectSelect from '@/components/ProjectSelect/index.vue';
import type { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as any;
const router = useRouter(); const router = useRouter();
const parseTime = (time?: string, pattern?: string) => {
if (!time) {
return '';
}
if (proxy?.parseTime) {
return proxy.parseTime(time, pattern);
}
return String(time).replace('T', ' ').slice(0, 16);
};
const { temp_task_priority, temp_task_status, temp_task_finish_result, wf_business_status } = toRefs<any>( const { temp_task_priority, temp_task_status, temp_task_finish_result, wf_business_status } = toRefs<any>(
proxy?.useDict('temp_task_priority', 'temp_task_status', 'temp_task_finish_result', 'wf_business_status') proxy?.useDict('temp_task_priority', 'temp_task_status', 'temp_task_finish_result', 'wf_business_status')
); );
@ -159,26 +197,33 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true); const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const total = ref(0); const total = ref(0);
const activeTab = ref('running'); const activeTab = ref('all');
const queryFormRef = ref<ElFormInstance>(); const queryFormRef = ref<ElFormInstance>();
const projectSelectRef = ref<InstanceType<typeof ProjectSelect>>();
const columns = ref<FieldOption[]>([ const columns = ref<FieldOption[]>([
{ key: 0, label: '任务编号', visible: true }, { key: 0, label: '序号', visible: true },
{ key: 1, label: '任务描述', visible: true }, { key: 1, label: '任务编号', visible: true },
{ key: 2, label: '优先级', visible: true }, { key: 2, label: '任务标题', visible: true },
{ key: 3, label: '发起人', visible: true }, { key: 3, label: '任务描述', visible: true },
{ key: 4, label: '发起部门', visible: true }, { key: 4, label: '优先级', visible: true },
{ key: 5, label: '指派人', visible: true }, { key: 5, label: '需求时间', visible: true },
{ key: 6, label: '执行人', visible: true }, { key: 6, label: '确认完成时间', visible: true },
{ key: 7, label: '项目', visible: true }, { key: 7, label: '项目编号', visible: true },
{ key: 8, label: '需求时间', visible: true }, { key: 8, label: '项目名称', visible: true },
{ key: 9, label: '确认完成', visible: true }, { key: 9, label: '发起人', visible: true },
{ key: 10, label: '预计工时', visible: true }, { key: 10, label: '发起部门', visible: true },
{ key: 11, label: '实际工时', visible: true }, { key: 11, label: '指派人', visible: true },
{ key: 12, label: '业务状态', visible: true }, { key: 12, label: '软件部领导', visible: true },
{ key: 13, label: '流程状态', visible: true }, { key: 13, label: '主执行人', visible: true },
{ key: 14, label: '完成结果', visible: true }, { key: 14, label: '预计工时', visible: true },
{ key: 15, label: '变更', visible: true } { key: 15, label: '实际工时', visible: true },
{ key: 16, label: '业务状态', visible: true },
{ key: 17, label: '流程状态', visible: true },
{ key: 18, label: '完成结果', visible: true },
{ key: 19, label: '待审批变更', visible: true },
{ key: 20, label: '创建时间', visible: true }
]); ]);
const queryParams = ref<TempTaskQuery>({ const queryParams = ref<TempTaskQuery>({
@ -187,38 +232,38 @@ const queryParams = ref<TempTaskQuery>({
tempTaskCode: undefined, tempTaskCode: undefined,
taskDesc: undefined, taskDesc: undefined,
priority: undefined, priority: undefined,
projectId: undefined,
projectName: undefined,
requesterName: undefined, requesterName: undefined,
dispatcherName: undefined, dispatcherName: undefined,
assigneeName: undefined, assigneeName: undefined,
projectName: undefined, taskStatus: undefined,
flowStatus: undefined,
finishResult: undefined, finishResult: undefined,
taskStatus: '2',
params: {} params: {}
}); });
const applyTabQuery = () => { const applyTabFilter = () => {
queryParams.value.taskStatus = undefined;
queryParams.value.flowStatus = undefined;
queryParams.value.finishResult = undefined;
if (activeTab.value === 'draft') { if (activeTab.value === 'draft') {
queryParams.value.taskStatus = '1'; queryParams.value.taskStatus = '1';
queryParams.value.finishResult = undefined;
} else if (activeTab.value === 'running') { } else if (activeTab.value === 'running') {
queryParams.value.taskStatus = '2'; queryParams.value.taskStatus = '2';
queryParams.value.finishResult = undefined;
} else if (activeTab.value === 'finished') { } else if (activeTab.value === 'finished') {
queryParams.value.taskStatus = '3'; queryParams.value.taskStatus = '3';
} else {
queryParams.value.taskStatus = undefined;
queryParams.value.finishResult = undefined;
} }
}; };
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
try { applyTabFilter();
applyTabQuery(); const res = await listTempTask(queryParams.value).finally(() => (loading.value = false));
const res = await listTempTask(queryParams.value); tempTaskList.value = res.rows;
tempTaskList.value = res.rows || []; total.value = res.total;
total.value = res.total || 0;
} finally {
loading.value = false;
}
}; };
const handleQuery = () => { const handleQuery = () => {
@ -228,6 +273,8 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value?.resetFields(); queryFormRef.value?.resetFields();
queryParams.value.projectId = undefined;
queryParams.value.projectName = undefined;
handleQuery(); handleQuery();
}; };
@ -241,12 +288,30 @@ const handleSelectionChange = (selection: TempTaskVO[]) => {
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
const openProjectSelect = () => {
projectSelectRef.value?.open();
};
const handleProjectSelect = (data: ProjectInfoVO[]) => {
const project = data?.[0];
if (!project) {
return;
}
queryParams.value.projectId = project.projectId;
queryParams.value.projectCode = project.projectCode;
queryParams.value.projectName = project.projectName;
};
const canEdit = (row: TempTaskVO) => {
return !row.finishResult && ['draft', 'back', 'cancel'].includes(row.flowStatus || '');
};
const openEditPage = (type: string, id?: string | number) => { const openEditPage = (type: string, id?: string | number) => {
router.push({ router.push({
path: '/oa/erp/tempTask/edit', path: '/oa/erp/tempTask/edit',
query: { query: {
id, type,
type ...(id ? { id } : {})
} }
}); });
}; };
@ -260,25 +325,10 @@ const handleView = (row: TempTaskVO) => {
}; };
const handleUpdate = (row?: TempTaskVO) => { const handleUpdate = (row?: TempTaskVO) => {
openEditPage('update', row?.tempTaskId || ids.value[0]); const id = row?.tempTaskId || ids.value[0];
}; if (id) {
openEditPage('update', id);
const handleChange = (row: TempTaskVO) => { }
openEditPage('change', row.tempTaskId);
};
const handleClose = (row: TempTaskVO) => {
openEditPage('close', row.tempTaskId);
};
const handleSubmit = async (row: TempTaskVO) => {
await proxy?.$modal.confirm(`是否确认提交临时任务"${row.tempTaskCode || row.taskTitle}"审批?`);
await submitTempTask({
...row,
flowCode: FlowCodeEnum.TEMP_TASK_CODE
});
proxy?.$modal.msgSuccess('提交成功');
await getList();
}; };
const handleDelete = async (row?: TempTaskVO) => { const handleDelete = async (row?: TempTaskVO) => {
@ -290,19 +340,16 @@ const handleDelete = async (row?: TempTaskVO) => {
}; };
const handleExport = () => { const handleExport = () => {
proxy?.download('oa/erp/tempTask/export', { ...queryParams.value }, `tempTask_${new Date().getTime()}.xlsx`); proxy?.download(
'oa/erp/tempTask/export',
{
...queryParams.value
},
`tempTask_${new Date().getTime()}.xlsx`
);
}; };
onMounted(() => { onMounted(() => {
getList(); getList();
}); });
</script> </script>
<style scoped>
.toolbar-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
</style>

@ -0,0 +1,286 @@
<template>
<div class="p-2">
<el-card shadow="never" class="mb-3">
<el-form :model="queryParams" :inline="true" label-width="90px">
<el-form-item label="关闭月份">
<el-date-picker
v-model="finishMonth"
type="month"
value-format="YYYY-MM"
placeholder="请选择月份"
clearable
@change="handleQuery"
/>
</el-form-item>
<el-form-item label="执行人">
<el-input v-model="queryParams.assigneeName" placeholder="请输入执行人" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="发起部门">
<el-input v-model="queryParams.requestDeptName" placeholder="请输入发起部门" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="完成结果">
<el-select v-model="queryParams.finishResult" placeholder="请选择完成结果" clearable>
<el-option v-for="dict in temp_task_finish_result" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['oa:erp:tempTask:export']"></el-button>
</el-form-item>
</el-form>
</el-card>
<div class="metric-grid mb-3">
<el-card v-for="item in metrics" :key="item.label" shadow="never">
<div class="metric-label">{{ item.label }}</div>
<div class="metric-value">{{ item.value }}</div>
</el-card>
</div>
<el-card shadow="never">
<el-tabs v-model="activeDimension">
<el-tab-pane label="按人员" name="assignee" />
<el-tab-pane label="按项目" name="project" />
<el-tab-pane label="按发起部门" name="dept" />
<el-tab-pane label="按完成结果" name="result" />
<el-tab-pane label="按关联链" name="chain" />
</el-tabs>
<el-table v-loading="loading" :data="activeRows" border>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column :label="dimensionLabel" prop="name" min-width="220" show-overflow-tooltip align="center" />
<el-table-column label="任务数" prop="count" width="100" align="center" />
<el-table-column label="实际工时" prop="workload" width="120" align="center" />
<el-table-column label="平均工时" prop="avgWorkload" width="120" align="center" />
<el-table-column label="已完成" prop="finishedCount" width="100" align="center" />
<el-table-column label="不执行/终止" prop="terminatedCount" width="120" align="center" />
<el-table-column label="部分完成后终止" prop="partialTerminatedCount" width="140" align="center" />
</el-table>
<el-divider />
<el-table :data="detailRows" border>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column label="任务编号" prop="tempTaskCode" width="150" align="center" />
<el-table-column label="任务描述" prop="taskDesc" min-width="240" show-overflow-tooltip align="center" />
<el-table-column label="主执行人" prop="assigneeName" width="110" align="center" />
<el-table-column label="项目名称" prop="projectName" min-width="170" show-overflow-tooltip align="center" />
<el-table-column label="发起部门" prop="requestDeptName" width="130" align="center" />
<el-table-column label="完成结果" prop="finishResult" width="130" align="center">
<template #default="scope">
<dict-tag :options="temp_task_finish_result" :value="scope.row.finishResult" />
</template>
</el-table-column>
<el-table-column label="实际工时" prop="actualWorkload" width="100" align="center" />
<el-table-column label="关闭时间" prop="actualFinishTime" width="160" align="center">
<template #default="scope">
<span>{{ formatDateTime(scope.row.actualFinishTime) }}</span>
</template>
</el-table-column>
<el-table-column label="关联原任务" prop="relatedTaskCode" width="150" align="center" />
</el-table>
</el-card>
</div>
</template>
<script setup name="TempTaskReport" lang="ts">
import { listTempTask } from '@/api/oa/erp/tempTask';
import type { TempTaskQuery, TempTaskVO } from '@/api/oa/erp/tempTask/types';
interface AggregateRow {
name: string;
count: number;
workload: number;
avgWorkload: number;
finishedCount: number;
terminatedCount: number;
partialTerminatedCount: number;
}
const { proxy } = getCurrentInstance() as any;
const { temp_task_finish_result } = toRefs<any>(proxy?.useDict('temp_task_finish_result'));
const loading = ref(false);
const activeDimension = ref('assignee');
const currentMonth = () => {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
};
const finishMonth = ref(currentMonth());
const detailRows = ref<TempTaskVO[]>([]);
const runningTotal = ref(0);
const queryParams = ref<TempTaskQuery>({
pageNum: 1,
pageSize: 1000,
taskStatus: '3',
finishResult: undefined,
assigneeName: undefined,
projectName: undefined,
requestDeptName: undefined,
params: {}
});
const monthRange = computed(() => {
if (!finishMonth.value) {
return {};
}
const [year, month] = finishMonth.value.split('-').map(Number);
const end = new Date(year, month, 0);
return {
beginActualFinishTime: `${finishMonth.value}-01 00:00:00`,
endActualFinishTime: `${finishMonth.value}-${String(end.getDate()).padStart(2, '0')} 23:59:59`
};
});
const filteredRows = computed(() => {
return detailRows.value.filter((row) => row.finishResult && row.actualFinishTime);
});
const totalWorkload = computed(() => sumWorkload(filteredRows.value));
const metrics = computed(() => [
{ label: '闭环任务数', value: filteredRows.value.length },
{ label: '实际工时合计', value: formatNumber(totalWorkload.value) },
{ label: '单任务平均工时', value: formatNumber(filteredRows.value.length ? totalWorkload.value / filteredRows.value.length : 0) },
{ label: '未关闭任务数', value: runningTotal.value }
]);
const dimensionLabel = computed(() => {
const labelMap: Record<string, string> = {
assignee: '主执行人',
project: '项目',
dept: '发起部门',
result: '完成结果',
chain: '关联链'
};
return labelMap[activeDimension.value] || '维度';
});
const activeRows = computed(() => {
const fieldMap: Record<string, (row: TempTaskVO) => string> = {
assignee: (row) => row.assigneeName || '未填写执行人',
project: (row) => row.projectName || row.projectCode || '未绑定项目',
dept: (row) => row.requestDeptName || '未填写部门',
result: (row) => getFinishResultLabel(row.finishResult),
chain: (row) => row.relatedTaskCode || row.tempTaskCode || '未形成关联链'
};
return aggregateBy(filteredRows.value, fieldMap[activeDimension.value]);
});
const getFinishResultLabel = (value?: string) => {
const dictList = unref(temp_task_finish_result) || [];
const target = dictList.find((item: any) => item.value === value);
return target?.label || value || '未填写完成结果';
};
const sumWorkload = (rows: TempTaskVO[]) => {
return rows.reduce((sum, row) => sum + Number(row.actualWorkload || 0), 0);
};
const formatNumber = (value: number) => {
return Number(value.toFixed(2));
};
const formatDateTime = (value?: string) => {
if (!value) {
return '';
}
return String(value).replace('T', ' ').slice(0, 16);
};
const aggregateBy = (rows: TempTaskVO[], keyGetter: (row: TempTaskVO) => string) => {
const result = new Map<string, AggregateRow>();
rows.forEach((row) => {
const key = keyGetter(row);
const current =
result.get(key) ||
({
name: key,
count: 0,
workload: 0,
avgWorkload: 0,
finishedCount: 0,
terminatedCount: 0,
partialTerminatedCount: 0
} as AggregateRow);
current.count += 1;
current.workload += Number(row.actualWorkload || 0);
if (row.finishResult === '1') {
current.finishedCount += 1;
} else if (row.finishResult === '2') {
current.terminatedCount += 1;
} else if (row.finishResult === '3') {
current.partialTerminatedCount += 1;
}
current.avgWorkload = current.count ? formatNumber(current.workload / current.count) : 0;
current.workload = formatNumber(current.workload);
result.set(key, current);
});
return Array.from(result.values()).sort((a, b) => b.workload - a.workload);
};
const buildQuery = () => {
queryParams.value.params = { ...monthRange.value };
return { ...queryParams.value };
};
const getList = async () => {
loading.value = true;
try {
const res: any = await listTempTask(buildQuery());
detailRows.value = res.rows || [];
const runningRes: any = await listTempTask({ pageNum: 1, pageSize: 1, taskStatus: '2' });
runningTotal.value = runningRes.total || 0;
} finally {
loading.value = false;
}
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
finishMonth.value = currentMonth();
queryParams.value.assigneeName = undefined;
queryParams.value.projectName = undefined;
queryParams.value.requestDeptName = undefined;
queryParams.value.finishResult = undefined;
handleQuery();
};
const handleExport = () => {
proxy?.download('oa/erp/tempTask/export', buildQuery(), `tempTask_report_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
getList();
});
</script>
<style scoped>
.metric-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.metric-label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.metric-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 26px;
font-weight: 600;
line-height: 1;
}
</style>
Loading…
Cancel
Save