This commit is contained in:
BianLzhaoMin 2026-01-29 16:18:21 +08:00
parent 0d286b2b41
commit 5ecde86836
10 changed files with 615 additions and 166 deletions

View File

@ -111,7 +111,7 @@ export const constantRoutes = [
component: () => import('@/views/planMange/dailyPlan/edit.vue'),
name: 'DailyPlanEdit',
meta: {
title: '日计划',
title: '日完成情况',
activeMenu: '/plan/dailyPlan', // 保持左侧高亮在列表菜单
},
},

View File

@ -19,7 +19,7 @@ export default {
},
{
prop: 'settlementUnitPrice',
label: '结算单价',
label: '预估单价',
slot: 'settlementUnitPrice',
},
{

View File

@ -20,7 +20,7 @@
<template #unitPrice="{ row }">
<span>{{ fenToYuan(row.unitPrice) }}</span>
</template>
<!-- 结算单价列插槽 -->
<!-- 预估单价列插槽 -->
<template #settlementUnitPrice="{ row }">
<span>{{ fenToYuan(row.settlementUnitPrice) }}</span>
</template>
@ -59,7 +59,7 @@
</el-input-number>
</el-form-item>
<el-form-item label="结算单价" prop="settlementUnitPrice">
<el-form-item label="预估单价" prop="settlementUnitPrice">
<el-input-number
style="width: 100%"
:min="0.1"
@ -129,7 +129,7 @@ const addAndEditForm = ref(getInitFormData())
const addAndEditRules = ref({
workloadCategoryName: [{ required: true, message: '请输入工作量类别名称', trigger: 'blur' }],
unitPrice: [{ required: true, message: '请输入绩效单价', trigger: 'blur' }],
settlementUnitPrice: [{ required: true, message: '请输入结算单价', trigger: 'blur' }],
settlementUnitPrice: [{ required: true, message: '请输入预估单价', trigger: 'blur' }],
})
const actionColumns = [

View File

@ -48,58 +48,86 @@ export const tableColumns = [
{ prop: 'projectName', label: '项目名称', fixed: true, width: '140' },
{ prop: 'workContent', label: '工作任务', width: '140' },
{ prop: 'riskLevel', label: '风险等级', width: '80' },
{ prop: 'plannedWorkload', label: '计划工作量(基)', width: '80' },
// { prop: 'plannedWorkload', label: '计划工作量(基)', width: '80' },
]
export const tableColumns_1 = [
{
prop: 'stationName',
label: '拟投入作业人员数量',
fixed: false,
width: '80',
// {
// prop: 'stationName',
// label: '拟投入作业人员数量',
// fixed: false,
// width: '80',
formatter: (row) => {
return row.proposedPersonnelList?.filter((item) => item.dataSource == 0).length || ''
},
},
// formatter: (row) => {
// return row.proposedPersonnelList?.filter((item) => item.dataSource == 0).length || ''
// },
// },
// {
// prop: 'majorName',
// label: '拟投入作业人员姓名',
// fixed: false,
// width: '200',
// formatter: (row) => {
// return (
// row.proposedPersonnelList
// ?.filter((item) => item.dataSource == 0)
// .map((item) => item.name)
// .join(',') || ''
// )
// },
// },
// {
// prop: 'proposedLongTimeCar',
// label: '拟投入车辆',
// fixed: false,
// width: '200',
// slot: 'proposedLongTimeCar',
// },
// {
// prop: 'projectName',
// label: '实际投入作业人员数量',
// fixed: false,
// width: '80',
// formatter: (row) => {
// return row.proposedPersonnelList?.filter((item) => item.dataSource == 1).length || ''
// },
// },
{
prop: 'majorName',
label: '拟投入作业人员姓名',
prop: 'workTask',
label: '实际投入全民人员姓名',
fixed: false,
width: '200',
formatter: (row) => {
return (
row.proposedPersonnelList
?.filter((item) => item.dataSource == 0)
?.filter((item) => item.dataSource == 1 && item.personnelClassificationName.includes('全民'))
.map((item) => item.name)
.join(',') || ''
)
},
},
{
prop: 'proposedLongTimeCar',
label: '拟投入车辆',
fixed: false,
width: '200',
slot: 'proposedLongTimeCar',
},
{
prop: 'projectName',
label: '实际投入作业人员数量',
fixed: false,
width: '80',
formatter: (row) => {
return row.proposedPersonnelList?.filter((item) => item.dataSource == 1).length || ''
},
},
{
prop: 'workTask',
label: '实际投入作业人员姓名',
label: '实际投入派遣人员姓名',
fixed: false,
width: '200',
formatter: (row) => {
return (
row.proposedPersonnelList
?.filter((item) => item.dataSource == 1)
?.filter((item) => item.dataSource == 1 && item.personnelClassificationName.includes('派遣'))
.map((item) => item.name)
.join(',') || ''
)
},
},
{
prop: 'workTask',
label: '实际投入海峡人员姓名',
fixed: false,
width: '200',
formatter: (row) => {
return (
row.proposedPersonnelList
?.filter((item) => item.dataSource == 1 && item.personnelClassificationName.includes('海峡'))
.map((item) => item.name)
.join(',') || ''
)
@ -115,7 +143,7 @@ export const tableColumns_1 = [
// return `长租车${row.actualLongTimeCar}辆,临租车${row.actualTemporaryCar}辆` || ''
// },
},
{ prop: 'actualWorkContent', label: '实际完成工作内容', fixed: false, width: '200' },
// { prop: 'actualWorkContent', label: '实际完成工作内容', fixed: false, width: '200' },
{ prop: 'actualWorkload', label: '实际完成工作量(基)', fixed: false, width: '80' },
{
prop: 'completionPercentage',
@ -131,6 +159,7 @@ export const tableColumns_1 = [
{ prop: 'planChanges', label: '计划变更及未完成情况说明', fixed: false, width: '200' },
]
export const tableColumns_2 = [
{ prop: 'plannedWorkload', label: '计划工作量(基)', width: '80' },
{
prop: 'stationName',
label: '拟投入高处作业人员数量',

View File

@ -196,6 +196,7 @@ const { options: personnelCommonOptions } = useOptions(
const mode = computed(() => route.query.mode || 'edit')
const isDetail = computed(() => mode.value === 'detail')
const personSubType = ref(null)
// 0-1-2-
const dayPlanType = computed(() => {
@ -223,9 +224,9 @@ const pageTitle = computed(() => {
2: '项目部',
}
const typeName = typeMap[dayPlanType.value] || '运行'
if (mode.value === 'edit') return `编辑日计划${typeName}`
if (mode.value === 'detail') return `计划详情(${typeName}`
return `新增日计划${typeName}`
if (mode.value === 'edit') return `编辑日完成情况${typeName}`
if (mode.value === 'detail') return `完成情况详情(${typeName}`
return `新增日完成情况${typeName}`
})
const baseFormRef = ref(null)
@ -256,6 +257,9 @@ const getRunFormData = () => ({
proposedTemporaryCar: null, //
//
actualPersonnelList: [], //
actualNationPersonnelList: [], //
actualDispatchPersonnelList: [], //
actualOverseasPersonnelList: [], //
actualPersonnel: '', // 使
actualLongTimeCar: null, //
actualTemporaryCar: null, //
@ -264,6 +268,17 @@ const getRunFormData = () => ({
completionPercentage: null, //
planCompletionStatus: null, //
planChanges: '', //
actualHighSub: '', //
actualGroundSub: '', //
actualWorkloadList: [
{
workloadCategoryId: '',
workloadCategoryName: '',
unitPrice: '',
settlementUnitPrice: '',
workloadNum: '',
},
], //
})
//
@ -381,16 +396,43 @@ const onSubmit = async () => {
if (type === '0' || type === 0) {
// /
;(formData.value.planPersonnelList || []).forEach((item) => {
// ;(formData.value.planPersonnelList || []).forEach((item) => {
// proposedPersonnelList.push({
// dayPlanId: route.query.id,
// inspectionStationName: item.inspectionStationName,
// personnelId: item.id,
// name: item.name,
// dataSource: 0, //
// })
// })
// ;(formData.value.actualPersonnelList || []).forEach((item) => {
// proposedPersonnelList.push({
// dayPlanId: route.query.id,
// inspectionStationName: item.inspectionStationName,
// personnelId: item.id,
// name: item.name,
// dataSource: 1, //
// })
// })
formData.value.actualNationPersonnelList.forEach((item) => {
proposedPersonnelList.push({
dayPlanId: route.query.id,
inspectionStationName: item.inspectionStationName,
personnelId: item.id,
name: item.name,
dataSource: 0, //
dataSource: 1, //
})
})
;(formData.value.actualPersonnelList || []).forEach((item) => {
formData.value.actualDispatchPersonnelList.forEach((item) => {
proposedPersonnelList.push({
dayPlanId: route.query.id,
inspectionStationName: item.inspectionStationName,
personnelId: item.id,
name: item.name,
dataSource: 1, //
})
})
formData.value.actualOverseasPersonnelList.forEach((item) => {
proposedPersonnelList.push({
dayPlanId: route.query.id,
inspectionStationName: item.inspectionStationName,
@ -454,12 +496,15 @@ const onSubmit = async () => {
actualPersonnel,
planPersonnel,
actualWorkloadList,
actualNationPersonnelList,
actualDispatchPersonnelList,
actualOverseasPersonnelList,
...submitData
} = formData.value
submitData.proposedPersonnelList = proposedPersonnelList
submitData.dayPlanType = type
if (type === '1' || type === 1) {
if (type === '1' || type === 1 || type === '0' || type === 0) {
submitData.workloadList = formData.value.actualWorkloadList
}
@ -503,14 +548,27 @@ const personList = computed(() => {
})
const filteredPersons = computed(() => {
const list = personList.value
if (!managerDialog.keyword) return list
const keyword = managerDialog.keyword.toLowerCase()
const list = personList.value || []
const keyword = (managerDialog.keyword || '').toLowerCase().trim()
const subType = (personSubType.value || '').toLowerCase().trim()
//
if (!keyword && !subType) return list
return list.filter((item) => {
const name = (item.name || '').toLowerCase()
const org = (item.org || '').toLowerCase()
const station = (item.station || '').toLowerCase()
return name.includes(keyword) || org.includes(keyword) || station.includes(keyword)
const nature = (item.personnelNatureName || '').toLowerCase()
//
const matchKeyword =
!keyword || name.includes(keyword) || org.includes(keyword) || station.includes(keyword)
// / /
const matchSubType = !subType || nature.includes(subType)
return matchKeyword && matchSubType
})
})
@ -558,15 +616,23 @@ watch(
},
)
const onOpenPersonPicker = async (type = 'plan') => {
const onOpenPersonPicker = async (type = 'plan', subType = null) => {
managerDialog.type = type
managerDialogConfig.outerVisible = true
personSubType.value = subType
let currentList = []
if (type === 'plan') {
currentList = formData.value.planPersonnelList || []
} else if (type === 'actual') {
currentList = formData.value.actualPersonnelList || []
// currentList = formData.value.actualPersonnelList || []
if (subType === '全民') {
currentList = formData.value.actualNationPersonnelList || []
} else if (subType === '派遣') {
currentList = formData.value.actualDispatchPersonnelList || []
} else if (subType === '海峡') {
currentList = formData.value.actualOverseasPersonnelList || []
}
} else if (type === 'highAltitude') {
currentList = formData.value.highAltitudePersonnelList || []
} else if (type === 'ground') {
@ -620,9 +686,18 @@ const onConfirmManager = () => {
formRef.value?.validateField?.('planPersonnelList')
})
} else if (type === 'actual') {
formData.value.actualPersonnelList = [...managerDialog.selected]
// formData.value.actualPersonnelList = [...managerDialog.selected]
if (personSubType.value === '全民') {
formData.value.actualNationPersonnelList = [...managerDialog.selected]
} else if (personSubType.value === '派遣') {
formData.value.actualDispatchPersonnelList = [...managerDialog.selected]
} else if (personSubType.value === '海峡') {
formData.value.actualOverseasPersonnelList = [...managerDialog.selected]
}
nextTick(() => {
formRef.value?.validateField?.('actualPersonnelList')
formRef.value?.validateField?.('actualNationPersonnelList')
formRef.value?.validateField?.('actualDispatchPersonnelList')
formRef.value?.validateField?.('actualOverseasPersonnelList')
})
} else if (type === 'highAltitude') {
formData.value.highAltitudePersonnelList = [...managerDialog.selected]
@ -674,28 +749,56 @@ const getDetail = async () => {
completionPercentage: data.completionPercentage,
planCompletionStatus: data.planCompletionStatus,
planChanges: data.planChanges,
actualHighSub: data.actualHighSub,
actualGroundSub: data.actualGroundSub,
}
console.log('data.proposedPersonnelList', data.proposedPersonnelList)
// proposedPersonnelList dataSource
const proposedPersonnelList = data.proposedPersonnelList || []
if (type === '0' || type === 0) {
// 使 planPersonnelList actualPersonnelList
baseFields.planPersonnelList = proposedPersonnelList
.filter((item) => item.dataSource === 0 || item.dataSource === '0')
baseFields.actualWorkloadList = data.workloadList
formData.value.actualNationPersonnelList = proposedPersonnelList
.filter((item) => item.personnelClassificationName.includes('全民'))
.map((item) => ({
id: item.personnelId,
name: item.name || '',
inspectionStationName: item.inspectionStationName || '',
}))
formData.value.actualDispatchPersonnelList = proposedPersonnelList
.filter((item) => item.personnelClassificationName.includes('派遣'))
.map((item) => ({
id: item.personnelId,
name: item.name || '',
inspectionStationName: item.inspectionStationName || '',
}))
formData.value.actualOverseasPersonnelList = proposedPersonnelList
.filter((item) => item.personnelClassificationName.includes('海峡'))
.map((item) => ({
id: item.personnelId,
name: item.name || '',
inspectionStationName: item.inspectionStationName || '',
}))
baseFields.actualPersonnelList = proposedPersonnelList
.filter((item) => item.dataSource === 1 || item.dataSource === '1')
.map((item) => ({
id: item.personnelId,
name: item.name || '',
inspectionStationName: item.inspectionStationName || '',
}))
// 使 planPersonnelList actualPersonnelList
// baseFields.planPersonnelList = proposedPersonnelList
// .filter((item) => item.dataSource === 0 || item.dataSource === '0')
// .map((item) => ({
// id: item.personnelId,
// name: item.name || '',
// inspectionStationName: item.inspectionStationName || '',
// }))
// baseFields.actualPersonnelList = proposedPersonnelList
// .filter((item) => item.dataSource === 1 || item.dataSource === '1')
// .map((item) => ({
// id: item.personnelId,
// name: item.name || '',
// inspectionStationName: item.inspectionStationName || '',
// }))
} else if (type === '1' || type === 1 || type === '2' || type === 2) {
// 使 highAltitudePersonnelListgroundPersonnelListactualHighAltitudePersonnelListactualGroundPersonnelList
baseFields.highAltitudePersonnelList = proposedPersonnelList
@ -920,10 +1023,12 @@ onMounted(() => {
}
.page-footer {
margin-top: 12px;
display: flex;
justify-content: flex-end;
gap: 12px;
position: sticky;
bottom: 4px;
background-color: #fff;
}
.clickable-suffix {

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-card shadow="never" class="card-section">
<!-- <el-card shadow="never" class="card-section">
<template #header>
<div class="card-header">计划投入资源情况</div>
</template>
@ -222,14 +222,9 @@
</el-col>
</el-row>
</el-form>
</el-card>
</el-card> -->
<el-card
v-if="showActualCompletion"
shadow="never"
class="card-section"
style="margin-top: 20px"
>
<el-card shadow="never" class="card-section" style="margin-top: 20px">
<template #header>
<div class="card-header">实际完成情况</div>
</template>
@ -490,7 +485,7 @@
</el-form>
</el-card>
<div class="form-actions" v-if="!isDetail">
<!-- <div class="form-actions" v-if="!isDetail">
<ComButton
v-if="!showActualCompletion"
type="primary"
@ -501,7 +496,7 @@
<ComButton v-else plain type="info" @click="onCancelActualCompletion">
取消实际完成情况
</ComButton>
</div>
</div> -->
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<div>
<!-- 计划投入资源情况 -->
<el-card shadow="never" class="card-section">
<!-- <el-card shadow="never" class="card-section">
<template #header>
<div class="card-header">计划投入资源情况</div>
</template>
@ -223,15 +223,10 @@
</el-col>
</el-row>
</el-form>
</el-card>
</el-card> -->
<!-- 实际完成情况默认隐藏 -->
<el-card
v-if="showActualCompletion"
shadow="never"
class="card-section"
style="margin-top: 20px"
>
<el-card shadow="never" class="card-section" style="margin-top: 20px">
<template #header>
<div class="card-header">实际完成情况</div>
</template>
@ -546,7 +541,7 @@
</el-card>
<!-- 操作按钮 -->
<div class="form-actions" v-if="!isDetail">
<!-- <div class="form-actions" v-if="!isDetail">
<ComButton
v-if="!showActualCompletion"
type="primary"
@ -557,7 +552,7 @@
<ComButton v-else plain type="info" @click="onCancelActualCompletion">
取消实际完成情况
</ComButton>
</div>
</div> -->
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<div>
<!-- 计划投入资源情况 -->
<el-card shadow="never" class="card-section">
<!-- <el-card shadow="never" class="card-section">
<template #header>
<div class="card-header">计划投入资源情况</div>
</template>
@ -91,15 +91,10 @@
</el-col>
</el-row>
</el-form>
</el-card>
</el-card> -->
<!-- 实际完成情况默认隐藏 -->
<el-card
v-if="showActualCompletion"
shadow="never"
class="card-section"
style="margin-top: 20px"
>
<el-card shadow="never" class="card-section" style="margin-top: 20px">
<template #header>
<div class="card-header">实际完成情况</div>
</template>
@ -114,12 +109,49 @@
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="实际投入作业人员" prop="actualPersonnelList">
<el-form-item label="实际投入全民员工" prop="actualNationPersonnelList">
<el-input
:model-value="selectedActualManagerNames"
:model-value="selectedActualNationManagerNames"
placeholder="请选择作业人员"
readonly
@click="onOpenActualPersonPicker"
@click="onOpenActualPersonPicker('全民')"
class="clickable-suffix"
>
<template #suffix>
<el-icon class="clickable-suffix">
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际投入派遣员工" prop="actualDispatchPersonnelList">
<el-input
:model-value="selectedActualDispatchManagerNames"
placeholder="请选择作业人员"
readonly
@click="onOpenActualPersonPicker('派遣')"
class="clickable-suffix"
>
<template #suffix>
<el-icon class="clickable-suffix">
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="实际投入海峡员工" prop="actualOverseasPersonnelList">
<el-input
:model-value="selectedActualOverseasManagerNames"
placeholder="请选择作业人员"
readonly
@click="onOpenActualPersonPicker('海峡')"
class="clickable-suffix"
>
<template #suffix>
@ -186,8 +218,7 @@
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="实际完成工作内容" prop="actualWorkContent">
<!-- <el-form-item label="实际完成工作内容" prop="actualWorkContent">
<el-input
:model-value="formData.actualWorkContent"
@update:model-value="(val) => updateField('actualWorkContent', val)"
@ -197,8 +228,88 @@
maxlength="500"
show-word-limit
/>
</el-form-item> -->
<template v-for="(item, index) in formData.actualWorkloadList" :key="index">
<el-col :span="8">
<el-form-item
:prop="`actualWorkloadList.${index}.workloadCategoryId`"
:class="{ 'hide-required': index !== 0 }"
>
<template #label>
<span v-if="index == 0">实际完成工作内容</span>
<span v-else style="visibility: hidden">工作量</span>
</template>
<el-select
clearable
placeholder="请选择工作量类别"
:model-value="item.workloadCategoryId"
disabled
style="width: 100%"
>
<el-option
v-for="option in planWorkLoadCategoryOptions"
:key="option.workloadCategoryId"
:value="option.workloadCategoryId"
:label="option.workloadCategoryName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item
label-width="0"
:prop="`actualWorkloadList.${index}.workloadNum`"
>
<el-input
clearable
style="width: 100%"
placeholder="输入数量"
show-word-limit
maxlength="7"
disabled
:model-value="item.workloadNum"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label-width="0"
:prop="`actualWorkloadList.${index}.actualWorkloadNum`"
>
<el-input
clearable
style="width: 100%"
placeholder="请填写实际完成工作量"
show-word-limit
maxlength="7"
:model-value="item.actualWorkloadNum"
@update:model-value="
(val) => updateWorkloadItem(index, 'actualWorkloadNum', val)
"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item
label-width="0"
:prop="`actualWorkloadList.${index}.completionRate`"
>
<el-input
clearable
disabled
readonly
style="width: 100%"
:model-value="getItemCompletionPercentage(index)"
placeholder="完成比例,自动计算"
>
<template #suffix>
<span class="input-suffix">%</span>
</template>
</el-input>
</el-form-item>
</el-col>
</template>
</el-row>
<el-row :gutter="24">
@ -241,6 +352,36 @@
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="实际投入高空分包" prop="actualHighSub">
<el-input
:model-value="formData.actualHighSub"
@update:model-value="(val) => updateField('actualHighSub', val)"
placeholder="请输入实际投入高空分包"
clearable
maxlength="7"
show-word-limit
>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际投入地面分包" prop="actualGroundSub">
<el-input
:model-value="formData.actualGroundSub"
@update:model-value="(val) => updateField('actualGroundSub', val)"
placeholder="请输入实际投入地面分包"
clearable
maxlength="7"
show-word-limit
>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="计划变更及未完成情况说明" prop="planChanges">
@ -260,7 +401,7 @@
</el-card>
<!-- 操作按钮 -->
<div class="form-actions" v-if="!isDetail">
<!-- <div class="form-actions" v-if="!isDetail">
<ComButton
v-if="!showActualCompletion"
type="primary"
@ -271,13 +412,15 @@
<ComButton v-else plain type="info" @click="onCancelActualCompletion">
取消实际完成情况
</ComButton>
</div>
</div> -->
</div>
</template>
<script setup name="RunForm">
import { ref, computed, nextTick, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { useOptions } from '@/hooks/useOptions'
import { getPlanMajorListSelectAPI } from '@/api/common.js'
import ComButton from '@/components/ComButton/index.vue'
const props = defineProps({
@ -308,35 +451,41 @@ const planCompletionStatusOptions = [
{ label: '未完成', value: '未完成' },
]
//
const { options: planWorkLoadCategoryOptions } = useOptions(
'planWorkLoadCategoryOptions',
getPlanMajorListSelectAPI,
{},
)
//
const updateField = (field, value) => {
const newData = {
...props.formData,
[field]: value,
}
//
if (field === 'actualWorkload' || field === 'plannedWorkload') {
//
if (field === 'actualWorkload' || field === 'actualWorkloadList') {
newData.completionPercentage = calculateCompletionPercentage(
newData.actualWorkload,
newData.plannedWorkload,
newData.actualWorkloadList,
)
}
emit('update:formData', newData)
}
// ÷ × 100%
const calculateCompletionPercentage = (actualWorkload, plannedWorkload) => {
// ÷ workloadNum × 100%
const calculateItemCompletionRate = (actualWorkloadNum, workloadNum) => {
try {
//
const actual = Number(actualWorkload)
const planned = Number(plannedWorkload)
const actual = Number(actualWorkloadNum)
const planned = Number(workloadNum)
//
if (isNaN(actual) || isNaN(planned)) {
return null
}
// 0
// workloadNum 0
if (planned <= 0) {
return null
}
@ -351,7 +500,73 @@ const calculateCompletionPercentage = (actualWorkload, plannedWorkload) => {
const roundedPercentage = Math.round(percentage * 100) / 100
// 100%100%
return roundedPercentage > 100 ? 100 : roundedPercentage
} catch (error) {
console.error('计算单条完成比例时出错:', error)
return null
}
}
//
const updateWorkloadItem = (index, field, value) => {
const newWorkloadList = [...(props.formData.actualWorkloadList || [])]
newWorkloadList[index] = {
...newWorkloadList[index],
[field]: value,
}
// actualWorkloadNum workloadNum completionRate
if (field === 'actualWorkloadNum' || field === 'workloadNum') {
const item = newWorkloadList[index]
const completionRate = calculateItemCompletionRate(item.actualWorkloadNum, item.workloadNum)
newWorkloadList[index] = {
...newWorkloadList[index],
completionRate: completionRate !== null ? completionRate : null,
}
}
updateField('actualWorkloadList', newWorkloadList)
}
//
const getItemCompletionPercentage = (index) => {
const item = props.formData.actualWorkloadList?.[index]
if (!item) return ''
// completionRate
if (item.completionRate !== null && item.completionRate !== undefined) {
return item.completionRate
}
//
const completionRate = calculateItemCompletionRate(item.actualWorkloadNum, item.workloadNum)
return completionRate !== null ? completionRate : ''
}
// ÷ × 100%
const calculateCompletionPercentage = (actualWorkload, actualWorkloadList) => {
try {
//
const actual = Number(actualWorkload)
//
if (isNaN(actual) || actual < 0) {
return null
}
//
const listLength = Array.isArray(actualWorkloadList) ? actualWorkloadList.length : 0
// 0
if (listLength <= 0) {
return null
}
// 2
const percentage = (actual / listLength) * 100
const roundedPercentage = Math.round(percentage * 100) / 100
// 100%100%
return roundedPercentage > 100 ? 100 : roundedPercentage
} catch (error) {
console.error('计算完成比例时出错:', error)
@ -363,17 +578,16 @@ const calculateCompletionPercentage = (actualWorkload, plannedWorkload) => {
const calculatedCompletionPercentage = computed(() => {
const percentage = calculateCompletionPercentage(
props.formData.actualWorkload,
props.formData.plannedWorkload,
props.formData.actualWorkloadList,
)
return percentage !== null ? percentage : ''
})
//
//
watch(
() => [props.formData.actualWorkload, props.formData.plannedWorkload],
([actualWorkload, plannedWorkload]) => {
if (showActualCompletion.value) {
const percentage = calculateCompletionPercentage(actualWorkload, plannedWorkload)
() => [props.formData.actualWorkload, props.formData.actualWorkloadList],
([actualWorkload, actualWorkloadList]) => {
const percentage = calculateCompletionPercentage(actualWorkload, actualWorkloadList)
if (percentage !== null && percentage !== props.formData.completionPercentage) {
// 使 emit
emit('update:formData', {
@ -381,19 +595,69 @@ watch(
completionPercentage: percentage,
})
}
},
{ immediate: true, deep: true },
)
// actualWorkloadList completionRate
watch(
() => props.formData.actualWorkloadList,
(newList, oldList) => {
if (!Array.isArray(newList) || newList.length === 0) return
// completionRate
let needUpdate = false
const updatedList = newList.map((item, index) => {
// actualWorkloadNum workloadNum completionRate
if (
item.actualWorkloadNum !== undefined &&
item.actualWorkloadNum !== null &&
item.workloadNum !== undefined &&
item.workloadNum !== null
) {
const calculatedRate = calculateItemCompletionRate(
item.actualWorkloadNum,
item.workloadNum,
)
//
if (calculatedRate !== null && calculatedRate !== item.completionRate) {
needUpdate = true
return {
...item,
completionRate: calculatedRate,
}
}
}
return item
})
//
if (needUpdate) {
// 使 nextTick watch
nextTick(() => {
emit('update:formData', {
...props.formData,
actualWorkloadList: updatedList,
})
})
}
},
{ immediate: true },
{ immediate: true, deep: true },
)
//
watch(
() => showActualCompletion.value,
(isShow) => {
if (isShow && props.formData.actualWorkload && props.formData.plannedWorkload) {
if (
isShow &&
props.formData.actualWorkload &&
props.formData.actualWorkloadList?.length > 0
) {
const percentage = calculateCompletionPercentage(
props.formData.actualWorkload,
props.formData.plannedWorkload,
props.formData.actualWorkloadList,
)
if (percentage !== null) {
emit('update:formData', {
@ -430,8 +694,20 @@ const rules = computed(() => ({
}))
//
const actualRules = computed(() => ({
actualPersonnelList: [{ required: true, message: '请选择实际入作业人员', trigger: 'change' }],
const actualRules = computed(() => {
const baseRules = {
actualPersonnelList: [
{ required: true, message: '请选择实际入作业人员', trigger: 'change' },
],
actualNationPersonnelList: [
{ required: true, message: '请选择实际入全民员工', trigger: 'change' },
],
actualDispatchPersonnelList: [
{ required: true, message: '请选择实际入派遣员工', trigger: 'change' },
],
actualOverseasPersonnelList: [
{ required: true, message: '请选择实际入海峡员工', trigger: 'change' },
],
actualLongTimeCar: [
{ required: true, message: '请输入实际入长租车数量', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
@ -445,16 +721,25 @@ const actualRules = computed(() => ({
{ required: true, message: '请输入实际完成工作量', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
],
actualHighSub: [
{ required: true, message: '请输入实际投入高空分包', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
],
actualGroundSub: [
{ required: true, message: '请输入实际投入地面分包', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
],
completionPercentage: [
{
required: true,
validator: (rule, value, callback) => {
const percentage = calculateCompletionPercentage(
props.formData.actualWorkload,
props.formData.plannedWorkload,
props.formData.actualWorkloadList,
)
if (percentage === null || percentage === '') {
callback(new Error('请先填写计划工作量和实际完成工作量'))
callback(new Error('请先填写实际完成工作内容和实际完成工作量'))
} else {
callback()
}
@ -465,20 +750,51 @@ const actualRules = computed(() => ({
planCompletionStatus: [
{ required: true, message: '请选择作业计划完成情况', trigger: 'change' },
],
planChanges: [{ required: false, message: '请输入计划变更及未完成情况说明', trigger: 'blur' }],
}))
planChanges: [
{ required: false, message: '请输入计划变更及未完成情况说明', trigger: 'blur' },
],
}
// actualWorkloadList
baseRules.actualWorkloadList = (props.formData.actualWorkloadList || []).map(() => ({
workloadCategoryId: [{ required: true, message: '请选择工作量类别', trigger: 'change' }],
workloadNum: [
{ required: true, message: '请输入工作量', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
],
actualWorkloadNum: [
{ required: true, message: '请输入实际完成工作量', trigger: 'blur' },
{ pattern: nonNegativeIntegerPattern, message: '请输入非负整数', trigger: 'blur' },
],
}))
return baseRules
})
//
const selectedActualManagerNames = computed(() =>
(props.formData.actualPersonnelList || []).map((item) => item.name).join('、'),
)
//
const selectedActualNationManagerNames = computed(() =>
(props.formData.actualNationPersonnelList || []).map((item) => item.name).join('、'),
)
//
const selectedActualDispatchManagerNames = computed(() =>
(props.formData.actualDispatchPersonnelList || []).map((item) => item.name).join('、'),
)
//
const selectedActualOverseasManagerNames = computed(() =>
(props.formData.actualOverseasPersonnelList || []).map((item) => item.name).join('、'),
)
const onOpenPersonPicker = () => {
emit('open-person-picker', 'plan')
}
const onOpenActualPersonPicker = () => {
emit('open-person-picker', 'actual')
const onOpenActualPersonPicker = (type) => {
emit('open-person-picker', 'actual', type)
}
//
@ -593,4 +909,10 @@ defineExpose({
display: flex;
justify-content: flex-start;
}
:deep(.hide-required) {
&.is-required .el-form-item__label::before {
display: none;
}
}
</style>

View File

@ -51,7 +51,7 @@
@click="onHandleAdd"
v-hasPermi="['plan:dailyPlan:add']"
>
新增日计划
新增日完成情况
</ComButton>
<ComButton
plain

View File

@ -689,7 +689,7 @@ const getInitFormData = () => ({
workloadCategoryName: '', //
unitPrice: '', //
workloadNum: '', //
settlementUnitPrice: '', //
settlementUnitPrice: '', //
},
],
personnelArrangementList: [], // [{day: '', personnelNames: ''}]
@ -1214,10 +1214,13 @@ onMounted(() => {
}
.page-footer {
margin-top: 12px;
// margin-top: 12px;
display: flex;
justify-content: flex-end;
gap: 12px;
position: sticky;
bottom: 4px;
background-color: #fff;
}
}