招标解析
This commit is contained in:
parent
8e1e74067d
commit
62b8abc4a8
File diff suppressed because one or more lines are too long
|
|
@ -25,161 +25,7 @@
|
||||||
<AnalysisResultPanel ref="resultPanel" :main-tabs="mainTabs" :default-main-tab="activeMainTab"
|
<AnalysisResultPanel ref="resultPanel" :main-tabs="mainTabs" :default-main-tab="activeMainTab"
|
||||||
:default-sub-tab="defaultSubTab" @main-tab-change="handleMainTabChange"
|
:default-sub-tab="defaultSubTab" @main-tab-change="handleMainTabChange"
|
||||||
@sub-tab-change="handleSubTabChange" :hide-main-tabs="true" :hide-header="true"
|
@sub-tab-change="handleSubTabChange" :hide-main-tabs="true" :hide-header="true"
|
||||||
:key="activeMainTab">
|
:key="activeMainTab" />
|
||||||
<!-- 项目信息 - 招标人 -->
|
|
||||||
<template #project-tenderer>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">招标人信息</div>
|
|
||||||
<div class="section-content">{{ analysisData.tenderer || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 基础信息 -->
|
|
||||||
<template #project-basic>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">基础信息</div>
|
|
||||||
<div class="section-content">{{ analysisData.basicInfo || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 代理机构 -->
|
|
||||||
<template #project-agency>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">代理机构</div>
|
|
||||||
<div class="section-content">{{ analysisData.agency || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 关键时间 -->
|
|
||||||
<template #project-keyTime>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">关键时间</div>
|
|
||||||
<div class="section-content">{{ analysisData.keyTime || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 联合体要求 -->
|
|
||||||
<template #project-consortium>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">联合体要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.consortium || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 分包要求 -->
|
|
||||||
<template #project-subcontract>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">分包要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.subcontract || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 最高限价 -->
|
|
||||||
<template #project-maxPrice>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">最高限价</div>
|
|
||||||
<div class="section-content">{{ analysisData.maxPrice || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 项目信息 - 响应和偏差 -->
|
|
||||||
<template #project-response>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">响应和偏差要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.response || '招标文件无此内容' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">澄清要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.clarification || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 标段信息 -->
|
|
||||||
<template #bid>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">标段信息</div>
|
|
||||||
<div class="section-content">{{ analysisData.bidInfo || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 保证金 - 投标保证金 -->
|
|
||||||
<template #deposit-bidDeposit>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">投标保证金金额</div>
|
|
||||||
<div class="section-content">{{ analysisData.bidDepositAmount || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">投标保证金形式</div>
|
|
||||||
<div class="section-content">{{ analysisData.bidDepositForm || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">投标保证金提交期限</div>
|
|
||||||
<div class="section-content">{{ analysisData.bidDepositSubmitDeadline || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">投标保证金退还期限</div>
|
|
||||||
<div class="section-content">{{ analysisData.bidDepositRefundDeadline || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 保证金 - 履约保证金 -->
|
|
||||||
<template #deposit-performanceDeposit>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">履约保证金</div>
|
|
||||||
<div class="section-content">{{ analysisData.performanceDeposit || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 其他标签页内容 -->
|
|
||||||
<template #qualification>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">资格要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.qualification || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #performance>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">业绩要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.performance || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #finance>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">财务要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.finance || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #personnel>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">人员要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.personnel || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #evaluation>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">开评定标要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.evaluation || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #rejection>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">废标项</div>
|
|
||||||
<div class="section-content">{{ analysisData.rejection || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #document>
|
|
||||||
<div class="content-section">
|
|
||||||
<div class="section-title">投标文件要求</div>
|
|
||||||
<div class="section-content">{{ analysisData.document || '--' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</AnalysisResultPanel>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:文档预览面板 -->
|
<!-- 右侧:文档预览面板 -->
|
||||||
|
|
@ -200,8 +46,106 @@ import { decryptWithSM4, encryptWithSM4 } from '@/utils/sm'
|
||||||
import AnalysisHeader from './child/AnalysisHeader.vue'
|
import AnalysisHeader from './child/AnalysisHeader.vue'
|
||||||
import AnalysisResultPanel from './child/AnalysisResultPanel.vue'
|
import AnalysisResultPanel from './child/AnalysisResultPanel.vue'
|
||||||
import DocumentPreviewPanel from './child/DocumentPreviewPanel.vue'
|
import DocumentPreviewPanel from './child/DocumentPreviewPanel.vue'
|
||||||
|
import analysisResultMock from '../analysisResultMock.json'
|
||||||
// import { getBidDetailAPI } from '@/api/analysis/analysis'
|
// import { getBidDetailAPI } from '@/api/analysis/analysis'
|
||||||
|
|
||||||
|
const HTML_TAG_REG = /<\/?[a-z][\s\S]*>/i
|
||||||
|
const MAX_PARSE_DEPTH = 5
|
||||||
|
|
||||||
|
const formatSectionValue = (raw, depth = 0) => {
|
||||||
|
const fallback = { content: '--', isHtml: false }
|
||||||
|
if (raw === undefined || raw === null) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
if (depth > MAX_PARSE_DEPTH) {
|
||||||
|
const content = String(raw)
|
||||||
|
return { content, isHtml: HTML_TAG_REG.test(content) }
|
||||||
|
}
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (!trimmed) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(trimmed)
|
||||||
|
return formatSectionValue(parsed, depth + 1)
|
||||||
|
} catch (error) {
|
||||||
|
return { content: trimmed, isHtml: HTML_TAG_REG.test(trimmed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
const lines = raw.map(item => {
|
||||||
|
if (typeof item === 'object' && item !== null) {
|
||||||
|
const label = item.name || item.label || item.title || item.key || ''
|
||||||
|
const valueField = item.content ?? item.value ?? item.text ?? ''
|
||||||
|
const formatted = formatSectionValue(valueField, depth + 1)
|
||||||
|
return label ? `${label}:${formatted.content}` : formatted.content
|
||||||
|
}
|
||||||
|
return formatSectionValue(item, depth + 1).content
|
||||||
|
}).filter(Boolean)
|
||||||
|
const content = lines.length > 0 ? lines.join('\n') : '--'
|
||||||
|
return { content, isHtml: false }
|
||||||
|
}
|
||||||
|
if (typeof raw === 'object') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(raw, 'value')) {
|
||||||
|
return formatSectionValue(raw.value, depth + 1)
|
||||||
|
}
|
||||||
|
const lines = Object.entries(raw).map(([key, value]) => {
|
||||||
|
const formatted = formatSectionValue(value, depth + 1)
|
||||||
|
return `${key}:${formatted.content}`
|
||||||
|
}).filter(Boolean)
|
||||||
|
const content = lines.length > 0 ? lines.join('\n') : '--'
|
||||||
|
return { content, isHtml: false }
|
||||||
|
}
|
||||||
|
const content = String(raw)
|
||||||
|
return { content, isHtml: HTML_TAG_REG.test(content) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectSectionsFromNode = (node) => {
|
||||||
|
const sections = []
|
||||||
|
const traverse = (target) => {
|
||||||
|
if (!target) return
|
||||||
|
if (Object.prototype.hasOwnProperty.call(target, 'tagValue')) {
|
||||||
|
const formatted = formatSectionValue(target.tagValue)
|
||||||
|
sections.push({
|
||||||
|
id: target.id,
|
||||||
|
title: target.name,
|
||||||
|
content: formatted.content,
|
||||||
|
isHtml: formatted.isHtml
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (target.children && target.children.length) {
|
||||||
|
sortByWeight(target.children).forEach(child => traverse(child))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(node)
|
||||||
|
return sections
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortByWeight = (list = []) => {
|
||||||
|
return [...list].sort((a, b) => {
|
||||||
|
const weightA = a?.weight ?? 0
|
||||||
|
const weightB = b?.weight ?? 0
|
||||||
|
return weightA - weightB
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTabsFromTree = (tree = []) => {
|
||||||
|
return sortByWeight(tree).map(node => {
|
||||||
|
const subTabs = sortByWeight(node.children || []).map(child => ({
|
||||||
|
name: child.id,
|
||||||
|
label: child.name,
|
||||||
|
sections: collectSectionsFromNode(child)
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
name: node.id,
|
||||||
|
label: node.name,
|
||||||
|
subTabs,
|
||||||
|
sections: subTabs.length === 0 ? collectSectionsFromNode(node) : []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AnalysisBidView',
|
name: 'AnalysisBidView',
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -213,79 +157,15 @@ export default {
|
||||||
return {
|
return {
|
||||||
proId: decryptWithSM4(this.$route.query.proId),
|
proId: decryptWithSM4(this.$route.query.proId),
|
||||||
bidId: decryptWithSM4(this.$route.query.bidId),
|
bidId: decryptWithSM4(this.$route.query.bidId),
|
||||||
activeMainTab: 'project',
|
activeMainTab: '',
|
||||||
defaultSubTab: '', // 初始为空,由 AnalysisResultPanel 组件自动获取第一个子标签
|
defaultSubTab: '', // 初始为空,由 AnalysisResultPanel 组件自动获取第一个子标签
|
||||||
analysisData: {},
|
|
||||||
tenderDocumentUrl: '',
|
tenderDocumentUrl: '',
|
||||||
tenderDocumentTitle: '',
|
tenderDocumentTitle: '',
|
||||||
tenderDocumentKey: '',
|
tenderDocumentKey: '',
|
||||||
bidDocumentUrl: '',
|
bidDocumentUrl: '',
|
||||||
bidDocumentTitle: '',
|
bidDocumentTitle: '',
|
||||||
bidDocumentKey: '',
|
bidDocumentKey: '',
|
||||||
mainTabs: [
|
mainTabs: []
|
||||||
{
|
|
||||||
name: 'project',
|
|
||||||
label: '项目信息',
|
|
||||||
subTabs: [
|
|
||||||
{ name: 'tenderer', label: '招标人' },
|
|
||||||
{ name: 'basic', label: '基础信息' },
|
|
||||||
{ name: 'agency', label: '代理机构' },
|
|
||||||
{ name: 'keyTime', label: '关键时间' },
|
|
||||||
{ name: 'consortium', label: '联合体要求' },
|
|
||||||
{ name: 'subcontract', label: '分包要求' },
|
|
||||||
{ name: 'maxPrice', label: '最高限价' },
|
|
||||||
{ name: 'response', label: '响应和偏差' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bid',
|
|
||||||
label: '标段信息',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'deposit',
|
|
||||||
label: '保证金',
|
|
||||||
subTabs: [
|
|
||||||
{ name: 'bidDeposit', label: '投标保证金' },
|
|
||||||
{ name: 'performanceDeposit', label: '履约保证金' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualification',
|
|
||||||
label: '资格要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'performance',
|
|
||||||
label: '业绩要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finance',
|
|
||||||
label: '财务要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'personnel',
|
|
||||||
label: '人员要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'evaluation',
|
|
||||||
label: '开评定标要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rejection',
|
|
||||||
label: '废标项',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document',
|
|
||||||
label: '投标文件要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
@ -302,34 +182,16 @@ export default {
|
||||||
// TODO: 根据实际API调整,获取标段解析结果数据
|
// TODO: 根据实际API调整,获取标段解析结果数据
|
||||||
// const res = await getBidDetailAPI({ proId: this.proId, bidId: this.bidId })
|
// const res = await getBidDetailAPI({ proId: this.proId, bidId: this.bidId })
|
||||||
// if (res.code === 200) {
|
// if (res.code === 200) {
|
||||||
// this.analysisData = res.data || {}
|
// this.mainTabs = buildTabsFromTree(res.data || [])
|
||||||
// this.initDocumentData(res.data)
|
// this.initDocumentData(res.data)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 临时模拟数据
|
// 临时使用本地 mock 数据
|
||||||
this.analysisData = {
|
const parsedTabs = buildTabsFromTree(analysisResultMock)
|
||||||
tenderer: '中国南方电网有限责任公司',
|
this.mainTabs = parsedTabs
|
||||||
basic: '基础信息内容',
|
if (parsedTabs.length > 0) {
|
||||||
agency: '代理机构信息',
|
this.activeMainTab = parsedTabs[0].name
|
||||||
keyTime: '关键时间信息',
|
this.defaultSubTab = parsedTabs[0].subTabs?.[0]?.name || ''
|
||||||
consortium: '联合体要求内容',
|
|
||||||
subcontract: '分包要求内容',
|
|
||||||
maxPrice: '最高限价信息',
|
|
||||||
response: '招标文件无此内容',
|
|
||||||
clarification: '澄清要求内容...',
|
|
||||||
bidInfo: '标段信息内容',
|
|
||||||
bidDepositAmount: '投标保证金金额',
|
|
||||||
bidDepositForm: '投标保证金形式',
|
|
||||||
bidDepositSubmitDeadline: '投标截止日前一天',
|
|
||||||
bidDepositRefundDeadline: '退还期限信息',
|
|
||||||
performanceDeposit: '履约保证金信息',
|
|
||||||
qualification: '资格要求内容',
|
|
||||||
performance: '业绩要求内容',
|
|
||||||
finance: '财务要求内容',
|
|
||||||
personnel: '人员要求内容',
|
|
||||||
evaluation: '开评定标要求内容',
|
|
||||||
rejection: '废标项内容',
|
|
||||||
document: '投标文件要求内容'
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取标段详情失败:', error)
|
console.error('获取标段详情失败:', error)
|
||||||
|
|
@ -609,30 +471,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-content {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
line-height: 1.8;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
background: #F5F7FA;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -8,48 +8,80 @@
|
||||||
<!-- 主标签页(可选,可通过hideMainTabs隐藏) -->
|
<!-- 主标签页(可选,可通过hideMainTabs隐藏) -->
|
||||||
<el-tabs v-if="!hideMainTabs" v-model="activeMainTab" @tab-click="handleMainTabClick" class="main-tabs">
|
<el-tabs v-if="!hideMainTabs" v-model="activeMainTab" @tab-click="handleMainTabClick" class="main-tabs">
|
||||||
<el-tab-pane v-for="tab in mainTabs" :key="tab.name" :label="tab.label" :name="tab.name">
|
<el-tab-pane v-for="tab in mainTabs" :key="tab.name" :label="tab.label" :name="tab.name">
|
||||||
<!-- 子标签页 -->
|
<template v-if="hasSubTabs(tab)">
|
||||||
<el-tabs v-if="tab.subTabs && tab.subTabs.length > 0" v-model="activeSubTab"
|
<el-tabs v-model="activeSubTab" @tab-click="handleSubTabClick" class="sub-tabs">
|
||||||
@tab-click="handleSubTabClick" class="sub-tabs">
|
<el-tab-pane v-for="subTab in tab.subTabs" :key="subTab.name" :label="subTab.label"
|
||||||
<el-tab-pane v-for="subTab in tab.subTabs" :key="subTab.name" :label="subTab.label"
|
:name="subTab.name">
|
||||||
:name="subTab.name">
|
<div class="tab-content">
|
||||||
<div class="tab-content">
|
<template v-if="hasSections(subTab.sections)">
|
||||||
<slot :name="`${tab.name}-${subTab.name}`">
|
<div class="content-section" v-for="section in subTab.sections"
|
||||||
<div class="empty-content">{{ subTab.label }}内容</div>
|
:key="section.id">
|
||||||
</slot>
|
<div class="section-title">{{ section.title }}</div>
|
||||||
</div>
|
<div class="section-content" v-if="!section.isHtml"
|
||||||
</el-tab-pane>
|
v-html="formatPlainText(section.content)"></div>
|
||||||
</el-tabs>
|
<div class="section-content html-content" v-else v-html="section.content">
|
||||||
<!-- 没有子标签页的内容 -->
|
</div>
|
||||||
<div v-else class="tab-content">
|
</div>
|
||||||
<slot :name="tab.name">
|
</template>
|
||||||
<div class="empty-content">{{ tab.label }}内容</div>
|
<div v-else class="empty-content">暂无数据</div>
|
||||||
</slot>
|
</div>
|
||||||
</div>
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="tab-content">
|
||||||
|
<template v-if="hasSections(tab.sections)">
|
||||||
|
<div class="content-section" v-for="section in tab.sections" :key="section.id">
|
||||||
|
<div class="section-title">{{ section.title }}</div>
|
||||||
|
<div class="section-content" v-if="!section.isHtml"
|
||||||
|
v-html="formatPlainText(section.content)"></div>
|
||||||
|
<div class="section-content html-content" v-else v-html="section.content"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="empty-content">暂无数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<!-- 隐藏主标签页时,直接显示当前主标签的内容 -->
|
<!-- 隐藏主标签页时,直接显示当前主标签的内容 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-for="tab in mainTabs">
|
<template v-for="tab in mainTabs">
|
||||||
<template v-if="tab.name === activeMainTab">
|
<template v-if="tab.name === activeMainTab">
|
||||||
<!-- 子标签页 -->
|
<template v-if="hasSubTabs(tab)">
|
||||||
<el-tabs v-if="tab.subTabs && tab.subTabs.length > 0" v-model="activeSubTab"
|
<el-tabs v-model="activeSubTab" @tab-click="handleSubTabClick" class="sub-tabs"
|
||||||
@tab-click="handleSubTabClick" class="sub-tabs" :key="`sub-tabs-${tab.name}`">
|
:key="`sub-tabs-${tab.name}`">
|
||||||
<el-tab-pane v-for="subTab in tab.subTabs" :key="subTab.name" :label="subTab.label"
|
<el-tab-pane v-for="subTab in tab.subTabs" :key="subTab.name" :label="subTab.label"
|
||||||
:name="subTab.name">
|
:name="subTab.name">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<slot :name="`${tab.name}-${subTab.name}`">
|
<template v-if="hasSections(subTab.sections)">
|
||||||
<div class="empty-content">{{ subTab.label }}内容</div>
|
<div class="content-section" v-for="section in subTab.sections"
|
||||||
</slot>
|
:key="section.id">
|
||||||
</div>
|
<div class="section-title">{{ section.title }}</div>
|
||||||
</el-tab-pane>
|
<div class="section-content" v-if="!section.isHtml"
|
||||||
</el-tabs>
|
v-html="formatPlainText(section.content)"></div>
|
||||||
<!-- 没有子标签页的内容 -->
|
<div class="section-content html-content" v-else
|
||||||
<div v-else class="tab-content" :key="`content-${tab.name}`">
|
v-html="section.content"></div>
|
||||||
<slot :name="tab.name">
|
</div>
|
||||||
<div class="empty-content">{{ tab.label }}内容</div>
|
</template>
|
||||||
</slot>
|
<div v-else class="empty-content">暂无数据</div>
|
||||||
</div>
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="tab-content" :key="`content-${tab.name}`">
|
||||||
|
<template v-if="hasSections(tab.sections)">
|
||||||
|
<div class="content-section" v-for="section in tab.sections" :key="section.id">
|
||||||
|
<div class="section-title">{{ section.title }}</div>
|
||||||
|
<div class="section-content" v-if="!section.isHtml"
|
||||||
|
v-html="formatPlainText(section.content)"></div>
|
||||||
|
<div class="section-content html-content" v-else v-html="section.content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="empty-content">暂无数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -64,70 +96,7 @@ export default {
|
||||||
// 主标签页数据
|
// 主标签页数据
|
||||||
mainTabs: {
|
mainTabs: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [
|
default: () => []
|
||||||
{
|
|
||||||
name: 'project',
|
|
||||||
label: '项目信息',
|
|
||||||
subTabs: [
|
|
||||||
{ name: 'tenderer', label: '招标人' },
|
|
||||||
{ name: 'basic', label: '基础信息' },
|
|
||||||
{ name: 'agency', label: '代理机构' },
|
|
||||||
{ name: 'keyTime', label: '关键时间' },
|
|
||||||
{ name: 'consortium', label: '联合体要求' },
|
|
||||||
{ name: 'subcontract', label: '分包要求' },
|
|
||||||
{ name: 'maxPrice', label: '最高限价' },
|
|
||||||
{ name: 'response', label: '响应和偏差' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bid',
|
|
||||||
label: '标段信息',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'deposit',
|
|
||||||
label: '保证金',
|
|
||||||
subTabs: [
|
|
||||||
{ name: 'bidDeposit', label: '投标保证金' },
|
|
||||||
{ name: 'performanceDeposit', label: '履约保证金' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualification',
|
|
||||||
label: '资格要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'performance',
|
|
||||||
label: '业绩要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finance',
|
|
||||||
label: '财务要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'personnel',
|
|
||||||
label: '人员要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'evaluation',
|
|
||||||
label: '开评定标要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rejection',
|
|
||||||
label: '废标项',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document',
|
|
||||||
label: '投标文件要求',
|
|
||||||
subTabs: []
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
// 默认激活的主标签
|
// 默认激活的主标签
|
||||||
defaultMainTab: {
|
defaultMainTab: {
|
||||||
|
|
@ -217,6 +186,18 @@ export default {
|
||||||
return mainTab.subTabs[0].name
|
return mainTab.subTabs[0].name
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
|
},
|
||||||
|
hasSubTabs(tab) {
|
||||||
|
return Boolean(tab && tab.subTabs && tab.subTabs.length > 0)
|
||||||
|
},
|
||||||
|
hasSections(sections) {
|
||||||
|
return Array.isArray(sections) && sections.length > 0
|
||||||
|
},
|
||||||
|
formatPlainText(text) {
|
||||||
|
if (!text) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
return text.replace(/\n/g, '<br/>')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +416,8 @@ export default {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 20px;
|
// padding: 20px;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.empty-content {
|
.empty-content {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -445,4 +427,36 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.8;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
background: #F5F7FA;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.html-content {
|
||||||
|
white-space: normal;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border: 1px solid #EBEEF5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue