zhangwencui
4 天以前 67aebf4dcaa249d99893a88685ea273cc46606f4
src/views/projectManagement/Management/components/ProjectExecutionPanel.vue
@@ -51,7 +51,7 @@
            <span v-if="data.followWindow?.dueDate">本次应跟进日 {{ data.followWindow.dueDate }},可填写 {{ data.followWindow.windowStart }} 至 {{ data.followWindow.windowEnd }}</span>
            <span v-else>首次跟进可直接填写</span>
          </div>
          <el-button v-if="!readonly" v-hasPermi="['project:follow:add']" type="primary" @click="openFollow()">填写跟进</el-button>
          <el-button v-if="!readonly && canManageFollow" v-hasPermi="['project:follow:add']" type="primary" @click="openFollow()">填写跟进</el-button>
        </div>
        <el-table :data="data.followUps" border>
          <el-table-column prop="followDate" label="跟进日期" width="130" />
@@ -63,7 +63,7 @@
              <span v-else class="empty-file">无附件</span>
            </template>
          </el-table-column>
          <el-table-column v-if="!readonly" label="操作" width="140" align="center"><template #default="{ row }"><el-button v-hasPermi="['project:follow:add']" link type="primary" @click="openFollow(row)">编辑</el-button><el-button v-hasPermi="['project:follow:remove']" link type="danger" @click="removeRecord('follow', row)">删除</el-button></template></el-table-column>
          <el-table-column v-if="!readonly && canManageFollow" label="操作" width="140" align="center"><template #default="{ row }"><el-button v-hasPermi="['project:follow:add']" link type="primary" @click="openFollow(row)">编辑</el-button><el-button v-hasPermi="['project:follow:remove']" link type="danger" @click="removeRecord('follow', row)">删除</el-button></template></el-table-column>
        </el-table>
      </el-tab-pane>
@@ -124,7 +124,7 @@
              </el-button>
            </template>
          </el-table-column>
          <el-table-column v-if="!readonly" label="操作" width="90" align="center"><template #default="{ row }"><el-button v-hasPermi="['project:material:remove']" link type="danger" @click="removeRecord('material', row)">删除</el-button></template></el-table-column>
          <el-table-column v-if="!readonly" label="操作" width="140" align="center"><template #default="{ row }"><el-button v-hasPermi="['project:material:add']" link type="primary" @click="openMaterial(row)">编辑</el-button><el-button v-hasPermi="['project:material:remove']" link type="danger" @click="removeRecord('material', row)">删除</el-button></template></el-table-column>
        </el-table>
      </el-tab-pane>
@@ -160,7 +160,7 @@
        </div>
        <div class="module-grid">
          <el-form-item label="模块名称" required><el-input v-model="module.moduleName" :disabled="module.locked" /></el-form-item>
          <el-form-item label="预计开始" required><el-date-picker v-model="module.plannedStartDate" :disabled="module.locked" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
          <el-form-item label="预计开始" required><el-date-picker v-model="module.plannedStartDate" :disabled="module.locked" :disabled-date="moduleDateDisabled" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
          <el-form-item label="工作量占比" required><el-input-number v-model="module.projectWeight" :disabled="module.locked" :min="0.01" :max="draftWeightMax(moduleIndex)" :precision="2" /><span class="unit">%</span><div class="percent-hint">其他模块 {{ otherDraftWeight(moduleIndex) }}%,本模块最多 {{ draftWeightMax(moduleIndex) }}%</div></el-form-item>
          <el-form-item label="模块概要" required class="summary-item"><el-input v-model="module.summary" :disabled="module.locked" type="textarea" :rows="2" maxlength="1000" show-word-limit /></el-form-item>
          <el-form-item label="模块执行人" required class="members-item">
@@ -181,7 +181,7 @@
    <el-dialog v-model="followVisible" :title="followForm.id ? '编辑项目跟进' : '填写项目跟进'" width="650px" destroy-on-close>
      <el-form ref="followFormRef" :model="followForm" :rules="followRules" label-width="90px">
        <el-form-item label="跟进日期" prop="followDate"><el-date-picker v-model="followForm.followDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item>
        <el-form-item label="跟进日期" prop="followDate"><el-date-picker v-model="followForm.followDate" :disabled="!!followForm.id" :disabled-date="date => dayjs(date).isAfter(dayjs(), 'day')" type="date" value-format="YYYY-MM-DD" style="width: 100%" /><div v-if="followForm.id" class="percent-hint">历史跟进日期不可修改,避免破坏后续跟进周期。</div></el-form-item>
        <el-form-item label="跟进详情" prop="detail"><el-input v-model="followForm.detail" type="textarea" :rows="5" maxlength="2000" show-word-limit /></el-form-item>
        <el-form-item label="附件"><AttachmentUploadFile v-model:file-list="followFiles" :limit="10" /></el-form-item>
      </el-form>
