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