From db42d47f5692ef64e5436c5a6d29dcb537b44596 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期一, 26 一月 2026 16:36:13 +0800
Subject: [PATCH] 浪潮对接单点登录:mis调整

---
 src/views/oaSystem/projectManagement/components/taskTree.vue |  834 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 834 insertions(+), 0 deletions(-)

diff --git a/src/views/oaSystem/projectManagement/components/taskTree.vue b/src/views/oaSystem/projectManagement/components/taskTree.vue
new file mode 100644
index 0000000..11e3ae8
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/components/taskTree.vue
@@ -0,0 +1,834 @@
+<template>
+  <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">
+        {{ showFilter ? '闅愯棌绛涢��' : '鏄剧ず绛涢��' }}
+      </el-button>
+    </div>
+
+    <!-- 绛涢�夋潯浠� -->
+    <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>
+        </el-form-item>
+        <el-form-item label="璐熻矗浜�">
+          <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-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"
+      >
+        <template #default="{ node, data }">
+          <!-- 鑺傜偣鍐呭 -->
+          <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" />
+            </div>
+
+            <!-- 鑺傜偣鏍囬鍜屾弿杩� -->
+            <div class="node-info">
+              <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>
+              </div>
+              <div v-if="data.description" class="node-description">{{ data.description }}</div>
+              
+              <!-- 浠诲姟鍏冧俊鎭� -->
+              <div v-if="data.type === 'task'" class="task-meta">
+                <span class="meta-item">
+                  <i class="el-icon-user"></i>
+                  {{ data.assigneeName || '鏈垎閰�' }}
+                </span>
+                <span class="meta-item">
+                  <i class="el-icon-calendar"></i>
+                  {{ formatDateRange(data.startDate, data.endDate) }}
+                </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>
+
+            <!-- 鎿嶄綔鎸夐挳 -->
+            <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']"
+              />
+            </div>
+          </div>
+        </template>
+      </el-tree>
+    </div>
+
+    <!-- 鍙抽敭鑿滃崟 -->
+    <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 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-form-item>
+        <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>
+        <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-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>
+        <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 @click="dialogOpen = false">鍙栨秷</el-button>
+          <el-button type="primary" @click="submitTaskForm">纭畾</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</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';
+
+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'
+        }
+      ]
+    },
+    {
+      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);
+    }
+    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;
+}
+
+.tree-actions {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.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-node-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 5px 0;
+  min-height: 40px;
+}
+
+.phase-node {
+  font-weight: bold;
+  color: #409eff;
+}
+
+.task-node {
+  color: #606266;
+}
+
+.node-icon {
+  display: flex;
+  align-items: center;
+  width: 20px;
+}
+
+.node-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.node-title {
+  font-weight: 500;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-bottom: 2px;
+}
+
+.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.medium {
+  background: #e6a23c;
+}
+
+.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;
+}
+
+.meta-item {
+  display: flex;
+  align-items: center;
+  gap: 3px;
+}
+
+.task-progress {
+  width: 120px;
+  margin: 0 10px;
+}
+
+.node-actions {
+  display: flex;
+  gap: 5px;
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+.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 .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:hover {
+  background-color: #f5f7fa;
+}
+
+.text-gray-400 {
+  color: #c0c4cc;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3