From 71a9eef518f2f2f1a1eb2fb90f2eb8ab7b155bc8 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 08 一月 2026 14:57:56 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_天津军泰伟业' into dev_天津军泰伟业

---
 src/views/oaSystem/projectManagement/projectDetail.vue |  868 +++++++++++++++++++++++++++++----------------------------
 1 files changed, 448 insertions(+), 420 deletions(-)

diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
index c3b0779..83f3dd8 100644
--- a/src/views/oaSystem/projectManagement/projectDetail.vue
+++ b/src/views/oaSystem/projectManagement/projectDetail.vue
@@ -8,7 +8,8 @@
           <span>椤圭洰鍩烘湰淇℃伅</span>
         </div>
       </template>
-      <el-descriptions :column="2" border>
+      <el-descriptions :column="2"
+                       border>
         <el-descriptions-item label="椤圭洰鍚嶇О">{{ projectInfo.projectName }}</el-descriptions-item>
         <el-descriptions-item label="椤圭洰璐熻矗浜�">{{ projectInfo.managerName }}</el-descriptions-item>
         <el-descriptions-item label="寮�濮嬫棩鏈�">{{ projectInfo.startDate }}</el-descriptions-item>
@@ -17,12 +18,13 @@
           <el-tag :type="getStatusType(projectInfo.status)">{{ getStatusText(projectInfo.status) }}</el-tag>
         </el-descriptions-item>
         <el-descriptions-item label="瀹屾垚搴�">
-          <el-progress :percentage="projectInfo.completionRate" :stroke-width="6" />
+          <el-progress :percentage="projectInfo.completionRate"
+                       :stroke-width="6" />
         </el-descriptions-item>
-        <el-descriptions-item label="椤圭洰鎻忚堪" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="椤圭洰鎻忚堪"
+                              :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
       </el-descriptions>
     </el-card>
-
     <!-- 椤圭洰杩涘害姒傝 -->
     <el-card class="mb20">
       <template #header>
@@ -57,7 +59,6 @@
         </el-col>
       </el-row>
     </el-card>
-
     <!-- 闃舵鍜屼换鍔$鐞� -->
     <!-- <el-card class="mb20">
       <template #header>
@@ -68,162 +69,175 @@
       </template>
       <task-tree :project-id="projectId" @refresh="getProjectDetail" />
     </el-card> -->
-
     <!-- 閲岀▼纰戠鐞� -->
     <el-card class="mb20">
       <template #header>
         <div class="card-header">
           <span>椤圭洰闃舵閲岀▼纰�</span>
-          <el-button type="primary" size="small" @click="handleAddMilestone">娣诲姞閲岀▼纰�</el-button>
+          <el-button type="primary"
+                     size="small"
+                     @click="handleAddMilestone">娣诲姞閲岀▼纰�</el-button>
         </div>
       </template>
-      <milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/>
+      <milestone-list :project-id="projectId"
+                      @refresh="getProjectDetail"
+                      :key="`milestone-${refreshProjectId}`" />
     </el-card>
-
     <!-- 闃舵鐩爣绠$悊 -->
     <el-card>
       <template #header>
         <div class="card-header">
           <span>闃舵浠诲姟</span>
-          <el-button type="primary" size="small" @click="handleAddPhaseGoal">娣诲姞闃舵鐩爣</el-button>
+          <el-button type="primary"
+                     size="small"
+                     @click="handleAddPhaseGoal">娣诲姞闃舵鐩爣</el-button>
         </div>
       </template>
-      <phase-goal-list :project-id="projectId" @refresh="getProjectDetail" @editGoal="handleEditPhaseGoal" :key="`phaseGoal-${refreshProjectId}`"/>
+      <phase-goal-list :project-id="projectId"
+                       @refresh="getProjectDetail"
+                       @editGoal="handleEditPhaseGoal"
+                       :key="`phaseGoal-${refreshProjectId}`" />
     </el-card>
