Files
ccdi/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue
2026-04-24 13:29:13 +08:00

1424 lines
56 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="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" v-show="showSearch" label-width="100px" class="query-form">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
style="width: 100%"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="标的物名称" prop="subjectName">
<el-input
v-model="queryParams.subjectName"
placeholder="请输入标的物名称"
clearable
style="width: 100%"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请人" prop="applicantName">
<el-input
v-model="queryParams.applicantName"
placeholder="请输入申请人"
clearable
style="width: 100%"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请日期">
<el-date-picker
v-model="dateRange"
style="width: 100%"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ccdi:purchaseTransaction:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-upload2"
size="mini"
@click="handleImport"
v-hasPermi="['ccdi:purchaseTransaction:import']"
>导入</el-button>
</el-col>
<el-col :span="1.5" v-if="showFailureButton">
<el-tooltip :content="getLastImportTooltip()" placement="top">
<el-button
type="warning"
plain
icon="el-icon-warning"
size="mini"
@click="viewImportFailures"
>查看导入失败记录</el-button>
</el-tooltip>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="transactionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="采购事项ID" align="center" prop="purchaseId" width="150" :show-overflow-tooltip="true" />
<el-table-column label="采购类别" align="center" prop="purchaseCategory" width="110" />
<el-table-column label="项目名称" align="center" prop="projectName" :show-overflow-tooltip="true" />
<el-table-column label="标的物名称" align="center" prop="subjectName" :show-overflow-tooltip="true" />
<el-table-column label="采购方式" align="center" prop="purchaseMethod" width="120" />
<el-table-column label="中标供应商" align="center" prop="supplierName" :show-overflow-tooltip="true">
<template slot-scope="scope">
<span>{{ scope.row.supplierName || "-" }}</span>
</template>
</el-table-column>
<el-table-column label="参与供应商数" align="center" prop="supplierCount" width="120">
<template slot-scope="scope">
<span>{{ scope.row.supplierCount || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="预算金额(元)" align="center" prop="budgetAmount" width="120">
<template slot-scope="scope">
{{ formatAmount(scope.row.budgetAmount) }}
</template>
</el-table-column>
<el-table-column label="申请人" align="center" prop="applicantName" width="100" />
<el-table-column label="申请部门" align="center" prop="applyDepartment" width="120" />
<el-table-column label="申请日期" align="center" prop="applyDate" width="120">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
v-hasPermi="['ccdi:purchaseTransaction:query']"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ccdi:purchaseTransaction:edit']"
>编辑</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ccdi:purchaseTransaction:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog :title="title" :visible.sync="open" width="80%" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-divider content-position="left">基本信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="采购事项ID" prop="purchaseId">
<el-input v-model="form.purchaseId" placeholder="请输入采购事项ID" maxlength="32" :disabled="!isAdd" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购类别" prop="purchaseCategory">
<el-input v-model="form.purchaseCategory" placeholder="请输入采购类别" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" maxlength="200" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="标的物名称" prop="subjectName">
<el-input v-model="form.subjectName" placeholder="请输入标的物名称" maxlength="200" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="标的物描述" prop="subjectDesc">
<el-input v-model="form.subjectDesc" type="textarea" :rows="2" placeholder="请输入标的物描述" />
</el-form-item>
<el-divider content-position="left">数量与金额</el-divider>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="采购数量" prop="purchaseQty">
<el-input-number v-model="form.purchaseQty" :min="0" :precision="2" placeholder="请输入采购数量" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预算金额(元)" prop="budgetAmount">
<el-input-number v-model="form.budgetAmount" :min="0" :precision="2" placeholder="请输入预算金额" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="中标金额(元)" prop="bidAmount">
<el-input-number v-model="form.bidAmount" :min="0" :precision="2" placeholder="请输入中标金额" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="实际采购金额(元)" prop="actualAmount">
<el-input-number v-model="form.actualAmount" :min="0" :precision="2" placeholder="请输入实际采购金额" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="合同金额(元)" prop="contractAmount">
<el-input-number v-model="form.contractAmount" :min="0" :precision="2" placeholder="请输入合同金额" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结算金额(元)" prop="settlementAmount">
<el-input-number v-model="form.settlementAmount" :min="0" :precision="2" placeholder="请输入结算金额" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="采购方式" prop="purchaseMethod">
<el-input v-model="form.purchaseMethod" placeholder="请输入采购方式" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">供应商明细</el-divider>
<div class="supplier-toolbar">
<span class="supplier-toolbar__tip">支持录入中标供应商之外的全部参标供应商并标记唯一中标供应商</span>
<el-button type="primary" plain size="mini" icon="el-icon-plus" @click="handleAddSupplier">添加供应商</el-button>
</div>
<el-empty
v-if="!form.supplierList.length"
:image-size="72"
description="当前未录入供应商信息"
class="supplier-empty"
/>
<el-table v-else :data="form.supplierList" border size="small" class="supplier-table">
<el-table-column label="中标" width="90" align="center">
<template slot-scope="scope">
<el-radio v-model="winnerSupplierIndex" :label="scope.$index">中标</el-radio>
</template>
</el-table-column>
<el-table-column label="序号" width="70" align="center">
<template slot-scope="scope">
<span>{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="供应商名称" min-width="220">
<template slot-scope="scope">
<el-form-item
class="supplier-form-item"
label-width="0px"
:prop="'supplierList.' + scope.$index + '.supplierName'"
:rules="getSupplierFieldRules('supplierName')"
>
<el-input v-model="scope.row.supplierName" placeholder="请输入供应商名称" maxlength="200" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="统一信用代码" min-width="200">
<template slot-scope="scope">
<el-form-item
class="supplier-form-item"
label-width="0px"
:prop="'supplierList.' + scope.$index + '.supplierUscc'"
:rules="getSupplierFieldRules('supplierUscc')"
>
<el-input v-model="scope.row.supplierUscc" placeholder="请输入统一信用代码" maxlength="18" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="联系人" min-width="140">
<template slot-scope="scope">
<el-form-item
class="supplier-form-item"
label-width="0px"
:prop="'supplierList.' + scope.$index + '.contactPerson'"
:rules="getSupplierFieldRules('contactPerson')"
>
<el-input v-model="scope.row.contactPerson" placeholder="请输入联系人" maxlength="50" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="联系电话" min-width="160">
<template slot-scope="scope">
<el-form-item
class="supplier-form-item"
label-width="0px"
:prop="'supplierList.' + scope.$index + '.contactPhone'"
:rules="getSupplierFieldRules('contactPhone')"
>
<el-input v-model="scope.row.contactPhone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="银行账户" min-width="180">
<template slot-scope="scope">
<el-form-item
class="supplier-form-item"
label-width="0px"
:prop="'supplierList.' + scope.$index + '.supplierBankAccount'"
:rules="getSupplierFieldRules('supplierBankAccount')"
>
<el-input v-model="scope.row.supplierBankAccount" placeholder="请输入银行账户" maxlength="50" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="handleRemoveSupplier(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">重要日期</el-divider>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="采购申请日期" prop="applyDate">
<el-date-picker
v-model="form.applyDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="计划批准日期" prop="planApproveDate">
<el-date-picker
v-model="form.planApproveDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="公告发布日期" prop="announceDate">
<el-date-picker
v-model="form.announceDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="开标日期" prop="bidOpenDate">
<el-date-picker
v-model="form.bidOpenDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="合同签订日期" prop="contractSignDate">
<el-date-picker
v-model="form.contractSignDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预计交货日期" prop="expectedDeliveryDate">
<el-date-picker
v-model="form.expectedDeliveryDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="实际交货日期" prop="actualDeliveryDate">
<el-date-picker
v-model="form.actualDeliveryDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="验收日期" prop="acceptanceDate">
<el-date-picker
v-model="form.acceptanceDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结算日期" prop="settlementDate">
<el-date-picker
v-model="form.settlementDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">申请人信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="申请人姓名" prop="applicantName">
<el-input v-model="form.applicantName" placeholder="请输入申请人姓名" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请人工号" prop="applicantId">
<el-input v-model="form.applicantId" placeholder="请输入申请人工号" maxlength="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="申请部门" prop="applyDepartment">
<el-input v-model="form.applyDepartment" placeholder="请输入申请部门" maxlength="100" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">采购负责人信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="采购负责人姓名" prop="purchaseLeaderName">
<el-input v-model="form.purchaseLeaderName" placeholder="请输入采购负责人姓名" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购负责人工号" prop="purchaseLeaderId">
<el-input v-model="form.purchaseLeaderId" placeholder="请输入采购负责人工号" maxlength="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="采购部门" prop="purchaseDepartment">
<el-input v-model="form.purchaseDepartment" placeholder="请输入采购部门" maxlength="100" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</el-dialog>
<el-dialog title="招投标信息详情" :visible.sync="detailOpen" width="1200px" append-to-body>
<div class="detail-container">
<el-divider content-position="left">基本信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="采购事项ID">{{ transactionDetail.purchaseId || "-" }}</el-descriptions-item>
<el-descriptions-item label="采购类别">{{ transactionDetail.purchaseCategory || "-" }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ transactionDetail.projectName || "-" }}</el-descriptions-item>
<el-descriptions-item label="标的物名称">{{ transactionDetail.subjectName || "-" }}</el-descriptions-item>
<el-descriptions-item label="采购方式">{{ transactionDetail.purchaseMethod || "-" }}</el-descriptions-item>
<el-descriptions-item label="标的物描述" :span="2">{{ transactionDetail.subjectDesc || "-" }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">数量与金额</el-divider>
<el-descriptions :column="3" border>
<el-descriptions-item label="采购数量">{{ transactionDetail.purchaseQty || "-" }}</el-descriptions-item>
<el-descriptions-item label="预算金额(元)">{{ formatAmount(transactionDetail.budgetAmount) }}</el-descriptions-item>
<el-descriptions-item label="中标金额(元)">{{ formatAmount(transactionDetail.bidAmount) }}</el-descriptions-item>
<el-descriptions-item label="实际采购金额(元)">{{ formatAmount(transactionDetail.actualAmount) }}</el-descriptions-item>
<el-descriptions-item label="合同金额(元)">{{ formatAmount(transactionDetail.contractAmount) }}</el-descriptions-item>
<el-descriptions-item label="结算金额(元)">{{ formatAmount(transactionDetail.settlementAmount) }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">供应商明细</el-divider>
<el-empty
v-if="!transactionDetail.supplierList || !transactionDetail.supplierList.length"
:image-size="72"
description="未录入供应商信息"
/>
<el-table v-else :data="transactionDetail.supplierList" border size="small">
<el-table-column label="排序" prop="sortOrder" width="90" align="center" />
<el-table-column label="中标结果" width="110" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.isBidWinner === 1 ? 'danger' : 'info'" size="mini">
{{ scope.row.isBidWinner === 1 ? "中标" : "参标" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="供应商名称" prop="supplierName" min-width="220" :show-overflow-tooltip="true" />
<el-table-column label="统一信用代码" prop="supplierUscc" min-width="180" :show-overflow-tooltip="true" />
<el-table-column label="联系人" prop="contactPerson" min-width="120" :show-overflow-tooltip="true" />
<el-table-column label="联系电话" prop="contactPhone" min-width="150" :show-overflow-tooltip="true" />
<el-table-column label="银行账户" prop="supplierBankAccount" min-width="180" :show-overflow-tooltip="true" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
:loading="enterpriseDetailLoading"
@click="handleSupplierEnterpriseDetail(scope.row)"
>详情</el-button>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">重要日期</el-divider>
<el-descriptions :column="3" border>
<el-descriptions-item label="采购申请日期">{{ transactionDetail.applyDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="计划批准日期">{{ transactionDetail.planApproveDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="公告发布日期">{{ transactionDetail.announceDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="开标日期">{{ transactionDetail.bidOpenDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="合同签订日期">{{ transactionDetail.contractSignDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="预计交货日期">{{ transactionDetail.expectedDeliveryDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="实际交货日期">{{ transactionDetail.actualDeliveryDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="验收日期">{{ transactionDetail.acceptanceDate || "-" }}</el-descriptions-item>
<el-descriptions-item label="结算日期">{{ transactionDetail.settlementDate || "-" }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">申请人信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="申请人姓名">{{ transactionDetail.applicantName || "-" }}</el-descriptions-item>
<el-descriptions-item label="申请人工号">{{ transactionDetail.applicantId || "-" }}</el-descriptions-item>
<el-descriptions-item label="申请部门">{{ transactionDetail.applyDepartment || "-" }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">采购负责人信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="采购负责人姓名">{{ transactionDetail.purchaseLeaderName || "-" }}</el-descriptions-item>
<el-descriptions-item label="采购负责人工号">{{ transactionDetail.purchaseLeaderId || "-" }}</el-descriptions-item>
<el-descriptions-item label="采购部门">{{ transactionDetail.purchaseDepartment || "-" }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">审计信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="创建时间">
{{ transactionDetail.createTime ? parseTime(transactionDetail.createTime) : "-" }}
</el-descriptions-item>
<el-descriptions-item label="创建人">{{ transactionDetail.createdBy || "-" }}</el-descriptions-item>
<el-descriptions-item label="更新时间">
{{ transactionDetail.updateTime ? parseTime(transactionDetail.updateTime) : "-" }}
</el-descriptions-item>
<el-descriptions-item label="更新人">{{ transactionDetail.updatedBy || "-" }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailOpen = false" icon="el-icon-close">关闭</el-button>
</div>
</el-dialog>
<el-dialog
title="企业信息详情"
:visible.sync="enterpriseDetailOpen"
width="1000px"
append-to-body
@close="resetEnterpriseDetail"
>
<el-descriptions :column="2" border v-loading="enterpriseDetailLoading">
<el-descriptions-item label="统一社会信用代码">{{ enterpriseDetailData.socialCreditCode || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业名称">{{ enterpriseDetailData.enterpriseName || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业类型">{{ enterpriseDetailData.enterpriseType || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业性质">{{ enterpriseDetailData.enterpriseNature || "-" }}</el-descriptions-item>
<el-descriptions-item label="行业分类">{{ enterpriseDetailData.industryClass || "-" }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ enterpriseDetailData.industryName || "-" }}</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ parseTime(enterpriseDetailData.establishDate, "{y}-{m}-{d}") || "-" }}</el-descriptions-item>
<el-descriptions-item label="注册地址">{{ enterpriseDetailData.registerAddress || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人">{{ enterpriseDetailData.legalRepresentative || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件类型">{{ enterpriseDetailData.legalCertType || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件号码">{{ enterpriseDetailData.legalCertNo || "-" }}</el-descriptions-item>
<el-descriptions-item label="经营状态">{{ formatEnterpriseStatus(enterpriseDetailData.status) }}</el-descriptions-item>
<el-descriptions-item label="风险等级">{{ formatEnterpriseRiskLevel(enterpriseDetailData.riskLevel) }}</el-descriptions-item>
<el-descriptions-item label="企业来源">{{ formatEnterpriseSource(enterpriseDetailData.entSource) }}</el-descriptions-item>
<el-descriptions-item label="数据来源">{{ formatEnterpriseDataSource(enterpriseDetailData.dataSource) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(enterpriseDetailData.createTime) || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东1">{{ enterpriseDetailData.shareholder1 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东2">{{ enterpriseDetailData.shareholder2 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东3">{{ enterpriseDetailData.shareholder3 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东4">{{ enterpriseDetailData.shareholder4 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东5">{{ enterpriseDetailData.shareholder5 || "-" }}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="enterpriseDetailOpen = false">关闭</el-button>
</div>
</el-dialog>
<el-dialog
:title="upload.title"
:visible.sync="upload.open"
width="420px"
append-to-body
@close="handleImportDialogClose"
>
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline;" @click="importTemplate">
下载模板
</el-link>
<span class="upload-tip-text">模板包含招投标主信息供应商明细两个 Sheet</span>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm" :loading="upload.isUploading">确定</el-button>
<el-button @click="upload.open = false" :disabled="upload.isUploading">取消</el-button>
</div>
</el-dialog>
<import-result-dialog
:visible.sync="importResultVisible"
:content="importResultContent"
title="导入结果"
@close="handleImportResultClose"
/>
<el-dialog
title="招投标导入失败记录"
:visible.sync="failureDialogVisible"
width="1200px"
append-to-body
>
<el-alert
v-if="lastImportInfo"
:title="lastImportInfo"
type="info"
:closable="false"
style="margin-bottom: 15px"
/>
<el-table :data="failureList" v-loading="failureLoading">
<el-table-column label="失败Sheet" prop="sheetName" align="center" width="140" />
<el-table-column label="失败行号" prop="sheetRowNum" align="center" width="120">
<template slot-scope="scope">
<span>{{ scope.row.sheetRowNum ? `${scope.row.sheetRowNum}` : "-" }}</span>
</template>
</el-table-column>
<el-table-column label="采购事项ID" prop="purchaseId" align="center" />
<el-table-column label="项目名称" prop="projectName" align="center" :show-overflow-tooltip="true" />
<el-table-column label="标的物名称" prop="subjectName" align="center" :show-overflow-tooltip="true" />
<el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
</el-table>
<pagination
v-show="failureTotal > 0"
:total="failureTotal"
:page.sync="failureQueryParams.pageNum"
:limit.sync="failureQueryParams.pageSize"
@pagination="getFailureList"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="failureDialogVisible = false">关闭</el-button>
<el-button type="danger" plain @click="clearImportHistory">清除历史记录</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addTransaction,
delTransaction,
getImportFailures,
getImportStatus,
getTransaction,
listTransaction,
updateTransaction
} from "@/api/ccdiPurchaseTransaction";
import { getEnterpriseBaseInfo } from "@/api/ccdiEnterpriseBaseInfo";
import {
getDataSourceOptions,
getEnterpriseRiskLevelOptions,
getEnterpriseSourceOptions
} from "@/api/ccdiEnum";
import { getToken } from "@/utils/auth";
import ImportResultDialog from "@/components/ImportResultDialog.vue";
const createSupplierRow = () => ({
supplierName: "",
supplierUscc: "",
contactPerson: "",
contactPhone: "",
supplierBankAccount: "",
sortOrder: null,
isBidWinner: 0
});
const createDefaultForm = () => ({
purchaseId: null,
purchaseCategory: null,
projectName: null,
subjectName: null,
subjectDesc: null,
purchaseQty: null,
budgetAmount: null,
bidAmount: null,
actualAmount: null,
contractAmount: null,
settlementAmount: null,
purchaseMethod: null,
applyDate: null,
planApproveDate: null,
announceDate: null,
bidOpenDate: null,
contractSignDate: null,
expectedDeliveryDate: null,
actualDeliveryDate: null,
acceptanceDate: null,
settlementDate: null,
applicantId: null,
applicantName: null,
applyDepartment: null,
purchaseLeaderId: null,
purchaseLeaderName: null,
purchaseDepartment: null,
supplierList: []
});
export default {
name: "PurchaseTransaction",
components: { ImportResultDialog },
data() {
return {
loading: true,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
transactionList: [],
title: "",
open: false,
detailOpen: false,
enterpriseDetailOpen: false,
enterpriseDetailLoading: false,
enterpriseDetailData: {},
transactionDetail: { supplierList: [] },
isAdd: false,
dateRange: [],
winnerSupplierIndex: null,
enterpriseRiskLevelOptions: [],
enterpriseSourceOptions: [],
enterpriseDataSourceOptions: [],
queryParams: {
pageNum: 1,
pageSize: 10,
projectName: null,
subjectName: null,
applicantName: null
},
form: createDefaultForm(),
rules: {
purchaseId: [
{ required: true, message: "采购事项ID不能为空", trigger: "blur" },
{ max: 32, message: "采购事项ID长度不能超过32个字符", trigger: "blur" }
],
purchaseCategory: [
{ required: true, message: "采购类别不能为空", trigger: "blur" },
{ max: 50, message: "采购类别长度不能超过50个字符", trigger: "blur" }
],
projectName: [
{ max: 200, message: "项目名称长度不能超过200个字符", trigger: "blur" }
],
subjectName: [
{ required: true, message: "标的物名称不能为空", trigger: "blur" },
{ max: 200, message: "标的物名称长度不能超过200个字符", trigger: "blur" }
],
subjectDesc: [
{ max: 500, message: "标的物描述长度不能超过500个字符", trigger: "blur" }
],
purchaseQty: [
{ required: true, message: "采购数量不能为空", trigger: "blur" },
{ type: "number", min: 0.0001, message: "采购数量必须大于0", trigger: "blur" }
],
budgetAmount: [
{ required: true, message: "预算金额不能为空", trigger: "blur" },
{ type: "number", min: 0.01, message: "预算金额必须大于0", trigger: "blur" }
],
bidAmount: [
{ type: "number", min: 0.01, message: "中标金额必须大于0", trigger: "blur" }
],
actualAmount: [
{ type: "number", min: 0.01, message: "实际采购金额必须大于0", trigger: "blur" }
],
contractAmount: [
{ type: "number", min: 0.01, message: "合同金额必须大于0", trigger: "blur" }
],
settlementAmount: [
{ type: "number", min: 0.01, message: "结算金额必须大于0", trigger: "blur" }
],
purchaseMethod: [
{ required: true, message: "采购方式不能为空", trigger: "blur" },
{ max: 50, message: "采购方式长度不能超过50个字符", trigger: "blur" }
],
applyDate: [
{ required: true, message: "采购申请日期不能为空", trigger: "change" }
],
applicantName: [
{ required: true, message: "申请人姓名不能为空", trigger: "blur" },
{ max: 50, message: "申请人姓名长度不能超过50个字符", trigger: "blur" }
],
applicantId: [
{ required: true, message: "申请人工号不能为空", trigger: "blur" },
{ pattern: /^\d{7}$/, message: "申请人工号必须为7位数字", trigger: "blur" }
],
applyDepartment: [
{ required: true, message: "申请部门不能为空", trigger: "blur" },
{ max: 100, message: "申请部门长度不能超过100个字符", trigger: "blur" }
],
purchaseLeaderName: [
{ max: 50, message: "采购负责人姓名长度不能超过50个字符", trigger: "blur" }
],
purchaseLeaderId: [
{ pattern: /^\d{7}$/, message: "采购负责人工号必须为7位数字", trigger: "blur" }
],
purchaseDepartment: [
{ max: 100, message: "采购部门长度不能超过100个字符", trigger: "blur" }
]
},
upload: {
open: false,
title: "",
isUploading: false,
headers: { Authorization: "Bearer " + getToken() },
url: process.env.VUE_APP_BASE_API + "/ccdi/purchaseTransaction/importData"
},
importResultVisible: false,
importResultContent: "",
importPollingTimer: null,
showFailureButton: false,
currentTaskId: null,
failureDialogVisible: false,
failureList: [],
failureLoading: false,
failureTotal: 0,
failureQueryParams: {
pageNum: 1,
pageSize: 10
}
};
},
computed: {
lastImportInfo() {
const savedTask = this.getImportTaskFromStorage();
if (savedTask && savedTask.totalCount) {
return `导入时间: ${this.parseTime(savedTask.saveTime)} | 总数: ${savedTask.totalCount}条 | 成功: ${savedTask.successCount}条 | 失败: ${savedTask.failureCount}`;
}
return "";
}
},
created() {
this.getList();
this.restoreImportState();
this.loadEnterpriseDetailOptions();
},
beforeDestroy() {
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
},
methods: {
loadEnterpriseDetailOptions() {
return Promise.all([
getEnterpriseRiskLevelOptions(),
getEnterpriseSourceOptions(),
getDataSourceOptions()
]).then(([riskRes, sourceRes, dataSourceRes]) => {
this.enterpriseRiskLevelOptions = riskRes.data || [];
this.enterpriseSourceOptions = sourceRes.data || [];
this.enterpriseDataSourceOptions = dataSourceRes.data || [];
}).catch(() => {
this.enterpriseRiskLevelOptions = [];
this.enterpriseSourceOptions = [];
this.enterpriseDataSourceOptions = [];
});
},
getEnterpriseOptionLabel(options, value) {
if (!value) return "-";
const matched = (options || []).find(item => item.value === value);
return matched ? matched.label : value;
},
getList() {
this.loading = true;
const params = this.addDateRangeFlat(this.queryParams, this.dateRange, "applyDateStart", "applyDateEnd");
listTransaction(params).then(response => {
this.transactionList = response.rows || [];
this.total = response.total || 0;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
restoreImportState() {
const savedTask = this.getImportTaskFromStorage();
if (!savedTask) {
this.showFailureButton = false;
this.currentTaskId = null;
return;
}
if (savedTask.hasFailures && savedTask.taskId) {
this.currentTaskId = savedTask.taskId;
this.showFailureButton = true;
}
},
getLastImportTooltip() {
const savedTask = this.getImportTaskFromStorage();
if (savedTask && savedTask.saveTime) {
const date = new Date(savedTask.saveTime);
return `上次导入: ${this.parseTime(date, "{y}-{m}-{d} {h}:{i}")}`;
}
return "";
},
formatAmount(amount) {
if (amount === null || amount === undefined || amount === "") return "-";
return parseFloat(amount).toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
formatEnterpriseRiskLevel(value) {
return this.getEnterpriseOptionLabel(this.enterpriseRiskLevelOptions, value);
},
formatEnterpriseSource(value) {
return this.getEnterpriseOptionLabel(this.enterpriseSourceOptions, value);
},
formatEnterpriseDataSource(value) {
return this.getEnterpriseOptionLabel(this.enterpriseDataSourceOptions, value);
},
formatEnterpriseStatus(value) {
return value || "-";
},
getSupplierFieldRules(field) {
const ruleMap = {
supplierName: [
{ required: true, message: "供应商名称不能为空", trigger: "blur" }
],
supplierUscc: [
{ required: true, message: "统一信用代码不能为空", trigger: "blur" }
],
};
return ruleMap[field] || [];
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = createDefaultForm();
this.winnerSupplierIndex = null;
this.resetForm("form");
},
resetEnterpriseDetail() {
this.enterpriseDetailOpen = false;
this.enterpriseDetailLoading = false;
this.enterpriseDetailData = {};
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.purchaseId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加招投标信息";
this.isAdd = true;
},
handleUpdate(row) {
this.reset();
const purchaseId = row.purchaseId || this.ids[0];
getTransaction(purchaseId).then(response => {
this.applyFormData(response.data);
this.open = true;
this.title = "修改招投标信息";
this.isAdd = false;
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate();
});
});
},
handleDetail(row) {
getTransaction(row.purchaseId).then(response => {
this.transactionDetail = this.normalizeTransactionData(response.data);
this.detailOpen = true;
});
},
async handleSupplierEnterpriseDetail(row) {
const socialCreditCode = row && row.supplierUscc ? row.supplierUscc.trim() : "";
if (!socialCreditCode) {
this.$message.warning("暂无企业信息");
return;
}
this.enterpriseDetailLoading = true;
this.enterpriseDetailData = {};
try {
const response = await getEnterpriseBaseInfo(socialCreditCode);
if (!response || !response.data) {
this.$message.warning("暂无企业信息");
return;
}
this.enterpriseDetailData = response.data;
this.enterpriseDetailOpen = true;
} catch (error) {
this.$message.warning("暂无企业信息");
} finally {
this.enterpriseDetailLoading = false;
}
},
handleAddSupplier() {
this.form.supplierList.push(createSupplierRow());
},
handleRemoveSupplier(index) {
this.form.supplierList.splice(index, 1);
if (this.winnerSupplierIndex === index) {
this.winnerSupplierIndex = null;
} else if (this.winnerSupplierIndex !== null && this.winnerSupplierIndex > index) {
this.winnerSupplierIndex -= 1;
}
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) {
return;
}
const payload = this.buildSubmitPayload();
if (!this.validateSupplierDuplicates(payload.supplierList)) {
return;
}
const request = this.isAdd ? addTransaction(payload) : updateTransaction(payload);
request.then(() => {
this.$modal.msgSuccess(this.isAdd ? "新增成功" : "修改成功");
this.open = false;
this.getList();
});
});
},
buildSubmitPayload() {
const payload = JSON.parse(JSON.stringify(this.form));
payload.supplierList = this.form.supplierList
.filter(item => this.hasAnySupplierValue(item))
.map((item, index) => ({
...item,
supplierName: item.supplierName ? item.supplierName.trim() : "",
supplierUscc: item.supplierUscc ? item.supplierUscc.trim() : "",
contactPerson: item.contactPerson ? item.contactPerson.trim() : "",
contactPhone: item.contactPhone ? item.contactPhone.trim() : "",
supplierBankAccount: item.supplierBankAccount ? item.supplierBankAccount.trim() : "",
sortOrder: item.sortOrder || index + 1,
isBidWinner: this.winnerSupplierIndex === index ? 1 : 0
}));
return payload;
},
validateSupplierDuplicates(supplierList) {
const seen = new Set();
for (const item of supplierList) {
const key = `${item.supplierName || ""}|${item.supplierUscc || ""}`;
if (seen.has(key)) {
this.$message.error("同一招投标事项存在重复供应商,请检查供应商名称和统一信用代码");
return false;
}
seen.add(key);
}
return true;
},
hasAnySupplierValue(item) {
return !!(
item.supplierName ||
item.supplierUscc ||
item.contactPerson ||
item.contactPhone ||
item.supplierBankAccount
);
},
handleDelete(row) {
const purchaseIds = row.purchaseId || this.ids;
this.$modal.confirm(`是否确认删除采购事项ID为"${purchaseIds}"的数据项?`).then(function() {
return delTransaction(purchaseIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
handleImport() {
this.upload.title = "招投标信息导入";
this.upload.open = true;
},
importTemplate() {
this.download("ccdi/purchaseTransaction/importTemplate", {}, `招投标信息维护导入模板_${new Date().getTime()}.xlsx`);
},
handleFileUploadProgress() {
this.upload.isUploading = true;
},
handleFileSuccess(response) {
this.upload.isUploading = false;
this.upload.open = false;
if (response.code !== 200) {
this.$modal.msgError(response.msg);
return;
}
if (!response.data || !response.data.taskId) {
this.$modal.msgError("导入任务创建失败: 缺少任务ID");
this.upload.open = true;
return;
}
const taskId = response.data.taskId;
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
this.clearImportTaskFromStorage();
this.saveImportTaskToStorage({
taskId,
status: "PROCESSING",
timestamp: Date.now(),
hasFailures: false
});
this.showFailureButton = false;
this.currentTaskId = taskId;
this.$notify({
title: "导入任务已提交",
message: "正在后台处理中,处理完成后将通知您",
type: "info",
duration: 3000
});
this.startImportStatusPolling(taskId);
},
startImportStatusPolling(taskId) {
let pollCount = 0;
const maxPolls = 150;
this.importPollingTimer = setInterval(async () => {
try {
pollCount += 1;
if (pollCount > maxPolls) {
clearInterval(this.importPollingTimer);
this.$modal.msgWarning("导入任务处理超时,请联系管理员");
return;
}
const response = await getImportStatus(taskId);
if (response.data && response.data.status !== "PROCESSING") {
clearInterval(this.importPollingTimer);
this.handleImportComplete(response.data);
}
} catch (error) {
clearInterval(this.importPollingTimer);
this.$modal.msgError("查询导入状态失败: " + error.message);
}
}, 2000);
},
getFailureList() {
this.failureLoading = true;
getImportFailures(
this.currentTaskId,
this.failureQueryParams.pageNum,
this.failureQueryParams.pageSize
).then(response => {
this.failureList = response.rows;
this.failureTotal = response.total;
this.failureLoading = false;
}).catch(error => {
this.failureLoading = false;
if (error.response) {
const status = error.response.status;
if (status === 404) {
this.$modal.msgWarning("导入记录已过期,无法查看失败记录");
this.clearImportTaskFromStorage();
this.showFailureButton = false;
this.currentTaskId = null;
this.failureDialogVisible = false;
} else if (status === 500) {
this.$modal.msgError("服务器错误,请稍后重试");
} else {
this.$modal.msgError(`查询失败: ${error.response.data.msg || "未知错误"}`);
}
} else if (error.request) {
this.$modal.msgError("网络连接失败,请检查网络");
} else {
this.$modal.msgError("查询失败记录失败: " + error.message);
}
});
},
viewImportFailures() {
this.failureDialogVisible = true;
this.getFailureList();
},
handleImportComplete(statusResult) {
this.saveImportTaskToStorage({
taskId: statusResult.taskId,
status: statusResult.status,
hasFailures: statusResult.failureCount > 0,
totalCount: statusResult.totalCount,
successCount: statusResult.successCount,
failureCount: statusResult.failureCount
});
if (statusResult.status === "SUCCESS") {
this.$notify({
title: "导入完成",
message: `全部成功! 共导入${statusResult.totalCount}条数据`,
type: "success",
duration: 5000
});
this.showFailureButton = false;
this.getList();
return;
}
if (statusResult.failureCount > 0) {
this.$notify({
title: "导入完成",
message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}`,
type: "warning",
duration: 5000
});
this.showFailureButton = true;
this.currentTaskId = statusResult.taskId;
this.getList();
}
},
handleImportResultClose() {
this.importResultVisible = false;
this.importResultContent = "";
},
submitFileForm() {
this.$refs.upload.submit();
},
handleImportDialogClose() {
this.upload.isUploading = false;
this.$refs.upload && this.$refs.upload.clearFiles();
},
saveImportTaskToStorage(taskData) {
try {
localStorage.setItem(
"purchase_transaction_import_last_task",
JSON.stringify({ ...taskData, saveTime: Date.now() })
);
} catch (error) {
console.error("保存导入任务状态失败:", error);
}
},
getImportTaskFromStorage() {
try {
const data = localStorage.getItem("purchase_transaction_import_last_task");
if (!data) return null;
const task = JSON.parse(data);
if (!task || !task.taskId) {
this.clearImportTaskFromStorage();
return null;
}
if (task.saveTime && typeof task.saveTime !== "number") {
this.clearImportTaskFromStorage();
return null;
}
const sevenDays = 7 * 24 * 60 * 60 * 1000;
if (Date.now() - task.saveTime > sevenDays) {
this.clearImportTaskFromStorage();
return null;
}
return task;
} catch (error) {
console.error("读取导入任务状态失败:", error);
this.clearImportTaskFromStorage();
return null;
}
},
clearImportHistory() {
this.$confirm("确认清除上次导入记录?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.clearImportTaskFromStorage();
this.showFailureButton = false;
this.currentTaskId = null;
this.failureDialogVisible = false;
this.$message.success("已清除");
}).catch(() => {});
},
clearImportTaskFromStorage() {
try {
localStorage.removeItem("purchase_transaction_import_last_task");
} catch (error) {
console.error("清除导入任务状态失败:", error);
}
},
applyFormData(data) {
const normalized = this.normalizeTransactionData(data);
this.form = normalized;
this.winnerSupplierIndex = normalized.supplierList.findIndex(item => item.isBidWinner === 1);
if (this.winnerSupplierIndex < 0) {
this.winnerSupplierIndex = null;
}
},
normalizeTransactionData(data) {
const normalized = {
...createDefaultForm(),
...(data || {})
};
let supplierList = Array.isArray(data && data.supplierList) ? data.supplierList : [];
if (!supplierList.length && data && data.supplierName) {
supplierList = [{
supplierName: data.supplierName || "",
supplierUscc: data.supplierUscc || "",
contactPerson: data.contactPerson || "",
contactPhone: data.contactPhone || "",
supplierBankAccount: data.supplierBankAccount || "",
sortOrder: 1,
isBidWinner: 1
}];
}
normalized.supplierList = supplierList.map((item, index) => ({
...createSupplierRow(),
...item,
sortOrder: item.sortOrder || index + 1,
isBidWinner: item.isBidWinner === 1 ? 1 : 0
}));
return normalized;
}
}
};
</script>
<style scoped>
.query-form ::v-deep .el-row {
display: flex;
flex-wrap: wrap;
}
.query-form ::v-deep .el-col {
float: none;
}
.query-form ::v-deep .el-form-item {
margin-right: 0;
}
.detail-container {
padding: 0 20px;
}
.el-divider {
margin: 16px 0;
}
.supplier-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.supplier-toolbar__tip {
color: #606266;
font-size: 13px;
}
.supplier-empty {
margin-bottom: 16px;
}
.supplier-table {
margin-bottom: 8px;
}
.supplier-form-item {
margin-bottom: 0;
}
.upload-tip-text {
margin-left: 8px;
color: #909399;
font-size: 12px;
}
</style>