|
|
<template>
|
|
|
<div class="p-2">
|
|
|
<el-card shadow="never">
|
|
|
<div v-if="showApprovalButton" class="mb-3 flex items-center justify-between">
|
|
|
<div>
|
|
|
<el-button v-if="showSubmitButtons" type="info" :loading="draftLoading" :disabled="submitLoading" @click="handleSubmitAction('draft')"
|
|
|
>暂存</el-button
|
|
|
>
|
|
|
<el-button v-if="showSubmitButtons" type="primary" :loading="submitLoading" :disabled="draftLoading" @click="handleSubmitAction('submit')"
|
|
|
>提交</el-button
|
|
|
>
|
|
|
<el-button v-if="showApprovalAction" type="primary" :disabled="draftLoading || submitLoading" @click="approvalVerifyOpen">审批</el-button>
|
|
|
<el-button v-if="showProgressButton" type="primary" :disabled="draftLoading || submitLoading" @click="handleApprovalRecord"
|
|
|
>流程进度</el-button
|
|
|
>
|
|
|
</div>
|
|
|
<div>
|
|
|
<el-button @click="goBack">返回</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-else-if="showLimitedActions" class="mb-3 text-right">
|
|
|
<el-button type="primary" :loading="buttonLoading" @click="handleSave">保存</el-button>
|
|
|
<el-button @click="goBack">返回</el-button>
|
|
|
</div>
|
|
|
<el-form ref="projectPlanFormRef" :model="form" :rules="rules" :disabled="isViewMode" label-width="120px">
|
|
|
<el-divider content-position="left">基本信息</el-divider>
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目名称" prop="projectId">
|
|
|
<el-select v-model="form.projectId" placeholder="请选择项目" filterable :disabled="!isBasicEditable" @change="handleProjectChange">
|
|
|
<el-option v-for="item in projectInfoList" :key="item.projectId" :label="item.projectName" :value="item.projectId" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目计划编号" prop="projectPlanCode">
|
|
|
<el-input v-model="form.projectPlanCode" placeholder="由系统自动生成" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目经理" prop="managerId">
|
|
|
<el-input v-model="form.managerName" placeholder="自动带出" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目经理部门">
|
|
|
<el-input v-model="form.managerDeptName" placeholder="自动带出" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="部门负责人" prop="chargeId">
|
|
|
<el-input v-model="form.chargeName" placeholder="自动带出" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目编号">
|
|
|
<el-input v-model="selectedProjectCode" placeholder="选择项目后自动显示" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="付款方式" prop="paymentMethod">
|
|
|
<el-input v-model="form.paymentMethod" placeholder="(如:3-3-3-1)" :disabled="!isBasicEditable" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="项目计划状态" prop="projectPlanStatus">
|
|
|
<!-- <el-select v-model="form.projectPlanStatus" placeholder="请选择项目计划状态" :disabled="!isBasicEditable"> -->
|
|
|
<el-select v-model="form.projectPlanStatus" placeholder="请选择项目计划状态" :disabled="true">
|
|
|
<el-option v-for="dict in project_plan_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="附件" prop="ossId">
|
|
|
<template v-if="isViewMode">
|
|
|
<el-button type="primary" plain icon="View" @click="handlePreview" :disabled="!form.ossId">预览附件</el-button>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<el-button type="primary" plain icon="Upload" @click="handleFile">上传附件</el-button>
|
|
|
</template>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="备注" prop="remark">
|
|
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="!isBasicEditable" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-divider content-position="left">项目阶段计划</el-divider>
|
|
|
<el-row :gutter="10" class="mb8" v-if="isBasicEditable">
|
|
|
<el-col :span="1.5">
|
|
|
<el-button type="primary" icon="Plus" @click="handleAddStage">添加阶段</el-button>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-table :data="form.planStageList" border stripe max-height="600">
|
|
|
<el-table-column label="序号" type="index" width="60" align="center" />
|
|
|
<el-table-column label="项目阶段" width="150" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-select v-model="scope.row.projectPhases" placeholder="请选择项目阶段" style="width: 100%" :disabled="!isBasicEditable">
|
|
|
<el-option v-for="dict in project_phases" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
</el-select>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="计划开始时间" width="160" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker
|
|
|
v-model="scope.row.planStartTime"
|
|
|
type="date"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
placeholder="选择日期"
|
|
|
style="width: 100%"
|
|
|
:disabled="!isBasicEditable"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="计划结束时间" width="160" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker
|
|
|
v-model="scope.row.planEndTime"
|
|
|
type="date"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
placeholder="选择日期"
|
|
|
style="width: 100%"
|
|
|
:disabled="!isBasicEditable"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="回款阶段" width="150" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-select v-model="scope.row.collectionStage" placeholder="请选择回款阶段" style="width: 100%" :disabled="!isBasicEditable">
|
|
|
<el-option v-for="dict in collection_stage" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
</el-select>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="回款比例(%)" width="120" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-input-number
|
|
|
v-model="scope.row.repaymentRate"
|
|
|
:min="0"
|
|
|
:max="100"
|
|
|
:precision="2"
|
|
|
controls-position="right"
|
|
|
style="width: 100%"
|
|
|
:disabled="!isBasicEditable"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="预计回款金额" width="140" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-input-number
|
|
|
v-model="scope.row.repaymentAmount"
|
|
|
:min="0"
|
|
|
:precision="2"
|
|
|
controls-position="right"
|
|
|
style="width: 100%"
|
|
|
:disabled="!isBasicEditable"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="预计回款时间" width="160" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker
|
|
|
v-model="scope.row.repaymentTime"
|
|
|
type="date"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
placeholder="选择日期"
|
|
|
style="width: 100%"
|
|
|
:disabled="!isBasicEditable"
|
|
|
@change="recalcReceivableDate(scope.row)"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="回款延期天数" width="140" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-input-number
|
|
|
v-model="scope.row.delayDay"
|
|
|
:min="0"
|
|
|
:step="1"
|
|
|
controls-position="right"
|
|
|
style="width: 100%"
|
|
|
:disabled="!canEditLimited"
|
|
|
@change="recalcReceivableDate(scope.row)"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<!-- <el-table-column label="应收款日期" width="160" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker v-model="scope.row.receivableDate" type="date" value-format="YYYY-MM-DD" placeholder="自动计算" style="width: 100%" disabled/>
|
|
|
</template>
|
|
|
</el-table-column>-->
|
|
|
<el-table-column label="实际开始" min-width="140">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker v-model="scope.row.realStartTime" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="实际结束" min-width="140">
|
|
|
<template #default="scope">
|
|
|
<el-date-picker v-model="scope.row.realEndTime" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="进度备注" width="200" align="center">
|
|
|
<template #default="scope">
|
|
|
<el-input v-model="scope.row.scheduleRemark" placeholder="请输入进度备注" :disabled="!canEditLimited" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="80" align="center" fixed="right" v-if="isBasicEditable">
|
|
|
<template #default="scope">
|
|
|
<el-button type="danger" link icon="Delete" @click="handleDeleteStage(scope.$index)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<!-- 动态变更列(多列合并展示) -->
|
|
|
<el-table-column
|
|
|
v-for="(change, changeIndex) in projectChangeList"
|
|
|
:key="`change-${change.projectChangeId || changeIndex}`"
|
|
|
:label="`第${change.changeNumber || changeIndex + 1}次变更`"
|
|
|
align="center"
|
|
|
>
|
|
|
<el-table-column label="变更开始" width="110" align="center">
|
|
|
<template #default="scope">
|
|
|
<span v-if="getChangeProgressByStage(change, scope.row.planStageId)" class="change-highlight">
|
|
|
{{ (getChangeProgressByStage(change, scope.row.planStageId).changedStart || '').substring(0, 10) || '-' }}
|
|
|
</span>
|
|
|
<span v-else class="text-gray-300">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="变更结束" width="110" align="center">
|
|
|
<template #default="scope">
|
|
|
<span v-if="getChangeProgressByStage(change, scope.row.planStageId)" class="change-highlight">
|
|
|
{{ (getChangeProgressByStage(change, scope.row.planStageId).changedEnd || '').substring(0, 10) || '-' }}
|
|
|
</span>
|
|
|
<span v-else class="text-gray-300">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="里程碑" width="100" align="center">
|
|
|
<template #default="scope">
|
|
|
<span v-if="getChangeProgressByStage(change, scope.row.planStageId)" class="change-highlight">
|
|
|
{{ getChangeProgressByStage(change, scope.row.planStageId).milestoneName || '-' }}
|
|
|
</span>
|
|
|
<span v-else class="text-gray-300">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="完成度" width="80" align="center">
|
|
|
<template #default="scope">
|
|
|
<span v-if="getChangeProgressByStage(change, scope.row.planStageId)" class="change-highlight">
|
|
|
{{
|
|
|
getChangeProgressByStage(change, scope.row.planStageId).completionDegree != null
|
|
|
? getChangeProgressByStage(change, scope.row.planStageId).completionDegree + '%'
|
|
|
: '-'
|
|
|
}}
|
|
|
</span>
|
|
|
<span v-else class="text-gray-300">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="备注" width="120" align="center" show-overflow-tooltip>
|
|
|
<template #default="scope">
|
|
|
<span v-if="getChangeProgressByStage(change, scope.row.planStageId)" class="change-highlight">
|
|
|
{{ getChangeProgressByStage(change, scope.row.planStageId).remark || '-' }}
|
|
|
</span>
|
|
|
<span v-else class="text-gray-300">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 提交审批组件 -->
|
|
|
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
|
|
|
<!-- 审批记录 -->
|
|
|
<approvalRecord ref="approvalRecordRef" />
|
|
|
|
|
|
<!-- 附件上传/预览对话框 -->
|
|
|
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
|
|
<el-form label-width="80px">
|
|
|
<el-form-item label="文件名">
|
|
|
<fileUpload v-if="type === 0" v-model="ossFileModel" :disabled="isViewMode" />
|
|
|
<imageUpload v-if="type === 1" v-model="ossFileModel" :disabled="isViewMode" />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
<el-button v-if="!isViewMode" type="primary" @click="submitOss">确 定</el-button>
|
|
|
<el-button @click="cancel">取 消</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="ErpProjectPlanEdit">
|
|
|
import { computed, getCurrentInstance, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
import { addErpProjectPlan, getErpProjectPlan, projectPlanSubmitAndFlowStart, updateErpProjectPlan } from '@/api/oa/erp/erpProjectPlan';
|
|
|
import { ErpProjectPlanForm } from '@/api/oa/erp/erpProjectPlan/types';
|
|
|
import { queryProjectChangeByProjectPlanId } from '@/api/oa/erp/erpProjectChange';
|
|
|
import { getErpProjectInfoList } from '@/api/oa/erp/projectInfo';
|
|
|
import type { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
|
|
|
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
|
|
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
|
|
import { useUserStore } from '@/store/modules/user';
|
|
|
import { FlowCodeEnum } from '@/enums/OAEnum';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
const { project_plan_status, project_phases, collection_stage, project_change_status } = toRefs<any>(
|
|
|
proxy?.useDict('project_plan_status', 'project_phases', 'collection_stage', 'project_change_status')
|
|
|
);
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
const userStore = useUserStore();
|
|
|
const refreshFlagKey = 'erpProjectPlanListShouldRefresh';
|
|
|
|
|
|
// 审批相关组件引用
|
|
|
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
|
|
|
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
|
|
|
|
|
|
// 路由参数
|
|
|
const routeParams = ref<any>({});
|
|
|
const syncRouteParams = () => {
|
|
|
const projectPlanId = (route.params.projectPlanId as string) || (route.query.id as string);
|
|
|
const type = (route.query.type as string) || (route.params.type as string) || 'edit';
|
|
|
const taskId = (route.query.taskId as string) || (route.params.taskId as string);
|
|
|
routeParams.value = {
|
|
|
projectPlanId,
|
|
|
type,
|
|
|
taskId
|
|
|
};
|
|
|
};
|
|
|
|
|
|
syncRouteParams();
|
|
|
|
|
|
// approvalButton组件的pageType(将'edit'转换为'update')
|
|
|
const approvalPageType = computed(() => {
|
|
|
if (routeParams.value.type === 'edit') {
|
|
|
return 'update';
|
|
|
}
|
|
|
return routeParams.value.type;
|
|
|
});
|
|
|
|
|
|
// 是否只读模式
|
|
|
const isViewMode = computed(() => ['view', 'approval'].includes(routeParams.value.type));
|
|
|
|
|
|
// 流程相关数据
|
|
|
const taskVariables = ref<any>({});
|
|
|
|
|
|
// 计算属性:判断基础信息是否可编辑
|
|
|
const isBasicEditable = computed(() => {
|
|
|
if (isViewMode.value) {
|
|
|
return false;
|
|
|
}
|
|
|
// 草稿、驳回、撤回均允许项目经理完整编辑
|
|
|
return form.value.projectPlanStatus === '1' || ['back', 'cancel'].includes(form.value.flowStatus as string);
|
|
|
});
|
|
|
|
|
|
// 计算属性:判断是否可编辑延迟/备注等有限字段
|
|
|
const canEditLimited = computed(() => {
|
|
|
if (isViewMode.value) {
|
|
|
return false;
|
|
|
}
|
|
|
if (isBasicEditable.value) {
|
|
|
return true;
|
|
|
}
|
|
|
// 审批完成允许维护延期与进度备注
|
|
|
return form.value.projectPlanStatus === '3' || form.value.flowStatus === 'finish';
|
|
|
});
|
|
|
|
|
|
// 限制模式(审批完成,仅维护部分字段)
|
|
|
const isLimitedMode = computed(() => !isBasicEditable.value && canEditLimited.value);
|
|
|
|
|
|
// 按钮展示控制
|
|
|
const showApprovalButton = computed(() => {
|
|
|
if (isLimitedMode.value) {
|
|
|
return false;
|
|
|
}
|
|
|
if (routeParams.value.type === 'approval') {
|
|
|
return true;
|
|
|
}
|
|
|
return !isViewMode.value;
|
|
|
});
|
|
|
const showLimitedActions = computed(() => !isViewMode.value && isLimitedMode.value);
|
|
|
|
|
|
const showSubmitButtons = computed(() => {
|
|
|
if (approvalPageType.value === 'add') return true;
|
|
|
if (approvalPageType.value === 'update' && form.value.flowStatus && ['draft', 'cancel', 'back'].includes(form.value.flowStatus as string)) {
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
});
|
|
|
|
|
|
const showApprovalAction = computed(() => {
|
|
|
return approvalPageType.value === 'approval' && form.value.flowStatus === 'waiting';
|
|
|
});
|
|
|
|
|
|
const showProgressButton = computed(() => {
|
|
|
return !!form.value.projectPlanId && form.value.flowStatus !== 'draft';
|
|
|
});
|
|
|
|
|
|
const projectPlanFormRef = ref<ElFormInstance>();
|
|
|
const buttonLoading = ref(false);
|
|
|
const draftLoading = ref(false);
|
|
|
const submitLoading = ref(false);
|
|
|
const currentAction = ref<'draft' | 'submit' | null>(null);
|
|
|
const projectInfoList = ref<Partial<ProjectInfoVO>[]>([]);
|
|
|
const selectedProjectCode = ref<string>(''); // 展示选中项目的编号
|
|
|
const baseDataLoaded = ref(false);
|
|
|
|
|
|
// 项目计划变更记录相关
|
|
|
const projectChangeList = ref<any[]>([]); // 变更记录列表
|
|
|
const activeChangeTab = ref<string>('0'); // 当前活跃的变更标签
|
|
|
|
|
|
// 附件上传对话框状态
|
|
|
const type = ref(0);
|
|
|
const dialog = reactive<{ visible: boolean; title: string }>({
|
|
|
visible: false,
|
|
|
title: ''
|
|
|
});
|
|
|
const ossFileModel = ref<string | string[] | undefined>(undefined);
|
|
|
|
|
|
const createEmptyForm = (): ErpProjectPlanForm => ({
|
|
|
projectPlanId: undefined,
|
|
|
projectId: undefined,
|
|
|
managerId: undefined,
|
|
|
chargeId: undefined,
|
|
|
managerName: undefined,
|
|
|
chargeName: undefined,
|
|
|
managerDeptName: undefined,
|
|
|
projectPlanCode: undefined,
|
|
|
paymentMethod: undefined,
|
|
|
projectPlanStatus: '1',
|
|
|
flowStatus: 'draft',
|
|
|
contractId: undefined,
|
|
|
ossId: undefined,
|
|
|
remark: undefined,
|
|
|
planStageList: []
|
|
|
});
|
|
|
|
|
|
const form = ref<ErpProjectPlanForm>(createEmptyForm());
|
|
|
|
|
|
const rules = reactive({
|
|
|
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
|
|
|
managerId: [{ required: true, message: '请选择项目经理', trigger: 'change' }]
|
|
|
});
|
|
|
|
|
|
const notifyListRefresh = () => {
|
|
|
sessionStorage.setItem(refreshFlagKey, Date.now().toString());
|
|
|
};
|
|
|
|
|
|
const resetForm = () => {
|
|
|
form.value = createEmptyForm();
|
|
|
selectedProjectCode.value = '';
|
|
|
};
|
|
|
|
|
|
/** 获取项目列表 */
|
|
|
const getProjectInfoList = async () => {
|
|
|
const res = await getErpProjectInfoList({});
|
|
|
projectInfoList.value = (res.data || []) as Partial<ProjectInfoVO>[];
|
|
|
};
|
|
|
|
|
|
const initBaseData = async () => {
|
|
|
if (baseDataLoaded.value) {
|
|
|
return;
|
|
|
}
|
|
|
await getProjectInfoList();
|
|
|
baseDataLoaded.value = true;
|
|
|
};
|
|
|
|
|
|
/** 添加阶段 */
|
|
|
const handleAddStage = () => {
|
|
|
const newStage = {
|
|
|
planStageId: undefined,
|
|
|
projectId: form.value.projectId,
|
|
|
projectPlanId: form.value.projectPlanId,
|
|
|
projectPhases: undefined,
|
|
|
planStartTime: undefined,
|
|
|
planEndTime: undefined,
|
|
|
collectionStage: undefined,
|
|
|
repaymentRate: undefined,
|
|
|
repaymentAmount: undefined,
|
|
|
repaymentTime: undefined,
|
|
|
delayDay: undefined,
|
|
|
receivableDate: undefined,
|
|
|
reasonsExplanation: undefined,
|
|
|
scheduleRemark: undefined,
|
|
|
realStartTime: undefined,
|
|
|
realEndTime: undefined,
|
|
|
sortOrder: (form.value.planStageList?.length || 0) + 1
|
|
|
};
|
|
|
if (!form.value.planStageList) {
|
|
|
form.value.planStageList = [];
|
|
|
}
|
|
|
form.value.planStageList.push(newStage);
|
|
|
};
|
|
|
|
|
|
/** 删除阶段 */
|
|
|
const handleDeleteStage = (index: number) => {
|
|
|
form.value.planStageList?.splice(index, 1);
|
|
|
};
|
|
|
|
|
|
interface ProjectSyncOptions {
|
|
|
syncManagerAndCharge?: boolean;
|
|
|
syncPaymentMethod?: boolean;
|
|
|
}
|
|
|
|
|
|
/** 处理项目选择变化,自动填充项目经理、部门负责人、付款方式和项目编号 */
|
|
|
const handleProjectChange = (projectId?: string | number, { syncManagerAndCharge = true, syncPaymentMethod = true }: ProjectSyncOptions = {}) => {
|
|
|
const isEmptySelection = projectId === undefined || projectId === null || projectId === '';
|
|
|
if (isEmptySelection) {
|
|
|
if (syncManagerAndCharge) {
|
|
|
form.value.managerId = undefined;
|
|
|
form.value.chargeId = undefined;
|
|
|
}
|
|
|
if (syncPaymentMethod) {
|
|
|
form.value.paymentMethod = undefined;
|
|
|
}
|
|
|
selectedProjectCode.value = '';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const project = projectInfoList.value.find((item) => String(item.projectId) === String(projectId));
|
|
|
if (!project) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (syncManagerAndCharge) {
|
|
|
form.value.managerId = project.managerId ?? undefined;
|
|
|
form.value.chargeId = project.chargeId ?? undefined;
|
|
|
// 名称字段从后端项目信息连表数据带出
|
|
|
form.value.managerName = ((project as any).managerName as string) ?? '';
|
|
|
form.value.chargeName = ((project as any).chargeName as string) ?? '';
|
|
|
}
|
|
|
|
|
|
if (syncPaymentMethod) {
|
|
|
form.value.paymentMethod = (project.paymentMethod as string) ?? undefined;
|
|
|
}
|
|
|
|
|
|
// 项目编号与项目经理部门均从后端项目信息中带出
|
|
|
selectedProjectCode.value = (project.projectCode as string) || '';
|
|
|
// ProjectInfoVO中deptName来源于后端连表(ErpProjectInfoVo.deptName)
|
|
|
form.value.managerDeptName = ((project as any).deptName as string) ?? '';
|
|
|
};
|
|
|
|
|
|
/** 根据回款时间与延期天数生成应收款日期 */
|
|
|
const recalcReceivableDate = (row: any) => {
|
|
|
if (!row) return;
|
|
|
const time = row.repaymentTime;
|
|
|
const delay = Number(row.delayDay || 0);
|
|
|
if (!time) {
|
|
|
row.receivableDate = undefined;
|
|
|
return;
|
|
|
}
|
|
|
const base = new Date(time as string);
|
|
|
if (isNaN(base.getTime())) {
|
|
|
row.receivableDate = undefined;
|
|
|
return;
|
|
|
}
|
|
|
const target = new Date(base.getTime());
|
|
|
target.setDate(target.getDate() + delay);
|
|
|
// 使用系统已有的parseTime进行格式化,保持与全局一致
|
|
|
row.receivableDate = proxy?.parseTime ? proxy.parseTime(target, '{y}-{m}-{d}') : target.toISOString().slice(0, 10);
|
|
|
};
|
|
|
|
|
|
// 打开附件上传对话框(编辑模式)
|
|
|
const handleFile = () => {
|
|
|
type.value = 0;
|
|
|
dialog.visible = true;
|
|
|
dialog.title = '上传项目计划附件';
|
|
|
// 回显已有附件
|
|
|
ossFileModel.value = form.value.ossId as any;
|
|
|
};
|
|
|
|
|
|
// 查看模式下预览附件
|
|
|
const handlePreview = () => {
|
|
|
if (!form.value.ossId) {
|
|
|
proxy?.$modal.msgWarning('暂无附件可预览');
|
|
|
return;
|
|
|
}
|
|
|
type.value = 0;
|
|
|
dialog.visible = true;
|
|
|
dialog.title = '预览项目计划附件';
|
|
|
ossFileModel.value = form.value.ossId as any;
|
|
|
};
|
|
|
|
|
|
// 提交附件
|
|
|
const submitOss = () => {
|
|
|
form.value.ossId = ossFileModel.value as any;
|
|
|
dialog.visible = false;
|
|
|
proxy?.$modal.msgSuccess('附件已更新');
|
|
|
};
|
|
|
|
|
|
// 关闭附件对话框
|
|
|
const cancel = () => {
|
|
|
dialog.visible = false;
|
|
|
};
|
|
|
|
|
|
/** 提交表单 */
|
|
|
const submitForm = async (status = 'draft') => {
|
|
|
currentAction.value = status as 'draft' | 'submit';
|
|
|
if (currentAction.value === 'draft') {
|
|
|
draftLoading.value = true;
|
|
|
} else if (currentAction.value === 'submit') {
|
|
|
submitLoading.value = true;
|
|
|
}
|
|
|
|
|
|
// 权限校验:只有项目经理才能提交(超级管理员跳过校验)
|
|
|
if (!form.value.managerId) {
|
|
|
proxy?.$modal.msgError('请先选择项目');
|
|
|
resetActionLoading();
|
|
|
return;
|
|
|
}
|
|
|
// 超级管理员跳过权限校验
|
|
|
const isSuperAdmin = userStore.roles.includes('admin') || userStore.roles.includes('superadmin');
|
|
|
if (!isSuperAdmin && userStore.userId !== form.value.managerId) {
|
|
|
proxy?.$modal.msgError('只有项目经理才能提交项目计划');
|
|
|
resetActionLoading();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
projectPlanFormRef.value?.validate(async (valid: boolean) => {
|
|
|
if (valid) {
|
|
|
buttonLoading.value = true;
|
|
|
try {
|
|
|
// 更新阶段的projectId
|
|
|
if (form.value.planStageList) {
|
|
|
form.value.planStageList.forEach((stage) => {
|
|
|
stage.projectId = form.value.projectId;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 提交审批
|
|
|
if (status === 'submit') {
|
|
|
const project = projectInfoList.value.find((p) => p.projectId === form.value.projectId);
|
|
|
form.value.flowCode = FlowCodeEnum.PROJECT_PLAN_CODE; //OAPS
|
|
|
form.value.variables = {
|
|
|
projectId: form.value.projectId,
|
|
|
projectName: project?.projectName,
|
|
|
managerId: form.value.managerId
|
|
|
};
|
|
|
// 流程实例业务扩展字段:编码使用项目计划编号,标题使用项目名称
|
|
|
form.value.bizExt = {
|
|
|
businessTitle: project?.projectName,
|
|
|
businessCode: form.value.projectPlanCode
|
|
|
};
|
|
|
form.value.projectPlanStatus = '2';
|
|
|
form.value.flowStatus = 'waiting';
|
|
|
const res = await projectPlanSubmitAndFlowStart(form.value).finally(() => (buttonLoading.value = false));
|
|
|
form.value = res.data;
|
|
|
buttonLoading.value = false;
|
|
|
proxy?.$modal.msgSuccess('操作成功');
|
|
|
notifyListRefresh();
|
|
|
proxy?.$tab.closePage();
|
|
|
router.go(-1);
|
|
|
} else {
|
|
|
// 暂存
|
|
|
if (status === 'draft') {
|
|
|
form.value.projectPlanStatus = '1';
|
|
|
form.value.flowStatus = 'draft';
|
|
|
}
|
|
|
if (form.value.projectPlanId) {
|
|
|
await updateErpProjectPlan(form.value).finally(() => (buttonLoading.value = false));
|
|
|
} else {
|
|
|
await addErpProjectPlan(form.value).finally(() => (buttonLoading.value = false));
|
|
|
}
|
|
|
buttonLoading.value = false;
|
|
|
proxy?.$modal.msgSuccess('暂存成功');
|
|
|
notifyListRefresh();
|
|
|
proxy?.$tab.closePage();
|
|
|
router.go(-1);
|
|
|
}
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
resetActionLoading();
|
|
|
}
|
|
|
} else {
|
|
|
resetActionLoading();
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const handleSubmitAction = (type: 'draft' | 'submit') => {
|
|
|
if (draftLoading.value || submitLoading.value) return;
|
|
|
submitForm(type);
|
|
|
};
|
|
|
|
|
|
const resetActionLoading = () => {
|
|
|
if (currentAction.value === 'draft') {
|
|
|
draftLoading.value = false;
|
|
|
}
|
|
|
if (currentAction.value === 'submit') {
|
|
|
submitLoading.value = false;
|
|
|
}
|
|
|
currentAction.value = null;
|
|
|
};
|
|
|
|
|
|
/** 保存按钮 - 保存为草稿 */
|
|
|
const handleSave = () => {
|
|
|
if (isLimitedMode.value) {
|
|
|
saveLimited();
|
|
|
} else {
|
|
|
submitForm('draft');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/** 审批完成后的保存逻辑 */
|
|
|
const saveLimited = () => {
|
|
|
projectPlanFormRef.value?.validate(async (valid: boolean) => {
|
|
|
if (!valid) return;
|
|
|
buttonLoading.value = true;
|
|
|
try {
|
|
|
if (form.value.planStageList) {
|
|
|
form.value.planStageList.forEach((stage) => {
|
|
|
stage.projectId = form.value.projectId;
|
|
|
});
|
|
|
}
|
|
|
await updateErpProjectPlan(form.value);
|
|
|
proxy?.$modal.msgSuccess('保存成功');
|
|
|
notifyListRefresh();
|
|
|
proxy?.$tab.closePage();
|
|
|
router.go(-1);
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
/** 审批对话框 */
|
|
|
const approvalVerifyOpen = async () => {
|
|
|
await submitVerifyRef.value?.openDialog(routeParams.value.taskId);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取指定阶段在某个变更中的进度信息
|
|
|
* @param change 变更记录
|
|
|
* @param planStageId 项目阶段ID
|
|
|
*/
|
|
|
const getChangeProgressByStage = (change: any, planStageId: any) => {
|
|
|
if (!change.progressList || change.progressList.length === 0) {
|
|
|
return null;
|
|
|
}
|
|
|
return change.progressList.find((progress: any) => progress.planStageId === planStageId);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取对齐的进度变更数据
|
|
|
* 将项目阶段与变更进度对齐显示
|
|
|
*/
|
|
|
const getAlignedProgressData = (change: any) => {
|
|
|
if (!form.value.planStageList || form.value.planStageList.length === 0) {
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
// 构建变更进度的Map,key为planStageId
|
|
|
const progressMap = new Map();
|
|
|
if (change.progressList && change.progressList.length > 0) {
|
|
|
change.progressList.forEach((progress: any) => {
|
|
|
progressMap.set(progress.planStageId, progress);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 将项目阶段与对应的变更进度合并
|
|
|
return form.value.planStageList.map((stage: any) => ({
|
|
|
...stage,
|
|
|
changeProgress: progressMap.get(stage.planStageId)
|
|
|
}));
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 加载项目计划的变更记录
|
|
|
*/
|
|
|
const loadProjectChangeList = async () => {
|
|
|
if (!form.value.projectPlanId) {
|
|
|
projectChangeList.value = [];
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const res = await queryProjectChangeByProjectPlanId(form.value.projectPlanId);
|
|
|
projectChangeList.value = res.data || [];
|
|
|
activeChangeTab.value = '0'; // 重置为第一个标签
|
|
|
} catch (error) {
|
|
|
projectChangeList.value = [];
|
|
|
console.error('加载项目计划变更记录失败:', error);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const loadFormData = async () => {
|
|
|
await initBaseData();
|
|
|
|
|
|
syncRouteParams();
|
|
|
const projectPlanId = routeParams.value.projectPlanId;
|
|
|
draftLoading.value = false;
|
|
|
submitLoading.value = false;
|
|
|
buttonLoading.value = false;
|
|
|
|
|
|
if (projectPlanId && projectPlanId !== '0') {
|
|
|
const res = await getErpProjectPlan(projectPlanId as string);
|
|
|
form.value = res.data;
|
|
|
if (!form.value.flowStatus) {
|
|
|
form.value.flowStatus = 'draft';
|
|
|
}
|
|
|
if (!form.value.planStageList) {
|
|
|
form.value.planStageList = [];
|
|
|
}
|
|
|
if (form.value.projectId) {
|
|
|
handleProjectChange(form.value.projectId, {
|
|
|
syncManagerAndCharge: false,
|
|
|
syncPaymentMethod: false
|
|
|
});
|
|
|
}
|
|
|
// 在查看模式或审批完成状态下加载变更记录
|
|
|
if (isViewMode.value || form.value.projectPlanStatus === '3') {
|
|
|
await loadProjectChangeList();
|
|
|
}
|
|
|
} else {
|
|
|
resetForm();
|
|
|
form.value.flowStatus = 'draft';
|
|
|
projectChangeList.value = [];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/** 返回列表 */
|
|
|
const goBack = () => {
|
|
|
router.back();
|
|
|
};
|
|
|
|
|
|
/** 审批记录 */
|
|
|
const handleApprovalRecord = () => {
|
|
|
approvalRecordRef.value?.init(form.value.projectPlanId);
|
|
|
};
|
|
|
|
|
|
// 提交回调
|
|
|
const submitCallback = async () => {
|
|
|
notifyListRefresh();
|
|
|
await proxy?.$tab.closePage(route);
|
|
|
router.go(-1);
|
|
|
};
|
|
|
|
|
|
watch(
|
|
|
() => route.fullPath,
|
|
|
() => {
|
|
|
loadFormData();
|
|
|
}
|
|
|
);
|
|
|
|
|
|
onMounted(() => {
|
|
|
loadFormData();
|
|
|
});
|
|
|
|
|
|
onActivated(() => {
|
|
|
loadFormData();
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.el-divider {
|
|
|
margin: 20px 0;
|
|
|
}
|
|
|
|
|
|
// 变更列样式
|
|
|
.change-cell {
|
|
|
padding: 8px;
|
|
|
background-color: #fffacd;
|
|
|
border-radius: 4px;
|
|
|
font-size: 12px;
|
|
|
|
|
|
.change-item {
|
|
|
margin-bottom: 6px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
|
|
|
&:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
.label {
|
|
|
font-weight: 500;
|
|
|
color: #333;
|
|
|
margin-right: 8px;
|
|
|
min-width: 60px;
|
|
|
}
|
|
|
|
|
|
.value {
|
|
|
color: #666;
|
|
|
word-break: break-word;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.change-highlight {
|
|
|
display: inline-block;
|
|
|
padding: 2px 6px;
|
|
|
background-color: #fffacd;
|
|
|
border-radius: 3px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.info-item {
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
.label {
|
|
|
font-weight: 500;
|
|
|
color: #333;
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
|
|
|
.value {
|
|
|
color: #666;
|
|
|
}
|
|
|
}
|
|
|
</style>
|