|
|
@@ -27,133 +27,44 @@
|
|
|
</ContentWrap>
|
|
|
|
|
|
<ContentWrap style="border: none">
|
|
|
- <zm-table
|
|
|
- v-loading="loading"
|
|
|
- :data="projectList"
|
|
|
- row-key="id"
|
|
|
- highlight-current-row
|
|
|
- class="project-table"
|
|
|
- @current-change="selectProject"
|
|
|
- >
|
|
|
- <zm-table-column label="项目名称" prop="projectName" min-width="280" show-overflow-tooltip />
|
|
|
- <zm-table-column label="项目负责人" prop="projectLeader" width="140" />
|
|
|
- <zm-table-column label="项目类型" prop="projectType" min-width="180">
|
|
|
+ <zm-table v-loading="loading" :data="projectList" row-key="id">
|
|
|
+ <zm-table-column label="项目名称" prop="projectName" min-width="240" show-overflow-tooltip />
|
|
|
+ <zm-table-column label="项目负责人" prop="projectLeader" width="120" />
|
|
|
+ <zm-table-column label="项目类型" prop="projectType" min-width="160">
|
|
|
<template #default="{ row }">
|
|
|
<dict-tag :type="DICT_TYPE.PROJECT_TYPE" :value="row.projectType" />
|
|
|
</template>
|
|
|
</zm-table-column>
|
|
|
- <zm-table-column label="生效日期" prop="projectDate" width="160">
|
|
|
+ <zm-table-column label="生效日期" prop="projectDate" width="120">
|
|
|
<template #default="{ row }">{{ formatDate(row.projectDate) }}</template>
|
|
|
</zm-table-column>
|
|
|
<zm-table-column label="任务数" prop="taskTotal" width="90" align="center" />
|
|
|
- <zm-table-column label="操作" width="120" fixed="right" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-button link type="primary" @click="selectProject(row)">分解任务</el-button>
|
|
|
- </template>
|
|
|
- </zm-table-column>
|
|
|
- </zm-table>
|
|
|
- <Pagination
|
|
|
- v-model:page="queryParams.pageNo"
|
|
|
- v-model:limit="queryParams.pageSize"
|
|
|
- :total="total"
|
|
|
- @pagination="getProjectList"
|
|
|
- />
|
|
|
- </ContentWrap>
|
|
|
-
|
|
|
- <ContentWrap style="border: none" v-if="selectedProject" class="task-panel">
|
|
|
- <div class="task-heading">
|
|
|
- <div>
|
|
|
- <div class="task-title-row">
|
|
|
- <h3>{{ selectedProject.projectName }}</h3>
|
|
|
- <el-tag type="success" size="small">已生效</el-tag>
|
|
|
- </div>
|
|
|
- <span>
|
|
|
- 项目负责人:{{ selectedProject.projectLeader }} · 已分解
|
|
|
- {{ selectedProject.taskTotal || 0 }} 个任务
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <el-button type="primary" @click="openTaskForm()" v-hasPermi="['rd:project-task:create']">
|
|
|
- <Icon icon="ep:plus" /> 新增一级任务
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-empty v-if="!taskTree.length" description="暂未分解任务">
|
|
|
- <el-button type="primary" @click="openTaskForm()">
|
|
|
- <Icon icon="ep:plus" /> 新增一级任务
|
|
|
- </el-button>
|
|
|
- </el-empty>
|
|
|
- <zm-table
|
|
|
- v-else
|
|
|
- :data="taskTree"
|
|
|
- row-key="id"
|
|
|
- default-expand-all
|
|
|
- border
|
|
|
- :tree-props="{ children: 'children' }"
|
|
|
- >
|
|
|
- <zm-table-column label="任务名称" min-width="250">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="task-name">
|
|
|
- <span>{{ row.taskName }}</span>
|
|
|
- <el-tag v-if="row.level === 3" type="info" effect="plain" size="small">末级</el-tag>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </zm-table-column>
|
|
|
- <zm-table-column label="开始时间" prop="planStartTime" width="130" />
|
|
|
- <zm-table-column label="结束时间" prop="planEndTime" width="130" />
|
|
|
- <zm-table-column label="责任人" prop="ownerName" width="120" />
|
|
|
- <zm-table-column label="优先级" width="100" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag :type="priorityTagType(row.priority)">
|
|
|
- {{ priorityLabel(row.priority) }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </zm-table-column>
|
|
|
- <zm-table-column label="进度" width="120" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-progress
|
|
|
- :percentage="row.progress || 0"
|
|
|
- :status="row.status === 3 ? 'exception' : row.status === 2 ? 'success' : ''"
|
|
|
- :stroke-width="8"
|
|
|
- />
|
|
|
- </template>
|
|
|
- </zm-table-column>
|
|
|
- <zm-table-column label="任务描述" prop="taskDesc" min-width="220" show-overflow-tooltip />
|
|
|
- <zm-table-column label="操作" width="240" fixed="right" align="center">
|
|
|
+ <zm-table-column label="操作" width="200" fixed="right" align="center">
|
|
|
<template #default="{ row }">
|
|
|
<el-button
|
|
|
- v-if="row.level < 3"
|
|
|
link
|
|
|
type="primary"
|
|
|
v-hasPermi="['rd:project-task:create']"
|
|
|
@click="openTaskForm(row)"
|
|
|
>
|
|
|
- 新增子任务
|
|
|
- </el-button>
|
|
|
- <el-button v-else link type="info" disabled>不可再分解</el-button>
|
|
|
- <el-button
|
|
|
- link
|
|
|
- type="primary"
|
|
|
- v-hasPermi="['rd:project-task:update']"
|
|
|
- @click="openTaskForm(row, true)"
|
|
|
- >
|
|
|
- 编辑
|
|
|
- </el-button>
|
|
|
- <el-button
|
|
|
- link
|
|
|
- type="danger"
|
|
|
- v-hasPermi="['rd:project-task:delete']"
|
|
|
- @click="removeTask(row)"
|
|
|
- >
|
|
|
- 删除
|
|
|
+ <Icon icon="ep:plus" /> 新增任务
|
|
|
</el-button>
|
|
|
+ <el-button link type="primary" @click="openTaskDrawer(row)">查看任务</el-button>
|
|
|
</template>
|
|
|
</zm-table-column>
|
|
|
</zm-table>
|
|
|
+ <Pagination
|
|
|
+ v-model:page="queryParams.pageNo"
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
+ :total="total"
|
|
|
+ @pagination="getProjectList"
|
|
|
+ />
|
|
|
</ContentWrap>
|
|
|
|
|
|
+ <!-- 新增/编辑任务弹窗 -->
|
|
|
<el-dialog v-model="taskDialogVisible" :title="taskDialogTitle" width="720px" destroy-on-close>
|
|
|
<el-alert v-if="parentTask" type="info" :closable="false" class="parent-alert">
|
|
|
- 当前父任务:{{ parentTask.taskName }}(已有子任务 {{ parentTask.children?.length || 0 }} 个)
|
|
|
+ 当前父任务:{{ parentTask.taskName }}
|
|
|
</el-alert>
|
|
|
<el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="auto">
|
|
|
<el-form-item label="任务名称" prop="taskName">
|
|
|
@@ -220,6 +131,105 @@
|
|
|
<el-button type="primary" :loading="submitLoading" @click="submitTask">保存任务</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 查看任务抽屉 -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="drawerVisible"
|
|
|
+ :title="`任务列表:${currentProject?.projectName || ''}`"
|
|
|
+ size="75%"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <div class="drawer-header">
|
|
|
+ <span
|
|
|
+ >项目负责人:{{ currentProject?.projectLeader }} · 已分解 {{ taskTree.length }} 个任务</span
|
|
|
+ >
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ style="background-color: #1560fc"
|
|
|
+ @click="openTaskForm()"
|
|
|
+ v-hasPermi="['rd:project-task:create']"
|
|
|
+ >
|
|
|
+ <Icon icon="ep:plus" /> 新增一级任务
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-empty v-if="!taskTree.length" description="暂未分解任务">
|
|
|
+ <el-button type="primary" @click="openTaskForm()">
|
|
|
+ <Icon icon="ep:plus" /> 新增一级任务
|
|
|
+ </el-button>
|
|
|
+ </el-empty>
|
|
|
+
|
|
|
+ <el-table
|
|
|
+ v-else
|
|
|
+ :data="taskTree"
|
|
|
+ row-key="id"
|
|
|
+ default-expand-all
|
|
|
+ :tree-props="{ children: 'children' }"
|
|
|
+ :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'none' }"
|
|
|
+ >
|
|
|
+ <el-table-column label="任务名称" min-width="220" class-name="task-name-col">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <!-- 将 div 换成 span,并设为 flex 布局 -->
|
|
|
+ <span class="task-name">
|
|
|
+ <span>{{ row.taskName }}</span>
|
|
|
+ <el-tag v-if="row.level === 3" type="info" effect="plain" size="small">末级</el-tag>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="开始时间" prop="planStartTime" width="120" />
|
|
|
+ <el-table-column label="结束时间" prop="planEndTime" width="120" />
|
|
|
+ <el-table-column label="责任人" prop="ownerName" width="110" />
|
|
|
+ <el-table-column label="优先级" width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="priorityTagType(row.priority)">
|
|
|
+ {{ priorityLabel(row.priority) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="进度" width="180" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-progress
|
|
|
+ :percentage="row.progress || 0"
|
|
|
+ :status="row.status === 3 ? 'exception' : row.status === 2 ? 'success' : ''"
|
|
|
+ :stroke-width="8"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="任务描述" prop="taskDesc" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column label="操作" width="220" fixed="right" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ v-if="row.level < 3"
|
|
|
+ link
|
|
|
+ style="color: #4b7bb1"
|
|
|
+ type="primary"
|
|
|
+ v-hasPermi="['rd:project-task:create']"
|
|
|
+ @click="openTaskForm(row)"
|
|
|
+ >
|
|
|
+ 新增子任务
|
|
|
+ </el-button>
|
|
|
+ <!-- <el-button v-else link type="info" disabled>不可再分解</el-button> -->
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ v-hasPermi="['rd:project-task:update']"
|
|
|
+ @click="openTaskForm(row, true)"
|
|
|
+ style="color: #4b7bb1"
|
|
|
+ >
|
|
|
+ 编辑
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ v-hasPermi="['rd:project-task:delete']"
|
|
|
+ @click="removeTask(row)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-drawer>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
@@ -253,7 +263,7 @@ const getProjectList = async () => {
|
|
|
try {
|
|
|
const data = await getProjectPage({
|
|
|
...queryParams,
|
|
|
- formalAuditStatus: 2 // ⭐ 只查正式审批通过的
|
|
|
+ formalAuditStatus: 2 // 只查正式审批通过的
|
|
|
})
|
|
|
projectList.value = data.list || []
|
|
|
total.value = data.total || 0
|
|
|
@@ -271,22 +281,23 @@ const resetQuery = () => {
|
|
|
handleQuery()
|
|
|
}
|
|
|
|
|
|
-// ========== 选中项目 + 任务树 ==========
|
|
|
-const selectedProject = ref<any>(null)
|
|
|
+// ========== 查看任务抽屉 ==========
|
|
|
+const drawerVisible = ref(false)
|
|
|
+const currentProject = ref<any>(null)
|
|
|
const taskTree = ref<any[]>([])
|
|
|
|
|
|
-const selectProject = async (project?: any) => {
|
|
|
- if (!project) return
|
|
|
- selectedProject.value = project
|
|
|
+const openTaskDrawer = async (project: any) => {
|
|
|
+ currentProject.value = project
|
|
|
+ drawerVisible.value = true
|
|
|
await loadTaskTree()
|
|
|
}
|
|
|
|
|
|
const loadTaskTree = async () => {
|
|
|
- if (!selectedProject.value?.id) {
|
|
|
+ if (!currentProject.value?.id) {
|
|
|
taskTree.value = []
|
|
|
return
|
|
|
}
|
|
|
- taskTree.value = await getTaskTreeByProjectId(selectedProject.value.id)
|
|
|
+ taskTree.value = await getTaskTreeByProjectId(currentProject.value.id)
|
|
|
}
|
|
|
|
|
|
// ========== 任务表单 ==========
|
|
|
@@ -320,13 +331,32 @@ const taskRules: FormRules = {
|
|
|
taskDesc: [{ required: true, message: '请输入任务描述', trigger: 'blur' }]
|
|
|
}
|
|
|
|
|
|
-const openTaskForm = (task?: any, editing = false) => {
|
|
|
- if (!selectedProject.value) return
|
|
|
- editingTask.value = editing ? task : undefined
|
|
|
- parentTask.value = !editing && task ? task : undefined
|
|
|
- taskDialogTitle.value = editing ? '编辑任务' : task ? '新增子任务' : '新增一级任务'
|
|
|
+/**
|
|
|
+ * 打开任务表单
|
|
|
+ * - project: 从项目列表点击时传入(新增一级任务)
|
|
|
+ * - task: 从任务树点击"新增子任务"或"编辑"时传入
|
|
|
+ * - editing: 是否编辑模式
|
|
|
+ */
|
|
|
+const openTaskForm = (projectOrTask?: any, editing = false) => {
|
|
|
+ // 场景 1:从项目列表点"新增任务"(projectOrTask 是项目对象)
|
|
|
+ if (projectOrTask && projectOrTask.projectName && !projectOrTask.taskName) {
|
|
|
+ editingTask.value = undefined
|
|
|
+ parentTask.value = undefined
|
|
|
+ taskDialogTitle.value = '新增一级任务'
|
|
|
+ Object.assign(taskForm, createEmptyTask(), {
|
|
|
+ projectId: projectOrTask.id
|
|
|
+ })
|
|
|
+ taskDialogVisible.value = true
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
+ // 场景 2:从任务树里点"新增子任务"或"编辑"(projectOrTask 是任务对象)
|
|
|
+ const task = projectOrTask
|
|
|
if (editing && task) {
|
|
|
+ // 编辑任务
|
|
|
+ editingTask.value = task
|
|
|
+ parentTask.value = undefined
|
|
|
+ taskDialogTitle.value = '编辑任务'
|
|
|
Object.assign(taskForm, {
|
|
|
id: task.id,
|
|
|
projectId: task.projectId,
|
|
|
@@ -340,8 +370,12 @@ const openTaskForm = (task?: any, editing = false) => {
|
|
|
planEndTime: task.planEndTime
|
|
|
})
|
|
|
} else {
|
|
|
+ // 新增子任务
|
|
|
+ editingTask.value = undefined
|
|
|
+ parentTask.value = task || undefined
|
|
|
+ taskDialogTitle.value = task ? '新增子任务' : '新增一级任务'
|
|
|
Object.assign(taskForm, createEmptyTask(), {
|
|
|
- projectId: selectedProject.value.id,
|
|
|
+ projectId: currentProject.value?.id || null,
|
|
|
parentId: task ? task.id : 0
|
|
|
})
|
|
|
}
|
|
|
@@ -364,8 +398,11 @@ const submitTask = async () => {
|
|
|
}
|
|
|
ElMessage.success(editingTask.value ? '任务已更新' : '任务已添加')
|
|
|
taskDialogVisible.value = false
|
|
|
- await loadTaskTree()
|
|
|
- // 刷新项目列表(任务总数变了)
|
|
|
+ // 如果抽屉是打开的,刷新任务树
|
|
|
+ if (drawerVisible.value) {
|
|
|
+ await loadTaskTree()
|
|
|
+ }
|
|
|
+ // 刷新项目列表(任务数变了)
|
|
|
await getProjectList()
|
|
|
} finally {
|
|
|
submitLoading.value = false
|
|
|
@@ -407,57 +444,44 @@ const formatDate = (dt: string) => {
|
|
|
}
|
|
|
|
|
|
// ========== 初始化 ==========
|
|
|
-onMounted(async () => {
|
|
|
- await getProjectList()
|
|
|
- // 默认选中第一条
|
|
|
- if (projectList.value.length > 0) {
|
|
|
- selectProject(projectList.value[0])
|
|
|
- }
|
|
|
+onMounted(() => {
|
|
|
+ getProjectList()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
-.task-heading,
|
|
|
-.task-title-row {
|
|
|
+.project-filter {
|
|
|
+ margin-bottom: -15px;
|
|
|
+}
|
|
|
+.task-name {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
-.task-heading {
|
|
|
- justify-content: space-between;
|
|
|
+.parent-alert {
|
|
|
margin-bottom: 18px;
|
|
|
}
|
|
|
-.task-heading h3 {
|
|
|
- margin: 0;
|
|
|
- font-size: 18px;
|
|
|
- color: var(--el-text-color-primary);
|
|
|
+.drawer-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 16px;
|
|
|
}
|
|
|
-.task-heading span {
|
|
|
- margin-top: 6px;
|
|
|
+.drawer-header span {
|
|
|
color: var(--el-text-color-secondary);
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
-.task-title-row {
|
|
|
- gap: 10px;
|
|
|
-}
|
|
|
-.project-filter {
|
|
|
- margin-bottom: -15px;
|
|
|
-}
|
|
|
-.task-panel {
|
|
|
- margin-top: 16px;
|
|
|
-}
|
|
|
-.task-name {
|
|
|
+
|
|
|
+/* 让该列的单元格内容变成 flex 布局,使图标和文字在同一行 */
|
|
|
+:deep(.task-name-col .cell) {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 8px;
|
|
|
-}
|
|
|
-.parent-alert {
|
|
|
- margin-bottom: 18px;
|
|
|
}
|
|
|
-@media (max-width: 900px) {
|
|
|
- .task-heading {
|
|
|
- align-items: flex-start;
|
|
|
- flex-direction: column;
|
|
|
- gap: 14px;
|
|
|
- }
|
|
|
+
|
|
|
+/* 任务名称的容器样式 */
|
|
|
+.task-name {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px; /* 给文字和标签之间加点间距 */
|
|
|
}
|
|
|
</style>
|