feat(hjc-web): 标书详情支持公告正文富文本与公告附件下载

中标公告区域两块改动:

- 公告正文改按富文本渲染。一站式推的是富文本编辑器出的 HTML,原来当纯文本渲染
  (whitespace-pre-line),会把 <p>、&nbsp;、style="…" 原样显示在页面上。
  判定(HTML / 纯文本)、净化、纯文本换行抽到 app/utils/richText.ts,
  与 hjc-h5 的 src/utils/richText.js 同一套口径。
- 公告附件:渲染后端下发的 bulletinFiles(后端已解析成 {name,url} 并拼好完整
  下载地址,含 URL 编码),复用 HjcTenderFiles 组件。公告附件是公开资料
  (不判登录 / 资质 / 购买),与标书附件(付费内容、仅已支付订单下发)是两条线,
  所以这里不做权限判断。
- types/tender.ts:补 bulletinFiles 字段。

验证:npm run build 通过
This commit is contained in:
2026-09-18 08:48:40 +08:00
parent 2e847b5e09
commit a322b2eed4
3 changed files with 128 additions and 5 deletions
@@ -36,13 +36,31 @@
</div>
</div>
<div v-if="tender.bulletinTitle || tender.bulletinContent" class="mt-8">
<div v-if="tender.bulletinTitle || bulletinHtml" class="mt-8">
<h2 class="mb-3 text-lg font-semibold text-gray-900">中标公告</h2>
<div class="rounded-md border border-gray-200 p-5 text-sm text-gray-700">
<h3 class="mb-2 font-semibold">{{ tender.bulletinTitle }}</h3>
<p class="whitespace-pre-line leading-relaxed">{{ tender.bulletinContent }}</p>
<h3 v-if="tender.bulletinTitle" class="mb-2 font-semibold">{{ tender.bulletinTitle }}</h3>
<!--
公告正文是一站式富文本编辑器出的 HTML**不能再当纯文本渲染**
原来那个 `whitespace-pre-line` 会把 `<p>``&nbsp;``style="…"`
原样显示在页面上。判定与净化在 app/utils/richText.ts
(纯文本内容仍按老样子保留换行)。
-->
<RichText v-if="bulletinHtml" :content="bulletinHtml" />
<p v-else class="text-gray-400">暂无公告正文</p>
</div>
</div>
<!--
公告附件:**公开资料**(公告正文本来就公开下发,附件跟着正文一起给),
与标书附件(`tenderFiles`,付费内容、只在已支付订单上出现)不是一回事,
所以这里不做任何权限判断——登录/资质/购买都不看。
唯一会「没有」的情况是项目已下架:后端对 status != 1 的项目不下发 `bulletinFiles`
-->
<div v-if="bulletinFiles.length" class="mt-8">
<h2 class="mb-3 text-lg font-semibold text-gray-900">公告附件</h2>
<HjcTenderFiles :files="bulletinFiles" />
</div>
</div>
<!-- 右侧:购买卡片 -->
@@ -102,13 +120,32 @@
</template>
<script setup lang="ts">
import type { Tender } from '~/types/tender'
import type { Tender, HjcTenderFile } from '~/types/tender'
import { toRichTextHtml } from '~/utils/richText'
const route = useRoute()
const id = route.params.id as string
const tender = shallowRef<Tender | null>(null)
const notFound = ref(false)
/**
* 公告附件:后端已解析成 `{name, url}` 列表(`bulletinFiles`)。
*
* 后端做了两件事:把 JSON/逗号分隔的原始串解析成列表、把相对路径拼成完整下载地址。
* 前端只渲染,不自己解析 `bulletinFileList`——三端各解一份必然分叉。
*/
const bulletinFiles = computed<HjcTenderFile[]>(() =>
Array.isArray(tender.value?.bulletinFiles) ? tender.value!.bulletinFiles! : []
)
/**
* 公告正文:一站式推的是富文本编辑器出的 HTML,直接插进页面会把标签露出来。
*
* 判定(HTML / 纯文本)、净化、纯文本换行都在 `app/utils/richText.ts`
* 与 h5 的 `src/utils/richText.js` 同一套口径。
*/
const bulletinHtml = computed(() => toRichTextHtml(tender.value?.bulletinContent))
/** 是否已收藏:由详情接口**内联**返回(未登录恒 false),不额外发请求 */
const favorited = ref(false)
const favBusy = ref(false)