1808 lines
74 KiB
HTML
1808 lines
74 KiB
HTML
<!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> |