Files
ccdi/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue
wkc f96d10d2e8 feat: 移除招聘信和采购交易的导入更新支持功能
## 变更内容
- 移除招聘信和采购交易导入功能中的isUpdateSupport参数
- 遇到已存在的数据直接报错,不再支持更新操作
- 前端移除"是否更新"复选框

## 后端修改
- CcdiStaffRecruitmentController: 移除updateSupport参数
- ICcdiStaffRecruitmentService: 移除updateSupport参数
- CcdiStaffRecruitmentServiceImpl: 简化导入逻辑,移除更新支持
- CcdiPurchaseTransactionController: 移除updateSupport参数
- ICcdiPurchaseTransactionService: 移除updateSupport参数
- ICcdiPurchaseTransactionImportService: 移除updateSupport参数
- CcdiPurchaseTransactionServiceImpl: 移除updateSupport参数
- CcdiPurchaseTransactionImportServiceImpl: 简化导入逻辑,移除更新支持

## 前端修改
- ccdiStaffRecruitment/index.vue: 移除"是否更新"复选框和相关参数
- ccdiPurchaseTransaction/index.vue: 移除"是否更新"复选框和相关参数

## 影响范围
- 导入时遇到已存在的招聘项目编号或采购事项ID将直接报错
- 错误提示显示具体的重复ID
- 不再支持通过导入文件更新已存在的数据
2026-02-09 01:12:22 +08:00

