| | |
| | | <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" /> |
| | |
| | | <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> |
| | | |
| | |
| | | </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> |
| | | |
| | |
| | | </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"> |
| | |
| | | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | 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)) |
| | | )) |
| | |
| | | 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() { |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | 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('请上传至少一个文件') |