zhangwencui
6 天以前 5333935ae59999c47653122a669f4326f0173c1c
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="请输入负责人ID" />
        <el-form-item label="负责人"
                      prop="assigneeId">
          <el-input v-model="taskForm.assigneeId"
                    placeholder="请输入负责人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: '需求文档编写',
          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: '开发业务逻辑和API接口',
          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: '负责人A',
          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: '负责人B',
          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 = () => {
  // 筛选逻辑已经在computed中实现
};
// 重置筛选
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: "需求文档编写",
            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: "开发业务逻辑和API接口",
            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: "负责人A",
            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: "负责人B",
            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 = () => {
    // 筛选逻辑已经在computed中实现
  };
  // 重置筛选
  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>