-
-    <!-- 閲岀▼纰戠鐞嗗脊妗� -->  
-    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
-      <el-form :model="form" ref="formRef" label-width="100px">
-    <el-form-item label="椤圭洰闃舵鍚嶇О" prop="phaseName">
-      <el-input
-        v-model="form.phaseName"
-        placeholder="璇疯緭鍏ラ」鐩樁娈靛悕绉�"
-        maxlength="50"
-      />
-    </el-form-item>
-    <el-row :gutter="20">
-      <el-col :span="12">
-        <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
-          <el-date-picker
-            v-model="form.startDate"
-            type="date"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-            placeholder="閫夋嫨寮�濮嬫棩鏈�"
-            style="width: 100%"
-          />
+    <!-- 閲岀▼纰戠鐞嗗脊妗� -->
+    <el-dialog :title="title"
+               v-model="open"
+               width="600px"
+               append-to-body>
+      <el-form :model="form"
+               ref="formRef"
+               label-width="100px">
+        <el-form-item label="椤圭洰闃舵鍚嶇О"
+                      prop="phaseName">
+          <el-input v-model="form.phaseName"
+                    placeholder="璇疯緭鍏ラ」鐩樁娈靛悕绉�"
+                    maxlength="50" />
         </el-form-item>
-      </el-col>
-      <el-col :span="12">
-        <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
-          <el-date-picker
-            v-model="form.endDate"
-            type="date"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-            placeholder="閫夋嫨缁撴潫鏃ユ湡"
-            style="width: 100%"
-          />
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="寮�濮嬫棩鏈�"
+                          prop="startDate">
+              <el-date-picker v-model="form.startDate"
+                              type="date"
+                              format="YYYY-MM-DD"
+                              value-format="YYYY-MM-DD"
+                              placeholder="閫夋嫨寮�濮嬫棩鏈�"
+                              style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="缁撴潫鏃ユ湡"
+                          prop="endDate">
+              <el-date-picker v-model="form.endDate"
+                              type="date"
+                              format="YYYY-MM-DD"
+                              value-format="YYYY-MM-DD"
+                              placeholder="閫夋嫨缁撴潫鏃ユ湡"
+                              style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="鐘舵��"
+                      prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio label="notStarted">鏈紑濮�</el-radio>
+            <el-radio label="completed">宸插畬鎴�</el-radio>
+            <el-radio label="delayed">宸插欢杩�</el-radio>
+          </el-radio-group>
         </el-form-item>
-      </el-col>
-    </el-row>
-    <el-form-item label="鐘舵��" prop="status">
-      <el-radio-group v-model="form.status">
-        <el-radio label="notStarted">鏈紑濮�</el-radio>
-        <el-radio label="completed">宸插畬鎴�</el-radio>
-        <el-radio label="delayed">宸插欢杩�</el-radio>
-      </el-radio-group>
-    </el-form-item>
-  </el-form>
+      </el-form>
       <template #footer>
         <div class="dialog-footer">
+          <el-button type="primary"
+                     @click="submitForm">纭畾</el-button>
           <el-button @click="cancel">鍙栨秷</el-button>
-          <el-button type="primary" @click="submitForm">纭畾</el-button>
         </div>
       </template>
     </el-dialog>
-
     <!-- 闃舵浠诲姟绠$悊寮规 -->
-    <el-dialog :title="goalTitle" v-model="goalOpen" width="600px" append-to-body>
-      <el-form :model="goalForm" ref="goalFormRef" label-width="100px">
-        <el-form-item label="鎵�灞為樁娈�" prop="phaseId">
-          <el-select v-model="goalForm.phaseId" placeholder="璇烽�夋嫨鎵�灞為樁娈�">
-            <el-option
-              v-for="phase in phaseList"
-              :key="phase.phaseId"
-              :label="phase.phaseName"
-              :value="phase.phaseId"
-            />
+    <el-dialog :title="goalTitle"
+               v-model="goalOpen"
+               width="600px"
+               append-to-body>
+      <el-form :model="goalForm"
+               ref="goalFormRef"
+               label-width="100px">
+        <el-form-item label="鎵�灞為樁娈�"
+                      prop="phaseId">
+          <el-select v-model="goalForm.phaseId"
+                     placeholder="璇烽�夋嫨鎵�灞為樁娈�">
+            <el-option v-for="phase in phaseList"
+                       :key="phase.phaseId"
+                       :label="phase.phaseName"
+                       :value="phase.phaseId" />
           </el-select>
         </el-form-item>
-        <el-form-item label="鐩爣鍚嶇О" prop="taskName">
-          <el-input
-            v-model="goalForm.taskName"
-            placeholder="璇疯緭鍏ョ洰鏍囧悕绉�"
-            maxlength="50"
-          />
+        <el-form-item label="鐩爣鍚嶇О"
+                      prop="taskName">
+          <el-input v-model="goalForm.taskName"
+                    placeholder="璇疯緭鍏ョ洰鏍囧悕绉�"
+                    maxlength="50" />
         </el-form-item>
-        <el-form-item label="鐩爣鍊�" prop="targetValue">
-          <el-input-number
-            v-model="goalForm.targetValue"
-            :min="0"
-            :precision="2"
-            placeholder="璇疯緭鍏ョ洰鏍囧��"
-            style="width: 100%"
-          />
+        <el-form-item label="鐩爣鍊�"
+                      prop="targetValue">
+          <el-input-number v-model="goalForm.targetValue"
+                           :min="0"
+                           :precision="2"
+                           placeholder="璇疯緭鍏ョ洰鏍囧��"
+                           style="width: 100%" />
         </el-form-item>
-        <el-form-item label="褰撳墠鍊�" prop="currentValue">
-          <el-input-number
-            v-model="goalForm.currentValue"
-            :min="0"
-            :precision="2"
-            placeholder="璇疯緭鍏ュ綋鍓嶅��"
-            style="width: 100%"
-          />
+        <el-form-item label="褰撳墠鍊�"
+                      prop="currentValue">
+          <el-input-number v-model="goalForm.currentValue"
+                           :min="0"
+                           :precision="2"
+                           placeholder="璇疯緭鍏ュ綋鍓嶅��"
+                           style="width: 100%" />
         </el-form-item>
-        <el-form-item label="鍗曚綅" prop="unit">
-          <el-input
-            v-model="goalForm.unit"
-            placeholder="璇疯緭鍏ュ崟浣�"
-            maxlength="10"
-          />
+        <el-form-item label="鍗曚綅"
+                      prop="unit">
+          <el-input v-model="goalForm.unit"
+                    placeholder="璇疯緭鍏ュ崟浣�"
+                    maxlength="10" />
         </el-form-item>
-        <el-form-item label="浠诲姟瀹屾垚鏃ユ湡" prop="targetDate">
-          <el-date-picker
-            v-model="goalForm.targetDate"
-            type="date"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-            placeholder="閫夋嫨鐩爣鏃ユ湡"
-            style="width: 100%"
-          />
+        <el-form-item label="浠诲姟瀹屾垚鏃ユ湡"
+                      prop="targetDate">
+          <el-date-picker v-model="goalForm.targetDate"
+                          type="date"
+                          format="YYYY-MM-DD"
+                          value-format="YYYY-MM-DD"
+                          placeholder="閫夋嫨鐩爣鏃ユ湡"
+                          style="width: 100%" />
         </el-form-item>
-        <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
-          <el-date-picker
-            v-model="goalForm.startDate"
-            type="date"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-            placeholder="閫夋嫨鐩爣鏃ユ湡"
-            style="width: 100%"
-          />
+        <el-form-item label="寮�濮嬫棩鏈�"
+                      prop="startDate">
+          <el-date-picker v-model="goalForm.startDate"
+                          type="date"
+                          format="YYYY-MM-DD"
+                          value-format="YYYY-MM-DD"
+                          placeholder="閫夋嫨鐩爣鏃ユ湡"
+                          style="width: 100%" />
         </el-form-item>
-        <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
-          <el-date-picker
-            v-model="goalForm.endDate"
-            type="date"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-            placeholder="閫夋嫨鐩爣鏃ユ湡"
-            style="width: 100%"
-          />
+        <el-form-item label="缁撴潫鏃ユ湡"
+                      prop="endDate">
+          <el-date-picker v-model="goalForm.endDate"
+                          type="date"
+                          format="YYYY-MM-DD"
+                          value-format="YYYY-MM-DD"
+                          placeholder="閫夋嫨鐩爣鏃ユ湡"
+                          style="width: 100%" />
         </el-form-item>
-        <el-form-item label="鐘舵��" prop="status">
-          <el-select v-model="goalForm.status" placeholder="璇烽�夋嫨鐘舵��">
-            <el-option label="鏈紑濮�" value="notStarted" />
-            <el-option label="杩涜涓�" value="inProgress" />
-            <el-option label="宸插畬鎴�" value="completed" />
-            <el-option label="宸插欢杩�" value="delayed" />
+        <el-form-item label="鐘舵��"
+                      prop="status">
+          <el-select v-model="goalForm.status"
+                     placeholder="璇烽�夋嫨鐘舵��">
+            <el-option label="鏈紑濮�"
+                       value="notStarted" />
+            <el-option label="杩涜涓�"
+                       value="inProgress" />
+            <el-option label="宸插畬鎴�"
+                       value="completed" />
+            <el-option label="宸插欢杩�"
+                       value="delayed" />
           </el-select>
         </el-form-item>
         <!-- <el-form-item label="瀹屾垚搴�" prop="completionRate">
@@ -239,8 +253,9 @@
       </el-form>
       <template #footer>
         <div class="dialog-footer">
+          <el-button type="primary"
+                     @click="submitGoalForm">纭畾</el-button>
           <el-button @click="cancelGoal">鍙栨秷</el-button>
-          <el-button type="primary" @click="submitGoalForm">纭畾</el-button>
         </div>
       </template>
     </el-dialog>
@@ -248,318 +263,331 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, watch } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import TaskTree from './components/taskTree.vue';
-import MilestoneList from './components/milestoneList.vue';
-import ProjectForm from './components/projectForm.vue';
-import PhaseGoalList from './components/phaseGoalList.vue';
-import { getProject, addProjectPhase, listProjectPhase, addProjectTask, updateProjectTask } from '@/api/oaSystem/projectManagement';
+  import { ref, reactive, onMounted, watch } from "vue";
+  import { useRoute, useRouter } from "vue-router";
+  import { ElMessage } from "element-plus";
+  import TaskTree from "./components/taskTree.vue";
+  import MilestoneList from "./components/milestoneList.vue";
+  import ProjectForm from "./components/projectForm.vue";
+  import PhaseGoalList from "./components/phaseGoalList.vue";
+  import {
+    getProject,
+    addProjectPhase,
+    listProjectPhase,
+    addProjectTask,
+    updateProjectTask,
+  } from "@/api/oaSystem/projectManagement";
 
-const route = useRoute();
-const router = useRouter();
-const open = ref(false);
-const title = ref('');
-const projectFormRef = ref();
-const formRef = ref();
-// 椤圭洰ID
-// 鍦ㄥ叾浠杛ef瀹氫箟闄勮繎娣诲姞
-const refreshProjectId = ref(0);
+  const route = useRoute();
+  const router = useRouter();
+  const open = ref(false);
+  const title = ref("");
+  const projectFormRef = ref();
+  const formRef = ref();
+  // 椤圭洰ID
+  // 鍦ㄥ叾浠杛ef瀹氫箟闄勮繎娣诲姞
+  const refreshProjectId = ref(0);
 
-const projectId = ref(route.params.projectId);
+  const projectId = ref(route.params.projectId);
 
-// 椤圭洰淇℃伅
-const projectInfo = reactive({
-  projectId: '',
-  projectName: '',
-  description: '',
-  startDate: '',
-  endDate: '',
-  managerId: '',
-  managerName: '',
-  status: 'planning',
-  completionRate: 0
-});
+  // 椤圭洰淇℃伅
+  const projectInfo = reactive({
+    projectId: "",
+    projectName: "",
+    description: "",
+    startDate: "",
+    endDate: "",
+    managerId: "",
+    managerName: "",
+    status: "planning",
+    completionRate: 0,
+  });
 
