You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

708 lines
27 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="p-2">
<el-card shadow="never" style="margin-top: 0">
<!-- 审批按钮组件 -->
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.projectId"
:status="form.flowStatus"
:pageType="routeParams.type"
:mode="false"
/>
</el-card>
<el-card shadow="never" style="margin-top: 0">
<el-form ref="projectInfoFormRef" :model="form" :disabled="routeParams.type === 'view' || routeParams.type === 'approval'" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="有无合同" prop="contractFlag">
<el-radio-group v-model="form.contractFlag">
<el-radio v-for="dict in contract_flag" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编号" prop="projectCode">
<el-input v-model="form.projectCode" placeholder="请输入项目编号">
<template #append>
<el-button type="primary" @click="generateProjectCode" :disabled="isCodeGenerated">生成项目编号 </el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="业务方向" prop="businessDirection">
<el-select v-model="form.businessDirection" placeholder="请选择业务方向">
<el-option v-for="dict in business_direction" :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="projectCategory">
<el-select v-model="form.projectCategory" placeholder="请选择项目类别" disabled>
<el-option v-for="dict in project_category" :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="spareFlag">
<el-radio-group v-model="form.spareFlag" disabled>
<el-radio v-for="dict in spare_flag" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目类型" prop="projectTypeId">
<el-cascader
v-model="form.projectTypeId"
:options="projectTypeOptions"
:props="{ value: 'projectTypeId', label: 'typeName', children: 'children', emitPath: false }"
placeholder="请选择项目类型"
style="width: 100%"
/>
</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" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门" prop="deptId">
<el-select v-model="form.deptId" placeholder="请选择部门">
<el-option v-for="item in deptInfoList" :key="item.deptId" :label="item.deptName" :value="item.deptId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目经理" prop="managerId">
<el-select v-model="form.managerId" placeholder="请选择项目经理" clearable filterable>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门负责人" prop="chargeId">
<el-select v-model="form.chargeId" placeholder="请选择部门负责人" clearable filterable>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分管副总" prop="deputyId">
<el-select v-model="form.deputyId" placeholder="请选择分管副总" clearable filterable>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="抄送人员" prop="peopleId">
<el-select v-model="form.peopleId" placeholder="请选择抄送人员" clearable filterable multiple>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="金额" prop="amount">
<el-input v-model="form.amount" placeholder="请输入金额">
<template #append>元</template>
</el-input>
</el-form-item>
</el-col>
<!-- <el-col :span="12">-->
<!-- <el-form-item label="项目状态" prop="projectStatus">-->
<!-- <el-radio-group v-model="form.projectStatus" disabled>-->
<!-- <el-radio v-for="dict in project_status" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 项目关联合同 -->
<el-card shadow="never" style="margin-top: 0">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<span class="card-title">项目关联合同</span>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAddContract" size="small" :disabled="routeParams.type === 'view' || routeParams.type === 'approval'"
>新增关联
</el-button>
</el-col>
</el-row>
</template>
<el-table v-loading="contractLoading" border :data="projectContractsList">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="合同编号" align="center" prop="contractCode" min-width="120" />
<el-table-column label="合同名称" align="center" prop="contractName" min-width="180" />
<el-table-column label="合同总价" align="center" prop="totalPrice" min-width="120">
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '-' }}
</template>
</el-table-column>
<el-table-column label="业务方向" align="center" prop="businessDirection" min-width="120">
<template #default="scope">
<dict-tag :options="business_direction" :value="scope.row.businessDirection" />
</template>
</el-table-column>
<el-table-column label="合同状态" align="center" prop="contractStatus" min-width="100">
<template #default="scope">
<dict-tag :options="contract_status" :value="scope.row.contractStatus" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="120" fixed="right" v-if="routeParams.type !== 'view'">
<template #default="scope">
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDeleteContract(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加合同关联对话框 -->
<el-dialog title="选择关联合同" v-model="contractDialog.visible" width="900px" append-to-body>
<el-form :model="contractQueryParams" :inline="true" label-width="100px">
<el-form-item label="合同编号">
<el-input
v-model="contractQueryParams.contractCode"
placeholder="请输入合同编号"
clearable
@keyup.enter="getContractList"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="合同名称">
<el-input
v-model="contractQueryParams.contractName"
placeholder="请输入合同名称"
clearable
@keyup.enter="getContractList"
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getContractList">搜索</el-button>
<el-button icon="Refresh" @click="resetContractQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
ref="contractTableRef"
v-loading="contractSelectLoading"
border
:data="contractList"
@selection-change="handleContractSelectionChange"
max-height="400"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="合同编号" align="center" prop="contractCode" min-width="120" />
<el-table-column label="合同名称" align="center" prop="contractName" min-width="180" show-overflow-tooltip />
<el-table-column label="合同总价" align="center" prop="totalPrice" min-width="120">
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '-' }}
</template>
</el-table-column>
<el-table-column label="业务方向" align="center" prop="businessDirection" min-width="120">
<template #default="scope">
<dict-tag :options="business_direction" :value="scope.row.businessDirection" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="contractTotal > 0"
:total="contractTotal"
v-model:page="contractQueryParams.pageNum"
v-model:limit="contractQueryParams.pageSize"
@pagination="getContractList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitContractRelation" :loading="contractButtonLoading"> </el-button>
<el-button @click="contractDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- 提交审批组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
</div>
</template>
<script setup name="ProjectInfoEdit" lang="ts">
import { getProjectInfo, addProjectInfo, updateProjectInfo } from '@/api/oa/erp/projectInfo';
import { projectSubmitAndFlowStart } from '@/api/oa/erp/projectInfo';
import { ProjectInfoForm } from '@/api/oa/erp/projectInfo/types';
import { getRuleGenerateCode } from '@/api/system/codeRule';
import { startWorkFlow } from '@/api/workflow/task';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { allListDept } from '@/api/system/dept';
import { listProjectType } from '@/api/oa/erp/projectType';
import { ProjectTypeVO } from '@/api/oa/erp/projectType/types';
import { listUser } from '@/api/system/user';
import { UserQuery } from '@/api/system/user/types';
import { CodeRuleEnum, FlowCodeEnum, ProjectCategoryEnum } from '@/enums/OAEnum';
import { listProjectContracts, addProjectContracts, delProjectContracts, getErpProjectContractsList } from '@/api/oa/erp/projectContracts';
import { ProjectContractsForm, ProjectContractsQuery } from '@/api/oa/erp/projectContracts/types';
import { listContractInfo } from '@/api/oa/erp/contractInfo';
import { ContractInfoVO, ContractInfoQuery } from '@/api/oa/erp/contractInfo/types';
import { getInfo } from '@/api/login';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
// 路由参数
const routeParams = ref<Record<string, any>>({});
const { business_direction, project_status, contract_flag, project_category, spare_flag, contract_status } = toRefs<any>(
proxy?.useDict('business_direction', 'project_status', 'contract_flag', 'project_category', 'spare_flag', 'contract_status')
);
const buttonLoading = ref(false);
const projectInfoFormRef = ref<ElFormInstance>();
// 审批相关组件引用
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
// 流程相关数据
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {},
bizExt: {}
});
// 任务变量
const taskVariables = ref<Record<string, any>>({});
const flowInstanceBizExtBo = ref<Record<string, any>>({});
// 项目编号生成状态
const isCodeGenerated = ref(false);
/** 查询部门信息下拉框结构 */
const deptInfoList = ref([]);
const getDeptInfoListSelect = async () => {
const params = { deptCategory: '03' } as any;
let res = await allListDept(params);
deptInfoList.value = res.data;
};
/** 查询用户列表 */
const userList = ref([]);
const getUserList = async () => {
const params: UserQuery = { pageNum: 1, pageSize: 9999 };
const res = await listUser(params);
userList.value = res.rows;
};
const projectTypeOptions = ref<ProjectTypeVO[]>([]);
// 保存原始的项目类型列表(扁平结构),用于查找 parentId
const projectTypeList = ref<ProjectTypeVO[]>([]);
/** 查询项目类型列表 */
const getProjectTypeList = async () => {
const params = { spareFlag: form.value.spareFlag } as any;
const res = await listProjectType(params);
// 保存原始数据用于查找
projectTypeList.value = res.data || [];
projectTypeOptions.value = proxy?.handleTree<ProjectTypeVO>(res.data, 'projectTypeId', 'parentId') || [];
};
type ProjectInfoFormEx = ProjectInfoForm & {
flowCode?: string;
variables?: any;
bizExt?: any;
};
const initFormData: ProjectInfoFormEx = {
projectId: undefined,
contractFlag: '1',
projectCode: undefined,
projectName: undefined,
businessDirection: undefined,
projectCategory: undefined,
spareFlag: undefined,
projectTypeId: undefined,
paymentMethod: undefined,
deptId: undefined,
managerId: undefined,
chargeId: undefined,
deputyId: undefined,
peopleId: undefined,
amount: undefined,
projectStatus: '1',
flowStatus: undefined,
sortOrder: 0,
contractId: undefined,
remark: undefined,
activeFlag: '1',
flowCode: undefined,
variables: undefined,
bizExt: undefined
};
const data = reactive<{ form: ProjectInfoFormEx; rules: any }>({
form: { ...initFormData },
rules: {
contractFlag: [{ required: true, message: '有无合同不能为空', trigger: 'change' }],
projectCode: [{ required: true, message: '项目编号不能为空', trigger: 'blur' }],
projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
businessDirection: [{ required: true, message: '业务方向不能为空', trigger: 'change' }],
projectCategory: [{ required: true, message: '项目类别不能为空', trigger: 'change' }]
}
});
const { form, rules } = toRefs(data);
// 生成项目编号
const generateProjectCode = async () => {
if (isCodeGenerated.value) return;
try {
const codeRuleCode = form.value.projectCategory === ProjectCategoryEnum.RD ? CodeRuleEnum.PROJECT_RD : CodeRuleEnum.PROJECT;
const params = { codeRuleCode: codeRuleCode } as any;
const res = await getRuleGenerateCode(params);
form.value.projectCode = res.msg;
isCodeGenerated.value = true;
proxy?.$modal.msgSuccess('项目编号生成成功');
} catch (error) {
console.error('生成项目编号失败:', error);
proxy?.$modal.msgError('生成项目编号失败');
}
};
/** 提交按钮 */
const submitForm = (status: string, mode: boolean) => {
try {
projectInfoFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 准备提交数据,将 peopleId 数组转换为字符串
const submitData = { ...form.value };
if (Array.isArray(submitData.peopleId)) {
submitData.peopleId = submitData.peopleId.join(',') as any;
}
// 设置后端发起和不等于草稿状态 直接走流程发起
if (status != 'draft') {
// 后端发起流程模式
submitData.flowCode = FlowCodeEnum.PROJECT_KEY;
// 根据 projectTypeId 查找对应的 parentId
let projectTypeParentId = undefined;
if (submitData.projectTypeId) {
const projectType = projectTypeList.value.find(
(item) => item.projectTypeId === submitData.projectTypeId
);
if (projectType) {
projectTypeParentId = projectType.parentId;
}
}
// 流程变量
submitData.variables = {
projectId: submitData.projectId,
projectCode: submitData.projectCode,
projectName: submitData.projectName,
contractFlag: Number(submitData.contractFlag),
projectTypeParentId: projectTypeParentId,
};
// 流程实例业务扩展字段
submitData.bizExt = {
businessTitle: '项目审批',
businessCode: submitData.projectCode
};
submitData.projectStatus = '2';
submitData.flowStatus = 'waiting';
const res = await projectSubmitAndFlowStart(submitData).finally(() => (buttonLoading.value = false));
form.value = res.data;
buttonLoading.value = false;
proxy?.$modal.msgSuccess('操作成功');
proxy?.$tab.closePage();
router.go(-1);
} else {
if (status === 'draft') {
submitData.projectStatus = '1';
submitData.flowStatus = 'draft';
}
if (submitData.projectId) {
await updateProjectInfo(submitData).finally(() => (buttonLoading.value = false));
} else {
await addProjectInfo(submitData).finally(() => (buttonLoading.value = false));
}
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy?.$tab.closePage();
router.go(-1);
}
}
});
} finally {
buttonLoading.value = false;
}
};
// 提交申请
// const handleStartWorkFlow = async (data: ProjectInfoForm) => {
// try {
// submitFormData.value.flowCode = 'OAP';
// submitFormData.value.businessId = data.projectId;
//
// // 流程变量
// taskVariables.value = {
// projectId: data.projectId,
// projectCode: data.projectCode,
// projectName: data.projectName,
// amount: data.amount,
// contractFlag: Number(data.contractFlag)
// };
//
// // 流程实例业务扩展字段
// flowInstanceBizExtBo.value = {
// businessTitle: '项目审批',
// businessCode: data.projectCode
// };
//
// submitFormData.value.variables = taskVariables.value;
// submitFormData.value.bizExt = flowInstanceBizExtBo.value;
//
// const resp = await startWorkFlow(submitFormData.value);
//
// if (submitVerifyRef.value) {
// buttonLoading.value = false;
// await submitVerifyRef.value.openDialog(resp.data.taskId);
// }
// } finally {
// buttonLoading.value = false;
// router.go(-1);
// }
// };
//初始化调用
const loadSelectOptions = () => {
getDeptInfoListSelect();
getUserList();
};
onMounted(async () => {
nextTick(async () => {
// 获取路由参数
routeParams.value = route.query;
proxy?.$modal.loading('正在加载数据,请稍后...');
loadSelectOptions();
const id = routeParams.value.id as string | number;
if (id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
const res = await getProjectInfo(id);
Object.assign(form.value, res.data);
// 将抄送人员字符串转换为数组,用于多选下拉框显示
if (form.value.peopleId && typeof form.value.peopleId === 'string') {
form.value.peopleId = (form.value.peopleId as string).split(',').map((id) => {
return String(id.trim());
}) as any;
}
// 编辑模式:如果已有项目编号,禁用生成按钮
if (form.value.projectCode) {
isCodeGenerated.value = true;
}
// 加载项目关联的合同列表
await getProjectContractsList();
} else {
// 新增模式:获取登录用户信息并自动赋值
try {
const userInfoRes = await getInfo();
const userVO = userInfoRes.data.user;
if (userInfoRes.data?.user) {
form.value.deptId = userVO.deptId;
form.value.managerId = userVO.userId;
form.value.chargeId = userVO.chargeId;
form.value.deputyId = userVO.deputyId;
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
// 新增模式:如果已有项目编号,禁用生成按钮
if (form.value.projectCode) {
isCodeGenerated.value = true;
}
// 新增模式:设置项目类别
if (routeParams.value.projectCategory) {
form.value.projectCategory = routeParams.value.projectCategory as string;
}
}
form.value.spareFlag = form.value.projectCategory === ProjectCategoryEnum.SALE_SPARE ? '1' : '0';
await getProjectTypeList();
proxy?.$modal.closeLoading();
});
});
// 审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.projectId);
};
// 提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(route);
router.go(-1);
};
// 审批
const approvalVerifyOpen = async () => {
await submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// ================== 项目关联合同相关 ==================
const contractLoading = ref(false);
const projectContractsList = ref<any[]>([]);
const contractDialog = reactive({ visible: false });
const contractTableRef = ref();
const contractSelectLoading = ref(false);
const contractList = ref<ContractInfoVO[]>([]);
const contractTotal = ref(0);
const selectedContracts = ref<ContractInfoVO[]>([]);
const contractButtonLoading = ref(false);
// 合同查询参数
const contractQueryParams = ref<ContractInfoQuery>({
pageNum: 1,
pageSize: 10,
contractStatus: '3' // 只查询可用状态的合同
});
/** 查询项目关联的合同列表 */
const getProjectContractsList = async () => {
if (!form.value.projectId) return;
try {
contractLoading.value = true;
const params = { projectId: form.value.projectId } as any;
const res = await getErpProjectContractsList(params);
// 后端已经通过left join返回了完整的合同信息
projectContractsList.value = res.data || [];
} catch (error) {
console.error('查询项目关联合同失败:', error);
} finally {
contractLoading.value = false;
}
};
/** 打开添加合同关联对话框 */
const handleAddContract = () => {
contractDialog.visible = true;
contractQueryParams.value.pageNum = 1;
getContractList();
};
/** 查询合同列表 */
const getContractList = async () => {
try {
contractSelectLoading.value = true;
const res = await listContractInfo(contractQueryParams.value);
// 过滤掉已经关联的合同
const existingContractIds = projectContractsList.value.map((item) => item.contractId);
contractList.value = res.rows.filter((contract) => !existingContractIds.includes(contract.contractId));
contractTotal.value = contractList.value.length;
} catch (error) {
console.error('查询合同列表失败:', error);
} finally {
contractSelectLoading.value = false;
}
};
/** 重置合同查询 */
const resetContractQuery = () => {
contractQueryParams.value.contractCode = undefined;
contractQueryParams.value.contractName = undefined;
contractQueryParams.value.pageNum = 1;
getContractList();
};
/** 合同多选框选中数据 */
const handleContractSelectionChange = (selection: ContractInfoVO[]) => {
selectedContracts.value = selection;
};
/** 提交合同关联 */
const submitContractRelation = async () => {
if (selectedContracts.value.length === 0) {
proxy?.$modal.msgWarning('请至少选择一个合同');
return;
}
try {
contractButtonLoading.value = true;
// 批量添加关联关系
const promises = selectedContracts.value.map((contract, index) => {
const data: ProjectContractsForm = {
projectId: form.value.projectId,
contractId: contract.contractId,
sortOrder: projectContractsList.value.length + index + 1,
activeFlag: '1'
};
return addProjectContracts(data);
});
await Promise.all(promises);
proxy?.$modal.msgSuccess('添加关联成功');
contractDialog.visible = false;
selectedContracts.value = [];
// 刷新列表
await getProjectContractsList();
} catch (error) {
console.error('添加合同关联失败:', error);
proxy?.$modal.msgError('添加关联失败');
} finally {
contractButtonLoading.value = false;
}
};
/** 删除合同关联 */
const handleDeleteContract = async (row: any) => {
try {
await proxy?.$modal.confirm(`是否确认删除与合同"${row.contractName}"的关联关系?`);
await delProjectContracts(row.projectContractsId);
proxy?.$modal.msgSuccess('删除关联成功');
// 刷新列表
await getProjectContractsList();
} catch (error) {
console.error('删除合同关联失败:', error);
}
};
</script>
<style scoped lang="scss">
.card-title {
font-weight: bold;
font-size: 16px;
color: #303133;
}
</style>