yuanxingsheji/项目管理/市场商务管理/招采业务_final.html

1808 lines
74 KiB
HTML
Raw Normal View History

2025-07-25 17:57:25 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目管理 - 招采业务</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
color: #333;
background-color: #f0f2f5;
}
.app-container {
display: flex;
min-height: 100vh;
overflow: hidden;
}
/* 侧边栏样式 */
.sidebar {
width: 220px;
background-color: #304156;
color: #fff;
transition: width 0.3s;
flex-shrink: 0;
overflow-y: auto;
}
.sidebar.collapsed {
width: 64px;
}
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
transition: margin-left 0.3s;
}
.sidebar.collapsed + .main-content {
margin-left: -156px;
}
.logo {
padding: 10px 0;
text-align: center;
position: relative;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.logo h2 {
font-size: 18px;
margin: 5px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10px;
}
.toggle-sidebar {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #fff;
}
/* 菜单项样式 */
.el-menu {
border-right: none;
background-color: transparent;
}
.el-menu-item, .el-submenu__title {
color: #fff;
height: 46px;
line-height: 46px;
}
.el-menu-item:hover, .el-submenu__title:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.el-menu-item.is-active {
background-color: rgba(0, 0, 0, 0.2) !important;
color: #409EFF !important;
}
.el-submenu .el-menu-item {
min-width: 0 !important;
padding-left: 50px !important;
background-color: #1f2d3d !important;
}
.el-submenu .el-menu-item:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.el-submenu__title {
padding-left: 20px !important;
}
.el-menu-item [class^=el-icon-], .el-submenu [class^=el-icon-] {
margin-right: 10px;
}
/* 主内容区样式 */
.module-title {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.card-container {
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
margin-bottom: 20px;
}
/* 表格操作按钮 */
.table-actions {
margin-bottom: 15px;
display: flex;
justify-content: space-between;
}
/* 分页样式 */
.pagination-container {
margin-top: 20px;
text-align: right;
}
/* 表单样式 */
.form-container {
max-width: 1100px;
margin: 0 auto;
}
/* 响应式调整 */
@media (max-width: 768px) {
.sidebar {
position: fixed;
z-index: 1000;
height: 100vh;
}
.sidebar.collapsed + .main-content {
margin-left: 0;
}
.main-content {
margin-left: 220px;
}
.sidebar.collapsed {
width: 0;
overflow: hidden;
}
}
/* 新增样式 */
.file-link {
display: flex;
align-items: center;
}
.file-link i {
margin-right: 5px;
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
background-color: #f0f0f0;
}
.status-badge.preparing {
background-color: #fdf6ec;
color: #e6a23c;
}
.status-badge.bidding {
background-color: #ecf5ff;
color: #409eff;
}
.status-badge.won {
background-color: #f0f9eb;
color: #67c23a;
}
.status-badge.lost {
background-color: #fef0f0;
color: #f56c6c;
}
.status-badge.pending {
background-color: #f0f0f0;
color: #909399;
}
.file-actions {
display: flex;
justify-content: space-between;
align-items: center;
}
/* 进度详情样式 */
.progress-timeline {
margin-top: 20px;
}
.progress-step {
margin-bottom: 30px;
position: relative;
padding-left: 30px;
}
.progress-step:last-child {
margin-bottom: 0;
}
.progress-step::before {
content: '';
position: absolute;
left: 6px;
top: 0;
height: 100%;
width: 2px;
background-color: #e4e7ed;
}
.progress-step.active::before {
background-color: #409eff;
}
.progress-step.completed::before {
background-color: #67c23a;
}
.progress-step-icon {
position: absolute;
left: 0;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #e4e7ed;
z-index: 1;
}
.progress-step.active .progress-step-icon {
background-color: #409eff;
}
.progress-step.completed .progress-step-icon {
background-color: #67c23a;
}
.progress-step-title {
font-weight: bold;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.progress-step-date {
color: #909399;
font-size: 12px;
margin-left: 10px;
}
.progress-step-content {
margin-left: 20px;
}
.progress-step-files {
margin-top: 10px;
}
.progress-step-file {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.progress-step-file i {
margin-right: 5px;
color: #409eff;
}
/* 爬虫日志样式 */
.crawler-log {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ebeef5;
padding: 10px;
margin-top: 10px;
background-color: #f5f7fa;
}
.log-item {
margin-bottom: 5px;
font-size: 12px;
}
.log-item.success {
color: #67c23a;
}
.log-item.error {
color: #f56c6c;
}
.log-item.warning {
color: #e6a23c;
}
/* 投标文件上传区域 */
.upload-area {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
padding: 20px;
text-align: center;
background-color: #fbfdff;
}
.upload-area:hover {
border-color: #409eff;
}
/* 文件上传区域 */
.file-upload-section {
margin-top: 15px;
padding: 15px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
background-color: #f5f7fa;
}
.file-preview {
display: flex;
align-items: center;
margin-top: 10px;
}
.file-preview i {
margin-right: 8px;
color: #409EFF;
}
.file-actions {
margin-left: 10px;
}
</style>
</head>
<body>
<div id="app" class="app-container">
<!-- 侧边栏导航 -->
<div class="sidebar" :class="{collapsed: isCollapse}">
<div class="logo">
<h2>项目管理</h2>
<div class="toggle-sidebar" @click="toggleSidebar">
<i :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
</div>
</div>
<el-menu
:default-active="activeModule"
class="el-menu-vertical"
:collapse="isCollapse"
:collapse-transition="false"
background-color="#304156"
text-color="#fff"
active-text-color="#409EFF"
>
<!-- 项目来源(一级菜单) -->
<el-submenu index="project-source">
<template slot="title">
<i class="el-icon-shopping-bag-1"></i>
<span>项目来源</span>
</template>
<!-- 招采业务(二级菜单) -->
<el-menu-item index="bidding" @click="switchModule('bidding')">招采业务</el-menu-item>
</el-submenu>
</el-menu>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 招采业务 -->
<div v-if="activeModule === 'bidding'">
<h2 class="module-title">招采业务</h2>
<div class="card-container">
<el-tabs v-model="biddingTab" type="card">
<el-tab-pane label="信息检索" name="search">
<div class="table-actions">
<el-input
placeholder="搜索招标项目"
v-model="biddingSearch.keyword"
clearable
style="width: 300px;"
@keyup.enter.native="searchBiddingInfo"
>
<el-button slot="append" icon="el-icon-search" @click="searchBiddingInfo"></el-button>
</el-input>
<div>
<el-button type="primary" icon="el-icon-video-play" @click="startCrawler">开始爬虫</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="refreshBiddingData">刷新数据</el-button>
<el-button type="success" icon="el-icon-download" @click="exportBiddingData">导出数据</el-button>
<el-button type="warning" icon="el-icon-setting" @click="showCrawlerSettings">爬虫设置</el-button>
</div>
</div>
<el-table :data="biddingData" border style="width: 100%">
<el-table-column prop="title" label="招标项目" width="300"></el-table-column>
<el-table-column prop="publisher" label="招标单位" width="180"></el-table-column>
<el-table-column prop="budget" label="预算金额" width="120" align="right"></el-table-column>
<el-table-column prop="deadline" label="截止时间" width="180"></el-table-column>
<el-table-column prop="crawlTime" label="爬取时间" width="180"></el-table-column>
<el-table-column prop="source" label="来源平台" width="120">
<template slot-scope="scope">
<el-link type="primary" :href="scope.row.sourceUrl" target="_blank" v-if="scope.row.sourceUrl">{{ scope.row.source }}</el-link>
<span v-else>{{ scope.row.source }}</span>
</template>
</el-table-column>
<el-table-column prop="documents" label="招标文件" width="150">
<template slot-scope="scope">
<div v-if="scope.row.documents && scope.row.documents.length > 0" class="file-actions">
<el-tag v-for="doc in scope.row.documents" :key="doc.name" size="mini" style="margin-right: 5px;">
<el-link :href="doc.url" target="_blank">{{ doc.name }}</el-link>
</el-tag>
</div>
<el-upload
class="upload-demo"
action=""
:on-change="(file, fileList) => handleBiddingFileUpload(file, fileList, scope.row)"
:auto-upload="false"
:show-file-list="false"
>
<el-button size="mini" type="text">上传文件</el-button>
</el-upload>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '待报名' ? 'warning' : 'success'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="viewBiddingDetail(scope.row)">详情</el-button>
<el-button size="mini" type="primary" @click="markAsRegistered(scope.row)"
:disabled="scope.row.status === '已报名'">{{ scope.row.status === '已报名' ? '已报名' : '报名' }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleBiddingSizeChange"
@current-change="handleBiddingCurrentChange"
:current-page="biddingSearch.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="biddingSearch.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="biddingTotal">
</el-pagination>
</div>
<!-- 招标详情对话框 -->
<el-dialog title="招标详情" :visible.sync="biddingDetailVisible" width="70%">
<div v-if="currentBidding">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目名称">{{ currentBidding.title }}</el-descriptions-item>
<el-descriptions-item label="招标单位">{{ currentBidding.publisher }}</el-descriptions-item>
<el-descriptions-item label="预算金额">{{ currentBidding.budget }}</el-descriptions-item>
<el-descriptions-item label="截止时间">{{ currentBidding.deadline }}</el-descriptions-item>
<el-descriptions-item label="爬取时间">{{ currentBidding.crawlTime }}</el-descriptions-item>
<el-descriptions-item label="项目地点">{{ currentBidding.location }}</el-descriptions-item>
<el-descriptions-item label="项目周期">{{ currentBidding.duration }}</el-descriptions-item>
<el-descriptions-item label="招标范围" :span="2">{{ currentBidding.scope }}</el-descriptions-item>
<el-descriptions-item label="资质要求" :span="2">{{ currentBidding.qualification }}</el-descriptions-item>
<el-descriptions-item label="来源平台" :span="2">
<el-link type="primary" :href="currentBidding.sourceUrl" target="_blank">{{ currentBidding.source }}</el-link>
</el-descriptions-item>
<el-descriptions-item label="招标文件" :span="2">
<div v-if="currentBidding.documents && currentBidding.documents.length > 0" class="file-actions">
<div v-for="doc in currentBidding.documents" :key="doc.name" class="file-link">
<i class="el-icon-document"></i>
<el-link :href="doc.url" target="_blank">{{ doc.name }}</el-link>
</div>
</div>
<el-upload
class="upload-demo"
action=""
:on-change="(file, fileList) => handleBiddingFileUpload(file, fileList, currentBidding)"
:auto-upload="false"
:show-file-list="false"
>
<el-button size="mini" type="primary">上传招标文件</el-button>
</el-upload>
</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 20px;">
<h3>招标内容</h3>
<div v-html="currentBidding.content" style="border: 1px solid #ebeef5; padding: 10px; border-radius: 4px;"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="biddingDetailVisible = false">关闭</el-button>
<el-button type="primary" @click="goToBiddingResponse">投标响应</el-button>
</span>
</el-dialog>
<!-- 爬虫设置对话框 -->
<el-dialog title="爬虫设置" :visible.sync="crawlerSettingsVisible" width="50%">
<el-form :model="crawlerSettings" label-width="120px">
<el-form-item label="爬取频率">
<el-select v-model="crawlerSettings.frequency" style="width: 200px;">
<el-option label="每小时" value="hourly"></el-option>
<el-option label="每天" value="daily"></el-option>
<el-option label="每周" value="weekly"></el-option>
</el-select>
</el-form-item>
<el-form-item label="关键词过滤">
<el-tag
v-for="tag in crawlerSettings.keywords"
:key="tag"
closable
@close="removeKeyword(tag)"
style="margin-right: 10px;"
>
{{ tag }}
</el-tag>
<el-input
v-model="newKeyword"
size="small"
style="width: 150px;"
@keyup.enter.native="addKeyword"
>
<el-button slot="append" icon="el-icon-plus" @click="addKeyword"></el-button>
</el-input>
</el-form-item>
<el-form-item label="来源平台">
<el-checkbox-group v-model="crawlerSettings.platforms">
<el-checkbox label="国能e招"></el-checkbox>
<el-checkbox label="中国节能"></el-checkbox>
<el-checkbox label="科幻集团"></el-checkbox>
<el-checkbox label="三峡招标"></el-checkbox>
<el-checkbox label="三峡采购"></el-checkbox>
<el-checkbox label="北京京能"></el-checkbox>
<el-checkbox label="华润守正"></el-checkbox>
<el-checkbox label="国能e购"></el-checkbox>
<el-checkbox label="华电电子"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="自动通知">
<el-switch v-model="crawlerSettings.autoNotify"></el-switch>
<span style="margin-left: 10px;">{{ crawlerSettings.autoNotify ? '已开启' : '已关闭' }}</span>
</el-form-item>
<el-form-item label="通知方式" v-if="crawlerSettings.autoNotify">
<el-checkbox-group v-model="crawlerSettings.notifyMethods">
<el-checkbox label="站内消息"></el-checkbox>
<el-checkbox label="电子邮件"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="通知邮箱" v-if="crawlerSettings.notifyMethods.includes('电子邮件')">
<el-input v-model="crawlerSettings.email" placeholder="请输入接收通知的邮箱"></el-input>
</el-form-item>
<el-form-item label="爬虫日志">
<div class="crawler-log">
<div v-for="(log, index) in crawlerLogs" :key="index" :class="['log-item', log.type]">
[{{ log.time }}] {{ log.message }}
</div>
</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="crawlerSettingsVisible = false">取消</el-button>
<el-button type="primary" @click="saveCrawlerSettings">保存设置</el-button>
</span>
</el-dialog>
</el-tab-pane>
<el-tab-pane label="投标响应" name="response">
<el-tabs v-model="biddingResponseTab">
<el-tab-pane label="投标项目" name="projects">
<div class="table-actions">
<el-input
placeholder="搜索投标项目"
v-model="biddingResponseSearch.keyword"
clearable
style="width: 300px;"
>
<el-button slot="append" icon="el-icon-search" @click="searchBiddingResponse"></el-button>
</el-input>
<div>
<el-button type="primary" icon="el-icon-plus" @click="showCreateBiddingDialog">新建投标</el-button>
</div>
</div>
<el-table :data="biddingResponseData" border style="width: 100%">
<el-table-column prop="projectName" label="项目名称" width="250"></el-table-column>
<el-table-column prop="publisher" label="招标单位" width="200"></el-table-column>
<el-table-column prop="bidAmount" label="投标金额" width="150" align="right"></el-table-column>
<el-table-column prop="deadline" label="截止时间" width="180"></el-table-column>
<el-table-column prop="status" label="投标状态" width="150">
<template slot-scope="scope">
<span :class="['status-badge',
scope.row.status === '准备中' ? 'preparing' :
scope.row.status === '已投标' ? 'bidding' :
scope.row.status === '已中标' ? 'won' :
scope.row.status === '未中标' ? 'lost' : 'pending']">
{{ scope.row.status }}
</span>
</template>
</el-table-column>
<el-table-column prop="bidFile" label="投标文件" width="200">
<template slot-scope="scope">
<div v-if="scope.row.bidFile">
<el-link :href="scope.row.bidFile.url" target="_blank">{{ scope.row.bidFile.name }}</el-link>
</div>
<div v-else style="color: #909399;">未上传</div>
</template>
</el-table-column>
<el-table-column prop="uploadTime" label="上传时间" width="180"></el-table-column>
<el-table-column label="操作" width="300">
<template slot-scope="scope">
<el-button size="mini" @click="viewBiddingResponse(scope.row)">详情</el-button>
<el-button size="mini" type="primary" @click="editBiddingResponse(scope.row)">编辑</el-button>
<el-button size="mini" type="success" @click="uploadBidFile(scope.row)"
v-if="!scope.row.bidFile">上传文件</el-button>
<el-button size="mini" type="danger" @click="deleteBiddingResponse(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleBiddingResponseSizeChange"
@current-change="handleBiddingResponseCurrentChange"
:current-page="biddingResponseSearch.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="biddingResponseSearch.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="biddingResponseTotal">
</el-pagination>
</div>
<!-- 投标项目详情对话框 -->
<el-dialog :title="`投标项目详情 - ${currentBiddingResponse.projectName}`" :visible.sync="biddingResponseDetailVisible" width="60%">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目名称">{{ currentBiddingResponse.projectName }}</el-descriptions-item>
<el-descriptions-item label="招标单位">{{ currentBiddingResponse.publisher }}</el-descriptions-item>
<el-descriptions-item label="投标金额">{{ currentBiddingResponse.bidAmount || '待定' }}</el-descriptions-item>
<el-descriptions-item label="截止时间">{{ currentBiddingResponse.deadline }}</el-descriptions-item>
<el-descriptions-item label="项目地点">{{ currentBiddingResponse.location }}</el-descriptions-item>
<el-descriptions-item label="项目类型">{{ currentBiddingResponse.projectType }}</el-descriptions-item>
<el-descriptions-item label="投标状态">{{ currentBiddingResponse.status }}</el-descriptions-item>
<el-descriptions-item label="投标文件" v-if="currentBiddingResponse.bidFile">
<el-link :href="currentBiddingResponse.bidFile.url" target="_blank">{{ currentBiddingResponse.bidFile.name }}</el-link>
</el-descriptions-item>
<el-descriptions-item label="上传时间">{{ currentBiddingResponse.uploadTime || '未上传' }}</el-descriptions-item>
<el-descriptions-item label="投标负责人">{{ currentBiddingResponse.manager }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ currentBiddingResponse.phone }}</el-descriptions-item>
<el-descriptions-item label="项目描述" :span="2">{{ currentBiddingResponse.description }}</el-descriptions-item>
<el-descriptions-item label="招标文件" :span="2">
<div v-if="currentBiddingResponse.biddingDocuments && currentBiddingResponse.biddingDocuments.length > 0">
<div v-for="doc in currentBiddingResponse.biddingDocuments" :key="doc.name" class="file-actions">
<div class="file-link">
<i class="el-icon-document"></i>
<el-link :href="doc.url" target="_blank">{{ doc.name }}</el-link>
</div>
</div>
</div>
<div v-else style="color: #909399;">暂无招标文件</div>
</el-descriptions-item>
</el-descriptions>
<div class="progress-timeline" style="margin-top: 20px;">
<h3>投标进度</h3>
<div v-for="(step, index) in currentBiddingResponse.progressSteps" :key="index"
class="progress-step" :class="step.status">
<div class="progress-step-icon"></div>
<div class="progress-step-title">
{{ step.title }}
<span class="progress-step-date" v-if="step.date">{{ step.date }}</span>
</div>
<div class="progress-step-content">{{ step.description }}</div>
<div class="progress-step-files" v-if="step.files && step.files.length > 0">
<div v-for="(file, fileIndex) in step.files" :key="fileIndex" class="progress-step-file">
<i class="el-icon-document"></i>
<el-link :href="file.url" target="_blank">{{ file.name }}</el-link>
</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="biddingResponseDetailVisible = false">关闭</el-button>
</span>
</el-dialog>
<!-- 编辑投标项目对话框 -->
<el-dialog :title="editBiddingForm.id ? '编辑投标项目' : '新建投标项目'" :visible.sync="editBiddingDialogVisible" width="50%">
<el-form :model="editBiddingForm" :rules="biddingRules" ref="editBiddingForm" label-width="120px">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="editBiddingForm.projectName"></el-input>
</el-form-item>
<el-form-item label="招标单位" prop="publisher">
<el-input v-model="editBiddingForm.publisher"></el-input>
</el-form-item>
<el-form-item label="投标金额" prop="bidAmount">
<el-input v-model="editBiddingForm.bidAmount" style="width: 200px;">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="截止时间" prop="deadline">
<el-date-picker
v-model="editBiddingForm.deadline"
type="datetime"
placeholder="选择日期时间"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
<el-form-item label="项目地点" prop="location">
<el-cascader
v-model="editBiddingForm.location"
:options="locationOptions"
style="width: 100%;"
></el-cascader>
</el-form-item>
<el-form-item label="项目类型" prop="projectType">
<el-select v-model="editBiddingForm.projectType" style="width: 100%;">
<el-option label="风电叶片检查" value="风电叶片检查"></el-option>
<el-option label="风电运维" value="风电运维"></el-option>
<el-option label="风电安装" value="风电安装"></el-option>
<el-option label="其他" value="其他"></el-option>
</el-select>
</el-form-item>
<el-form-item label="投标负责人" prop="manager">
<el-input v-model="editBiddingForm.manager"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="editBiddingForm.phone"></el-input>
</el-form-item>
<el-form-item label="项目描述" prop="description">
<el-input type="textarea" v-model="editBiddingForm.description" :rows="3"></el-input>
</el-form-item>
<!-- 新增投标文件上传区域 -->
<el-form-item label="投标文件">
<div class="file-upload-section">
<div v-if="editBiddingForm.bidFile">
<div class="file-preview">
<i class="el-icon-document"></i>
<el-link :href="editBiddingForm.bidFile.url" target="_blank">{{ editBiddingForm.bidFile.name }}</el-link>
<div class="file-actions">
<el-button type="text" icon="el-icon-delete" @click="removeBidFile"></el-button>
</div>
</div>
</div>
<div v-else>
<el-upload
class="upload-demo"
action=""
:on-change="handleBidFileChangeEdit"
:auto-upload="false"
:show-file-list="false"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">支持格式doc, docx, pdf</div>
</el-upload>
</div>
</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editBiddingDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEditBidding">保存</el-button>
</span>
</el-dialog>
<!-- 上传投标文件对话框 -->
<el-dialog :title="`上传投标文件 - ${currentBiddingResponse.projectName}`" :visible.sync="uploadBidFileDialogVisible" width="50%">
<el-form :model="uploadBidFileForm" label-width="100px">
<el-form-item label="投标金额">
<el-input v-model="uploadBidFileForm.bidAmount" style="width: 200px;">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="工期(天)">
<el-input-number v-model="uploadBidFileForm.duration" :min="1"></el-input-number>
</el-form-item>
<el-form-item label="投标文件">
<div class="upload-area" @click="$refs.bidFileInput.click()">
<i class="el-icon-upload" style="font-size: 40px; color: #c0c4cc;"></i>
<div style="margin-top: 10px;">点击或拖拽文件到此处上传</div>
<div style="margin-top: 10px; color: #909399;">支持格式doc, docx, pdf</div>
<div v-if="uploadBidFileForm.file" style="margin-top: 10px;">
<i class="el-icon-document"></i>
{{ uploadBidFileForm.file.name }}
</div>
</div>
<input type="file" ref="bidFileInput" style="display: none;" @change="handleBidFileChange">
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="uploadBidFileDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitBidFileUpload">提交</el-button>
</span>
</el-dialog>
</el-tab-pane>
<el-tab-pane label="中标通知" name="notices">
<div class="table-actions">
<el-input
placeholder="搜索中标通知"
v-model="noticeSearch.keyword"
clearable
style="width: 300px;"
>
<el-button slot="append" icon="el-icon-search" @click="searchNotices"></el-button>
</el-input>
<div>
<el-button type="primary" icon="el-icon-plus" @click="showCreateNoticeDialog">新建通知</el-button>
</div>
</div>
<el-table :data="noticeData" border style="width: 100%">
<el-table-column prop="projectName" label="项目名称" width="250"></el-table-column>
<el-table-column prop="publisher" label="招标单位" width="200"></el-table-column>
<el-table-column prop="winningPrice" label="中标金额" width="180" align="right"></el-table-column>
<el-table-column prop="duration" label="工期要求" width="150"></el-table-column>
<el-table-column prop="noticeFile" label="中标通知书" width="200">
<template slot-scope="scope">
<div v-if="scope.row.noticeFile">
<el-link :href="scope.row.noticeFile.url" target="_blank">{{ scope.row.noticeFile.name }}</el-link>
</div>
<div v-else style="color: #909399;">未上传</div>
</template>
</el-table-column>
<el-table-column prop="noticeDate" label="通知日期" width="180"></el-table-column>
<el-table-column prop="status" label="状态" width="150">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '已签约' ? 'success' : 'primary'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="250">
<template slot-scope="scope">
<el-button size="mini" @click="downloadNotice(scope.row)">下载</el-button>
<el-button size="mini" type="primary" @click="editNotice(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="deleteNotice(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleNoticeSizeChange"
@current-change="handleNoticeCurrentChange"
:current-page="noticeSearch.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="noticeSearch.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="noticeTotal">
</el-pagination>
</div>
<!-- 新建/编辑中标通知对话框 -->
<el-dialog :title="editNoticeForm.id ? '编辑中标通知' : '新建中标通知'" :visible.sync="editNoticeDialogVisible" width="50%">
<el-form :model="editNoticeForm" :rules="noticeRules" ref="editNoticeForm" label-width="120px">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="editNoticeForm.projectName"></el-input>
</el-form-item>
<el-form-item label="招标单位" prop="publisher">
<el-input v-model="editNoticeForm.publisher"></el-input>
</el-form-item>
<el-form-item label="中标金额" prop="winningPrice">
<el-input v-model="editNoticeForm.winningPrice" style="width: 200px;">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="工期要求" prop="duration">
<el-input v-model="editNoticeForm.duration" style="width: 200px;">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="通知日期" prop="noticeDate">
<el-date-picker
v-model="editNoticeForm.noticeDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="editNoticeForm.status" style="width: 200px;">
<el-option label="已通知" value="已通知"></el-option>
<el-option label="已签约" value="已签约"></el-option>
</el-select>
</el-form-item>
<el-form-item label="中标通知书" prop="noticeFile">
<div v-if="editNoticeForm.noticeFile" style="margin-bottom: 10px;">
<el-link :href="editNoticeForm.noticeFile.url" target="_blank">{{ editNoticeForm.noticeFile.name }}</el-link>
<el-button type="text" icon="el-icon-delete" @click="editNoticeForm.noticeFile = null"></el-button>
</div>
<div class="upload-area" @click="$refs.noticeFileInput.click()">
<i class="el-icon-upload" style="font-size: 40px; color: #c0c4cc;"></i>
<div style="margin-top: 10px;">点击或拖拽文件到此处上传</div>
<div style="margin-top: 10px; color: #909399;">支持格式pdf, doc, docx</div>
</div>
<input type="file" ref="noticeFileInput" style="display: none;" @change="handleNoticeFileChange">
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editNoticeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitNoticeForm">保存</el-button>
</span>
</el-dialog>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
isCollapse: false,
activeModule: 'bidding',
// 招采业务
biddingTab: 'search',
biddingSearch: {
keyword: '',
page: 1,
limit: 10
},
biddingData: [
{
id: 1,
title: '某风电场2023年叶片检查服务采购项目',
publisher: '某风电有限公司',
budget: '120.00万元',
deadline: '2023-12-15 17:00',
crawlTime: '2023-11-01 09:30',
source: '中国招标投标网',
sourceUrl: 'https://www.cebpubservice.com/',
status: '待报名',
location: '内蒙古自治区',
duration: '6个月',
scope: '风电场50台机组叶片检查服务',
qualification: '具备风电叶片检查资质',
documents: [
{ name: '招标文件.pdf', url: 'https://example.com/bidding-docs/123' },
{ name: '技术规范.docx', url: 'https://example.com/bidding-docs/124' }
],
content: '<p>本项目为某风电场2023年叶片检查服务采购包括但不限于</p><ul><li>叶片外观检查</li><li>无损检测</li><li>缺陷记录与评估</li><li>检查报告编制</li></ul>',
interested: false
},
{
id: 2,
title: '某风电场2023年运维服务采购项目',
publisher: '某新能源集团',
budget: '200.00万元',
deadline: '2023-12-20 17:00',
crawlTime: '2023-11-02 10:15',
source: '某省公共资源交易中心',
sourceUrl: 'https://www.example-province.gov.cn/',
status: '已报名',
location: '河北省',
duration: '1年',
scope: '风电场全年运维服务',
qualification: '具备风电运维资质',
documents: [],
content: '<p>本项目为某风电场2023年全年运维服务采购包括但不限于</p><ul><li>日常巡检</li><li>定期维护</li><li>故障处理</li><li>数据记录</li></ul>',
interested: true
}
],
biddingTotal: 20,
currentBidding: null,
biddingDetailVisible: false,
crawlerSettingsVisible: false,
crawlerSettings: {
frequency: 'daily',
keywords: ['风电', '叶片', '检查', '运维'],
platforms: ['中国招标投标公共服务平台', '各省市公共资源交易中心'],
autoNotify: true,
notifyMethods: ['站内消息', '电子邮件'],
email: ''
},
crawlerLogs: [
{ time: '2023-11-01 09:30', message: '成功爬取中国招标投标网数据', type: 'success' },
{ time: '2023-11-01 09:32', message: '成功爬取某省公共资源交易中心数据', type: 'success' },
{ time: '2023-11-02 10:15', message: '爬取企业自有招标平台失败:连接超时', type: 'error' }
],
newKeyword: '',
// 投标响应
biddingResponseTab: 'projects',
biddingResponseSearch: {
keyword: '',
page: 1,
limit: 10
},
biddingResponseData: [
{
id: 1,
biddingId: 1,
projectName: '某风电场2023年叶片检查服务采购项目',
publisher: '某风电有限公司',
bidAmount: '108.00万元',
deadline: '2023-12-15 17:00',
location: '内蒙古自治区',
projectType: '风电叶片检查',
description: '为某风电场提供叶片检查服务',
status: '已投标',
uploadTime: '2023-11-15 14:30',
bidFile: {
name: '投标文件-某风电场2023年叶片检查服务.pdf',
url: 'https://example.com/bid-files/123'
},
biddingDocuments: [
{ name: '招标文件.pdf', url: 'https://example.com/bidding-docs/123' },
{ name: '技术规范.docx', url: 'https://example.com/bidding-docs/124' }
],
progressSteps: [
{
title: '项目立项',
description: '项目立项并分配负责人',
date: '2023-11-01',
status: 'completed',
files: []
},
{
title: '招标文件分析',
description: '完成招标文件分析和技术要求评估',
date: '2023-11-05',
status: 'completed',
files: [
{ name: '招标分析报告.docx', url: 'https://example.com/progress/123' }
]
},
{
title: '投标文件准备',
description: '投标文件准备完成',
date: '2023-11-10',
status: 'completed',
files: []
},
{
title: '投标文件审核',
description: '投标文件审核通过',
date: '2023-11-14',
status: 'completed',
files: []
},
{
title: '提交投标',
description: '投标文件已提交',
date: '2023-11-15',
status: 'completed',
files: [
{ name: '投标文件-某风电场2023年叶片检查服务.pdf', url: 'https://example.com/bid-files/123' }
]
}
]
},
{
id: 2,
biddingId: 3,
projectName: '某风电场2022年运维服务项目',
publisher: '某新能源公司',
bidAmount: '162.00万元',
deadline: '2022-11-30 17:00',
location: '甘肃省',
projectType: '风电运维',
description: '为某风电场提供全年运维服务',
status: '已中标',
uploadTime: '2022-10-30 16:45',
bidFile: {
name: '投标文件-某风电场2022年运维服务.pdf',
url: 'https://example.com/bid-files/125'
},
biddingDocuments: [
{ name: '招标文件.pdf', url: 'https://example.com/bidding-docs/125' }
],
progressSteps: [
{
title: '项目立项',
description: '项目立项并分配负责人',
date: '2022-10-01',
status: 'completed',
files: []
},
{
title: '招标文件分析',
description: '完成招标文件分析和技术要求评估',
date: '2022-10-05',
status: 'completed',
files: [
{ name: '招标分析报告.docx', url: 'https://example.com/progress/125' }
]
},
{
title: '投标文件准备',
description: '投标文件准备完成',
date: '2022-10-25',
status: 'completed',
files: [
{ name: '投标文件.docx', url: 'https://example.com/documents/125' }
]
},
{
title: '投标文件审核',
description: '投标文件审核通过',
date: '2022-10-28',
status: 'completed',
files: []
},
{
title: '提交投标',
description: '投标文件已提交',
date: '2022-10-30',
status: 'completed',
files: []
},
{
title: '中标通知',
description: '收到中标通知书',
date: '2022-12-05',
status: 'completed',
files: [
{ name: '中标通知书.pdf', url: 'https://example.com/notices/123' }
]
},
{
title: '合同签订',
description: '合同已签订',
date: '2022-12-15',
status: 'completed',
files: [
{ name: '合同文件.pdf', url: 'https://example.com/contracts/123' }
]
}
]
}
],
biddingResponseTotal: 15,
currentBiddingResponse: {},
biddingResponseDetailVisible: false,
editBiddingDialogVisible: false,
editBiddingForm: {
id: null,
projectName: '',
publisher: '',
bidAmount: '',
deadline: '',
location: [],
projectType: '',
description: '',
bidFile: null, // 新增bidFile字段
manager: '',
phone: ''
},
biddingRules: {
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' }
],
publisher: [
{ required: true, message: '请输入招标单位', trigger: 'blur' }
],
deadline: [
{ required: true, message: '请选择截止时间', trigger: 'change' }
]
},
uploadBidFileDialogVisible: false,
uploadBidFileForm: {
bidAmount: '',
duration: 1,
file: null
},
// 中标通知
noticeSearch: {
keyword: '',
page: 1,
limit: 10
},
noticeData: [
{
id: 1,
projectId: 2,
projectName: '某风电场2022年运维服务项目',
publisher: '某新能源公司',
winningPrice: '162.00万元',
duration: '365天',
noticeDate: '2022-12-05',
status: '已签约',
noticeFile: {
name: '中标通知书-某风电场2022年运维服务.pdf',
url: 'https://example.com/notices/123'
}
}
],
noticeTotal: 1,
editNoticeDialogVisible: false,
editNoticeForm: {
id: null,
projectName: '',
publisher: '',
winningPrice: '',
duration: '',
noticeDate: '',
status: '已通知',
noticeFile: null
},
noticeRules: {
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' }
],
publisher: [
{ required: true, message: '请输入招标单位', trigger: 'blur' }
],
noticeDate: [
{ required: true, message: '请选择通知日期', trigger: 'change' }
]
},
locationOptions: [
{
value: '北京市',
label: '北京市',
children: [
{ value: '东城区', label: '东城区' },
{ value: '西城区', label: '西城区' }
]
},
{
value: '内蒙古自治区',
label: '内蒙古自治区',
children: [
{ value: '呼和浩特市', label: '呼和浩特市' },
{ value: '包头市', label: '包头市' }
]
}
]
}
},
computed: {
interestedBiddingProjects() {
return this.biddingData.filter(item => item.status === '已报名');
}
},
mounted() {
// 初始化数据
},
methods: {
toggleSidebar() {
this.isCollapse = !this.isCollapse;
},
switchModule(module) {
this.activeModule = module;
},
// 招采业务 - 信息检索
startCrawler() {
this.$message.success('爬虫任务已启动');
// 添加日志记录
this.crawlerLogs.unshift({
time: this.formatTime(new Date()),
message: '爬虫任务已启动',
type: 'success'
});
// 实际应用中这里应该调用API启动爬虫
},
searchBiddingInfo() {
this.$message(`搜索招标信息: ${this.biddingSearch.keyword}`);
// 实际应用中这里应该调用API获取数据
},
refreshBiddingData() {
this.$message('刷新招标数据');
// 实际应用中这里应该调用API获取数据
},
exportBiddingData() {
this.$message('导出招标数据');
},
showCrawlerSettings() {
this.crawlerSettingsVisible = true;
},
viewBiddingDetail(row) {
this.currentBidding = row;
this.biddingDetailVisible = true;
},
markAsRegistered(row) {
this.$confirm(`确定要报名招标项目 "${row.title}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
row.status = '已报名';
// 自动创建投标项目
const newProject = {
id: Date.now(),
biddingId: row.id,
projectName: row.title,
publisher: row.publisher,
bidAmount: '',
deadline: row.deadline,
location: row.location,
projectType: this.getProjectType(row.title),
description: row.scope,
status: '准备中',
uploadTime: '',
bidFile: null,
biddingDocuments: row.documents || [],
progressSteps: [
{
title: '项目立项',
description: '项目立项并分配负责人',
date: this.formatDate(new Date()),
status: 'completed',
files: []
},
{
title: '招标文件分析',
description: '分析招标文件和技术要求',
date: '',
status: '',
files: []
},
{
title: '投标文件准备',
description: '准备投标文件',
date: '',
status: '',
files: []
},
{
title: '投标文件审核',
description: '审核投标文件',
date: '',
status: '',
files: []
},
{
title: '提交投标',
description: '提交投标文件',
date: '',
status: '',
files: []
}
]
};
this.biddingResponseData.unshift(newProject);
this.biddingResponseTotal += 1;
this.$message({
type: 'success',
message: '报名成功! 已创建投标项目'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消操作'
});
});
},
getProjectType(title) {
if (title.includes('叶片')) return '风电叶片检查';
if (title.includes('运维')) return '风电运维';
if (title.includes('安装')) return '风电安装';
return '其他';
},
goToBiddingResponse() {
this.biddingTab = 'response';
this.biddingDetailVisible = false;
},
addKeyword() {
if (this.newKeyword && !this.crawlerSettings.keywords.includes(this.newKeyword)) {
this.crawlerSettings.keywords.push(this.newKeyword);
this.newKeyword = '';
}
},
removeKeyword(tag) {
this.crawlerSettings.keywords = this.crawlerSettings.keywords.filter(item => item !== tag);
},
saveCrawlerSettings() {
this.$message.success('爬虫设置已保存');
this.crawlerSettingsVisible = false;
},
handleBiddingSizeChange(val) {
this.biddingSearch.limit = val;
this.refreshBiddingData();
},
handleBiddingCurrentChange(val) {
this.biddingSearch.page = val;
this.refreshBiddingData();
},
handleBiddingFileUpload(file, fileList, row) {
// 模拟上传文件
const newDoc = {
name: file.name,
url: URL.createObjectURL(file.raw)
};
if (!row.documents) {
row.documents = [];
}
row.documents.push(newDoc);
this.$message.success(`文件 ${file.name} 上传成功`);
},
// 招采业务 - 投标响应
searchBiddingResponse() {
this.$message(`搜索投标项目: ${this.biddingResponseSearch.keyword}`);
// 实际应用中这里应该调用API获取数据
},
showCreateBiddingDialog() {
this.editBiddingForm = {
id: null,
projectName: '',
publisher: '',
bidAmount: '',
deadline: '',
location: [],
projectType: '',
description: '',
bidFile: null,
manager: '',
phone: ''
};
this.editBiddingDialogVisible = true;
},
viewBiddingResponse(row) {
this.currentBiddingResponse = JSON.parse(JSON.stringify(row));
this.biddingResponseDetailVisible = true;
},
editBiddingResponse(row) {
this.editBiddingForm = {
id: row.id,
projectName: row.projectName,
publisher: row.publisher,
bidAmount: row.bidAmount,
deadline: row.deadline,
location: row.location ? row.location.split(' ') : [],
projectType: row.projectType,
description: row.description,
bidFile: row.bidFile ? {...row.bidFile} : null,
manager: row.manager || '',
phone: row.phone || ''
};
this.editBiddingDialogVisible = true;
},
handleBidFileChangeEdit(file, fileList) {
// 编辑表单中的文件上传处理
const fileUrl = URL.createObjectURL(file.raw);
this.editBiddingForm.bidFile = {
name: file.name,
url: fileUrl
};
this.$message.success(`文件 ${file.name} 上传成功`);
},
removeBidFile() {
this.$confirm('确定要删除投标文件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.editBiddingForm.bidFile = null;
this.$message.success('投标文件已删除');
}).catch(() => {
this.$message.info('已取消删除');
});
},
submitEditBidding() {
this.$refs.editBiddingForm.validate(valid => {
if (valid) {
if (this.editBiddingForm.id) {
// 编辑现有项目
const index = this.biddingResponseData.findIndex(item => item.id === this.editBiddingForm.id);
if (index !== -1) {
this.biddingResponseData[index].projectName = this.editBiddingForm.projectName;
this.biddingResponseData[index].publisher = this.editBiddingForm.publisher;
this.biddingResponseData[index].bidAmount = this.editBiddingForm.bidAmount;
this.biddingResponseData[index].deadline = this.editBiddingForm.deadline;
this.biddingResponseData[index].location = this.editBiddingForm.location.join(' ');
this.biddingResponseData[index].projectType = this.editBiddingForm.projectType;
this.biddingResponseData[index].description = this.editBiddingForm.description;
this.biddingResponseData[index].bidFile = this.editBiddingForm.bidFile;
this.biddingResponseData[index].manager = this.editBiddingForm.manager;
this.biddingResponseData[index].phone = this.editBiddingForm.phone;
// 如果有上传新文件,更新上传时间
if (this.editBiddingForm.bidFile && !this.biddingResponseData[index].uploadTime) {
this.biddingResponseData[index].uploadTime = this.formatDateTime(new Date());
this.biddingResponseData[index].status = '已投标';
}
}
} else {
// 新建项目
const newProject = {
id: Date.now(),
biddingId: null,
projectName: this.editBiddingForm.projectName,
publisher: this.editBiddingForm.publisher,
bidAmount: this.editBiddingForm.bidAmount,
deadline: this.editBiddingForm.deadline,
location: this.editBiddingForm.location.join(' '),
projectType: this.editBiddingForm.projectType,
description: this.editBiddingForm.description,
status: this.editBiddingForm.bidFile ? '已投标' : '准备中',
uploadTime: this.editBiddingForm.bidFile ? this.formatDateTime(new Date()) : '',
bidFile: this.editBiddingForm.bidFile,
manager: this.editBiddingForm.manager,
phone: this.editBiddingForm.phone,
biddingDocuments: [],
progressSteps: [
{
title: '项目立项',
description: '项目立项并分配负责人',
date: this.formatDate(new Date()),
status: 'completed',
files: []
},
{
title: '招标文件分析',
description: '分析招标文件和技术要求',
date: '',
status: '',
files: []
},
{
title: '投标文件准备',
description: '准备投标文件',
date: '',
status: '',
files: []
},
{
title: '投标文件审核',
description: '审核投标文件',
date: '',
status: '',
files: []
},
{
title: '提交投标',
description: '提交投标文件',
date: this.editBiddingForm.bidFile ? this.formatDate(new Date()) : '',
status: this.editBiddingForm.bidFile ? 'completed' : '',
files: this.editBiddingForm.bidFile ? [{
name: this.editBiddingForm.bidFile.name,
url: this.editBiddingForm.bidFile.url
}] : []
}
]
};
this.biddingResponseData.unshift(newProject);
this.biddingResponseTotal += 1;
}
this.$message.success('保存成功');
this.editBiddingDialogVisible = false;
} else {
return false;
}
});
},
uploadBidFile(row) {
this.currentBiddingResponse = row;
this.uploadBidFileForm = {
bidAmount: row.bidAmount || '',
duration: 1,
file: null
};
this.uploadBidFileDialogVisible = true;
},
handleBidFileChange(event) {
const file = event.target.files[0];
if (file) {
this.uploadBidFileForm.file = file;
}
},
submitBidFileUpload() {
if (!this.uploadBidFileForm.file) {
this.$message.warning('请先上传投标文件');
return;
}
// 模拟上传文件
const fileUrl = URL.createObjectURL(this.uploadBidFileForm.file);
// 更新投标项目记录
this.currentBiddingResponse.bidAmount = this.uploadBidFileForm.bidAmount || '待定';
this.currentBiddingResponse.bidFile = {
name: this.uploadBidFileForm.file.name,
url: fileUrl
};
this.currentBiddingResponse.uploadTime = this.formatDateTime(new Date());
this.currentBiddingResponse.status = '已投标';
// 更新进度步骤
const stepIndex = this.currentBiddingResponse.progressSteps.findIndex(
step => step.title.includes('提交投标')
);
if (stepIndex !== -1) {
this.currentBiddingResponse.progressSteps[stepIndex].status = 'completed';
this.currentBiddingResponse.progressSteps[stepIndex].date = this.formatDate(new Date());
this.currentBiddingResponse.progressSteps[stepIndex].files = [{
name: this.uploadBidFileForm.file.name,
url: fileUrl
}];
this.updateProgressPercentage(this.currentBiddingResponse);
}
this.$message.success('投标文件上传成功');
this.uploadBidFileDialogVisible = false;
},
deleteBiddingResponse(row) {
this.$confirm(`确定要删除投标项目 "${row.projectName}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.biddingResponseData = this.biddingResponseData.filter(item => item.id !== row.id);
this.biddingResponseTotal -= 1;
this.$message.success('删除成功');
}).catch(() => {
this.$message.info('已取消删除');
});
},
updateProgressPercentage(row) {
const completedSteps = row.progressSteps.filter(step => step.status === 'completed').length;
const totalSteps = row.progressSteps.length;
row.progress = Math.round((completedSteps / totalSteps) * 100);
},
handleBiddingResponseSizeChange(val) {
this.biddingResponseSearch.limit = val;
this.searchBiddingResponse();
},
handleBiddingResponseCurrentChange(val) {
this.biddingResponseSearch.page = val;
this.searchBiddingResponse();
},
// 中标通知
searchNotices() {
this.$message(`搜索中标通知: ${this.noticeSearch.keyword}`);
// 实际应用中这里应该调用API获取数据
},
showCreateNoticeDialog() {
this.editNoticeForm = {
id: null,
projectName: '',
publisher: '',
winningPrice: '',
duration: '',
noticeDate: '',
status: '已通知',
noticeFile: null
};
this.editNoticeDialogVisible = true;
},
editNotice(row) {
this.editNoticeForm = {
id: row.id,
projectName: row.projectName,
publisher: row.publisher,
winningPrice: row.winningPrice,
duration: row.duration.replace('天', ''),
noticeDate: row.noticeDate,
status: row.status,
noticeFile: row.noticeFile ? {...row.noticeFile} : null
};
this.editNoticeDialogVisible = true;
},
handleNoticeFileChange(event) {
const file = event.target.files[0];
if (file) {
this.editNoticeForm.noticeFile = {
name: file.name,
url: URL.createObjectURL(file)
};
}
},
submitNoticeForm() {
this.$refs.editNoticeForm.validate(valid => {
if (valid) {
if (this.editNoticeForm.id) {
// 编辑现有通知
const index = this.noticeData.findIndex(item => item.id === this.editNoticeForm.id);
if (index !== -1) {
this.noticeData[index].projectName = this.editNoticeForm.projectName;
this.noticeData[index].publisher = this.editNoticeForm.publisher;
this.noticeData[index].winningPrice = this.editNoticeForm.winningPrice;
this.noticeData[index].duration = this.editNoticeForm.duration + '天';
this.noticeData[index].noticeDate = this.editNoticeForm.noticeDate;
this.noticeData[index].status = this.editNoticeForm.status;
this.noticeData[index].noticeFile = this.editNoticeForm.noticeFile;
}
} else {
// 新建通知
const newNotice = {
id: Date.now(),
projectId: null,
projectName: this.editNoticeForm.projectName,
publisher: this.editNoticeForm.publisher,
winningPrice: this.editNoticeForm.winningPrice,
duration: this.editNoticeForm.duration + '天',
noticeDate: this.editNoticeForm.noticeDate,
status: this.editNoticeForm.status,
noticeFile: this.editNoticeForm.noticeFile
};
this.noticeData.unshift(newNotice);
this.noticeTotal += 1;
}
this.$message.success('保存成功');
this.editNoticeDialogVisible = false;
} else {
return false;
}
});
},
downloadNotice(row) {
if (row.noticeFile) {
const link = document.createElement('a');
link.href = row.noticeFile.url;
link.download = row.noticeFile.name;
link.click();
} else {
this.$message.warning('该通知没有上传文件');
}
},
deleteNotice(row) {
this.$confirm(`确定要删除中标通知 "${row.projectName}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.noticeData = this.noticeData.filter(item => item.id !== row.id);
this.noticeTotal -= 1;
this.$message.success('删除成功');
}).catch(() => {
this.$message.info('已取消删除');
});
},
handleNoticeSizeChange(val) {
this.noticeSearch.limit = val;
this.searchNotices();
},
handleNoticeCurrentChange(val) {
this.noticeSearch.page = val;
this.searchNotices();
},
// 辅助方法
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
formatDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
},
formatTime(date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${this.formatDate(date)} ${hours}:${minutes}`;
}
}
});
</script>
</body>
</html>