From 5df6d6cd93ed5655065652a008189d1630fbfeab Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 29 十月 2026 16:34:34 +0800
Subject: [PATCH] change

---
 src/components/ProjectManagement/ProgressReportDialog.vue |  302 ++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 278 insertions(+), 24 deletions(-)

diff --git a/src/components/ProjectManagement/ProgressReportDialog.vue b/src/components/ProjectManagement/ProgressReportDialog.vue
index dc6afe7..a112c85 100644
--- a/src/components/ProjectManagement/ProgressReportDialog.vue
+++ b/src/components/ProjectManagement/ProgressReportDialog.vue
@@ -1,10 +1,11 @@
 <template>
-  <el-dialog v-model="visible" title="杩涘害姹囨姤" width="900px" top="8vh" append-to-body destroy-on-close @close="handleClose">
+  <el-dialog v-model="visible" title="杩涘害姹囨姤" width="960px" top="6vh" append-to-body destroy-on-close @close="handleClose">
     <el-form ref="formRef" :model="form" :rules="rules" label-position="top" label-width="120px">
       <el-form-item label="椤圭洰闃舵" prop="planNodeId">
-        <el-select v-model="form.planNodeId" placeholder="璇烽�夋嫨" clearable style="width: 100%">
+        <el-select v-model="form.planNodeId" placeholder="璇烽�夋嫨" style="width: 100%" @change="handleStageChange">
           <el-option v-for="opt in stageOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
         </el-select>
+        <div v-if="stageTip" class="stage-tip">{{ stageTip }}</div>
       </el-form-item>
       <el-row :gutter="20">
         <el-col :span="8">
@@ -63,8 +64,8 @@
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="涓婃杩涘害(%)" prop="lastProgress">
-            <el-input-number v-model="form.lastProgress" :min="0" :max="100" controls-position="right" style="width: 100%" />
+          <el-form-item label="涓婃杩涘害(%)">
+            <el-input-number v-model="form.lastProgress" :min="0" :max="100" controls-position="right" style="width: 100%" disabled />
           </el-form-item>
         </el-col>
         <el-col :span="8">
@@ -115,6 +116,70 @@
         </el-col>
       </el-row>
 
+      <div v-if="isTeamStage" class="team-block">
+        <div class="team-header">
+          <span class="team-title">椤圭洰鍥㈤槦</span>
+          <el-button type="primary" size="small" @click="addTeamRow">鏂板鎴愬憳</el-button>
+        </div>
+        <el-table :data="teamRows" border size="small">
+          <el-table-column label="濮撳悕" width="150">
+            <template #default="{ row }">
+              <el-select v-model="row.userId" placeholder="璇烽�夋嫨" filterable clearable style="width: 100%">
+                <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="椤圭洰缁勮鑹�" width="150">
+            <template #default="{ row }">
+              <el-select v-model="row.userRoleId" placeholder="璇烽�夋嫨" filterable clearable style="width: 100%">
+                <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="杩涘叆鏃ユ湡" width="150">
+            <template #default="{ row }">
+              <el-date-picker
+                v-model="row.joinTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨"
+                style="width: 100%"
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="绂诲紑鏃ユ湡" width="150">
+            <template #default="{ row }">
+              <el-date-picker
+                v-model="row.departTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨"
+                style="width: 100%"
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="鑱旂郴鏂瑰紡" min-width="150">
+            <template #default="{ row }">
+              <el-input v-model="row.contact" placeholder="璇疯緭鍏�" clearable />
+            </template>
+          </el-table-column>
+          <el-table-column label="澶囨敞" min-width="140">
+            <template #default="{ row }">
+              <el-input v-model="row.remark" placeholder="璇疯緭鍏�" clearable />
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="70" align="center">
+            <template #default="{ $index }">
+              <el-button link type="danger" size="small" @click="removeTeamRow($index)">鍒犻櫎</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
       <el-form-item label="闄勪欢" prop="attachmentIds">
         <FileUpload v-model:file-list="form.storageBlobDTOs" />
       </el-form-item>
@@ -130,16 +195,18 @@
 </template>
 
 <script setup name="ProgressReportDialog">
-import { computed, reactive, ref, watch } from 'vue'
-import { ElMessage } from 'element-plus'
-import { getToken } from '@/utils/auth'
-import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+import { computed, ref, watch } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import FileUpload from '@/components/AttachmentUpload/file/index.vue'
+import { findRoleListPage } from '@/api/projectManagement/role'
+import { userListAll } from '@/api/publicApi'
 
 const props = defineProps({
   modelValue: { type: Boolean, default: false },
   projectId: { type: [Number, String], default: undefined },
   projectInfo: { type: Object, default: () => ({}) },
   planNodes: { type: Array, default: () => [] },
+  stageRecords: { type: Array, default: () => [] },
   defaultPlanNodeId: { type: [Number, String], default: undefined }
 })
 
