<template>
|
<div class="app-container">
|
<div class="search_form">
|
<el-form :model="searchForm" :inline="true">
|
<el-form-item label="项目名称:">
|
<el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="招标单位:">
|
<el-input v-model="searchForm.tenderUnit" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="投标日期:">
|
<el-date-picker
|
v-model="searchForm.bidDateRange"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="daterange"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
clearable
|
@change="changeBidDateRange"
|
/>
|
</el-form-item>
|
<el-form-item label="招投标结果:">
|
<el-select v-model="searchForm.bidResult" placeholder="请选择" clearable style="width: 160px">
|
<el-option v-for="item in bidResultOptions" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
<el-button @click="resetSearch">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary" @click="openForm('add')">新增招投标</el-button>
|
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
<el-button type="danger" plain @click="handleDelete">删除</el-button>
|
</div>
|
</div>
|
<el-table
|
:data="tableData"
|
border
|
v-loading="tableLoading"
|
@selection-change="handleSelectionChange"
|
style="width: 100%"
|
height="calc(100vh - 18.5em)"
|
>
|
<el-table-column align="center" type="selection" width="55" fixed="left" />
|
<el-table-column align="center" label="序号" type="index" width="60" fixed="left" />
|
<el-table-column label="所属项目" prop="projectName" min-width="160" show-overflow-tooltip />
|
<el-table-column label="标书名称" prop="bidDocName" min-width="160" show-overflow-tooltip />
|
<el-table-column label="招标单位" prop="tenderUnit" width="160" show-overflow-tooltip />
|
<el-table-column label="投标单位" prop="bidUnit" width="160" show-overflow-tooltip />
|
<el-table-column label="招标日期" prop="tenderDate" width="110" show-overflow-tooltip />
|
<el-table-column label="投标日期" prop="bidDate" width="110" show-overflow-tooltip />
|
<el-table-column label="标书款金额(元)" prop="bidDocFee" width="130" align="right" :formatter="formatAmountColumn" />
|
<el-table-column label="标书款状态" prop="bidDocFeeStatus" width="110" align="center">
|
<template #default="scope">
|
<el-tag :type="getBidDocFeeStatusType(scope.row.bidDocFeeStatus)">
|
{{ scope.row.bidDocFeeStatus || "-" }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="投标保证金(元)" prop="bidDeposit" width="130" align="right" :formatter="formatAmountColumn" />
|
<el-table-column label="招投标结果" prop="bidResult" width="140" align="center">
|
<template #default="scope">
|
<el-tag :type="getBidResultType(scope.row.bidResult)">
|
{{ scope.row.bidResult || "-" }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column fixed="right" label="操作" width="220" align="center">
|
<template #default="scope">
|
<el-button link type="primary" @click="openForm('view', scope.row)">详情</el-button>
|
<el-button link type="primary" @click="openForm('edit', scope.row)">编辑</el-button>
|
<el-button link type="danger" @click="handleRowDelete(scope.row)">删除</el-button>
|
<el-button v-if="isWon(scope.row)" link type="success" @click="handleStartContract(scope.row)">发起合同录入</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination
|
v-show="total > 0"
|
:total="total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="page.current"
|
:limit="page.size"
|
@pagination="paginationChange"
|
/>
|
</div>
|
|
<!-- 新增/编辑/详情弹窗 -->
|
<FormDialog
|
v-model="dialogVisible"
|
:title="
|
operationType === 'add'
|
? '新增招投标'
|
: operationType === 'edit'
|
? '编辑招投标'
|
: '招投标详情'
|
"
|
:width="'60%'"
|
:operation-type="operationType"
|
@close="closeDialog"
|
@confirm="submitForm"
|
@cancel="closeDialog"
|
>
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="160px" label-position="top">
|
<el-divider content-position="left">关联项目</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="16">
|
<el-form-item label="所属项目:" prop="projectId">
|
<el-select v-model="form.projectId" placeholder="请选择项目" clearable filterable style="width: 100%" :disabled="operationType === 'view'" @change="handleProjectChange">
|
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">招标与投标信息</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="标书名称:" prop="bidDocName">
|
<el-input v-model="form.bidDocName" placeholder="请输入标书名称" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="招标单位:" prop="tenderUnit">
|
<el-input v-model="form.tenderUnit" placeholder="请输入招标单位" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="投标单位:" prop="bidUnit">
|
<el-input v-model="form.bidUnit" placeholder="请输入投标单位" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="招标日期:" prop="tenderDate">
|
<el-date-picker
|
v-model="form.tenderDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="投标日期:" prop="bidDate">
|
<el-date-picker
|
v-model="form.bidDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">标书款与保证金</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="标书款金额(元):">
|
<el-input-number v-model="form.bidDocFee" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="标书款状态:" prop="bidDocFeeStatus">
|
<el-select v-model="form.bidDocFeeStatus" placeholder="请选择" clearable style="width: 100%" :disabled="operationType === 'view'" @change="handleBidDocFeeStatusChange">
|
<el-option v-for="item in bidDocFeeStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="标书款状态变更日期:">
|
<el-date-picker
|
v-model="form.bidDocFeeStatusChangeDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="自动填入"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="投标保证金金额(元):">
|
<el-input-number v-model="form.bidDeposit" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">招投标结果</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="招投标结果:" prop="bidResult">
|
<el-select v-model="form.bidResult" placeholder="请选择" clearable style="width: 100%" :disabled="operationType === 'view'">
|
<el-option v-for="item in bidResultOptions" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
|
import { ElMessageBox } from "element-plus";
|
import dayjs from "dayjs";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { listProject } from "@/api/projectManagement/project.js";
|
import {
|
biddingListPage,
|
getBiddingDetail,
|
addOrUpdateBidding,
|
delBidding,
|
} from "@/api/salesManagement/biddingManagement.js";
|
|
const { proxy } = getCurrentInstance();
|
|
// 标书款状态选项
|
const bidDocFeeStatusOptions = [
|
{ label: "未交", value: "未交" },
|
{ label: "已交", value: "已交" },
|
{ label: "已退", value: "已退" },
|
];
|
|
// 招投标结果选项
|
const bidResultOptions = [
|
{ label: "废标", value: "废标" },
|
{ label: "流标", value: "流标" },
|
{ label: "未中标", value: "未中标" },
|
{ label: "中标待出通知书", value: "中标待出通知书" },
|
{ label: "已出中标通知书", value: "已出中标通知书" },
|
{ label: "已签合同", value: "已签合同" },
|
];
|
|
// 中标状态(可发起合同录入)
|
const wonStatuses = ["中标待出通知书", "已出中标通知书", "已签合同"];
|
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const total = ref(0);
|
const selectedRows = ref([]);
|
const page = reactive({ current: 1, size: 10 });
|
|
const searchForm = reactive({
|
projectName: "",
|
tenderUnit: "",
|
bidResult: "",
|
bidDateRange: null,
|
bidDateStart: undefined,
|
bidDateEnd: undefined,
|
});
|
|
const operationType = ref("");
|
const dialogVisible = ref(false);
|
const currentId = ref("");
|
const formRef = ref();
|
|
const form = reactive({
|
projectId: "",
|
projectName: "",
|
bidDocName: "",
|
tenderUnit: "",
|
bidUnit: "",
|
tenderDate: "",
|
bidDate: "",
|
bidDocFee: undefined,
|
bidDocFeeStatus: "",
|
bidDocFeeStatusChangeDate: "",
|
bidDeposit: undefined,
|
bidResult: "",
|
});
|
|
const rules = {
|
projectId: [{ required: true, message: "请选择所属项目", trigger: "change" }],
|
bidDocName: [{ required: true, message: "请输入标书名称", trigger: "blur" }],
|
tenderUnit: [{ required: true, message: "请输入招标单位", trigger: "blur" }],
|
bidUnit: [{ required: true, message: "请输入投标单位", trigger: "blur" }],
|
tenderDate: [{ required: true, message: "请选择招标日期", trigger: "change" }],
|
bidDate: [{ required: true, message: "请选择投标日期", trigger: "change" }],
|
bidDocFeeStatus: [{ required: true, message: "请选择标书款状态", trigger: "change" }],
|
bidResult: [{ required: true, message: "请选择招投标结果", trigger: "change" }],
|
};
|
|
// 项目下拉
|
const projectOptions = ref([]);
|
|
const formatAmount = (val) => {
|
if (val === undefined || val === null || val === "") return "0.00";
|
const num = Number(val);
|
return Number.isNaN(num) ? "0.00" : num.toFixed(2);
|
};
|
const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
|
|
const getBidDocFeeStatusType = (status) => {
|
const map = {
|
未交: "warning",
|
已交: "success",
|
已退: "info",
|
};
|
return map[status] || "info";
|
};
|
|
const getBidResultType = (status) => {
|
const map = {
|
废标: "info",
|
流标: "info",
|
未中标: "warning",
|
中标待出通知书: "primary",
|
已出中标通知书: "success",
|
已签合同: "success",
|
};
|
return map[status] || "info";
|
};
|
|
const isWon = (row) => wonStatuses.includes(row.bidResult);
|
|
const initProjects = async () => {
|
try {
|
const res = await listProject({ pageSize: 1000 });
|
const rows = res?.data?.records || res?.rows || res?.records || [];
|
projectOptions.value = rows.map((p) => ({
|
label: p.title ?? p.projectName,
|
value: p.id,
|
}));
|
} catch (e) {
|
console.error("加载项目列表失败", e);
|
}
|
};
|
|
const handleProjectChange = (val) => {
|
const project = projectOptions.value.find((item) => item.value === val);
|
form.projectName = project ? project.label : "";
|
};
|
|
// 标书款状态变化时自动填入变更日期
|
const handleBidDocFeeStatusChange = (val) => {
|
if (val && !form.bidDocFeeStatusChangeDate) {
|
form.bidDocFeeStatusChangeDate = dayjs().format("YYYY-MM-DD");
|
}
|
};
|
|
const changeBidDateRange = (value) => {
|
if (value) {
|
searchForm.bidDateStart = value[0];
|
searchForm.bidDateEnd = value[1];
|
} else {
|
searchForm.bidDateStart = undefined;
|
searchForm.bidDateEnd = undefined;
|
}
|
handleQuery();
|
};
|
|
const resetSearch = () => {
|
searchForm.projectName = "";
|
searchForm.tenderUnit = "";
|
searchForm.bidResult = "";
|
searchForm.bidDateRange = null;
|
searchForm.bidDateStart = undefined;
|
searchForm.bidDateEnd = undefined;
|
page.current = 1;
|
getList();
|
};
|
|
const handleQuery = () => {
|
page.current = 1;
|
getList();
|
};
|
|
const paginationChange = (obj) => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
|
const getList = () => {
|
tableLoading.value = true;
|
const { bidDateRange, ...rest } = searchForm;
|
const params = { ...rest, current: page.current, size: page.size };
|
delete params.bidDateRange;
|
return biddingListPage(params)
|
.then((res) => {
|
tableLoading.value = false;
|
tableData.value = res.records || res.data?.records || [];
|
total.value = res.total ?? res.data?.total ?? 0;
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
const handleSelectionChange = (selection) => {
|
selectedRows.value = selection;
|
};
|
|
const resetFormData = () => {
|
Object.keys(form).forEach((key) => {
|
form[key] = "";
|
});
|
form.bidDocFee = undefined;
|
form.bidDeposit = undefined;
|
};
|
|
const openForm = async (type, row) => {
|
operationType.value = type;
|
resetFormData();
|
await initProjects();
|
if (type === "add") {
|
dialogVisible.value = true;
|
} else {
|
currentId.value = row.id;
|
getBiddingDetail({ id: row.id }).then((res) => {
|
const detail = res.data || res;
|
Object.keys(form).forEach((key) => {
|
if (detail[key] !== undefined && detail[key] !== null) {
|
form[key] = detail[key];
|
}
|
});
|
dialogVisible.value = true;
|
});
|
}
|
};
|
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate((valid) => {
|
if (!valid) return;
|
addOrUpdateBidding(form).then(() => {
|
proxy.$modal.msgSuccess("保存成功");
|
closeDialog();
|
getList();
|
});
|
});
|
};
|
|
const closeDialog = () => {
|
proxy.resetForm("formRef");
|
dialogVisible.value = false;
|
};
|
|
// 中标后发起销售合同录入:跳转合同管理页,携带项目与招标单位信息
|
const handleStartContract = (row) => {
|
proxy.$tab.openPage("发起销售合同录入", "/salesManagement/contractManagement", {
|
projectId: row.projectId,
|
projectName: row.projectName,
|
signingUnit: row.tenderUnit,
|
});
|
};
|
|
const handleDelete = () => {
|
if (selectedRows.value.length === 0) {
|
proxy.$modal.msgWarning("请选择数据");
|
return;
|
}
|
const ids = selectedRows.value.map((item) => item.id);
|
ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delBidding(ids).then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {});
|
};
|
|
const handleRowDelete = (row) => {
|
ElMessageBox.confirm("该招投标记录将被删除,是否确认删除?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delBidding([row.id]).then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {});
|
};
|
|
const handleExport = () => {
|
proxy.download("/sales/bidding/export", { ...searchForm }, "招投标管理.xlsx");
|
};
|
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.search_form {
|
margin-bottom: 10px;
|
}
|
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
</style>
|