招标解析页面

This commit is contained in:
cwchen 2025-11-27 16:22:28 +08:00
parent 62b8abc4a8
commit a8b86d5f77
3 changed files with 180 additions and 23 deletions

View File

@ -212,7 +212,7 @@ export default {
// 700px // 700px
tableMaxHeight: { tableMaxHeight: {
type: [Number, String], type: [Number, String],
default: 700, default: 650,
}, },
// //
autoLoad: { autoLoad: {

View File

@ -52,14 +52,14 @@ import analysisResultMock from '../analysisResultMock.json'
const HTML_TAG_REG = /<\/?[a-z][\s\S]*>/i const HTML_TAG_REG = /<\/?[a-z][\s\S]*>/i
const MAX_PARSE_DEPTH = 5 const MAX_PARSE_DEPTH = 5
const formatSectionValue = (raw, depth = 0) => { function formatSectionValue(raw, depth = 0) {
const fallback = { content: '--', isHtml: false } const fallback = { content: '--', isHtml: false, tableRows: null }
if (raw === undefined || raw === null) { if (raw === undefined || raw === null) {
return fallback return fallback
} }
if (depth > MAX_PARSE_DEPTH) { if (depth > MAX_PARSE_DEPTH) {
const content = String(raw) const content = String(raw)
return { content, isHtml: HTML_TAG_REG.test(content) } return { content, isHtml: HTML_TAG_REG.test(content), tableRows: null }
} }
if (typeof raw === 'string') { if (typeof raw === 'string') {
const trimmed = raw.trim() const trimmed = raw.trim()
@ -70,10 +70,14 @@ const formatSectionValue = (raw, depth = 0) => {
const parsed = JSON.parse(trimmed) const parsed = JSON.parse(trimmed)
return formatSectionValue(parsed, depth + 1) return formatSectionValue(parsed, depth + 1)
} catch (error) { } catch (error) {
return { content: trimmed, isHtml: HTML_TAG_REG.test(trimmed) } return { content: trimmed, isHtml: HTML_TAG_REG.test(trimmed), tableRows: null }
} }
} }
if (Array.isArray(raw)) { if (Array.isArray(raw)) {
const tableRows = buildTableRows(raw, depth)
if (tableRows.length > 0) {
return { content: '', isHtml: false, tableRows }
}
const lines = raw.map(item => { const lines = raw.map(item => {
if (typeof item === 'object' && item !== null) { if (typeof item === 'object' && item !== null) {
const label = item.name || item.label || item.title || item.key || '' const label = item.name || item.label || item.title || item.key || ''
@ -84,7 +88,7 @@ const formatSectionValue = (raw, depth = 0) => {
return formatSectionValue(item, depth + 1).content return formatSectionValue(item, depth + 1).content
}).filter(Boolean) }).filter(Boolean)
const content = lines.length > 0 ? lines.join('\n') : '--' const content = lines.length > 0 ? lines.join('\n') : '--'
return { content, isHtml: false } return { content, isHtml: false, tableRows: null }
} }
if (typeof raw === 'object') { if (typeof raw === 'object') {
if (Object.prototype.hasOwnProperty.call(raw, 'value')) { if (Object.prototype.hasOwnProperty.call(raw, 'value')) {
@ -95,10 +99,34 @@ const formatSectionValue = (raw, depth = 0) => {
return `${key}${formatted.content}` return `${key}${formatted.content}`
}).filter(Boolean) }).filter(Boolean)
const content = lines.length > 0 ? lines.join('\n') : '--' const content = lines.length > 0 ? lines.join('\n') : '--'
return { content, isHtml: false } return { content, isHtml: false, tableRows: null }
} }
const content = String(raw) const content = String(raw)
return { content, isHtml: HTML_TAG_REG.test(content) } return { content, isHtml: HTML_TAG_REG.test(content), tableRows: null }
}
function buildTableRows(items, depth) {
const rows = items.map(item => {
if (typeof item !== 'object' || item === null) {
return null
}
const title = item.name ?? item.label ?? item.title ?? item.key ?? ''
if (!title && title !== 0) {
return null
}
const valueSource = item.content ?? item.value ?? item.text ?? item.desc ?? item.detail
const formatted = formatSectionValue(valueSource, depth + 1)
const nestedTable = formatted.tableRows && formatted.tableRows.length > 0
? formatted.tableRows.map(row => `${row.title}${row.value}`).join('\n')
: ''
const value = nestedTable || formatted.content || '--'
return {
title,
value,
isHtml: formatted.isHtml
}
}).filter(Boolean)
return rows
} }
const collectSectionsFromNode = (node) => { const collectSectionsFromNode = (node) => {
@ -111,7 +139,8 @@ const collectSectionsFromNode = (node) => {
id: target.id, id: target.id,
title: target.name, title: target.name,
content: formatted.content, content: formatted.content,
isHtml: formatted.isHtml isHtml: formatted.isHtml,
tableRows: formatted.tableRows
}) })
} }
if (target.children && target.children.length) { if (target.children && target.children.length) {

View File

@ -17,10 +17,33 @@
<div class="content-section" v-for="section in subTab.sections" <div class="content-section" v-for="section in subTab.sections"
:key="section.id"> :key="section.id">
<div class="section-title">{{ section.title }}</div> <div class="section-title">{{ section.title }}</div>
<template v-if="hasTable(section.tableRows)">
<table class="section-table">
<thead>
<tr>
<th>标题</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr v-for="row in section.tableRows"
:key="`${section.id}-${row.title}`">
<td class="cell-title">{{ row.title }}</td>
<td class="cell-value">
<div v-if="!row.isHtml"
v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<div class="section-content" v-if="!section.isHtml" <div class="section-content" v-if="!section.isHtml"
v-html="formatPlainText(section.content)"></div> v-html="formatPlainText(section.content)"></div>
<div class="section-content html-content" v-else v-html="section.content"> <div class="section-content html-content" v-else v-html="section.content">
</div> </div>
</template>
</div> </div>
</template> </template>
<div v-else class="empty-content">暂无数据</div> <div v-else class="empty-content">暂无数据</div>
@ -33,9 +56,32 @@
<template v-if="hasSections(tab.sections)"> <template v-if="hasSections(tab.sections)">
<div class="content-section" v-for="section in tab.sections" :key="section.id"> <div class="content-section" v-for="section in tab.sections" :key="section.id">
<div class="section-title">{{ section.title }}</div> <div class="section-title">{{ section.title }}</div>
<div class="section-content" v-if="!section.isHtml" <template v-if="hasTable(section.tableRows)">
v-html="formatPlainText(section.content)"></div> <table class="section-table">
<div class="section-content html-content" v-else v-html="section.content"></div> <thead>
<tr>
<th>标题</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr v-for="row in section.tableRows"
:key="`${section.id}-${row.title}`">
<td class="cell-title">{{ row.title }}</td>
<td class="cell-value">
<div v-if="!row.isHtml"
v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<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>
</template>
</div> </div>
</template> </template>
<div v-else class="empty-content">暂无数据</div> <div v-else class="empty-content">暂无数据</div>
@ -57,10 +103,33 @@
<div class="content-section" v-for="section in subTab.sections" <div class="content-section" v-for="section in subTab.sections"
:key="section.id"> :key="section.id">
<div class="section-title">{{ section.title }}</div> <div class="section-title">{{ section.title }}</div>
<div class="section-content" v-if="!section.isHtml" <template v-if="hasTable(section.tableRows)">
v-html="formatPlainText(section.content)"></div> <table class="section-table">
<div class="section-content html-content" v-else <thead>
v-html="section.content"></div> <tr>
<th>标题</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr v-for="row in section.tableRows"
:key="`${section.id}-${row.title}`">
<td class="cell-title">{{ row.title }}</td>
<td class="cell-value">
<div v-if="!row.isHtml"
v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<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>
</template>
</div> </div>
</template> </template>
<div v-else class="empty-content">暂无数据</div> <div v-else class="empty-content">暂无数据</div>
@ -73,10 +142,34 @@
<template v-if="hasSections(tab.sections)"> <template v-if="hasSections(tab.sections)">
<div class="content-section" v-for="section in tab.sections" :key="section.id"> <div class="content-section" v-for="section in tab.sections" :key="section.id">
<div class="section-title">{{ section.title }}</div> <div class="section-title">{{ section.title }}</div>
<div class="section-content" v-if="!section.isHtml" <template v-if="hasTable(section.tableRows)">
v-html="formatPlainText(section.content)"></div> <table class="section-table">
<div class="section-content html-content" v-else v-html="section.content"> <thead>
</div> <tr>
<th>标题</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr v-for="row in section.tableRows"
:key="`${section.id}-${row.title}`">
<td class="cell-title">{{ row.title }}</td>
<td class="cell-value">
<div v-if="!row.isHtml"
v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<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>
</template>
</div> </div>
</template> </template>
<div v-else class="empty-content">暂无数据</div> <div v-else class="empty-content">暂无数据</div>
@ -193,6 +286,9 @@ export default {
hasSections(sections) { hasSections(sections) {
return Array.isArray(sections) && sections.length > 0 return Array.isArray(sections) && sections.length > 0
}, },
hasTable(rows) {
return Array.isArray(rows) && rows.length > 0
},
formatPlainText(text) { formatPlainText(text) {
if (!text) { if (!text) {
return '--' return '--'
@ -404,7 +500,7 @@ export default {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 20px; padding: 20px 0;
} }
.el-tab-pane { .el-tab-pane {
@ -416,8 +512,7 @@ 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;
@ -458,5 +553,38 @@ export default {
border: 1px solid #EBEEF5; border: 1px solid #EBEEF5;
} }
} }
.section-table {
width: 100%;
border: 1px solid #EBEEF5;
border-collapse: collapse;
font-size: 14px;
background: #FFFFFF;
th,
td {
border: 1px solid #EBEEF5;
padding: 12px 16px;
text-align: left;
vertical-align: top;
}
th {
background: #FAFBFF;
color: #909399;
font-weight: 500;
}
.cell-title {
width: 180px;
color: #303133;
font-weight: 500;
}
.cell-value {
color: #606266;
word-break: break-word;
}
}
} }
</style> </style>