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/projectDetail.vue |  565 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 565 insertions(+), 0 deletions(-)

diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
new file mode 100644
index 0000000..c3b0779
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/projectDetail.vue
@@ -0,0 +1,565 @@
+// ... existing code ...
+<template>
+  <div class="app-container">
+    <!-- 椤圭洰鍩烘湰淇℃伅 -->
+    <el-card class="mb20">
+      <template #header>
+        <div class="card-header">
+          <span>椤圭洰鍩烘湰淇℃伅</span>
+        </div>
+      </template>
+      <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>
+        <el-descriptions-item label="缁撴潫鏃ユ湡">{{ projectInfo.endDate }}</el-descriptions-item>
+        <el-descriptions-item label="椤圭洰鐘舵��">
+          <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-descriptions-item>
+        <el-descriptions-item label="椤圭洰鎻忚堪" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <!-- 椤圭洰杩涘害姒傝 -->
+    <el-card class="mb20">
+      <template #header>
+        <div class="card-header">
+          <span>椤圭洰杩涘害姒傝</span>
+        </div>
+      </template>
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <div class="progress-item">
+            <div class="progress-title">鎬讳綋杩涘害</div>
+            <div class="progress-number">{{ projectInfo.completionRate }}%</div>
+          </div>
+        </el-col>
+        <el-col :span="6">
+          <div class="progress-item">
+            <div class="progress-title">闃舵鎬绘暟</div>
+            <div class="progress-number">{{ statistics.totalPhases }}</div>
+          </div>
+        </el-col>
+        <el-col :span="6">
+          <div class="progress-item">
+            <div class="progress-title">浠诲姟鎬绘暟</div>
+            <div class="progress-number">{{ statistics.totalTasks }}</div>
+          </div>
+        </el-col>
+        <el-col :span="6">
+          <div class="progress-item">
+            <div class="progress-title">宸插畬鎴愪换鍔�</div>
+            <div class="progress-number">{{ statistics.completedTasks }}</div>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 闃舵鍜屼换鍔$鐞� -->
+    <!-- <el-card class="mb20">
+      <template #header>
+        <div class="card-header">
+          <span>椤圭洰浠诲姟缁撴瀯</span>
+          <el-button type="primary" size="small" @click="handleAddPhase">娣诲姞闃舵</el-button>
+        </div>
+      </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>
+        </div>
+      </template>
+      <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>
+        </div>
+      </template>
+      <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-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-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <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-select>
+        </el-form-item>
+        <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>
+        <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>
+        <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>
+        <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-select>
+        </el-form-item>
+        <!-- <el-form-item label="瀹屾垚搴�" prop="completionRate">
+          <el-input-number
+            v-model="goalForm.completionRate"
+            :min="0"
+            :max="100"
+            :precision="2"
+            placeholder="璇疯緭鍏ュ畬鎴愬害"
+            style="width: 100%"
+          />
+        </el-form-item> -->
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancelGoal">鍙栨秷</el-button>
+          <el-button type="primary" @click="submitGoalForm">纭畾</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</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';
+
+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 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 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);
+
+      // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
+      // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
+      refreshProjectId.value++;
+    })
+  } 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 getPhaseList = async () => {
+  try {
+    const { data } = await listProjectPhase(projectId.value);
+    phaseList.value = data.rows || data;
+  } catch (error) {
+    ElMessage.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触');
+    console.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:', error);
+  }
+};
+
+// 璁$畻瀹屾垚搴�
+const calculateCompletionRate = () => {
+  if (goalForm.targetValue > 0) {
+    goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
+  } else {
+    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();
+  }
+});
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.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-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.progress-number {
+  font-size: 24px;
+  font-weight: bold;
+  color: #409eff;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+</style>

--
Gitblit v1.9.3