|
@@ -45,49 +45,48 @@
|
|
|
</p>
|
|
</p>
|
|
|
<div class="monitoring-actions" v-if="(steps.filter(item => item.n === stage)[0] || {}).left_btn_text">
|
|
<div class="monitoring-actions" v-if="(steps.filter(item => item.n === stage)[0] || {}).left_btn_text">
|
|
|
<div v-if="(steps.filter(item => item.n === stage)[0] || {}).label == '标书制作'" style="width: 100%;">
|
|
<div v-if="(steps.filter(item => item.n === stage)[0] || {}).label == '标书制作'" style="width: 100%;">
|
|
|
- <div v-if="stepsType == '1'" style="display: inline-block;">
|
|
|
|
|
- <el-button type="primary" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'')">
|
|
|
|
|
- 提交资料
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="stepsType == '2'" style="display: inline-block;">
|
|
|
|
|
- <el-button type="primary" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'')">
|
|
|
|
|
- 生成标书
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button type="primary" v-if="!bidFile.isFileFlag" class="btn enter-btn" @click="dialogFormBidFileVisible = true">
|
|
|
|
|
- 上传参考标书
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <!-- <el-button type="primary" :loading="swb_status == '2'" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'swb')">
|
|
|
|
|
- 生成商务标书
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button type="primary" :loading="jsb_status == '2'" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'jsb')">
|
|
|
|
|
- 生成技术标书
|
|
|
|
|
- </el-button> -->
|
|
|
|
|
|
|
+ <!-- 招标公告与需求拆解:报名确认后可上传;待确认后可生成 -->
|
|
|
|
|
+ <div v-if="stepsType == '1' || stepsType == '2'" class="bid-make-actions">
|
|
|
|
|
+ <div class="bid-make-left">
|
|
|
|
|
+ <el-button type="primary" class="btn enter-btn" :loading="generateLoading" :disabled="!canShowBidMakeActions" @click="handleGenerateBid">
|
|
|
|
|
+ 生成标书
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <span v-if="!canShowBidMakeActions" class="bid-make-tip">请先完成「报名并获取招标公告」</span>
|
|
|
|
|
+ <span v-else-if="!canGenerateBid" class="bid-make-tip">请先确认任务清单中的「招标公告与需求拆解」</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="canShowBidMakeActions" class="bid-make-right">
|
|
|
|
|
+ <el-button type="primary" class="btn" :loading="uploadLoading.zbwj" @click="triggerUpload('zbwj')">
|
|
|
|
|
+ {{ projects.c_zbwj ? '修改招标文件' : '上传招标文件' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" class="btn" :loading="uploadLoading.cgxqwj" @click="triggerUpload('cgxqwj')">
|
|
|
|
|
+ {{ projects.c_cgxqwj ? '修改采购需求文件' : '上传采购需求文件' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" class="btn" :loading="uploadLoading.ckbs" @click="triggerUpload('ckbs')">
|
|
|
|
|
+ {{ hasRefBidFile ? '修改参考标书' : '上传参考标书' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" class="btn" :loading="uploadLoading.cqgg" @click="triggerUpload('cqgg')">
|
|
|
|
|
+ {{ projects.c_cqggwj ? '修改澄清公告' : '上传澄清公告' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <input ref="uploadInputZbwj" type="file" accept=".pdf" style="display:none" @change="onUploadFileChange('zbwj', $event)" />
|
|
|
|
|
+ <input ref="uploadInputCgxq" type="file" accept=".docx,.pdf" style="display:none" @change="onUploadFileChange('cgxqwj', $event)" />
|
|
|
|
|
+ <input ref="uploadInputCkbs" type="file" accept=".docx" style="display:none" @change="onUploadFileChange('ckbs', $event)" />
|
|
|
|
|
+ <input ref="uploadInputCqgg" type="file" accept=".docx,.pdf" style="display:none" @change="onUploadFileChange('cqgg', $event)" />
|
|
|
</div>
|
|
</div>
|
|
|
<div v-if="stepsType == '3'" style="display: inline-block;">
|
|
<div v-if="stepsType == '3'" style="display: inline-block;">
|
|
|
- <el-button type="primary" :loading="stepsType == '3'" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'')">
|
|
|
|
|
|
|
+ <el-button type="primary" :loading="true" class="btn enter-btn">
|
|
|
生成标书
|
|
生成标书
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-if="stepsType == '4'" style="display: inline-block;">
|
|
|
|
|
- <el-button type="primary" :disabled="!bidFile.flag" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'')">
|
|
|
|
|
|
|
+ <div v-if="stepsType == '4'" style="display: inline-block; width: 100%;">
|
|
|
|
|
+ <el-button type="primary" :disabled="!canEnterBidSubmit" class="btn enter-btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'')">
|
|
|
进入投标提交
|
|
进入投标提交
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <el-button type="primary" class="btn" @click="getBidFile()">
|
|
|
|
|
|
|
+ <el-button type="primary" class="btn" :loading="getBidLoading" @click="getBidFile()">
|
|
|
获取标书
|
|
获取标书
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <!-- <el-button type="primary" class="btn" @click="getBidFile('swb')">
|
|
|
|
|
- 获取商务标书
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button type="primary" class="btn" @click="getBidFile('jsb')">
|
|
|
|
|
- 获取技术标书
|
|
|
|
|
- </el-button> -->
|
|
|
|
|
|
|
+ <div v-if="generateProgressTip" class="bid-progress-tip">{{ generateProgressTip }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- <div style="float: right;">
|
|
|
|
|
- <el-button type="primary" class="btn" @click="handleViewClick(steps.filter(item => item.n === stage)[0] || {},'cqgg')">
|
|
|
|
|
- 上传澄清公告
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div> -->
|
|
|
|
|
</div>
|
|
</div>
|
|
|
<div v-else-if="(steps.filter(item => item.n === stage)[0] || {}).label == '招标监控'" style="width: 100%;">
|
|
<div v-else-if="(steps.filter(item => item.n === stage)[0] || {}).label == '招标监控'" style="width: 100%;">
|
|
|
<!-- <div v-if="stepsType == '1'" style="display: inline-block;">
|
|
<!-- <div v-if="stepsType == '1'" style="display: inline-block;">
|
|
@@ -114,6 +113,15 @@
|
|
|
进入中标推送
|
|
进入中标推送
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- 投标提交:登记投标;生成完成后、开标监控创建前可下载标书 -->
|
|
|
|
|
+ <div v-else-if="(steps.filter(item => item.n === stage)[0] || {}).label == '投标提交'" style="width: 100%;">
|
|
|
|
|
+ <el-button type="primary" class="btn enter-btn" @click="handleViewClick((steps.filter(item => item.n === stage)[0] || {}),'')" :disabled="left_btn_disabled" :loading="left_btn_disabled">
|
|
|
|
|
+ {{(steps.filter(item => item.n === stage)[0] || {}).left_btn_text || '登记投标'}}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button v-if="showDownloadBidAfterSubmit" type="primary" class="btn" :loading="downloadBidLoading" @click="downloadCurrentBidFile">
|
|
|
|
|
+ 下载标书
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
<div v-else>
|
|
<div v-else>
|
|
|
<el-button type="primary" class="btn enter-btn" @click="handleViewClick((steps.filter(item => item.n === stage)[0] || {}),'')" :disabled="left_btn_disabled" :loading="left_btn_disabled">
|
|
<el-button type="primary" class="btn enter-btn" @click="handleViewClick((steps.filter(item => item.n === stage)[0] || {}),'')" :disabled="left_btn_disabled" :loading="left_btn_disabled">
|
|
|
{{(steps.filter(item => item.n === stage)[0] || {}).left_btn_text || ''}}
|
|
{{(steps.filter(item => item.n === stage)[0] || {}).left_btn_text || ''}}
|
|
@@ -134,7 +142,7 @@
|
|
|
<div class="card-body">
|
|
<div class="card-body">
|
|
|
<ul class="checklist">
|
|
<ul class="checklist">
|
|
|
<li :class="['checklist-item']" v-for="task in projects.rwlcqd" :key="task.id">
|
|
<li :class="['checklist-item']" v-for="task in projects.rwlcqd" :key="task.id">
|
|
|
- <span :class="['checklist-status', task.done ? 'done' : '']">{{task.done ? '✓' : '○'}} {{task.text}}</span>
|
|
|
|
|
|
|
+ <span :class="['checklist-status', task.done ? 'done' : '']">{{task.done ? '✓' : '○'}} {{getTaskDisplayText(task)}}</span>
|
|
|
<!-- <span class="checklist-text">{{task.text}}</span> -->
|
|
<!-- <span class="checklist-text">{{task.text}}</span> -->
|
|
|
<strong style="cursor: pointer;color: var(--primary);" v-if="task.done && projects.zbmsxx?.zb_detail_url && task.id == 4" @click="handleOpenViewClick('1')">查看招标公告</strong>
|
|
<strong style="cursor: pointer;color: var(--primary);" v-if="task.done && projects.zbmsxx?.zb_detail_url && task.id == 4" @click="handleOpenViewClick('1')">查看招标公告</strong>
|
|
|
<div class="checklist-actions" v-if="task.seq<=stage">
|
|
<div class="checklist-actions" v-if="task.seq<=stage">
|
|
@@ -145,14 +153,20 @@
|
|
|
<span v-if="task.done">匹配成功</span>
|
|
<span v-if="task.done">匹配成功</span>
|
|
|
<span v-else @click="confirmTask(task)">{{task.type == 1 ? '匹配中' : task.type == 2 ? '待确认' :''}}</span>
|
|
<span v-else @click="confirmTask(task)">{{task.type == 1 ? '匹配中' : task.type == 2 ? '待确认' :''}}</span>
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <el-button type="primary" size="small" :disabled="task.done" :plain="task.done" v-if="[7].includes(task.id) && !task.done">
|
|
|
|
|
|
|
+ <el-button type="primary" size="small" :disabled="task.done || finishBidLoading" :plain="task.done" :loading="finishBidLoading" v-if="[7].includes(task.id) && !task.done">
|
|
|
<span v-if="!task.done" @click="confirmTask(task)">人工处理</span>
|
|
<span v-if="!task.done" @click="confirmTask(task)">人工处理</span>
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <el-button type="primary" size="small" :disabled="task.done" :plain="task.done" v-if="task.id == 7" :loading="task.type == 1">
|
|
|
|
|
|
|
+ <el-button type="primary" size="small" :disabled="task.done || finishBidLoading" :plain="task.done" v-if="task.id == 7" :loading="task.type == 1 || finishBidLoading">
|
|
|
<span v-if="task.done">已完成</span>
|
|
<span v-if="task.done">已完成</span>
|
|
|
<span v-else @click="confirmTask(task)">{{task.type == 1 ? '监控中' : task.type == 2 ? '待确认' :''}}</span>
|
|
<span v-else @click="confirmTask(task)">{{task.type == 1 ? '监控中' : task.type == 2 ? '待确认' :''}}</span>
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <el-button type="primary" size="small" :disabled="task.done" :plain="task.done" v-if="task.id != 3 && task.id != 7">
|
|
|
|
|
|
|
+ <!-- 任务5:须先完成报名(任务4) -->
|
|
|
|
|
+ <el-button type="primary" size="small" :disabled="task.done || !isTaskDone(4)" :plain="task.done" v-if="task.id == 5">
|
|
|
|
|
+ <span v-if="task.done">已完成</span>
|
|
|
|
|
+ <span v-else-if="!isTaskDone(4)">请先报名</span>
|
|
|
|
|
+ <span v-else @click="confirmTask(task)">待确认</span>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" size="small" :disabled="task.done" :plain="task.done" v-if="task.id != 3 && task.id != 7 && task.id != 5">
|
|
|
<span v-if="task.done">已完成</span>
|
|
<span v-if="task.done">已完成</span>
|
|
|
<span v-else @click="confirmTask(task)">待确认</span>
|
|
<span v-else @click="confirmTask(task)">待确认</span>
|
|
|
</el-button>
|
|
</el-button>
|
|
@@ -313,7 +327,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogKBJKVisible" draggable title="提示信息" width="500">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogKBJKVisible" draggable title="提示信息" width="500" :close-on-click-modal="false">
|
|
|
<div style="height: auto;overflow: auto;">
|
|
<div style="height: auto;overflow: auto;">
|
|
|
<el-form
|
|
<el-form
|
|
|
ref="ruleKBJKFormRef"
|
|
ref="ruleKBJKFormRef"
|
|
@@ -404,7 +418,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog> -->
|
|
</el-dialog> -->
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogZBJKVisible" draggable title="提示信息" width="500">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogZBJKVisible" draggable title="提示信息" width="500" :close-on-click-modal="false">
|
|
|
<div style="height: auto;overflow: auto;">
|
|
<div style="height: auto;overflow: auto;">
|
|
|
<el-select v-model="ruleZBJKForm.c_project_jkname" placeholder="请选择匹配的项目" value-key="value" @change="handleJKTypeChange" >
|
|
<el-select v-model="ruleZBJKForm.c_project_jkname" placeholder="请选择匹配的项目" value-key="value" @change="handleJKTypeChange" >
|
|
|
<el-option v-for="item in zbjkProject" :key="item.id" :label="item.name" :value="item.id" />
|
|
<el-option v-for="item in zbjkProject" :key="item.id" :label="item.name" :value="item.id" />
|
|
@@ -421,7 +435,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogZBVisible" draggable title="提示信息" width="500">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogZBVisible" draggable title="提示信息" width="500" :close-on-click-modal="false">
|
|
|
<div style="overflow: auto;">
|
|
<div style="overflow: auto;">
|
|
|
<el-form
|
|
<el-form
|
|
|
ref="ruleZBFormRef"
|
|
ref="ruleZBFormRef"
|
|
@@ -482,7 +496,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogBidVisible" draggable title="提示信息" width="500">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogBidVisible" draggable title="提示信息" width="500" :close-on-click-modal="false">
|
|
|
<div style="height: 500px;overflow: auto;">
|
|
<div style="height: 500px;overflow: auto;">
|
|
|
<el-form
|
|
<el-form
|
|
|
ref="ruleBidFormRef"
|
|
ref="ruleBidFormRef"
|
|
@@ -579,7 +593,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogFinishBidVisible" draggable title="提示信息" width="350">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogFinishBidVisible" draggable title="提示信息" width="350" :close-on-click-modal="false">
|
|
|
<div style="margin-bottom: 5px;">
|
|
<div style="margin-bottom: 5px;">
|
|
|
<el-switch v-model="finishBidObjectTemp.flag" inline-prompt active-text="从中标结果中选择" inactive-text="手动输入" style="background-color: unset;" @change="handleSwitchChange"/>
|
|
<el-switch v-model="finishBidObjectTemp.flag" inline-prompt active-text="从中标结果中选择" inactive-text="手动输入" style="background-color: unset;" @change="handleSwitchChange"/>
|
|
|
</div>
|
|
</div>
|
|
@@ -605,7 +619,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogFinishBidVisible2" draggable title="提示信息" width="350">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogFinishBidVisible2" draggable title="提示信息" width="350" :close-on-click-modal="false">
|
|
|
<div v-if="finishBidObject?.c_detail_url" style="padding-top: 20px;text-align: center;color: var(--primary);">
|
|
<div v-if="finishBidObject?.c_detail_url" style="padding-top: 20px;text-align: center;color: var(--primary);">
|
|
|
<strong style="cursor: pointer;" @click="handleOpenViewClick('0')">点击查看招标结果文件</strong>
|
|
<strong style="cursor: pointer;" @click="handleOpenViewClick('0')">点击查看招标结果文件</strong>
|
|
|
</div>
|
|
</div>
|
|
@@ -620,7 +634,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogBidFileVisible" draggable title="提示信息" width="300">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogBidFileVisible" draggable title="提示信息" width="300" :close-on-click-modal="false">
|
|
|
<div>
|
|
<div>
|
|
|
<strong style="cursor: pointer;" @click="handleOpenViewClick('1')">点击查看招标公告</strong>
|
|
<strong style="cursor: pointer;" @click="handleOpenViewClick('1')">点击查看招标公告</strong>
|
|
|
</div>
|
|
</div>
|
|
@@ -635,7 +649,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogFormFileVisible" draggable title="提示信息" width="300">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogFormFileVisible" draggable title="提示信息" width="300" :close-on-click-modal="false">
|
|
|
<el-input v-model="FileForm.name" autocomplete="off" placeholder="请输入文件名称"/>
|
|
<el-input v-model="FileForm.name" autocomplete="off" placeholder="请输入文件名称"/>
|
|
|
<div style="margin-top: 10px;">
|
|
<div style="margin-top: 10px;">
|
|
|
<el-upload
|
|
<el-upload
|
|
@@ -665,7 +679,7 @@
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
- <el-dialog v-model="dialogFormBidFileVisible" draggable title="提示信息" width="300">
|
|
|
|
|
|
|
+ <el-dialog v-model="dialogFormBidFileVisible" draggable title="提示信息" width="300" :close-on-click-modal="false">
|
|
|
<!-- <el-input v-model="FileForm.name" autocomplete="off" placeholder="请输入文件名称"/> -->
|
|
<!-- <el-input v-model="FileForm.name" autocomplete="off" placeholder="请输入文件名称"/> -->
|
|
|
<div style="margin-top: 10px;">
|
|
<div style="margin-top: 10px;">
|
|
|
<el-upload
|
|
<el-upload
|
|
@@ -698,7 +712,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref,reactive, onMounted } from 'vue'
|
|
|
|
|
|
|
+import { ref,reactive, onMounted, computed } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
import { useRouter } from 'vue-router'
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
import moment from 'moment'
|
|
import moment from 'moment'
|
|
@@ -751,6 +765,83 @@ const steps = ref<any[]>([]);
|
|
|
const stage = ref(Number(sessionStorage.getItem('progres')) || 0);
|
|
const stage = ref(Number(sessionStorage.getItem('progres')) || 0);
|
|
|
|
|
|
|
|
const stepsType = ref('')
|
|
const stepsType = ref('')
|
|
|
|
|
+const generateLoading = ref(false)
|
|
|
|
|
+const uploadLoading = reactive({
|
|
|
|
|
+ zbwj: false,
|
|
|
|
|
+ cgxqwj: false,
|
|
|
|
|
+ ckbs: false,
|
|
|
|
|
+ cqgg: false,
|
|
|
|
|
+})
|
|
|
|
|
+const uploadInputZbwj = ref<HTMLInputElement | null>(null)
|
|
|
|
|
+const uploadInputCgxq = ref<HTMLInputElement | null>(null)
|
|
|
|
|
+const uploadInputCkbs = ref<HTMLInputElement | null>(null)
|
|
|
|
|
+const uploadInputCqgg = ref<HTMLInputElement | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+/** 是否为可用的 docx 路径(去 query 后须以 .docx 结尾) */
|
|
|
|
|
+const isDocxPath = (path: any) => {
|
|
|
|
|
+ if (!path || typeof path !== 'string') return false
|
|
|
|
|
+ const clean = path.toLowerCase().replace(/\?.*$/, '').trim()
|
|
|
|
|
+ return clean.endsWith('.docx')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 投续标表是否已有可用参考标书(字段 c_ckbs) */
|
|
|
|
|
+const hasRefBidFile = computed(() => isDocxPath(projects.value?.c_ckbs))
|
|
|
|
|
+
|
|
|
|
|
+/** 报名任务(id=4)是否完成,完成后才进入标书拆解/上传 */
|
|
|
|
|
+const isTaskDone = (id: number | string) => {
|
|
|
|
|
+ const list = projects.value?.rwlcqd
|
|
|
|
|
+ if (!Array.isArray(list)) return false
|
|
|
|
|
+ const task = list.find((p: any) => p.id == id)
|
|
|
|
|
+ return !!task?.done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const canShowBidMakeActions = computed(() => isTaskDone(4))
|
|
|
|
|
+/** 须先点任务5「待确认」,才可生成标书 */
|
|
|
|
|
+const canGenerateBid = computed(() => isTaskDone(4) && isTaskDone(5))
|
|
|
|
|
+
|
|
|
|
|
+/** 任务展示名(兼容历史数据旧文案) */
|
|
|
|
|
+const getTaskDisplayText = (task: any) => {
|
|
|
|
|
+ if (task?.id == 5 || task?.id == '5') return '招标公告与需求拆解'
|
|
|
|
|
+ return task?.text || ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 待确认「招标公告与需求拆解」前校验必要文件 */
|
|
|
|
|
+const validateBidMakeFilesForConfirm = () => {
|
|
|
|
|
+ if (!projects.value?.c_zbwj) {
|
|
|
|
|
+ ElMessage.error('请先上传招标文件')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!projects.value?.c_cgxqwj) {
|
|
|
|
|
+ ElMessage.error('请先上传采购需求文件')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!hasRefBidFile.value) {
|
|
|
|
|
+ if (projects.value?.c_is_xb === true) {
|
|
|
|
|
+ ElMessage.error('续标项目未找到参考标书,请上传参考标书(.docx)')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('请先上传参考标书(.docx)')
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 仅当生成完成且已成功下载标书后,才可进入投标提交 */
|
|
|
|
|
+const canEnterBidSubmit = computed(() => {
|
|
|
|
|
+ const sczt = String(projects.value?.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ return sczt === '完成' && !!bidFile.value.flag
|
|
|
|
|
+})
|
|
|
|
|
+/**
|
|
|
|
|
+ * 进入投标提交后、开标监控创建前:显示「下载标书」
|
|
|
|
|
+ * 开标监控在登记投标时创建,成功后投续标写入 c_xgjk
|
|
|
|
|
+ */
|
|
|
|
|
+const showDownloadBidAfterSubmit = computed(() => {
|
|
|
|
|
+ const sczt = String(projects.value?.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ const hasKbjk = !!(projects.value?.c_xgjk)
|
|
|
|
|
+ return Number(stage.value) === 5 && sczt === '完成' && !hasKbjk
|
|
|
|
|
+})
|
|
|
|
|
+const downloadBidLoading = ref(false)
|
|
|
|
|
+const finishBidLoading = ref(false)
|
|
|
// const swb_status = ref('1')
|
|
// const swb_status = ref('1')
|
|
|
// const jsb_status = ref('1')
|
|
// const jsb_status = ref('1')
|
|
|
|
|
|
|
@@ -993,14 +1084,29 @@ const confirmTask = async (task: any) => {
|
|
|
const msg = getMessage(4)
|
|
const msg = getMessage(4)
|
|
|
if(msg){
|
|
if(msg){
|
|
|
ElMessage.error(msg)
|
|
ElMessage.error(msg)
|
|
|
|
|
+ }else if(!isTaskDone(4)){
|
|
|
|
|
+ ElMessage.error("请先完成「报名并获取招标公告」后,再进行「招标公告与需求拆解」")
|
|
|
|
|
+ }else if(!validateBidMakeFilesForConfirm()){
|
|
|
|
|
+ // 缺少招标文件/采购需求/参考标书时已提示
|
|
|
}else{
|
|
}else{
|
|
|
|
|
+ // 同步任务展示名为新文案后写入
|
|
|
|
|
+ const t = projects.value.rwlcqd?.find((p: any) => p.id == 5 || p.id == '5')
|
|
|
|
|
+ if (t) t.text = '招标公告与需求拆解'
|
|
|
updaterwlcqdProject(task)
|
|
updaterwlcqdProject(task)
|
|
|
}
|
|
}
|
|
|
}else if(task.id == "6"){
|
|
}else if(task.id == "6"){
|
|
|
updaterwlcqdProject(task)
|
|
updaterwlcqdProject(task)
|
|
|
}else if(task.id == "7"){
|
|
}else if(task.id == "7"){
|
|
|
- const res = await getfinishProjects()
|
|
|
|
|
- if(res){
|
|
|
|
|
|
|
+ if (finishBidLoading.value) return
|
|
|
|
|
+ finishBidLoading.value = true
|
|
|
|
|
+ const loading = ElLoading.service({
|
|
|
|
|
+ lock: true,
|
|
|
|
|
+ text: '加载中标结果数据中,请稍候...',
|
|
|
|
|
+ background: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getfinishProjects()
|
|
|
|
|
+ if(res){
|
|
|
finishBidOption.value = res
|
|
finishBidOption.value = res
|
|
|
if(task.type != 2){
|
|
if(task.type != 2){
|
|
|
dialogFinishBidVisible.value = true
|
|
dialogFinishBidVisible.value = true
|
|
@@ -1011,7 +1117,14 @@ const confirmTask = async (task: any) => {
|
|
|
}
|
|
}
|
|
|
}else{
|
|
}else{
|
|
|
finishBidOption.value = []
|
|
finishBidOption.value = []
|
|
|
|
|
+ ElMessage.warning('暂无中标结果数据')
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ ElMessage.error(e?.message || '加载中标结果失败,请稍后重试')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ finishBidLoading.value = false
|
|
|
|
|
+ loading.close()
|
|
|
|
|
+ }
|
|
|
}else if(task.id == "8"){
|
|
}else if(task.id == "8"){
|
|
|
sessionStorage.setItem('activeTab','won')
|
|
sessionStorage.setItem('activeTab','won')
|
|
|
sessionStorage.setItem('jkId', projects.value.c_xgjk);
|
|
sessionStorage.setItem('jkId', projects.value.c_xgjk);
|
|
@@ -1063,50 +1176,12 @@ const handleViewClick = (item: any,type: string) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}else if(item.n == 4){
|
|
}else if(item.n == 4){
|
|
|
- if(type == 'cqgg'){
|
|
|
|
|
- dialogFormFileVisible.value = true
|
|
|
|
|
- }else if(stepsType.value == '1'){
|
|
|
|
|
- let flag = projects.value.rwlcqd.find((p: any) => p.id == '4').done
|
|
|
|
|
- if(flag){
|
|
|
|
|
- dialogBidVisible.value = true
|
|
|
|
|
- }else{
|
|
|
|
|
- ElMessage.error("请先报名获取招标文件后,可进行提交资料")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- }else if(stepsType.value == '2'){
|
|
|
|
|
- let flag = projects.value.rwlcqd.find((p: any) => p.id == '5').done
|
|
|
|
|
- if(flag){
|
|
|
|
|
- if(soureProjects.value.c_history_tender){// 有历史标书
|
|
|
|
|
- if(soureProjects.value.c_history_tender.indexOf(".docx") != -1){// 历史标书是正常的docx文件
|
|
|
|
|
- // 生成招标文件
|
|
|
|
|
- api.getGenerateTender({txbId: projects.value.id}).then((res: any) => {
|
|
|
|
|
- if ((res as any).code === 200) {
|
|
|
|
|
- updateTypeProject(3)
|
|
|
|
|
- ElMessage.success("标书文件生成成功")
|
|
|
|
|
- bidFile.value.isFileFlag = false
|
|
|
|
|
- sessionStorage.setItem('isFileFlag', 'false')
|
|
|
|
|
- }else{
|
|
|
|
|
- ElMessage.error((res as any).content || "标书文件生成失败")
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- }else{
|
|
|
|
|
- ElMessage.error("请先确认上传参考标书")
|
|
|
|
|
- bidFile.value.isFileFlag = false
|
|
|
|
|
- sessionStorage.setItem('isFileFlag', 'false')
|
|
|
|
|
- }
|
|
|
|
|
- }else{
|
|
|
|
|
- // 无历史标书
|
|
|
|
|
- ElMessage.error("请先确认上传参考标书")
|
|
|
|
|
- bidFile.value.isFileFlag = false
|
|
|
|
|
- sessionStorage.setItem('isFileFlag', 'false')
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- }else{
|
|
|
|
|
- ElMessage.error("请先确认标书需求拆解与生成")
|
|
|
|
|
|
|
+ // 标书制作:stepsType 1/2 由页面「生成标书」/上传按钮处理;步骤4 仅进入投标提交
|
|
|
|
|
+ if(stepsType.value == '4'){
|
|
|
|
|
+ if (!canEnterBidSubmit.value) {
|
|
|
|
|
+ ElMessage.error('请先获取并下载标书后再进入投标提交')
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
- }else if(stepsType.value == '4'){
|
|
|
|
|
updateProject()
|
|
updateProject()
|
|
|
}
|
|
}
|
|
|
}else if(item.n == 5){
|
|
}else if(item.n == 5){
|
|
@@ -1250,6 +1325,12 @@ const getProjects = async () => {
|
|
|
money: soureProjects.value.c_nhhtsr,
|
|
money: soureProjects.value.c_nhhtsr,
|
|
|
address: soureProjects.value.c_query_address || ''
|
|
address: soureProjects.value.c_query_address || ''
|
|
|
}]
|
|
}]
|
|
|
|
|
+ // 续标:投续标参考标书为空时,尝试用在管项目历史标书(仅 .docx)回填
|
|
|
|
|
+ await syncRefBidFromSourceProject()
|
|
|
|
|
+ // 未生成完成时不允许沿用旧的「已下载」状态进入投标提交
|
|
|
|
|
+ syncBidDownloadFlagByAiStatus()
|
|
|
|
|
+ // 处于「获取标书」时,进入页面先拉取并展示生成步骤
|
|
|
|
|
+ await refreshBidProgressTipOnEnter()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(projects.value.rwlcqd[2].done == false){//匹配招标监控
|
|
if(projects.value.rwlcqd[2].done == false){//匹配招标监控
|
|
@@ -1678,11 +1759,11 @@ const handleBidFileChange = (file: any) => {
|
|
|
|
|
|
|
|
const submitBidFileForm = () => {
|
|
const submitBidFileForm = () => {
|
|
|
console.log(BidFileForm.value)
|
|
console.log(BidFileForm.value)
|
|
|
- const columnId = CONFIG.COLUMNIDARR[0]
|
|
|
|
|
|
|
+ // 参考标书写入投续标表字段 c_ckbs(modelId 1852)
|
|
|
const params: any = {
|
|
const params: any = {
|
|
|
- columnId: columnId,
|
|
|
|
|
- contentId: soureProjects.value.id,
|
|
|
|
|
- paraName: 'c_history_tender',
|
|
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ contentId: projects.value.id,
|
|
|
|
|
+ paraName: 'c_ckbs',
|
|
|
type: 3,
|
|
type: 3,
|
|
|
file: BidFileForm.value.file.raw
|
|
file: BidFileForm.value.file.raw
|
|
|
}
|
|
}
|
|
@@ -1690,15 +1771,16 @@ const submitBidFileForm = () => {
|
|
|
if((res as any).code === 200){
|
|
if((res as any).code === 200){
|
|
|
const content = res.content
|
|
const content = res.content
|
|
|
const params: any = {
|
|
const params: any = {
|
|
|
- columnId: CONFIG.COLUMNIDARR[0],
|
|
|
|
|
- modelId: CONFIG.MODELIDARR[7],
|
|
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ modelId: CONFIG.MODELIDARR[0],
|
|
|
content: JSON.stringify({
|
|
content: JSON.stringify({
|
|
|
- id: soureProjects.value.id,
|
|
|
|
|
- c_history_tender: content,
|
|
|
|
|
|
|
+ id: projects.value.id,
|
|
|
|
|
+ c_ckbs: content,
|
|
|
}),
|
|
}),
|
|
|
}
|
|
}
|
|
|
api.updateContent(params).then((res: any) => {
|
|
api.updateContent(params).then((res: any) => {
|
|
|
if((res as any).code === 200){
|
|
if((res as any).code === 200){
|
|
|
|
|
+ projects.value.c_ckbs = content
|
|
|
dialogFormBidFileVisible.value = false
|
|
dialogFormBidFileVisible.value = false
|
|
|
bidFile.value.isFileFlag = true
|
|
bidFile.value.isFileFlag = true
|
|
|
sessionStorage.setItem('isFileFlag', 'true')
|
|
sessionStorage.setItem('isFileFlag', 'true')
|
|
@@ -1818,17 +1900,21 @@ const handleFileChange3 = (file: any) => {
|
|
|
// filename.value = file.raw.name
|
|
// filename.value = file.raw.name
|
|
|
// ruleBidForm.file3.file = fileList
|
|
// ruleBidForm.file3.file = fileList
|
|
|
// }
|
|
// }
|
|
|
-// 更新项目进度状态
|
|
|
|
|
-const updateTypeProject = async (type:any) => {
|
|
|
|
|
|
|
+// 更新项目进度状态;extra 可附带清空 c_ai_sczt 等字段
|
|
|
|
|
+const updateTypeProject = async (type:any, extra: Record<string, any> = {}) => {
|
|
|
let xmjd_json = projects.value.c_xmjd_json;
|
|
let xmjd_json = projects.value.c_xmjd_json;
|
|
|
stepsType.value = type
|
|
stepsType.value = type
|
|
|
xmjd_json[xmjd_json.length-1].type = type
|
|
xmjd_json[xmjd_json.length-1].type = type
|
|
|
|
|
+ if (Object.prototype.hasOwnProperty.call(extra, 'c_ai_sczt')) {
|
|
|
|
|
+ projects.value.c_ai_sczt = extra.c_ai_sczt
|
|
|
|
|
+ }
|
|
|
const params: any = {
|
|
const params: any = {
|
|
|
columnId: CONFIG.COLUMNIDARR[2],
|
|
columnId: CONFIG.COLUMNIDARR[2],
|
|
|
modelId: CONFIG.MODELIDARR[0],
|
|
modelId: CONFIG.MODELIDARR[0],
|
|
|
content: JSON.stringify({
|
|
content: JSON.stringify({
|
|
|
id: projects.value.id,
|
|
id: projects.value.id,
|
|
|
- c_xmjd_json: xmjd_json
|
|
|
|
|
|
|
+ c_xmjd_json: xmjd_json,
|
|
|
|
|
+ ...extra,
|
|
|
}),
|
|
}),
|
|
|
}
|
|
}
|
|
|
const res = await api.updateContent(params)
|
|
const res = await api.updateContent(params)
|
|
@@ -1836,6 +1922,191 @@ const updateTypeProject = async (type:any) => {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/** 续标场景:将在管项目历史标书(docx)同步到投续标参考标书字段 c_ckbs */
|
|
|
|
|
+const syncRefBidFromSourceProject = async () => {
|
|
|
|
|
+ if (projects.value?.c_is_xb !== true) return
|
|
|
|
|
+ if (isDocxPath(projects.value?.c_ckbs)) return
|
|
|
|
|
+ const history = soureProjects.value?.c_history_tender
|
|
|
|
|
+ if (!isDocxPath(history)) return
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ modelId: CONFIG.MODELIDARR[0],
|
|
|
|
|
+ content: JSON.stringify({
|
|
|
|
|
+ id: projects.value.id,
|
|
|
|
|
+ c_ckbs: history,
|
|
|
|
|
+ }),
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await api.updateContent(params)
|
|
|
|
|
+ if ((res as any).code === 200) {
|
|
|
|
|
+ projects.value.c_ckbs = history
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const triggerUpload = (kind: 'zbwj' | 'cgxqwj' | 'ckbs' | 'cqgg') => {
|
|
|
|
|
+ if (!canShowBidMakeActions.value) {
|
|
|
|
|
+ ElMessage.error("请先完成「报名并获取招标公告」")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const map: Record<string, HTMLInputElement | null | undefined> = {
|
|
|
|
|
+ zbwj: uploadInputZbwj.value,
|
|
|
|
|
+ cgxqwj: uploadInputCgxq.value,
|
|
|
|
|
+ ckbs: uploadInputCkbs.value,
|
|
|
|
|
+ cqgg: uploadInputCqgg.value,
|
|
|
|
|
+ }
|
|
|
|
|
+ const input = map[kind]
|
|
|
|
|
+ if (input) {
|
|
|
|
|
+ input.value = ''
|
|
|
|
|
+ input.click()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onUploadFileChange = async (kind: 'zbwj' | 'cgxqwj' | 'ckbs' | 'cqgg', event: Event) => {
|
|
|
|
|
+ const input = event.target as HTMLInputElement
|
|
|
|
|
+ const file = input.files?.[0]
|
|
|
|
|
+ if (!file) return
|
|
|
|
|
+
|
|
|
|
|
+ const fieldMap: Record<string, string> = {
|
|
|
|
|
+ zbwj: 'c_zbwj',
|
|
|
|
|
+ cgxqwj: 'c_cgxqwj',
|
|
|
|
|
+ ckbs: 'c_ckbs',
|
|
|
|
|
+ cqgg: 'c_cqggwj',
|
|
|
|
|
+ }
|
|
|
|
|
+ const field = fieldMap[kind]
|
|
|
|
|
+ const name = file.name.toLowerCase()
|
|
|
|
|
+ const acceptRules: Record<string, { ok: boolean; tip: string }> = {
|
|
|
|
|
+ zbwj: { ok: name.endsWith('.pdf'), tip: '招标文件仅支持 .pdf 格式' },
|
|
|
|
|
+ cgxqwj: { ok: name.endsWith('.docx') || name.endsWith('.pdf'), tip: '采购需求文件仅支持 .docx / .pdf 格式' },
|
|
|
|
|
+ ckbs: { ok: name.endsWith('.docx'), tip: '参考标书仅支持 .docx 格式' },
|
|
|
|
|
+ cqgg: { ok: name.endsWith('.docx') || name.endsWith('.pdf'), tip: '澄清公告仅支持 .docx / .pdf 格式' },
|
|
|
|
|
+ }
|
|
|
|
|
+ const rule = acceptRules[kind]
|
|
|
|
|
+ if (rule && !rule.ok) {
|
|
|
|
|
+ ElMessage.error(rule.tip)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uploadLoading[kind] = true
|
|
|
|
|
+ const loading = ElLoading.service({
|
|
|
|
|
+ lock: true,
|
|
|
|
|
+ text: '文件上传中...',
|
|
|
|
|
+ background: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ const path = await getFIlePath({
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ contentId: projects.value.id,
|
|
|
|
|
+ paraName: field,
|
|
|
|
|
+ type: 3,
|
|
|
|
|
+ file,
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!path) {
|
|
|
|
|
+ ElMessage.error('上传失败')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ modelId: CONFIG.MODELIDARR[0],
|
|
|
|
|
+ content: JSON.stringify({
|
|
|
|
|
+ id: projects.value.id,
|
|
|
|
|
+ [field]: path,
|
|
|
|
|
+ }),
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await api.updateContent(params)
|
|
|
|
|
+ if ((res as any).code === 200) {
|
|
|
|
|
+ projects.value[field] = path
|
|
|
|
|
+ ElMessage.success('上传成功')
|
|
|
|
|
+ // 澄清公告同步到澄清栏目(可选附件)
|
|
|
|
|
+ if (kind === 'cqgg') {
|
|
|
|
|
+ submitFileForm(file)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error((res as any).content || '保存文件失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ ElMessage.error(e?.message || '上传失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploadLoading[kind] = false
|
|
|
|
|
+ loading.close()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 校验并生成标书 */
|
|
|
|
|
+const handleGenerateBid = async () => {
|
|
|
|
|
+ if (!canShowBidMakeActions.value) {
|
|
|
|
|
+ ElMessage.error("请先完成「报名并获取招标公告」")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!canGenerateBid.value) {
|
|
|
|
|
+ ElMessage.error("请先确认任务清单中的「招标公告与需求拆解」")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!projects.value.c_zbwj) {
|
|
|
|
|
+ ElMessage.error('请先上传招标文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!projects.value.c_cgxqwj) {
|
|
|
|
|
+ ElMessage.error('请先上传采购需求文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 投标:参考标书必传;续标:优先用投续标表参考标书,无值则必须上传
|
|
|
|
|
+ if (!hasRefBidFile.value) {
|
|
|
|
|
+ if (projects.value.c_is_xb === true) {
|
|
|
|
|
+ ElMessage.error('续标项目未找到参考标书,请上传参考标书(.docx)')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('投标项目请先上传参考标书(.docx)')
|
|
|
|
|
+ }
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ generateLoading.value = true
|
|
|
|
|
+ // 重新生成时清空已下载状态,禁止未下载就进入投标提交
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ generateProgressTip.value = ''
|
|
|
|
|
+ const loading = ElLoading.service({
|
|
|
|
|
+ lock: true,
|
|
|
|
|
+ text: '标书生成中...',
|
|
|
|
|
+ background: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 进入生成中:清空 AI 状态,避免仍残留「失败」导致刷新误回退
|
|
|
|
|
+ await updateTypeProject(3, { c_ai_sczt: '' })
|
|
|
|
|
+ projects.value.c_ai_sczt = ''
|
|
|
|
|
+ const res: any = await api.getGenerateTender({ txbId: projects.value.id })
|
|
|
|
|
+ if (res?.code === 200) {
|
|
|
|
|
+ // 任务5已由「待确认」完成;此处仅进入「获取标书」态
|
|
|
|
|
+ const xmjd_json = projects.value.c_xmjd_json
|
|
|
|
|
+ xmjd_json[xmjd_json.length - 1].type = 4
|
|
|
|
|
+ stepsType.value = 4 as any
|
|
|
|
|
+ projects.value.c_ai_sczt = ''
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ modelId: CONFIG.MODELIDARR[0],
|
|
|
|
|
+ content: JSON.stringify({
|
|
|
|
|
+ id: projects.value.id,
|
|
|
|
|
+ c_xmjd_json: xmjd_json,
|
|
|
|
|
+ c_ai_sczt: '',
|
|
|
|
|
+ }),
|
|
|
|
|
+ }
|
|
|
|
|
+ await api.updateContent(params)
|
|
|
|
|
+ generateProgressTip.value = '标书生成任务已提交,请点击「获取标书」查看进度或下载'
|
|
|
|
|
+ ElMessage.success(typeof res.content === 'string' && res.content ? res.content : '标书生成任务已提交')
|
|
|
|
|
+ bidFile.value.isFileFlag = hasRefBidFile.value
|
|
|
|
|
+ sessionStorage.setItem('isFileFlag', hasRefBidFile.value ? 'true' : 'false')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await updateTypeProject(2)
|
|
|
|
|
+ ElMessage.error(res?.content || res?.message || '标书文件生成失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ await updateTypeProject(2)
|
|
|
|
|
+ ElMessage.error(e?.message || e?.response?.data?.content || '标书文件生成失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ generateLoading.value = false
|
|
|
|
|
+ loading.close()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 更新项目文件
|
|
// 更新项目文件
|
|
|
const updateFIleProject = async (loading:any) => {
|
|
const updateFIleProject = async (loading:any) => {
|
|
|
let param = {
|
|
let param = {
|
|
@@ -1880,51 +2151,250 @@ const getFIlePath = async (params: any) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
const bidFile = ref({
|
|
const bidFile = ref({
|
|
|
- flag: sessionStorage.getItem('bidFileFlag') == 'true' ? true : false, //是否获取到标书
|
|
|
|
|
|
|
+ flag: false, // 本次是否已成功下载标书(仅下载成功后为 true)
|
|
|
swb:false,
|
|
swb:false,
|
|
|
jsb:false,
|
|
jsb:false,
|
|
|
isFileFlag: sessionStorage.getItem('isFileFlag') == 'true' ? true : false, //是否历史标书
|
|
isFileFlag: sessionStorage.getItem('isFileFlag') == 'true' ? true : false, //是否历史标书
|
|
|
})
|
|
})
|
|
|
|
|
+const getBidLoading = ref(false)
|
|
|
|
|
+const generateProgressTip = ref('')
|
|
|
|
|
+
|
|
|
|
|
+const clearBidDownloadedFlag = () => {
|
|
|
|
|
+ bidFile.value.flag = false
|
|
|
|
|
+ sessionStorage.setItem('bidFileFlag', 'false')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 按 c_ai_sczt 同步下载态:非「完成」一律不可进入投标提交 */
|
|
|
|
|
+const syncBidDownloadFlagByAiStatus = () => {
|
|
|
|
|
+ const sczt = String(projects.value?.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ if (sczt !== '完成') {
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 已完成:仅当本会话曾成功下载过才保持可进入;否则仍需点「获取标书」下载
|
|
|
|
|
+ const downloaded = sessionStorage.getItem('bidFileFlag') == 'true'
|
|
|
|
|
+ bidFile.value.flag = downloaded
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 下载 AI 生成标书(c_ai_scbs) */
|
|
|
|
|
+const downloadAiBidFile = (aiscbs: string) => {
|
|
|
|
|
+ if (!aiscbs) {
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ ElMessage.error('暂无标书文件路径')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ let filename = aiscbs.split('/').pop() || ''
|
|
|
|
|
+ filename = filename.replace(/\?.*$/, '')
|
|
|
|
|
+ if (!filename.toLowerCase().includes('.docx')) {
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ ElMessage.error('暂无标书文件')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ UTIL.downloadFile(filename, CONFIG.FILEPATH + (aiscbs.replace('/data/model', '')))
|
|
|
|
|
+ bidFile.value.flag = true
|
|
|
|
|
+ sessionStorage.setItem('bidFileFlag', 'true')
|
|
|
|
|
+ ElMessage.success('获取标书成功')
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 投标提交阶段:下载当前投续标已生成的标书(不依赖获取流程) */
|
|
|
|
|
+const downloadCurrentBidFile = async () => {
|
|
|
|
|
+ downloadBidLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const latest = await getTypeProjects(CONFIG.COLUMNIDARR[2], projects.value.id, 'id')
|
|
|
|
|
+ if (latest?.id) {
|
|
|
|
|
+ projects.value.c_ai_sczt = latest.c_ai_sczt
|
|
|
|
|
+ projects.value.c_ai_scbs = latest.c_ai_scbs
|
|
|
|
|
+ }
|
|
|
|
|
+ const sczt = String(projects.value?.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ if (sczt !== '完成') {
|
|
|
|
|
+ ElMessage.error('标书尚未生成完成,暂不可下载')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const aiscbs = projects.value.c_ai_scbs || ''
|
|
|
|
|
+ if (!aiscbs) {
|
|
|
|
|
+ ElMessage.error('暂无标书文件路径')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ let filename = aiscbs.split('/').pop() || ''
|
|
|
|
|
+ filename = filename.replace(/\?.*$/, '')
|
|
|
|
|
+ if (!filename.toLowerCase().includes('.docx')) {
|
|
|
|
|
+ ElMessage.error('暂无标书文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ UTIL.downloadFile(filename, CONFIG.FILEPATH + (aiscbs.replace('/data/model', '')))
|
|
|
|
|
+ ElMessage.success('下载标书成功')
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ ElMessage.error(e?.message || '下载标书失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ downloadBidLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 生成失败:回退到上传/生成资料步骤,并清空 AI 生成状态,避免下次误判为失败 */
|
|
|
|
|
+const revertBidMakeOnGenerateFail = async () => {
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ generateProgressTip.value = '标书生成失败,请重新上传相关文件后再次生成'
|
|
|
|
|
+ projects.value.c_ai_sczt = ''
|
|
|
|
|
+ const xmjd_json = projects.value.c_xmjd_json
|
|
|
|
|
+ xmjd_json[xmjd_json.length - 1].type = 2
|
|
|
|
|
+ stepsType.value = 2 as any
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ columnId: CONFIG.COLUMNIDARR[2],
|
|
|
|
|
+ modelId: CONFIG.MODELIDARR[0],
|
|
|
|
|
+ content: JSON.stringify({
|
|
|
|
|
+ id: projects.value.id,
|
|
|
|
|
+ c_xmjd_json: xmjd_json,
|
|
|
|
|
+ c_ai_sczt: '',
|
|
|
|
|
+ }),
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await api.updateContent(params)
|
|
|
|
|
+ if ((res as any).code !== 200) {
|
|
|
|
|
+ ElMessage.error((res as any).content || '回退进度失败,请稍后重试')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 将进度接口结果格式化为页面提示文案 */
|
|
|
|
|
+const formatTenderProgressTip = (progress: any): string => {
|
|
|
|
|
+ if (!progress || typeof progress !== 'object') {
|
|
|
|
|
+ return '标书正在生成中,请稍后再试'
|
|
|
|
|
+ }
|
|
|
|
|
+ const st = String(progress.status || '')
|
|
|
|
|
+ const msg = typeof progress.message === 'string' ? progress.message : ''
|
|
|
|
|
+ const total = Number(progress.total_steps) || 6
|
|
|
|
|
+ const doneList = Array.isArray(progress.completed_steps) ? progress.completed_steps : []
|
|
|
|
|
+ const doneCount = doneList.length
|
|
|
|
|
+ const stepName = progress.current_step_name || (progress.current_step != null ? `第 ${progress.current_step} 步` : '')
|
|
|
|
|
+
|
|
|
|
|
+ if (st === 'queued') {
|
|
|
|
|
+ return msg || '任务已排队,请稍后再获取标书'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (st === 'processing') {
|
|
|
|
|
+ return msg || '任务处理中,请稍后再获取标书'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (st === 'failed') {
|
|
|
|
|
+ return msg || '标书生成失败,请重新上传相关文件后再生成'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (progress.current_step != null || doneCount > 0 || stepName) {
|
|
|
|
|
+ const stepPart = stepName
|
|
|
|
|
+ ? (st === 'running' ? `${stepName} 正在处理` : `${stepName} 处理完成`)
|
|
|
|
|
+ : ''
|
|
|
|
|
+ const base = `标书生成中:已完成 ${doneCount}/${total} 步`
|
|
|
|
|
+ return [base, stepPart || msg].filter(Boolean).join('。')
|
|
|
|
|
+ }
|
|
|
|
|
+ return msg || '标书正在生成中,请稍后再试'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 根据进度接口响应更新前端提示;silent=true 时只更新文案,不弹 Toast */
|
|
|
|
|
+const tipTenderProgress = (progress: any, silent = false) => {
|
|
|
|
|
+ // 生成过程中不可进入投标提交
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ const tip = formatTenderProgressTip(progress)
|
|
|
|
|
+ generateProgressTip.value = tip
|
|
|
|
|
+ if (silent) return
|
|
|
|
|
+ const st = String(progress?.status || '')
|
|
|
|
|
+ if (st === 'failed') {
|
|
|
|
|
+ ElMessage.error(tip)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.info(tip)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 进入/刷新指挥页:若处于「获取标书」态,先请求进度并展示 */
|
|
|
|
|
+const refreshBidProgressTipOnEnter = async () => {
|
|
|
|
|
+ // 大阶段在「标书制作」,且子状态为获取标书(type=4)或生成中(type=3)
|
|
|
|
|
+ if (Number(stage.value) !== 4) return
|
|
|
|
|
+ const type = String(stepsType.value)
|
|
|
|
|
+ if (!(type === '4' || type === '3')) return
|
|
|
|
|
+ if (!projects.value?.id) return
|
|
|
|
|
+
|
|
|
|
|
+ const sczt = String(projects.value?.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ if (sczt === '完成') {
|
|
|
|
|
+ generateProgressTip.value = bidFile.value.flag
|
|
|
|
|
+ ? '标书已下载,可进入投标提交'
|
|
|
|
|
+ : '标书已生成完成,请点击「获取标书」下载'
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (sczt === '失败') {
|
|
|
|
|
+ // 进入/刷新只展示提示,不自动回退;由用户点「获取标书」再执行回退清空
|
|
|
|
|
+ generateProgressTip.value = '标书生成失败,请点击「获取标书」回退并重新上传相关文件后再生成'
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const progress: any = await api.getTenderProgress(projects.value.id)
|
|
|
|
|
+ tipTenderProgress(progress, true)
|
|
|
|
|
+ } catch (err: any) {
|
|
|
|
|
+ const status = err?.response?.status
|
|
|
|
|
+ if (status === 404) {
|
|
|
|
|
+ generateProgressTip.value = '进度任务不存在或尚未就绪,请稍后点击「获取标书」刷新'
|
|
|
|
|
+ } else {
|
|
|
|
|
+ generateProgressTip.value = '标书正在生成中,进度暂不可用,请稍后点击「获取标书」刷新'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const getBidFile = async () => {
|
|
const getBidFile = async () => {
|
|
|
|
|
+ if (!projects.value?.id) {
|
|
|
|
|
+ ElMessage.error('未找到投续标数据')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ getBidLoading.value = true
|
|
|
const loading = ElLoading.service({
|
|
const loading = ElLoading.service({
|
|
|
lock: true,
|
|
lock: true,
|
|
|
text: '获取中...',
|
|
text: '获取中...',
|
|
|
background: 'rgba(0, 0, 0, 0.7)',
|
|
background: 'rgba(0, 0, 0, 0.7)',
|
|
|
})
|
|
})
|
|
|
- setTimeout(() => {
|
|
|
|
|
- // const fileName = `杨浦区图书馆总馆物业管理服务项目投标文件.docx`
|
|
|
|
|
- // UTIL.downloadFile(fileName, filePath)
|
|
|
|
|
- const aiscbs = projects.value.c_ai_scbs || "";
|
|
|
|
|
- // let aiscbs = "/data/model/shenqin_data/57f7bf655e994fa8a8867e4bc55e8709/1779-杨浦区图书馆总馆物业管理服务项目招标文件.pdf-ca9d5daf-443b-44c6-a1f3-bf43890a55d8.docx"
|
|
|
|
|
- if(aiscbs){
|
|
|
|
|
- let filename = aiscbs.split('/').pop() || ''
|
|
|
|
|
- filename = filename.replace(/\?.*$/, '')
|
|
|
|
|
- if(filename.includes('.docx')){
|
|
|
|
|
- UTIL.downloadFile(filename, CONFIG.FILEPATH + (aiscbs.replace("/data/model","")))
|
|
|
|
|
- loading.close()
|
|
|
|
|
- ElMessage.success('获取标书成功')
|
|
|
|
|
- bidFile.value.flag = true
|
|
|
|
|
- sessionStorage.setItem('bidFileFlag', "true")
|
|
|
|
|
- }else{
|
|
|
|
|
- sessionStorage.setItem('bidFileFlag', "false")
|
|
|
|
|
- ElMessage.error('暂无标书文件')
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 刷新投续标最新状态
|
|
|
|
|
+ const latest = await getTypeProjects(CONFIG.COLUMNIDARR[2], projects.value.id, 'id')
|
|
|
|
|
+ if (!latest?.id) {
|
|
|
|
|
+ ElMessage.error('获取投续标数据失败')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ projects.value.c_ai_sczt = latest.c_ai_sczt
|
|
|
|
|
+ projects.value.c_ai_scbs = latest.c_ai_scbs
|
|
|
|
|
+
|
|
|
|
|
+ const sczt = String(latest.c_ai_sczt ?? '').trim()
|
|
|
|
|
+ if (sczt === '完成') {
|
|
|
|
|
+ const ok = downloadAiBidFile(latest.c_ai_scbs || '')
|
|
|
|
|
+ generateProgressTip.value = ok ? '标书已下载,可进入投标提交' : '标书状态为完成,但文件不可用,请联系管理员'
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (sczt === '失败') {
|
|
|
|
|
+ await revertBidMakeOnGenerateFail()
|
|
|
|
|
+ ElMessage.error('标书生成失败,请重新上传相关文件后再生成')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 空值:仍在生成中,更新步骤提示(仅主动点击时可回退,刷新不改 stepsType)
|
|
|
|
|
+ clearBidDownloadedFlag()
|
|
|
|
|
+ loading.setText('标书生成中,查询进度...')
|
|
|
|
|
+ try {
|
|
|
|
|
+ const progress: any = await api.getTenderProgress(projects.value.id)
|
|
|
|
|
+ if (String(progress?.status || '') === 'failed') {
|
|
|
|
|
+ await revertBidMakeOnGenerateFail()
|
|
|
|
|
+ ElMessage.error(formatTenderProgressTip(progress) || '标书生成失败,请重新上传相关文件后再生成')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ tipTenderProgress(progress, false)
|
|
|
|
|
+ } catch (err: any) {
|
|
|
|
|
+ const status = err?.response?.status
|
|
|
|
|
+ const detail = err?.response?.data?.detail || err?.message
|
|
|
|
|
+ if (status === 404) {
|
|
|
|
|
+ generateProgressTip.value = '进度任务不存在,请确认已发起生成'
|
|
|
|
|
+ ElMessage.error(generateProgressTip.value)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ generateProgressTip.value = detail || '查询生成进度失败,请稍后重试'
|
|
|
|
|
+ ElMessage.warning(generateProgressTip.value)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- }else{
|
|
|
|
|
- loading.close()
|
|
|
|
|
- // bidFile.value.flag = true
|
|
|
|
|
- ElMessage.error('获取标书失败')
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- }, 3000);
|
|
|
|
|
-
|
|
|
|
|
- // if(bittype == 'swb'){
|
|
|
|
|
- // ElMessage.success('获取商务标书成功')
|
|
|
|
|
- // bidFile.value.swb = true
|
|
|
|
|
- // }else if(bittype == 'jsb'){
|
|
|
|
|
- // ElMessage.success('获取技术标书成功')
|
|
|
|
|
- // bidFile.value.jsb = true
|
|
|
|
|
- // }
|
|
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ ElMessage.error(e?.message || '获取标书失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ getBidLoading.value = false
|
|
|
|
|
+ loading.close()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleChange = (val: any) => {
|
|
const handleChange = (val: any) => {
|
|
@@ -1998,6 +2468,38 @@ const updateProjectParams = async (columnId:any,modelId:any,content:any) => {
|
|
|
background: var(--bg);
|
|
background: var(--bg);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.bid-make-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+.bid-make-left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+.bid-make-right {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.bid-make-tip {
|
|
|
|
|
+ color: var(--text2);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+.bid-progress-tip {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
th, td {
|
|
th, td {
|
|
|
text-align: left;
|
|
text-align: left;
|
|
|
padding: 10px 12px;
|
|
padding: 10px 12px;
|