You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

440 lines
18 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>