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

1808 lines
74 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>