@@ -205,14 +205,14 @@
          </div>
        </el-form-item>
        <el-form-item label="本次进度" prop="incrementProgress"><el-input-number v-model="reportForm.incrementProgress" :min="0.01" :max="reportIncrementMax" :precision="2" /><span class="unit">%</span><span class="current-progress-hint">{{ reportProgressHint }}</span></el-form-item>
        <el-form-item label="汇报时间" prop="reportDate"><el-date-picker v-model="reportForm.reportDate" :disabled="!!reportForm.id" type="date" value-format="YYYY-MM-DD" style="width: 100%" /><div v-if="reportForm.id" class="percent-hint">编辑已有汇报时不允许调整时间,以保证历史进度顺序正确。</div></el-form-item>
        <el-form-item label="汇报时间" prop="reportDate"><el-date-picker v-model="reportForm.reportDate" :disabled="!!reportForm.id" :disabled-date="date => dayjs(date).isAfter(dayjs(), 'day')" type="date" value-format="YYYY-MM-DD" style="width: 100%" /><div v-if="reportForm.id" class="percent-hint">编辑已有汇报时不允许调整时间,以保证历史进度顺序正确。</div></el-form-item>
        <el-form-item label="备注"><el-input v-model="reportForm.remark" type="textarea" :rows="3" maxlength="2000" show-word-limit /></el-form-item>
        <el-form-item label="文件"><AttachmentUploadFile v-model:file-list="reportFiles" :limit="10" /></el-form-item>
      </el-form>
      <template #footer><el-button @click="reportVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="submitReport">{{ reportForm.id ? '保存修改' : '提交汇报' }}</el-button></template>
    </el-dialog>
    <el-dialog v-model="materialVisible" title="上传项目资料" width="650px" destroy-on-close>
    <el-dialog v-model="materialVisible" :title="materialForm.id ? '编辑项目资料' : '上传项目资料'" width="650px" destroy-on-close>
      <el-form ref="materialFormRef" :model="materialForm" :rules="materialRules" label-width="90px">
        <el-form-item label="资料名称" prop="materialName"><el-input v-model="materialForm.materialName" /></el-form-item>
        <el-form-item label="资料类型"><el-select v-model="materialForm.materialType" allow-create filterable clearable style="width: 100%"><el-option v-for="type in ['立项资料','开发资料','申报资料','结果资料','验收资料','其他']" :key="type" :label="type" :value="type" /></el-select></el-form-item>
@@ -261,6 +261,7 @@
const reportShareRemaining = computed(() => Math.max(0, 100 - Number(reportShareTotal.value)).toFixed(2))
const followCycleLabel = computed(() => ({ MONTH: '每月', QUARTER: '每季度', YEAR: '每年' }[props.projectInfo?.followCycle] || props.projectInfo?.followCycle || '每月'))
const isStarted = computed(() => !!props.projectInfo?.currentPhase && props.projectInfo.currentPhase !== '未开始')
const canManageFollow = computed(() => String(props.projectInfo?.projectLeaderId ?? '') === String(userStore.id ?? ''))
const memberReportModules = computed(() => data.modules.filter(module =>
  (module.members || []).some(member => String(member.userId) === String(userStore.id))
))
@@ -334,7 +335,7 @@
const reportRules = { moduleId: [{ required: true, message: '请选择项目模块', trigger: 'change' }], reportDate: [{ required: true, message: '请选择汇报时间', trigger: 'change' }] }
const materialVisible = ref(false)
const materialFormRef = ref()
const materialForm = reactive({ materialName: '', materialType: '', remark: '' })
const materialForm = reactive({ id: null, materialName: '', materialType: '', remark: '' })
const materialFiles = ref([])
const materialRules = { materialName: [{ required: true, message: '请输入资料名称', trigger: 'blur' }] }
async function loadUsers() {
@@ -468,7 +469,13 @@
}
function canEditReport(row) {
  return memberReportModules.value.some(module => String(module.id) === String(row.moduleId))
  return (row.reporterIdList || []).some(userId => String(userId) === String(userStore.id))
}
function moduleDateDisabled(date) {
  const start = props.projectInfo?.planStartTime
  const end = props.projectInfo?.planEndTime
  return (start && dayjs(date).isBefore(dayjs(start), 'day')) || (end && dayjs(date).isAfter(dayjs(end), 'day'))
}
function reporterShareLimit(index) {
@@ -494,7 +501,13 @@
  finally { saving.value = false }
}
function openMaterial() { Object.assign(materialForm, { materialName: '', materialType: '', remark: '' }); materialFiles.value = []; materialVisible.value = true }
function openMaterial(row = null) {
  Object.assign(materialForm, row
    ? { id: row.id, materialName: row.materialName || '', materialType: row.materialType || '', remark: row.remark || '' }
    : { id: null, materialName: '', materialType: '', remark: '' })
  materialFiles.value = row ? [...(row.attachmentList || [])] : []
  materialVisible.value = true
}
async function submitMaterial() {
  await materialFormRef.value?.validate()
  if (!materialFiles.value.length) return ElMessage.warning('请上传至少一个文件')