From fa113c192df7e3b5aad9dcb465307dcffb58f6c5 Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期三, 11 三月 2026 18:01:35 +0800
Subject: [PATCH] feat(项目管理): 新增项目详情页与进度汇报功能

---
 src/views/projectManagement/Management/index.vue |  221 +++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 159 insertions(+), 62 deletions(-)

diff --git a/src/views/projectManagement/Management/index.vue b/src/views/projectManagement/Management/index.vue
index 1a48db5..7547209 100644
--- a/src/views/projectManagement/Management/index.vue
+++ b/src/views/projectManagement/Management/index.vue
@@ -35,10 +35,10 @@
             </el-dropdown-menu>
           </template>
         </el-dropdown> -->
-        <el-button @click="handleSubmit">鎻愪氦</el-button>
-        <el-button @click="handleAudit">瀹℃牳</el-button>
-        <el-button @click="handleReverseAudit">鍙嶅鏍�</el-button>
-        <el-button @click="handleDelete">鍒犻櫎</el-button>
+        <el-button :loading="submitLoading" @click="handleSubmit">鎻愪氦</el-button>
+        <el-button :loading="auditLoading" @click="handleAudit">瀹℃牳</el-button>
+        <el-button :loading="reverseAuditLoading" @click="handleReverseAudit">鍙嶅鏍�</el-button>
+        <el-button :loading="deleteLoading" @click="handleDelete">鍒犻櫎</el-button>
       </div>
 
       <PIMTable
@@ -58,38 +58,63 @@
         </template>
         <template #action="{ row }">
           <el-button link type="primary" @click="handleEdit(row)">缂栬緫</el-button>
-          <el-button link type="primary" @click="handleProgressReport(row)">杩涘害姹囨姤</el-button>
-          <el-button link type="primary" @click="handleDiscussProgress(row)">娲借皥杩涘睍</el-button>
+          <el-button link type="primary" :loading="progressBtnLoadingId===row.id" @click="handleProgressReport(row)">杩涘害姹囨姤</el-button>
           <el-button link type="primary" @click="handleDetail(row)">璇︽儏</el-button>
         </template>
       </PIMTable>
     </div>
 
     <FormDia ref="formDiaRef" @completed="getList" />
+    <ProgressReportDialog
+      v-model="progressReportVisible"
+      :project-id="progressProjectId"
+      :project-info="progressProjectInfo"
+      :plan-nodes="progressPlanNodes"
+      :default-plan-node-id="progressDefaultPlanNodeId"
+      @submitted="handleProgressSubmitted"
+    />
   </div>
 </template>
 
 <script setup name="ProjectManagement">
 import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { useRouter } from 'vue-router'
 import SearchPanel from '@/components/SearchPanel/index.vue'
 import PIMTable from '@/components/PIMTable/PIMTable.vue'
 import FormDia from './components/formDia.vue'
+import ProgressReportDialog from '@/components/ProjectManagement/ProgressReportDialog.vue'
 import {
   listProject,
   delProject,
   submitProject,
   auditProject,
-  reverseAuditProject
+  reverseAuditProject,
+  getProject,
+  saveStage
 } from '@/api/projectManagement/project'
+import { listPlan } from '@/api/projectManagement/projectType'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import useUserStore from '@/store/modules/user'
 
 const { proxy } = getCurrentInstance()
 const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status')
+const router = useRouter()
+const userStore = useUserStore()
 
 const loading = ref(false)
 const ids = ref([])
 const tableData = ref([])
 const formDiaRef = ref()
