diff --git a/pipeline_opportunity/opp_common.py b/pipeline_opportunity/opp_common.py index 0ad104c..eb12e44 100644 --- a/pipeline_opportunity/opp_common.py +++ b/pipeline_opportunity/opp_common.py @@ -23,6 +23,58 @@ logger = logging.getLogger("pipeline.opportunity") # 爬虫平台默认接入点(内网网卡地址,公网不可达);可被 params 表覆盖 DEFAULT_API_BASE = "http://192.168.16.2:9085" +# ── 数据来源登记表(code → 显示名称/来源主页)── +# 名称与主页对齐爬虫平台数据源注册表 tender_opp_build/sites.yaml(事实源), +# 众包/海外平台补齐。明细弹窗用名称展示、点击直跳来源 url(记录级 source_url +# 优先,缺失回退来源主页)。新增采集源时在此登记。 +SOURCE_REGISTRY = { + "ccgp": ("中国政府采购网", "http://www.ccgp.gov.cn/"), + "ggzy": ("全国公共资源交易平台", "https://www.ggzy.gov.cn/"), + "cebpub": ("中国招标投标公共服务平台", "https://www.cebpubservice.com/"), + "zycg": ("中央国家机关政府采购中心", "https://www.zycg.gov.cn/"), + "weain": ("军队采购网", "http://www.weain.mil.cn/"), + "chinabidding": ("中国招标网", "https://www.chinabidding.com.cn/"), + "qianlima": ("千里马招标网", "https://www.qianlima.com/"), + "bidcenter": ("中国采购与招标网", "http://www.bidcenter.com.cn/"), + "jianyu360": ("剑鱼标讯", "https://www.jianyu360.cn/"), + "zbj": ("猪八戒", "https://www.zbj.com/"), + "epwk": ("一品威客", "https://www.epwk.com/"), + "freelancer": ("Freelancer.com", "https://www.freelancer.com/"), + "pph": ("PeoplePerHour", "https://www.peopleperhour.com/"), +} + + +def source_display(code): + """来源 code → (显示名称, 来源主页 url)。未登记来源原样回显 code。""" + code = (code or "").strip() + if code in SOURCE_REGISTRY: + return SOURCE_REGISTRY[code] + return (code or "", "") + + +def source_button_widget(code, source_url=""): + """来源名称按钮:显示来源名称,点击新窗口直跳来源 url。 + + 跳转目标优先记录级 source_url(采集来源页,最精确),缺失回退来源主页; + 两者皆无返回 None(不渲染按钮)。code 缺失但有 source_url 时名称显示 + 「采集来源页」。 + """ + code = (code or "").strip() + name, home = source_display(code) + target = (source_url or "").strip() or home + if not target: + return None + if not name: + name = "采集来源页" + return { + "widgettype": "Button", + "options": {"label": name, "css": "small"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "script", + "target": "self", + "script": "window.open(%s,'_blank','noopener');" + % json.dumps(target)}], + } + # ── 报告状态 ── RP_DRAFT = "draft" # 草稿(agent 编写中/待审阅) RP_CONFIRMED = "confirmed" # 人工确认通过(门禁) diff --git a/wwwroot/api/opp_demand_items.dspy b/wwwroot/api/opp_demand_items.dspy index 47ace65..12984cc 100644 --- a/wwwroot/api/opp_demand_items.dspy +++ b/wwwroot/api/opp_demand_items.dspy @@ -16,6 +16,7 @@ dbname = get_module_dbname('pipeline-opportunity') async with DBPools().sqlorContext(dbname) as sor: from pipeline_opportunity.opp_data_capability import category_references + from pipeline_opportunity.opp_common import source_button_widget ok, res = await category_references(sor, category, days=days, limit=500, record_type="demand") await sor.sqlExe("COMMIT", {}) @@ -40,11 +41,10 @@ else: "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": "window.open(" + _json.dumps(url) + ",'_blank','noopener');"}]}) - if src and src != url: - sub_btns.append({"widgettype": "Button", "options": {"label": "来源页", "css": "small"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "script", - "target": "self", - "script": "window.open(" + _json.dumps(src) + ",'_blank','noopener');"}]}) + # 来源:显示来源名称,点击直跳来源 url(2026-09-08 用户定夺) + _sb = source_button_widget(str(it.get("source") or ""), src) + if _sb: + sub_btns.append(_sb) budget = it.get("budget_wan") or 0 plat = SRC_NAME.get(str(it.get("source") or ""), str(it.get("source") or "众包平台")) meta = "%s | %s | %s" % ( @@ -82,7 +82,7 @@ return { "padding": "12px 16px", "gap": "0px", "overflow": "auto"}, "subwidgets": [ {"widgettype": "Text", "options": { - "text": "每条「需求详情」为众包平台(猪八戒/一品威客)原始需求页,「来源页」为采集来源页,均在新窗口打开,可逐条查证。", + "text": "每条「需求详情」为众包平台原始需求页;来源按钮显示平台名称、点击直达该条采集来源页,均在新窗口打开,可逐条查证。", "cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}} ] + rows }] diff --git a/wwwroot/api/opp_overseas_items.dspy b/wwwroot/api/opp_overseas_items.dspy index a2a7861..0096959 100644 --- a/wwwroot/api/opp_overseas_items.dspy +++ b/wwwroot/api/opp_overseas_items.dspy @@ -16,6 +16,7 @@ dbname = get_module_dbname('pipeline-opportunity') async with DBPools().sqlorContext(dbname) as sor: from pipeline_opportunity.opp_data_capability import overseas_references + from pipeline_opportunity.opp_common import source_button_widget ok, res = await overseas_references(sor, category, days=days, limit=500) await sor.sqlExe("COMMIT", {}) @@ -40,6 +41,10 @@ else: "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": "window.open(" + _json.dumps(url) + ",'_blank','noopener');"}]}) + # 来源:显示来源名称,点击直跳来源 url(2026-09-08 用户定夺) + _sb = source_button_widget(str(it.get("source") or ""), str(it.get("source_url") or "")) + if _sb: + sub_btns.append(_sb) budget = it.get("budget_wan") or 0 meta = "%s | %s | %s" % ( str(it.get("publish_time") or "")[:10], @@ -76,7 +81,7 @@ return { "padding": "12px 16px", "gap": "0px", "overflow": "auto"}, "subwidgets": [ {"widgettype": "Text", "options": { - "text": "每条「需求详情」为海外众包平台(Freelancer.com / PeoplePerHour)原始需求页,在新窗口打开,可逐条查证。", + "text": "每条「需求详情」为海外众包平台原始需求页;来源按钮显示平台名称、点击直达平台主页,在新窗口打开,可逐条查证。", "cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}} ] + rows }] diff --git a/wwwroot/api/opp_references_items.dspy b/wwwroot/api/opp_references_items.dspy index 915f5bd..0e8b21a 100644 --- a/wwwroot/api/opp_references_items.dspy +++ b/wwwroot/api/opp_references_items.dspy @@ -16,6 +16,7 @@ dbname = get_module_dbname('pipeline-opportunity') async with DBPools().sqlorContext(dbname) as sor: from pipeline_opportunity.opp_data_capability import category_references + from pipeline_opportunity.opp_common import source_button_widget ok, res = await category_references(sor, category, days=days, limit=500) await sor.sqlExe("COMMIT", {}) @@ -38,11 +39,10 @@ else: "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": "window.open(" + _json.dumps(url) + ",'_blank','noopener');"}]}) - if src and src != url: - sub_btns.append({"widgettype": "Button", "options": {"label": "来源页", "css": "small"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "script", - "target": "self", - "script": "window.open(" + _json.dumps(src) + ",'_blank','noopener');"}]}) + # 来源:显示来源名称,点击直跳来源 url(2026-09-08 用户定夺) + _sb = source_button_widget(str(it.get("source") or ""), src) + if _sb: + sub_btns.append(_sb) budget = it.get("budget_wan") or 0 meta = "%s | %s | %s" % ( str(it.get("publish_time") or "")[:10], @@ -79,7 +79,7 @@ return { "padding": "12px 16px", "gap": "0px", "overflow": "auto"}, "subwidgets": [ {"widgettype": "Text", "options": { - "text": "每条「公告原文」为招标公告原链接、「来源页」为采集来源页,均在新窗口打开,可逐条查证。", + "text": "每条「公告原文」为招标公告原链接;来源按钮显示数据来源名称、点击直达该条采集来源页,均在新窗口打开,可逐条查证。", "cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}} ] + rows }]