Files
hjc-web/app/templates/template-07/pages/TenderList.vue
T
weicw1996 2828ba6718 feat(tender): 汇吉采标书购买 阶段3 - C端标书列表/详情/购买入口
- server/api/tender/{list.get,detail.get,order.post}.ts 代理到 mp-api /api/hjc/bid-project|order
- app/types/tender.ts 前端视图模型
- app/templates/template-07/pages/{TenderList,TenderDetail,BuyDocument}.vue(Tailwind,仿 Product 页)
- app/pages/tender/index.vue + tender/[id].vue + buy.vue 路由,经 useTemplate 加载
- useTemplate.ts 注册 TenderList/TenderDetail/BuyDocument(全部模板安全可选)
- 附 BUY_TENDER_MAP_REPORT.md(hjc-web 结构映射参考)
2026-09-08 21:45:34 +08:00

106 lines
3.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="container mx-auto px-4 py-10">
<h1 class="text-2xl font-bold text-gray-900 mb-2">标书中心</h1>
<p class="text-sm text-gray-500 mb-6">汇聚各类采购项目选择您需要的标书进行购买</p>
<!-- 搜索 + 分类 -->
<div class="mb-6 flex flex-col sm:flex-row gap-3">
<input
v-model="keywords"
class="flex-1 rounded-md border border-gray-300 px-4 py-2 text-sm"
placeholder="搜索项目名称 / 采购编号"
@keyup.enter="load()"
/>
<select v-model="category" class="rounded-md border border-gray-300 px-4 py-2 text-sm">
<option value="">全部项目</option>
<option value="服务类">服务类</option>
<option value="工程类">工程类</option>
<option value="货物类">货物类</option>
</select>
<button
class="rounded-md bg-blue-600 px-5 py-2 text-sm text-white hover:bg-blue-700"
@click="load()"
>
搜索
</button>
</div>
<!-- 列表 -->
<div v-if="items.length" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<NuxtLink
v-for="item in items"
:key="item.id"
:to="`/tender/${item.id}`"
class="group rounded-lg border border-gray-200 p-5 hover:shadow-md transition"
>
<div class="text-xs text-gray-400 mb-1">项目编号{{ item.projectNo }}</div>
<h2 class="text-base font-semibold text-gray-900 group-hover:text-blue-600 line-clamp-2">
{{ item.projectName }}
</h2>
<div class="mt-3 flex items-center gap-2 text-xs text-gray-500">
<span class="rounded bg-gray-100 px-2 py-0.5">{{ item.category || '未分类' }}</span>
<span>截止{{ item.deadlineTime?.slice(0, 19)?.replace('T', ' ') || '-' }}</span>
</div>
<div class="mt-4 flex items-end justify-between">
<div class="text-blue-600">
<span class="text-xl font-bold">¥{{ formatMoney(item.tenderPrice) }}</span>
<span class="text-xs text-gray-400 ml-1">信息服务费</span>
</div>
<span class="text-sm text-blue-600 group-hover:underline">查看详情</span>
</div>
</NuxtLink>
</div>
<div v-else-if="loaded" class="py-16 text-center text-gray-500">暂无标书项目</div>
<SiteLoading v-show="!loaded" />
<!-- 加载更多 -->
<div v-if="items.length < count" class="mt-8 text-center">
<button class="rounded-md border border-gray-300 px-6 py-2 text-sm" @click="loadMore()">
加载更多
</button>
</div>
</div>
</template>
<script setup lang="ts">
import type { Tender } from '~/types/tender'
const keywords = ref('')
const category = ref('')
const page = ref(1)
const limit = 12
const loaded = ref(false)
const listState = shallowRef<Tender[]>([])
const items = computed(() => listState.value)
const count = computed(() => (listState as any).__count || 0)
async function load() {
page.value = 1
const res: any = await $fetch('/api/tender/list', {
query: { page: page.value, limit, keywords: keywords.value || undefined, category: category.value || undefined }
})
listState.value = res?.list || []
;(listState as any).__count = res?.count || 0
loaded.value = true
}
async function loadMore() {
page.value += 1
const res: any = await $fetch('/api/tender/list', {
query: { page: page.value, limit, keywords: keywords.value || undefined, category: category.value || undefined }
})
const next = res?.list || []
listState.value = [...listState.value, ...next]
;(listState as any).__count = res?.count || 0
}
function formatMoney(n?: number | string) {
if (n === undefined || n === null) return '0.00'
return Number(n).toFixed(2)
}
await load()
</script>