fix(ui): 读标、查标走查修复;查标结果页按原型补齐
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled

走查(注册 → 读标 → 查标 → 导出报告)发现的问题:
- 下拉:原生 select 换成 SelectBox(Reka UI,规范 §7.21):右侧 chevron 有边距、
  sm 高 32 和搜索框一致、浮层和触发框同宽、选中项加粗带 ✓;响应方式栏的签名不再被挤成竖排
- 读标:「依据 · 第 N 页」改成行内出处链接;证明材料标签不再被挤压
- 概览:关键日期同一种、同一时刻只列一次
- 报告页:「还有 N 项致命」读项目实时值(检查的 counts 是快照)
- 读标抽取:★ 只表示「重要」的文件,清单项也挂到 ★ 规则上(「已识别 N 条」)
- 检查范围文案和后端覆盖范围一致;报告出处不重复写文件名(模板 2026.11-v3)
- 带加载态的按钮图标和文字间距;单选、半选、禁用复选框样式

查标结果页按原型 results.html 补齐:
- 本次处理的问题留在原位(署名行可撤销),修了处理后仍显示处理按钮的问题
- 键盘 J / K / O / 1 / 2 / 3 / X / U / Esc;「提示」批量确认无误;「判断有误?」反馈
- 覆盖范围默认收起成一行;顶部加导出报告入口和致命提示;已处理计数
- 检查进度卡和解析进度卡同一种样式
- 项目头「就绪 / 未就绪」,致命数挂在查标标签上
- 上传投标文件页:已有分册时新增表单收起到「添加分册」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-05 11:38:56 +08:00
parent 6e0d4c1dd6
commit 1966ce9b15
16 changed files with 529 additions and 127 deletions

View File

@@ -565,6 +565,13 @@ def _item(
if "basis" not in kw and importance == "substantive":
# 按哪种标注认定为实质性:清单项据此挂到对应的标注规则上;★ 节里的句子挂 ★
kw["basis"] = next((m for x in sents if (m := is_marked(x, doc.declared))), "star")
elif (
"basis" not in kw
and importance == "important"
and STAR_IMPORTANT in doc.declared
and any("★" in x.text for x in sents)
):
kw["basis"] = "star" # ★ 只是「重要」的文件也挂到 ★ 规则上,清单页「已识别 N 条」才数得对
if importance == "substantive" and "invalidity" not in categories:
categories = [*categories, "invalidity"] # ★ 条款同时进「无效 / 废标」类(FR-M2-05)
return Extracted(

View File

@@ -470,9 +470,12 @@ async def key_dates(s: AsyncSession, project_id: UUID) -> list[dict[str, Any]]:
).scalars()
}
out = []
for r in rows:
seen: set[tuple[str, str]] = set()
for r in sorted(rows, key=lambda x: x.sort_key or ""):
sched = (r.spec or {}).get("schedule") or {}
if sched.get("at"):
# 同一种日期、同一时刻在文件里写了几处(须知前附表、公告、投标资料表):时间线上只列一次
if sched.get("at") and (sched.get("date_kind", "other"), str(sched["at"])) not in seen:
seen.add((sched.get("date_kind", "other"), str(sched["at"])))
out.append(
{
"item_id": r.id,

View File

@@ -19,7 +19,7 @@ from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo
TEMPLATE_VERSION = "2026.11-v2" # v2:资质问题的「依据:资质库记录」
TEMPLATE_VERSION = "2026.11-v3" # v2:资质问题的「依据:资质库记录」;v3:出处不重复写文件名
TITLE = "投标文件辅助检查报告"
FOOTER = "本报告为辅助检查结果,仅供投标前自查参考"
DISCLAIMER = (
@@ -96,6 +96,12 @@ def _cite(c: dict[str, Any]) -> str:
)
def _side(side: str, c: dict[str, Any]) -> str:
"""出处前标明是招标侧还是投标侧;文件名本身就是「招标文件」「投标文件」时不重复写。"""
head = "" if (c.get("document_label") or "").startswith(side) else f"{side}:"
return f"<div>{head}{_cite(c)}</div>"
def _library(f: dict[str, Any]) -> list[str]:
"""只按资质库记录判断的资质问题:依据是资质库那条记录的快照(QL2)。"""
if f.get("type") != "qualification":
@@ -182,8 +188,8 @@ def findings(d: dict[str, Any]) -> str:
for f in d["findings"]:
cites = ""
if d["options"].get("citations", True):
parts = [f"<div>招标文件:{_cite(c)}</div>" for c in f.get("tender_citations") or []]
parts += [f"<div>投标文件:{_cite(c)}</div>" for c in f.get("bid_citations") or []]
parts = [_side("招标文件", c) for c in f.get("tender_citations") or []]
parts += [_side("投标文件", c) for c in f.get("bid_citations") or []]
parts += _library(f)
cites = f"<div class='cites'>{''.join(parts)}</div>" if parts else ""
carried = "(沿用上次检查)" if f.get("presence") == "carried" else ""