-// 缁熻淇℃伅
-const statistics = reactive({
-  totalPhases: 0,
-  totalTasks: 0,
-  completedTasks: 0
-});
-const form = reactive({
-  phaseId: '',
-  phaseName: '',
-  startDate: '',
-  endDate: '',
-  status: 'planning',
-  oaProjectId: projectId.value,
-})
+  // 缁熻淇℃伅
+  const statistics = reactive({
+    totalPhases: 0,
+    totalTasks: 0,
+    completedTasks: 0,
+  });
+  const form = reactive({
+    phaseId: "",
+    phaseName: "",
+    startDate: "",
+    endDate: "",
+    status: "planning",
+    oaProjectId: projectId.value,
+  });
 
-// 闃舵鐩爣鐩稿叧
-const goalOpen = ref(false);
-const goalTitle = ref('');
-const goalFormRef = ref();
-const phaseList = ref([]);
-const goalForm = reactive({
-  taskId: '',
-  phaseId: '',
-  taskName: '',
-  targetValue: 100,
-  currentValue: 0,
-  unit: '%',
-  targetDate: '',
-  startDate: '',
-  endDate: '',
-  status: 'notStarted',
-  completionRate: 0
-});
+  // 闃舵鐩爣鐩稿叧
+  const goalOpen = ref(false);
+  const goalTitle = ref("");
+  const goalFormRef = ref();
+  const phaseList = ref([]);
+  const goalForm = reactive({
+    taskId: "",
+    phaseId: "",
+    taskName: "",
+    targetValue: 100,
+    currentValue: 0,
+    unit: "%",
+    targetDate: "",
+    startDate: "",
+    endDate: "",
+    status: "notStarted",
+    completionRate: 0,
+  });
 
-// 鑾峰彇椤圭洰璇︽儏
-const getProjectDetail = async () => {
-  try {
-    getProject().then((res)=>{
-      console.log("椤圭洰璇︽儏",res)
-      const projectData = res.data[projectId.value];
-      // 鏇存柊椤圭洰淇℃伅
-      Object.assign(projectInfo, projectData);
-      
-      // 鏇存柊缁熻淇℃伅
-      updateStatistics(projectData);
+  // 鑾峰彇椤圭洰璇︽儏
+  const getProjectDetail = async () => {
+    try {
+      getProject().then(res => {
+        console.log("椤圭洰璇︽儏", res);
+        const projectData = res.data[projectId.value];
+        // 鏇存柊椤圭洰淇℃伅
+        Object.assign(projectInfo, projectData);
 
-      // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
-      // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
-      refreshProjectId.value++;
-    })
-  } catch (error) {
-    ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
-    console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
-  }
-};
+        // 鏇存柊缁熻淇℃伅
+        updateStatistics(projectData);
 
-// 鏇存柊缁熻淇℃伅
-const updateStatistics = (projectData) => {
-  // 杩欓噷鍋囪projectData涓寘鍚簡缁熻淇℃伅
-  // 濡傛灉娌℃湁锛岄渶瑕佸崟鐙姹傜粺璁℃暟鎹�
-  statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
-  statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
-  statistics.completedTasks = projectData.tasks ? 
-    projectData.tasks.filter(task => task.status === 'completed').length : 0;
-};
+        // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
+        // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
+        refreshProjectId.value++;
+      });
+    } catch (error) {
+      ElMessage.error("鑾峰彇椤圭洰璇︽儏澶辫触");
+      console.error("鑾峰彇椤圭洰璇︽儏澶辫触:", error);
+    }
+  };
 
-// 鑾峰彇椤圭洰闃舵鍒楄〃
-const getPhaseList = async () => {
-  try {
-    const { data } = await listProjectPhase(projectId.value);
-    phaseList.value = data.rows || data;
-  } catch (error) {
-    ElMessage.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触');
-    console.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:', error);
-  }
-};
+  // 鏇存柊缁熻淇℃伅
+  const updateStatistics = projectData => {
+    // 杩欓噷鍋囪projectData涓寘鍚簡缁熻淇℃伅
+    // 濡傛灉娌℃湁锛岄渶瑕佸崟鐙姹傜粺璁℃暟鎹�
+    statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
+    statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
+    statistics.completedTasks = projectData.tasks
+      ? projectData.tasks.filter(task => task.status === "completed").length
+      : 0;
+  };
 
