<template>
|
<el-dialog v-model="visible" title="进度汇报" width="900px" top="8vh" 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-option v-for="opt in stageOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
</el-select>
|
</el-form-item>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="计划开始时间" prop="planStartTime">
|
<el-date-picker
|
v-model="form.planStartTime"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="计划完工时间" prop="planEndTime">
|
<el-date-picker
|
v-model="form.planEndTime"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="实际开工日期" prop="actualStartTime">
|
<el-date-picker
|
v-model="form.actualStartTime"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="本次进度日期" prop="reportDate">
|
<el-date-picker
|
v-model="form.reportDate"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%"
|
clearable
|
/>
|
</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>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="完成进度(%)" prop="completionProgress">
|
<div style="display: flex; gap: 8px; width: 100%;">
|
<el-input-number v-model="form.completionProgress" :min="0" :max="100" controls-position="right" style="flex: 1" />
|
<el-button type="danger" @click="markDone">完成</el-button>
|
</div>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="累计进度(%)" prop="totalProgress">
|
<el-input-number v-model="form.totalProgress" :min="0" :max="100" controls-position="right" style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="实际完工日期" prop="actualEndTime">
|
<el-date-picker
|
v-model="form.actualEndTime"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="负责人" prop="managerName">
|
<el-input v-model="form.managerName" placeholder="请输入" clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="部门" prop="departmentName">
|
<el-input v-model="form.departmentName" placeholder="请输入" clearable />
|
</el-form-item>
|
</el-col>
|
<el-col :span="16">
|
<el-form-item label="备注" prop="remark">
|
<el-input v-model="form.remark" placeholder="请输入" clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-form-item label="附件" prop="attachmentIds">
|
<el-upload
|
v-model:file-list="fileList"
|
:action="upload.url"
|
:headers="upload.headers"
|
multiple
|
name="files"
|
:on-success="handleUploadSuccess"
|
:on-error="handleUploadError"
|
:on-remove="handleRemove"
|
>
|
<el-button type="primary">上传文件</el-button>
|
</el-upload>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="visible = false">取消</el-button>
|
<el-button type="primary" @click="submit">确定</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup name="ProgressReportDialog">
|
import { computed, reactive, ref, watch } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import { getToken } from '@/utils/auth'
|
|
const props = defineProps({
|
modelValue: { type: Boolean, default: false },
|
projectId: { type: [Number, String], default: undefined },
|
projectInfo: { type: Object, default: () => ({}) },
|
planNodes: { type: Array, default: () => [] },
|
defaultPlanNodeId: { type: [Number, String], default: undefined }
|
})
|
|
const emit = defineEmits(['update:modelValue', 'submitted'])
|
|
const visible = computed({
|
get: () => props.modelValue,
|
set: v => emit('update:modelValue', v)
|
})
|
|
const formRef = ref()
|
const fileList = ref([])
|
const upload = reactive({
|
url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload',
|
headers: { Authorization: 'Bearer ' + getToken() }
|
})
|
|
const form = ref({
|
planNodeId: undefined,
|
planStartTime: '',
|
planEndTime: '',
|
actualStartTime: '',
|
actualEndTime: '',
|
reportDate: '',
|
lastProgress: 0,
|
completionProgress: 0,
|
totalProgress: 0,
|
managerName: '',
|
departmentName: '',
|
remark: '',
|
attachmentIds: []
|
})
|
|
const rules = {
|
planNodeId: [{ required: true, message: '请选择', trigger: 'change' }],
|
planStartTime: [{ required: true, message: '请选择计划开始时间', trigger: 'change' }],
|
planEndTime: [{ required: true, message: '请选择计划完工时间', trigger: 'change' }],
|
reportDate: [{ required: true, message: '请选择', trigger: 'change' }],
|
completionProgress: [{ required: true, message: '请输入', trigger: 'change' }]
|
}
|
|
const stageOptions = 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
|
.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 !== '')
|
})
|
|
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,
|
completionProgress: 0,
|
totalProgress: Number(info.totalProgress ?? info.progress ?? 0) || 0,
|
managerName: info.managerName || '',
|
departmentName: info.departmentName || '',
|
remark: '',
|
attachmentIds: []
|
}
|
fileList.value = []
|
}
|
|
watch(
|
() => props.modelValue,
|
v => {
|
if (v) resetFromProject()
|
}
|
)
|
|
function handleClose() {
|
formRef.value?.resetFields?.()
|
}
|
|
function markDone() {
|
form.value.completionProgress = 100
|
form.value.totalProgress = 100
|
if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || ''
|
}
|
|
function handleUploadError() {
|
ElMessage.error('上传文件失败')
|
}
|
|
function handleUploadSuccess(res, file) {
|
if (res?.code !== 200) {
|
ElMessage.error(res?.msg || '上传失败')
|
return
|
}
|
const attachmentId = res?.data?.id ?? res?.data?.tempId ?? ''
|
if (!attachmentId) return
|
form.value.attachmentIds.push(attachmentId)
|
try {
|
file.attachmentId = attachmentId
|
} catch (e) {}
|
ElMessage.success('上传成功')
|
}
|
|
function handleRemove(file) {
|
const attachmentId = file?.attachmentId
|
if (!attachmentId) return
|
form.value.attachmentIds = (form.value.attachmentIds || []).filter(id => id !== attachmentId)
|
}
|
|
async function submit() {
|
await formRef.value?.validate?.()
|
emit('submitted', {
|
projectId: props.projectId,
|
...form.value
|
})
|
visible.value = false
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
</style>
|