Files
school-developer/src/views/finance/purchasingrequisition/add.vue
2026-02-27 10:29:53 +08:00

1955 lines
90 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header v-if="isEditMode">
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
{{ pageTitle }}
</span>
</div>
</template>
<div v-loading="loading" style="padding-bottom: 20px;">
<div class="form-toolbar mb12" style="text-align: right;">
<el-button type="primary" link icon="QuestionFilled" @click="helpDialogVisible = true">
帮助
</el-button>
</div>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="150px"
:disabled="isViewMode || flowFormDisabled"
class="compact-form">
<!-- 基本信息三列紧凑 -->
<div class="form-section-compact">
<el-row :gutter="16">
<el-col :span="8" class="mb12">
<el-form-item label="采购项目名称" prop="projectName">
<el-input v-model="dataForm.projectName" placeholder="请输入采购项目名称" clearable :disabled="flowFieldDisabled('projectName')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="填报日期" prop="applyDate">
<el-date-picker v-model="dataForm.applyDate" type="date" placeholder="请选择填报日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" :disabled="flowFieldDisabled('applyDate')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="资金来源" prop="fundSource">
<el-select v-model="dataForm.fundSource" placeholder="请选择资金来源" clearable style="width: 100%" :disabled="flowFieldDisabled('fundSource')">
<el-option v-for="item in fundSourceList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="预算金额" prop="budget">
<div class="budget-yuan-wrap">
<el-input-number v-model="dataForm.budget" :min="0.01" :precision="2" placeholder="请输入金额" :controls="false" style="width: 100%" :disabled="flowFieldDisabled('budget')" />
<span class="budget-unit"></span>
</div>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="是否集采" prop="isCentralized">
<el-select v-model="dataForm.isCentralized" placeholder="请选择是否集采" clearable style="width: 100%" :disabled="flowFieldDisabled('isCentralized')">
<el-option v-for="item in isCentralizedList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="是否特殊情况" prop="isSpecial">
<el-select v-model="dataForm.isSpecial" placeholder="请选择是否特殊情况" clearable style="width: 100%" :disabled="flowFieldDisabled('isSpecial')">
<el-option v-for="item in isSpecialList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb12">
<el-form-item label="品目编码" prop="categoryCode">
<el-cascader v-model="categoryCodePath" :options="categoryTreeData" :props="{ value: 'code', label: 'name', children: 'children', checkStrictly: false }" placeholder="请选择品目编码(仅最后一级)" clearable filterable :show-all-levels="true" style="width: 100%" :disabled="flowFieldDisabled('categoryCode')" @change="handleCategoryChange" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 采购详情新增时需先填是否特殊情况是否集采预算金额后才显示 -->
<div>
<el-alert v-if="!showPurchaseDetailBlocks" type="info" :closable="false" class="mb16" show-icon>
请先填写上方是否特殊情况是否集采预算金额系统将根据填写结果展示部门自行采购学校统一采购表单
</el-alert>
<!-- 分支一部门自行采购三列紧凑textarea 单独一行 -->
<div class="mb20 form-section-compact" v-if="showPurchaseDetailBlocks && isDeptPurchase">
<div class="step-title mb12">部门自行采购</div>
<el-row :gutter="16">
<el-col :span="8" class="mb12">
<el-form-item label="采购途径" prop="purchaseChannel">
<el-select v-model="dataForm.purchaseChannel" placeholder="请选择采购途径" clearable :disabled="flowFieldDisabled('purchaseChannel')" style="width: 100%">
<el-option :label="'自行采购'" :value="PURCHASE_CHANNEL.SELF" />
<el-option :label="'委托采购中心采购'" :value="PURCHASE_CHANNEL.ENTRUST_CENTER" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="!isEntrustCenterChannel || (isFlowEmbed && isPurchaseCenter)">
<el-form-item label="采购方式" prop="purchaseType" required>
<el-select
v-model="dataForm.purchaseType"
placeholder="请选择采购方式"
clearable
:disabled="(isFlowEmbed && isPurchaseCenter) ? false : (isDeptSelfMallLocked || isAutoSelectPurchaseType)"
style="width: 100%">
<el-option
v-for="item in purchaseTypeDeptList"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
<div
v-if="isFlowEmbed && isPurchaseCenter && isEntrustCenterChannel && isAutoSelectPurchaseType"
class="template-note mt5"
>
<el-text type="info" size="small">服务网上商城</el-text>
</div>
</el-form-item>
</el-col>
<el-col :span="24" class="mb12">
<el-form-item label="采购内容" prop="projectContent">
<el-input v-model="dataForm.projectContent" type="textarea" :rows="3" :maxlength="1000" show-word-limit placeholder="请输入采购内容限制1000字" clearable :disabled="flowFieldDisabled('projectContent')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.BUSINESS_NEGOTIATION)">
<el-form-item label="商务洽谈表" prop="businessNegotiationTable" required>
<upload-file v-model="dataForm.businessNegotiationTable" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.businessNegotiationTable }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('businessNegotiationTable')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('business_negotiation')" style="margin-top: 8px; display: inline-block">下载商务洽谈表模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.MARKET_PURCHASE)">
<el-form-item label="市场采购纪要" prop="marketPurchaseMinutes" required>
<upload-file v-model="dataForm.marketPurchaseMinutes" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.marketPurchaseMinutes }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('marketPurchaseMinutes')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('market_purchase_minutes')" style="margin-top: 8px; display: inline-block">下载市场采购纪要模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.ONLINE_MALL)">
<el-form-item label="网上商城采购相关材料" prop="onlineMallMaterials" required>
<upload-file v-model="dataForm.onlineMallMaterials" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.onlineMallMaterials }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('onlineMallMaterials')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isInquiryPurchaseType">
<el-form-item label="询价模板" prop="inquiryTemplate" required>
<upload-file v-model="dataForm.inquiryTemplate" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.inquiryTemplate }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('inquiryTemplate')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('inquiry')" style="margin-top: 8px; display: inline-block">下载部门采购询价模版模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isEntrustCenterChannel && dataForm.entrustCenterType === 'service_online' && categoryCodePath && categoryCodePath[0] === 'C'">
<el-form-item label="是否有供应商" prop="hasSupplier">
<el-radio-group v-model="dataForm.hasSupplier" :disabled="flowFieldDisabled('hasSupplier')">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isEntrustCenterChannel && dataForm.entrustCenterType === 'service_online' && categoryCodePath && categoryCodePath[0] === 'C' && dataForm.hasSupplier === '1'">
<el-form-item label="需求文件" prop="serviceDirectSelect" required>
<upload-file v-model="dataForm.serviceDirectSelect" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.serviceDirectSelect }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('serviceDirectSelect')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('direct_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板直选模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isEntrustCenterChannel && dataForm.entrustCenterType === 'service_online' && categoryCodePath && categoryCodePath[0] === 'C' && dataForm.hasSupplier === '0'">
<el-form-item label="需求文件" prop="serviceInviteSelect" required>
<upload-file v-model="dataForm.serviceInviteSelect" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('serviceInviteSelect')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('public_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板公开比选模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isEntrustCenterChannel && dataForm.entrustCenterType === 'other' && categoryCodePath && categoryCodePath[0] === 'A'">
<el-form-item label="需求模板" prop="purchaseRequirementTemplate" required>
<upload-file v-model="dataForm.purchaseRequirementTemplate" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.purchaseRequirementTemplate }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('purchaseRequirementTemplate')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('purchase_requirement')" style="margin-top: 8px; display: inline-block">下载表1需求模板模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="showAutoInviteSelect">
<el-form-item label="是否有推荐供应商" prop="hasSupplier">
<el-radio-group v-model="dataForm.hasSupplier" :disabled="flowFieldDisabled('hasSupplier')">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- <el-col :span="8" class="mb12" v-if="showAutoInviteSelect && dataForm.hasSupplier === '1'">
<el-form-item label="推荐供应商" prop="suppliers" class="mb16">
<el-input v-model="dataForm.suppliers" placeholder="请输入三家供应商名称,用逗号分隔" clearable />
<div class="template-note mt5"><el-text type="info" size="small">请输入三家供应商名称用逗号分隔</el-text></div>
</el-form-item>
</el-col> -->
<el-col :span="8" class="mb12" v-if="showAutoInviteSelect && dataForm.hasSupplier === '1'">
<el-form-item label="服务商城项目需求模板(邀请比选)" prop="serviceInviteSelect" required>
<upload-file v-model="dataForm.serviceInviteSelect" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('serviceInviteSelect')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('invite_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板邀请比选模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="showAutoInviteSelect && dataForm.hasSupplier === '0'">
<el-form-item label="服务商城项目需求模板(公开比选)" prop="servicePublicSelectAuto" required>
<upload-file v-model="dataForm.servicePublicSelectAuto" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectAuto }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('servicePublicSelectAuto')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('public_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板公开比选模版</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="其他材料" prop="otherMaterials">
<upload-file v-model="dataForm.otherMaterials" :limit="1" :file-type="['zip']" :data="{ fileType: FILE_TYPE_MAP.otherMaterials }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('otherMaterials')" />
<div class="template-note">支持上传zip格式的压缩包文件</div>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 分支二学校统一采购三列紧凑 -->
<div class="mb20 form-section-compact" v-if="showPurchaseDetailBlocks && !isDeptPurchase">
<div class="step-title mb12">学校统一采购</div>
<el-row :gutter="16">
<el-col :span="8" class="mb12">
<el-form-item label="采购形式" prop="purchaseMode">
<el-radio-group v-model="dataForm.purchaseMode" :disabled="schoolUnifiedPurchaseFormDisabled">
<el-radio v-for="item in purchaseModeSchoolList" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
<!-- <div v-if="schoolUnifiedPurchaseFormDefault != null" class="template-note mt5"><el-text type="info" size="small">根据预算金额与是否集采由系统自动选择</el-text></div> -->
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isDeptPurchase || isFlowEmbed">
<el-form-item label="采购方式" prop="purchaseType" :required="!isDeptPurchase">
<el-select v-model="dataForm.purchaseType" placeholder="请选择采购方式" clearable :disabled="(isFlowEmbed && isPurchaseCenter) ? false : (isAutoSelectPurchaseTypeUnion || flowFieldDisabled('purchaseType'))" style="width: 100%">
<el-option v-for="item in purchaseTypeUnionList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="业务分管处室" prop="deptClassifyUserId" :required="!isDeptPurchase">
<el-select v-model="dataForm.deptClassifyUserId" placeholder="请选择业务分管处室" clearable filterable @change="handleBusinessDeptChange" style="width: 100%" :disabled="flowFieldDisabled('deptClassifyUserId')">
<el-option v-for="item in businessDeptList" :key="item.id" :label="item.deptName" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="分管校领导" prop="schoolLeaderUserId" :required="!isDeptPurchase">
<el-select v-model="dataForm.schoolLeaderUserId" placeholder="请选择分管校领导" clearable filterable @change="handleSchoolLeaderChange" style="width: 100%" :disabled="flowFieldDisabled('schoolLeaderUserId')">
<el-option v-for="item in schoolLeaderList" :key="item.id" :label="item.name" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="dataForm.budget && dataForm.budget >= BUDGET_FEASIBILITY_THRESHOLD">
<el-form-item label="项目可行性论证报告" prop="feasibilityReport" required>
<upload-file v-model="dataForm.feasibilityReport" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.feasibilityReport }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('feasibilityReport')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('feasibility_report')" style="margin-top: 8px; display: inline-block">下载项目可行性论证报告模板.doc</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="dataForm.budget && dataForm.budget >= BUDGET_FEASIBILITY_THRESHOLD && !isUrgentSpecial && !isSpecialType('2') && !isSpecialType('3')">
<el-form-item label="会议纪要" prop="meetingMinutes" required>
<upload-file v-model="dataForm.meetingMinutes" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.meetingMinutes }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('meetingMinutes')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isUrgentSpecial">
<el-form-item label="会议纪要" prop="meetingMinutesUrgent" required>
<upload-file v-model="dataForm.meetingMinutesUrgent" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.meetingMinutesUrgent }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('meetingMinutesUrgent')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isSpecialType('2')">
<el-form-item label="单一来源论专家证附件" prop="singleSourceProof" required>
<upload-file v-model="dataForm.singleSourceProof" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.singleSourceProof }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('singleSourceProof')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('single_source')" style="margin-top: 8px; display: inline-block">下载单一来源论专家证附件.docx</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isSpecialType('2')">
<el-form-item label="会议纪要" prop="meetingMinutesSingle" required>
<upload-file v-model="dataForm.meetingMinutesSingle" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.meetingMinutesSingle }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('meetingMinutesSingle')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isSpecialType('3')">
<el-form-item label="进口产品申请及专家论证意见表" prop="importApplication" required>
<upload-file v-model="dataForm.importApplication" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.importApplication }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('importApplication')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('import_application')" style="margin-top: 8px; display: inline-block">下载进口产品申请及专家论证意见表.doc</el-button>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isSpecialType('3')">
<el-form-item label="会议纪要" prop="meetingMinutesImport" required>
<upload-file v-model="dataForm.meetingMinutesImport" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.meetingMinutesImport }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('meetingMinutesImport')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="showAutoInviteSelectSchool">
<el-form-item label="是否有推荐供应商" prop="hasSupplier">
<el-radio-group v-model="dataForm.hasSupplier" :disabled="flowFieldDisabled('hasSupplier')">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- <el-col :span="8" class="mb12" v-if="showAutoInviteSelectSchool && dataForm.hasSupplier === '1'">
<el-form-item label="推荐供应商" prop="suppliers">
<el-input v-model="dataForm.suppliers" type="textarea" :rows="2" placeholder="请输入至少三家供应商名称,用逗号或分号分隔" clearable :disabled="flowFieldDisabled('suppliers')" />
<div class="template-note mt5"><el-text type="info" size="small">请输入至少三家供应商名称用逗号或分号分隔</el-text></div>
</el-form-item>
</el-col> -->
<el-col :span="8" class="mb12">
<el-form-item label="需求文件" :prop="getRequirementFileProp()" required class="mb16">
<template v-if="showAutoInviteSelectSchool">
<template v-if="dataForm.hasSupplier === '1'">
<upload-file v-model="dataForm.serviceInviteSelectSchool" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelectSchool }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('serviceInviteSelectSchool')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('invite_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板邀请比选模版</el-button>
</template>
<template v-else-if="dataForm.hasSupplier === '0'">
<upload-file v-model="dataForm.servicePublicSelectSchoolAuto" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchoolAuto }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('servicePublicSelectSchoolAuto')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('public_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板公开比选模版</el-button>
</template>
</template>
<template v-else-if="showAutoPublicSelect">
<upload-file v-model="dataForm.servicePublicSelectSchool" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchool }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('servicePublicSelectSchool')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('public_select')" style="margin-top: 8px; display: inline-block">下载服务商城项目需求模板公开比选模版</el-button>
</template>
<template v-else>
<upload-file v-model="dataForm.purchaseRequirement" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.purchaseRequirement }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('purchaseRequirement')" />
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('purchase_requirement')" style="margin-top: 8px; display: inline-block">下载需求模板模版</el-button>
</template>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="dataForm.budget && dataForm.budget >= BUDGET_GOV_PURCHASE_THRESHOLD">
<el-form-item label="政府采购意向申请表" prop="governmentPurchaseIntent" required>
<upload-file v-model="dataForm.governmentPurchaseIntent" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.governmentPurchaseIntent }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('governmentPurchaseIntent')" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb12">
<el-form-item label="其他材料" prop="otherMaterials">
<upload-file v-model="dataForm.otherMaterials" :limit="1" :file-type="['zip']" :data="{ fileType: FILE_TYPE_MAP.otherMaterials }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('otherMaterials')" />
<div class="template-note">支持上传zip格式的压缩包文件</div>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
<el-row :gutter="16">
<el-col :span="24" class="mb12">
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" type="textarea" :rows="2" placeholder="请输入备注" clearable :disabled="flowFieldDisabled('remark')" />
</el-form-item>
</el-col>
</el-row>
<!-- 查看时招标代理与实施采购信息同风格分割线+标签值同行 -->
<div v-if="isViewMode && dataForm.agentName" class="implement-info-block mb20">
<el-divider content-position="left">招标代理</el-divider>
<el-row :gutter="24">
<el-col :span="8" class="mb12">
<div class="view-label">代理名称</div>
<div class="view-value">{{ dataForm.agentName }}</div>
</el-col>
</el-row>
</div>
<!-- 帮助弹窗展示金额与规则说明 -->
<el-dialog
v-model="helpDialogVisible"
title="帮助 - 采购规则说明"
width="620px"
destroy-on-close
>
<el-alert
type="info"
:closable="false"
show-icon
class="mb12"
>
以下规则仅作为自动推荐与必填校验依据实际录入时可在允许范围内调整
</el-alert>
<el-scrollbar max-height="360px">
<ul class="help-rules-list">
<li>
<strong>部门自行采购 / 学校统一采购划分</strong>
是否特殊情况 = 是否集采 = 且预算金额 &lt; 5 {{ BUDGET_DEPT_PURCHASE_THRESHOLD }} 系统判定为部门自行采购否则为学校统一采购
</li>
<li>
<strong>学校统一采购 - 采购形式默认值</strong>
当预算金额 100 {{ BUDGET_GOV_PURCHASE_THRESHOLD }} 时默认政府采购
5 预算金额 &lt; 100 万时根据是否集采自动推荐政府采购学校自主采购
但申请人可以在界面上自行修改
</li>
<li>
<strong>部门自行采购 - 自动网上商城</strong>
部门自行采购且为服务类特殊品目时系统会自动将采购方式推荐为网上商城服务类同时要求上传相应模板文件
</li>
<li>
<strong>学校统一采购 - 自动邀请/公开比选模板</strong>
对服务类特殊品目
5 预算金额 &lt; 40 万时系统根据是否有推荐供应商在邀请比选 / 公开比选模板之间自动切换必填文件
40 预算金额 &lt; 100 万时系统默认要求使用公开比选模板
</li>
<li>
<strong>可行性论证与会议纪要学校统一采购</strong>
当预算金额 30 {{ BUDGET_FEASIBILITY_THRESHOLD }} 且不是紧急/单一/进口等特殊情况时
需要上传项目可行性论证报告会议纪要紧急单一来源进口等特殊情况对应有单独的证明材料与会议纪要要求
</li>
<li>
<strong>政府采购意向表</strong>
当预算金额 100 {{ BUDGET_GOV_PURCHASE_THRESHOLD }} 需要填写政府采购意向表并上传对应模板文件
</li>
<li>
<strong>委托采购中心采购</strong>
部门自行采购中选择委托采购中心采购作为采购途径时申请阶段隐藏采购方式由采购中心在审核环节选择
对服务类特殊品目且金额在 5 ~ 40 万区间时采购中心会优先推荐网上商城服务网上商城方式
</li>
</ul>
</el-scrollbar>
<template #footer>
<el-button type="primary" @click="helpDialogVisible = false">我知道了</el-button>
</template>
</el-dialog>
<!-- 查看时实施采购信息 -->
<div v-if="isViewMode && (dataForm.implementType || viewImplementPurchaseFiles.length)" class="implement-info-block mb20">
<el-divider content-position="left">实施采购信息</el-divider>
<el-row :gutter="24">
<el-col :span="8" class="mb12" v-if="dataForm.implementType">
<div class="view-label">实施采购方式</div>
<div class="view-value">{{ dataForm.implementType === '1' ? '自行组织采购' : dataForm.implementType === '2' ? '委托代理采购' : dataForm.implementType || '—' }}</div>
</el-col>
<el-col :span="8" class="mb12" v-if="dataForm.fileFlowInstId">
<div class="view-label">文件审批状态</div>
<div class="view-value">
<el-tag v-if="dataForm.fileFlowStatus === '-2'" type="info">撤回</el-tag>
<el-tag v-else-if="dataForm.fileFlowStatus === '-1'" type="warning">暂存</el-tag>
<el-tag v-else-if="dataForm.fileFlowStatus === '0'" type="primary">运行中</el-tag>
<el-tag v-else-if="dataForm.fileFlowStatus === '1'" type="success">完成</el-tag>
<el-tag v-else-if="dataForm.fileFlowStatus === '2'" type="danger">作废</el-tag>
<el-tag v-else-if="dataForm.fileFlowStatus === '3'" type="info">终止</el-tag>
<span v-else></span>
</div>
</el-col>
</el-row>
<div v-if="viewImplementPurchaseFiles.length" class="mb16">
<div class="view-label mb8">采购文件</div>
<el-table :data="viewImplementPurchaseFiles" border size="small" max-height="240">
<el-table-column type="index" label="版本" width="70" align="center">
<template #default="{ $index }">V{{ $index + 1 }}</template>
</el-table-column>
<el-table-column prop="fileTitle" label="文件名" min-width="180" show-overflow-tooltip />
<el-table-column label="上传时间" width="165" align="center">
<template #default="{ row }">{{ formatImplementFileTime(row.createTime) }}</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="downloadImplementFile(row)">下载</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-form>
<!-- 操作按钮流程嵌入时隐藏取消/返回由流程页处理 -->
<div class="form-footer">
<template v-if="isViewMode">
<el-button v-if="!isFlowEmbed" @click="handleCancel">返回</el-button>
</template>
<template v-else>
<el-button v-if="!isFlowEmbed" @click="handleCancel">取消</el-button>
<el-button
v-if="!isEditMode && !flowSubmitDisabled"
type="warning"
@click="handleTempStore"
:disabled="loading">
暂存
</el-button>
<!-- <el-button -->
<!-- type="primary" -->
<!-- @click="handleSubmit" -->
<!-- :disabled="loading">-->
<!-- {{ isEditMode ? '保存' : '提交' }}-->
<!-- </el-button>-->
</template>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="PurchasingRequisitionAdd">
import { reactive, ref, onMounted, computed, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { addObj, tempStore, getObj, editObj, getApplyFiles } from '/@/api/finance/purchasingrequisition';
import { getTree } from '/@/api/finance/purchasingcategory';
import { getDicts } from '/@/api/admin/dict';
import { useMessage } from '/@/hooks/message';
import UploadFile from '/@/components/Upload/index.vue';
import other from '/@/utils/other';
import { Document, Download, QuestionFilled } from '@element-plus/icons-vue';
import { fetchList as getBusinessDeptList } from '/@/api/purchase/purchasingBusinessDept';
import { getPage as getSchoolLeaderPage } from '/@/api/finance/purchasingschoolleader';
import { Session } from '/@/utils/storage';
import * as orderVue from '/@/api/order/order-key-vue';
/** 采购中心角色编码:审批时仅该角色可编辑采购方式/采购形式 */
const PURCHASE_CENTER_ROLE_CODE = 'PURCHASE_CENTER';
// 兼容流程 dynamic-link 引用:接收 currJob / currElTab并支持 handleJob 事件
const props = defineProps({
currJob: { type: Object, default: null },
currElTab: { type: Object, default: null }
});
const emit = defineEmits(['handleJob']);
// 路由
const router = useRouter();
const route = useRoute();
/** 是否被流程 handle 页通过 dynamic-link 嵌入 */
const isFlowEmbed = computed(() => !!props.currJob);
/** 当前使用的申请单 ID优先来自流程 currJob.orderId否则来自 route.query.id */
const effectiveQueryId = computed(() => {
if (props.currJob?.orderId != null && props.currJob?.orderId !== '') {
return String(props.currJob.orderId);
}
const q = route.query.id;
return q ? String(q) : '';
});
// 模式add | edit | viewURL 参数 或 流程嵌入时的 currJob/currElTab
const isEditMode = computed(() => {
if (isFlowEmbed.value && props.currElTab) {
return !!effectiveQueryId.value && props.currElTab.isFormEdit !== '0' && !props.currJob?.hiJob;
}
return String(route.query.mode) === 'edit';
});
const isViewMode = computed(() => {
if (isFlowEmbed.value && props.currJob) {
if (props.currJob.hiJob) return true;
if (props.currElTab?.isFormEdit === '0') return true;
return false;
}
return String(route.query.mode) === 'view';
});
const pageTitle = computed(() => {
if (isViewMode.value) return '查看采购申请';
if (isEditMode.value) return '编辑采购申请';
return '新增采购申请';
});
/** 流程嵌入时,由 currElTabIsView 控制的只读/禁用提交 */
const flowFormDisabled = ref(false);
const flowSubmitDisabled = ref(false);
/** 流程嵌入时当前节点是否为采购中心(仅采购中心可编辑采购方式/采购形式) */
const isPurchaseCenter = ref(false);
/** 流程嵌入时:采购中心审核节点放开所有字段编辑;非采购中心节点只读 */
function flowFieldDisabled(_key: string) {
if (isFlowEmbed.value && isPurchaseCenter.value) return false;
return !!isFlowEmbed.value;
}
// 定义变量内容
const formRef = ref();
const dataForm = reactive({
id: '',
projectName: '',
projectType: '',
projectContent: '',
applyDate: '',
fundSource: '',
budget: null as number | null,
isCentralized: '',
isSpecial: '',
purchaseMode: '',
purchaseType: '',
purchaseTypeUnion: '',
purchaseChannel: '',
categoryCode: '',
remark: '',
status: '',
// 部门自行采购字段
businessNegotiationTable: '',
marketPurchaseMinutes: '',
onlineMallMaterials: '',
inquiryTemplate: '', // 询价模板
entrustCenterType: '',
hasSupplier: '0',
suppliers: '', // 供应商名称(逗号或分号分隔)
serviceDirectSelect: '',
servicePublicSelect: '',
purchaseRequirementTemplate: '',
serviceInviteSelect: '',
servicePublicSelectAuto: '',
// 学校统一采购字段
purchaseRequirement: '',
meetingMinutes: '',
feasibilityReport: '',
meetingMinutesUrgent: '',
meetingMinutesSingle: '',
meetingMinutesImport: '',
singleSourceProof: '',
importApplication: '',
governmentPurchaseIntent: '',
servicePublicSelectSchool: '',
// 学校统一采购特殊规则字段5万<=金额<40万
serviceInviteSelectSchool: '',
servicePublicSelectSchoolAuto: '',
// 业务分管处室和分管校领导
deptClassifyUserId: '',
deptClassifyName: '',
schoolLeaderUserId: '',
schoolLeaderName: '',
// 其他材料zip压缩包
otherMaterials: '',
// 实施采购信息(查看时展示)
implementType: '',
fileFlowInstId: '',
fileFlowStatus: '',
agentId: '',
agentName: '',
});
/** 查看时展示的采购文件列表(实施采购上传的 type=130 */
const viewImplementPurchaseFiles = ref<{ id: string; fileTitle?: string; createTime?: string; remark?: string }[]>([]);
const categoryTreeData = ref<any[]>([]);
const categoryCodePath = ref<string[]>([]); // 级联选择器的路径数组
const fundSourceList = ref<any[]>([]);
const isCentralizedList = ref<any[]>([]);
const isSpecialList = ref<any[]>([]);
const purchaseTypeDeptList = ref<any[]>([]);
const purchaseModeSchoolList = ref<any[]>([]);
const purchaseTypeUnionList = ref<any[]>([]);
const businessDeptList = ref<any[]>([]);
const schoolLeaderList = ref<any[]>([]);
const loading = ref(false);
const helpDialogVisible = ref(false);
// 文件类型映射(对应数据库 file_type 字段)
// 10:商务洽谈纪要 20:市场采购纪要 30:网上商城采购相关材料 40:可行性论证报告 50:会议记录 60:其他材料 70:单一来源专家论证表 90:进口产品专家论证表 100:政府采购意向表 110:履约验收单 120:采购需求表 130:采购文件
const FILE_TYPE_MAP: Record<string, string> = {
businessNegotiationTable: '10', // 商务洽谈纪要
marketPurchaseMinutes: '20', // 市场采购纪要
inquiryTemplate: '20', // 询价模板(归类到市场采购纪要)
onlineMallMaterials: '30', // 网上商城采购相关材料
feasibilityReport: '40', // 可行性论证报告
meetingMinutes: '50', // 会议记录
meetingMinutesUrgent: '50', // 会议记录
meetingMinutesSingle: '50', // 会议记录
meetingMinutesImport: '50', // 会议记录
otherMaterials: '60', // 其他材料
singleSourceProof: '70', // 单一来源专家论证表
importApplication: '90', // 进口产品申请表
governmentPurchaseIntent: '100', // 政府采购意向表
// 需求文件相关 - 所有需求模板都应该是120采购需求表
serviceDirectSelect: '120', // 服务商城项目需求模板(直选)- 采购需求表
serviceInviteSelect: '120', // 服务商城项目需求模板(邀请比选)- 采购需求表
servicePublicSelect: '120', // 服务商城项目需求模板(公开比选)- 采购需求表
servicePublicSelectAuto: '120', // 服务商城项目需求模板(公开比选-自动)- 采购需求表
purchaseRequirementTemplate: '120', // 需求模板 - 采购需求表
purchaseRequirement: '120', // 需求模板 - 采购需求表
serviceInviteSelectSchool: '120', // 服务商城项目需求模板(邀请比选-学校)- 采购需求表
servicePublicSelectSchoolAuto: '120', // 服务商城项目需求模板(公开比选-学校-自动)- 采购需求表
servicePublicSelectSchool: '120', // 服务商城项目需求模板(公开比选-学校)- 采购需求表
};
// fileType -> 表单字段名数组(顺序与回填分配一致,同类型多字段时按此顺序分配)
const FILE_TYPE_TO_FIELDS: Record<string, string[]> = {};
Object.entries(FILE_TYPE_MAP).forEach(([field, type]) => {
if (!FILE_TYPE_TO_FIELDS[type]) FILE_TYPE_TO_FIELDS[type] = [];
FILE_TYPE_TO_FIELDS[type].push(field);
});
// 金额阈值常量(与后端 PurchaseConstants 保持一致)
const BUDGET_DEPT_PURCHASE_THRESHOLD = 50000; // 部门自行采购上限(< 5 万)
const BUDGET_FEASIBILITY_THRESHOLD = 300000; // 可行性论证/会议纪要起点(≥ 30 万)
const BUDGET_PUBLIC_SELECT_THRESHOLD = 400000; // 公开比选起点(≥ 40 万)
const BUDGET_GOV_PURCHASE_THRESHOLD = 1000000; // 政府采购起点(≥ 100 万)
// 部门采购方式字典 value与 DeptPurchaseTypeEnum 一致)
const DEPT_PURCHASE_TYPE = {
ONLINE_MALL: '1',
MARKET_PURCHASE: '2',
BUSINESS_NEGOTIATION: '3',
ENTRUST_CENTER: '4',
INQUIRY: '5',
} as const;
// 学校统一采购方式字典 value与 PurchaseTypeEnum 一致)
const UNION_PURCHASE_TYPE = {
ONLINE_MALL: '8',
} as const;
// 辅助函数:判断当前采购方式是否为指定类型(通过字典 value 匹配)
const isPurchaseType = (dictValue: string) => {
if (!dataForm.purchaseType) return false;
return dataForm.purchaseType === dictValue;
};
// 辅助函数:判断当前采购方式是否为"询价"(通过 value 匹配)
const isInquiryPurchaseType = computed(() => isPurchaseType(DEPT_PURCHASE_TYPE.INQUIRY));
// 辅助函数:判断特殊情况是否为指定类型(通过字典 value 匹配)
const isSpecialType = (dictValue: string) => {
if (!dataForm.isSpecial) return false;
const item = isSpecialList.value.find(item => item.value === dictValue);
return item ? dataForm.isSpecial === item.value : dataForm.isSpecial === dictValue;
};
// 采购途径(与后端 purchasing_apply.purchase_channel 一致1 自行采购 2 委托采购中心采购)
const PURCHASE_CHANNEL = {
SELF: '1',
ENTRUST_CENTER: '2',
} as const;
// 判断是否为部门自行采购
// 条件:特殊情况=否 且 集采=否 且 预算金额<5万 → 部门自行采购
// 其他情况 → 学校统一采购
const isDeptPurchase = computed(() => {
const isSpecialNo = isSpecialList.value.find(item => item.value === '0');
const isCentralizedNo = isCentralizedList.value.find(item => item.value === '0');
const isSpecialNoValue = isSpecialNo ? isSpecialNo.value : null;
const isCentralizedNoValue = isCentralizedNo ? isCentralizedNo.value : null;
return !!(isSpecialNoValue && isCentralizedNoValue &&
dataForm.isSpecial === isSpecialNoValue &&
dataForm.isCentralized === isCentralizedNoValue &&
dataForm.budget && dataForm.budget < 50000);
});
// 是否为“委托采购中心采购”途径
const isEntrustCenterChannel = computed(() => dataForm.purchaseChannel === PURCHASE_CHANNEL.ENTRUST_CENTER);
// 是否已填入“是否特殊情况”“是否集采”“预算金额”,从而能明确是部门自行采购还是学校统一采购(新增时先隐藏采购详情,填完后再显示)
const isPurchaseTypeDetermined = computed(() => {
return dataForm.isSpecial !== '' && dataForm.isCentralized !== '' &&
dataForm.budget != null && Number(dataForm.budget) > 0;
});
// 是否显示“部门自行采购/学校统一采购”区块:查看/编辑/流程嵌入/已保存过 时直接显示;新增时仅当类型已明确后显示
const showPurchaseDetailBlocks = computed(() => {
return !!isViewMode.value || !!isFlowEmbed.value || !!(dataForm.id) || isPurchaseTypeDetermined.value;
});
// 学校统一采购时采购形式默认值(不由用户选择):>=100万→政府采购('1')5万~100万且集采=否→学校自主('2')5万~100万且集采=政府集中采购→政府采购('1')5万~100万且集采=学校集中采购→学校自主('2')
const schoolUnifiedPurchaseFormDefault = computed(() => {
if (isDeptPurchase.value || dataForm.budget == null) return null;
const budget = Number(dataForm.budget);
if (budget >= BUDGET_GOV_PURCHASE_THRESHOLD) return '1'; // 政府采购
if (budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_GOV_PURCHASE_THRESHOLD) {
if (dataForm.isCentralized === '0') return '2'; // 集采=否 → 学校自主采购
if (dataForm.isCentralized === '1') return '1'; // 政府集中采购 → 政府采购
if (dataForm.isCentralized === '2') return '2'; // 学校集中采购 → 学校自主采购
}
return null;
});
// 学校统一采购时采购形式是否禁用
// 申请阶段:始终可选(根据默认值自动选中后,允许用户自行修改)
// 流程嵌入:采购中心节点可编辑,其他节点只读
const schoolUnifiedPurchaseFormDisabled = computed(() => {
if (!isFlowEmbed.value) {
return false;
}
// 流程嵌入且为采购中心:放开编辑
if (isPurchaseCenter.value) {
return false;
}
// 其他流程节点只读
return true;
});
// 特殊情况字典 value0否 1紧急 2单一 3进口
const isUrgentSpecial = computed(() => dataForm.isSpecial === '1');
// 第二步标题
const stepTwoTitle = computed(() => {
return isDeptPurchase.value ? '部门自行采购' : '学校统一采购';
});
// 根据 code 查找完整路径(用于回显)
const findCategoryPath = (data: any[], targetCode: string, path: string[] = []): string[] | null => {
for (const item of data) {
const currentPath = [...path, item.code];
if (item.code === targetCode) {
return currentPath;
}
if (item.children && item.children.length > 0) {
const found = findCategoryPath(item.children, targetCode, currentPath);
if (found) return found;
}
}
return null;
};
// 级联选择器变化处理
const handleCategoryChange = (value: string[]) => {
if (value && value.length > 0) {
// 取最后一个值作为选中的 code
dataForm.categoryCode = value[value.length - 1];
} else {
dataForm.categoryCode = '';
}
};
// 从品目编码中获取项目类型和属性
const getCategoryInfo = () => {
if (!dataForm.categoryCode || categoryTreeData.value.length === 0) {
return null;
}
const findCategory = (data: any[], code: string): any => {
for (const item of data) {
if (item.code === code) {
return item;
}
if (item.children && item.children.length > 0) {
const found = findCategory(item.children, code);
if (found) return found;
}
}
return null;
};
return findCategory(categoryTreeData.value, dataForm.categoryCode);
};
// 判断是否为服务类
const isServiceCategory = computed(() => {
// 通过 categoryCodePath 判断:第一个字符为 'C' 表示服务类
if (categoryCodePath.value && categoryCodePath.value.length > 0) {
return categoryCodePath.value[0] === 'C';
}
// 备用判断:通过 category 对象判断
const category = getCategoryInfo();
if (!category) return false;
return category.type === 'C' ;
});
// 判断是否为货物类
const isGoodsCategory = computed(() => {
const category = getCategoryInfo();
if (!category) return false;
return category.type === 'A';
});
// 判断是否为特殊服务类目isMallService=1、isProjectService=1
const isSpecialServiceCategory = computed(() => {
const category = getCategoryInfo();
if (!category) return false;
return Number(category.isMallService) === 1 || Number(category.isProjectService) === 1;
});
// 部门自行采购 & 采购途径=自行采购 & 特殊服务类目 → 采购方式固定网上商城
const isDeptSelfMallLocked = computed(() => {
return isDeptPurchase.value && !isEntrustCenterChannel.value && isSpecialServiceCategory.value;
});
// 委托采购中心方式自动判断:
// - 服务类:若末级节点 isMallService=0 且 isMallProject=0则选“其他方式”否则选“服务类网上商城”
// - 非服务类:默认选“其他方式”
const calcEntrustCenterType = (): 'service_online' | 'other' | '' => {
if (!isEntrustCenterChannel.value) return '';
if (!dataForm.categoryCode) return '';
const category = getCategoryInfo();
if (!category) return '';
// 兼容字段:接口可能为 isMallProject也可能历史字段为 isProjectService
const mallService = Number(category.isMallService ?? 0);
const mallProject = Number(category.isMallProject ?? category.isProjectService ?? 0);
if (isServiceCategory.value) {
return mallService === 0 && mallProject === 0 ? 'other' : 'service_online';
}
return 'other';
};
// 监听品目/采购途径变化,自动设置委托采购中心方式,并清理无关字段
watch(
[() => dataForm.purchaseChannel, () => dataForm.categoryCode, () => categoryTreeData.value],
() => {
const nextType = calcEntrustCenterType();
if (!nextType) return;
const prevType = dataForm.entrustCenterType as any;
if (prevType === nextType) return;
dataForm.entrustCenterType = nextType;
// 切换时清理不相关字段,避免脏数据
if (nextType === 'other') {
dataForm.hasSupplier = '0';
dataForm.suppliers = '';
dataForm.serviceDirectSelect = '';
dataForm.serviceInviteSelect = '';
dataForm.servicePublicSelect = '';
dataForm.servicePublicSelectAuto = '';
} else if (nextType === 'service_online') {
dataForm.purchaseRequirementTemplate = '';
}
},
{ immediate: true }
);
// 判断是否自动选择网上商城采购方式5万<=金额<40万服务类目特殊服务类目
const isAutoSelectPurchaseType = computed(() => {
if (!dataForm.budget) return false;
const budget = dataForm.budget;
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isServiceCategory.value && isSpecialServiceCategory.value;
});
// 判断是否显示自动邀请比选模版5万<=金额<40万服务类目特殊服务类目
const showAutoInviteSelect = computed(() => {
if (!isDeptPurchase.value) return false;
if (!dataForm.budget) return false;
const budget = dataForm.budget;
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isServiceCategory.value && isSpecialServiceCategory.value;
});
// 判断是否显示学校统一采购的自动邀请比选模版5万<=金额<40万服务类目特殊服务类目
const showAutoInviteSelectSchool = computed(() => {
if (isDeptPurchase.value) return false;
if (!dataForm.budget) return false;
const budget = dataForm.budget;
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isSpecialServiceCategory.value;
});
// 判断是否显示自动公开比选模版40万<=金额<100万特殊服务类目isMallService=1、isProjectService=1
const showAutoPublicSelect = computed(() => {
if (isDeptPurchase.value) return false;
if (!dataForm.budget) return false;
const budget = dataForm.budget;
return budget >= BUDGET_PUBLIC_SELECT_THRESHOLD && budget < BUDGET_GOV_PURCHASE_THRESHOLD && isSpecialServiceCategory.value;
});
// 获取需求文件的 prop 名称(用于表单验证)
const getRequirementFileProp = () => {
if (showAutoInviteSelectSchool.value) {
if (dataForm.hasSupplier === '1') {
return 'serviceInviteSelectSchool';
} else if (dataForm.hasSupplier === '0') {
return 'servicePublicSelectSchoolAuto';
}
} else if (showAutoPublicSelect.value) {
return 'servicePublicSelectSchool';
}
return 'purchaseRequirement';
};
// 判断学校统一采购是否需要自动设置采购方式5万<=金额<40万服务类目特殊服务类目
const isAutoSelectPurchaseTypeUnion = computed(() => {
if (isDeptPurchase.value) return false;
if (!dataForm.budget) return false;
const budget = dataForm.budget;
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isSpecialServiceCategory.value;
});
// 监听品目编码、预算金额、采购类型及采购途径变化,自动设置/清空采购方式
watch(
[() => dataForm.categoryCode, () => dataForm.budget, () => isDeptPurchase.value, () => isFlowEmbed.value, () => dataForm.purchaseChannel, () => isPurchaseCenter.value],
() => {
// 学校统一采购申请阶段:采购方式隐藏,由审批环节采购中心补充,此处不自动写入且清空已有值
if (!isDeptPurchase.value && !isFlowEmbed.value) {
dataForm.purchaseType = '';
return;
}
// 部门自行采购 & 采购途径为“委托采购中心采购”且为申请阶段:采购方式隐藏且不设置
if (isDeptPurchase.value && isEntrustCenterChannel.value && !isFlowEmbed.value) {
dataForm.purchaseType = '';
return;
}
// 部门自行采购 & 采购途径为“自行采购” & 特殊服务类目:固定网上商城(无论金额区间)
if (isDeptSelfMallLocked.value) {
const onlineMallOption = purchaseTypeDeptList.value.find(item => item.value === DEPT_PURCHASE_TYPE.ONLINE_MALL);
if (onlineMallOption && dataForm.purchaseType !== onlineMallOption.value) {
dataForm.purchaseType = onlineMallOption.value;
}
// 已锁定网上商城,不再走后续自动推荐逻辑
return;
}
// 其他部门自行采购 & 采购途径为“自行采购”:在金额区间内自动推荐网上商城
if (isAutoSelectPurchaseType.value && isDeptPurchase.value && !isEntrustCenterChannel.value) {
const onlineMallOption = purchaseTypeDeptList.value.find(item => item.value === DEPT_PURCHASE_TYPE.ONLINE_MALL);
if (onlineMallOption && dataForm.purchaseType !== onlineMallOption.value) {
dataForm.purchaseType = onlineMallOption.value;
}
}
// 部门自行采购 & 采购途径为“委托采购中心采购” & 采购中心审批节点:根据特殊服务类目自动设置网上商城
if (isAutoSelectPurchaseType.value && isDeptPurchase.value && isEntrustCenterChannel.value && isFlowEmbed.value && isPurchaseCenter.value) {
const onlineMallOption = purchaseTypeDeptList.value.find(item => item.value === DEPT_PURCHASE_TYPE.ONLINE_MALL);
if (onlineMallOption && dataForm.purchaseType !== onlineMallOption.value) {
dataForm.purchaseType = onlineMallOption.value;
}
}
// 学校统一采购审批阶段:自动设置网上商城采购方式
if (isAutoSelectPurchaseTypeUnion.value && !isDeptPurchase.value && isFlowEmbed.value) {
const onlineMallOption = purchaseTypeUnionList.value.find(item => item.value === UNION_PURCHASE_TYPE.ONLINE_MALL);
if (onlineMallOption && dataForm.purchaseType !== onlineMallOption.value) {
dataForm.purchaseType = onlineMallOption.value;
}
}
},
{ immediate: true });
// 学校统一采购:采购形式由规则默认选择,随预算与集采变化自动更新
watch([() => schoolUnifiedPurchaseFormDefault.value, () => isDeptPurchase.value], () => {
if (isDeptPurchase.value) return;
const def = schoolUnifiedPurchaseFormDefault.value;
if (def != null && dataForm.purchaseMode !== def) {
dataForm.purchaseMode = def;
}
}, { immediate: true });
// 下载模版:统一走后端接口,按原始文件下载(避免前端静态资源被当成 HTML 返回)
const downloadTemplate = async (type: string) => {
const templateMap: Record<string, { displayName: string }> = {
business_negotiation: { displayName: '商务洽谈表.doc' },
market_purchase_minutes: { displayName: '部门自行采购市场采购纪要.doc' },
inquiry: { displayName: '部门采购询价模版.doc' },
direct_select: { displayName: '服务商城项目需求模板(直选).doc' },
public_select: { displayName: '服务商城项目需求模板(公开比选).doc' },
invite_select: { displayName: '服务商城项目需求模板(邀请比选).doc' },
purchase_requirement: { displayName: '采购需求填报模板.doc' },
import_application: { displayName: '进口产品申请及专家论证意见表.doc' },
single_source: { displayName: '单一来源论专家证附件.docx' },
feasibility_report: { displayName: '项目可行性论证报告模板.doc' },
};
const template = templateMap[type];
if (!template) {
useMessage().error('模版不存在');
return;
}
try {
await other.downBlobFile(
`/purchase/purchasingtemplate/download?type=${encodeURIComponent(type)}`,
{},
template.displayName,
);
useMessage().success('模版下载成功');
} catch (err) {
useMessage().error('模版下载失败,请联系管理员维护模版文件');
}
};
const dataRules = reactive({
projectName: [
{ required: true, message: '采购项目名称不能为空', trigger: 'blur' }
],
applyDate: [
{ required: true, message: '填报日期不能为空', trigger: 'change' }
],
fundSource: [
{ required: true, message: '资金来源不能为空', trigger: 'change' }
],
budget: [
{ required: true, message: '预算金额不能为空', trigger: 'blur' },
{ type: 'number', min: 0.01, message: '预算金额必须大于0.01', trigger: 'blur' }
],
isCentralized: [
{ required: true, message: '请选择是否集采', trigger: 'change' }
],
isSpecial: [
{ required: true, message: '请选择是否特殊情况', trigger: 'change' }
],
categoryCode: [
{ required: true, message: '品目编码不能为空', trigger: 'change' }
],
projectContent: [
{ required: true, message: '采购内容不能为空', trigger: 'blur' },
{ max: 1000, message: '采购内容不能超过1000字', trigger: 'blur' }
],
purchaseChannel: [
{
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
if (!isDeptPurchase.value) {
callback();
return;
}
if (!value || String(value).trim() === '') {
callback(new Error('请选择采购途径'));
return;
}
callback();
},
trigger: 'change',
},
],
purchaseType: [
{
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
// 部门自行采购且采购途径为“委托采购中心采购”并且为申请阶段:此处不校验采购方式
if (isDeptPurchase.value && isEntrustCenterChannel.value && !isFlowEmbed.value) {
callback();
return;
}
if (!value || String(value).trim() === '') {
callback(new Error('请选择采购方式'));
return;
}
callback();
},
trigger: 'change',
},
],
// 学校统一采购时必填
deptClassifyUserId: [
{
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
if (!isDeptPurchase.value) {
if (!value || String(value).trim() === '') {
callback(new Error('请选择业务分管处室'));
return;
}
}
callback();
},
trigger: 'change'
}
],
schoolLeaderUserId: [
{
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
if (!isDeptPurchase.value) {
if (!value || String(value).trim() === '') {
callback(new Error('请选择分管校领导'));
return;
}
}
callback();
},
trigger: 'change'
}
],
});
// 取消
const handleCancel = () => {
// 流程嵌入时由流程页处理返回,不 postMessage
if (isFlowEmbed.value) {
return;
}
if (window.parent !== window) {
window.parent.postMessage({
type: 'purchasingrequisition:close'
}, '*');
} else {
router.back();
}
};
/** 根据申请单 ID 加载详情并回填表单(供 URL 与流程嵌入共用) */
async function loadDetail(applyId: string | number) {
if (!applyId) return;
try {
const res = await getObj(Number(applyId));
const detail = res?.data;
if (detail && typeof detail === 'object') {
Object.assign(dataForm, {
id: detail.id ?? dataForm.id,
projectName: detail.projectName ?? '',
projectType: detail.projectType ?? '',
projectContent: detail.projectContent ?? '',
applyDate: detail.applyDate ?? '',
fundSource: detail.fundSource ?? '',
budget: detail.budget != null ? Number(detail.budget) : null,
isCentralized: detail.isCentralized != null ? String(detail.isCentralized) : '',
isSpecial: detail.isSpecial != null ? String(detail.isSpecial) : '',
purchaseMode: detail.purchaseMode ?? '',
purchaseType: detail.purchaseType === DEPT_PURCHASE_TYPE.ENTRUST_CENTER ? '' : (detail.purchaseType ?? ''),
purchaseChannel: (detail as any).purchaseChannel ?? (detail.purchaseType === DEPT_PURCHASE_TYPE.ENTRUST_CENTER ? PURCHASE_CHANNEL.ENTRUST_CENTER : ''),
purchaseTypeUnion: detail.purchaseTypeUnion ?? '',
categoryCode: detail.categoryCode ?? '',
remark: detail.remark ?? '',
status: detail.status ?? '',
businessNegotiationTable: detail.businessNegotiationTable ?? '',
marketPurchaseMinutes: detail.marketPurchaseMinutes ?? '',
onlineMallMaterials: detail.onlineMallMaterials ?? '',
inquiryTemplate: detail.inquiryTemplate ?? '',
entrustCenterType: detail.entrustCenterType ?? '',
hasSupplier: detail.hasSupplier != null && detail.hasSupplier !== '' ? detail.hasSupplier : '0',
suppliers: detail.suppliers ?? '',
serviceDirectSelect: detail.serviceDirectSelect ?? '',
servicePublicSelect: detail.servicePublicSelect ?? '',
purchaseRequirementTemplate: detail.purchaseRequirementTemplate ?? '',
serviceInviteSelect: detail.serviceInviteSelect ?? '',
servicePublicSelectAuto: detail.servicePublicSelectAuto ?? '',
purchaseRequirement: detail.purchaseRequirement ?? '',
meetingMinutes: detail.meetingMinutes ?? '',
feasibilityReport: detail.feasibilityReport ?? '',
meetingMinutesUrgent: detail.meetingMinutesUrgent ?? '',
meetingMinutesSingle: detail.meetingMinutesSingle ?? '',
meetingMinutesImport: detail.meetingMinutesImport ?? '',
singleSourceProof: detail.singleSourceProof ?? '',
importApplication: detail.importApplication ?? '',
governmentPurchaseIntent: detail.governmentPurchaseIntent ?? '',
servicePublicSelectSchool: detail.servicePublicSelectSchool ?? '',
serviceInviteSelectSchool: detail.serviceInviteSelectSchool ?? '',
servicePublicSelectSchoolAuto: detail.servicePublicSelectSchoolAuto ?? '',
deptClassifyUserId: detail.deptClassifyUserId ?? '',
deptClassifyName: detail.deptClassifyName ?? '',
schoolLeaderUserId: detail.schoolLeaderUserId ?? '',
schoolLeaderName: detail.schoolLeaderName ?? '',
otherMaterials: detail.otherMaterials ?? '',
implementType: detail.implementType ?? '',
fileFlowInstId: detail.fileFlowInstId ?? '',
fileFlowStatus: detail.fileFlowStatus ?? '',
agentId: detail.agentId ?? '',
agentName: detail.agentName ?? '',
});
setCategoryCodePath();
try {
const fileRes = await getApplyFiles(String(applyId));
const fileList: { id: string; fileType: string; fileTitle?: string }[] = fileRes?.data ?? [];
if (Array.isArray(fileList) && fileList.length > 0) {
const byType: Record<string, { id: string; fileTitle?: string }[]> = {};
fileList.forEach((f: any) => {
const t = String(f.fileType || '');
if (!byType[t]) byType[t] = [];
byType[t].push({ id: f.id, fileTitle: f.fileTitle });
});
Object.entries(byType).forEach(([fileType, files]) => {
const fields = FILE_TYPE_TO_FIELDS[fileType];
if (!fields || fields.length === 0) return;
const fileItems = files.map((f) => ({ id: f.id, name: f.fileTitle || '附件' }));
if (fields.length === 1) {
(dataForm as any)[fields[0]] = fileItems;
} else {
fields.forEach((field) => {
(dataForm as any)[field] = fileItems.length ? [...fileItems] : '';
});
}
});
// 查看时展示实施采购的采购文件列表type=130
const purchaseFiles = fileList.filter((f: any) => String(f.fileType) === '130').map((f: any) => ({
id: f.id,
fileTitle: f.fileTitle || f.file_title || '采购文件',
createTime: f.createTime || f.create_time,
remark: f.remark,
}));
viewImplementPurchaseFiles.value = purchaseFiles;
} else {
viewImplementPurchaseFiles.value = [];
}
} catch (err) {
console.error('加载采购申请附件失败', err);
viewImplementPurchaseFiles.value = [];
}
}
} catch (e) {
console.error('加载采购申请详情失败', e);
useMessage().error('加载详情失败');
}
}
function formatImplementFileTime(t?: string) {
if (!t) return '—';
const d = new Date(t);
return isNaN(d.getTime()) ? t : d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function downloadImplementFile(file: { remark?: string; fileTitle?: string }) {
if (!file?.remark) {
useMessage().warning('无法获取文件路径');
return;
}
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '采购文件')}`;
other.downBlobFile(url, {}, file.fileTitle || '采购文件');
}
/** 流程嵌入时提供给 orderVue.currElTabIsView 的 methods只读/禁用提交) */
const flowMethods = {
disableForm(disabled?: boolean) {
flowFormDisabled.value = !!disabled;
},
disableSubmit() {
flowSubmitDisabled.value = true;
},
enableSubmit() {
flowSubmitDisabled.value = false;
},
};
/** 流程嵌入时采购申请权限根据前端缓存的角色cloud-ui:roleCode判断非采购中心整表只读采购中心仅采购方式/采购形式可编辑 */
function applyPurchaseApplyFormPerm() {
if (!isFlowEmbed.value) return;
const roleCode = Session.getRoleCode() || '';
isPurchaseCenter.value = roleCode === PURCHASE_CENTER_ROLE_CODE;
flowFormDisabled.value = !isPurchaseCenter.value;
}
/** 流程嵌入时的“保存”回调:校验后调用 editObj并通知流程已保存 */
async function flowSubmitForm() {
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value?.validate().catch(() => false);
if (!valid) {
loading.value = false;
return;
}
const submitData: any = { ...dataForm };
const fileFields = [
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials', 'inquiryTemplate',
'serviceDirectSelect', 'servicePublicSelect', 'purchaseRequirementTemplate',
'serviceInviteSelect', 'servicePublicSelectAuto', 'purchaseRequirement',
'meetingMinutes', 'feasibilityReport', 'meetingMinutesUrgent',
'meetingMinutesSingle', 'meetingMinutesImport', 'singleSourceProof', 'importApplication',
'governmentPurchaseIntent', 'servicePublicSelectSchool', 'serviceInviteSelectSchool',
'servicePublicSelectSchoolAuto', 'otherMaterials'
];
const allFileIds: string[] = [];
fileFields.forEach(field => {
if (submitData[field]) {
allFileIds.push(...getFileIdsArray(submitData[field]));
delete submitData[field];
}
});
if (allFileIds.length > 0) submitData.fileIds = allFileIds;
await editObj(submitData);
useMessage().success('保存成功');
if (props.currJob && props.currElTab?.id) {
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit);
}
} catch (err: any) {
if (!err?.msg) useMessage().error('保存失败');
} finally {
loading.value = false;
}
}
// 获取品目树形数据
const getCategoryTreeData = async () => {
try {
const res = await getTree();
categoryTreeData.value = [];
if (res.data && Array.isArray(res.data)) {
categoryTreeData.value = res.data;
}
} catch (err: any) {
console.error('获取品目树形数据失败', err);
categoryTreeData.value = [];
}
};
// 获取资金来源字典
const getFundSourceDict = async () => {
try {
const res = await getDicts('PURCHASE_FUND_SOURCE');
fundSourceList.value = [];
if (res.data && Array.isArray(res.data)) {
fundSourceList.value = res.data.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
} else {
fundSourceList.value = [
{ label: '切块经费', value: '0' },
{ label: '设备购置费', value: '1' },
{ label: '专项经费', value: '2' },
{ label: '代办费', value: '3' },
{ label: '培训经费', value: '4' },
{ label: '日常公用经费', value: '5' },
{ label: '技能大赛经费', value: '6' },
{ label: '基本建设资金', value: '7' },
{ label: '暂存款', value: '8' },
{ label: '会议费', value: '9' },
];
}
} catch (err) {
fundSourceList.value = [
{ label: '切块经费', value: '0' },
{ label: '设备购置费', value: '1' },
{ label: '专项经费', value: '2' },
{ label: '代办费', value: '3' },
{ label: '培训经费', value: '4' },
{ label: '日常公用经费', value: '5' },
{ label: '技能大赛经费', value: '6' },
{ label: '基本建设资金', value: '7' },
{ label: '暂存款', value: '8' },
{ label: '会议费', value: '9' },
];
}
};
// 获取是否集采字典
const getIsCentralizedDict = async () => {
try {
const res = await getDicts('PURCHASE_IS_CEN');
isCentralizedList.value = res.data && Array.isArray(res.data)
? res.data.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
: [];
} catch (err) {
isCentralizedList.value = [];
}
};
// 获取是否特殊情况字典
const getIsSpecialDict = async () => {
try {
const res = await getDicts('PURCHASE_IS_SPEC');
isSpecialList.value = res.data && Array.isArray(res.data)
? res.data.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
: [];
} catch (err) {
isSpecialList.value = [];
}
};
// 获取部门采购方式字典(过滤掉“委托采购中心采购”,由采购途径字段控制)
const getPurchaseTypeDeptDict = async () => {
try {
const res = await getDicts('PURCHASE_TYPE_DEPT');
purchaseTypeDeptList.value = res.data && Array.isArray(res.data)
? res.data
.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code,
}))
.filter((item: any) => item.value !== DEPT_PURCHASE_TYPE.ENTRUST_CENTER)
: [];
} catch (err) {
purchaseTypeDeptList.value = [];
}
};
// 获取学校采购形式字典
const getPurchaseModeSchoolDict = async () => {
try {
const res = await getDicts('PURCHASE_MODE_SCHOOL');
purchaseModeSchoolList.value = res.data && Array.isArray(res.data)
? res.data.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
: [];
} catch (err) {
purchaseModeSchoolList.value = [];
}
};
// 获取学校统一采购方式字典
const getPurchaseTypeUnionDict = async () => {
try {
const res = await getDicts('PURCHASE_TYPE_UNION');
purchaseTypeUnionList.value = res.data && Array.isArray(res.data)
? res.data.map((item: any) => ({
id: item.id,
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
: [];
} catch (err) {
purchaseTypeUnionList.value = [];
}
};
// 获取业务分管处室列表
const getBusinessDeptListData = async () => {
try {
const res = await getBusinessDeptList({ records: 1000 }); // 获取所有数据
if (res.data && res.data.records) {
businessDeptList.value = res.data.records.map((item: any) => ({
id: item.id,
deptId: item.deptId,
deptName: item.deptName,
userId: item.userId,
name: item.name,
username: item.username
}));
}
} catch (err) {
console.error('获取业务分管处室列表失败:', err);
businessDeptList.value = [];
}
};
// 获取分管校领导列表
const getSchoolLeaderListData = async () => {
try {
const res = await getSchoolLeaderPage({ records: 1000 }); // 获取所有数据
if (res.data && res.data.records) {
schoolLeaderList.value = res.data.records.map((item: any) => ({
id: item.id,
userId: item.userId,
name: item.name,
username: item.username
}));
}
} catch (err) {
console.error('获取分管校领导列表失败:', err);
schoolLeaderList.value = [];
}
};
// 处理业务分管处室选择变化
const handleBusinessDeptChange = (value: string) => {
if (value) {
const selected = businessDeptList.value.find(item => item.userId === value);
if (selected) {
dataForm.deptClassifyUserId = selected.userId;
dataForm.deptClassifyName = selected.deptName || '';
}
} else {
dataForm.deptClassifyUserId = '';
dataForm.deptClassifyName = '';
}
};
// 处理分管校领导选择变化
const handleSchoolLeaderChange = (value: string) => {
if (value) {
const selected = schoolLeaderList.value.find(item => item.userId === value);
if (selected) {
dataForm.schoolLeaderUserId = selected.userId;
dataForm.schoolLeaderName = selected.name || '';
}
} else {
dataForm.schoolLeaderUserId = '';
dataForm.schoolLeaderName = '';
}
};
// 处理文件ID字符串或对象数组转ID数组
// 支持:逗号分隔的字符串、字符串数组、{ id, name? }[](编辑回填时的格式)
const getFileIdsArray = (fileIds: string | string[] | { id?: string; name?: string }[]): string[] => {
if (!fileIds) return [];
if (Array.isArray(fileIds)) {
return fileIds.map((item: any) => {
if (item && typeof item === 'object' && item.id) return String(item.id).trim();
if (typeof item === 'string') return item.trim();
return '';
}).filter(Boolean);
}
const items = String(fileIds).split(',').filter((item: string) => item.trim());
const ids: string[] = [];
items.forEach(item => {
const trimmed = item.trim();
// 首先检查是否是直接的ID格式32位十六进制字符串
if (/^[a-f0-9]{32}$/i.test(trimmed)) {
ids.push(trimmed);
return;
}
// 如果不是ID格式尝试从URL中提取id参数
try {
const urlObj = new URL(trimmed, window.location.origin);
// 优先从URL参数中获取id
let id = urlObj.searchParams.get('id');
// 如果没有id参数尝试从路径中提取可能是直接的文件ID
if (!id) {
const pathParts = urlObj.pathname.split('/').filter(p => p);
const lastPart = pathParts[pathParts.length - 1];
if (lastPart && /^[a-f0-9]{32}$/i.test(lastPart)) {
id = lastPart;
}
}
if (id) {
ids.push(id);
} else {
// 如果无法提取ID使用原始字符串可能是URL
// 但这种情况不应该发生因为上传接口应该返回id
console.warn('无法从URL中提取文件ID:', trimmed);
ids.push(trimmed);
}
} catch {
// URL解析失败如果原始字符串是ID格式则使用否则忽略
if (/^[a-f0-9]{32}$/i.test(trimmed)) {
ids.push(trimmed);
} else {
console.warn('无法解析文件标识:', trimmed);
}
}
});
return ids;
};
// 提交
const handleSubmit = async () => {
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value?.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData: any = {
...dataForm,
};
// 学校统一采购申请阶段:采购方式由审批环节补充,提交时不写入
if (!isFlowEmbed.value && !isDeptPurchase.value) {
submitData.purchaseType = '';
}
// 处理所有文件字段 - 收集所有文件ID到fileIds数组中
const fileFields = [
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials', 'inquiryTemplate',
'serviceDirectSelect', 'servicePublicSelect', 'purchaseRequirementTemplate',
'serviceInviteSelect', 'servicePublicSelectAuto', 'purchaseRequirement',
'meetingMinutes', 'feasibilityReport', 'meetingMinutesUrgent',
'meetingMinutesSingle', 'meetingMinutesImport', 'singleSourceProof', 'importApplication',
'governmentPurchaseIntent', 'servicePublicSelectSchool', 'serviceInviteSelectSchool',
'servicePublicSelectSchoolAuto', 'otherMaterials'
];
// 收集所有文件ID到一个数组中
const allFileIds: string[] = [];
fileFields.forEach(field => {
if (submitData[field]) {
const ids = getFileIdsArray(submitData[field]);
console.log(`字段 ${field} 的文件ID:`, ids);
// 收集到总数组中
allFileIds.push(...ids);
// 清空原字段,不再单独传递
delete submitData[field];
}
});
// 将所有文件ID统一放到fileIds字段中
if (allFileIds.length > 0) {
submitData.fileIds = allFileIds;
console.log('所有文件ID (fileIds):', allFileIds);
}
console.log('提交数据:', submitData);
if (dataForm.id) {
await editObj(submitData);
useMessage().success('保存成功');
} else {
await addObj(submitData);
useMessage().success('提交成功');
}
// 如果是在 iframe 中,向父窗口发送消息
if (window.parent !== window) {
window.parent.postMessage({
type: 'purchasingrequisition:submitSuccess'
}, '*');
} else {
router.push('/finance/purchasingrequisition');
}
} catch (err: any) {
// 全局拦截器已经显示了错误提示,这里不需要再次显示
// 只有当错误没有 msg 时才显示默认错误提示
if (!err?.msg) {
useMessage().error('提交失败');
}
} finally {
loading.value = false;
}
};
// 暂存
const handleTempStore = async () => {
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value?.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData: any = {
...dataForm,
};
// 学校统一采购申请阶段:采购方式由审批环节补充,暂存时不写入
if (!isFlowEmbed.value && !isDeptPurchase.value) {
submitData.purchaseType = '';
}
// 处理所有文件字段 - 收集所有文件ID到fileIds数组中
const fileFields = [
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials', 'inquiryTemplate',
'serviceDirectSelect', 'servicePublicSelect', 'purchaseRequirementTemplate',
'serviceInviteSelect', 'servicePublicSelectAuto', 'purchaseRequirement',
'meetingMinutes', 'feasibilityReport', 'meetingMinutesUrgent',
'meetingMinutesSingle', 'meetingMinutesImport', 'singleSourceProof', 'importApplication',
'governmentPurchaseIntent', 'servicePublicSelectSchool', 'serviceInviteSelectSchool',
'servicePublicSelectSchoolAuto', 'otherMaterials'
];
// 收集所有文件ID到一个数组中
const allFileIds: string[] = [];
fileFields.forEach(field => {
if (submitData[field]) {
const ids = getFileIdsArray(submitData[field]);
// 收集到总数组中
allFileIds.push(...ids);
// 清空原字段,不再单独传递
delete submitData[field];
}
});
// 将所有文件ID统一放到fileIds字段中
if (allFileIds.length > 0) {
submitData.fileIds = allFileIds;
}
await tempStore(submitData);
useMessage().success('暂存成功');
// 流程嵌入时不关闭、不跳转
if (!isFlowEmbed.value) {
if (window.parent !== window) {
window.parent.postMessage({
type: 'purchasingrequisition:submitSuccess'
}, '*');
} else {
router.push('/finance/purchasingrequisition');
}
}
} catch (err: any) {
if (!err?.msg) {
useMessage().error('暂存失败');
}
} finally {
loading.value = false;
}
};
// 设置品目编码回显路径
const setCategoryCodePath = () => {
if (dataForm.categoryCode && categoryTreeData.value.length > 0) {
const path = findCategoryPath(categoryTreeData.value, dataForm.categoryCode);
if (path) {
categoryCodePath.value = path;
} else {
categoryCodePath.value = [];
}
} else {
categoryCodePath.value = [];
}
};
// 监听 categoryTreeData 变化,设置回显路径
watch(() => categoryTreeData.value, () => {
if (dataForm.categoryCode) {
setCategoryCodePath();
}
}, { deep: true });
// 流程嵌入:切换工单时重新加载该 tab 对应的申请单
watch(
() => props.currJob?.id,
async (newVal, oldVal) => {
if (!isFlowEmbed.value || !props.currJob?.orderId) return;
if (newVal !== oldVal) {
await loadDetail(props.currJob.orderId);
}
}
);
// 初始化
onMounted(async () => {
// 检测是否在 iframe 中,如果是,则修改相关元素的 overflow 样式以支持滚动
if (window.parent !== window) {
nextTick(() => {
// 修改 html 和 body
document.documentElement.style.overflow = 'auto';
document.documentElement.style.height = 'auto';
document.body.style.overflow = 'auto';
document.body.style.height = 'auto';
// 修改 html 和 body添加类名
document.documentElement.classList.add('iframe-mode');
document.body.classList.add('iframe-mode');
// 修改 #app
const app = document.getElementById('app');
if (app) {
app.style.overflow = 'auto';
app.style.height = 'auto';
app.style.minHeight = '100%';
// 添加一个类名标记,方便样式控制
app.classList.add('iframe-mode');
}
});
}
await Promise.all([
getCategoryTreeData(),
getFundSourceDict(),
getIsCentralizedDict(),
getIsSpecialDict(),
getPurchaseTypeDeptDict(),
getPurchaseModeSchoolDict(),
getPurchaseTypeUnionDict(),
getBusinessDeptListData(),
getSchoolLeaderListData(),
]);
// 编辑/查看:从 URL 或流程 currJob.orderId 加载详情
const queryId = effectiveQueryId.value;
if (queryId) {
await loadDetail(queryId);
}
// 流程嵌入:注册 tab 显隐与保存回调,供审批页调用
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
orderVue.currElTabIsExist(props.currJob, props.currElTab.id);
await orderVue.currElTabIsView(flowMethods, props.currJob, props.currElTab.id, flowSubmitForm);
applyPurchaseApplyFormPerm();
}
// 新增模式下设置默认值(只有在没有 id 的情况下才设置)
if (!dataForm.id) {
// 填报日期默认为当天
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
dataForm.applyDate = `${year}-${month}-${day}`;
// 是否集采默认为"否"value: '0'
dataForm.isCentralized = '0';
// 是否特殊情况默认为"否"value: '0'
dataForm.isSpecial = '0';
}
// 如果有 categoryCode设置回显路径
if (dataForm.categoryCode) {
setCategoryCodePath();
}
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.mb20 {
margin-bottom: 20px;
}
.mb16 {
margin-bottom: 16px;
}
.mb8 {
margin-bottom: 8px;
}
.mb10 {
margin-bottom: 10px;
}
.mt5 {
margin-top: 5px;
}
.step-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
padding-bottom: 10px;
border-bottom: 1px solid var(--el-border-color-light);
margin-bottom: 16px;
}
.implement-info-block {
.view-label {
font-size: 14px;
color: var(--el-text-color-secondary);
margin-bottom: 4px;
}
.view-value {
font-size: 14px;
color: var(--el-text-color-primary);
}
}
/* 紧凑表单样式 */
.compact-form {
:deep(.el-form-item) {
margin-bottom: 16px;
}
:deep(.el-form-item__label) {
padding-right: 12px;
font-size: 14px;
}
:deep(.el-input__inner),
:deep(.el-textarea__inner) {
font-size: 14px;
}
}
.template-note {
margin-top: 5px;
color: var(--el-text-color-secondary);
font-size: 12px;
}
/* 预算金额:输入框 + 单位元 */
.budget-yuan-wrap {
display: flex;
align-items: center;
width: 100%;
gap: 8px;
:deep(.el-input-number) {
flex: 1;
}
.budget-unit {
color: var(--el-text-color-regular);
font-size: 14px;
flex-shrink: 0;
}
}
.form-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 16px;
border-top: 1px solid var(--el-border-color-light);
margin-top: 16px;
}
</style>
<style>
/* 当页面在 iframe 中时,允许滚动 */
html.iframe-mode,
body.iframe-mode {
overflow: auto !important;
height: 100% !important;
min-height: 100% !important;
}
#app.iframe-mode {
overflow: auto !important;
height: auto !important;
min-height: 100% !important;
}
/* 在 iframe 模式下,修改页面容器样式 */
.iframe-mode .modern-page-container {
min-height: auto !important;
height: auto !important;
overflow: visible !important;
}
</style>