1304 lines
48 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" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="标的物名称" prop="subjectName">
<el-input
v-model="queryParams.subjectName"
placeholder="请输入标的物名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="申请人" prop="applicantName">
<el-input
v-model="queryParams.applicantName"
placeholder="请输入申请人"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="申请日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<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">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ccdi:purchaseTransaction:export']"
>导出</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"></right-toolbar>
</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="100"/>
<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"/>
<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="1000px" 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>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="中标供应商名称" prop="supplierName">
<el-input v-model="form.supplierName" placeholder="请输入供应商名称" maxlength="200" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商统一信用代码" prop="supplierUscc">
<el-input v-model="form.supplierUscc" placeholder="请输入统一信用代码" maxlength="18" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="供应商联系人" prop="contactPerson">
<el-input v-model="form.contactPerson" placeholder="请输入联系人" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="供应商银行账户" prop="supplierBankAccount">
<el-input v-model="form.supplierBankAccount" placeholder="请输入银行账户" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<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="1000px" 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-descriptions :column="2" border>
<el-descriptions-item label="中标供应商名称">{{ transactionDetail.supplierName || '-' }}</el-descriptions-item>
<el-descriptions-item label="统一信用代码">{{ transactionDetail.supplierUscc || '-' }}</el-descriptions-item>
<el-descriptions-item label="供应商联系人">{{ transactionDetail.contactPerson || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ transactionDetail.contactPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="供应商银行账户" :span="2">{{ transactionDetail.supplierBankAccount || '-' }}</el-descriptions-item>
</el-descriptions>
<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="upload.title"
:visible.sync="upload.open"
width="400px"
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"></i>
<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>
</div>
<div class="el-upload__tip" slot="tip">
<span>仅允许导入"xls""xlsx"格式文件</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="采购事项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 {getToken} from "@/utils/auth";
import ImportResultDialog from "@/components/ImportResultDialog.vue";
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,
// 采购交易详情
transactionDetail: {},
// 是否为新增操作
isAdd: false,
// 日期范围
dateRange: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
projectName: null,
subjectName: null,
applicantName: null
},
// 表单参数
form: {},
// 表单校验
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" }
],
supplierName: [
{ max: 200, message: "供应商名称长度不能超过200个字符", trigger: "blur" }
],
supplierUscc: [
{ pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/, message: "请输入正确的18位统一信用代码", trigger: "blur" }
],
contactPerson: [
{ max: 50, message: "联系人长度不能超过50个字符", trigger: "blur" }
],
contactPhone: [
{ pattern: /^1[3-9]\d{9}$|^0\d{2,3}-?\d{7,8}$/, message: "请输入正确的联系电话(手机号或座机号)", trigger: "blur" }
],
supplierBankAccount: [
{ 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,
// 当前导入任务ID
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(); // 恢复导入状态
},
beforeDestroy() {
// 清理定时器
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
},
methods: {
/** 查询采购交易列表 */
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;
this.loading = false;
});
},
/**
* 恢复导入状态
* 在created()钩子中调用
*/
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;
}
},
/**
* 获取上次导入的提示信息
* @returns {String} 提示文本
*/
getLastImportTooltip() {
const savedTask = this.getImportTaskFromStorage();
if (savedTask && savedTask.saveTime) {
const date = new Date(savedTask.saveTime);
const timeStr = this.parseTime(date, '{y}-{m}-{d} {h}:{i}');
return `上次导入: ${timeStr}`;
}
return '';
},
// 格式化金额
formatAmount(amount) {
if (amount === null || amount === undefined || amount === '') return '-';
return parseFloat(amount).toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
purchaseId: null,
purchaseCategory: null,
projectName: null,
subjectName: null,
subjectDesc: null,
purchaseQty: null,
budgetAmount: null,
bidAmount: null,
actualAmount: null,
contractAmount: null,
settlementAmount: null,
purchaseMethod: null,
supplierName: null,
supplierUscc: null,
contactPerson: null,
contactPhone: null,
supplierBankAccount: 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
};
this.resetForm("form");
},
/** 搜索按钮操作 */
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.form = response.data;
this.open = true;
this.title = "修改采购交易";
this.isAdd = false;
});
},
/** 详情按钮操作 */
handleDetail(row) {
const purchaseId = row.purchaseId;
getTransaction(purchaseId).then(response => {
this.transactionDetail = response.data;
this.detailOpen = true;
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.isAdd) {
addTransaction(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
} else {
updateTransaction(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
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(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ccdi/purchaseTransaction/export', {
...this.queryParams
}, `采购交易_${new Date().getTime()}.xlsx`);
},
/** 导入按钮操作 */
handleImport() {
this.upload.title = "采购交易数据导入";
this.upload.open = true;
},
/** 下载模板操作 */
importTemplate() {
this.download('ccdi/purchaseTransaction/importTemplate', {}, `采购交易导入模板_${new Date().getTime()}.xlsx`);
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.upload.open = false;
if (response.code === 200) {
// 验证响应数据完整性
if (!response.data || !response.data.taskId) {
this.$modal.msgError('导入任务创建失败:缺少任务ID');
this.upload.isUploading = false;
this.upload.open = true;
return;
}
const taskId = response.data.taskId;
// 清除旧的轮询定时器
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
this.clearImportTaskFromStorage();
// 保存新任务的初始状态
this.saveImportTaskToStorage({
taskId: 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);
} else {
this.$modal.msgError(response.msg);
}
},
/** 开始轮询导入状态 */
startImportStatusPolling(taskId) {
let pollCount = 0;
const maxPolls = 150; // 最多轮询150次(5分钟)
this.importPollingTimer = setInterval(async () => {
try {
pollCount++;
// 超时检查
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); // 每2秒轮询一次
},
/** 查询失败记录列表 */
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) {
// 更新localStorage中的任务状态
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();
} else 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();
}
},
// 开始轮询导入状态
startImportPolling(taskId) {
const message = this.$message({
message: '正在导入数据,请稍候...',
type: 'info',
duration: 0,
showClose: false
});
// 立即查询一次
this.checkImportStatus(taskId, message);
// 每2秒轮询一次
this.importPollingTimer = setInterval(() => {
this.checkImportStatus(taskId, message);
}, 2000);
},
// 检查导入状态
checkImportStatus(taskId, message) {
getImportStatus(taskId).then(response => {
const status = response.data;
if (status.status === 'completed') {
// 导入完成,停止轮询
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
message.close();
// 显示导入结果
this.showImportResult(taskId, status);
} else if (status.status === 'failed') {
// 导入失败,停止轮询
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
message.close();
this.$modal.msgError('导入失败: ' + (status.errorMsg || '未知错误'));
this.getList();
}
// 如果还在进行中,继续轮询
}).catch(error => {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
message.close();
this.$modal.msgError('查询导入状态失败');
});
},
// 显示导入结果
showImportResult(taskId, status) {
let resultHtml = '<div style="padding: 10px;">';
resultHtml += '<p><strong>导入完成!</strong></p>';
resultHtml += `<p>成功: ${status.successCount} 条</p>`;
resultHtml += `<p>失败: ${status.failureCount} 条</p>`;
// 如果有失败记录,获取失败详情
if (status.failureCount > 0) {
resultHtml += '<p style="color: #F56C6C; margin-top: 10px;"><strong>失败记录:</strong></p>';
resultHtml += '<div style="max-height: 300px; overflow-y: auto; border: 1px solid #EBEEF5; padding: 10px; background-color: #F5F7FA;">';
// 获取失败记录详情
getImportFailures(taskId, 1, 100).then(response => {
const failures = response.rows || [];
failures.forEach((failure, index) => {
resultHtml += `<div style="margin-bottom: 10px; padding: 8px; background-color: white; border-left: 3px solid #F56C6C;">
<p style="margin: 5px 0;"><strong>第 ${failure.rowNum} 行:</strong></p>
<p style="margin: 5px 0; color: #606266;">${failure.errorMessage || failure.errorMsg || '未知错误'}</p>
</div>`;
});
resultHtml += '</div></div>';
this.importResultContent = resultHtml;
this.importResultVisible = true;
}).catch(() => {
resultHtml += '<p style="color: #909399;">获取失败记录详情失败</p>';
resultHtml += '</div></div>';
this.importResultContent = resultHtml;
this.importResultVisible = true;
});
} else {
resultHtml += '</div>';
this.importResultContent = resultHtml;
this.importResultVisible = true;
}
// 刷新列表
this.getList();
},
// 导入结果弹窗关闭
handleImportResultClose() {
this.importResultVisible = false;
this.importResultContent = "";
},
// 提交上传文件
submitFileForm() {
this.$refs.upload.submit();
},
// 关闭导入对话框
handleImportDialogClose() {
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
},
/**
* 保存导入任务到localStorage
* @param {Object} taskData - 任务数据
*/
saveImportTaskToStorage(taskData) {
try {
const data = {
...taskData,
saveTime: Date.now()
};
localStorage.setItem('purchase_transaction_import_last_task', JSON.stringify(data));
} catch (error) {
console.error('保存导入任务状态失败:', error);
}
},
/**
* 从localStorage读取导入任务
* @returns {Object|null} 任务数据或null
*/
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;
}
// 过期检查(7天)
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(() => {});
},
/**
* 清除localStorage中的导入任务
*/
clearImportTaskFromStorage() {
try {
localStorage.removeItem('purchase_transaction_import_last_task');
} catch (error) {
console.error('清除导入任务状态失败:', error);
}
}
}
};
</script>
<style scoped>
.detail-container {
padding: 0 20px;
}
.el-divider {
margin: 16px 0;
}
</style>