招标解析

This commit is contained in:
cwchen 2025-11-27 16:53:26 +08:00
parent 887b3ab82b
commit f591ac9298
2 changed files with 82 additions and 20 deletions

View File

@ -53,13 +53,13 @@ const HTML_TAG_REG = /<\/?[a-z][\s\S]*>/i
const MAX_PARSE_DEPTH = 5 const MAX_PARSE_DEPTH = 5
function formatSectionValue(raw, depth = 0) { function formatSectionValue(raw, depth = 0) {
const fallback = { content: '--', isHtml: false, tableRows: null } const fallback = { content: '--', isHtml: false, tableRows: null, hasScoreRange: false }
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), tableRows: null } return { content, isHtml: HTML_TAG_REG.test(content), tableRows: null, hasScoreRange: false }
} }
if (typeof raw === 'string') { if (typeof raw === 'string') {
const trimmed = raw.trim() const trimmed = raw.trim()
@ -70,13 +70,13 @@ function 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), tableRows: null } return { content: trimmed, isHtml: HTML_TAG_REG.test(trimmed), tableRows: null, hasScoreRange: false }
} }
} }
if (Array.isArray(raw)) { if (Array.isArray(raw)) {
const tableRows = buildTableRows(raw, depth) const { rows: tableRows, hasScoreRange } = buildTableRows(raw, depth)
if (tableRows.length > 0) { if (tableRows.length > 0) {
return { content: '', isHtml: false, tableRows } return { content: '', isHtml: false, tableRows, hasScoreRange }
} }
const lines = raw.map(item => { const lines = raw.map(item => {
if (typeof item === 'object' && item !== null) { if (typeof item === 'object' && item !== null) {
@ -88,7 +88,7 @@ function 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, tableRows: null } return { content, isHtml: false, tableRows: null, hasScoreRange: false }
} }
if (typeof raw === 'object') { if (typeof raw === 'object') {
if (Object.prototype.hasOwnProperty.call(raw, 'value')) { if (Object.prototype.hasOwnProperty.call(raw, 'value')) {
@ -99,10 +99,14 @@ function 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, tableRows: null } return { content, isHtml: false, tableRows: null, hasScoreRange: false }
} }
const content = String(raw) const content = String(raw)
return { content, isHtml: HTML_TAG_REG.test(content), tableRows: null } return { content, isHtml: HTML_TAG_REG.test(content), tableRows: null, hasScoreRange: false }
}
function hasScoreValue(value) {
return value !== undefined && value !== null && value !== ''
} }
function buildTableRows(items, depth) { function buildTableRows(items, depth) {
@ -111,7 +115,7 @@ function buildTableRows(items, depth) {
return null return null
} }
const title = item.name ?? item.label ?? item.title ?? item.key ?? '' const title = item.name ?? item.label ?? item.title ?? item.key ?? ''
if (!title && title !== 0) { if (title === '' || title === undefined) {
return null return null
} }
const valueSource = item.content ?? item.value ?? item.text ?? item.desc ?? item.detail const valueSource = item.content ?? item.value ?? item.text ?? item.desc ?? item.detail
@ -120,13 +124,24 @@ function buildTableRows(items, depth) {
? formatted.tableRows.map(row => `${row.title}${row.value}`).join('\n') ? formatted.tableRows.map(row => `${row.title}${row.value}`).join('\n')
: '' : ''
const value = nestedTable || formatted.content || '--' const value = nestedTable || formatted.content || '--'
const minScore = item.minScore ?? item.scoreMin ?? item.min ?? item.lowScore
const maxScore = item.maxScore ?? item.scoreMax ?? item.max ?? item.highScore
const scoreRange = item.scoreRange ?? item.range ?? item.score ?? null
return { return {
title, title,
value, value,
isHtml: formatted.isHtml isHtml: formatted.isHtml,
minScore,
maxScore,
scoreRange
} }
}).filter(Boolean) }).filter(Boolean)
return rows const hasScoreRange = rows.some(row =>
hasScoreValue(row.minScore) ||
hasScoreValue(row.maxScore) ||
hasScoreValue(row.scoreRange)
)
return { rows, hasScoreRange }
} }
const collectSectionsFromNode = (node) => { const collectSectionsFromNode = (node) => {
@ -140,7 +155,8 @@ const collectSectionsFromNode = (node) => {
title: target.name, title: target.name,
content: formatted.content, content: formatted.content,
isHtml: formatted.isHtml, isHtml: formatted.isHtml,
tableRows: formatted.tableRows tableRows: formatted.tableRows,
hasScoreRange: formatted.hasScoreRange
}) })
} }
if (target.children && target.children.length) { if (target.children && target.children.length) {

View File

@ -21,8 +21,9 @@
<table class="section-table"> <table class="section-table">
<thead> <thead>
<tr> <tr>
<th>标题</th> <th>{{ hasScoreRange(section) ? '名称' : '标题' }}</th>
<th>内容</th> <th>{{ hasScoreRange(section) ? '详情' : '内容' }}</th>
<th v-if="hasScoreRange(section)">分值范围</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -34,6 +35,9 @@
v-html="formatPlainText(row.value)"></div> v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div> <div v-else v-html="row.value"></div>
</td> </td>
<td class="cell-score" v-if="hasScoreRange(section)">
{{ formatScoreRange(row) }}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -60,8 +64,9 @@
<table class="section-table"> <table class="section-table">
<thead> <thead>
<tr> <tr>
<th>标题</th> <th>{{ hasScoreRange(section) ? '名称' : '标题' }}</th>
<th>内容</th> <th>{{ hasScoreRange(section) ? '详情' : '内容' }}</th>
<th v-if="hasScoreRange(section)">分值范围</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -73,6 +78,9 @@
v-html="formatPlainText(row.value)"></div> v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div> <div v-else v-html="row.value"></div>
</td> </td>
<td class="cell-score" v-if="hasScoreRange(section)">
{{ formatScoreRange(row) }}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -107,8 +115,9 @@
<table class="section-table"> <table class="section-table">
<thead> <thead>
<tr> <tr>
<th>标题</th> <th>{{ hasScoreRange(section) ? '名称' : '标题' }}</th>
<th>内容</th> <th>{{ hasScoreRange(section) ? '详情' : '内容' }}</th>
<th v-if="hasScoreRange(section)">分值范围</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -120,6 +129,9 @@
v-html="formatPlainText(row.value)"></div> v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div> <div v-else v-html="row.value"></div>
</td> </td>
<td class="cell-score" v-if="hasScoreRange(section)">
{{ formatScoreRange(row) }}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -146,8 +158,9 @@
<table class="section-table"> <table class="section-table">
<thead> <thead>
<tr> <tr>
<th>标题</th> <th>{{ hasScoreRange(section) ? '名称' : '标题' }}</th>
<th>内容</th> <th>{{ hasScoreRange(section) ? '详情' : '内容' }}</th>
<th v-if="hasScoreRange(section)">分值范围</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -159,6 +172,9 @@
v-html="formatPlainText(row.value)"></div> v-html="formatPlainText(row.value)"></div>
<div v-else v-html="row.value"></div> <div v-else v-html="row.value"></div>
</td> </td>
<td class="cell-score" v-if="hasScoreRange(section)">
{{ formatScoreRange(row) }}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -289,11 +305,34 @@ export default {
hasTable(rows) { hasTable(rows) {
return Array.isArray(rows) && rows.length > 0 return Array.isArray(rows) && rows.length > 0
}, },
hasScoreRange(section) {
return Boolean(section && section.hasScoreRange)
},
formatPlainText(text) { formatPlainText(text) {
if (!text) { if (!text) {
return '--' return '--'
} }
return text.replace(/\n/g, '<br/>') return text.replace(/\n/g, '<br/>')
},
formatScoreRange(row) {
if (!row) {
return '--'
}
if (row.scoreRange !== undefined && row.scoreRange !== null && row.scoreRange !== '') {
return row.scoreRange
}
const hasMin = row.minScore !== undefined && row.minScore !== null && row.minScore !== ''
const hasMax = row.maxScore !== undefined && row.maxScore !== null && row.maxScore !== ''
if (hasMin && hasMax) {
return `${row.minScore} ~ ${row.maxScore}`
}
if (hasMin) {
return `${row.minScore}`
}
if (hasMax) {
return `${row.maxScore}`
}
return '--'
} }
} }
} }
@ -585,6 +624,13 @@ export default {
color: #606266; color: #606266;
word-break: break-word; word-break: break-word;
} }
.cell-score {
width: 140px;
color: #303133;
text-align: center;
white-space: nowrap;
}
} }
} }
</style> </style>