@@ -151,6 +218,9 @@
 })
 
 const formRef = ref()
+const userOptions = ref([])
+const roleOptions = ref([])
+const teamRows = ref([])
 
 const form = ref({
   planNodeId: undefined,
@@ -176,40 +246,176 @@
   completionProgress: [{ required: true, message: '璇疯緭鍏�', trigger: 'change' }]
 }
 
-const stageOptions = computed(() => {
+const sortedNodes = computed(() => {
   const list = Array.isArray(props.planNodes) ? props.planNodes : []
-  const sorted = [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0))
-  return sorted
+  return [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0))
+})
+
+const stageOptions = computed(() =>
+  sortedNodes.value
     .map(n => ({
       label: n.name || n.workContent || n.title || String(n.id ?? ''),
       value: n.id
     }))
     .filter(i => i.value !== undefined && i.value !== null && i.value !== '')
+)
+
+const stageNameOf = nodeId => {
+  const node = sortedNodes.value.find(n => String(n.id) === String(nodeId))
+  return node?.name || node?.workContent || ''
+}
+
+// 鍚勯樁娈垫渶杩戜竴鏉℃眹鎶ヨ褰曪紙璁板綍鎸夐樁娈点�佹椂闂村崌搴忚繑鍥烇級
+const latestProgressMap = computed(() => {
+  const map = {}
+  const records = Array.isArray(props.stageRecords) ? props.stageRecords : []
+  records.forEach(r => {
+    const key = String(r.projectManagementPlanNodeId ?? '')
+    map[key] = Number(r.progress) || 0
+  })
+  return map
 })
+
+// 褰撳墠搴旀眹鎶ョ殑涓嬩竴闃舵锛氱涓�涓湭瀹屾垚锛堣繘搴� < 100锛夌殑闃舵
+const expectedNodeId = computed(() => {
+  const nodes = sortedNodes.value
+  if (nodes.length === 0) return undefined
+  const next = nodes.find(n => (latestProgressMap.value[String(n.id)] ?? 0) < 100)
+  return (next || nodes[nodes.length - 1])?.id
+})
+
+const isTeamStage = computed(() => stageNameOf(form.value.planNodeId).includes('鍥㈤槦'))
+
+const stageTip = computed(() => {
+  if (!form.value.planNodeId) return ''
+  const progress = latestProgressMap.value[String(form.value.planNodeId)] ?? 0
+  if (progress >= 100) return '璇ラ樁娈靛凡瀹屾垚锛屼粛鍙户缁ˉ鍏呮眹鎶�'
+  if (expectedNodeId.value && String(form.value.planNodeId) !== String(expectedNodeId.value)) {
+    return '褰撳墠閫夋嫨鐨勪笉鏄簲姹囨姤鐨勪笅涓�闃舵'
+  }
+  return ''
+})
+
+function todayStr() {
+  const d = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
+}
+
+function latestRecordOf(nodeId) {
+  const records = Array.isArray(props.stageRecords) ? props.stageRecords : []
+  const matched = records.filter(r => String(r.projectManagementPlanNodeId) === String(nodeId))
+  return matched.length ? matched[matched.length - 1] : null
+}
+
+function applyStageBase(nodeId) {
+  const record = latestRecordOf(nodeId)
+  form.value.lastProgress = Number(record?.progress ?? 0) || 0
+  form.value.completionProgress = 0
+  form.value.totalProgress = Number(record?.progress ?? 0) || 0
+  form.value.planStartTime = record?.planStartTime || props.projectInfo?.planStartTime || ''
+  form.value.planEndTime = record?.planEndTime || props.projectInfo?.planEndTime || ''
+  form.value.actualStartTime = record?.actualStartTime || props.projectInfo?.actualStartTime || ''
+  form.value.actualEndTime = record?.actualEndTime || ''
+}
+
+async function handleStageChange(val) {
+  applyStageBase(val)
+  if (!val) return
+  const expected = expectedNodeId.value
+  if (expected && String(val) !== String(expected)) {
+    try {
+      await ElMessageBox.confirm(
+        `褰撳墠搴旀眹鎶ョ殑鈥�${stageNameOf(expected)}鈥濋樁娈佃繕鏈畬鎴愶紝閫夋嫨鍏朵粬闃舵灏嗚烦杩囦腑闂撮樁娈碉紝鏄惁缁х画锛焋,
+        '鎻愮ず',
+        { confirmButtonText: '缁х画', cancelButtonText: '杩斿洖', type: 'warning' }
+      )
+    } catch {
+      form.value.planNodeId = expected
+      applyStageBase(expected)
+    }
+  }
+}
 
 function resetFromProject() {
   const info = props.projectInfo || {}
   form.value = {
-    planNodeId: props.defaultPlanNodeId ?? stageOptions.value[0]?.value,
-    planStartTime: info.planStartTime || '',
-    planEndTime: info.planEndTime || '',
-    actualStartTime: info.actualStartTime || '',
-    actualEndTime: info.actualEndTime || '',
-    reportDate: '',
-    lastProgress: Number(info.lastProgress ?? 0) || 0,
+    planNodeId: props.defaultPlanNodeId ?? expectedNodeId.value ?? stageOptions.value[0]?.value,
+    planStartTime: '',
+    planEndTime: '',
+    actualStartTime: '',
+    actualEndTime: '',
+    reportDate: todayStr(),
+    lastProgress: 0,
     completionProgress: 0,
-    totalProgress: Number(info.totalProgress ?? info.progress ?? 0) || 0,
+    totalProgress: 0,
     managerName: info.managerName || '',
     departmentName: info.departmentName || '',
     remark: '',
     storageBlobDTOs: []
+  }
+  applyStageBase(form.value.planNodeId)
+  form.value.reportDate = todayStr()
+  loadTeamRows()
+}
+
+function loadTeamRows() {
+  const list = Array.isArray(props.projectInfo?.teamList) ? props.projectInfo.teamList : []
+  teamRows.value = list.map(t => ({
+    userId: t.userId,
+    userRoleId: t.userRoleId,
+    joinTime: t.joinTime || '',
+    departTime: t.departTime || '',
+    contact: t.contact || '',
+    remark: t.remark || ''
+  }))
+  if (teamRows.value.length === 0) addTeamRow()
+}
+
+function addTeamRow() {
+  teamRows.value.push({ userId: undefined, userRoleId: undefined, joinTime: '', departTime: '', contact: '', remark: '' })
+}
+
+function removeTeamRow(index) {
+  teamRows.value.splice(index, 1)
+  if (teamRows.value.length === 0) addTeamRow()
+}
+
+async function loadOptions() {
+  try {
+    const res = await userListAll()
+    const list = res?.data || res?.rows || res || []
+    userOptions.value = (Array.isArray(list) ? list : []).map(u => ({
+      label: u.nickName || u.userName || u.username || u.name,
+      value: u.userId || u.id
+    }))
+  } catch {
+    userOptions.value = []
+  }
+  try {
+    const res = await findRoleListPage({ pageNum: 1, pageSize: 999 })
+    const records = res?.data?.records || res?.rows || res?.records || []
+    roleOptions.value = records.map(item => ({ label: item.roleName || item.name, value: item.id }))
+  } catch {
+    roleOptions.value = []
   }
 }
 
 watch(
   () => props.modelValue,
   v => {
-    if (v) resetFromProject()
+    if (v) {
+      loadOptions()
+      resetFromProject()
+    }
+  }
+)
+
+// 绱杩涘害璺熼殢鏈瀹屾垚杩涘害
+watch(
+  () => form.value.completionProgress,
+  v => {
+    form.value.totalProgress = Number(v) || 0
   }
 )
 
@@ -220,15 +426,39 @@
 function markDone() {
   form.value.completionProgress = 100
   form.value.totalProgress = 100
-  if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || ''
+  if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || todayStr()
+}
+
+function buildTeamList() {
+  const findLabel = (list, value) => (list || []).find(i => String(i.value) === String(value))?.label || ''
+  return teamRows.value
+    .filter(r => r.userId !== undefined && r.userId !== null && r.userId !== '')
+    .map(r => ({
+      userId: Number(r.userId),
+      userName: findLabel(userOptions.value, r.userId),
+      userRoleId: r.userRoleId ?? '',
+      userRoleName: findLabel(roleOptions.value, r.userRoleId),
+      joinTime: r.joinTime || null,
+      departTime: r.departTime || null,
+      contact: r.contact || '',
+      remark: r.remark || ''
+    }))
 }
 
 async function submit() {
-  await formRef.value?.validate?.()
-  emit('submitted', {
+  try {
+    await formRef.value?.validate?.()
+  } catch {
+    return
+  }
+  const payload = {
     projectId: props.projectId,
     ...form.value
-  })
+  }
+  if (isTeamStage.value) {
+    payload.teamList = buildTeamList()
+  }
+  emit('submitted', payload)
   visible.value = false
 }
 </script>
@@ -239,4 +469,28 @@
   justify-content: flex-end;
   gap: 10px;
 }
+
+.stage-tip {
+  margin-top: 6px;
+  font-size: 12px;
+  color: #e6a23c;
+  line-height: 1.4;
+}
+
+.team-block {
+  margin-bottom: 18px;
+}
+
+.team-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.team-title {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
 </style>

--
Gitblit v1.9.3