-// 璁$畻瀹屾垚搴�
-const calculateCompletionRate = () => {
-  if (goalForm.targetValue > 0) {
-    goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
-  } else {
-    goalForm.completionRate = 0;
-  }
-};
+  // 鑾峰彇椤圭洰闃舵鍒楄〃
+  const getPhaseList = async () => {
+    try {
+      const { data } = await listProjectPhase(projectId.value);
+      phaseList.value = data.rows || data;
+    } catch (error) {
+      ElMessage.error("鑾峰彇椤圭洰闃舵鍒楄〃澶辫触");
+      console.error("鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:", error);
+    }
+  };
 
-// 娣诲姞闃舵
-const handleAddPhase = () => {
-  // resetForm();
-  ElMessage.info('娣诲姞闃舵鍔熻兘寰呭疄鐜�');
-};
-
-// 娣诲姞閲岀▼纰�
-const handleAddMilestone = () => {
-  resetForm();
-  open.value = true;
-  title.value = '鏂板椤圭洰闃舵';
-};
-
-// 娣诲姞闃舵浠诲姟
-const handleAddPhaseGoal = () => {
-  goalForm.taskId = '';
-  goalForm.phaseId = '';
-  goalForm.taskName = '';
-  goalForm.targetValue = 0;
-  goalForm.currentValue = 0;
-  goalForm.unit = '%';
-  goalForm.targetDate = '';
-  goalForm.startDate = '';
-  goalForm.endDate = '';
-  goalForm.status = 'notStarted';
-  goalForm.completionRate = 0;
-  if (goalFormRef.value) {
-    goalFormRef.value.resetFields();
-  }
-  getPhaseList();
-  goalTitle.value = '鏂板闃舵鐩爣';
-  goalOpen.value = true;
-};
-
-// 鎻愪氦琛ㄥ崟
-const submitForm = async () => {
-  try {
-    await formRef.value.validate();
-
-    if (form.phaseId) {
-      // await updateProject(form);
-      // ElMessage.success('淇敼椤圭洰闃舵鎴愬姛');
+  // 璁$畻瀹屾垚搴�
+  const calculateCompletionRate = () => {
+    if (goalForm.targetValue > 0) {
+      goalForm.completionRate = Math.min(
+        Math.round((goalForm.currentValue / goalForm.targetValue) * 100),
+        100
+      );
     } else {
-      console.log("form",form);
-      await addProjectPhase(form);
-      ElMessage.success('鏂板椤圭洰闃舵鎴愬姛');
+      goalForm.completionRate = 0;
+    }
+  };
+
+  // 娣诲姞闃舵
+  const handleAddPhase = () => {
+    // resetForm();
+    ElMessage.info("娣诲姞闃舵鍔熻兘寰呭疄鐜�");
+  };
+
+  // 娣诲姞閲岀▼纰�
+  const handleAddMilestone = () => {
+    resetForm();
+    open.value = true;
+    title.value = "鏂板椤圭洰闃舵";
+  };
+
+  // 娣诲姞闃舵浠诲姟
+  const handleAddPhaseGoal = () => {
+    goalForm.taskId = "";
+    goalForm.phaseId = "";
+    goalForm.taskName = "";
+    goalForm.targetValue = 0;
+    goalForm.currentValue = 0;
+    goalForm.unit = "%";
+    goalForm.targetDate = "";
+    goalForm.startDate = "";
+    goalForm.endDate = "";
+    goalForm.status = "notStarted";
+    goalForm.completionRate = 0;
+    if (goalFormRef.value) {
+      goalFormRef.value.resetFields();
+    }
+    getPhaseList();
+    goalTitle.value = "鏂板闃舵鐩爣";
+    goalOpen.value = true;
+  };
+
+  // 鎻愪氦琛ㄥ崟
+  const submitForm = async () => {
+    try {
+      await formRef.value.validate();
+
+      if (form.phaseId) {
+        // await updateProject(form);
+        // ElMessage.success('淇敼椤圭洰闃舵鎴愬姛');
+      } else {
+        console.log("form", form);
+        await addProjectPhase(form);
+        ElMessage.success("鏂板椤圭洰闃舵鎴愬姛");
+        getProjectDetail();
+      }
+      open.value = false;
+    } catch (error) {
+      console.error("鎻愪氦琛ㄥ崟澶辫触:", error);
+    }
+  };
+
+  // 鎻愪氦闃舵浠诲姟琛ㄥ崟
+  const submitGoalForm = async () => {
+    try {
+      await goalFormRef.value.validate();
+      calculateCompletionRate();
+
+      const goalData = {
+        ...goalForm,
+        oaProjectId: projectId.value,
+      };
+
+      if (goalForm.taskId) {
+        await updateProjectTask(goalData);
+        ElMessage.success("淇敼闃舵鐩爣鎴愬姛");
+      } else {
+        await addProjectTask(goalData);
+        ElMessage.success("鏂板闃舵鐩爣鎴愬姛");
+      }
+      // 璋冪敤getProjectDetail鍒锋柊鎵�鏈夌浉鍏虫暟鎹�
+      getProjectDetail();
+      goalOpen.value = false;
+    } catch (error) {
+      console.error("鎻愪氦闃舵鐩爣琛ㄥ崟澶辫触:", error);
+    }
+  };
+
+  // 閲嶇疆閲岀▼纰戣〃鍗�
+  const resetForm = () => {
+    form.phaseId = "";
+    form.phaseName = "";
+    form.startDate = "";
+    form.endDate = "";
+    form.status = "planning";
+    form.oaProjectId = projectId.value;
+    if (formRef.value) {
+      formRef.value.resetFields();
+    }
+  };
+
+  // 鍙栨秷闃舵浠诲姟鎿嶄綔
+  const cancelGoal = () => {
+    goalOpen.value = false;
+  };
+
+  // 鍙栨秷鎿嶄綔
+  const cancel = () => {
+    open.value = false;
+  };
+  // 缂栬緫闃舵浠诲姟
+  const handleEditPhaseGoal = async goal => {
+    // 澶嶅埗鐩爣鏁版嵁鍒拌〃鍗�
+    Object.assign(goalForm, goal);
+
+    // 鑾峰彇椤圭洰闃舵鍒楄〃
+    await getPhaseList();
+
+    // 鎵撳紑缂栬緫寮圭獥
+    goalTitle.value = "缂栬緫闃舵鐩爣";
+    goalOpen.value = true;
+  };
+  // 鑾峰彇鐘舵�佹爣绛剧被鍨�
+  const getStatusType = status => {
+    const statusTypeMap = {
+      planning: "info",
+      inProgress: "primary",
+      completed: "success",
+      paused: "warning",
+    };
+    return statusTypeMap[status] || "default";
+  };
+
+  // 鑾峰彇鐘舵�佹枃鏈�
+  const getStatusText = status => {
+    const statusTextMap = {
+      planning: "瑙勫垝涓�",
+      inProgress: "杩涜涓�",
+      completed: "宸插畬鎴�",
+      paused: "宸叉殏鍋�",
+    };
+    return statusTextMap[status] || status;
+  };
+
+  // 鐩戝惉璺敱鍙傛暟鍙樺寲
+  watch(
+    () => route.params.projectId,
+    newProjectId => {
+      // console.log('璺敱鍙傛暟鍙樺寲:', projectId);
+      if (newProjectId) {
+        projectId.value = newProjectId;
+        getProjectDetail();
+      }
+    }
+  );
+
+  // 鐩戝惉褰撳墠鍊煎拰鐩爣鍊煎彉鍖栵紝閲嶆柊璁$畻瀹屾垚搴�
+  watch(
+    () => [goalForm.currentValue, goalForm.targetValue],
+    () => {
+      calculateCompletionRate();
+    }
+  );
+
+  // 鍒濆鍖�
+  onMounted(() => {
+    if (projectId.value) {
       getProjectDetail();
     }
-    open.value = false;
-  } catch (error) {
-    console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
-  }
-};
-
-// 鎻愪氦闃舵浠诲姟琛ㄥ崟
-const submitGoalForm = async () => {
-  try {
-    await goalFormRef.value.validate();
-    calculateCompletionRate();
-    
-    const goalData = {
-      ...goalForm,
-      oaProjectId: projectId.value
-    };
-    
-    if (goalForm.taskId) {
-      await updateProjectTask(goalData);
-      ElMessage.success('淇敼闃舵鐩爣鎴愬姛');
-
-    } else {
-      await addProjectTask(goalData);
-      ElMessage.success('鏂板闃舵鐩爣鎴愬姛');
-      
-    }
-    // 璋冪敤getProjectDetail鍒锋柊鎵�鏈夌浉鍏虫暟鎹�
-    getProjectDetail();
-    goalOpen.value = false;
-
-  } catch (error) {
-    console.error('鎻愪氦闃舵鐩爣琛ㄥ崟澶辫触:', error);
-  }
-};
-
-// 閲嶇疆閲岀▼纰戣〃鍗�
-const resetForm = () => {
-  form.phaseId = '';
-  form.phaseName = '';
-  form.startDate = '';
-  form.endDate = '';
-  form.status = 'planning';
-  form.oaProjectId = projectId.value;
-  if (formRef.value) {
-    formRef.value.resetFields();
-  }
-};
-
-// 鍙栨秷闃舵浠诲姟鎿嶄綔
-const cancelGoal = () => {
-  goalOpen.value = false;
-};
-
-// 鍙栨秷鎿嶄綔
-const cancel = () => {
-  open.value = false;
-};
-// 缂栬緫闃舵浠诲姟
-const handleEditPhaseGoal = async (goal) => {
-  // 澶嶅埗鐩爣鏁版嵁鍒拌〃鍗�
-  Object.assign(goalForm, goal);
-  
-  // 鑾峰彇椤圭洰闃舵鍒楄〃
-  await getPhaseList();
-  
-  // 鎵撳紑缂栬緫寮圭獥
-  goalTitle.value = '缂栬緫闃舵鐩爣';
-  goalOpen.value = true;
-};
-// 鑾峰彇鐘舵�佹爣绛剧被鍨�
-const getStatusType = (status) => {
-  const statusTypeMap = {
-    planning: 'info',
-    inProgress: 'primary',
-    completed: 'success',
-    paused: 'warning'
-  };
-  return statusTypeMap[status] || 'default';
-};
-
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
-  const statusTextMap = {
-    planning: '瑙勫垝涓�',
-    inProgress: '杩涜涓�',
-    completed: '宸插畬鎴�',
-    paused: '宸叉殏鍋�'
-  };
-  return statusTextMap[status] || status;
-};
-
-// 鐩戝惉璺敱鍙傛暟鍙樺寲
-watch(() => route.params.projectId, (newProjectId) => {
-  // console.log('璺敱鍙傛暟鍙樺寲:', projectId);
-  if (newProjectId) {
-    projectId.value = newProjectId;
-    getProjectDetail();
-  }
-});
-
-// 鐩戝惉褰撳墠鍊煎拰鐩爣鍊煎彉鍖栵紝閲嶆柊璁$畻瀹屾垚搴�
-watch(() => [goalForm.currentValue, goalForm.targetValue], () => {
-  calculateCompletionRate();
-});
-
-// 鍒濆鍖�
-onMounted(() => {
-  if (projectId.value) {
-    getProjectDetail();
-  }
-});
+  });
 </script>
 
 <style scoped>
-.app-container {
-  padding: 20px;
-}
+  .app-container {
+    padding: 20px;
+  }
 
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
 
-.progress-item {
-  text-align: center;
-  padding: 20px;
-  background-color: #f5f7fa;
-  border-radius: 8px;
-}
+  .progress-item {
+    text-align: center;
+    padding: 20px;
+    background-color: #f5f7fa;
+    border-radius: 8px;
+  }
 
-.progress-title {
-  font-size: 14px;
-  color: #606266;
-  margin-bottom: 10px;
-}
+  .progress-title {
+    font-size: 14px;
+    color: #606266;
+    margin-bottom: 10px;
+  }
 
-.progress-number {
-  font-size: 24px;
-  font-weight: bold;
-  color: #409eff;
-}
+  .progress-number {
+    font-size: 24px;
+    font-weight: bold;
+    color: #409eff;
+  }
 
-.mb20 {
-  margin-bottom: 20px;
-}
+  .mb20 {
+    margin-bottom: 20px;
+  }
 </style>

--
Gitblit v1.9.3