From 5333935ae59999c47653122a669f4326f0173c1c Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 08 一月 2026 14:47:02 +0800
Subject: [PATCH] 确认按钮在左边
---
src/views/oaSystem/projectManagement/components/taskTree.vue | 1505 ++++++++++++++++++++++++++++++---------------------------
1 files changed, 783 insertions(+), 722 deletions(-)
diff --git a/src/views/oaSystem/projectManagement/components/taskTree.vue b/src/views/oaSystem/projectManagement/components/taskTree.vue
index 11e3ae8..ddcdb54 100644
--- a/src/views/oaSystem/projectManagement/components/taskTree.vue
+++ b/src/views/oaSystem/projectManagement/components/taskTree.vue
@@ -2,69 +2,98 @@
<div class="task-tree-container">
<!-- 浠诲姟鏍戞搷浣滄寜閽� -->
<div class="tree-actions mb10">
- <el-button type="primary" size="small" icon="Plus" @click="handleAddTask">娣诲姞浠诲姟</el-button>
- <el-button type="success" size="small" icon="RefreshRight" @click="refreshTree">鍒锋柊</el-button>
- <el-button type="info" size="small" icon="Filter" @click="toggleFilter">
+ <el-button type="primary"
+ size="small"
+ icon="Plus"
+ @click="handleAddTask">娣诲姞浠诲姟</el-button>
+ <el-button type="success"
+ size="small"
+ icon="RefreshRight"
+ @click="refreshTree">鍒锋柊</el-button>
+ <el-button type="info"
+ size="small"
+ icon="Filter"
+ @click="toggleFilter">
{{ showFilter ? '闅愯棌绛涢��' : '鏄剧ず绛涢��' }}
</el-button>
</div>
-
<!-- 绛涢�夋潯浠� -->
- <div v-if="showFilter" class="filter-section mb10">
- <el-form :inline="true" :model="filterParams">
+ <div v-if="showFilter"
+ class="filter-section mb10">
+ <el-form :inline="true"
+ :model="filterParams">
<el-form-item label="浠诲姟鐘舵��">
- <el-select v-model="filterParams.status" placeholder="鍏ㄩ儴" clearable style="width: 120px">
- <el-option label="鏈紑濮�" value="notStarted" />
- <el-option label="杩涜涓�" value="inProgress" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸查�炬湡" value="overdue" />
+ <el-select v-model="filterParams.status"
+ placeholder="鍏ㄩ儴"
+ clearable
+ style="width: 120px">
+ <el-option label="鏈紑濮�"
+ value="notStarted" />
+ <el-option label="杩涜涓�"
+ value="inProgress" />
+ <el-option label="宸插畬鎴�"
+ value="completed" />
+ <el-option label="宸查�炬湡"
+ value="overdue" />
</el-select>
</el-form-item>
<el-form-item label="璐熻矗浜�">
- <el-input v-model="filterParams.assignee" placeholder="杈撳叆璐熻矗浜�" clearable style="width: 150px" />
+ <el-input v-model="filterParams.assignee"
+ placeholder="杈撳叆璐熻矗浜�"
+ clearable
+ style="width: 150px" />
</el-form-item>
<el-form-item>
- <el-button type="primary" size="small" @click="filterTree">绛涢��</el-button>
- <el-button size="small" @click="resetFilter">閲嶇疆</el-button>
+ <el-button type="primary"
+ size="small"
+ @click="filterTree">绛涢��</el-button>
+ <el-button size="small"
+ @click="resetFilter">閲嶇疆</el-button>
</el-form-item>
</el-form>
</div>
-
<!-- 浠诲姟鏍� -->
<div class="tree-content">
- <el-tree
- v-loading="loading"
- :data="taskTreeData"
- :props="defaultProps"
- :expand-on-click-node="false"
- node-key="nodeId"
- ref="treeRef"
- @node-contextmenu="handleContextMenu"
- @node-click="handleNodeClick"
- >
+ <el-tree v-loading="loading"
+ :data="taskTreeData"
+ :props="defaultProps"
+ :expand-on-click-node="false"
+ node-key="nodeId"
+ ref="treeRef"
+ @node-contextmenu="handleContextMenu"
+ @node-click="handleNodeClick">
<template #default="{ node, data }">
<!-- 鑺傜偣鍐呭 -->
- <div class="tree-node-content" :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }">
+ <div class="tree-node-content"
+ :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }">
<!-- 鑺傜偣鍥炬爣 -->
<div class="node-icon">
- <i v-if="data.type === 'phase'" class="el-icon-folder text-primary" />
- <i v-else-if="data.status === 'completed'" class="el-icon-circle-check text-success" />
- <i v-else-if="data.status === 'inProgress'" class="el-icon-circle-check text-primary" />
- <i v-else-if="data.status === 'overdue'" class="el-icon-alarm-clock text-danger" />
- <i v-else class="el-icon-circle-close text-gray-400" />
+ <i v-if="data.type === 'phase'"
+ class="el-icon-folder text-primary" />
+ <i v-else-if="data.status === 'completed'"
+ class="el-icon-circle-check text-success" />
+ <i v-else-if="data.status === 'inProgress'"
+ class="el-icon-circle-check text-primary" />
+ <i v-else-if="data.status === 'overdue'"
+ class="el-icon-alarm-clock text-danger" />
+ <i v-else
+ class="el-icon-circle-close text-gray-400" />
</div>
-
<!-- 鑺傜偣鏍囬鍜屾弿杩� -->
<div class="node-info">
- <div class="node-title" :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }">
+ <div class="node-title"
+ :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }">
{{ node.label }}
- <span v-if="data.type === 'task' && data.priority === 'high'" class="priority-tag">楂樹紭</span>
- <span v-else-if="data.type === 'task' && data.priority === 'medium'" class="priority-tag medium">涓紭</span>
+ <span v-if="data.type === 'task' && data.priority === 'high'"
+ class="priority-tag">楂樹紭</span>
+ <span v-else-if="data.type === 'task' && data.priority === 'medium'"
+ class="priority-tag medium">涓紭</span>
</div>
- <div v-if="data.description" class="node-description">{{ data.description }}</div>
-
+ <div v-if="data.description"
+ class="node-description">{{ data.description }}</div>
<!-- 浠诲姟鍏冧俊鎭� -->
- <div v-if="data.type === 'task'" class="task-meta">
+ <div v-if="data.type === 'task'"
+ class="task-meta">
<span class="meta-item">
<i class="el-icon-user"></i>
{{ data.assigneeName || '鏈垎閰�' }}
@@ -75,97 +104,115 @@
</span>
</div>
</div>
-
<!-- 浠诲姟杩涘害鏉� -->
- <div v-if="data.type === 'task'" class="task-progress">
- <el-progress :percentage="data.progress || 0" :stroke-width="4" :show-text="false" />
+ <div v-if="data.type === 'task'"
+ class="task-progress">
+ <el-progress :percentage="data.progress || 0"
+ :stroke-width="4"
+ :show-text="false" />
</div>
-
<!-- 鎿嶄綔鎸夐挳 -->
<div class="node-actions">
- <el-button
- v-if="data.type === 'task'"
- type="text"
- size="small"
- icon="Edit"
- @click.stop="handleEditTask(data)"
- v-hasPermi="['oaSystem:task:edit']"
- />
- <el-button
- v-if="data.type === 'phase'"
- type="text"
- size="small"
- icon="Plus"
- @click.stop="handleAddTaskUnderPhase(data)"
- v-hasPermi="['oaSystem:task:add']"
- />
- <el-button
- type="text"
- size="small"
- icon="Delete"
- @click.stop="handleDeleteNode(data)"
- v-hasPermi="['oaSystem:task:remove']"
- />
+ <el-button v-if="data.type === 'task'"
+ type="text"
+ size="small"
+ icon="Edit"
+ @click.stop="handleEditTask(data)"
+ v-hasPermi="['oaSystem:task:edit']" />
+ <el-button v-if="data.type === 'phase'"
+ type="text"
+ size="small"
+ icon="Plus"
+ @click.stop="handleAddTaskUnderPhase(data)"
+ v-hasPermi="['oaSystem:task:add']" />
+ <el-button type="text"
+ size="small"
+ icon="Delete"
+ @click.stop="handleDeleteNode(data)"
+ v-hasPermi="['oaSystem:task:remove']" />
</div>
</div>
</template>
</el-tree>
</div>
-
<!-- 鍙抽敭鑿滃崟 -->
- <div v-if="showContextMenu" :style="contextMenuStyle" class="context-menu">
+ <div v-if="showContextMenu"
+ :style="contextMenuStyle"
+ class="context-menu">
<el-menu @select="handleContextMenuSelect">
- <el-menu-item v-if="selectedNode.type === 'task'" index="edit">缂栬緫浠诲姟</el-menu-item>
- <el-menu-item v-if="selectedNode.type === 'phase'" index="addTask">娣诲姞瀛愪换鍔�</el-menu-item>
+ <el-menu-item v-if="selectedNode.type === 'task'"
+ index="edit">缂栬緫浠诲姟</el-menu-item>
+ <el-menu-item v-if="selectedNode.type === 'phase'"
+ index="addTask">娣诲姞瀛愪换鍔�</el-menu-item>
<el-menu-item index="delete">鍒犻櫎</el-menu-item>
<el-menu-item index="expandAll">灞曞紑鍏ㄩ儴</el-menu-item>
<el-menu-item index="collapseAll">鏀惰捣鍏ㄩ儴</el-menu-item>
</el-menu>
</div>
-
<!-- 浠诲姟琛ㄥ崟瀵硅瘽妗� -->
- <el-dialog :title="dialogTitle" v-model="dialogOpen" width="600px" append-to-body>
- <el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="80px">
- <el-form-item label="浠诲姟鍚嶇О" prop="taskName">
- <el-input v-model="taskForm.taskName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" />
+ <el-dialog :title="dialogTitle"
+ v-model="dialogOpen"
+ width="600px"
+ append-to-body>
+ <el-form ref="taskFormRef"
+ :model="taskForm"
+ :rules="taskRules"
+ label-width="80px">
+ <el-form-item label="浠诲姟鍚嶇О"
+ prop="taskName">
+ <el-input v-model="taskForm.taskName"
+ placeholder="璇疯緭鍏ヤ换鍔″悕绉�" />
</el-form-item>
- <el-form-item label="璐熻矗浜�" prop="assigneeId">
- <el-input v-model="taskForm.assigneeId" placeholder="璇疯緭鍏ヨ礋璐d汉ID" />
+ <el-form-item label="璐熻矗浜�"
+ prop="assigneeId">
+ <el-input v-model="taskForm.assigneeId"
+ placeholder="璇疯緭鍏ヨ礋璐d汉ID" />
</el-form-item>
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="taskForm.startDate"
- type="date"
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- style="width: 100%"
- />
+ <el-form-item label="寮�濮嬫棩鏈�"
+ prop="startDate">
+ <el-date-picker v-model="taskForm.startDate"
+ type="date"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%" />
</el-form-item>
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="taskForm.endDate"
- type="date"
- placeholder="閫夋嫨缁撴潫鏃ユ湡"
- style="width: 100%"
- />
+ <el-form-item label="缁撴潫鏃ユ湡"
+ prop="endDate">
+ <el-date-picker v-model="taskForm.endDate"
+ type="date"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%" />
</el-form-item>
- <el-form-item label="浼樺厛绾�" prop="priority">
- <el-select v-model="taskForm.priority" placeholder="閫夋嫨浼樺厛绾�">
- <el-option label="浣�" value="low" />
- <el-option label="涓�" value="medium" />
- <el-option label="楂�" value="high" />
+ <el-form-item label="浼樺厛绾�"
+ prop="priority">
+ <el-select v-model="taskForm.priority"
+ placeholder="閫夋嫨浼樺厛绾�">
+ <el-option label="浣�"
+ value="low" />
+ <el-option label="涓�"
+ value="medium" />
+ <el-option label="楂�"
+ value="high" />
</el-select>
</el-form-item>
- <el-form-item label="杩涘害" prop="progress">
- <el-input-number v-model="taskForm.progress" :min="0" :max="100" style="width: 100%" />
+ <el-form-item label="杩涘害"
+ prop="progress">
+ <el-input-number v-model="taskForm.progress"
+ :min="0"
+ :max="100"
+ style="width: 100%" />
</el-form-item>
- <el-form-item label="鎻忚堪" prop="description">
- <el-input v-model="taskForm.description" type="textarea" placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
+ <el-form-item label="鎻忚堪"
+ prop="description">
+ <el-input v-model="taskForm.description"
+ type="textarea"
+ placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary"
+ @click="submitTaskForm">纭畾</el-button>
<el-button @click="dialogOpen = false">鍙栨秷</el-button>
- <el-button type="primary" @click="submitTaskForm">纭畾</el-button>
</div>
</template>
</el-dialog>
@@ -173,662 +220,676 @@
</template>
<script setup>
-import { ref, reactive, computed, watch, onMounted } from 'vue';
-import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from 'element-plus';
-// import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement';
+ import { ref, reactive, computed, watch, onMounted } from "vue";
+ import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from "element-plus";
+ // import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement';
-const props = defineProps({
- projectId: {
- type: String,
- required: true
- }
-});
-
-const emit = defineEmits(['refresh']);
-
-// 缁勪欢鐘舵��
-const loading = ref(false);
-const treeRef = ref();
-const showContextMenu = ref(false);
-const contextMenuStyle = ref({});
-const selectedNode = ref({});
-const showFilter = ref(false);
-const dialogOpen = ref(false);
-const dialogTitle = ref('');
-const taskFormRef = ref();
-
-// 绛涢�夊弬鏁�
-const filterParams = reactive({
- status: '',
- assignee: ''
-});
-
-// 浠诲姟琛ㄥ崟鏁版嵁
-const taskForm = reactive({
- taskId: undefined,
- taskName: '',
- description: '',
- startDate: '',
- endDate: '',
- assigneeId: '',
- assigneeName: '',
- status: 'notStarted',
- progress: 0,
- priority: 'medium',
- phaseId: '',
- projectId: props.projectId
-});
-
-// 琛ㄥ崟楠岃瘉瑙勫垯
-const taskRules = {
- taskName: [
- { required: true, message: '浠诲姟鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- { min: 2, max: 50, message: '浠诲姟鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
- ],
- startDate: [
- { required: true, message: '寮�濮嬫棩鏈熶笉鑳戒负绌�', trigger: 'change' }
- ],
- endDate: [
- { required: true, message: '缁撴潫鏃ユ湡涓嶈兘涓虹┖', trigger: 'change' }
- ],
- assigneeId: [
- { required: true, message: '璐熻矗浜轰笉鑳戒负绌�', trigger: 'blur' }
- ],
- progress: [
- { required: true, message: '杩涘害涓嶈兘涓虹┖', trigger: 'blur' },
- { type: 'number', min: 0, max: 100, message: '杩涘害蹇呴』鍦� 0 鍒� 100 涔嬮棿', trigger: 'blur' }
- ]
-};
-
-// 浠诲姟鏍戞暟鎹�
-const rawTaskTreeData = ref([]);
-
-// 妯℃嫙浠诲姟鏁版嵁
-const mockTaskData = {
- 'PRJ2023001': [
- {
- phaseId: 'PHASE001',
- phaseName: '闇�姹傚垎鏋�',
- startDate: '2023-11-01',
- endDate: '2023-11-15',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK001',
- taskName: '闇�姹傝皟鐮�',
- description: '璋冪爺鐢ㄦ埛闇�姹傚拰涓氬姟娴佺▼',
- startDate: '2023-11-01',
- endDate: '2023-11-05',
- assigneeId: 'USER001',
- assigneeName: '寮犱笁',
- status: 'completed',
- progress: 100,
- priority: 'medium'
- },
- {
- taskId: 'TASK002',
- taskName: '闇�姹傛枃妗g紪鍐�',
- description: '缂栧啓璇︾粏鐨勯渶姹傝鏍艰鏄庝功',
- startDate: '2023-11-06',
- endDate: '2023-11-15',
- assigneeId: 'USER002',
- assigneeName: '鏉庡洓',
- status: 'completed',
- progress: 100,
- priority: 'high'
- }
- ]
+ const props = defineProps({
+ projectId: {
+ type: String,
+ required: true,
},
- {
- phaseId: 'PHASE002',
- phaseName: '绯荤粺璁捐',
- startDate: '2023-11-16',
- endDate: '2023-12-10',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK003',
- taskName: '绯荤粺鏋舵瀯璁捐',
- description: '璁捐绯荤粺鏁翠綋鏋舵瀯',
- startDate: '2023-11-16',
- endDate: '2023-11-25',
- assigneeId: 'USER003',
- assigneeName: '鐜嬩簲',
- status: 'completed',
- progress: 100,
- priority: 'high'
- },
- {
- taskId: 'TASK004',
- taskName: '鏁版嵁搴撹璁�',
- description: '璁捐鏁版嵁搴撹〃缁撴瀯鍜屽叧绯�',
- startDate: '2023-11-26',
- endDate: '2023-12-10',
- assigneeId: 'USER004',
- assigneeName: '璧靛叚',
- status: 'completed',
- progress: 100,
- priority: 'medium'
- }
- ]
- },
- {
- phaseId: 'PHASE003',
- phaseName: '寮�鍙戝疄鐜�',
- startDate: '2023-12-11',
- endDate: '2024-01-31',
- status: 'inProgress',
- tasks: [
- {
- taskId: 'TASK005',
- taskName: '鍓嶇寮�鍙�',
- description: '寮�鍙戠敤鎴风晫闈㈠拰浜や簰閫昏緫',
- startDate: '2023-12-11',
- endDate: '2024-01-15',
- assigneeId: 'USER005',
- assigneeName: '閽变竷',
- status: 'inProgress',
- progress: 70,
- priority: 'high'
- },
- {
- taskId: 'TASK006',
- taskName: '鍚庣寮�鍙�',
- description: '寮�鍙戜笟鍔¢�昏緫鍜孉PI鎺ュ彛',
- startDate: '2023-12-11',
- endDate: '2024-01-20',
- assigneeId: 'USER006',
- assigneeName: '瀛欏叓',
- status: 'inProgress',
- progress: 60,
- priority: 'high'
- }
- ]
- }
- ],
- // 榛樿鏁版嵁
- default: [
- {
- phaseId: 'PHASE_DEFAULT1',
- phaseName: '鍑嗗闃舵',
- startDate: '2023-01-01',
- endDate: '2023-03-31',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK_DEFAULT1',
- taskName: '椤圭洰鍚姩',
- description: '鍙紑椤圭洰鍚姩浼氳',
- startDate: '2023-01-01',
- endDate: '2023-01-05',
- assigneeId: 'USER_DEFAULT1',
- assigneeName: '璐熻矗浜篈',
- status: 'completed',
- progress: 100,
- priority: 'high'
- }
- ]
- },
- {
- phaseId: 'PHASE_DEFAULT2',
- phaseName: '鎵ц闃舵',
- startDate: '2023-04-01',
- endDate: '2023-09-30',
- status: 'inProgress',
- tasks: [
- {
- taskId: 'TASK_DEFAULT2',
- taskName: '鏍稿績鍔熻兘寮�鍙�',
- description: '寮�鍙戠郴缁熸牳蹇冨姛鑳芥ā鍧�',
- startDate: '2023-04-01',
- endDate: '2023-06-30',
- assigneeId: 'USER_DEFAULT2',
- assigneeName: '璐熻矗浜築',
- status: 'inProgress',
- progress: 50,
- priority: 'high'
- }
- ]
- }
- ]
-};
-
-const taskTreeData = computed(() => {
- // 搴旂敤绛涢�夋潯浠�
- if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) {
- return rawTaskTreeData.value;
- }
-
- // 娣辨嫹璐濆師濮嬫暟鎹互閬垮厤淇敼
- const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value));
-
- // 閫掑綊绛涢�夎妭鐐�
- const filterNodes = (nodes) => {
- const result = [];
-
- nodes.forEach(node => {
- // 瀵逛簬闃舵鑺傜偣锛屾鏌ュ叾瀛愪换鍔℃槸鍚︾鍚堢瓫閫夋潯浠�
- if (node.type === 'phase' && node.children) {
- const filteredChildren = filterNodes(node.children);
- if (filteredChildren.length > 0) {
- // 淇濈暀鑷冲皯鏈変竴涓瓙浠诲姟绗﹀悎鏉′欢鐨勯樁娈�
- node.children = filteredChildren;
- result.push(node);
- }
- }
- // 瀵逛簬浠诲姟鑺傜偣锛岀洿鎺ュ簲鐢ㄧ瓫閫夋潯浠�
- else if (node.type === 'task') {
- const statusMatch = !filterParams.status || node.status === filterParams.status;
- const assigneeMatch = !filterParams.assignee ||
- (node.assigneeName && node.assigneeName.includes(filterParams.assignee));
-
- if (statusMatch && assigneeMatch) {
- result.push(node);
- }
- }
- });
-
- return result;
- };
-
- return filterNodes(filteredData);
-});
-
-// 鏍戣妭鐐归厤缃�
-const defaultProps = {
- children: 'children',
- label: (data) => {
- if (data.type === 'phase') {
- return `${data.phaseName}`;
- } else {
- return `${data.taskName}`;
- }
- }
-};
-
-// 鍔犺浇浠诲姟鏍戞暟鎹�
-const loadTaskTree = async () => {
- loading.value = true;
- // try {
- // const { data } = await getProject(props.projectId);
- // rawTaskTreeData.value = buildTaskTree(data.phases || []);
- // } catch (error) {
- // ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
- // console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
- // } finally {
- // loading.value = false;
- // }
- try {
- // 妯℃嫙缃戠粶寤惰繜
- await new Promise(resolve => setTimeout(resolve, 500));
-
- // 浣跨敤妯℃嫙鏁版嵁鏇夸唬API璇锋眰
- const phases = mockTaskData[props.projectId] || mockTaskData.default;
- rawTaskTreeData.value = buildTaskTree(phases);
- } catch (error) {
- ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
- console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
- } finally {
- loading.value = false;
- }
-};
-
-// 鏋勫缓浠诲姟鏍�
-const buildTaskTree = (phases) => {
- return phases.map(phase => ({
- nodeId: phase.phaseId,
- phaseId: phase.phaseId,
- phaseName: phase.phaseName,
- type: 'phase',
- children: (phase.tasks || []).map(task => ({
- nodeId: task.taskId,
- taskId: task.taskId,
- taskName: task.taskName,
- description: task.description,
- startDate: task.startDate,
- endDate: task.endDate,
- assigneeId: task.assigneeId,
- assigneeName: task.assigneeName,
- status: task.status,
- progress: task.progress,
- priority: task.priority,
- phaseId: task.phaseId,
- projectId: props.projectId,
- type: 'task'
- }))
- }));
-};
-
-// 鏍煎紡鍖栨棩鏈熻寖鍥�
-const formatDateRange = (startDate, endDate) => {
- if (!startDate || !endDate) return '';
- return `${startDate} - ${endDate}`;
-};
-
-// 鍒锋柊鏍�
-const refreshTree = () => {
- loadTaskTree();
- // 閫氱煡鐖剁粍浠跺埛鏂版暟鎹�
- emit('refresh');
-};
-
-// 鍒囨崲绛涢�夐潰鏉�
-const toggleFilter = () => {
- showFilter.value = !showFilter.value;
-};
-
-// 搴旂敤绛涢��
-const filterTree = () => {
- // 绛涢�夐�昏緫宸茬粡鍦╟omputed涓疄鐜�
-};
-
-// 閲嶇疆绛涢��
-const resetFilter = () => {
- filterParams.status = '';
- filterParams.assignee = '';
-};
-
-// 澶勭悊鑺傜偣鐐瑰嚮
-const handleNodeClick = (data, node) => {
- // 鍒囨崲灞曞紑/鏀惰捣鐘舵��
- if (data.type === 'phase') {
- node.expanded = !node.expanded;
- }
-};
-
-// 澶勭悊鍙抽敭鑿滃崟
-const handleContextMenu = (event, data) => {
- event.preventDefault();
- selectedNode.value = data;
- contextMenuStyle.value = {
- position: 'fixed',
- left: `${event.clientX}px`,
- top: `${event.clientY}px`,
- zIndex: 1000
- };
- showContextMenu.value = true;
-};
-
-// 澶勭悊鍙抽敭鑿滃崟閫夋嫨
-const handleContextMenuSelect = (index) => {
- showContextMenu.value = false;
- switch (index) {
- case 'edit':
- if (selectedNode.value.type === 'task') {
- handleEditTask(selectedNode.value);
- }
- break;
- case 'addTask':
- if (selectedNode.value.type === 'phase') {
- handleAddTaskUnderPhase(selectedNode.value);
- }
- break;
- case 'delete':
- handleDeleteNode(selectedNode.value);
- break;
- case 'expandAll':
- treeRef.value?.expandAll();
- break;
- case 'collapseAll':
- treeRef.value?.collapseAll();
- break;
- }
-};
-
-// 娣诲姞浠诲姟
-const handleAddTask = () => {
- resetTaskForm();
- dialogTitle.value = '娣诲姞浠诲姟';
- dialogOpen.value = true;
-};
-
-// 鍦ㄦ寚瀹氶樁娈典笅娣诲姞浠诲姟
-const handleAddTaskUnderPhase = (phase) => {
- resetTaskForm();
- taskForm.phaseId = phase.phaseId;
- dialogTitle.value = '娣诲姞瀛愪换鍔�';
- dialogOpen.value = true;
-};
-
-// 缂栬緫浠诲姟
-const handleEditTask = (task) => {
- resetTaskForm();
- Object.assign(taskForm, { ...task });
- dialogTitle.value = '缂栬緫浠诲姟';
- dialogOpen.value = true;
-};
-
-// 鍒犻櫎鑺傜偣
-const handleDeleteNode = async (node) => {
- const confirmMessage = node.type === 'phase'
- ? `纭畾瑕佸垹闄ら樁娈� "${node.phaseName}" 鍙婂叾鎵�鏈夊瓙浠诲姟鍚楋紵`
- : `纭畾瑕佸垹闄や换鍔� "${node.taskName}" 鍚楋紵`;
-
- await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).catch(() => {
- throw new Error('鍙栨秷鍒犻櫎');
});
-
- try {
- if (node.type === 'phase') {
- await deletePhase(node.phaseId);
- } else {
- await deleteTask(node.taskId);
+
+ const emit = defineEmits(["refresh"]);
+
+ // 缁勪欢鐘舵��
+ const loading = ref(false);
+ const treeRef = ref();
+ const showContextMenu = ref(false);
+ const contextMenuStyle = ref({});
+ const selectedNode = ref({});
+ const showFilter = ref(false);
+ const dialogOpen = ref(false);
+ const dialogTitle = ref("");
+ const taskFormRef = ref();
+
+ // 绛涢�夊弬鏁�
+ const filterParams = reactive({
+ status: "",
+ assignee: "",
+ });
+
+ // 浠诲姟琛ㄥ崟鏁版嵁
+ const taskForm = reactive({
+ taskId: undefined,
+ taskName: "",
+ description: "",
+ startDate: "",
+ endDate: "",
+ assigneeId: "",
+ assigneeName: "",
+ status: "notStarted",
+ progress: 0,
+ priority: "medium",
+ phaseId: "",
+ projectId: props.projectId,
+ });
+
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ const taskRules = {
+ taskName: [
+ { required: true, message: "浠诲姟鍚嶇О涓嶈兘涓虹┖", trigger: "blur" },
+ {
+ min: 2,
+ max: 50,
+ message: "浠诲姟鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�",
+ trigger: "blur",
+ },
+ ],
+ startDate: [
+ { required: true, message: "寮�濮嬫棩鏈熶笉鑳戒负绌�", trigger: "change" },
+ ],
+ endDate: [{ required: true, message: "缁撴潫鏃ユ湡涓嶈兘涓虹┖", trigger: "change" }],
+ assigneeId: [{ required: true, message: "璐熻矗浜轰笉鑳戒负绌�", trigger: "blur" }],
+ progress: [
+ { required: true, message: "杩涘害涓嶈兘涓虹┖", trigger: "blur" },
+ {
+ type: "number",
+ min: 0,
+ max: 100,
+ message: "杩涘害蹇呴』鍦� 0 鍒� 100 涔嬮棿",
+ trigger: "blur",
+ },
+ ],
+ };
+
+ // 浠诲姟鏍戞暟鎹�
+ const rawTaskTreeData = ref([]);
+
+ // 妯℃嫙浠诲姟鏁版嵁
+ const mockTaskData = {
+ PRJ2023001: [
+ {
+ phaseId: "PHASE001",
+ phaseName: "闇�姹傚垎鏋�",
+ startDate: "2023-11-01",
+ endDate: "2023-11-15",
+ status: "completed",
+ tasks: [
+ {
+ taskId: "TASK001",
+ taskName: "闇�姹傝皟鐮�",
+ description: "璋冪爺鐢ㄦ埛闇�姹傚拰涓氬姟娴佺▼",
+ startDate: "2023-11-01",
+ endDate: "2023-11-05",
+ assigneeId: "USER001",
+ assigneeName: "寮犱笁",
+ status: "completed",
+ progress: 100,
+ priority: "medium",
+ },
+ {
+ taskId: "TASK002",
+ taskName: "闇�姹傛枃妗g紪鍐�",
+ description: "缂栧啓璇︾粏鐨勯渶姹傝鏍艰鏄庝功",
+ startDate: "2023-11-06",
+ endDate: "2023-11-15",
+ assigneeId: "USER002",
+ assigneeName: "鏉庡洓",
+ status: "completed",
+ progress: 100,
+ priority: "high",
+ },
+ ],
+ },
+ {
+ phaseId: "PHASE002",
+ phaseName: "绯荤粺璁捐",
+ startDate: "2023-11-16",
+ endDate: "2023-12-10",
+ status: "completed",
+ tasks: [
+ {
+ taskId: "TASK003",
+ taskName: "绯荤粺鏋舵瀯璁捐",
+ description: "璁捐绯荤粺鏁翠綋鏋舵瀯",
+ startDate: "2023-11-16",
+ endDate: "2023-11-25",
+ assigneeId: "USER003",
+ assigneeName: "鐜嬩簲",
+ status: "completed",
+ progress: 100,
+ priority: "high",
+ },
+ {
+ taskId: "TASK004",
+ taskName: "鏁版嵁搴撹璁�",
+ description: "璁捐鏁版嵁搴撹〃缁撴瀯鍜屽叧绯�",
+ startDate: "2023-11-26",
+ endDate: "2023-12-10",
+ assigneeId: "USER004",
+ assigneeName: "璧靛叚",
+ status: "completed",
+ progress: 100,
+ priority: "medium",
+ },
+ ],
+ },
+ {
+ phaseId: "PHASE003",
+ phaseName: "寮�鍙戝疄鐜�",
+ startDate: "2023-12-11",
+ endDate: "2024-01-31",
+ status: "inProgress",
+ tasks: [
+ {
+ taskId: "TASK005",
+ taskName: "鍓嶇寮�鍙�",
+ description: "寮�鍙戠敤鎴风晫闈㈠拰浜や簰閫昏緫",
+ startDate: "2023-12-11",
+ endDate: "2024-01-15",
+ assigneeId: "USER005",
+ assigneeName: "閽变竷",
+ status: "inProgress",
+ progress: 70,
+ priority: "high",
+ },
+ {
+ taskId: "TASK006",
+ taskName: "鍚庣寮�鍙�",
+ description: "寮�鍙戜笟鍔¢�昏緫鍜孉PI鎺ュ彛",
+ startDate: "2023-12-11",
+ endDate: "2024-01-20",
+ assigneeId: "USER006",
+ assigneeName: "瀛欏叓",
+ status: "inProgress",
+ progress: 60,
+ priority: "high",
+ },
+ ],
+ },
+ ],
+ // 榛樿鏁版嵁
+ default: [
+ {
+ phaseId: "PHASE_DEFAULT1",
+ phaseName: "鍑嗗闃舵",
+ startDate: "2023-01-01",
+ endDate: "2023-03-31",
+ status: "completed",
+ tasks: [
+ {
+ taskId: "TASK_DEFAULT1",
+ taskName: "椤圭洰鍚姩",
+ description: "鍙紑椤圭洰鍚姩浼氳",
+ startDate: "2023-01-01",
+ endDate: "2023-01-05",
+ assigneeId: "USER_DEFAULT1",
+ assigneeName: "璐熻矗浜篈",
+ status: "completed",
+ progress: 100,
+ priority: "high",
+ },
+ ],
+ },
+ {
+ phaseId: "PHASE_DEFAULT2",
+ phaseName: "鎵ц闃舵",
+ startDate: "2023-04-01",
+ endDate: "2023-09-30",
+ status: "inProgress",
+ tasks: [
+ {
+ taskId: "TASK_DEFAULT2",
+ taskName: "鏍稿績鍔熻兘寮�鍙�",
+ description: "寮�鍙戠郴缁熸牳蹇冨姛鑳芥ā鍧�",
+ startDate: "2023-04-01",
+ endDate: "2023-06-30",
+ assigneeId: "USER_DEFAULT2",
+ assigneeName: "璐熻矗浜築",
+ status: "inProgress",
+ progress: 50,
+ priority: "high",
+ },
+ ],
+ },
+ ],
+ };
+
+ const taskTreeData = computed(() => {
+ // 搴旂敤绛涢�夋潯浠�
+ if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) {
+ return rawTaskTreeData.value;
}
- ElMessage.success('鍒犻櫎鎴愬姛');
- refreshTree();
- } catch (error) {
- if (error.message !== '鍙栨秷鍒犻櫎') {
- ElMessage.error('鍒犻櫎澶辫触');
- console.error('鍒犻櫎澶辫触:', error);
+
+ // 娣辨嫹璐濆師濮嬫暟鎹互閬垮厤淇敼
+ const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value));
+
+ // 閫掑綊绛涢�夎妭鐐�
+ const filterNodes = nodes => {
+ const result = [];
+
+ nodes.forEach(node => {
+ // 瀵逛簬闃舵鑺傜偣锛屾鏌ュ叾瀛愪换鍔℃槸鍚︾鍚堢瓫閫夋潯浠�
+ if (node.type === "phase" && node.children) {
+ const filteredChildren = filterNodes(node.children);
+ if (filteredChildren.length > 0) {
+ // 淇濈暀鑷冲皯鏈変竴涓瓙浠诲姟绗﹀悎鏉′欢鐨勯樁娈�
+ node.children = filteredChildren;
+ result.push(node);
+ }
+ }
+ // 瀵逛簬浠诲姟鑺傜偣锛岀洿鎺ュ簲鐢ㄧ瓫閫夋潯浠�
+ else if (node.type === "task") {
+ const statusMatch =
+ !filterParams.status || node.status === filterParams.status;
+ const assigneeMatch =
+ !filterParams.assignee ||
+ (node.assigneeName &&
+ node.assigneeName.includes(filterParams.assignee));
+
+ if (statusMatch && assigneeMatch) {
+ result.push(node);
+ }
+ }
+ });
+
+ return result;
+ };
+
+ return filterNodes(filteredData);
+ });
+
+ // 鏍戣妭鐐归厤缃�
+ const defaultProps = {
+ children: "children",
+ label: data => {
+ if (data.type === "phase") {
+ return `${data.phaseName}`;
+ } else {
+ return `${data.taskName}`;
+ }
+ },
+ };
+
+ // 鍔犺浇浠诲姟鏍戞暟鎹�
+ const loadTaskTree = async () => {
+ loading.value = true;
+ // try {
+ // const { data } = await getProject(props.projectId);
+ // rawTaskTreeData.value = buildTaskTree(data.phases || []);
+ // } catch (error) {
+ // ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
+ // console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
+ // } finally {
+ // loading.value = false;
+ // }
+ try {
+ // 妯℃嫙缃戠粶寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // 浣跨敤妯℃嫙鏁版嵁鏇夸唬API璇锋眰
+ const phases = mockTaskData[props.projectId] || mockTaskData.default;
+ rawTaskTreeData.value = buildTaskTree(phases);
+ } catch (error) {
+ ElMessage.error("鍔犺浇浠诲姟鏍戝け璐�");
+ console.error("鍔犺浇浠诲姟鏍戝け璐�:", error);
+ } finally {
+ loading.value = false;
}
- }
-};
+ };
-// 閲嶇疆浠诲姟琛ㄥ崟
-const resetTaskForm = () => {
- taskForm.taskId = undefined;
- taskForm.taskName = '';
- taskForm.description = '';
- taskForm.startDate = '';
- taskForm.endDate = '';
- taskForm.assigneeId = '';
- taskForm.assigneeName = '';
- taskForm.status = 'notStarted';
- taskForm.progress = 0;
- taskForm.priority = 'medium';
- taskForm.phaseId = '';
- taskForm.projectId = props.projectId;
-
- if (taskFormRef.value) {
- taskFormRef.value.resetFields();
- }
-};
+ // 鏋勫缓浠诲姟鏍�
+ const buildTaskTree = phases => {
+ return phases.map(phase => ({
+ nodeId: phase.phaseId,
+ phaseId: phase.phaseId,
+ phaseName: phase.phaseName,
+ type: "phase",
+ children: (phase.tasks || []).map(task => ({
+ nodeId: task.taskId,
+ taskId: task.taskId,
+ taskName: task.taskName,
+ description: task.description,
+ startDate: task.startDate,
+ endDate: task.endDate,
+ assigneeId: task.assigneeId,
+ assigneeName: task.assigneeName,
+ status: task.status,
+ progress: task.progress,
+ priority: task.priority,
+ phaseId: task.phaseId,
+ projectId: props.projectId,
+ type: "task",
+ })),
+ }));
+ };
-// 鎻愪氦浠诲姟琛ㄥ崟
-const submitTaskForm = async () => {
- try {
- await taskFormRef.value.validate();
-
- if (taskForm.taskId) {
- await updateTask(taskForm);
- ElMessage.success('淇敼浠诲姟鎴愬姛');
- } else {
- await addTask(taskForm);
- ElMessage.success('娣诲姞浠诲姟鎴愬姛');
- }
-
- dialogOpen.value = false;
- refreshTree();
- } catch (error) {
- console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
- }
-};
+ // 鏍煎紡鍖栨棩鏈熻寖鍥�
+ const formatDateRange = (startDate, endDate) => {
+ if (!startDate || !endDate) return "";
+ return `${startDate} - ${endDate}`;
+ };
-// 鐐瑰嚮鍏朵粬鍖哄煙鍏抽棴鍙抽敭鑿滃崟
-document.addEventListener('click', () => {
- if (showContextMenu.value) {
- showContextMenu.value = false;
- }
-});
-
-// 鐩戝惉椤圭洰ID鍙樺寲
-watch(() => props.projectId, (newProjectId) => {
- if (newProjectId) {
+ // 鍒锋柊鏍�
+ const refreshTree = () => {
loadTaskTree();
- }
-});
+ // 閫氱煡鐖剁粍浠跺埛鏂版暟鎹�
+ emit("refresh");
+ };
-// 鍒濆鍖�
-onMounted(() => {
- loadTaskTree();
-});
+ // 鍒囨崲绛涢�夐潰鏉�
+ const toggleFilter = () => {
+ showFilter.value = !showFilter.value;
+ };
+
+ // 搴旂敤绛涢��
+ const filterTree = () => {
+ // 绛涢�夐�昏緫宸茬粡鍦╟omputed涓疄鐜�
+ };
+
+ // 閲嶇疆绛涢��
+ const resetFilter = () => {
+ filterParams.status = "";
+ filterParams.assignee = "";
+ };
+
+ // 澶勭悊鑺傜偣鐐瑰嚮
+ const handleNodeClick = (data, node) => {
+ // 鍒囨崲灞曞紑/鏀惰捣鐘舵��
+ if (data.type === "phase") {
+ node.expanded = !node.expanded;
+ }
+ };
+
+ // 澶勭悊鍙抽敭鑿滃崟
+ const handleContextMenu = (event, data) => {
+ event.preventDefault();
+ selectedNode.value = data;
+ contextMenuStyle.value = {
+ position: "fixed",
+ left: `${event.clientX}px`,
+ top: `${event.clientY}px`,
+ zIndex: 1000,
+ };
+ showContextMenu.value = true;
+ };
+
+ // 澶勭悊鍙抽敭鑿滃崟閫夋嫨
+ const handleContextMenuSelect = index => {
+ showContextMenu.value = false;
+ switch (index) {
+ case "edit":
+ if (selectedNode.value.type === "task") {
+ handleEditTask(selectedNode.value);
+ }
+ break;
+ case "addTask":
+ if (selectedNode.value.type === "phase") {
+ handleAddTaskUnderPhase(selectedNode.value);
+ }
+ break;
+ case "delete":
+ handleDeleteNode(selectedNode.value);
+ break;
+ case "expandAll":
+ treeRef.value?.expandAll();
+ break;
+ case "collapseAll":
+ treeRef.value?.collapseAll();
+ break;
+ }
+ };
+
+ // 娣诲姞浠诲姟
+ const handleAddTask = () => {
+ resetTaskForm();
+ dialogTitle.value = "娣诲姞浠诲姟";
+ dialogOpen.value = true;
+ };
+
+ // 鍦ㄦ寚瀹氶樁娈典笅娣诲姞浠诲姟
+ const handleAddTaskUnderPhase = phase => {
+ resetTaskForm();
+ taskForm.phaseId = phase.phaseId;
+ dialogTitle.value = "娣诲姞瀛愪换鍔�";
+ dialogOpen.value = true;
+ };
+
+ // 缂栬緫浠诲姟
+ const handleEditTask = task => {
+ resetTaskForm();
+ Object.assign(taskForm, { ...task });
+ dialogTitle.value = "缂栬緫浠诲姟";
+ dialogOpen.value = true;
+ };
+
+ // 鍒犻櫎鑺傜偣
+ const handleDeleteNode = async node => {
+ const confirmMessage =
+ node.type === "phase"
+ ? `纭畾瑕佸垹闄ら樁娈� "${node.phaseName}" 鍙婂叾鎵�鏈夊瓙浠诲姟鍚楋紵`
+ : `纭畾瑕佸垹闄や换鍔� "${node.taskName}" 鍚楋紵`;
+
+ await ElMessageBox.confirm(confirmMessage, "纭鎿嶄綔", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).catch(() => {
+ throw new Error("鍙栨秷鍒犻櫎");
+ });
+
+ try {
+ if (node.type === "phase") {
+ await deletePhase(node.phaseId);
+ } else {
+ await deleteTask(node.taskId);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ refreshTree();
+ } catch (error) {
+ if (error.message !== "鍙栨秷鍒犻櫎") {
+ ElMessage.error("鍒犻櫎澶辫触");
+ console.error("鍒犻櫎澶辫触:", error);
+ }
+ }
+ };
+
+ // 閲嶇疆浠诲姟琛ㄥ崟
+ const resetTaskForm = () => {
+ taskForm.taskId = undefined;
+ taskForm.taskName = "";
+ taskForm.description = "";
+ taskForm.startDate = "";
+ taskForm.endDate = "";
+ taskForm.assigneeId = "";
+ taskForm.assigneeName = "";
+ taskForm.status = "notStarted";
+ taskForm.progress = 0;
+ taskForm.priority = "medium";
+ taskForm.phaseId = "";
+ taskForm.projectId = props.projectId;
+
+ if (taskFormRef.value) {
+ taskFormRef.value.resetFields();
+ }
+ };
+
+ // 鎻愪氦浠诲姟琛ㄥ崟
+ const submitTaskForm = async () => {
+ try {
+ await taskFormRef.value.validate();
+
+ if (taskForm.taskId) {
+ await updateTask(taskForm);
+ ElMessage.success("淇敼浠诲姟鎴愬姛");
+ } else {
+ await addTask(taskForm);
+ ElMessage.success("娣诲姞浠诲姟鎴愬姛");
+ }
+
+ dialogOpen.value = false;
+ refreshTree();
+ } catch (error) {
+ console.error("鎻愪氦琛ㄥ崟澶辫触:", error);
+ }
+ };
+
+ // 鐐瑰嚮鍏朵粬鍖哄煙鍏抽棴鍙抽敭鑿滃崟
+ document.addEventListener("click", () => {
+ if (showContextMenu.value) {
+ showContextMenu.value = false;
+ }
+ });
+
+ // 鐩戝惉椤圭洰ID鍙樺寲
+ watch(
+ () => props.projectId,
+ newProjectId => {
+ if (newProjectId) {
+ loadTaskTree();
+ }
+ }
+ );
+
+ // 鍒濆鍖�
+ onMounted(() => {
+ loadTaskTree();
+ });
</script>
<style scoped>
-.task-tree-container {
- padding: 10px;
-}
+ .task-tree-container {
+ padding: 10px;
+ }
-.tree-actions {
- display: flex;
- gap: 10px;
- align-items: center;
-}
+ .tree-actions {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ }
-.filter-section {
- background: #f5f7fa;
- padding: 10px;
- border-radius: 4px;
-}
+ .filter-section {
+ background: #f5f7fa;
+ padding: 10px;
+ border-radius: 4px;
+ }
-.tree-content {
- background: #fff;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 10px;
- max-height: 600px;
- overflow-y: auto;
-}
+ .tree-content {
+ background: #fff;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ padding: 10px;
+ max-height: 600px;
+ overflow-y: auto;
+ }
-.tree-node-content {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 5px 0;
- min-height: 40px;
-}
+ .tree-node-content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 5px 0;
+ min-height: 40px;
+ }
-.phase-node {
- font-weight: bold;
- color: #409eff;
-}
+ .phase-node {
+ font-weight: bold;
+ color: #409eff;
+ }
-.task-node {
- color: #606266;
-}
+ .task-node {
+ color: #606266;
+ }
-.node-icon {
- display: flex;
- align-items: center;
- width: 20px;
-}
+ .node-icon {
+ display: flex;
+ align-items: center;
+ width: 20px;
+ }
-.node-info {
- flex: 1;
- min-width: 0;
-}
+ .node-info {
+ flex: 1;
+ min-width: 0;
+ }
-.node-title {
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: 2px;
-}
+ .node-title {
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 2px;
+ }
-.overdue-title {
- color: #f56c6c;
- font-weight: bold;
-}
+ .overdue-title {
+ color: #f56c6c;
+ font-weight: bold;
+ }
-.priority-tag {
- background: #f56c6c;
- color: white;
- font-size: 10px;
- padding: 1px 4px;
- border-radius: 2px;
- margin-left: 5px;
-}
+ .priority-tag {
+ background: #f56c6c;
+ color: white;
+ font-size: 10px;
+ padding: 1px 4px;
+ border-radius: 2px;
+ margin-left: 5px;
+ }
-.priority-tag.medium {
- background: #e6a23c;
-}
+ .priority-tag.medium {
+ background: #e6a23c;
+ }
-.node-description {
- font-size: 12px;
- color: #909399;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
+ .node-description {
+ font-size: 12px;
+ color: #909399;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
-.task-meta {
- display: flex;
- gap: 15px;
- font-size: 12px;
- color: #909399;
- margin-top: 2px;
-}
+ .task-meta {
+ display: flex;
+ gap: 15px;
+ font-size: 12px;
+ color: #909399;
+ margin-top: 2px;
+ }
-.meta-item {
- display: flex;
- align-items: center;
- gap: 3px;
-}
+ .meta-item {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ }
-.task-progress {
- width: 120px;
- margin: 0 10px;
-}
+ .task-progress {
+ width: 120px;
+ margin: 0 10px;
+ }
-.node-actions {
- display: flex;
- gap: 5px;
- opacity: 0;
- transition: opacity 0.3s;
-}
+ .node-actions {
+ display: flex;
+ gap: 5px;
+ opacity: 0;
+ transition: opacity 0.3s;
+ }
-.tree-node-content:hover .node-actions {
- opacity: 1;
-}
+ .tree-node-content:hover .node-actions {
+ opacity: 1;
+ }
-.context-menu {
- background: white;
- border: 1px solid #ebeef5;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
- border-radius: 4px;
-}
+ .context-menu {
+ background: white;
+ border: 1px solid #ebeef5;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+ }
-.context-menu .el-menu {
- min-width: 120px;
- border: none;
-}
+ .context-menu .el-menu {
+ min-width: 120px;
+ border: none;
+ }
-.context-menu .el-menu-item {
- padding: 0 15px;
- height: 36px;
- line-height: 36px;
-}
+ .context-menu .el-menu-item {
+ padding: 0 15px;
+ height: 36px;
+ line-height: 36px;
+ }
-.context-menu .el-menu-item:hover {
- background-color: #f5f7fa;
-}
+ .context-menu .el-menu-item:hover {
+ background-color: #f5f7fa;
+ }
-.text-gray-400 {
- color: #c0c4cc;
-}
+ .text-gray-400 {
+ color: #c0c4cc;
+ }
</style>
\ No newline at end of file
--
Gitblit v1.9.3