|
|
<template>
|
|
|
<div class="p-2">
|
|
|
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
<div v-show="showSearch" class="mb-[10px]">
|
|
|
<el-card shadow="hover">
|
|
|
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px">
|
|
|
<el-form-item label="任务编号" prop="tempTaskCode">
|
|
|
<el-input v-model="queryParams.tempTaskCode" placeholder="请输入任务编号" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<!-- <el-form-item label="任务描述" prop="taskDesc">
|
|
|
<el-input v-model="queryParams.taskDesc" placeholder="请输入任务描述" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>-->
|
|
|
<el-form-item label="任务类型" prop="taskType">
|
|
|
<el-select v-model="queryParams.taskType" placeholder="请选择任务类型" clearable>
|
|
|
<el-option v-for="dict in temp_task_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
</el-select>
|
|
|
</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="deptId">
|
|
|
<el-select v-model="queryParams.deptId" placeholder="请选择部门" clearable filterable @change="handleDeptChange">
|
|
|
<el-option v-for="dept in deptList" :key="dept.deptId" :label="dept.deptName" :value="dept.deptId" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="发起人" prop="requesterName">
|
|
|
<el-input v-model="queryParams.requesterName" placeholder="请输入发起人" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="实际需求人" prop="realRequesterName">
|
|
|
<el-input v-model="queryParams.realRequesterName" placeholder="请输入实际需求人" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="主执行人" prop="assigneeName">
|
|
|
<el-input v-model="queryParams.assigneeName" placeholder="请输入主执行人" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="业务状态" prop="taskStatus">
|
|
|
<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-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
</div>
|
|
|
</transition>
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
<template #header>
|
|
|
<el-row :gutter="10" class="mb8">
|
|
|
<el-col :span="1.5">
|
|
|
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['oa/erp:tempTask:add']">新增</el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa/erp:tempTask:edit']">
|
|
|
修改
|
|
|
</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-col>
|
|
|
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
|
|
|
</el-row>
|
|
|
</template>
|
|
|
|
|
|
<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="approving" />
|
|
|
<el-tab-pane label="执行中" name="running" />
|
|
|
<el-tab-pane label="待领导审核" name="pendingFinal" />
|
|
|
<el-tab-pane label="已关闭" name="closed" />
|
|
|
<el-tab-pane label="作废" name="invalid" />
|
|
|
</el-tabs>
|
|
|
|
|
|
<el-table v-loading="loading" border :data="tempTaskList" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="55" 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" align="center" v-if="columns[1].visible" />
|
|
|
<el-table-column label="任务类型" prop="taskType" width="100" align="center" v-if="columns[2].visible">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="temp_task_type" :value="scope.row.taskType" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="任务标题" prop="taskTitle" min-width="160" show-overflow-tooltip align="center" v-if="columns[3].visible" />
|
|
|
<el-table-column label="任务描述" prop="taskDesc" min-width="220" show-overflow-tooltip align="center" v-if="columns[4].visible" />
|
|
|
<el-table-column label="计划开始" prop="planStartTime" width="150" align="center" v-if="columns[5].visible">
|
|
|
<template #default="scope">
|
|
|
<span>{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d}') }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="计划完成" prop="planEndTime" width="150" align="center" v-if="columns[6].visible">
|
|
|
<template #default="scope">
|
|
|
<span>{{ parseTime(scope.row.planEndTime, '{y}-{m}-{d}') }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="项目名称" prop="projectName" min-width="170" show-overflow-tooltip align="center" v-if="columns[7].visible" />
|
|
|
<el-table-column label="归集部门" prop="deptName" width="130" show-overflow-tooltip align="center" v-if="columns[8].visible" />
|
|
|
<el-table-column label="发起人" prop="requesterName" width="100" align="center" v-if="columns[9].visible" />
|
|
|
<el-table-column label="实际需求人" prop="realRequesterName" width="110" align="center" v-if="columns[10].visible" />
|
|
|
<el-table-column
|
|
|
label="实际需求部门"
|
|
|
prop="realRequestDeptName"
|
|
|
width="130"
|
|
|
show-overflow-tooltip
|
|
|
align="center"
|
|
|
v-if="columns[11].visible"
|
|
|
/>
|
|
|
<el-table-column label="主执行人" prop="assigneeName" width="110" align="center" v-if="columns[12].visible" />
|
|
|
<el-table-column label="预计工时" prop="estimateWorkload" width="100" align="center" v-if="columns[13].visible" />
|
|
|
<el-table-column label="累计工时" prop="totalHours" width="100" align="center" v-if="columns[14].visible" />
|
|
|
<el-table-column label="业务状态" prop="taskStatus" width="110" align="center" v-if="columns[15].visible">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="temp_task_status" :value="scope.row.taskStatus" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="流程状态" prop="flowStatus" width="110" align="center" v-if="columns[16].visible">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="创建时间" prop="createTime" width="160" align="center" v-if="columns[17].visible">
|
|
|
<template #default="scope">
|
|
|
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" align="center" fixed="right" width="240" class-name="small-padding fixed-width">
|
|
|
<template #default="scope">
|
|
|
<el-tooltip content="查看" placement="top">
|
|
|
<el-button link type="primary" icon="View" @click="handleView(scope.row)" />
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="修改" placement="top">
|
|
|
<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 content="执行" placement="top">
|
|
|
<el-button
|
|
|
v-if="canExecute(scope.row)"
|
|
|
link
|
|
|
type="success"
|
|
|
icon="Clock"
|
|
|
@click="handleExecute(scope.row)"
|
|
|
v-hasPermi="['oa/erp:tempTask:list']"
|
|
|
/>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="删除" placement="top">
|
|
|
<el-button
|
|
|
v-if="canDelete(scope.row)"
|
|
|
link
|
|
|
type="primary"
|
|
|
icon="Delete"
|
|
|
@click="handleDelete(scope.row)"
|
|
|
v-hasPermi="['oa/erp:tempTask:remove']"
|
|
|
/>
|
|
|
</el-tooltip>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
</el-card>
|
|
|
|
|
|
<ProjectSelect ref="projectSelectRef" :multiple="false" @confirm-call-back="handleProjectSelect" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="TempTask" lang="ts">
|
|
|
import { delTempTask, listTempTask } from '@/api/oa/erp/tempTask';
|
|
|
import type { TempTaskQuery, TempTaskVO } from '@/api/oa/erp/tempTask/types';
|
|
|
import type { DeptVO } from '@/api/system/dept/types';
|
|
|
import { allListDept } from '@/api/system/dept';
|
|
|
import ProjectSelect from '@/components/ProjectSelect/index.vue';
|
|
|
import type { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as any;
|
|
|
const route = useRoute();
|
|
|
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_type, temp_task_status, wf_business_status } = toRefs<any>(
|
|
|
proxy?.useDict('temp_task_type', 'temp_task_status', 'wf_business_status')
|
|
|
);
|
|
|
|
|
|
const tempTaskList = ref<TempTaskVO[]>([]);
|
|
|
const deptList = ref<DeptVO[]>([]);
|
|
|
const loading = ref(true);
|
|
|
const showSearch = ref(true);
|
|
|
const ids = ref<Array<string | number>>([]);
|
|
|
const single = ref(true);
|
|
|
const multiple = ref(true);
|
|
|
const total = ref(0);
|
|
|
const activeTab = ref('all');
|
|
|
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
const projectSelectRef = ref<InstanceType<typeof ProjectSelect>>();
|
|
|
|
|
|
const columns = ref<FieldOption[]>([
|
|
|
{ key: 0, label: '序号', visible: true },
|
|
|
{ key: 1, label: '任务编号', visible: true },
|
|
|
{ key: 2, label: '任务类型', visible: true },
|
|
|
{ key: 3, label: '任务标题', visible: true },
|
|
|
{ key: 4, label: '任务描述', visible: true },
|
|
|
{ key: 5, label: '计划开始', visible: true },
|
|
|
{ key: 6, label: '计划完成', visible: true },
|
|
|
{ key: 7, label: '项目名称', visible: true },
|
|
|
{ key: 8, label: '归集部门', visible: true },
|
|
|
{ key: 9, label: '发起人', visible: true },
|
|
|
{ key: 10, label: '实际需求人', visible: true },
|
|
|
{ key: 11, label: '实际需求部门', visible: true },
|
|
|
{ key: 12, label: '主执行人', visible: true },
|
|
|
{ key: 13, label: '预计工时', visible: true },
|
|
|
{ key: 14, label: '累计工时', visible: true },
|
|
|
{ key: 15, label: '业务状态', visible: true },
|
|
|
{ key: 16, label: '流程状态', visible: true },
|
|
|
{ key: 17, label: '创建时间', visible: true }
|
|
|
]);
|
|
|
|
|
|
const queryParams = ref<TempTaskQuery>({
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
tempTaskCode: undefined,
|
|
|
taskDesc: undefined,
|
|
|
taskType: undefined,
|
|
|
projectId: undefined,
|
|
|
projectName: undefined,
|
|
|
deptId: undefined,
|
|
|
deptName: undefined,
|
|
|
requesterName: undefined,
|
|
|
realRequesterName: undefined,
|
|
|
assigneeName: undefined,
|
|
|
taskStatus: undefined,
|
|
|
flowStatus: undefined,
|
|
|
params: {}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* Tab 标签页与业务状态(taskStatus)的映射关系。
|
|
|
*
|
|
|
* Tab 本质是对 taskStatus 的快捷筛选器:
|
|
|
* - all: 不限制 taskStatus,显示全部任务
|
|
|
* - draft: taskStatus=1(暂存),发起人可继续编辑或删除
|
|
|
* - approving: taskStatus=2(审批中),正在工作流审批链路中
|
|
|
* - running: taskStatus=3(执行中),主执行人和协作人可填报工时
|
|
|
* - pendingFinal: taskStatus=4(待领导审核),等待软件部领导评分关闭
|
|
|
* - closed: taskStatus=5(已关闭),归档只读
|
|
|
* - invalid: taskStatus=6(作废),归档只读
|
|
|
*
|
|
|
* tabStatusMap 的值直接赋值给 queryParams.taskStatus 作为后端过滤条件。
|
|
|
* all 的值为 undefined,表示不传 taskStatus 过滤参数。
|
|
|
*/
|
|
|
const tabStatusMap: Record<string, string | undefined> = {
|
|
|
all: undefined,
|
|
|
draft: '1',
|
|
|
approving: '2',
|
|
|
running: '3',
|
|
|
pendingFinal: '4',
|
|
|
closed: '5',
|
|
|
invalid: '6'
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 根据当前激活的 Tab 设置 taskStatus 过滤条件。
|
|
|
* 在 getList 调用前执行,确保查询参数携带正确的 taskStatus 值。
|
|
|
*/
|
|
|
const applyTabFilter = () => {
|
|
|
queryParams.value.taskStatus = tabStatusMap[activeTab.value];
|
|
|
};
|
|
|
|
|
|
const getList = async () => {
|
|
|
loading.value = true;
|
|
|
applyTabFilter();
|
|
|
const res = await listTempTask(queryParams.value).finally(() => (loading.value = false));
|
|
|
tempTaskList.value = res.rows;
|
|
|
total.value = res.total;
|
|
|
};
|
|
|
|
|
|
const getDeptList = async () => {
|
|
|
const res = await allListDept({ status: 0 } as any);
|
|
|
deptList.value = res.data || [];
|
|
|
};
|
|
|
|
|
|
const handleDeptChange = (deptId?: string | number) => {
|
|
|
const dept = deptList.value.find((item) => item.deptId === deptId);
|
|
|
queryParams.value.deptName = dept?.deptName;
|
|
|
};
|
|
|
|
|
|
const handleQuery = () => {
|
|
|
queryParams.value.pageNum = 1;
|
|
|
getList();
|
|
|
};
|
|
|
|
|
|
const resetQuery = () => {
|
|
|
queryFormRef.value?.resetFields();
|
|
|
queryParams.value.projectId = undefined;
|
|
|
queryParams.value.projectName = undefined;
|
|
|
queryParams.value.deptId = undefined;
|
|
|
queryParams.value.deptName = undefined;
|
|
|
handleQuery();
|
|
|
};
|
|
|
|
|
|
const handleTabChange = () => {
|
|
|
handleQuery();
|
|
|
};
|
|
|
|
|
|
const handleSelectionChange = (selection: TempTaskVO[]) => {
|
|
|
ids.value = selection.map((item) => item.tempTaskId);
|
|
|
single.value = selection.length !== 1;
|
|
|
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;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 判断列表行是否可编辑(显示修改按钮)。
|
|
|
*
|
|
|
* 可编辑条件:flowStatus 为 draft(草稿)/back(退回)/cancel(撤销) 时允许修改。
|
|
|
* 为什么这三种状态可编辑:
|
|
|
* - draft: 从未提交过,可以任意修改
|
|
|
* - back: 被审批人退回,发起人需修改后重新提交
|
|
|
* - cancel: 发起人主动撤销,可修改后重新提交
|
|
|
*
|
|
|
* 不可编辑的状态:
|
|
|
* - waiting(审批中): 修改会破坏当前审批数据一致性
|
|
|
* - finish(已完成): 已归档数据不允许修改
|
|
|
* - invalid(作废): 作废数据不允许修改
|
|
|
*/
|
|
|
const canEdit = (row: TempTaskVO) => {
|
|
|
return ['draft', 'back', 'cancel'].includes(row.flowStatus || '');
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 判断列表行是否可删除(显示删除按钮)。
|
|
|
*
|
|
|
* 可删除条件:taskStatus === '1'(暂存) 且 flowStatus 为 draft/back/cancel。
|
|
|
* 为什么需要同时检查 taskStatus 和 flowStatus:
|
|
|
* taskStatus 单独判断不够,因为存在 taskStatus=1 但 flowStatus=waiting 的中间态
|
|
|
* (例如刚提交但流程尚未完全落库),此时不应允许删除。
|
|
|
* flowStatus 单独判断也不够,因为存在 flowStatus=draft 但 taskStatus 已变更的脏数据。
|
|
|
* 双重检查确保只有当任务确实处于可删除阶段时才开放删除操作。
|
|
|
*/
|
|
|
const canDelete = (row: TempTaskVO) => {
|
|
|
return row.taskStatus === '1' && ['draft', 'back', 'cancel'].includes(row.flowStatus || '');
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 判断是否可进入执行页面(显示"执行"按钮)。
|
|
|
* taskStatus=3(执行中) 时显示,角色校验由后端负责。
|
|
|
*/
|
|
|
const canExecute = (row: TempTaskVO) => {
|
|
|
return row.taskStatus === '3';
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 打开执行页面,专用于维护参与人和工时明细。
|
|
|
*/
|
|
|
const handleExecute = (row: TempTaskVO) => {
|
|
|
openEditPage('execute', row.tempTaskId);
|
|
|
};
|
|
|
|
|
|
const openEditPage = (type: string, id?: string | number) => {
|
|
|
proxy.$tab.closePage(route);
|
|
|
router.push({
|
|
|
path: '/timesheet/tempTask/edit',
|
|
|
query: {
|
|
|
type,
|
|
|
...(id ? { id } : {})
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
openEditPage('add');
|
|
|
};
|
|
|
|
|
|
const handleView = (row: TempTaskVO) => {
|
|
|
openEditPage('view', row.tempTaskId);
|
|
|
};
|
|
|
|
|
|
const handleUpdate = (row?: TempTaskVO) => {
|
|
|
const id = row?.tempTaskId || ids.value[0];
|
|
|
if (id) {
|
|
|
openEditPage('update', id);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleDelete = async (row?: TempTaskVO) => {
|
|
|
const tempTaskIds = row?.tempTaskId || ids.value;
|
|
|
await proxy?.$modal.confirm(`是否确认删除临时任务编号为"${tempTaskIds}"的数据项?`);
|
|
|
await delTempTask(tempTaskIds);
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
await getList();
|
|
|
};
|
|
|
|
|
|
const handleExport = () => {
|
|
|
proxy?.download('oa/erp/tempTask/export', { ...queryParams.value }, `tempTask_${new Date().getTime()}.xlsx`);
|
|
|
};
|
|
|
|
|
|
onMounted(async () => {
|
|
|
await getDeptList();
|
|
|
await getList();
|
|
|
});
|
|
|
</script>
|