diff --git a/AGENTS.md b/AGENTS.md index 2b7e824..e6bbd22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -240,4 +240,5 @@ samples/ - 运营后台:独立进程 `bid-doctor ops` 和 `apps/admin`,用户名 + 密码 + TOTP + IP 白名单,可以处理开通申请、查企业、调额度、看误报 / 漏报、失败任务和审计,不看业务内容(`ops-api.md`)。新增数据库角色 `bd_ops`,迁移 0010、0011。 - 顺手修了取消解析 / 检查时接口报 500 的问题。后端 156 个测试,浏览器从注册走到注销。 - 2026-11-05:运营后台第二版(M6-9、M6-10):左侧菜单(标准 logo、可展开收起的分组、图标、待办数、环境标签),运营人员角色只留结构(`ops_admin.role`、权限点、每个接口的检查点、菜单按权限过滤,现在只有管理员),迁移 0012。 +- 2026-11-05:前端走查修改。报告预览改用 pdf.js(浏览器自带的 PDF 查看器是深色的,改不了);读标页改成和查标一样的左右布局(项目头滚出屏幕,清单卡片和原文区贴住顶栏),响应方式收进清单栏的按钮;报告页「导出」统一改称「生成」(生成一份存进项目,下载才是存到电脑),PDF 模板 2026.11-v4;概览检查记录的报告列只写份数和「查看报告」;招标 / 投标文件切换标签改为双箭头、指向展开方向;项目头三项取值对齐。 - 下一步:可做的有:读标抽取专项优化(`extract-status.md`)、更多真实投标文件、M6 的 P1(协作权限、资产库)。 diff --git a/apps/web/src/components/AppShell.vue b/apps/web/src/components/AppShell.vue index c1b1596..aab5656 100644 --- a/apps/web/src/components/AppShell.vue +++ b/apps/web/src/components/AppShell.vue @@ -113,7 +113,7 @@ async function logout() {
-

专业版已到期,数据只读:可以查看、下载已导出的报告,不能新建项目、解析和检查。

+

专业版已到期,数据只读:可以查看、下载已生成的报告,不能新建项目、解析和检查。

续费
diff --git a/apps/web/src/components/DocStage.vue b/apps/web/src/components/DocStage.vue index b7ad3f9..b2cbf54 100644 --- a/apps/web/src/components/DocStage.vue +++ b/apps/web/src/components/DocStage.vue @@ -5,7 +5,7 @@ * 点露出的那一条、或点到另一边的出处,就滑过去,原来那个从另一侧露出一窄条。两边各自记着翻到的页和高亮。 * 露出的那一条在截断处淡入页面底色,不留硬边;标签上的箭头指向文件所在的一侧,悬停时文件先往外探出一点。 */ -import { ChevronLeft, ChevronRight } from "@lucide/vue"; +import { ChevronsLeft, ChevronsRight } from "@lucide/vue"; import { computed } from "vue"; import PdfViewer, { type ViewerTarget } from "@/components/PdfViewer.vue"; @@ -40,9 +40,11 @@ const index = computed(() => Math.max(0, props.slots.findIndex((s) => s.key === :aria-label="`看${s.label}`" @click="active = s.key" > + - @@ -122,7 +124,7 @@ const index = computed(() => Math.max(0, props.slots.findIndex((s) => s.key === flex-direction: column; align-items: center; gap: var(--sp-1); - padding: var(--sp-2) 3px var(--sp-3); + padding: var(--sp-2) 3px; border: var(--line) solid var(--border-subtle); border-radius: var(--radius-ctl); background: var(--bg-surface); @@ -145,10 +147,10 @@ const index = computed(() => Math.max(0, props.slots.findIndex((s) => s.key === border-color: var(--c-ink); } .peek.left:hover .ic { - transform: translateX(-2px); + transform: translateX(2px); } .peek.right:hover .ic { - transform: translateX(2px); + transform: translateX(-2px); } .peek:focus-visible { outline: 0; diff --git a/apps/web/src/components/ProjectHead.vue b/apps/web/src/components/ProjectHead.vue index 64f7216..ed777c0 100644 --- a/apps/web/src/components/ProjectHead.vue +++ b/apps/web/src/components/ProjectHead.vue @@ -133,7 +133,7 @@ const checkTo = computed(() =>
就绪
就绪 - 未就绪 + 未就绪
diff --git a/apps/web/src/components/ProjectRow.vue b/apps/web/src/components/ProjectRow.vue index 58da244..0e24716 100644 --- a/apps/web/src/components/ProjectRow.vue +++ b/apps/web/src/components/ProjectRow.vue @@ -26,7 +26,7 @@ const home = computed(() => { return { name: "results", params: { projectId: id, runId: p.value.latest_check_run_id } }; return { name: "checklist", params: { projectId: id } }; }); -/** 「下一步」:导出报告去报告页,其余同默认入口 */ +/** 「下一步」:生成报告去报告页,其余同默认入口 */ const next = computed(() => p.value.next_action === "export_report" ? { name: "report", params: { projectId: p.value.id } } : home.value, ); @@ -50,7 +50,7 @@ const next = computed(() => 就绪 diff --git a/apps/web/src/components/ReportViewer.vue b/apps/web/src/components/ReportViewer.vue new file mode 100644 index 0000000..5742c6e --- /dev/null +++ b/apps/web/src/components/ReportViewer.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/apps/web/src/lib/labels.ts b/apps/web/src/lib/labels.ts index 05cc578..a4536d6 100644 --- a/apps/web/src/lib/labels.ts +++ b/apps/web/src/lib/labels.ts @@ -138,7 +138,7 @@ export const NEXT_ACTION: Dict = { upload_bid: "上传投标文件", run_check: "发起检查", handle_fatal: "处理致命问题", - export_report: "导出检查报告", + export_report: "生成检查报告", archive: "归档项目", none: "查看项目", }; diff --git a/apps/web/src/pages/ChecklistPage.vue b/apps/web/src/pages/ChecklistPage.vue index 1feb7c2..6daee38 100644 --- a/apps/web/src/pages/ChecklistPage.vue +++ b/apps/web/src/pages/ChecklistPage.vue @@ -10,8 +10,9 @@ * - 资格条件、评分项里的证书要求:写资质库里有没有、在投标截止日是否有效(资质库空的不写)。 */ import { unwrap } from "@bid-doctor/api-client"; -import { FileText, Info, Plus, Search } from "@lucide/vue"; +import { FileText, Info, Plus, Search, SlidersHorizontal } from "@lucide/vue"; import { useQuery, useQueryClient } from "@tanstack/vue-query"; +import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from "reka-ui"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; import { RouterLink, useRoute, useRouter } from "vue-router"; import ChecklistItemRow from "@/components/ChecklistItemRow.vue"; @@ -196,6 +197,8 @@ const MODE_OPTIONS = [ ]; const MODE_STATUS: Record = { unknown: "招标文件没写明,按逐条响应处理", conflict: "招标文件两处写法不一致,按逐条响应处理" }; const modeBusy = ref(""); +// 响应方式一个项目设一次,平时收在清单栏的按钮里;招标文件没写明或两处不一致、又没人改过时,按钮上加提示点 +const modeAttn = computed(() => (cl.value?.response_modes ?? []).some((m) => m.origin !== "human" && !!MODE_STATUS[m.status])); async function setMode(category: "technical" | "commercial", mode: "explicit" | "implicit") { modeBusy.value = category; try { @@ -347,28 +350,54 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); + + @@ -603,35 +614,48 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); align-items: baseline; margin-bottom: var(--sp-3); } -/* 响应方式(C12):清单栏下面一行,每类一个选择框 */ -.modes { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--sp-2) var(--sp-6); - margin-bottom: var(--sp-3); +/* 响应方式(C12):收在清单栏「响应方式」按钮的浮层里 */ +.mode-btn { + position: relative; } -.modes .mode { - display: flex; - align-items: center; - gap: var(--sp-2); +.mode-btn .dot { + position: absolute; + top: 5px; + right: 5px; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--warn); + box-shadow: 0 0 0 2px var(--bg-surface); } -.modes label { +[data-reka-popper-content-wrapper] :deep(.modes-pop) { + width: 380px; + max-height: none; + padding: var(--sp-4); +} +.modes-pop > p { + margin: 0; +} +.modes-pop .mode { + display: grid; + gap: var(--sp-1); + margin-top: var(--sp-4); +} +.modes-pop label { font-size: var(--fs-caption); color: var(--text-secondary); - white-space: nowrap; } -.modes :deep(.select) { - width: 236px; +.modes-pop .mode-meta { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--sp-1) var(--sp-3); } -.modes .sign { - flex: none; - flex-wrap: nowrap; +.modes-pop .mode-meta:empty { + display: none; +} +.modes-pop .sign { margin: 0; - white-space: nowrap; -} -.modes > .cap { - white-space: nowrap; } /* 清单栏:标题 + 清单状态;右侧页面级操作(主按钮一个,Q12) */ .cl-bar { @@ -640,20 +664,30 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); gap: var(--sp-3); margin-bottom: var(--sp-3); } +.cl-title { + min-width: 0; +} .cl-bar h2 { + display: flex; + align-items: center; + gap: var(--sp-2); margin: 0; font: var(--fw-bold) var(--fs-title) / var(--lh-title) var(--font-sans); white-space: nowrap; } +.cl-bar h2 .tag { + font-weight: var(--fw-regular); +} .cl-bar .status { - display: flex; - align-items: center; - gap: var(--sp-2); + margin: 2px 0 0; min-width: 0; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; } .cl-bar .status .sign { + display: inline-flex; + flex-wrap: nowrap; margin: 0; } .cl-bar .actions { @@ -665,38 +699,51 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); .warn-row { margin-bottom: var(--sp-3); } -/* 整屏布局(原型 shell fit):页面不滚动,清单和原文查看器各自滚动 */ -.reading { - display: flex; - flex-direction: column; - height: calc(100vh - var(--topbar-h)); -} -.reading > .content { - flex: 1; - min-height: 0; - display: flex; - flex-direction: column; - padding-bottom: var(--sp-4); -} -.cmp { - flex: 1; - min-height: 0; - display: grid; - grid-template-columns: minmax(560px, 1fr) minmax(400px, 45%); - gap: var(--gap-card); -} +/* 左右布局(同查标、概览):清单随页面滚动,项目头和清单栏滚出屏幕,只留顶栏; + 分类导航、搜索栏贴住(和右边原文区一样离顶栏 --sp-2),右边原文区贴住视口。 + 卡片不裁切(overflow: visible),里面的 sticky 才跟着页面走 */ .cmp-list { - min-height: 0; + --stick: calc(var(--topbar-h) + var(--sp-2) + var(--line)); + overflow: visible; +} +/* 贴住时的卡片上沿:一条 0 高的 sticky 横条,画出圆角上边框; + 外面一圈页面底色,盖住从顶栏和卡片之间滚过去的内容,圆角外也不露出底下的条目 */ +.cap-edge { + grid-column: 1 / -1; + position: sticky; + top: var(--stick); + z-index: 3; + height: 0; +} +.cap-edge::before { + content: ""; + position: absolute; + top: calc(-1 * var(--line)); + left: calc(-1 * var(--line)); + right: calc(-1 * var(--line)); + height: calc(var(--radius-card) + var(--line)); + border: var(--line) solid var(--border-subtle); + border-bottom: 0; + border-radius: var(--radius-card) var(--radius-card) 0 0; + box-shadow: 0 0 0 var(--sp-3) var(--bg-page); + clip-path: inset(calc(-1 * var(--sp-3)) calc(-1 * var(--sp-3)) 0); + pointer-events: none; } .cmp-list > .lst { - min-height: 0; - overflow: auto; -} -.cmp-viewer { - min-height: 0; + border-left: var(--line) solid var(--border-subtle); } .cats { + position: sticky; + top: var(--stick); + align-self: start; + max-height: calc(100vh - var(--stick)); overflow: auto; + border-right: 0; +} +/* J / K 翻到的条目不钻到顶栏和搜索栏下面 */ +.lst :deep([data-item]) { + scroll-margin-top: calc(var(--topbar-h) + 72px); + scroll-margin-bottom: var(--sp-4); } .cats .hint { margin: var(--sp-3) 0 0; @@ -709,7 +756,7 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); } .lst-tb { position: sticky; - top: 0; + top: var(--stick); z-index: 2; display: flex; align-items: center; @@ -717,7 +764,6 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); padding: var(--sp-3) var(--card-x); background: var(--bg-surface); border-bottom: var(--line) solid var(--border-subtle); - border-top-right-radius: var(--radius-card); } .lst-tb .search { flex: 1; diff --git a/apps/web/src/pages/OverviewPage.vue b/apps/web/src/pages/OverviewPage.vue index e0d2b65..b888806 100644 --- a/apps/web/src/pages/OverviewPage.vue +++ b/apps/web/src/pages/OverviewPage.vue @@ -6,7 +6,7 @@ * 补遗(FR-M2-10):招标文件解析后可以追加;补遗带来的新值在这里「采用 / 不采用」(AD3),没确认前查标用旧值(AD4)。 */ import { unwrap, type Schemas } from "@bid-doctor/api-client"; -import { Download, Pencil, Plus } from "@lucide/vue"; +import { Pencil, Plus } from "@lucide/vue"; import { useQueryClient } from "@tanstack/vue-query"; import { computed, ref, watchEffect } from "vue"; import { RouterLink } from "vue-router"; @@ -19,7 +19,6 @@ import { useCrumbs } from "@/lib/crumbs"; import { qk } from "@/lib/keys"; import { deadline, FIELD_LABEL, FIELD_ORDER, fieldText, fmtDateTime, fmtDateTimeFull, fmtYuan, SEVERITY, SEVERITY_CLASS } from "@/lib/labels"; import { - reportFileUrl, useCheckRuns, useLots, useProject, @@ -94,19 +93,13 @@ const canAddendum = computed(() => !!p.value && p.value.status !== "tracking" && const runsQ = useCheckRuns(() => props.projectId); const runs = computed(() => runsQ.data.value?.items ?? []); const reportsQ = useReports(() => props.projectId); -const reportsByRun = computed(() => { - const m = new Map["items"]>(); - for (const r of reportsQ.data.value?.items ?? []) m.set(r.check_run_id, [...(m.get(r.check_run_id) ?? []), r]); +// 一次检查可能生成过很多份报告,这里只写份数、给一个入口,到「报告」标签页看 +const reportCount = computed(() => { + const m = new Map(); + for (const r of reportsQ.data.value?.items ?? []) m.set(r.check_run_id, (m.get(r.check_run_id) ?? 0) + 1); return m; }); const RUN_STATE: Record = { queued: "排队中", running: "检查中", failed: "检查失败", cancelled: "已取消" }; -async function openReport(id: string) { - try { - window.open(await reportFileUrl(id), "_blank", "noopener"); - } catch (e) { - toast(errorTitle(e), { ok: false }); - } -} /* ---------- 文件:招标方的文件全列;投标文件每个分册只列最新版本 ---------- */ const tenderDocs = computed(() => (docsQ.data.value?.items ?? []).filter((d) => d.role !== "bid_volume")); @@ -216,7 +209,7 @@ function openCitation(c: Citation) { {{ runs.length ? "上传新版本并检查" : "上传投标文件并检查" }} -

还没有检查过。上传投标文件后,每次检查的结果和导出的报告都列在这里。

+

还没有检查过。上传投标文件后,每次检查的结果和生成的报告都列在这里。

@@ -240,10 +233,11 @@ function openCitation(c: Citation) { {{ RUN_STATE[r.status] ?? r.status }} @@ -337,19 +331,15 @@ function openCitation(c: Citation) { .runs .nowrap { white-space: nowrap; } +.rep-n { + margin-right: var(--sp-2); + white-space: nowrap; +} .sevs { display: inline-flex; gap: var(--sp-1); margin-right: var(--sp-2); } -.rep { - display: inline-flex; - gap: var(--sp-1); - align-items: center; -} -.rep + .rep { - margin-left: var(--sp-2); -} .files { display: flex; flex-direction: column; diff --git a/apps/web/src/pages/QualificationsPage.vue b/apps/web/src/pages/QualificationsPage.vue index 8b9618b..c37c7d5 100644 --- a/apps/web/src/pages/QualificationsPage.vue +++ b/apps/web/src/pages/QualificationsPage.vue @@ -204,7 +204,7 @@ async function remove() { -

删除后,查标不再用它核对资质,附件一并删除,不能恢复。已导出的检查报告不受影响。

+

删除后,查标不再用它核对资质,附件一并删除,不能恢复。已生成的检查报告不受影响。

diff --git a/apps/web/src/pages/QuotaPage.vue b/apps/web/src/pages/QuotaPage.vue index da6f8d3..d6b5065 100644 --- a/apps/web/src/pages/QuotaPage.vue +++ b/apps/web/src/pages/QuotaPage.vue @@ -111,7 +111,7 @@ async function exportCsv() {
-

专业版已于 {{ day(plan?.ends_at) }} 到期,数据只读:可以查看、下载已导出的报告、删除项目,不能新建项目、解析和检查。续费到账后恢复。

+

专业版已于 {{ day(plan?.ends_at) }} 到期,数据只读:可以查看、下载已生成的报告、删除项目,不能新建项目、解析和检查。续费到账后恢复。

diff --git a/apps/web/src/pages/ReportPage.vue b/apps/web/src/pages/ReportPage.vue index ce3c3af..28a54d2 100644 --- a/apps/web/src/pages/ReportPage.vue +++ b/apps/web/src/pages/ReportPage.vue @@ -1,16 +1,18 @@ @@ -92,54 +107,61 @@ watch(reports, (list) => {
还没有检查结果 -

报告对应最近一次检查。先上传投标文件并检查。

+

报告按最近一次检查生成。先上传投标文件并检查。

去检查
-
- -
- 还没有导出过报告 -

在右边选好内容后导出,生成的 PDF 会在这里显示。

-
-
+
+ + + + +
@@ -153,18 +175,7 @@ watch(reports, (list) => { gap: var(--gap-card); align-items: start; } -.preview { - min-height: 70vh; - overflow: hidden; -} -.preview iframe { - display: block; - width: 100%; - height: calc(100vh - 220px); - min-height: 70vh; - border: 0; -} -.preview .empty { +.split-doc .empty { padding: var(--sp-8) var(--card-x); } .rp-side { @@ -220,16 +231,11 @@ watch(reports, (list) => { line-height: var(--lh-caption); color: var(--text-secondary); } -.exports .new { - margin-left: var(--sp-2); - padding: 0 6px; - border-radius: var(--radius-tag); - background: var(--c-n100); - font-size: var(--fs-caption); - font-weight: var(--fw-regular); -} .exports li.cur b { text-decoration: underline; text-underline-offset: 3px; } +.card-h .fig { + font-weight: var(--fw-medium); +} diff --git a/apps/web/src/pages/ResultsPage.vue b/apps/web/src/pages/ResultsPage.vue index 7444c7b..7d4f0af 100644 --- a/apps/web/src/pages/ResultsPage.vue +++ b/apps/web/src/pages/ResultsPage.vue @@ -12,7 +12,7 @@ * 「提示」可以批量确认无误;每个问题可以反馈判断有误;从工作台带 ?f=问题编号 进来时选中那一条。 */ import { unwrap } from "@bid-doctor/api-client"; -import { ChevronDown, ChevronRight, Download } from "@lucide/vue"; +import { ChevronDown, ChevronRight, FileText } from "@lucide/vue"; import { useQueryClient } from "@tanstack/vue-query"; import { computed, onBeforeUnmount, onMounted, ref, watch, watchEffect } from "vue"; import { RouterLink, useRoute } from "vue-router"; @@ -390,7 +390,7 @@ const errorText = computed(() => {
重新检查 - 导出报告 + 生成报告
还有 {{ p.readiness.open_fatal_count }} 项致命问题待处理,报告会如实列出
diff --git a/apps/web/src/styles/app.css b/apps/web/src/styles/app.css index d3ab645..1dbde8c 100644 --- a/apps/web/src/styles/app.css +++ b/apps/web/src/styles/app.css @@ -74,7 +74,7 @@ a.me:hover{background:var(--side-hover)}a.me[aria-current="page"]{background:var .ph .facts{display:flex;gap:var(--sp-8);margin:0 0 0 auto;flex:none} .ph .facts > div{display:flex;flex-direction:column;gap:2px} .ph .facts dt{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} -.ph .facts dd{display:flex;align-items:center;gap:6px;min-height:var(--lh-body);margin:0;white-space:nowrap} +.ph .facts dd{display:flex;align-items:center;gap:6px;height:var(--control-h-sm);margin:0;white-space:nowrap} /* 各列取值同高(状态列有按钮),值都在同一条中线上 */ .ph .tabs{margin-top:var(--sp-3)} .ph + .content{padding-top:var(--sp-5)} /* 左右布局(查标、概览,同读标页):左边内容随页面滚动,右边原文区贴住视口、自己翻页 */ diff --git a/docs/architecture/openapi.yaml b/docs/architecture/openapi.yaml index 194f9de..696489c 100644 --- a/docs/architecture/openapi.yaml +++ b/docs/architecture/openapi.yaml @@ -3186,7 +3186,7 @@ components: page_count: { type: integer } sha256: { type: string } template_version: { type: string } - open_fatal_at_export: { type: integer, description: 导出时还没处理的致命问题数 } + open_fatal_at_export: { type: integer, description: 生成时还没处理的致命问题数 } generated_by: { $ref: '#/components/schemas/UserRef' } created_at: { $ref: '#/components/schemas/Timestamp' } diff --git a/packages/api-client/src/schema.d.ts b/packages/api-client/src/schema.d.ts index 1e275ea..28294c7 100644 --- a/packages/api-client/src/schema.d.ts +++ b/packages/api-client/src/schema.d.ts @@ -2319,7 +2319,7 @@ export interface components { page_count: number; sha256?: string; template_version: string; - /** @description 导出时还没处理的致命问题数 */ + /** @description 生成时还没处理的致命问题数 */ open_fatal_at_export?: number; generated_by: components["schemas"]["UserRef"]; created_at: components["schemas"]["Timestamp"]; diff --git a/server/src/bid_doctor/report/template.py b/server/src/bid_doctor/report/template.py index 7ffff29..4d3ac7c 100644 --- a/server/src/bid_doctor/report/template.py +++ b/server/src/bid_doctor/report/template.py @@ -1,7 +1,7 @@ """检查报告的 HTML(设计规范 §7.17,原型 report.html):纯函数,输入整理好的数据,输出正文、页眉、页脚三段 HTML, 由 Gotenberg 的 Chromium 转成 PDF(A4 竖版,页边距 20mm,页眉页脚每页重复、页码自动生成)。 -- 首页:项目信息、投标文件版本、检查依据、检查时间、规则版本、导出人和时间、**检查覆盖范围**(查了 / 没查)、 +- 首页:项目信息、投标文件版本、检查依据、检查时间、规则版本、生成人和时间、**检查覆盖范围**(查了 / 没查)、 汇总、免责声明; - 问题明细:级别、问题、说明、出处(可选)、处理状态和处理人; - 附录:需人工核对的条目和勾选结果(可选); @@ -19,11 +19,11 @@ from pathlib import Path from typing import Any from zoneinfo import ZoneInfo -TEMPLATE_VERSION = "2026.11-v3" # v2:资质问题的「依据:资质库记录」;v3:出处不重复写文件名 +TEMPLATE_VERSION = "2026.11-v4" # v2:资质问题的「依据:资质库记录」;v3:出处不重复写文件名;v4:「导出」改称「生成」 TITLE = "投标文件辅助检查报告" FOOTER = "本报告为辅助检查结果,仅供投标前自查参考" DISCLAIMER = ( - "本报告由标博士比对招标文件和投标文件生成,经用户逐项处理后导出,供投标前自查使用。检查范围以首页「检查覆盖范围」为准;" + "本报告由标博士比对招标文件和投标文件生成,经用户逐项处理后生成,供投标前自查使用。检查范围以首页「检查覆盖范围」为准;" "结果可能有遗漏或误判,不代表资格审查、符合性审查或评标的结论。是否递交、如何修改,由投标人自行决定。" ) SEVERITY = {"fatal": "致命", "important": "重要", "hint": "提示"} @@ -152,7 +152,7 @@ def cover(d: dict[str, Any]) -> str: checked_at = f'{when(r.get("finished_at") or r.get("created_at"))}' run_no = f'第 {r["run_no"]} 次检查' alert = ( - f'
导出时还有 {open_fatal} 项致命问题待处理,报告如实列出。
' + f'
生成时还有 {open_fatal} 项致命问题待处理,报告如实列出。
' if open_fatal else "" ) @@ -169,7 +169,7 @@ def cover(d: dict[str, Any]) -> str:
检查依据
{basis}
检查时间
{checked_at}({run_no})
规则版本
{_e(r.get("rule_set_version"))}
-
导出
{_e(d["exported_by"])} · {when(d["exported_at"])}
+
生成
{_e(d["exported_by"])} · {when(d["exported_at"])}
{alert}

检查覆盖范围

@@ -230,7 +230,7 @@ def appendix(d: dict[str, Any]) -> str: return f"""

附录 · 需人工核对

这些要求在投标文件里看不出来,需要投标人递交前自己核对。 -「未勾选」表示导出时还没有人在系统里勾选核对。

+「未勾选」表示生成时还没有人在系统里勾选核对。

批次时间投标文件结果报告
- - — + + —
{body}
要求出处核对
"""