+const progressReportVisible = ref(false)
+const progressProjectId = ref(undefined)
+const progressProjectInfo = ref({})
+const progressPlanNodes = ref([])
+const progressDefaultPlanNodeId = ref(undefined)
+const progressBtnLoadingId = ref(null)
+const submitLoading = ref(false)
+const auditLoading = ref(false)
+const reverseAuditLoading = ref(false)
+const deleteLoading = ref(false)
 
 const data = reactive({
   queryParams: {
@@ -208,102 +233,174 @@
   formDiaRef.value?.openDialog({ operationType: 'add' })
 }
 
-function handleDelete() {
+async function handleDelete() {
   const delIds = ids.value
   if (delIds.length === 0) {
     ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁椤�')
     return
   }
-  ElMessageBox.confirm('鏄惁纭鍒犻櫎鎵�閫夋暟鎹」?', '璀﹀憡', {
-    confirmButtonText: '纭畾',
-    cancelButtonText: '鍙栨秷',
-    type: 'warning'
-  })
-    .then(() => delProject(delIds))
-    .then(() => {
-      getList()
-      ElMessage.success('鍒犻櫎鎴愬姛')
+  try {
+    await ElMessageBox.confirm('鏄惁纭鍒犻櫎鎵�閫夋暟鎹」?', '璀﹀憡', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
     })
-    .catch(() => {})
+    deleteLoading.value = true
+    await delProject(delIds)
+    getList()
+    ElMessage.success('鍒犻櫎鎴愬姛')
+  } catch {} finally {
+    deleteLoading.value = false
+  }
 }
 
-function handleSubmit() {
+async function handleSubmit() {
   const submitIds = ids.value
   if (submitIds.length === 0) {
     ElMessage.warning('璇烽�夋嫨瑕佹彁浜ょ殑鏁版嵁椤�')
     return
   }
-  ElMessageBox.confirm('鏄惁纭鎻愪氦鎵�閫夋暟鎹」?', '鎻愮ず', {
-    confirmButtonText: '纭畾',
-    cancelButtonText: '鍙栨秷',
-    type: 'warning'
-  })
-    .then(async () => {
-      await Promise.all(submitIds.map(id => submitProject({ id })))
+  try {
+    await ElMessageBox.confirm('鏄惁纭鎻愪氦鎵�閫夋暟鎹」?', '鎻愮ず', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
     })
-    .then(() => {
-      getList()
-      ElMessage.success('鎻愪氦鎴愬姛')
-    })
-    .catch(() => {})
+    submitLoading.value = true
+    await Promise.all(submitIds.map(id => submitProject({ id })))
+    getList()
+    ElMessage.success('鎻愪氦鎴愬姛')
+  } catch {} finally {
+    submitLoading.value = false
+  }
 }
 
-function handleAudit() {
+async function handleAudit() {
   const auditIds = ids.value
   if (auditIds.length === 0) {
     ElMessage.warning('璇烽�夋嫨瑕佸鏍哥殑鏁版嵁椤�')
     return
   }
-  ElMessageBox.confirm('鏄惁纭瀹℃牳鎵�閫夋暟鎹」?', '鎻愮ず', {
-    confirmButtonText: '纭畾',
-    cancelButtonText: '鍙栨秷',
-    type: 'warning'
-  })
-    .then(async () => {
-      await Promise.all(auditIds.map(id => auditProject({ id })))
+  try {
+    await ElMessageBox.confirm('鏄惁纭瀹℃牳鎵�閫夋暟鎹」?', '鎻愮ず', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
     })
-    .then(() => {
-      getList()
-      ElMessage.success('瀹℃牳鎴愬姛')
-    })
-    .catch(() => {})
+    auditLoading.value = true
+    await Promise.all(auditIds.map(id => auditProject({ id })))
+    getList()
+    ElMessage.success('瀹℃牳鎴愬姛')
+  } catch {} finally {
+    auditLoading.value = false
+  }
 }
 
-function handleReverseAudit() {
+async function handleReverseAudit() {
   const reverseAuditIds = ids.value
   if (reverseAuditIds.length === 0) {
     ElMessage.warning('璇烽�夋嫨瑕佸弽瀹℃牳鐨勬暟鎹」')
     return
   }
-  ElMessageBox.confirm('鏄惁纭鍙嶅鏍告墍閫夋暟鎹」?', '鎻愮ず', {
-    confirmButtonText: '纭畾',
-    cancelButtonText: '鍙栨秷',
-    type: 'warning'
-  })
-    .then(async () => {
-      await Promise.all(reverseAuditIds.map(id => reverseAuditProject({ id })))
+  try {
+    await ElMessageBox.confirm('鏄惁纭鍙嶅鏍告墍閫夋暟鎹」?', '鎻愮ず', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
     })
-    .then(() => {
-      getList()
-      ElMessage.success('鍙嶅鏍告垚鍔�')
-    })
-    .catch(() => {})
+    reverseAuditLoading.value = true
+    await Promise.all(reverseAuditIds.map(id => reverseAuditProject({ id })))
+    getList()
+    ElMessage.success('鍙嶅鏍告垚鍔�')
+  } catch {} finally {
+    reverseAuditLoading.value = false
+  }
 }
 
 function handleGenerateBill(command) {
   ElMessage.info(`鐢熸垚鍗曟嵁: ${command}`)
 }
 
-function handleProgressReport(row) {
-  formDiaRef.value?.openDialog({ operationType: 'view', row })
+function computeDefaultPlanNodeId(stageVal, nodes) {
+  const list = Array.isArray(nodes) ? nodes : []
+  if (list.length === 0) return undefined
+  const direct = list.find(n => String(n.id) === String(stageVal))
+  if (direct?.id) return direct.id
+  const sorted = [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0))
+  const idx = Number(stageVal)
+  if (Number.isFinite(idx)) {
+    const byIndex = sorted[idx - 1] || sorted[idx] || sorted[0]
+    if (byIndex?.id) return byIndex.id
+  }
+  return sorted[0]?.id
 }
 
-function handleDiscussProgress(row) {
-  formDiaRef.value?.openDialog({ operationType: 'view', row })
+async function handleProgressReport(row) {
+  if (!row?.id) return
+  try {
+    progressBtnLoadingId.value = row.id
+    const res = await getProject(row.id)
+    const detail = res?.data?.data ?? res?.data ?? res
+    const info = detail?.info || {}
+    progressProjectId.value = info.id
+    progressProjectInfo.value = info
+
+    const planId = info.projectManagementPlanId
+    if (planId) {
+      const planRes = await listPlan({ current: 1, size: 999 })
+      const records = planRes?.data?.records || planRes?.records || planRes?.rows || []
+      const plan = (records || []).find(p => String(p.id) === String(planId)) || {}
+      progressPlanNodes.value = Array.isArray(plan?.planNodeList) ? plan.planNodeList : []
+    } else {
+      progressPlanNodes.value = []
+    }
+
+    progressDefaultPlanNodeId.value = computeDefaultPlanNodeId(info.stage, progressPlanNodes.value)
+    progressReportVisible.value = true
+  } catch (e) {
+    ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触')
+  } finally {
+    progressBtnLoadingId.value = null
+  }
+}
+
+async function handleProgressSubmitted(payload) {
+  try {
+    const nodes = Array.isArray(progressPlanNodes.value) ? progressPlanNodes.value : []
+    const node = nodes.find(n => String(n.id) === String(payload.planNodeId)) || {}
+    const description = payload.remark
+      ? `${payload.reportDate || ''} ${payload.remark}`.trim()
+      : `${payload.reportDate || ''} 杩涘害姹囨姤`.trim()
+    const req = {
+      id: null,
+      projectManagementPlanNodeId: payload.planNodeId,
+      projectManagementInfoId: progressProjectId.value,
+      description,
+      actualLeaderId: userStore.id || progressProjectInfo.value?.managerId,
+      actualLeaderName: userStore.nickName || progressProjectInfo.value?.managerName,
+      estimatedDuration: Number(node.estimatedDuration ?? 0) || 0,
+      planStartTime: payload.planStartTime || progressProjectInfo.value?.planStartTime,
+      planEndTime: payload.planEndTime || progressProjectInfo.value?.planEndTime,
+      actualStartTime: payload.actualStartTime || null,
+      actualEndTime: payload.actualEndTime || null,
+      progress: Number(payload.totalProgress ?? payload.completionProgress ?? 0) || 0,
+      attachmentIds: Array.isArray(payload.attachmentIds) ? payload.attachmentIds : []
+    }
+    const res = await saveStage(req)
+    if (res?.code === 200) {
+      ElMessage.success('鎻愪氦鎴愬姛')
+      getList()
+      return
+    }
+    ElMessage.error(res?.msg || '鎻愪氦澶辫触')
+  } catch (e) {
+    ElMessage.error('鎻愪氦澶辫触')
+  }
 }
 
 function handleDetail(row) {
-  formDiaRef.value?.openDialog({ operationType: 'view', row })
+  if (!row?.id) return
+  router.push(`/projectManagement/Management/detail/${row.id}`)
 }
 
 function handleEdit(row) {

--
Gitblit v1.9.3