commit 68b9e253e2a009c0e5aafb55dd2030f6c79c0164 Author: wanghep Date: Fri Mar 20 22:59:54 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f858d97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +frontend/node_modules/ +frontend/dist/ +backend/.venv/ +backend/__pycache__/ +backend/app/__pycache__/ +*.pyc +*.log + diff --git a/.playwright-cli/page-2026-03-19T13-50-58-945Z.yml b/.playwright-cli/page-2026-03-19T13-50-58-945Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-50-58-945Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-51-15-675Z.yml b/.playwright-cli/page-2026-03-19T13-51-15-675Z.yml new file mode 100644 index 0000000..58cad9b --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-51-15-675Z.yml @@ -0,0 +1,44 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e47] [cursor=pointer] + - button "3/19" [ref=e23] [cursor=pointer] + - button "3/18" [ref=e48] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: Failed to fetch + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-51-29-769Z.png b/.playwright-cli/page-2026-03-19T13-51-29-769Z.png new file mode 100644 index 0000000..9176e45 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-51-29-769Z.png differ diff --git a/.playwright-cli/page-2026-03-19T13-52-33-918Z.yml b/.playwright-cli/page-2026-03-19T13-52-33-918Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-52-33-918Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-52-33-919Z.yml b/.playwright-cli/page-2026-03-19T13-52-33-919Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-52-33-919Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-53-40-876Z.png b/.playwright-cli/page-2026-03-19T13-53-40-876Z.png new file mode 100644 index 0000000..31e3669 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-53-40-876Z.png differ diff --git a/.playwright-cli/page-2026-03-19T13-54-45-662Z.yml b/.playwright-cli/page-2026-03-19T13-54-45-662Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-54-45-662Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-54-45-856Z.png b/.playwright-cli/page-2026-03-19T13-54-45-856Z.png new file mode 100644 index 0000000..f0ae9a4 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-54-45-856Z.png differ diff --git a/.playwright-cli/page-2026-03-19T13-54-46-624Z.yml b/.playwright-cli/page-2026-03-19T13-54-46-624Z.yml new file mode 100644 index 0000000..f023411 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-54-46-624Z.yml @@ -0,0 +1,369 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e47] [cursor=pointer] + - button "3/19" [ref=e23] [cursor=pointer] + - button "3/18" [ref=e48] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 资讯列表展示所选日期内的财联社 7x24 资讯,当日数据来自 cls.cn 实时抓取,每 3 分钟更新一次。 + - generic [ref=e49]: + - generic [ref=e50]: AI + - generic [ref=e51]: 算力 + - generic [ref=e52]: 石油天然气 + - generic [ref=e53]: 半导体 + - generic [ref=e54]: 券商 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - generic [ref=e55]: + - article [ref=e56]: + - generic [ref=e57]: + - generic [ref=e58]: + - paragraph [ref=e59]: 当天资讯 + - heading "热点概览" [level=3] [ref=e60] + - generic [ref=e61]: + - generic [ref=e62]: 3/19 + - generic [ref=e63]: 最近更新 3/19 21:54 + - generic [ref=e64]: 3 分钟更新 + - generic [ref=e65]: + - paragraph [ref=e66]: 资讯列表展示所选日期内的财联社 7x24 资讯,当日数据来自 cls.cn 实时抓取,每 3 分钟更新一次。 + - paragraph [ref=e67]: 热点概览只保留对板块存在明显影响的方向,当前主要集中在 AI、算力、石油天然气。 + - generic [ref=e68]: + - generic [ref=e69]: 关键方向 + - generic [ref=e70]: + - generic [ref=e71]: AI + - generic [ref=e72]: 算力 + - generic [ref=e73]: 石油天然气 + - generic [ref=e74]: 半导体 + - generic [ref=e75]: 券商 + - generic [ref=e76]: + - article [ref=e77]: + - generic [ref=e78]: + - generic [ref=e79]: + - strong [ref=e80]: AI + - paragraph [ref=e81]: AI 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e82]: 看多 + - list [ref=e83]: + - listitem [ref=e84]: 【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响 + - listitem [ref=e85]: 推动亚太地区能源合作 中方提出三方面建议 + - article [ref=e86]: + - generic [ref=e87]: + - generic [ref=e88]: + - strong [ref=e89]: 算力 + - paragraph [ref=e90]: 算力 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e91]: 看多 + - list [ref=e92]: + - listitem [ref=e93]: 【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响 + - listitem [ref=e94]: 财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入 + - article [ref=e95]: + - generic [ref=e96]: + - generic [ref=e97]: + - strong [ref=e98]: 石油天然气 + - paragraph [ref=e99]: 石油天然气 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e100]: 看多 + - list [ref=e101]: + - listitem [ref=e102]: 财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年 + - listitem [ref=e103]: 财联社3月19日晚间新闻精选 + - article [ref=e104]: + - generic [ref=e105]: + - generic [ref=e106]: + - strong [ref=e107]: 半导体 + - paragraph [ref=e108]: 半导体 方向有讨论但仍需验证,短线以中性观察为主。 + - generic [ref=e109]: 中性 + - list [ref=e110]: + - listitem [ref=e111]: 费城半导体指数开盘跌超3% 美光科技跌近8% + - generic [ref=e112]: + - generic [ref=e113]: + - generic [ref=e114]: + - generic [ref=e115]: 资讯列表 + - generic [ref=e116]: 展示 3月19日周四 全天财联社推送,可直接滚轮浏览 + - generic [ref=e117]: 20 / 20 条 + - generic [ref=e118]: + - textbox "按资讯标题或摘要筛选" [ref=e119] + - combobox [ref=e120]: + - option "全部来源" [selected] + - option "财联社7x24" + - option "央视新闻" + - generic [ref=e121]: + - article [ref=e122]: + - generic [ref=e123]: + - generic [ref=e124]: 财联社7x24 + - generic [ref=e125]: 3/19 21:50 + - generic [ref=e126]: 看多 + - heading "【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响" [level=3] [ref=e127] + - paragraph [ref=e128]: ①金油比回落对美股、A股风格的暗示;②单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;③今日全市场机构研报共发布126篇,三元股份、万辰集团评级得到上调,9家公司获得首度覆盖,其中振石股份、江苏神通获新财富分析师深度覆盖;④在个股机构关注度排行中,万辰集团首次上榜,前五名依次为宝丰能源>天味食品>宁德时代>万辰集团>招商积余。 + - generic [ref=e129]: + - generic [ref=e130]: + - generic [ref=e131]: AI + - generic [ref=e132]: 算力 + - link "参考链接" [ref=e133] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318249?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e134]: + - generic [ref=e135]: + - generic [ref=e136]: 财联社7x24 + - generic [ref=e137]: 3/19 21:49 + - generic [ref=e138]: 中性 + - heading "推动亚太地区能源合作 中方提出三方面建议" [level=3] [ref=e139] + - paragraph [ref=e140]: 【推动亚太地区能源合作 中方提出三方面建议】财联社3月19日电,据国家能源局,3月18日至19日,亚太经合组织(APEC)能源工作组第71次会议在江西南昌举行,中方围绕“高质量能源普遍服务”“人工智能+能源”“亚太能源协同共治”三大优先方向提出三方面建议。一是坚持普惠,倡导通过包容性进程、多元化技术路径、针对性赋能与紧密区域协作,共同推进亚太地区能源高质量普遍服务,让发展成果惠及亚太每一个人。二是加快创新,倡导通过发展战略、治理规则、技术标准等方面的融合对接,共同推动人工智能与能源的融合发展和双向赋能,形成具有地区特色、面向未来的创新发展共识。三是强化协同,倡导通过加强政策对话、信息共享、能力 + - generic [ref=e141]: + - generic [ref=e143]: AI + - link "参考链接" [ref=e144] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318691?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e145]: + - generic [ref=e146]: + - generic [ref=e147]: 财联社7x24 + - generic [ref=e148]: 3/19 21:45 + - generic [ref=e149]: 中性 + - heading "财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入" [level=3] [ref=e150] + - paragraph [ref=e151]: 财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入。 + - generic [ref=e152]: + - generic [ref=e153]: + - generic [ref=e154]: AI + - generic [ref=e155]: 算力 + - link "参考链接" [ref=e156] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318689?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e157]: + - generic [ref=e158]: + - generic [ref=e159]: 财联社7x24 + - generic [ref=e160]: 3/19 21:45 + - generic [ref=e161]: 看多 + - heading "财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年" [level=3] [ref=e162] + - paragraph [ref=e163]: 财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年。 + - generic [ref=e164]: + - generic [ref=e166]: 石油天然气 + - link "参考链接" [ref=e167] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318688?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e168]: + - generic [ref=e169]: + - generic [ref=e170]: 财联社7x24 + - generic [ref=e171]: 3/19 21:42 + - generic [ref=e172]: 看多 + - heading "财联社3月19日晚间新闻精选" [level=3] [ref=e173] + - paragraph [ref=e174]: 【财联社3月19日晚间新闻精选】 1、工信部:探索人工智能等在材料研发、中试、生产等典型场景应用,加速前沿新材料创制应用。 2、阿里宣布AI战略商业目标:未来五年,云和AI商业化年收入突破1000亿美元。 3、美国财政部长贝森特表示,或在未来数日内解除对海上伊朗石油的制裁。此外,美国或再次释放战略石油储备以抑制油价。 4、国际贵金属大幅走低,现货白银日内跌幅一度超13%,报65.205美元/盎司。现货黄金一度跌6.8%,报4491美元/盎司。 5、美国上周初请失业金人数不及预期,交易员不再押注美联储2026年降息。此外,交易员充分消化了英国央行年内三次、每次25个基点的加息预期。 6、①江波龙 + - generic [ref=e175]: + - generic [ref=e176]: + - generic [ref=e177]: AI + - generic [ref=e178]: 石油天然气 + - link "参考链接" [ref=e179] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318684?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e180]: + - generic [ref=e181]: + - generic [ref=e182]: 财联社7x24 + - generic [ref=e183]: 3/19 21:42 + - generic [ref=e184]: 中性 + - heading "财联社3月19日电,墨西哥主要股票指数开盘后不久即下跌近2%" [level=3] [ref=e185] + - paragraph [ref=e186]: 财联社3月19日电,墨西哥主要股票指数开盘后不久即下跌近2%。 + - generic [ref=e187]: + - generic [ref=e188]: + - generic [ref=e189]: AI + - generic [ref=e190]: 算力 + - link "参考链接" [ref=e191] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318686?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e192]: + - generic [ref=e193]: + - generic [ref=e194]: 财联社7x24 + - generic [ref=e195]: 3/19 21:41 + - generic [ref=e196]: 中性 + - heading "财联社3月19日电,美国监管机构公布放宽大银行资本金要求的计划" [level=3] [ref=e197] + - paragraph [ref=e198]: 财联社3月19日电,美国监管机构公布放宽大银行资本金要求的计划。 + - generic [ref=e199]: + - generic [ref=e200]: + - generic [ref=e201]: AI + - generic [ref=e202]: 算力 + - link "参考链接" [ref=e203] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318683?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e204]: + - generic [ref=e205]: + - generic [ref=e206]: 财联社7x24 + - generic [ref=e207]: 3/19 21:36 + - generic [ref=e208]: 中性 + - heading "宜家母公司英格卡或裁员800人以推进重组" [level=3] [ref=e209] + - paragraph [ref=e210]: 【宜家母公司英格卡或裁员800人以推进重组】财联社3月19日电,宜家母公司英格卡集团表示,为简化集团架构,其集团职能部门可能裁减800个岗位。此外,英格卡还计划在明年九月前开设多达20家新门店,预计将创造500个新岗位。 + - generic [ref=e211]: + - generic [ref=e212]: + - generic [ref=e213]: AI + - generic [ref=e214]: 算力 + - link "参考链接" [ref=e215] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318680?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e216]: + - generic [ref=e217]: + - generic [ref=e218]: 财联社7x24 + - generic [ref=e219]: 3/19 21:36 + - generic [ref=e220]: 中性 + - heading "费城半导体指数开盘跌超3% 美光科技跌近8%" [level=3] [ref=e221] + - paragraph [ref=e222]: 【费城半导体指数开盘跌超3% 美光科技跌近8%】财联社3月19日电,费城半导体指数开盘跌超3%。英伟达股价下跌1.74%,台积电股价下跌3.07%,博通股价下跌1.82%,阿斯麦股价下跌2.98%,美光科技股价下跌7.63%。 + - generic [ref=e223]: + - generic [ref=e225]: 半导体 + - link "参考链接" [ref=e226] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318677?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e227]: + - generic [ref=e228]: + - generic [ref=e229]: 财联社7x24 + - generic [ref=e230]: 3/19 21:35 + - generic [ref=e231]: 中性 + - heading "中国联通:2025年净利润同比增长1.1% 拟派发全年股利约51.12亿元" [level=3] [ref=e232] + - paragraph [ref=e233]: 【中国联通:2025年净利润同比增长1.1%,拟派发全年股利约51.12亿元】财联社3月19日电,中国联通(600050.SH)公告称,公司2025年实现营业收入3922.23亿元,同比增长0.7%;归属于上市公司股东的净利润为91.27亿元,同比增长1.1%。公司拟派发2025年度末期现金股利每10股0.523元(含税),连同中期股利,全年每10股合计派发1.635元(含税),共计约51.12亿元(含税)。本次利润分配方案尚需提交股东大会审议。 + - generic [ref=e234]: + - generic [ref=e235]: + - generic [ref=e236]: AI + - generic [ref=e237]: 算力 + - link "参考链接" [ref=e238] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318676?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e239]: + - generic [ref=e240]: + - generic [ref=e241]: 财联社7x24 + - generic [ref=e242]: 3/19 21:35 + - generic [ref=e243]: 中性 + - heading "美股纳指低开1.3% 阿里巴巴跌超8%" [level=3] [ref=e244] + - paragraph [ref=e245]: 【美股纳指低开1.3% 阿里巴巴跌超8%】美股三大指数集体低开,纳指跌1.28%,道指跌0.72%,标普500指数跌0.99%。科技股全线下跌,美光科技跌超8%,西部数据跌超4%,AMD跌超2%,英伟达、甲骨文跌超1%。中概股普跌,阿里巴巴跌超8%,百度跌超3%。 + - generic [ref=e246]: + - generic [ref=e247]: + - generic [ref=e248]: AI + - generic [ref=e249]: 算力 + - link "参考链接" [ref=e250] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318671?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e251]: + - generic [ref=e252]: + - generic [ref=e253]: 财联社7x24 + - generic [ref=e254]: 3/19 21:34 + - generic [ref=e255]: 中性 + - heading "财联社3月19日电,纳斯达克中国金龙指数跌幅扩大,现跌3.0%,最新报6804.11点" [level=3] [ref=e256] + - paragraph [ref=e257]: 财联社3月19日电,纳斯达克中国金龙指数跌幅扩大,现跌3.0%,最新报6804.11点。 + - generic [ref=e258]: + - generic [ref=e259]: + - generic [ref=e260]: AI + - generic [ref=e261]: 算力 + - link "参考链接" [ref=e262] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318678?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e263]: + - generic [ref=e264]: + - generic [ref=e265]: 财联社7x24 + - generic [ref=e266]: 3/19 21:32 + - generic [ref=e267]: 中性 + - heading "财联社3月19日电,利弗莫尔中概股龙头指数盘初跌3.8%,成分股中,阿特斯太阳能跌超27%,阿里巴巴跌超9%,金山云跌超5%" [level=3] [ref=e268] + - paragraph [ref=e269]: 财联社3月19日电,利弗莫尔中概股龙头指数盘初跌3.8%,成分股中,阿特斯太阳能跌超27%,阿里巴巴跌超9%,金山云跌超5%。 + - generic [ref=e270]: + - generic [ref=e271]: + - generic [ref=e272]: AI + - generic [ref=e273]: 算力 + - link "参考链接" [ref=e274] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318675?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e275]: + - generic [ref=e276]: + - generic [ref=e277]: 财联社7x24 + - generic [ref=e278]: 3/19 21:31 + - generic [ref=e279]: 中性 + - heading "财联社3月19日电,罗素2000指数今日开盘跌幅达1.1%,较盘中纪录高点下跌10%" [level=3] [ref=e280] + - paragraph [ref=e281]: 财联社3月19日电,罗素2000指数今日开盘跌幅达1.1%,较盘中纪录高点下跌10%。 + - generic [ref=e282]: + - generic [ref=e283]: + - generic [ref=e284]: AI + - generic [ref=e285]: 算力 + - link "参考链接" [ref=e286] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318674?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e287]: + - generic [ref=e288]: + - generic [ref=e289]: 财联社7x24 + - generic [ref=e290]: 3/19 21:31 + - generic [ref=e291]: 中性 + - heading "财联社3月19日电,捷克央行将关键利率维持在3.5%,符合市场预期" [level=3] [ref=e292] + - paragraph [ref=e293]: 财联社3月19日电,捷克央行将关键利率维持在3.5%,符合市场预期。 + - generic [ref=e294]: + - generic [ref=e295]: + - generic [ref=e296]: AI + - generic [ref=e297]: 算力 + - link "参考链接" [ref=e298] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318672?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e299]: + - generic [ref=e300]: + - generic [ref=e301]: 财联社7x24 + - generic [ref=e302]: 3/19 21:28 + - generic [ref=e303]: 中性 + - heading "卡塔尔能源公司:袭击造成的年收入损失约为200亿美元" [level=3] [ref=e304] + - paragraph [ref=e305]: 【卡塔尔能源公司:袭击造成的年收入损失约为200亿美元】财联社3月19日电,卡塔尔能源公司CEO表示,在袭击中,14条液化天然气生产线中的2条以及两座天然气制油(GTL)设施中的1座遭到破坏。三个受损设施造成的年收入损失约为200亿美元。 + - generic [ref=e306]: + - generic [ref=e308]: 石油天然气 + - link "参考链接" [ref=e309] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318669?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e310]: + - generic [ref=e311]: + - generic [ref=e312]: 财联社7x24 + - generic [ref=e313]: 3/19 21:28 + - generic [ref=e314]: 中性 + - heading "财联社3月19日电,据报道,印度尼西亚将确保预算赤字保持在GDP的3%以下" [level=3] [ref=e315] + - paragraph [ref=e316]: 财联社3月19日电,据报道,印度尼西亚将确保预算赤字保持在GDP的3%以下。 + - generic [ref=e317]: + - generic [ref=e318]: + - generic [ref=e319]: AI + - generic [ref=e320]: 算力 + - link "参考链接" [ref=e321] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318670?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e322]: + - generic [ref=e323]: + - generic [ref=e324]: 财联社7x24 + - generic [ref=e325]: 3/19 21:26 + - generic [ref=e326]: 中性 + - heading "财联社3月19日电,中东冲突导致燃油价格飙升,德国考虑征收暴利税" [level=3] [ref=e327] + - paragraph [ref=e328]: 财联社3月19日电,中东冲突导致燃油价格飙升,德国考虑征收暴利税。 + - generic [ref=e329]: + - generic [ref=e330]: + - generic [ref=e331]: AI + - generic [ref=e332]: 算力 + - link "参考链接" [ref=e333] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318666?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e334]: + - generic [ref=e335]: + - generic [ref=e336]: 财联社7x24 + - generic [ref=e337]: 3/19 21:25 + - generic [ref=e338]: 中性 + - heading "四川英发睿能科技股份有限公司向港交所提交上市申请书" [level=3] [ref=e339] + - paragraph [ref=e340]: 【四川英发睿能科技股份有限公司向港交所提交上市申请书】财联社3月19日电,利弗莫尔证券显示,四川英发睿能科技股份有限公司向港交所提交上市申请书,联席保荐人为中信建投国际、华泰国际。 + - generic [ref=e341]: + - generic [ref=e343]: 券商 + - link "参考链接" [ref=e344] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318667?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e345]: + - generic [ref=e346]: + - generic [ref=e347]: 央视新闻 + - generic [ref=e348]: 3/19 21:21 + - generic [ref=e349]: 中性 + - heading "阿联酋总统与埃及总统会谈 讨论中东局势等问题" [level=3] [ref=e350] + - paragraph [ref=e351]: 【阿联酋总统与埃及总统会谈 讨论中东局势等问题】财联社3月19日电,阿联酋总统穆罕默德在阿联酋阿布扎比会见到访的埃及总统塞西。双方就双边合作以及当前中东局势进行了讨论。双方表示,持续的军事行动升级对地区和国际安全与稳定产生了严重影响。塞西表示,埃及支持阿联酋为维护国家安全、领土完整及人民安全所采取的措施。双方同时强调,应立即停止局势升级,通过对话和外交方式解决地区问题,以防止紧张局势和危机进一步加剧,维护地区安全与稳定。 + - generic [ref=e352]: + - generic [ref=e353]: + - generic [ref=e354]: AI + - generic [ref=e355]: 算力 + - link "参考链接" [ref=e356] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318662?os=&sv=8.4.4&app=CailianpressWap \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-54-59-504Z.png b/.playwright-cli/page-2026-03-19T13-54-59-504Z.png new file mode 100644 index 0000000..390c0dc Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-54-59-504Z.png differ diff --git a/.playwright-cli/page-2026-03-19T13-54-59-566Z.yml b/.playwright-cli/page-2026-03-19T13-54-59-566Z.yml new file mode 100644 index 0000000..f023411 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-54-59-566Z.yml @@ -0,0 +1,369 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e47] [cursor=pointer] + - button "3/19" [ref=e23] [cursor=pointer] + - button "3/18" [ref=e48] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 资讯列表展示所选日期内的财联社 7x24 资讯,当日数据来自 cls.cn 实时抓取,每 3 分钟更新一次。 + - generic [ref=e49]: + - generic [ref=e50]: AI + - generic [ref=e51]: 算力 + - generic [ref=e52]: 石油天然气 + - generic [ref=e53]: 半导体 + - generic [ref=e54]: 券商 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - generic [ref=e55]: + - article [ref=e56]: + - generic [ref=e57]: + - generic [ref=e58]: + - paragraph [ref=e59]: 当天资讯 + - heading "热点概览" [level=3] [ref=e60] + - generic [ref=e61]: + - generic [ref=e62]: 3/19 + - generic [ref=e63]: 最近更新 3/19 21:54 + - generic [ref=e64]: 3 分钟更新 + - generic [ref=e65]: + - paragraph [ref=e66]: 资讯列表展示所选日期内的财联社 7x24 资讯,当日数据来自 cls.cn 实时抓取,每 3 分钟更新一次。 + - paragraph [ref=e67]: 热点概览只保留对板块存在明显影响的方向,当前主要集中在 AI、算力、石油天然气。 + - generic [ref=e68]: + - generic [ref=e69]: 关键方向 + - generic [ref=e70]: + - generic [ref=e71]: AI + - generic [ref=e72]: 算力 + - generic [ref=e73]: 石油天然气 + - generic [ref=e74]: 半导体 + - generic [ref=e75]: 券商 + - generic [ref=e76]: + - article [ref=e77]: + - generic [ref=e78]: + - generic [ref=e79]: + - strong [ref=e80]: AI + - paragraph [ref=e81]: AI 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e82]: 看多 + - list [ref=e83]: + - listitem [ref=e84]: 【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响 + - listitem [ref=e85]: 推动亚太地区能源合作 中方提出三方面建议 + - article [ref=e86]: + - generic [ref=e87]: + - generic [ref=e88]: + - strong [ref=e89]: 算力 + - paragraph [ref=e90]: 算力 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e91]: 看多 + - list [ref=e92]: + - listitem [ref=e93]: 【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响 + - listitem [ref=e94]: 财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入 + - article [ref=e95]: + - generic [ref=e96]: + - generic [ref=e97]: + - strong [ref=e98]: 石油天然气 + - paragraph [ref=e99]: 石油天然气 方向出现催化或景气强化,短线偏正向影响。 + - generic [ref=e100]: 看多 + - list [ref=e101]: + - listitem [ref=e102]: 财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年 + - listitem [ref=e103]: 财联社3月19日晚间新闻精选 + - article [ref=e104]: + - generic [ref=e105]: + - generic [ref=e106]: + - strong [ref=e107]: 半导体 + - paragraph [ref=e108]: 半导体 方向有讨论但仍需验证,短线以中性观察为主。 + - generic [ref=e109]: 中性 + - list [ref=e110]: + - listitem [ref=e111]: 费城半导体指数开盘跌超3% 美光科技跌近8% + - generic [ref=e112]: + - generic [ref=e113]: + - generic [ref=e114]: + - generic [ref=e115]: 资讯列表 + - generic [ref=e116]: 展示 3月19日周四 全天财联社推送,可直接滚轮浏览 + - generic [ref=e117]: 20 / 20 条 + - generic [ref=e118]: + - textbox "按资讯标题或摘要筛选" [ref=e119] + - combobox [ref=e120]: + - option "全部来源" [selected] + - option "财联社7x24" + - option "央视新闻" + - generic [ref=e121]: + - article [ref=e122]: + - generic [ref=e123]: + - generic [ref=e124]: 财联社7x24 + - generic [ref=e125]: 3/19 21:50 + - generic [ref=e126]: 看多 + - heading "【风口研报·洞察】单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;金油比回落的影响" [level=3] [ref=e127] + - paragraph [ref=e128]: ①金油比回落对美股、A股风格的暗示;②单日涨幅高达20%,硅烷偶联剂开启强势修复,多家行业龙头采取“一价一议、实时报价”策略,目前当前主流品种价格仅处于历史低位区间,相关上市公司的利润水平有望迎来反弹;③今日全市场机构研报共发布126篇,三元股份、万辰集团评级得到上调,9家公司获得首度覆盖,其中振石股份、江苏神通获新财富分析师深度覆盖;④在个股机构关注度排行中,万辰集团首次上榜,前五名依次为宝丰能源>天味食品>宁德时代>万辰集团>招商积余。 + - generic [ref=e129]: + - generic [ref=e130]: + - generic [ref=e131]: AI + - generic [ref=e132]: 算力 + - link "参考链接" [ref=e133] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318249?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e134]: + - generic [ref=e135]: + - generic [ref=e136]: 财联社7x24 + - generic [ref=e137]: 3/19 21:49 + - generic [ref=e138]: 中性 + - heading "推动亚太地区能源合作 中方提出三方面建议" [level=3] [ref=e139] + - paragraph [ref=e140]: 【推动亚太地区能源合作 中方提出三方面建议】财联社3月19日电,据国家能源局,3月18日至19日,亚太经合组织(APEC)能源工作组第71次会议在江西南昌举行,中方围绕“高质量能源普遍服务”“人工智能+能源”“亚太能源协同共治”三大优先方向提出三方面建议。一是坚持普惠,倡导通过包容性进程、多元化技术路径、针对性赋能与紧密区域协作,共同推进亚太地区能源高质量普遍服务,让发展成果惠及亚太每一个人。二是加快创新,倡导通过发展战略、治理规则、技术标准等方面的融合对接,共同推动人工智能与能源的融合发展和双向赋能,形成具有地区特色、面向未来的创新发展共识。三是强化协同,倡导通过加强政策对话、信息共享、能力 + - generic [ref=e141]: + - generic [ref=e143]: AI + - link "参考链接" [ref=e144] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318691?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e145]: + - generic [ref=e146]: + - generic [ref=e147]: 财联社7x24 + - generic [ref=e148]: 3/19 21:45 + - generic [ref=e149]: 中性 + - heading "财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入" [level=3] [ref=e150] + - paragraph [ref=e151]: 财联社3月19日电,葫芦岛海事局发布航行警告,3月20日6时至8时,渤海部分海域进行军事演习,禁止驶入。 + - generic [ref=e152]: + - generic [ref=e153]: + - generic [ref=e154]: AI + - generic [ref=e155]: 算力 + - link "参考链接" [ref=e156] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318689?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e157]: + - generic [ref=e158]: + - generic [ref=e159]: 财联社7x24 + - generic [ref=e160]: 3/19 21:45 + - generic [ref=e161]: 看多 + - heading "财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年" [level=3] [ref=e162] + - paragraph [ref=e163]: 财联社3月19日电,据报道,伊朗袭击导致卡塔尔17%的液化天然气产能受损,修复期预计3至5年。 + - generic [ref=e164]: + - generic [ref=e166]: 石油天然气 + - link "参考链接" [ref=e167] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318688?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e168]: + - generic [ref=e169]: + - generic [ref=e170]: 财联社7x24 + - generic [ref=e171]: 3/19 21:42 + - generic [ref=e172]: 看多 + - heading "财联社3月19日晚间新闻精选" [level=3] [ref=e173] + - paragraph [ref=e174]: 【财联社3月19日晚间新闻精选】 1、工信部:探索人工智能等在材料研发、中试、生产等典型场景应用,加速前沿新材料创制应用。 2、阿里宣布AI战略商业目标:未来五年,云和AI商业化年收入突破1000亿美元。 3、美国财政部长贝森特表示,或在未来数日内解除对海上伊朗石油的制裁。此外,美国或再次释放战略石油储备以抑制油价。 4、国际贵金属大幅走低,现货白银日内跌幅一度超13%,报65.205美元/盎司。现货黄金一度跌6.8%,报4491美元/盎司。 5、美国上周初请失业金人数不及预期,交易员不再押注美联储2026年降息。此外,交易员充分消化了英国央行年内三次、每次25个基点的加息预期。 6、①江波龙 + - generic [ref=e175]: + - generic [ref=e176]: + - generic [ref=e177]: AI + - generic [ref=e178]: 石油天然气 + - link "参考链接" [ref=e179] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318684?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e180]: + - generic [ref=e181]: + - generic [ref=e182]: 财联社7x24 + - generic [ref=e183]: 3/19 21:42 + - generic [ref=e184]: 中性 + - heading "财联社3月19日电,墨西哥主要股票指数开盘后不久即下跌近2%" [level=3] [ref=e185] + - paragraph [ref=e186]: 财联社3月19日电,墨西哥主要股票指数开盘后不久即下跌近2%。 + - generic [ref=e187]: + - generic [ref=e188]: + - generic [ref=e189]: AI + - generic [ref=e190]: 算力 + - link "参考链接" [ref=e191] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318686?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e192]: + - generic [ref=e193]: + - generic [ref=e194]: 财联社7x24 + - generic [ref=e195]: 3/19 21:41 + - generic [ref=e196]: 中性 + - heading "财联社3月19日电,美国监管机构公布放宽大银行资本金要求的计划" [level=3] [ref=e197] + - paragraph [ref=e198]: 财联社3月19日电,美国监管机构公布放宽大银行资本金要求的计划。 + - generic [ref=e199]: + - generic [ref=e200]: + - generic [ref=e201]: AI + - generic [ref=e202]: 算力 + - link "参考链接" [ref=e203] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318683?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e204]: + - generic [ref=e205]: + - generic [ref=e206]: 财联社7x24 + - generic [ref=e207]: 3/19 21:36 + - generic [ref=e208]: 中性 + - heading "宜家母公司英格卡或裁员800人以推进重组" [level=3] [ref=e209] + - paragraph [ref=e210]: 【宜家母公司英格卡或裁员800人以推进重组】财联社3月19日电,宜家母公司英格卡集团表示,为简化集团架构,其集团职能部门可能裁减800个岗位。此外,英格卡还计划在明年九月前开设多达20家新门店,预计将创造500个新岗位。 + - generic [ref=e211]: + - generic [ref=e212]: + - generic [ref=e213]: AI + - generic [ref=e214]: 算力 + - link "参考链接" [ref=e215] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318680?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e216]: + - generic [ref=e217]: + - generic [ref=e218]: 财联社7x24 + - generic [ref=e219]: 3/19 21:36 + - generic [ref=e220]: 中性 + - heading "费城半导体指数开盘跌超3% 美光科技跌近8%" [level=3] [ref=e221] + - paragraph [ref=e222]: 【费城半导体指数开盘跌超3% 美光科技跌近8%】财联社3月19日电,费城半导体指数开盘跌超3%。英伟达股价下跌1.74%,台积电股价下跌3.07%,博通股价下跌1.82%,阿斯麦股价下跌2.98%,美光科技股价下跌7.63%。 + - generic [ref=e223]: + - generic [ref=e225]: 半导体 + - link "参考链接" [ref=e226] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318677?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e227]: + - generic [ref=e228]: + - generic [ref=e229]: 财联社7x24 + - generic [ref=e230]: 3/19 21:35 + - generic [ref=e231]: 中性 + - heading "中国联通:2025年净利润同比增长1.1% 拟派发全年股利约51.12亿元" [level=3] [ref=e232] + - paragraph [ref=e233]: 【中国联通:2025年净利润同比增长1.1%,拟派发全年股利约51.12亿元】财联社3月19日电,中国联通(600050.SH)公告称,公司2025年实现营业收入3922.23亿元,同比增长0.7%;归属于上市公司股东的净利润为91.27亿元,同比增长1.1%。公司拟派发2025年度末期现金股利每10股0.523元(含税),连同中期股利,全年每10股合计派发1.635元(含税),共计约51.12亿元(含税)。本次利润分配方案尚需提交股东大会审议。 + - generic [ref=e234]: + - generic [ref=e235]: + - generic [ref=e236]: AI + - generic [ref=e237]: 算力 + - link "参考链接" [ref=e238] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318676?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e239]: + - generic [ref=e240]: + - generic [ref=e241]: 财联社7x24 + - generic [ref=e242]: 3/19 21:35 + - generic [ref=e243]: 中性 + - heading "美股纳指低开1.3% 阿里巴巴跌超8%" [level=3] [ref=e244] + - paragraph [ref=e245]: 【美股纳指低开1.3% 阿里巴巴跌超8%】美股三大指数集体低开,纳指跌1.28%,道指跌0.72%,标普500指数跌0.99%。科技股全线下跌,美光科技跌超8%,西部数据跌超4%,AMD跌超2%,英伟达、甲骨文跌超1%。中概股普跌,阿里巴巴跌超8%,百度跌超3%。 + - generic [ref=e246]: + - generic [ref=e247]: + - generic [ref=e248]: AI + - generic [ref=e249]: 算力 + - link "参考链接" [ref=e250] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318671?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e251]: + - generic [ref=e252]: + - generic [ref=e253]: 财联社7x24 + - generic [ref=e254]: 3/19 21:34 + - generic [ref=e255]: 中性 + - heading "财联社3月19日电,纳斯达克中国金龙指数跌幅扩大,现跌3.0%,最新报6804.11点" [level=3] [ref=e256] + - paragraph [ref=e257]: 财联社3月19日电,纳斯达克中国金龙指数跌幅扩大,现跌3.0%,最新报6804.11点。 + - generic [ref=e258]: + - generic [ref=e259]: + - generic [ref=e260]: AI + - generic [ref=e261]: 算力 + - link "参考链接" [ref=e262] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318678?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e263]: + - generic [ref=e264]: + - generic [ref=e265]: 财联社7x24 + - generic [ref=e266]: 3/19 21:32 + - generic [ref=e267]: 中性 + - heading "财联社3月19日电,利弗莫尔中概股龙头指数盘初跌3.8%,成分股中,阿特斯太阳能跌超27%,阿里巴巴跌超9%,金山云跌超5%" [level=3] [ref=e268] + - paragraph [ref=e269]: 财联社3月19日电,利弗莫尔中概股龙头指数盘初跌3.8%,成分股中,阿特斯太阳能跌超27%,阿里巴巴跌超9%,金山云跌超5%。 + - generic [ref=e270]: + - generic [ref=e271]: + - generic [ref=e272]: AI + - generic [ref=e273]: 算力 + - link "参考链接" [ref=e274] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318675?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e275]: + - generic [ref=e276]: + - generic [ref=e277]: 财联社7x24 + - generic [ref=e278]: 3/19 21:31 + - generic [ref=e279]: 中性 + - heading "财联社3月19日电,罗素2000指数今日开盘跌幅达1.1%,较盘中纪录高点下跌10%" [level=3] [ref=e280] + - paragraph [ref=e281]: 财联社3月19日电,罗素2000指数今日开盘跌幅达1.1%,较盘中纪录高点下跌10%。 + - generic [ref=e282]: + - generic [ref=e283]: + - generic [ref=e284]: AI + - generic [ref=e285]: 算力 + - link "参考链接" [ref=e286] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318674?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e287]: + - generic [ref=e288]: + - generic [ref=e289]: 财联社7x24 + - generic [ref=e290]: 3/19 21:31 + - generic [ref=e291]: 中性 + - heading "财联社3月19日电,捷克央行将关键利率维持在3.5%,符合市场预期" [level=3] [ref=e292] + - paragraph [ref=e293]: 财联社3月19日电,捷克央行将关键利率维持在3.5%,符合市场预期。 + - generic [ref=e294]: + - generic [ref=e295]: + - generic [ref=e296]: AI + - generic [ref=e297]: 算力 + - link "参考链接" [ref=e298] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318672?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e299]: + - generic [ref=e300]: + - generic [ref=e301]: 财联社7x24 + - generic [ref=e302]: 3/19 21:28 + - generic [ref=e303]: 中性 + - heading "卡塔尔能源公司:袭击造成的年收入损失约为200亿美元" [level=3] [ref=e304] + - paragraph [ref=e305]: 【卡塔尔能源公司:袭击造成的年收入损失约为200亿美元】财联社3月19日电,卡塔尔能源公司CEO表示,在袭击中,14条液化天然气生产线中的2条以及两座天然气制油(GTL)设施中的1座遭到破坏。三个受损设施造成的年收入损失约为200亿美元。 + - generic [ref=e306]: + - generic [ref=e308]: 石油天然气 + - link "参考链接" [ref=e309] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318669?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e310]: + - generic [ref=e311]: + - generic [ref=e312]: 财联社7x24 + - generic [ref=e313]: 3/19 21:28 + - generic [ref=e314]: 中性 + - heading "财联社3月19日电,据报道,印度尼西亚将确保预算赤字保持在GDP的3%以下" [level=3] [ref=e315] + - paragraph [ref=e316]: 财联社3月19日电,据报道,印度尼西亚将确保预算赤字保持在GDP的3%以下。 + - generic [ref=e317]: + - generic [ref=e318]: + - generic [ref=e319]: AI + - generic [ref=e320]: 算力 + - link "参考链接" [ref=e321] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318670?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e322]: + - generic [ref=e323]: + - generic [ref=e324]: 财联社7x24 + - generic [ref=e325]: 3/19 21:26 + - generic [ref=e326]: 中性 + - heading "财联社3月19日电,中东冲突导致燃油价格飙升,德国考虑征收暴利税" [level=3] [ref=e327] + - paragraph [ref=e328]: 财联社3月19日电,中东冲突导致燃油价格飙升,德国考虑征收暴利税。 + - generic [ref=e329]: + - generic [ref=e330]: + - generic [ref=e331]: AI + - generic [ref=e332]: 算力 + - link "参考链接" [ref=e333] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318666?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e334]: + - generic [ref=e335]: + - generic [ref=e336]: 财联社7x24 + - generic [ref=e337]: 3/19 21:25 + - generic [ref=e338]: 中性 + - heading "四川英发睿能科技股份有限公司向港交所提交上市申请书" [level=3] [ref=e339] + - paragraph [ref=e340]: 【四川英发睿能科技股份有限公司向港交所提交上市申请书】财联社3月19日电,利弗莫尔证券显示,四川英发睿能科技股份有限公司向港交所提交上市申请书,联席保荐人为中信建投国际、华泰国际。 + - generic [ref=e341]: + - generic [ref=e343]: 券商 + - link "参考链接" [ref=e344] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318667?os=&sv=8.4.4&app=CailianpressWap + - article [ref=e345]: + - generic [ref=e346]: + - generic [ref=e347]: 央视新闻 + - generic [ref=e348]: 3/19 21:21 + - generic [ref=e349]: 中性 + - heading "阿联酋总统与埃及总统会谈 讨论中东局势等问题" [level=3] [ref=e350] + - paragraph [ref=e351]: 【阿联酋总统与埃及总统会谈 讨论中东局势等问题】财联社3月19日电,阿联酋总统穆罕默德在阿联酋阿布扎比会见到访的埃及总统塞西。双方就双边合作以及当前中东局势进行了讨论。双方表示,持续的军事行动升级对地区和国际安全与稳定产生了严重影响。塞西表示,埃及支持阿联酋为维护国家安全、领土完整及人民安全所采取的措施。双方同时强调,应立即停止局势升级,通过对话和外交方式解决地区问题,以防止紧张局势和危机进一步加剧,维护地区安全与稳定。 + - generic [ref=e352]: + - generic [ref=e353]: + - generic [ref=e354]: AI + - generic [ref=e355]: 算力 + - link "参考链接" [ref=e356] [cursor=pointer]: + - /url: https://api3.cls.cn/share/article/2318662?os=&sv=8.4.4&app=CailianpressWap \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-57-43-304Z.yml b/.playwright-cli/page-2026-03-19T13-57-43-304Z.yml new file mode 100644 index 0000000..fd63ed6 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-57-43-304Z.yml @@ -0,0 +1,44 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e23] [cursor=pointer] + - button "3/19" [ref=e24] [cursor=pointer] + - button "3/18" [ref=e25] [cursor=pointer] + - generic [ref=e26]: + - paragraph [ref=e27]: 当前摘要 + - paragraph [ref=e28]: 等待财联社资讯数据。 + - main [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - paragraph [ref=e32]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e33] + - paragraph [ref=e34]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e35]: + - generic [ref=e36]: 当前日期 + - strong [ref=e37]: 3月19日周四 + - paragraph [ref=e38]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e40]: + - generic [ref=e41]: + - generic [ref=e42]: + - paragraph [ref=e43]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e44] + - paragraph [ref=e45]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e46] [cursor=pointer] + - paragraph [ref=e48]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-57-43-782Z.png b/.playwright-cli/page-2026-03-19T13-57-43-782Z.png new file mode 100644 index 0000000..f504c92 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-57-43-782Z.png differ diff --git a/.playwright-cli/page-2026-03-19T13-58-02-281Z.yml b/.playwright-cli/page-2026-03-19T13-58-02-281Z.yml new file mode 100644 index 0000000..e28b3e7 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-58-02-281Z.yml @@ -0,0 +1,160 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [active] [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e23] [cursor=pointer] + - button "3/19" [ref=e24] [cursor=pointer] + - button "3/18" [ref=e25] [cursor=pointer] + - generic [ref=e26]: + - paragraph [ref=e27]: 当前摘要 + - paragraph [ref=e28]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e49]: + - generic [ref=e50]: AI + - generic [ref=e357]: CPO + - generic [ref=e358]: 存储芯片 + - generic [ref=e359]: 机器人 + - main [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - paragraph [ref=e32]: Finance Dashboard + - heading "大V观点" [level=2] [ref=e360] + - paragraph [ref=e34]: 按日期拆分日报,顶部保留摘要,正文按文章展开,并清晰展示对应板块。 + - generic [ref=e35]: + - generic [ref=e36]: 当前日期 + - strong [ref=e37]: 3月19日周四 + - paragraph [ref=e38]: 系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。 + - generic [ref=e361]: + - generic [ref=e362]: + - generic [ref=e363]: + - paragraph [ref=e364]: Opinion Daily + - heading "大V日报" [level=2] [ref=e365] + - paragraph [ref=e366]: 每篇文章独立展示账号、时间、情绪、摘要和对应板块,减少错位和重复信息。 + - generic [ref=e367]: + - button "3/20" [ref=e368] [cursor=pointer] + - button "3/19" [ref=e369] [cursor=pointer] + - button "3/18" [ref=e370] [cursor=pointer] + - generic [ref=e371]: + - article [ref=e372]: + - generic [ref=e373]: + - paragraph [ref=e374]: 生成时间 3/19 17:54 + - paragraph [ref=e375]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e376]: + - generic [ref=e377]: + - generic [ref=e378]: + - generic [ref=e379]: 文章数 + - strong [ref=e380]: "4" + - generic [ref=e381]: + - generic [ref=e382]: 账号数 + - strong [ref=e383]: "4" + - generic [ref=e384]: + - generic [ref=e385]: 核心板块 + - generic [ref=e386]: + - generic [ref=e387]: AI + - generic [ref=e388]: CPO + - generic [ref=e389]: 存储芯片 + - generic [ref=e390]: 机器人 + - generic [ref=e391]: + - article [ref=e392]: + - generic [ref=e393]: + - generic [ref=e394]: + - heading "投资明见:L429hddgfi18eojpdujja 观察" [level=3] [ref=e395] + - generic [ref=e396]: + - generic [ref=e397]: 投资明见 + - generic [ref=e398]: 主题观点 + - generic [ref=e399]: 12:00 + - generic [ref=e400]: 中性 + - generic [ref=e401]: + - generic [ref=e402]: + - generic [ref=e403]: 观点摘要 + - paragraph [ref=e404]: 投资明见:L429hddgfi18eojpdujja 观察 围绕 AI、算力 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e405]: + - generic [ref=e406]: 对应板块 + - generic [ref=e407]: + - generic [ref=e408]: AI + - generic [ref=e409]: 算力 + - generic [ref=e410]: + - generic [ref=e411]: 查看原文链接 + - link "打开原文" [ref=e412] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA + - article [ref=e413]: + - generic [ref=e414]: + - generic [ref=e415]: + - heading "马志明收评:I0vlr02f7ydb9gvca7idtw 观察" [level=3] [ref=e416] + - generic [ref=e417]: + - generic [ref=e418]: 马志明收评 + - generic [ref=e419]: 市场收评 + - generic [ref=e420]: 11:00 + - generic [ref=e421]: 中性 + - generic [ref=e422]: + - generic [ref=e423]: + - generic [ref=e424]: 观点摘要 + - paragraph [ref=e425]: 马志明收评:I0vlr02f7ydb9gvca7idtw 观察 围绕 AI、券商 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e426]: + - generic [ref=e427]: 对应板块 + - generic [ref=e428]: + - generic [ref=e429]: AI + - generic [ref=e430]: 券商 + - generic [ref=e431]: + - generic [ref=e432]: 查看原文链接 + - link "打开原文" [ref=e433] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw + - article [ref=e434]: + - generic [ref=e435]: + - generic [ref=e436]: + - heading "老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察" [level=3] [ref=e437] + - generic [ref=e438]: + - generic [ref=e439]: 老白分析室观点 + - generic [ref=e440]: 主题观点 + - generic [ref=e441]: 10:00 + - generic [ref=e442]: 中性 + - generic [ref=e443]: + - generic [ref=e444]: + - generic [ref=e445]: 观点摘要 + - paragraph [ref=e446]: 老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察 围绕 机器人、半导体 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e447]: + - generic [ref=e448]: 对应板块 + - generic [ref=e449]: + - generic [ref=e450]: 机器人 + - generic [ref=e451]: 半导体 + - generic [ref=e452]: + - generic [ref=e453]: 查看原文链接 + - link "打开原文" [ref=e454] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ + - article [ref=e455]: + - generic [ref=e456]: + - generic [ref=e457]: + - heading "爱股君2020:1no9toallxkkrjpj4wzt9q 观察" [level=3] [ref=e458] + - generic [ref=e459]: + - generic [ref=e460]: 爱股君2020 + - generic [ref=e461]: 主题观点 + - generic [ref=e462]: 09:00 + - generic [ref=e463]: 中性 + - generic [ref=e464]: + - generic [ref=e465]: + - generic [ref=e466]: 观点摘要 + - paragraph [ref=e467]: 爱股君2020:1no9toallxkkrjpj4wzt9q 观察 围绕 CPO、存储芯片 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e468]: + - generic [ref=e469]: 对应板块 + - generic [ref=e470]: + - generic [ref=e471]: CPO + - generic [ref=e472]: 存储芯片 + - generic [ref=e473]: + - generic [ref=e474]: 查看原文链接 + - link "打开原文" [ref=e475] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-58-17-013Z.yml b/.playwright-cli/page-2026-03-19T13-58-17-013Z.yml new file mode 100644 index 0000000..e28b3e7 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T13-58-17-013Z.yml @@ -0,0 +1,160 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [active] [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e23] [cursor=pointer] + - button "3/19" [ref=e24] [cursor=pointer] + - button "3/18" [ref=e25] [cursor=pointer] + - generic [ref=e26]: + - paragraph [ref=e27]: 当前摘要 + - paragraph [ref=e28]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e49]: + - generic [ref=e50]: AI + - generic [ref=e357]: CPO + - generic [ref=e358]: 存储芯片 + - generic [ref=e359]: 机器人 + - main [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - paragraph [ref=e32]: Finance Dashboard + - heading "大V观点" [level=2] [ref=e360] + - paragraph [ref=e34]: 按日期拆分日报,顶部保留摘要,正文按文章展开,并清晰展示对应板块。 + - generic [ref=e35]: + - generic [ref=e36]: 当前日期 + - strong [ref=e37]: 3月19日周四 + - paragraph [ref=e38]: 系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。 + - generic [ref=e361]: + - generic [ref=e362]: + - generic [ref=e363]: + - paragraph [ref=e364]: Opinion Daily + - heading "大V日报" [level=2] [ref=e365] + - paragraph [ref=e366]: 每篇文章独立展示账号、时间、情绪、摘要和对应板块,减少错位和重复信息。 + - generic [ref=e367]: + - button "3/20" [ref=e368] [cursor=pointer] + - button "3/19" [ref=e369] [cursor=pointer] + - button "3/18" [ref=e370] [cursor=pointer] + - generic [ref=e371]: + - article [ref=e372]: + - generic [ref=e373]: + - paragraph [ref=e374]: 生成时间 3/19 17:54 + - paragraph [ref=e375]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e376]: + - generic [ref=e377]: + - generic [ref=e378]: + - generic [ref=e379]: 文章数 + - strong [ref=e380]: "4" + - generic [ref=e381]: + - generic [ref=e382]: 账号数 + - strong [ref=e383]: "4" + - generic [ref=e384]: + - generic [ref=e385]: 核心板块 + - generic [ref=e386]: + - generic [ref=e387]: AI + - generic [ref=e388]: CPO + - generic [ref=e389]: 存储芯片 + - generic [ref=e390]: 机器人 + - generic [ref=e391]: + - article [ref=e392]: + - generic [ref=e393]: + - generic [ref=e394]: + - heading "投资明见:L429hddgfi18eojpdujja 观察" [level=3] [ref=e395] + - generic [ref=e396]: + - generic [ref=e397]: 投资明见 + - generic [ref=e398]: 主题观点 + - generic [ref=e399]: 12:00 + - generic [ref=e400]: 中性 + - generic [ref=e401]: + - generic [ref=e402]: + - generic [ref=e403]: 观点摘要 + - paragraph [ref=e404]: 投资明见:L429hddgfi18eojpdujja 观察 围绕 AI、算力 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e405]: + - generic [ref=e406]: 对应板块 + - generic [ref=e407]: + - generic [ref=e408]: AI + - generic [ref=e409]: 算力 + - generic [ref=e410]: + - generic [ref=e411]: 查看原文链接 + - link "打开原文" [ref=e412] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA + - article [ref=e413]: + - generic [ref=e414]: + - generic [ref=e415]: + - heading "马志明收评:I0vlr02f7ydb9gvca7idtw 观察" [level=3] [ref=e416] + - generic [ref=e417]: + - generic [ref=e418]: 马志明收评 + - generic [ref=e419]: 市场收评 + - generic [ref=e420]: 11:00 + - generic [ref=e421]: 中性 + - generic [ref=e422]: + - generic [ref=e423]: + - generic [ref=e424]: 观点摘要 + - paragraph [ref=e425]: 马志明收评:I0vlr02f7ydb9gvca7idtw 观察 围绕 AI、券商 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e426]: + - generic [ref=e427]: 对应板块 + - generic [ref=e428]: + - generic [ref=e429]: AI + - generic [ref=e430]: 券商 + - generic [ref=e431]: + - generic [ref=e432]: 查看原文链接 + - link "打开原文" [ref=e433] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw + - article [ref=e434]: + - generic [ref=e435]: + - generic [ref=e436]: + - heading "老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察" [level=3] [ref=e437] + - generic [ref=e438]: + - generic [ref=e439]: 老白分析室观点 + - generic [ref=e440]: 主题观点 + - generic [ref=e441]: 10:00 + - generic [ref=e442]: 中性 + - generic [ref=e443]: + - generic [ref=e444]: + - generic [ref=e445]: 观点摘要 + - paragraph [ref=e446]: 老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察 围绕 机器人、半导体 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e447]: + - generic [ref=e448]: 对应板块 + - generic [ref=e449]: + - generic [ref=e450]: 机器人 + - generic [ref=e451]: 半导体 + - generic [ref=e452]: + - generic [ref=e453]: 查看原文链接 + - link "打开原文" [ref=e454] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ + - article [ref=e455]: + - generic [ref=e456]: + - generic [ref=e457]: + - heading "爱股君2020:1no9toallxkkrjpj4wzt9q 观察" [level=3] [ref=e458] + - generic [ref=e459]: + - generic [ref=e460]: 爱股君2020 + - generic [ref=e461]: 主题观点 + - generic [ref=e462]: 09:00 + - generic [ref=e463]: 中性 + - generic [ref=e464]: + - generic [ref=e465]: + - generic [ref=e466]: 观点摘要 + - paragraph [ref=e467]: 爱股君2020:1no9toallxkkrjpj4wzt9q 观察 围绕 CPO、存储芯片 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e468]: + - generic [ref=e469]: 对应板块 + - generic [ref=e470]: + - generic [ref=e471]: CPO + - generic [ref=e472]: 存储芯片 + - generic [ref=e473]: + - generic [ref=e474]: 查看原文链接 + - link "打开原文" [ref=e475] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T13-58-17-056Z.png b/.playwright-cli/page-2026-03-19T13-58-17-056Z.png new file mode 100644 index 0000000..4efcaa2 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T13-58-17-056Z.png differ diff --git a/.playwright-cli/page-2026-03-19T14-16-20-711Z.yml b/.playwright-cli/page-2026-03-19T14-16-20-711Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T14-16-20-711Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T14-16-38-519Z.yml b/.playwright-cli/page-2026-03-19T14-16-38-519Z.yml new file mode 100644 index 0000000..3ba6622 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T14-16-38-519Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 等待财联社资讯数据。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "财联社新闻" [level=2] [ref=e31] + - paragraph [ref=e32]: 左侧日期会同步联动热点概览和 7x24 资讯列表,仅查看所选日期当日内容。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e38]: + - generic [ref=e39]: + - generic [ref=e40]: + - paragraph [ref=e41]: CLS Live Monitor + - heading "3月19日周四 财联社 7x24" [level=2] [ref=e42] + - paragraph [ref=e43]: 左侧日期会同步联动热点概览和资讯列表,仅展示所选日期当日资讯。 + - button "刷新当日资讯" [ref=e44] [cursor=pointer] + - paragraph [ref=e46]: 当前日期暂无资讯数据。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T14-16-38-692Z.png b/.playwright-cli/page-2026-03-19T14-16-38-692Z.png new file mode 100644 index 0000000..c4e691a Binary files /dev/null and b/.playwright-cli/page-2026-03-19T14-16-38-692Z.png differ diff --git a/.playwright-cli/page-2026-03-19T14-16-39-812Z.yml b/.playwright-cli/page-2026-03-19T14-16-39-812Z.yml new file mode 100644 index 0000000..2ec7750 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T14-16-39-812Z.yml @@ -0,0 +1,41 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [active] [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - button "3/19" [ref=e23] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 选择日期后查看对应日报内容。 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "大V观点" [level=2] [ref=e47] + - paragraph [ref=e32]: 按日期拆分日报,顶部保留摘要,正文按文章展开,并清晰展示对应板块。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已加载,可直接查看资讯、切换日期或录入当日链接。 + - generic [ref=e48]: + - generic [ref=e49]: + - generic [ref=e50]: + - paragraph [ref=e51]: Opinion Daily + - heading "大V日报" [level=2] [ref=e52] + - paragraph [ref=e53]: 每篇文章独立展示账号、时间、情绪、摘要和对应板块,减少错位和重复信息。 + - button "3/19" [ref=e55] [cursor=pointer] + - paragraph [ref=e57]: 当前日期暂无日报内容。 \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T14-16-55-701Z.yml b/.playwright-cli/page-2026-03-19T14-16-55-701Z.yml new file mode 100644 index 0000000..86e3399 --- /dev/null +++ b/.playwright-cli/page-2026-03-19T14-16-55-701Z.yml @@ -0,0 +1,160 @@ +- generic [ref=e3]: + - complementary [ref=e4]: + - generic [ref=e5]: + - paragraph [ref=e6]: Finance Daily + - heading "财经内容日报" [level=1] [ref=e7] + - paragraph [ref=e8]: 面向日常资讯跟踪、观点汇总和日报录入,保持结构清晰,减少无效装饰。 + - navigation [ref=e9]: + - button "财联社 7x24 按日期查看全天资讯与热点概览" [ref=e10] [cursor=pointer]: + - generic [ref=e11]: 财联社 7x24 + - generic [ref=e12]: 按日期查看全天资讯与热点概览 + - button "大V日报 按日期查看文章观点与对应板块" [active] [ref=e13] [cursor=pointer]: + - generic [ref=e14]: 大V日报 + - generic [ref=e15]: 按日期查看文章观点与对应板块 + - button "日报录入 按公众号补录当天文章链接" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: 日报录入 + - generic [ref=e18]: 按公众号补录当天文章链接 + - generic [ref=e19]: + - generic [ref=e20]: 观察日期 + - textbox "观察日期" [ref=e21]: 2026-03-19 + - generic [ref=e22]: + - button "3/20" [ref=e58] [cursor=pointer] + - button "3/19" [ref=e23] [cursor=pointer] + - button "3/18" [ref=e59] [cursor=pointer] + - generic [ref=e24]: + - paragraph [ref=e25]: 当前摘要 + - paragraph [ref=e26]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e60]: + - generic [ref=e61]: AI + - generic [ref=e62]: CPO + - generic [ref=e63]: 存储芯片 + - generic [ref=e64]: 机器人 + - main [ref=e27]: + - generic [ref=e28]: + - generic [ref=e29]: + - paragraph [ref=e30]: Finance Dashboard + - heading "大V观点" [level=2] [ref=e47] + - paragraph [ref=e32]: 按日期拆分日报,顶部保留摘要,正文按文章展开,并清晰展示对应板块。 + - generic [ref=e33]: + - generic [ref=e34]: 当前日期 + - strong [ref=e35]: 3月19日周四 + - paragraph [ref=e36]: 系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。 + - generic [ref=e48]: + - generic [ref=e49]: + - generic [ref=e50]: + - paragraph [ref=e51]: Opinion Daily + - heading "大V日报" [level=2] [ref=e52] + - paragraph [ref=e53]: 每篇文章独立展示账号、时间、情绪、摘要和对应板块,减少错位和重复信息。 + - generic [ref=e54]: + - button "3/20" [ref=e65] [cursor=pointer] + - button "3/19" [ref=e55] [cursor=pointer] + - button "3/18" [ref=e66] [cursor=pointer] + - generic [ref=e67]: + - article [ref=e68]: + - generic [ref=e69]: + - paragraph [ref=e70]: 生成时间 3/19 17:54 + - paragraph [ref=e71]: 2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账户。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、CPO、存储芯片、机器人。 + - generic [ref=e72]: + - generic [ref=e73]: + - generic [ref=e74]: + - generic [ref=e75]: 文章数 + - strong [ref=e76]: "4" + - generic [ref=e77]: + - generic [ref=e78]: 账号数 + - strong [ref=e79]: "4" + - generic [ref=e80]: + - generic [ref=e81]: 核心板块 + - generic [ref=e82]: + - generic [ref=e83]: AI + - generic [ref=e84]: CPO + - generic [ref=e85]: 存储芯片 + - generic [ref=e86]: 机器人 + - generic [ref=e87]: + - article [ref=e88]: + - generic [ref=e89]: + - generic [ref=e90]: + - heading "投资明见:L429hddgfi18eojpdujja 观察" [level=3] [ref=e91] + - generic [ref=e92]: + - generic [ref=e93]: 投资明见 + - generic [ref=e94]: 主题观点 + - generic [ref=e95]: 12:00 + - generic [ref=e96]: 中性 + - generic [ref=e97]: + - generic [ref=e98]: + - generic [ref=e99]: 观点摘要 + - paragraph [ref=e100]: 投资明见:L429hddgfi18eojpdujja 观察 围绕 AI、算力 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e101]: + - generic [ref=e102]: 对应板块 + - generic [ref=e103]: + - generic [ref=e104]: AI + - generic [ref=e105]: 算力 + - generic [ref=e106]: + - generic [ref=e107]: 查看原文链接 + - link "打开原文" [ref=e108] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA + - article [ref=e109]: + - generic [ref=e110]: + - generic [ref=e111]: + - heading "马志明收评:I0vlr02f7ydb9gvca7idtw 观察" [level=3] [ref=e112] + - generic [ref=e113]: + - generic [ref=e114]: 马志明收评 + - generic [ref=e115]: 市场收评 + - generic [ref=e116]: 11:00 + - generic [ref=e117]: 中性 + - generic [ref=e118]: + - generic [ref=e119]: + - generic [ref=e120]: 观点摘要 + - paragraph [ref=e121]: 马志明收评:I0vlr02f7ydb9gvca7idtw 观察 围绕 AI、券商 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e122]: + - generic [ref=e123]: 对应板块 + - generic [ref=e124]: + - generic [ref=e125]: AI + - generic [ref=e126]: 券商 + - generic [ref=e127]: + - generic [ref=e128]: 查看原文链接 + - link "打开原文" [ref=e129] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw + - article [ref=e130]: + - generic [ref=e131]: + - generic [ref=e132]: + - heading "老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察" [level=3] [ref=e133] + - generic [ref=e134]: + - generic [ref=e135]: 老白分析室观点 + - generic [ref=e136]: 主题观点 + - generic [ref=e137]: 10:00 + - generic [ref=e138]: 中性 + - generic [ref=e139]: + - generic [ref=e140]: + - generic [ref=e141]: 观点摘要 + - paragraph [ref=e142]: 老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察 围绕 机器人、半导体 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e143]: + - generic [ref=e144]: 对应板块 + - generic [ref=e145]: + - generic [ref=e146]: 机器人 + - generic [ref=e147]: 半导体 + - generic [ref=e148]: + - generic [ref=e149]: 查看原文链接 + - link "打开原文" [ref=e150] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ + - article [ref=e151]: + - generic [ref=e152]: + - generic [ref=e153]: + - heading "爱股君2020:1no9toallxkkrjpj4wzt9q 观察" [level=3] [ref=e154] + - generic [ref=e155]: + - generic [ref=e156]: 爱股君2020 + - generic [ref=e157]: 主题观点 + - generic [ref=e158]: 09:00 + - generic [ref=e159]: 中性 + - generic [ref=e160]: + - generic [ref=e161]: + - generic [ref=e162]: 观点摘要 + - paragraph [ref=e163]: 爱股君2020:1no9toallxkkrjpj4wzt9q 观察 围绕 CPO、存储芯片 展开,给出的结论是更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。 + - generic [ref=e164]: + - generic [ref=e165]: 对应板块 + - generic [ref=e166]: + - generic [ref=e167]: CPO + - generic [ref=e168]: 存储芯片 + - generic [ref=e169]: + - generic [ref=e170]: 查看原文链接 + - link "打开原文" [ref=e171] [cursor=pointer]: + - /url: https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q \ No newline at end of file diff --git a/.playwright-cli/page-2026-03-19T14-16-55-767Z.png b/.playwright-cli/page-2026-03-19T14-16-55-767Z.png new file mode 100644 index 0000000..4efcaa2 Binary files /dev/null and b/.playwright-cli/page-2026-03-19T14-16-55-767Z.png differ diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..755008c --- /dev/null +++ b/AGENT.md @@ -0,0 +1,6 @@ +1、显式指定语言:在每次提问或写注释时,强制加上“用中文回答”或“Chinese response”的要求。 +2、回答问题时候也用中文 +3、所有代码修改之后,必须安排测试工作,然后重启服务,而不是换一个端口 +4、前端代码不能有滚动条 +5、 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d9bb90 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# 财经内容日报网站 MVP + +基于需求文档实现的第一版系统骨架,包含: + +- `backend/`:FastAPI 接口、JSON 文件存储、演示数据种子 +- `frontend/`:Vue 3 + Vite + TypeScript 固定视口工作台 +- `data/`:运行后生成的日报、录入和财联社资讯缓存 + +当前版本特性: + +- 财联社新闻、大V观点、日报录入 3 个核心模块 +- 页面采用左侧导航 + 右侧内容区,整页固定视口,不出现页面级滚动条 +- 财联社资讯支持手动刷新 +- 大V日报支持按日期查看 +- 录入页支持同一公众号多条链接、去空、去重、保存和生成日报 +- 后端会自动生成演示数据,便于直接预览界面 + +## 本地启动 + +### 后端 + +```powershell +python -m pip install -r .\backend\requirements.txt +uvicorn app.main:app --app-dir .\backend --reload --host 127.0.0.1 --port 8000 +``` + +### 前端 + +```powershell +cd .\frontend +npm install +npm run dev +``` + +如果前端需要指向其他 API 地址,可以设置环境变量: + +```powershell +$env:VITE_API_BASE_URL="http://127.0.0.1:8000" +``` + +## 当前实现边界 + +本版重点是把信息结构、录入流和日报浏览工作台先搭起来。公众号文章抓取与摘要分析目前采用本地规则化生成,后续可以在后端把 `generate_report` 和录入链路替换成真实抓取器与分析器。 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..da79022 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi import Query +from fastapi.middleware.cors import CORSMiddleware + +from app.models import ( + Account, + ClsNewsDocument, + DailyInputDocument, + DailyInputUpsertPayload, + ReportDocument, + ReportListItem, +) +from app.services.domain import ( + get_accounts, + get_cls_news, + list_reports, + load_daily_input, + load_report, + normalize_daily_input, + normalize_date, + refresh_cls_news, + save_daily_input, + save_report, + seed_demo_content, + generate_report, +) +from app.services.storage import init_database + + +@asynccontextmanager +async def lifespan(_app: FastAPI): + init_database() + seed_demo_content() + yield + + +app = FastAPI( + title="WeChat Finance Daily", + version="0.1.0", + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/api/health") +def health() -> dict[str, str]: + return {"status": "ok"} + + +@app.get("/api/accounts", response_model=list[Account]) +def accounts() -> list[Account]: + return get_accounts() + + +@app.get("/api/daily-inputs/{date_str}", response_model=DailyInputDocument) +def get_daily_inputs(date_str: str) -> DailyInputDocument: + return load_daily_input(normalize_date(date_str)) + + +@app.put("/api/daily-inputs/{date_str}", response_model=DailyInputDocument) +def put_daily_inputs(date_str: str, payload: DailyInputUpsertPayload) -> DailyInputDocument: + document = normalize_daily_input(normalize_date(date_str), payload) + return save_daily_input(document) + + +@app.post("/api/reports/{date_str}/generate", response_model=ReportDocument) +def generate_daily_report(date_str: str) -> ReportDocument: + normalized_date = normalize_date(date_str) + input_document = load_daily_input(normalized_date) + report = generate_report(normalized_date, input_document) + return save_report(report) + + +@app.get("/api/reports", response_model=list[ReportListItem]) +def get_report_list() -> list[ReportListItem]: + return list_reports() + + +@app.get("/api/opinions/{date_str}", response_model=ReportDocument) +def get_opinion_report(date_str: str) -> ReportDocument: + normalized_date = normalize_date(date_str) + existing = load_report(normalized_date) + if existing is not None: + return existing + report = generate_report(normalized_date, load_daily_input(normalized_date)) + return save_report(report) + + +@app.get("/api/cls-news", response_model=ClsNewsDocument) +def get_cls_news_payload(date: str | None = Query(default=None)) -> ClsNewsDocument: + return get_cls_news(date) + + +@app.post("/api/cls-news/refresh", response_model=ClsNewsDocument) +def refresh_cls_news_payload(date: str | None = Query(default=None)) -> ClsNewsDocument: + return refresh_cls_news(date) diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..1ecac3c --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, Field + +Sentiment = Literal["看多", "看空", "中性"] + + +class Account(BaseModel): + id: str + name: str + description: str + + +class DailyInputAccountPayload(BaseModel): + account_id: str + links: list[str] = Field(default_factory=list) + + +class DailyInputUpsertPayload(BaseModel): + accounts: list[DailyInputAccountPayload] = Field(default_factory=list) + + +class DailyInputAccount(BaseModel): + account_id: str + account_name: str + links: list[str] = Field(default_factory=list) + + +class DailyInputDocument(BaseModel): + date: str + updated_at: str + accounts: list[DailyInputAccount] = Field(default_factory=list) + + +class OpinionArticle(BaseModel): + id: str + account_id: str + account_name: str + title: str + published_at: str + summary: str + source_url: str + sectors: list[str] = Field(default_factory=list) + sentiment: Sentiment + article_type: str + + +class ReportDocument(BaseModel): + date: str + generated_at: str + summary: str + focus_sectors: list[str] = Field(default_factory=list) + article_count: int + account_count: int + articles: list[OpinionArticle] = Field(default_factory=list) + + +class ReportListItem(BaseModel): + date: str + generated_at: str + summary: str + article_count: int + focus_sectors: list[str] = Field(default_factory=list) + + +class ClsNewsItem(BaseModel): + id: str + title: str + published_at: str + source: str + summary: str + reference_url: str + sectors: list[str] = Field(default_factory=list) + sentiment: Sentiment + + +class ClsNewsSummary(BaseModel): + overview: str + hot_topics: str + watch_list: list[str] = Field(default_factory=list) + + +class ClsSectorImpact(BaseModel): + sector: str + sentiment: Sentiment + reason: str + related_titles: list[str] = Field(default_factory=list) + + +class ClsNewsDocument(BaseModel): + date: str + updated_at: str + window_label: str + summary: ClsNewsSummary + sector_impacts: list[ClsSectorImpact] = Field(default_factory=list) + items: list[ClsNewsItem] = Field(default_factory=list) diff --git a/backend/app/services/domain.py b/backend/app/services/domain.py new file mode 100644 index 0000000..4533444 --- /dev/null +++ b/backend/app/services/domain.py @@ -0,0 +1,733 @@ +from __future__ import annotations + +import json +import re +from collections import Counter +from datetime import datetime, time, timedelta +from typing import Iterable +from urllib.parse import unquote, urlparse +from zoneinfo import ZoneInfo + +import requests +from bs4 import BeautifulSoup + +from app.models import ( + Account, + ClsNewsDocument, + ClsNewsItem, + ClsNewsSummary, + ClsSectorImpact, + DailyInputAccount, + DailyInputDocument, + DailyInputUpsertPayload, + OpinionArticle, + ReportDocument, + ReportListItem, +) +from app.services.storage import ( + fetch_accounts, + fetch_cls_news_document, + fetch_daily_input_document, + fetch_report_document, + fetch_report_list, + save_accounts, + save_cls_news_document, + save_daily_input_document, + save_report_document, +) + +SHANGHAI = ZoneInfo("Asia/Shanghai") +CLS_REFRESH_INTERVAL = timedelta(minutes=3) +CLS_TELEGRAPH_URL = "https://m.cls.cn/telegraph" +HTTP_HEADERS = { + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" + ), + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", +} + +SENTIMENT_BULL = "\u770b\u591a" +SENTIMENT_BEAR = "\u770b\u7a7a" +SENTIMENT_NEUTRAL = "\u4e2d\u6027" + +ACCOUNTS: list[Account] = [ + Account( + id="touzi-mingjian", + name="\u6295\u8d44\u660e\u89c1", + description="\u504f\u4e3b\u9898\u8f6e\u52a8\u4e0e\u4e3b\u7ebf\u5224\u65ad\uff0c\u9002\u5408\u8ddf\u8e2a\u5e02\u573a\u504f\u597d\u53d8\u5316\u3002", + ), + Account( + id="aigujun-2020", + name="\u7231\u80a1\u541b2020", + description="\u5173\u6ce8\u60c5\u7eea\u3001\u70ed\u70b9\u6269\u6563\u4e0e\u4ea4\u6613\u7ec6\u8282\u3002", + ), + Account( + id="mazhiming-shouping", + name="\u9a6c\u5fd7\u660e\u6536\u8bc4", + description="\u65e5\u5185\u6536\u8bc4\u4e0e\u60c5\u7eea\u53d8\u5316\u603b\u7ed3\u3002", + ), + Account( + id="laobai-guandian", + name="\u8001\u767d\u5206\u6790\u5ba4\u89c2\u70b9", + description="\u504f\u7b56\u7565\u62c6\u89e3\u548c\u5173\u952e\u677f\u5757\u8ddf\u8e2a\u3002", + ), +] + +ACCOUNT_FOCUS = { + "touzi-mingjian": ["AI", "\u7b97\u529b", "\u673a\u5668\u4eba"], + "aigujun-2020": ["CPO", "\u5b58\u50a8\u82af\u7247", "\u65b0\u80fd\u6e90"], + "mazhiming-shouping": ["AI", "\u5238\u5546", "\u6c7d\u8f66"], + "laobai-guandian": ["\u673a\u5668\u4eba", "\u534a\u5bfc\u4f53", "\u65b0\u80fd\u6e90"], +} + +SECTOR_KEYWORDS = { + "AI": ["ai", "\u4eba\u5de5\u667a\u80fd", "\u5927\u6a21\u578b", "\u6a21\u578b"], + "\u7b97\u529b": ["\u7b97\u529b", "compute", "server", "gpu"], + "CPO": ["cpo", "\u5149\u6a21\u5757", "\u9ad8\u901f\u4e92\u8054"], + "\u5b58\u50a8\u82af\u7247": ["\u5b58\u50a8", "memory", "dram", "nand"], + "\u534a\u5bfc\u4f53": ["\u534a\u5bfc\u4f53", "chip", "wafer", "\u6676\u5706"], + "\u5238\u5546": ["\u5238\u5546", "broker", "\u8bc1\u5238"], + "\u77f3\u6cb9\u5929\u7136\u6c14": ["\u77f3\u6cb9", "\u5929\u7136\u6c14", "\u6cb9\u6c14", "\u80fd\u6e90\u4ef7\u683c"], + "\u65b0\u80fd\u6e90": ["\u65b0\u80fd\u6e90", "\u9502\u7535", "\u5149\u4f0f", "\u50a8\u80fd"], + "\u519b\u5de5": ["\u519b\u5de5", "\u536b\u661f", "\u822a\u5929"], + "\u673a\u5668\u4eba": ["\u673a\u5668\u4eba", "robot", "\u81ea\u52a8\u5316"], + "\u6c7d\u8f66": ["\u6c7d\u8f66", "\u8f66\u4f01", "\u667a\u9a7e", "\u6574\u8f66"], + "\u533b\u836f": ["\u533b\u836f", "\u521b\u65b0\u836f", "\u533b\u7597"], +} + +POSITIVE_KEYWORDS = [ + "\u673a\u4f1a", + "\u4fee\u590d", + "\u589e\u5f3a", + "\u4e3b\u7ebf", + "\u589e\u91cf", + "\u53cd\u5f39", + "\u7a81\u7834", + "\u79ef\u6781", + "up", + "bull", +] +NEGATIVE_KEYWORDS = [ + "\u98ce\u9669", + "\u627f\u538b", + "\u8c28\u614e", + "\u56de\u8c03", + "\u7f29\u91cf", + "\u89c2\u671b", + "\u5206\u6b67", + "bear", + "down", +] + +ARTICLE_TYPE_PATTERNS = [ + ("\u6536\u8bc4", "\u5e02\u573a\u6536\u8bc4"), + ("\u5348", "\u76d8\u4e2d\u89c2\u5bdf"), + ("\u7b56\u7565", "\u7b56\u7565\u8ddf\u8e2a"), + ("\u590d\u76d8", "\u76d8\u9762\u590d\u76d8"), + ("\u884c\u4e1a", "\u884c\u4e1a\u89c2\u5bdf"), +] + +CLS_NEWS_TEMPLATES = [ + { + "title": "\u8d22\u8054\u793e\u76d8\u524d\u7cbe\u9009\uff1a\u7b97\u529b\u94fe\u56de\u6696\uff0c\u8d44\u91d1\u91cd\u65b0\u805a\u7126\u9ad8\u666f\u6c14\u65b9\u5411", + "summary": "\u9694\u591c\u5e02\u573a\u98ce\u9669\u504f\u597d\u56de\u5347\uff0c\u7b97\u529b\u4e0e\u670d\u52a1\u5668\u94fe\u6761\u83b7\u8d44\u91d1\u91cd\u65b0\u914d\u7f6e\uff0c\u60c5\u7eea\u4fee\u590d\u5148\u4e8e\u6210\u4ea4\u5168\u9762\u653e\u5927\u3002", + "sectors": ["\u7b97\u529b", "AI"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/compute-rebound", + }, + { + "title": "AI Daily\uff1aCPO \u4e0e\u5b58\u50a8\u82af\u7247\u540c\u6b65\u8d70\u5f3a\uff0c\u666f\u6c14\u5ea6\u7ebf\u7d22\u5ef6\u7eed", + "summary": "\u9ad8\u901f\u4e92\u8054\u4e0e\u5b58\u50a8\u62a5\u4ef7\u9884\u671f\u652f\u6491\u677f\u5757\u8868\u73b0\uff0c\u8d44\u91d1\u66f4\u503e\u5411\u4e8e\u56f4\u7ed5\u786e\u5b9a\u6027\u73af\u8282\u96c6\u4e2d\u3002", + "sectors": ["CPO", "\u5b58\u50a8\u82af\u7247"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/ai-daily-cpo-memory", + }, + { + "title": "\u8d22\u8054\u793e\u884c\u4e1a\u89c2\u5bdf\uff1a\u673a\u5668\u4eba\u94fe\u6761\u5206\u5316\uff0c\u8ba2\u5355\u5151\u73b0\u6210\u4e3a\u77ed\u671f\u7126\u70b9", + "summary": "\u673a\u5668\u4eba\u65b9\u5411\u5185\u90e8\u5f00\u59cb\u51fa\u73b0\u5151\u73b0\u4e0e\u6362\u624b\uff0c\u5e02\u573a\u4ece\u6982\u5ff5\u6269\u6563\u8f6c\u5411\u4e1a\u7ee9\u4e0e\u8ba2\u5355\u9a8c\u8bc1\u3002", + "sectors": ["\u673a\u5668\u4eba"], + "sentiment": SENTIMENT_NEUTRAL, + "reference_url": "https://www.cls.cn/detail/robotics-orders", + }, + { + "title": "\u8d22\u8054\u793e\u80fd\u6e90\u8ffd\u8e2a\uff1a\u6cb9\u6c14\u677f\u5757\u9ad8\u4f4d\u9707\u8361\uff0c\u8d44\u91d1\u5207\u5411\u9632\u5fa1\u54c1\u79cd", + "summary": "\u539f\u6cb9\u4ef7\u683c\u7ef4\u6301\u9ad8\u4f4d\u540e\uff0c\u6cb9\u6c14\u65b9\u5411\u51fa\u73b0\u9ad8\u4f4d\u9707\u8361\uff0c\u90e8\u5206\u8d44\u91d1\u8f6c\u5411\u533b\u836f\u7b49\u9632\u5b88\u677f\u5757\u3002", + "sectors": ["\u77f3\u6cb9\u5929\u7136\u6c14", "\u533b\u836f"], + "sentiment": SENTIMENT_NEUTRAL, + "reference_url": "https://www.cls.cn/detail/energy-rotation", + }, + { + "title": "AI Daily\uff1a\u6c7d\u8f66\u4e0e\u667a\u9a7e\u5ef6\u7eed\u5206\u6b67\uff0c\u4e3b\u7ebf\u4ecd\u9700\u7b49\u5f85\u9500\u91cf\u6570\u636e\u9a8c\u8bc1", + "summary": "\u6574\u8f66\u4e0e\u667a\u9a7e\u65b9\u5411\u5173\u6ce8\u5ea6\u4ecd\u9ad8\uff0c\u4f46\u5e02\u573a\u5bf9\u4f30\u503c\u6269\u5f20\u5df2\u6709\u4fdd\u7559\uff0c\u7b49\u5f85\u9500\u91cf\u548c\u8ba2\u5355\u6570\u636e\u786e\u8ba4\u3002", + "sectors": ["\u6c7d\u8f66"], + "sentiment": SENTIMENT_BEAR, + "reference_url": "https://www.cls.cn/detail/auto-data-watch", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u534a\u5bfc\u4f53\u8bbe\u5907\u65b9\u5411\u8d70\u5f3a\uff0c\u673a\u6784\u79f0\u56fd\u4ea7\u66ff\u4ee3\u8282\u594f\u63d0\u901f", + "summary": "\u6676\u5706\u5236\u9020\u4e0e\u8bbe\u5907\u94fe\u6761\u51fa\u73b0\u5f02\u52a8\uff0c\u5e02\u573a\u56f4\u7ed5\u56fd\u4ea7\u66ff\u4ee3\u548c\u8d44\u672c\u5f00\u652f\u6062\u590d\u91cd\u65b0\u5b9a\u4ef7\u3002", + "sectors": ["\u534a\u5bfc\u4f53"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/semi-equipment-up", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u5238\u5546\u677f\u5757\u5348\u540e\u62c9\u5347\uff0c\u5e02\u573a\u60c5\u7eea\u6709\u6240\u4fee\u590d", + "summary": "\u6307\u6570\u9707\u8361\u8fc7\u7a0b\u4e2d\u5238\u5546\u627f\u62c5\u60c5\u7eea\u4fee\u590d\u529f\u80fd\uff0c\u5e26\u52a8\u90e8\u5206\u9ad8\u5f39\u6027\u65b9\u5411\u56de\u6696\u3002", + "sectors": ["\u5238\u5546"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/broker-rebound", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u521b\u65b0\u836f\u65b9\u5411\u6301\u7eed\u6d3b\u8dc3\uff0c\u8d44\u91d1\u8f6c\u5411\u9632\u5b88\u4e0e\u6210\u957f\u517c\u987e", + "summary": "\u533b\u836f\u677f\u5757\u83b7\u5f97\u589e\u91cf\u8d44\u91d1\u5173\u6ce8\uff0c\u521b\u65b0\u836f\u548c\u5668\u68b0\u7ec6\u5206\u8868\u73b0\u66f4\u5f3a\u3002", + "sectors": ["\u533b\u836f"], + "sentiment": SENTIMENT_NEUTRAL, + "reference_url": "https://www.cls.cn/detail/medical-active", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u65b0\u80fd\u6e90\u94fe\u6761\u5206\u5316\u52a0\u5267\uff0c\u673a\u6784\u63d0\u9192\u5173\u6ce8\u4ea7\u80fd\u51fa\u6e05\u8282\u594f", + "summary": "\u65b0\u80fd\u6e90\u677f\u5757\u5185\u90e8\u8f6e\u52a8\u660e\u663e\uff0c\u8d44\u91d1\u66f4\u504f\u5411\u4f4e\u4f4d\u73af\u8282\u548c\u6210\u672c\u6539\u5584\u65b9\u5411\u3002", + "sectors": ["\u65b0\u80fd\u6e90"], + "sentiment": SENTIMENT_NEUTRAL, + "reference_url": "https://www.cls.cn/detail/new-energy-split", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u519b\u5de5\u677f\u5757\u76d8\u4e2d\u5f02\u52a8\uff0c\u8ba2\u5355\u5151\u73b0\u9884\u671f\u91cd\u65b0\u5347\u6e29", + "summary": "\u519b\u5de5\u94fe\u6761\u76d8\u4e2d\u8d70\u5f3a\uff0c\u5e02\u573a\u5173\u6ce8\u540e\u7eed\u8ba2\u5355\u5151\u73b0\u4e0e\u4f30\u503c\u5207\u6362\u7a7a\u95f4\u3002", + "sectors": ["\u519b\u5de5"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/defense-orders", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u673a\u5668\u4eba\u677f\u5757\u51b2\u9ad8\u56de\u843d\uff0c\u77ed\u7ebf\u535a\u5f08\u60c5\u7eea\u5347\u6e29", + "summary": "\u673a\u5668\u4eba\u65b9\u5411\u9ad8\u4f4d\u9707\u8361\uff0c\u8d44\u91d1\u5728\u9898\u6750\u6269\u6563\u4e0e\u5151\u73b0\u538b\u529b\u4e4b\u95f4\u53cd\u590d\u5207\u6362\u3002", + "sectors": ["\u673a\u5668\u4eba"], + "sentiment": SENTIMENT_NEUTRAL, + "reference_url": "https://www.cls.cn/detail/robotics-intraday", + }, + { + "title": "\u8d22\u8054\u793e7x24\uff1a\u5b58\u50a8\u82af\u7247\u62a5\u4ef7\u9884\u671f\u7ee7\u7eed\u4e0a\u4fee\uff0c\u4ea7\u4e1a\u94fe\u666f\u6c14\u5ea6\u53d7\u5173\u6ce8", + "summary": "\u5b58\u50a8\u73af\u8282\u4ef7\u683c\u4fee\u590d\u903b\u8f91\u5ef6\u7eed\uff0c\u5e02\u573a\u91cd\u65b0\u4ea4\u6613\u4f9b\u9700\u6539\u5584\u4e0e\u76c8\u5229\u5f39\u6027\u3002", + "sectors": ["\u5b58\u50a8\u82af\u7247"], + "sentiment": SENTIMENT_BULL, + "reference_url": "https://www.cls.cn/detail/memory-price-up", + }, +] + +SAMPLE_INPUTS = { + 1: { + "touzi-mingjian": ["https://mp.weixin.qq.com/s/semiconductor-capacity-and-chip-cycle"], + "aigujun-2020": ["https://mp.weixin.qq.com/s/storage-chip-price-repair"], + "mazhiming-shouping": ["https://mp.weixin.qq.com/s/market-close-sector-rotation"], + "laobai-guandian": ["https://mp.weixin.qq.com/s/robotics-and-energy-balance"], + }, +} + + +def now_local() -> datetime: + return datetime.now(SHANGHAI) + + +def iso_timestamp(value: datetime | None = None) -> str: + return (value or now_local()).replace(microsecond=0).isoformat() + + +def ensure_local_timezone(value: datetime) -> datetime: + if value.tzinfo is None: + return value.replace(tzinfo=SHANGHAI) + return value.astimezone(SHANGHAI) + + +def normalize_whitespace(value: str) -> str: + return re.sub(r"\s+", " ", value).strip() + + +def extract_json_object(script_text: str, marker: str) -> str: + marker_index = script_text.find(marker) + if marker_index < 0: + raise RuntimeError(f"Marker not found: {marker}") + + start = script_text.find("{", marker_index) + if start < 0: + raise RuntimeError(f"JSON object start not found for marker: {marker}") + + depth = 0 + in_string = False + escaped = False + for index in range(start, len(script_text)): + char = script_text[index] + if in_string: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == '"': + in_string = False + continue + + if char == '"': + in_string = True + continue + if char == "{": + depth += 1 + continue + if char == "}": + depth -= 1 + if depth == 0: + return script_text[start : index + 1] + + raise RuntimeError(f"JSON object end not found for marker: {marker}") + + +def parse_telegraph_timestamp(date_str: str, time_str: str) -> str: + normalized_time = time_str if len(time_str.split(":")) == 3 else f"{time_str}:00" + return datetime.fromisoformat(f"{date_str}T{normalized_time}").replace(tzinfo=SHANGHAI).isoformat(timespec="seconds") + + +def split_title_and_summary(content: str) -> tuple[str, str]: + cleaned = normalize_whitespace(content) + bracket_match = re.match(r"^[\[({\u3010\u3016](.+?)[\])}\u3011\u3017][\uff1a: ]*(.*)$", cleaned) + if bracket_match: + title = normalize_whitespace(bracket_match.group(1)) + summary = normalize_whitespace(bracket_match.group(2) or cleaned) + return title[:80], summary or title + + sentence_parts = re.split(r"[。;;!?!?]", cleaned, maxsplit=1) + title = sentence_parts[0][:80] + summary = cleaned if len(cleaned) <= 220 else f"{cleaned[:217]}..." + return title, summary + + +def build_fallback_cls_items(reference_time: datetime) -> list[ClsNewsItem]: + items: list[ClsNewsItem] = [] + for index, template in enumerate(CLS_NEWS_TEMPLATES): + published_at = (reference_time - timedelta(minutes=index * 95 + 8)).replace(microsecond=0).isoformat() + items.append( + ClsNewsItem( + id=f"cls-{index + 1}", + title=template["title"], + published_at=published_at, + source="\u8d22\u8054\u793e" if index % 2 == 0 else "\u8d22\u8054\u793e AI Daily", + summary=template["summary"], + reference_url=template["reference_url"], + sectors=template["sectors"], + sentiment=template["sentiment"], + ) + ) + return sorted(items, key=lambda item: item.published_at, reverse=True) + + +def fetch_cls_telegraph_items(reference_time: datetime) -> list[ClsNewsItem]: + session = requests.Session() + session.trust_env = False + response = session.get(CLS_TELEGRAPH_URL, headers=HTTP_HEADERS, timeout=15) + response.raise_for_status() + response.encoding = "utf-8" + + soup = BeautifulSoup(response.text, "html.parser") + next_data_script = None + for script in soup.find_all("script"): + script_text = script.string or script.get_text() + if "__NEXT_DATA__ =" in script_text: + next_data_script = script_text + break + if not next_data_script: + raise RuntimeError("Missing __NEXT_DATA__ payload on cls.cn") + + next_data = json.loads(extract_json_object(next_data_script, "__NEXT_DATA__ =")) + roll_data = ( + next_data.get("props", {}) + .get("initialState", {}) + .get("roll_data", []) + ) + if not isinstance(roll_data, list) or not roll_data: + raise RuntimeError("Missing roll_data in cls.cn payload") + + target_date = reference_time.date() + items: list[ClsNewsItem] = [] + seen_ids: set[int] = set() + latest_limit = 80 + for entry in roll_data: + if len(items) >= latest_limit: + break + + item_id = int(entry.get("id") or 0) + if not item_id or item_id in seen_ids: + continue + seen_ids.add(item_id) + + timestamp = int(entry.get("modified_time") or entry.get("ctime") or 0) + if not timestamp: + continue + published_dt = datetime.fromtimestamp(timestamp, tz=SHANGHAI) + if published_dt.date() != target_date: + continue + + raw_content = normalize_whitespace( + entry.get("content") + or entry.get("brief") + or entry.get("title") + or "" + ) + if len(raw_content) < 8: + continue + + title = normalize_whitespace(entry.get("title") or "") + if not title: + title, _ = split_title_and_summary(raw_content) + + summary = normalize_whitespace(entry.get("brief") or "") + if not summary: + _, summary = split_title_and_summary(raw_content) + + source = normalize_whitespace(entry.get("author") or "\u8d22\u8054\u793e7x24") + reference_url = normalize_whitespace(entry.get("shareurl") or "") + if not reference_url: + reference_url = f"https://www.cls.cn/detail/{item_id}" + + sectors = infer_sectors(f"{title} {summary}", "touzi-mingjian") + sentiment = infer_sentiment(f"{title} {summary}") + items.append( + ClsNewsItem( + id=f"cls-live-{item_id}", + title=title[:120], + published_at=published_dt.isoformat(timespec="seconds"), + source=source, + summary=summary[:500], + reference_url=reference_url, + sectors=sectors, + sentiment=sentiment, + ) + ) + + if not items: + raise RuntimeError("No telegraph items parsed from cls.cn") + + return sorted(items, key=lambda item: item.published_at, reverse=True) + + +def get_accounts() -> list[Account]: + records = fetch_accounts() + return records or ACCOUNTS + + +def normalize_date(value: str) -> str: + return datetime.fromisoformat(value).date().isoformat() + + +def blank_daily_input(date_str: str) -> DailyInputDocument: + return DailyInputDocument( + date=date_str, + updated_at=iso_timestamp(), + accounts=[ + DailyInputAccount(account_id=account.id, account_name=account.name, links=[]) + for account in get_accounts() + ], + ) + + +def clean_links(links: Iterable[str]) -> list[str]: + normalized: list[str] = [] + seen: set[str] = set() + for raw_link in links: + link = raw_link.strip() + if not link or link in seen: + continue + seen.add(link) + normalized.append(link) + return normalized + + +def normalize_daily_input(date_str: str, payload: DailyInputUpsertPayload) -> DailyInputDocument: + payload_map = {item.account_id: clean_links(item.links) for item in payload.accounts} + return DailyInputDocument( + date=date_str, + updated_at=iso_timestamp(), + accounts=[ + DailyInputAccount( + account_id=account.id, + account_name=account.name, + links=payload_map.get(account.id, []), + ) + for account in get_accounts() + ], + ) + + +def load_daily_input(date_str: str) -> DailyInputDocument: + payload = fetch_daily_input_document(date_str) + if payload is None: + return blank_daily_input(date_str) + return payload + + +def save_daily_input(document: DailyInputDocument) -> DailyInputDocument: + return save_daily_input_document(document) + + +def load_report(date_str: str) -> ReportDocument | None: + return fetch_report_document(date_str) + + +def save_report(document: ReportDocument) -> ReportDocument: + return save_report_document(document) + + +def list_reports() -> list[ReportListItem]: + return fetch_report_list() + + +def title_from_link(account_name: str, url: str, index: int) -> str: + text = unquote(urlparse(url).path or url) + tokens = [ + token + for token in re.split(r"[\W_]+", text.lower()) + if token and token not in {"s", "mp", "weixin", "qq", "com"} + ] + meaningful = [token for token in tokens if len(token) > 1] + if meaningful: + topic = " / ".join(token.upper() if len(token) <= 3 else token.capitalize() for token in meaningful[:3]) + return f"{account_name}\uff1a{topic} \u89c2\u5bdf" + return f"{account_name}\uff1a\u5e02\u573a\u8ddf\u8e2a\u7b2c {index + 1} \u6761" + + +def infer_sectors(text: str, account_id: str) -> list[str]: + lowered = text.lower() + sectors = [ + sector + for sector, keywords in SECTOR_KEYWORDS.items() + if any(keyword.lower() in lowered for keyword in keywords) + ] + if sectors: + return sectors[:3] + return ACCOUNT_FOCUS.get(account_id, ["AI", "\u7b97\u529b"])[:2] + + +def infer_sentiment(text: str) -> str: + lowered = text.lower() + positive = sum(keyword.lower() in lowered for keyword in POSITIVE_KEYWORDS) + negative = sum(keyword.lower() in lowered for keyword in NEGATIVE_KEYWORDS) + if positive > negative: + return SENTIMENT_BULL + if negative > positive: + return SENTIMENT_BEAR + return SENTIMENT_NEUTRAL + + +def infer_article_type(title: str) -> str: + lowered = title.lower() + for keyword, article_type in ARTICLE_TYPE_PATTERNS: + if keyword.lower() in lowered: + return article_type + return "\u4e3b\u9898\u89c2\u70b9" + + +def build_article_summary(title: str, sectors: list[str], sentiment: str) -> str: + sector_text = "\u3001".join(sectors[:2]) if sectors else "\u6838\u5fc3\u4e3b\u7ebf" + sentiment_text = { + SENTIMENT_BULL: "\u504f\u79ef\u6781\u7684\u8282\u594f\u5224\u65ad", + SENTIMENT_BEAR: "\u660e\u663e\u504f\u8c28\u614e\u7684\u98ce\u9669\u63d0\u9192", + SENTIMENT_NEUTRAL: "\u66f4\u5f3a\u8c03\u7ed3\u6784\u5206\u5316\u4e0e\u7b49\u5f85\u786e\u8ba4", + }[sentiment] + return f"{title} \u56f4\u7ed5 {sector_text} \u5c55\u5f00\uff0c\u7ed9\u51fa\u7684\u7ed3\u8bba\u662f{sentiment_text}\uff0c\u9002\u5408\u4f5c\u4e3a\u5f53\u65e5\u76d8\u9762\u8ddf\u8e2a\u4e0e\u590d\u76d8\u53c2\u8003\u3002" + + +def generate_report(date_str: str, input_document: DailyInputDocument) -> ReportDocument: + base_date = datetime.fromisoformat(date_str) + articles: list[OpinionArticle] = [] + for account_index, account in enumerate(input_document.accounts): + for link_index, url in enumerate(account.links): + title = title_from_link(account.account_name, url, link_index) + sectors = infer_sectors(f"{title} {url}", account.account_id) + sentiment = infer_sentiment(f"{title} {url}") + published_at = ( + base_date.replace(hour=9 + ((account_index + link_index) % 8), minute=(link_index * 12) % 60) + .replace(tzinfo=SHANGHAI) + .isoformat(timespec="seconds") + ) + articles.append( + OpinionArticle( + id=f"{date_str}-{account.account_id}-{link_index}", + account_id=account.account_id, + account_name=account.account_name, + title=title, + published_at=published_at, + summary=build_article_summary(title, sectors, sentiment), + source_url=url, + sectors=sectors, + sentiment=sentiment, + article_type=infer_article_type(title), + ) + ) + + if not articles: + return ReportDocument( + date=date_str, + generated_at=iso_timestamp(), + summary="\u5f53\u65e5\u5c1a\u672a\u5f55\u5165\u6587\u7ae0\u94fe\u63a5\uff0c\u7cfb\u7edf\u5df2\u4fdd\u7559\u65e5\u62a5\u7ed3\u6784\uff0c\u7b49\u5f85\u8865\u5145\u516c\u4f17\u53f7\u6587\u7ae0\u540e\u518d\u751f\u6210\u5b8c\u6574\u7ed3\u8bba\u3002", + focus_sectors=[], + article_count=0, + account_count=0, + articles=[], + ) + + sector_counter = Counter(sector for article in articles for sector in article.sectors) + focus_sectors = [sector for sector, _count in sector_counter.most_common(4)] + + sentiment_counter = Counter(article.sentiment for article in articles) + if sentiment_counter[SENTIMENT_BULL] > sentiment_counter[SENTIMENT_BEAR]: + tone = "\u6574\u4f53\u504f\u79ef\u6781\uff0c\u4e3b\u7ebf\u8ba8\u8bba\u96c6\u4e2d\u5ea6\u8f83\u9ad8" + elif sentiment_counter[SENTIMENT_BEAR] > sentiment_counter[SENTIMENT_BULL]: + tone = "\u6574\u4f53\u504f\u8c28\u614e\uff0c\u98ce\u9669\u63a7\u5236\u4ecd\u662f\u4e3b\u53d9\u4e8b" + else: + tone = "\u591a\u7a7a\u5206\u6b67\u5e76\u5b58\uff0c\u5e02\u573a\u66f4\u770b\u91cd\u9a8c\u8bc1\u4e0e\u8282\u594f" + + active_accounts = len([account for account in input_document.accounts if account.links]) + sector_text = "\u3001".join(focus_sectors) if focus_sectors else "\u6682\u65e0\u805a\u7126\u677f\u5757" + summary = ( + f"{date_str} \u5171\u6574\u7406 {len(articles)} \u7bc7\u516c\u4f17\u53f7\u89c2\u70b9\uff0c\u8986\u76d6 {active_accounts} \u4e2a\u8d26\u6237\u3002" + f"{tone}\uff0c\u8ba8\u8bba\u91cd\u70b9\u843d\u5728 {sector_text}\u3002" + ) + + return ReportDocument( + date=date_str, + generated_at=iso_timestamp(), + summary=summary, + focus_sectors=focus_sectors, + article_count=len(articles), + account_count=active_accounts, + articles=sorted(articles, key=lambda item: item.published_at, reverse=True), + ) + + +def build_cls_news_document( + reference_time: datetime | None = None, + *, + allow_live_fetch: bool = True, +) -> ClsNewsDocument: + current = reference_time or now_local() + try: + if allow_live_fetch: + items = fetch_cls_telegraph_items(current) + else: + raise RuntimeError("Live fetch disabled for non-current date") + except Exception: + items = build_fallback_cls_items(current) + + sector_counter = Counter(sector for item in items for sector in item.sectors) + watch_list = [sector for sector, _count in sector_counter.most_common(5)] + + overview = ( + "\u8d44\u8baf\u5217\u8868\u5c55\u793a\u6240\u9009\u65e5\u671f\u5185\u7684\u8d22\u8054\u793e 7x24 \u8d44\u8baf\uff0c" + "\u5f53\u65e5\u6570\u636e\u6765\u81ea cls.cn \u5b9e\u65f6\u6293\u53d6\uff0c\u6bcf 3 \u5206\u949f\u66f4\u65b0\u4e00\u6b21\u3002" + ) + hot_topics = ( + "\u70ed\u70b9\u6982\u89c8\u53ea\u4fdd\u7559\u5bf9\u677f\u5757\u5b58\u5728\u660e\u663e\u5f71\u54cd\u7684\u65b9\u5411\uff0c" + f"\u5f53\u524d\u4e3b\u8981\u96c6\u4e2d\u5728 {'\u3001'.join(watch_list[:3])}\u3002" + ) + + sector_impacts: list[ClsSectorImpact] = [] + seen_sectors: set[str] = set() + for sector in watch_list[:4]: + if sector in seen_sectors: + continue + seen_sectors.add(sector) + related_items = [item for item in items if sector in item.sectors] + if not related_items: + continue + + sentiment_counter = Counter(item.sentiment for item in related_items) + if sentiment_counter[SENTIMENT_BULL] > sentiment_counter[SENTIMENT_BEAR]: + sentiment = SENTIMENT_BULL + reason = f"{sector} \u65b9\u5411\u51fa\u73b0\u50ac\u5316\u6216\u666f\u6c14\u5f3a\u5316\uff0c\u77ed\u7ebf\u504f\u6b63\u5411\u5f71\u54cd\u3002" + elif sentiment_counter[SENTIMENT_BEAR] > sentiment_counter[SENTIMENT_BULL]: + sentiment = SENTIMENT_BEAR + reason = f"{sector} \u65b9\u5411\u51fa\u73b0\u5151\u73b0\u6216\u5206\u6b67\uff0c\u77ed\u7ebf\u504f\u8d1f\u5411\u5f71\u54cd\u3002" + else: + sentiment = SENTIMENT_NEUTRAL + reason = f"{sector} \u65b9\u5411\u6709\u8ba8\u8bba\u4f46\u4ecd\u9700\u9a8c\u8bc1\uff0c\u77ed\u7ebf\u4ee5\u4e2d\u6027\u89c2\u5bdf\u4e3a\u4e3b\u3002" + + sector_impacts.append( + ClsSectorImpact( + sector=sector, + sentiment=sentiment, + reason=reason, + related_titles=list(dict.fromkeys(item.title for item in related_items[:2])), + ) + ) + + return ClsNewsDocument( + date=current.date().isoformat(), + updated_at=iso_timestamp(current), + window_label="\u5f53\u5929\u8d44\u8baf", + summary=ClsNewsSummary( + overview=overview, + hot_topics=hot_topics, + watch_list=watch_list, + ), + sector_impacts=sector_impacts, + items=items, + ) + + +def load_cls_news(date_str: str) -> ClsNewsDocument | None: + return fetch_cls_news_document(date_str) + + +def build_reference_time(date_str: str) -> datetime: + date_value = datetime.fromisoformat(date_str).date() + if date_value == now_local().date(): + return now_local() + return datetime.combine(date_value, time(hour=15, minute=0), tzinfo=SHANGHAI) + + +def refresh_cls_news(date_str: str | None = None) -> ClsNewsDocument: + normalized_date = normalize_date(date_str or now_local().date().isoformat()) + existing = load_cls_news(normalized_date) + reference_time = build_reference_time(normalized_date) + allow_live_fetch = normalized_date == now_local().date().isoformat() + try: + document = build_cls_news_document(reference_time, allow_live_fetch=allow_live_fetch) + except Exception: + if existing is not None: + return existing + raise + return save_cls_news_document(document) + + +def get_cls_news(date_str: str | None = None) -> ClsNewsDocument: + normalized_date = normalize_date(date_str or now_local().date().isoformat()) + document = load_cls_news(normalized_date) + if document is None: + return refresh_cls_news(normalized_date) + if normalized_date != now_local().date().isoformat(): + return document + updated_at = ensure_local_timezone(datetime.fromisoformat(document.updated_at)) + if now_local() - updated_at >= CLS_REFRESH_INTERVAL: + return refresh_cls_news(normalized_date) + return document + + +def seed_demo_content() -> None: + save_accounts(ACCOUNTS) + + today = now_local().date() + for offset, account_links in SAMPLE_INPUTS.items(): + date_str = (today - timedelta(days=offset)).isoformat() + if fetch_daily_input_document(date_str) is not None and fetch_report_document(date_str) is not None: + continue + + payload = DailyInputUpsertPayload( + accounts=[ + {"account_id": account.id, "links": account_links.get(account.id, [])} + for account in ACCOUNTS + ] + ) + input_document = normalize_daily_input(date_str, payload) + save_daily_input_document(input_document) + save_report_document(generate_report(date_str, input_document)) + + today_str = today.isoformat() + if fetch_cls_news_document(today_str) is None: + save_cls_news_document(build_cls_news_document()) diff --git a/backend/app/services/storage.py b/backend/app/services/storage.py new file mode 100644 index 0000000..23b943f --- /dev/null +++ b/backend/app/services/storage.py @@ -0,0 +1,480 @@ +from __future__ import annotations + +import hashlib +import json +from contextlib import contextmanager +from datetime import date, datetime +from pathlib import Path +from typing import Any, Iterator + +from sqlalchemy import JSON, Date, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, create_engine, delete, select +from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship, sessionmaker + +from app.models import ( + Account, + ClsNewsDocument, + ClsNewsItem, + ClsNewsSummary, + ClsSectorImpact, + DailyInputAccount, + DailyInputDocument, + OpinionArticle, + ReportDocument, + ReportListItem, +) + +PROJECT_ROOT = Path(__file__).resolve().parents[3] +CONFIG_DIR = PROJECT_ROOT / "backend" / "config" +DATABASE_CONFIG_PATH = CONFIG_DIR / "database.json" + + +class Base(DeclarativeBase): + pass + + +class AccountRecord(Base): + __tablename__ = "accounts" + + id: Mapped[str] = mapped_column(String(64), primary_key=True) + name: Mapped[str] = mapped_column(String(128), nullable=False) + description: Mapped[str] = mapped_column(String(255), nullable=False) + + +class DailyInputRecord(Base): + __tablename__ = "daily_inputs" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + date: Mapped[Any] = mapped_column(Date, nullable=False, unique=True, index=True) + updated_at: Mapped[Any] = mapped_column(DateTime(timezone=True), nullable=False) + + links: Mapped[list["DailyInputLinkRecord"]] = relationship( + back_populates="daily_input", + cascade="all, delete-orphan", + order_by="DailyInputLinkRecord.sort_order", + ) + + +class DailyInputLinkRecord(Base): + __tablename__ = "daily_input_links" + __table_args__ = ( + UniqueConstraint("daily_input_id", "account_id", "url_hash", name="uq_daily_input_account_url"), + ) + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + daily_input_id: Mapped[int] = mapped_column(ForeignKey("daily_inputs.id", ondelete="CASCADE"), nullable=False) + account_id: Mapped[str] = mapped_column(ForeignKey("accounts.id"), nullable=False, index=True) + url: Mapped[str] = mapped_column(String(1024), nullable=False) + url_hash: Mapped[str] = mapped_column(String(64), nullable=False) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + + daily_input: Mapped[DailyInputRecord] = relationship(back_populates="links") + + +class ReportRecord(Base): + __tablename__ = "reports" + + date: Mapped[Any] = mapped_column(Date, primary_key=True) + generated_at: Mapped[Any] = mapped_column(DateTime(timezone=True), nullable=False) + summary: Mapped[str] = mapped_column(Text, nullable=False) + focus_sectors: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) + article_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + account_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + + articles: Mapped[list["ReportArticleRecord"]] = relationship( + back_populates="report", + cascade="all, delete-orphan", + order_by="ReportArticleRecord.sort_order", + ) + + +class ReportArticleRecord(Base): + __tablename__ = "report_articles" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + report_date: Mapped[Any] = mapped_column(ForeignKey("reports.date", ondelete="CASCADE"), nullable=False, index=True) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + article_id: Mapped[str] = mapped_column(String(128), nullable=False) + account_id: Mapped[str] = mapped_column(String(64), nullable=False) + account_name: Mapped[str] = mapped_column(String(128), nullable=False) + title: Mapped[str] = mapped_column(String(255), nullable=False) + published_at: Mapped[str] = mapped_column(String(64), nullable=False) + summary: Mapped[str] = mapped_column(Text, nullable=False) + source_url: Mapped[str] = mapped_column(Text, nullable=False) + sectors: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) + sentiment: Mapped[str] = mapped_column(String(16), nullable=False) + article_type: Mapped[str] = mapped_column(String(64), nullable=False) + + report: Mapped[ReportRecord] = relationship(back_populates="articles") + + +class ClsNewsSnapshotRecord(Base): + __tablename__ = "cls_news_snapshots" + + date: Mapped[Any] = mapped_column(Date, primary_key=True) + updated_at: Mapped[Any] = mapped_column(DateTime(timezone=True), nullable=False) + window_label: Mapped[str] = mapped_column(String(64), nullable=False) + overview: Mapped[str] = mapped_column(Text, nullable=False) + hot_topics: Mapped[str] = mapped_column(Text, nullable=False) + watch_list: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) + + sector_impacts: Mapped[list["ClsSectorImpactRecord"]] = relationship( + back_populates="snapshot", + cascade="all, delete-orphan", + order_by="ClsSectorImpactRecord.sort_order", + ) + items: Mapped[list["ClsNewsItemRecord"]] = relationship( + back_populates="snapshot", + cascade="all, delete-orphan", + order_by="ClsNewsItemRecord.sort_order", + ) + + +class ClsSectorImpactRecord(Base): + __tablename__ = "cls_sector_impacts" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + snapshot_date: Mapped[Any] = mapped_column(ForeignKey("cls_news_snapshots.date", ondelete="CASCADE"), nullable=False, index=True) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + sector: Mapped[str] = mapped_column(String(64), nullable=False) + sentiment: Mapped[str] = mapped_column(String(16), nullable=False) + reason: Mapped[str] = mapped_column(Text, nullable=False) + related_titles: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) + + snapshot: Mapped[ClsNewsSnapshotRecord] = relationship(back_populates="sector_impacts") + + +class ClsNewsItemRecord(Base): + __tablename__ = "cls_news_items" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + snapshot_date: Mapped[Any] = mapped_column(ForeignKey("cls_news_snapshots.date", ondelete="CASCADE"), nullable=False, index=True) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + item_id: Mapped[str] = mapped_column(String(128), nullable=False) + title: Mapped[str] = mapped_column(String(255), nullable=False) + published_at: Mapped[str] = mapped_column(String(64), nullable=False) + source: Mapped[str] = mapped_column(String(128), nullable=False) + summary: Mapped[str] = mapped_column(Text, nullable=False) + reference_url: Mapped[str] = mapped_column(Text, nullable=False) + sectors: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) + sentiment: Mapped[str] = mapped_column(String(16), nullable=False) + + snapshot: Mapped[ClsNewsSnapshotRecord] = relationship(back_populates="items") + + +def load_database_config() -> dict[str, Any]: + if not DATABASE_CONFIG_PATH.exists(): + raise RuntimeError(f"Database config not found: {DATABASE_CONFIG_PATH}") + config = json.loads(DATABASE_CONFIG_PATH.read_text(encoding="utf-8")) + required_fields = ("host", "port", "username", "password", "database") + missing = [field for field in required_fields if not config.get(field)] + if missing: + raise RuntimeError(f"Database config is incomplete: {', '.join(missing)}") + if any(str(config.get(field)).strip() == "CHANGE_ME" for field in required_fields): + raise RuntimeError(f"Database config still contains placeholders: {DATABASE_CONFIG_PATH}") + config.setdefault("charset", "utf8mb4") + config.setdefault("echo", False) + return config + + +_ENGINE = None +_SESSION_FACTORY: sessionmaker[Session] | None = None + + +def parse_date(value: str) -> date: + return date.fromisoformat(value) + + +def parse_datetime(value: str) -> datetime: + return datetime.fromisoformat(value) + + +def hash_url(value: str) -> str: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + + +def get_engine(): + global _ENGINE + if _ENGINE is None: + config = load_database_config() + _ENGINE = create_engine( + ( + f"mysql+pymysql://{config['username']}:{config['password']}" + f"@{config['host']}:{config['port']}/{config['database']}?charset={config['charset']}" + ), + echo=bool(config.get("echo", False)), + future=True, + ) + return _ENGINE + + +def get_session_factory() -> sessionmaker[Session]: + global _SESSION_FACTORY + if _SESSION_FACTORY is None: + _SESSION_FACTORY = sessionmaker(bind=get_engine(), autoflush=False, autocommit=False, future=True) + return _SESSION_FACTORY + + +def init_database() -> None: + Base.metadata.create_all(get_engine()) + + +@contextmanager +def session_scope() -> Iterator[Session]: + session = get_session_factory()() + try: + yield session + session.commit() + except Exception: + session.rollback() + raise + finally: + session.close() + + +def save_accounts(accounts: list[Account]) -> None: + with session_scope() as session: + for account in accounts: + existing = session.get(AccountRecord, account.id) + if existing is None: + session.add(AccountRecord(id=account.id, name=account.name, description=account.description)) + continue + existing.name = account.name + existing.description = account.description + + +def fetch_accounts() -> list[Account]: + with session_scope() as session: + records = session.scalars(select(AccountRecord).order_by(AccountRecord.id)).all() + return [Account(id=record.id, name=record.name, description=record.description) for record in records] + + +def fetch_daily_input_document(date_str: str) -> DailyInputDocument | None: + with session_scope() as session: + record = session.scalar(select(DailyInputRecord).where(DailyInputRecord.date == parse_date(date_str))) + if record is None: + return None + account_records = session.scalars(select(AccountRecord).order_by(AccountRecord.id)).all() + links_by_account: dict[str, list[str]] = {} + for link in record.links: + links_by_account.setdefault(link.account_id, []).append(link.url) + return DailyInputDocument( + date=str(record.date), + updated_at=record.updated_at.isoformat(timespec="seconds"), + accounts=[ + DailyInputAccount( + account_id=account.id, + account_name=account.name, + links=links_by_account.get(account.id, []), + ) + for account in account_records + ], + ) + + +def save_daily_input_document(document: DailyInputDocument) -> DailyInputDocument: + with session_scope() as session: + record = session.scalar(select(DailyInputRecord).where(DailyInputRecord.date == parse_date(document.date))) + if record is None: + record = DailyInputRecord(date=parse_date(document.date), updated_at=parse_datetime(document.updated_at)) + session.add(record) + session.flush() + else: + record.updated_at = parse_datetime(document.updated_at) + record.links.clear() + session.flush() + + sort_order = 0 + for account in document.accounts: + for url in account.links: + record.links.append( + DailyInputLinkRecord( + account_id=account.account_id, + url=url, + url_hash=hash_url(url), + sort_order=sort_order, + ) + ) + sort_order += 1 + return document + + +def fetch_report_document(date_str: str) -> ReportDocument | None: + with session_scope() as session: + record = session.get(ReportRecord, parse_date(date_str)) + if record is None: + return None + return ReportDocument( + date=str(record.date), + generated_at=record.generated_at.isoformat(timespec="seconds"), + summary=record.summary, + focus_sectors=list(record.focus_sectors or []), + article_count=record.article_count, + account_count=record.account_count, + articles=[ + OpinionArticle( + id=article.article_id, + account_id=article.account_id, + account_name=article.account_name, + title=article.title, + published_at=article.published_at, + summary=article.summary, + source_url=article.source_url, + sectors=list(article.sectors or []), + sentiment=article.sentiment, + article_type=article.article_type, + ) + for article in record.articles + ], + ) + + +def save_report_document(document: ReportDocument) -> ReportDocument: + with session_scope() as session: + record = session.get(ReportRecord, parse_date(document.date)) + if record is None: + record = ReportRecord( + date=parse_date(document.date), + generated_at=parse_datetime(document.generated_at), + summary=document.summary, + focus_sectors=document.focus_sectors, + article_count=document.article_count, + account_count=document.account_count, + ) + session.add(record) + else: + record.generated_at = parse_datetime(document.generated_at) + record.summary = document.summary + record.focus_sectors = document.focus_sectors + record.article_count = document.article_count + record.account_count = document.account_count + record.articles.clear() + session.flush() + + for index, article in enumerate(document.articles): + record.articles.append( + ReportArticleRecord( + sort_order=index, + article_id=article.id, + account_id=article.account_id, + account_name=article.account_name, + title=article.title, + published_at=article.published_at, + summary=article.summary, + source_url=article.source_url, + sectors=article.sectors, + sentiment=article.sentiment, + article_type=article.article_type, + ) + ) + return document + + +def fetch_report_list() -> list[ReportListItem]: + with session_scope() as session: + records = session.scalars(select(ReportRecord).order_by(ReportRecord.date.desc())).all() + return [ + ReportListItem( + date=str(record.date), + generated_at=record.generated_at.isoformat(timespec="seconds"), + summary=record.summary, + article_count=record.article_count, + focus_sectors=list(record.focus_sectors or []), + ) + for record in records + ] + + +def fetch_cls_news_document(date_str: str) -> ClsNewsDocument | None: + with session_scope() as session: + record = session.get(ClsNewsSnapshotRecord, parse_date(date_str)) + if record is None: + return None + return ClsNewsDocument( + date=str(record.date), + updated_at=record.updated_at.isoformat(timespec="seconds"), + window_label=record.window_label, + summary=ClsNewsSummary( + overview=record.overview, + hot_topics=record.hot_topics, + watch_list=list(record.watch_list or []), + ), + sector_impacts=[ + ClsSectorImpact( + sector=item.sector, + sentiment=item.sentiment, + reason=item.reason, + related_titles=list(item.related_titles or []), + ) + for item in record.sector_impacts + ], + items=[ + ClsNewsItem( + id=item.item_id, + title=item.title, + published_at=item.published_at, + source=item.source, + summary=item.summary, + reference_url=item.reference_url, + sectors=list(item.sectors or []), + sentiment=item.sentiment, + ) + for item in record.items + ], + ) + + +def save_cls_news_document(document: ClsNewsDocument) -> ClsNewsDocument: + with session_scope() as session: + record = session.get(ClsNewsSnapshotRecord, parse_date(document.date)) + if record is None: + record = ClsNewsSnapshotRecord( + date=parse_date(document.date), + updated_at=parse_datetime(document.updated_at), + window_label=document.window_label, + overview=document.summary.overview, + hot_topics=document.summary.hot_topics, + watch_list=document.summary.watch_list, + ) + session.add(record) + else: + record.updated_at = parse_datetime(document.updated_at) + record.window_label = document.window_label + record.overview = document.summary.overview + record.hot_topics = document.summary.hot_topics + record.watch_list = document.summary.watch_list + session.execute( + delete(ClsSectorImpactRecord).where(ClsSectorImpactRecord.snapshot_date == record.date) + ) + session.execute( + delete(ClsNewsItemRecord).where(ClsNewsItemRecord.snapshot_date == record.date) + ) + record.sector_impacts = [] + record.items = [] + session.flush() + + for index, impact in enumerate(document.sector_impacts): + record.sector_impacts.append( + ClsSectorImpactRecord( + sort_order=index, + sector=impact.sector, + sentiment=impact.sentiment, + reason=impact.reason, + related_titles=impact.related_titles, + ) + ) + + for index, item in enumerate(document.items): + record.items.append( + ClsNewsItemRecord( + sort_order=index, + item_id=item.id, + title=item.title, + published_at=item.published_at, + source=item.source, + summary=item.summary, + reference_url=item.reference_url, + sectors=item.sectors, + sentiment=item.sentiment, + ) + ) + return document diff --git a/backend/config/database.example.json b/backend/config/database.example.json new file mode 100644 index 0000000..9060583 --- /dev/null +++ b/backend/config/database.example.json @@ -0,0 +1,9 @@ +{ + "host": "127.0.0.1", + "port": 3306, + "username": "your-user", + "password": "your-password", + "database": "your-database", + "charset": "utf8mb4", + "echo": false +} diff --git a/backend/config/database.json b/backend/config/database.json new file mode 100644 index 0000000..672436c --- /dev/null +++ b/backend/config/database.json @@ -0,0 +1,9 @@ +{ + "host": "152.136.100.182", + "port": 3306, + "username": "root", + "password": "4a3986024e6662f9e571782ece1587298291d18925b44f1f", + "database": "zixun", + "charset": "utf8mb4", + "echo": false +} diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..31a9955 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.116.1 +uvicorn[standard]==0.35.0 +pydantic==2.11.7 +SQLAlchemy==2.0.36 +PyMySQL==1.1.1 +requests==2.32.3 +beautifulsoup4==4.12.3 diff --git a/backend/run_backend.ps1 b/backend/run_backend.ps1 new file mode 100644 index 0000000..9216918 --- /dev/null +++ b/backend/run_backend.ps1 @@ -0,0 +1,7 @@ +$ErrorActionPreference = 'Stop' + +$root = Split-Path -Parent $PSScriptRoot +Set-Location $root + +python -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 3000 + diff --git a/backend/verify_backend.py b/backend/verify_backend.py new file mode 100644 index 0000000..42e46d7 --- /dev/null +++ b/backend/verify_backend.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "backend")) + +from app.main import ( # noqa: E402 + accounts, + generate_daily_report, + get_cls_news_payload, + get_daily_inputs, + get_opinion_report, + get_report_list, + health, + put_daily_inputs, +) +from app.models import DailyInputUpsertPayload # noqa: E402 +from app.services.storage import ( # noqa: E402 + CLS_NEWS_PATH, + daily_input_path, + ensure_data_dirs, + report_path, +) + + +def assert_true(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def run() -> None: + ensure_data_dirs() + + date_str = "2026-03-19" + payload = DailyInputUpsertPayload( + accounts=[ + { + "account_id": "touzi-mingjian", + "links": ["https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA"], + }, + { + "account_id": "aigujun-2020", + "links": ["https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q"], + }, + { + "account_id": "mazhiming-shouping", + "links": ["https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw"], + }, + { + "account_id": "laobai-guandian", + "links": ["https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ"], + }, + ] + ) + + saved_input = put_daily_inputs(date_str, payload) + generated_report = generate_daily_report(date_str) + loaded_input = get_daily_inputs(date_str) + loaded_report = get_opinion_report(date_str) + report_list = get_report_list() + cls_news = get_cls_news_payload() + + assert_true(health()["status"] == "ok", "health endpoint failed") + assert_true(len(accounts()) == 4, "account count should be 4") + assert_true(len(saved_input.accounts) == 4, "saved daily input should contain 4 accounts") + assert_true(len(loaded_input.accounts) == 4, "loaded daily input should contain 4 accounts") + assert_true(generated_report.article_count == 4, "generated report should contain 4 articles") + assert_true(loaded_report.article_count == 4, "loaded report should contain 4 articles") + assert_true(any(item.date == date_str for item in report_list), "report list should include target date") + assert_true(len(cls_news.items) > 0, "cls news should not be empty") + assert_true(daily_input_path(date_str).exists(), "daily input json file missing") + assert_true(report_path(date_str).exists(), "report json file missing") + assert_true(CLS_NEWS_PATH.exists(), "cls news json file missing") + + summary = { + "health": health(), + "accounts": [item.name for item in accounts()], + "daily_input_file": str(daily_input_path(date_str)), + "report_file": str(report_path(date_str)), + "cls_news_file": str(CLS_NEWS_PATH), + "daily_input_accounts": len(loaded_input.accounts), + "report_article_count": loaded_report.article_count, + "report_focus_sectors": loaded_report.focus_sectors, + "report_dates": [item.date for item in report_list[:5]], + "cls_news_items": len(cls_news.items), + } + print(json.dumps(summary, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + run() diff --git a/data/cls_news/weekly.json b/data/cls_news/weekly.json new file mode 100644 index 0000000..34c70f6 --- /dev/null +++ b/data/cls_news/weekly.json @@ -0,0 +1,202 @@ +{ + "date": "2026-03-19", + "updated_at": "2026-03-19T18:00:27+08:00", + "window_label": "当天资讯", + "summary": { + "overview": "当天资讯列表展示全部财联社资讯,不按板块裁切,方便直接查看完整新闻流。", + "hot_topics": "热点概览只保留对板块存在明显影响的方向,当前主要集中在 存储芯片、机器人、医药。", + "watch_list": [ + "存储芯片", + "机器人", + "医药", + "算力", + "AI" + ] + }, + "sector_impacts": [ + { + "sector": "存储芯片", + "sentiment": "看多", + "reason": "存储芯片 方向出现催化或景气强化,短线偏正向影响。", + "related_titles": [ + "AI Daily:CPO 与存储芯片同步走强,景气度线索延续", + "财联社7x24:存储芯片报价预期继续上修,产业链景气度受关注" + ] + }, + { + "sector": "机器人", + "sentiment": "中性", + "reason": "机器人 方向有讨论但仍需验证,短线以中性观察为主。", + "related_titles": [ + "财联社行业观察:机器人链条分化,订单兑现成为短期焦点", + "财联社7x24:机器人板块冲高回落,短线博弈情绪升温" + ] + }, + { + "sector": "医药", + "sentiment": "中性", + "reason": "医药 方向有讨论但仍需验证,短线以中性观察为主。", + "related_titles": [ + "财联社能源追踪:油气板块高位震荡,资金切向防御品种", + "财联社7x24:创新药方向持续活跃,资金转向防守与成长兼顾" + ] + }, + { + "sector": "算力", + "sentiment": "看多", + "reason": "算力 方向出现催化或景气强化,短线偏正向影响。", + "related_titles": [ + "财联社盘前精选:算力链回暖,资金重新聚焦高景气方向" + ] + } + ], + "items": [ + { + "id": "cls-1", + "title": "财联社盘前精选:算力链回暖,资金重新聚焦高景气方向", + "published_at": "2026-03-19T17:52:27+08:00", + "source": "财联社", + "summary": "隔夜市场风险偏好回升,算力与服务器链条获资金重新配置,情绪修复先于成交全面放大。", + "reference_url": "https://www.cls.cn/detail/compute-rebound", + "sectors": [ + "算力", + "AI" + ], + "sentiment": "看多" + }, + { + "id": "cls-2", + "title": "AI Daily:CPO 与存储芯片同步走强,景气度线索延续", + "published_at": "2026-03-19T16:17:27+08:00", + "source": "财联社 AI Daily", + "summary": "高速互联与存储报价预期支撑板块表现,资金更倾向于围绕确定性环节集中。", + "reference_url": "https://www.cls.cn/detail/ai-daily-cpo-memory", + "sectors": [ + "CPO", + "存储芯片" + ], + "sentiment": "看多" + }, + { + "id": "cls-3", + "title": "财联社行业观察:机器人链条分化,订单兑现成为短期焦点", + "published_at": "2026-03-19T14:42:27+08:00", + "source": "财联社", + "summary": "机器人方向内部开始出现兑现与换手,市场从概念扩散转向业绩与订单验证。", + "reference_url": "https://www.cls.cn/detail/robotics-orders", + "sectors": [ + "机器人" + ], + "sentiment": "中性" + }, + { + "id": "cls-4", + "title": "财联社能源追踪:油气板块高位震荡,资金切向防御品种", + "published_at": "2026-03-19T13:07:27+08:00", + "source": "财联社 AI Daily", + "summary": "原油价格维持高位后,油气方向出现高位震荡,部分资金转向医药等防守板块。", + "reference_url": "https://www.cls.cn/detail/energy-rotation", + "sectors": [ + "石油天然气", + "医药" + ], + "sentiment": "中性" + }, + { + "id": "cls-5", + "title": "AI Daily:汽车与智驾延续分歧,主线仍需等待销量数据验证", + "published_at": "2026-03-19T11:32:27+08:00", + "source": "财联社", + "summary": "整车与智驾方向关注度仍高,但市场对估值扩张已有保留,等待销量和订单数据确认。", + "reference_url": "https://www.cls.cn/detail/auto-data-watch", + "sectors": [ + "汽车" + ], + "sentiment": "看空" + }, + { + "id": "cls-6", + "title": "财联社7x24:半导体设备方向走强,机构称国产替代节奏提速", + "published_at": "2026-03-19T09:57:27+08:00", + "source": "财联社 AI Daily", + "summary": "晶圆制造与设备链条出现异动,市场围绕国产替代和资本开支恢复重新定价。", + "reference_url": "https://www.cls.cn/detail/semi-equipment-up", + "sectors": [ + "半导体" + ], + "sentiment": "看多" + }, + { + "id": "cls-7", + "title": "财联社7x24:券商板块午后拉升,市场情绪有所修复", + "published_at": "2026-03-19T08:22:27+08:00", + "source": "财联社", + "summary": "指数震荡过程中券商承担情绪修复功能,带动部分高弹性方向回暖。", + "reference_url": "https://www.cls.cn/detail/broker-rebound", + "sectors": [ + "券商" + ], + "sentiment": "看多" + }, + { + "id": "cls-8", + "title": "财联社7x24:创新药方向持续活跃,资金转向防守与成长兼顾", + "published_at": "2026-03-19T06:47:27+08:00", + "source": "财联社 AI Daily", + "summary": "医药板块获得增量资金关注,创新药和器械细分表现更强。", + "reference_url": "https://www.cls.cn/detail/medical-active", + "sectors": [ + "医药" + ], + "sentiment": "中性" + }, + { + "id": "cls-9", + "title": "财联社7x24:新能源链条分化加剧,机构提醒关注产能出清节奏", + "published_at": "2026-03-19T05:12:27+08:00", + "source": "财联社", + "summary": "新能源板块内部轮动明显,资金更偏向低位环节和成本改善方向。", + "reference_url": "https://www.cls.cn/detail/new-energy-split", + "sectors": [ + "新能源" + ], + "sentiment": "中性" + }, + { + "id": "cls-10", + "title": "财联社7x24:军工板块盘中异动,订单兑现预期重新升温", + "published_at": "2026-03-19T03:37:27+08:00", + "source": "财联社 AI Daily", + "summary": "军工链条盘中走强,市场关注后续订单兑现与估值切换空间。", + "reference_url": "https://www.cls.cn/detail/defense-orders", + "sectors": [ + "军工" + ], + "sentiment": "看多" + }, + { + "id": "cls-11", + "title": "财联社7x24:机器人板块冲高回落,短线博弈情绪升温", + "published_at": "2026-03-19T02:02:27+08:00", + "source": "财联社", + "summary": "机器人方向高位震荡,资金在题材扩散与兑现压力之间反复切换。", + "reference_url": "https://www.cls.cn/detail/robotics-intraday", + "sectors": [ + "机器人" + ], + "sentiment": "中性" + }, + { + "id": "cls-12", + "title": "财联社7x24:存储芯片报价预期继续上修,产业链景气度受关注", + "published_at": "2026-03-19T00:27:27+08:00", + "source": "财联社 AI Daily", + "summary": "存储环节价格修复逻辑延续,市场重新交易供需改善与盈利弹性。", + "reference_url": "https://www.cls.cn/detail/memory-price-up", + "sectors": [ + "存储芯片" + ], + "sentiment": "看多" + } + ] +} \ No newline at end of file diff --git a/data/daily_inputs/2026-03-18.json b/data/daily_inputs/2026-03-18.json new file mode 100644 index 0000000..5be1cb0 --- /dev/null +++ b/data/daily_inputs/2026-03-18.json @@ -0,0 +1,34 @@ +{ + "date": "2026-03-18", + "updated_at": "2026-03-19T16:13:50+08:00", + "accounts": [ + { + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "links": [ + "https://mp.weixin.qq.com/s/semiconductor-capacity-and-chip-cycle" + ] + }, + { + "account_id": "aigujun-2020", + "account_name": "爱股君2020", + "links": [ + "https://mp.weixin.qq.com/s/storage-chip-price-repair" + ] + }, + { + "account_id": "mazhiming-shouping", + "account_name": "马志明收评", + "links": [ + "https://mp.weixin.qq.com/s/market-close-sector-rotation" + ] + }, + { + "account_id": "laobai-guandian", + "account_name": "老白分析室观点", + "links": [ + "https://mp.weixin.qq.com/s/robotics-and-energy-balance" + ] + } + ] +} \ No newline at end of file diff --git a/data/daily_inputs/2026-03-19.json b/data/daily_inputs/2026-03-19.json new file mode 100644 index 0000000..c38d06d --- /dev/null +++ b/data/daily_inputs/2026-03-19.json @@ -0,0 +1,34 @@ +{ + "date": "2026-03-19", + "updated_at": "2026-03-19T17:12:43+08:00", + "accounts": [ + { + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "links": [ + "https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA" + ] + }, + { + "account_id": "aigujun-2020", + "account_name": "爱股君2020", + "links": [ + "https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q" + ] + }, + { + "account_id": "mazhiming-shouping", + "account_name": "马志明收评", + "links": [ + "https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw" + ] + }, + { + "account_id": "laobai-guandian", + "account_name": "老白分析室观点", + "links": [ + "https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ" + ] + } + ] +} \ No newline at end of file diff --git a/data/daily_inputs/2026-03-20.json b/data/daily_inputs/2026-03-20.json new file mode 100644 index 0000000..dd89baf --- /dev/null +++ b/data/daily_inputs/2026-03-20.json @@ -0,0 +1,28 @@ +{ + "date": "2026-03-20", + "updated_at": "2026-03-19T16:13:50+08:00", + "accounts": [ + { + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "links": [ + "https://mp.weixin.qq.com/s/custom-ai-note" + ] + }, + { + "account_id": "aigujun-2020", + "account_name": "爱股君2020", + "links": [] + }, + { + "account_id": "mazhiming-shouping", + "account_name": "马志明收评", + "links": [] + }, + { + "account_id": "laobai-guandian", + "account_name": "老白分析室观点", + "links": [] + } + ] +} \ No newline at end of file diff --git a/data/reports/2026-03-18.json b/data/reports/2026-03-18.json new file mode 100644 index 0000000..bf94922 --- /dev/null +++ b/data/reports/2026-03-18.json @@ -0,0 +1,73 @@ +{ + "date": "2026-03-18", + "generated_at": "2026-03-19T16:13:50+08:00", + "summary": "2026-03-18 共整理 4 篇公众号观点,覆盖 4 个账号。多空分歧并存,市场更看重验证与节奏,讨论重点落在 半导体、AI、券商、机器人。", + "focus_sectors": [ + "半导体", + "AI", + "券商", + "机器人" + ], + "article_count": 4, + "account_count": 4, + "articles": [ + { + "id": "2026-03-18-laobai-guandian-0", + "account_id": "laobai-guandian", + "account_name": "老白分析室观点", + "title": "老白分析室观点:Robotics / AND / Energy 观察", + "published_at": "2026-03-18T12:00:00+08:00", + "summary": "老白分析室观点:Robotics / AND / Energy 观察 围绕 机器人 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/robotics-and-energy-balance", + "sectors": [ + "机器人" + ], + "sentiment": "中性", + "article_type": "主题观点" + }, + { + "id": "2026-03-18-mazhiming-shouping-0", + "account_id": "mazhiming-shouping", + "account_name": "马志明收评", + "title": "马志明收评:Market / Close / Sector 观察", + "published_at": "2026-03-18T11:00:00+08:00", + "summary": "马志明收评:Market / Close / Sector 观察 围绕 AI、券商 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/market-close-sector-rotation", + "sectors": [ + "AI", + "券商" + ], + "sentiment": "中性", + "article_type": "市场收评" + }, + { + "id": "2026-03-18-aigujun-2020-0", + "account_id": "aigujun-2020", + "account_name": "爱股君2020", + "title": "爱股君2020:Storage / Chip / Price 观察", + "published_at": "2026-03-18T10:00:00+08:00", + "summary": "爱股君2020:Storage / Chip / Price 观察 围绕 AI、半导体 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/storage-chip-price-repair", + "sectors": [ + "AI", + "半导体" + ], + "sentiment": "中性", + "article_type": "主题观点" + }, + { + "id": "2026-03-18-touzi-mingjian-0", + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "title": "投资明见:Semiconductor / Capacity / AND 观察", + "published_at": "2026-03-18T09:00:00+08:00", + "summary": "投资明见:Semiconductor / Capacity / AND 观察 围绕 半导体 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/semiconductor-capacity-and-chip-cycle", + "sectors": [ + "半导体" + ], + "sentiment": "中性", + "article_type": "主题观点" + } + ] +} \ No newline at end of file diff --git a/data/reports/2026-03-19.json b/data/reports/2026-03-19.json new file mode 100644 index 0000000..9943bb1 --- /dev/null +++ b/data/reports/2026-03-19.json @@ -0,0 +1,75 @@ +{ + "date": "2026-03-19", + "generated_at": "2026-03-19T17:12:43+08:00", + "summary": "2026-03-19 共整理 4 篇公众号观点,覆盖 4 个账号。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI、算力、CPO、存储芯片。", + "focus_sectors": [ + "AI", + "算力", + "CPO", + "存储芯片" + ], + "article_count": 4, + "account_count": 4, + "articles": [ + { + "id": "2026-03-19-laobai-guandian-0", + "account_id": "laobai-guandian", + "account_name": "老白分析室观点", + "title": "老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察", + "published_at": "2026-03-19T12:00:00+08:00", + "summary": "老白分析室观点:Hrqoxqor8uniww3uej6 / YQ 观察 围绕 机器人、半导体 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/hRqoxqoR8UNiWw3UEj6_yQ", + "sectors": [ + "机器人", + "半导体" + ], + "sentiment": "中性", + "article_type": "主题观点" + }, + { + "id": "2026-03-19-mazhiming-shouping-0", + "account_id": "mazhiming-shouping", + "account_name": "马志明收评", + "title": "马志明收评:I0vlr02f7ydb9gvca7idtw 观察", + "published_at": "2026-03-19T11:00:00+08:00", + "summary": "马志明收评:I0vlr02f7ydb9gvca7idtw 观察 围绕 AI、券商 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/i0vlr02f7Ydb9GvCa7idTw", + "sectors": [ + "AI", + "券商" + ], + "sentiment": "中性", + "article_type": "市场收评" + }, + { + "id": "2026-03-19-aigujun-2020-0", + "account_id": "aigujun-2020", + "account_name": "爱股君2020", + "title": "爱股君2020:1no9toallxkkrjpj4wzt9q 观察", + "published_at": "2026-03-19T10:00:00+08:00", + "summary": "爱股君2020:1no9toallxkkrjpj4wzt9q 观察 围绕 CPO、存储芯片 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/1No9toallxkKRjpj4wZt9Q", + "sectors": [ + "CPO", + "存储芯片" + ], + "sentiment": "中性", + "article_type": "主题观点" + }, + { + "id": "2026-03-19-touzi-mingjian-0", + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "title": "投资明见:L429hddgfi18eojpdujja 观察", + "published_at": "2026-03-19T09:00:00+08:00", + "summary": "投资明见:L429hddgfi18eojpdujja 观察 围绕 AI、算力 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/_l429HDdGFi18eOJpDujjA", + "sectors": [ + "AI", + "算力" + ], + "sentiment": "中性", + "article_type": "主题观点" + } + ] +} \ No newline at end of file diff --git a/data/reports/2026-03-20.json b/data/reports/2026-03-20.json new file mode 100644 index 0000000..e85131f --- /dev/null +++ b/data/reports/2026-03-20.json @@ -0,0 +1,26 @@ +{ + "date": "2026-03-20", + "generated_at": "2026-03-19T16:13:50+08:00", + "summary": "2026-03-20 共整理 1 篇公众号观点,覆盖 1 个账号。多空分歧并存,市场更看重验证与节奏,讨论重点落在 AI。", + "focus_sectors": [ + "AI" + ], + "article_count": 1, + "account_count": 1, + "articles": [ + { + "id": "2026-03-20-touzi-mingjian-0", + "account_id": "touzi-mingjian", + "account_name": "投资明见", + "title": "投资明见:Custom / AI / Note 观察", + "published_at": "2026-03-20T09:00:00+08:00", + "summary": "投资明见:Custom / AI / Note 观察 围绕 AI 展开,给出的结论是 更强调结构分化与等待确认,适合作为当日盘面跟踪与复盘参考。", + "source_url": "https://mp.weixin.qq.com/s/custom-ai-note", + "sectors": [ + "AI" + ], + "sentiment": "中性", + "article_type": "主题观点" + } + ] +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..397a394 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 财经内容日报网站 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..bd39f74 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1500 @@ +{ + "name": "wechat-finance-daily-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wechat-finance-daily-frontend", + "version": "0.1.0", + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.4", + "typescript": "^5.8.2", + "vite": "^6.3.5", + "vue-tsc": "^2.2.10" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.30.tgz", + "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.30", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", + "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", + "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.30", + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", + "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.30.tgz", + "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.30.tgz", + "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", + "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/runtime-core": "3.5.30", + "@vue/shared": "3.5.30", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.30.tgz", + "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "vue": "3.5.30" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.30.tgz", + "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.30.tgz", + "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-sfc": "3.5.30", + "@vue/runtime-dom": "3.5.30", + "@vue/server-renderer": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..bf27f79 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "wechat-finance-daily-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1 --port 2000", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview --host 127.0.0.1 --port 2001" + }, + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.4", + "typescript": "^5.8.2", + "vite": "^6.3.5", + "vue-tsc": "^2.2.10" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..b8469b0 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..ccfff08 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css new file mode 100644 index 0000000..6e9c95e --- /dev/null +++ b/frontend/src/assets/main.css @@ -0,0 +1,77 @@ +:root { + color-scheme: light; + font-family: "IBM Plex Sans", "PingFang SC", "Microsoft YaHei", sans-serif; + line-height: 1.5; + font-weight: 400; + --bg-base: #f1ebe1; + --bg-soft: #f7f2ea; + --bg-panel: rgba(255, 251, 245, 0.94); + --bg-panel-strong: rgba(255, 250, 244, 0.98); + --ink-strong: #171412; + --ink-main: #2a241d; + --ink-soft: #6a6257; + --line-soft: rgba(39, 30, 20, 0.08); + --line-strong: rgba(39, 30, 20, 0.16); + --accent: #8f5a2d; + --accent-soft: rgba(143, 90, 45, 0.12); + --positive: #1b7a5e; + --negative: #9a4b45; + --neutral: #67645d; + --shadow-soft: 0 10px 28px rgba(59, 44, 27, 0.08); + --radius-xl: 32px; + --radius-lg: 24px; + --radius-md: 18px; + --radius-sm: 12px; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; +} + +body { + color: var(--ink-main); + background: linear-gradient(180deg, #f5eee4 0%, #efe5d9 100%); +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input { + font: inherit; +} + +button { + border: none; + background: none; + cursor: pointer; +} + +input { + color: inherit; +} + +.hide-scrollbar { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.hide-scrollbar::-webkit-scrollbar { + width: 0; + height: 0; +} + +#app { + min-height: 100vh; +} diff --git a/frontend/src/components/dashboard/AccountInputCard.vue b/frontend/src/components/dashboard/AccountInputCard.vue new file mode 100644 index 0000000..52fc600 --- /dev/null +++ b/frontend/src/components/dashboard/AccountInputCard.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/frontend/src/components/dashboard/AppSidebar.vue b/frontend/src/components/dashboard/AppSidebar.vue new file mode 100644 index 0000000..2a03574 --- /dev/null +++ b/frontend/src/components/dashboard/AppSidebar.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/frontend/src/components/dashboard/ClsNewsPanel.vue b/frontend/src/components/dashboard/ClsNewsPanel.vue new file mode 100644 index 0000000..612bbe1 --- /dev/null +++ b/frontend/src/components/dashboard/ClsNewsPanel.vue @@ -0,0 +1,603 @@ + + + + + diff --git a/frontend/src/components/dashboard/InputPanel.vue b/frontend/src/components/dashboard/InputPanel.vue new file mode 100644 index 0000000..6d824f7 --- /dev/null +++ b/frontend/src/components/dashboard/InputPanel.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/frontend/src/components/dashboard/OpinionPanel.vue b/frontend/src/components/dashboard/OpinionPanel.vue new file mode 100644 index 0000000..d8ba571 --- /dev/null +++ b/frontend/src/components/dashboard/OpinionPanel.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/frontend/src/composables/useFinanceDashboard.ts b/frontend/src/composables/useFinanceDashboard.ts new file mode 100644 index 0000000..d227471 --- /dev/null +++ b/frontend/src/composables/useFinanceDashboard.ts @@ -0,0 +1,269 @@ +import { computed, reactive, ref, shallowRef, watch } from 'vue' +import { ApiError, api } from '../lib/api' +import { todayIso } from '../lib/format' +import type { + Account, + ClsNewsDocument, + DailyInputAccount, + DailyInputDocument, + DashboardSection, + ReportDocument, + ReportListItem, +} from '../types' + +function buildEmptyDailyInput(date: string, accounts: Account[]): DailyInputDocument { + return { + date, + updated_at: '', + accounts: accounts.map((account) => ({ + account_id: account.id, + account_name: account.name, + links: [], + })), + } +} + +function formatError(error: unknown): string { + if (error instanceof ApiError) { + return `接口请求失败:${error.message}` + } + if (error instanceof Error) { + return error.message + } + return '出现未预期错误,请稍后再试。' +} + +function isFulfilled(result: PromiseSettledResult): result is PromiseFulfilledResult { + return result.status === 'fulfilled' +} + +export function useFinanceDashboard() { + const activeSection = shallowRef('cls') + const selectedDate = shallowRef(todayIso()) + const statusMessage = shallowRef('系统已加载,可直接查看资讯、切换日期或录入当日链接。') + const initialized = shallowRef(false) + + const accounts = ref([]) + const clsNews = ref(null) + const reportList = ref([]) + const opinionReport = ref(null) + const dailyInput = ref(null) + + const loading = reactive({ + boot: false, + news: false, + report: false, + input: false, + save: false, + generate: false, + }) + + const availableDates = computed(() => { + const dateSet = new Set(reportList.value.map((item) => item.date)) + dateSet.add(selectedDate.value) + return [...dateSet].sort((left, right) => right.localeCompare(left)) + }) + + const inputAccounts = computed(() => { + if (dailyInput.value) { + return dailyInput.value.accounts + } + return buildEmptyDailyInput(selectedDate.value, accounts.value).accounts + }) + + async function refreshStaticResources() { + const [accountData, reportData] = await Promise.all([ + api.getAccounts(), + api.getReportList(), + ]) + + accounts.value = accountData + reportList.value = reportData + } + + async function refreshDateBundle(date: string) { + const [inputResult, reportResult, newsResult] = await Promise.allSettled([ + api.getDailyInput(date), + api.getOpinionReport(date), + api.getClsNews(date), + ]) + + if (isFulfilled(inputResult)) { + dailyInput.value = inputResult.value + } + if (isFulfilled(reportResult)) { + opinionReport.value = reportResult.value + } + if (isFulfilled(newsResult)) { + clsNews.value = newsResult.value + } + + const failures = [inputResult, reportResult, newsResult].filter((result) => result.status === 'rejected') + if (failures.length > 0) { + throw failures[0].reason + } + } + + async function initializeDashboard() { + loading.boot = true + try { + await refreshStaticResources() + await refreshDateBundle(selectedDate.value) + initialized.value = true + statusMessage.value = '系统已就绪,支持查看财联社 7x24、浏览大V日报和录入链接。' + } catch (error) { + statusMessage.value = formatError(error) + } finally { + loading.boot = false + } + } + + watch( + selectedDate, + async (date, _previous, onCleanup) => { + if (!initialized.value) { + return + } + + let cancelled = false + onCleanup(() => { + cancelled = true + }) + + dailyInput.value = null + opinionReport.value = null + clsNews.value = null + loading.input = true + loading.report = true + loading.news = true + try { + const [inputResult, reportResult, newsResult] = await Promise.allSettled([ + api.getDailyInput(date), + api.getOpinionReport(date), + api.getClsNews(date), + ]) + + if (cancelled) { + return + } + + if (isFulfilled(inputResult)) { + dailyInput.value = inputResult.value + } + if (isFulfilled(reportResult)) { + opinionReport.value = reportResult.value + } + if (isFulfilled(newsResult)) { + clsNews.value = newsResult.value + } + + const failures = [inputResult, reportResult, newsResult].filter((result) => result.status === 'rejected') + if (failures.length > 0) { + statusMessage.value = formatError(failures[0].reason) + } + } catch (error) { + if (!cancelled) { + statusMessage.value = formatError(error) + } + } finally { + if (!cancelled) { + loading.input = false + loading.report = false + loading.news = false + } + } + }, + { flush: 'post' } + ) + + function setSection(section: DashboardSection) { + activeSection.value = section + } + + function setSelectedDate(date: string) { + selectedDate.value = date + } + + function updateAccountLinks(accountId: string, links: string[]) { + const current = dailyInput.value ?? buildEmptyDailyInput(selectedDate.value, accounts.value) + dailyInput.value = { + ...current, + accounts: current.accounts.map((account) => + account.account_id === accountId + ? { ...account, links } + : account + ), + } + } + + async function saveInput(): Promise { + loading.save = true + try { + const payload = { + accounts: inputAccounts.value.map((account) => ({ + account_id: account.account_id, + links: account.links, + })), + } + dailyInput.value = await api.saveDailyInput(selectedDate.value, payload) + statusMessage.value = `已保存 ${selectedDate.value} 的录入内容。` + return true + } catch (error) { + statusMessage.value = formatError(error) + return false + } finally { + loading.save = false + } + } + + async function generateReport() { + loading.generate = true + try { + const saved = await saveInput() + if (!saved) { + return + } + opinionReport.value = await api.generateOpinionReport(selectedDate.value) + reportList.value = await api.getReportList() + activeSection.value = 'opinions' + statusMessage.value = `已生成 ${selectedDate.value} 的日报,可直接查看。` + } catch (error) { + statusMessage.value = formatError(error) + } finally { + loading.generate = false + } + } + + async function refreshNews() { + loading.news = true + try { + clsNews.value = await api.refreshClsNews(selectedDate.value) + statusMessage.value = `财联社资讯已按 ${selectedDate.value} 刷新。` + } catch (error) { + statusMessage.value = formatError(error) + } finally { + loading.news = false + } + } + + return { + activeSection, + selectedDate, + statusMessage, + accounts, + clsNews, + reportList, + opinionReport, + dailyInput, + inputAccounts, + availableDates, + loading, + initializeDashboard, + setSection, + setSelectedDate, + updateAccountLinks, + saveInput, + generateReport, + refreshNews, + } +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..5f2f026 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,96 @@ +import type { + Account, + ClsNewsDocument, + DailyInputDocument, + DailyInputUpsertPayload, + ReportDocument, + ReportListItem, +} from '../types' + +const API_BASE = import.meta.env.VITE_API_BASE_URL ?? 'http://127.0.0.1:3000' + +export class ApiError extends Error { + status: number + + constructor(message: string, status: number) { + super(message) + this.name = 'ApiError' + this.status = status + } +} + +function sleep(ms: number) { + return new Promise((resolve) => { + window.setTimeout(resolve, ms) + }) +} + +async function request(path: string, init?: RequestInit): Promise { + const method = init?.method ?? 'GET' + const execute = async () => { + const response = await fetch(`${API_BASE}${path}`, { + headers: { + 'Content-Type': 'application/json', + ...(init?.headers ?? {}), + }, + ...init, + }) + + if (!response.ok) { + const text = await response.text() + throw new ApiError(text || `Request failed with status ${response.status}`, response.status) + } + + return response.json() as Promise + } + + try { + return await execute() + } catch (error) { + if (method === 'GET') { + await sleep(250) + return execute() + } + throw error + } +} + +function withDateQuery(path: string, date?: string): string { + if (!date) { + return path + } + const separator = path.includes('?') ? '&' : '?' + return `${path}${separator}date=${encodeURIComponent(date)}` +} + +export const api = { + getAccounts() { + return request('/api/accounts') + }, + getClsNews(date?: string) { + return request(withDateQuery('/api/cls-news', date)) + }, + refreshClsNews(date?: string) { + return request(withDateQuery('/api/cls-news/refresh', date), { method: 'POST' }) + }, + getDailyInput(date: string) { + return request(`/api/daily-inputs/${date}`) + }, + saveDailyInput(date: string, payload: DailyInputUpsertPayload) { + return request(`/api/daily-inputs/${date}`, { + method: 'PUT', + body: JSON.stringify(payload), + }) + }, + getOpinionReport(date: string) { + return request(`/api/opinions/${date}`) + }, + generateOpinionReport(date: string) { + return request(`/api/reports/${date}/generate`, { + method: 'POST', + }) + }, + getReportList() { + return request('/api/reports') + }, +} diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts new file mode 100644 index 0000000..7606a0e --- /dev/null +++ b/frontend/src/lib/format.ts @@ -0,0 +1,40 @@ +export function todayIso(): string { + return new Date().toLocaleDateString('en-CA') +} + +export function formatDate(dateValue: string): string { + const date = new Date(dateValue) + return new Intl.DateTimeFormat('zh-CN', { + month: 'long', + day: 'numeric', + weekday: 'short', + }).format(date) +} + +export function formatDateCompact(dateValue: string): string { + const date = new Date(dateValue) + return new Intl.DateTimeFormat('zh-CN', { + month: 'numeric', + day: 'numeric', + }).format(date) +} + +export function formatDateTime(dateValue: string): string { + const date = new Date(dateValue) + return new Intl.DateTimeFormat('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).format(date) +} + +export function formatClock(dateValue: string): string { + const date = new Date(dateValue) + return new Intl.DateTimeFormat('zh-CN', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).format(date) +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..b7ac228 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './assets/main.css' + +createApp(App).mount('#app') + diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..ae39653 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,91 @@ +export type DashboardSection = 'cls' | 'opinions' | 'input' +export type Sentiment = '看多' | '看空' | '中性' + +export interface Account { + id: string + name: string + description: string +} + +export interface DailyInputAccount { + account_id: string + account_name: string + links: string[] +} + +export interface DailyInputDocument { + date: string + updated_at: string + accounts: DailyInputAccount[] +} + +export interface DailyInputUpsertPayload { + accounts: Array<{ + account_id: string + links: string[] + }> +} + +export interface OpinionArticle { + id: string + account_id: string + account_name: string + title: string + published_at: string + summary: string + source_url: string + sectors: string[] + sentiment: Sentiment + article_type: string +} + +export interface ReportDocument { + date: string + generated_at: string + summary: string + focus_sectors: string[] + article_count: number + account_count: number + articles: OpinionArticle[] +} + +export interface ReportListItem { + date: string + generated_at: string + summary: string + article_count: number + focus_sectors: string[] +} + +export interface ClsNewsItem { + id: string + title: string + published_at: string + source: string + summary: string + reference_url: string + sectors: string[] + sentiment: Sentiment +} + +export interface ClsNewsSummary { + overview: string + hot_topics: string + watch_list: string[] +} + +export interface ClsSectorImpact { + sector: string + sentiment: Sentiment + reason: string + related_titles: string[] +} + +export interface ClsNewsDocument { + date: string + updated_at: string + window_label: string + summary: ClsNewsSummary + sector_impacts: ClsSectorImpact[] + items: ClsNewsItem[] +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..ed77210 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..14aa0dc --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "types": ["vite/client"], + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..7a4667e --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..a913a56 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) + diff --git a/微信公众号财经分析需求.md b/微信公众号财经分析需求.md new file mode 100644 index 0000000..04c05ea --- /dev/null +++ b/微信公众号财经分析需求.md @@ -0,0 +1,438 @@ +# 微信公众号财经分析网站需求文档 + +## 1. 项目定位 + +### 1.1 项目名称 + +财经内容日报网站 + +### 1.2 项目目标 + +建设一个可部署到服务器的网站,用于每天汇总财经新闻与大V观点,并生成可浏览的财经日报页面。 + +### 1.3 技术方向 + +- 前端:Vue 3 + Vite +- 后端:Python + FastAPI +- 存储:首期以 JSON 文件为主,后续可迁移 SQLite / MySQL + +### 1.4 当前版本原则 + +- 网站以“可落地、可维护、可每日使用”为第一优先级 +- 不强依赖全自动发现微信公众号最新文章 +- 大V观点模块首期采用“手动录入当天文章链接 + 系统自动抓取和分析”的模式 +- 财联社模块优先接入财联社官网或 APP 可获取的公开数据 + +## 2. 项目范围 + +网站包含 3 个核心模块: + +1. 财联社新闻 +2. 大V观点 +3. 日报录入 + +## 3. 目标数据来源 + +### 3.1 财联社新闻 + +- 财联社 +- 财联社 AI Daily + +说明: + +- 如果微信公众号端无法稳定自动获取,允许直接使用财联社官网或 APP 的公开内容作为主要数据源 +- 财联社模块目标是获取最近一周的资讯汇总,不依赖手动逐条录入 + +### 3.2 大V观点公众号 + +- 投资明见 +- 爱股君2020 +- 马志明收评 +- 老白分析室观点 + +说明: + +- 一个公众号一天可能发布多篇文章 +- 系统必须支持同一公众号在同一天录入多条链接 + +## 4. 核心业务流程 + +### 4.1 财联社新闻流程 + +1. 后端定时抓取财联社最新资讯 +2. 将资讯缓存到本地 +3. 基于最近一周数据生成汇总分析 +4. 前端展示最近一周资讯、热点板块、简要总结和参考链接 + +### 4.2 大V观点流程 + +1. 用户每天进入“日报录入”页面 +2. 选择日期 +3. 按公众号录入当天文章链接 +4. 后端抓取文章标题、发布时间、正文、摘要等内容 +5. 系统进行摘要、板块识别、情绪倾向分析 +6. 生成当日大V观点日报 +7. 前端按日期展示日报,支持切换历史日期 + +### 4.3 历史日报流程 + +1. 每天生成独立日报数据 +2. 数据按日期保存 +3. 网站支持按日期查看历史日报 + +## 5. 功能需求 + +### 5.1 模块一:财联社新闻 + +#### 5.1.1 功能目标 + +展示最近一周财联社相关新闻,并形成持续滚动更新的财经资讯模块。 + +#### 5.1.2 刷新频率 + +- 默认每 30 分钟更新一次 +- 页面支持手动立即刷新 + +#### 5.1.3 展示内容 + +每条资讯至少包含: + +- 标题 +- 发布时间 +- 来源 +- 简要摘要 +- 参考链接 +- 情绪倾向 +- 涉及板块 + +#### 5.1.4 汇总内容 + +模块顶部需要提供最近一周的简要汇总,包括: + +- 最近一周资讯概览 +- 热点方向总结 +- 当前值得关注的板块 + +#### 5.1.5 展示要求 + +- 财联社列表按时间倒序展示 +- 不需要堆砌过多统计卡片 +- 信息以内容为主,次要数据弱化展示 + +### 5.2 模块二:大V观点 + +#### 5.2.1 功能目标 + +按天展示财经类大V公众号观点,不同日期严格分开展示。 + +#### 5.2.2 日期规则 + +- 页面默认显示当天数据 +- 支持通过日期 Tab 切换历史日报 +- 两天的数据不能混在一起展示 + +#### 5.2.3 展示内容 + +每篇文章至少展示: + +- 公众号名称 +- 标题 +- 发布时间 +- 摘要 +- 参考链接 +- 涉及板块 +- 情绪倾向 + +#### 5.2.4 当日汇总 + +每个日期需要有一段“当日总结”,用于概括当天大V整体观点。 + +#### 5.2.5 页面展示取舍 + +- 板块信息仅作为辅助信息,使用小标签展示,不单独占很大篇幅 +- “摘要”和“文章列表”不要分成两块重复区域 +- 应将当日总结与文章列表放在同一信息区域内,减少重复 +- 页面尽量减少无效说明、冗余统计和不必要的装饰 + +### 5.3 模块三:日报录入 + +#### 5.3.1 功能目标 + +提供每天的录入入口,供用户录入各公众号当天文章链接。 + +#### 5.3.2 基本能力 + +- 选择录入日期 +- 按公众号录入文章链接 +- 支持新增多条链接输入框 +- 支持删除错误链接 +- 支持保存 +- 支持点击“生成日报” + +#### 5.3.3 数据规则 + +- 同一公众号同一天可录入多篇文章 +- 空链接不保存 +- 重复链接应自动去重 +- 某公众号当天未录入时,可为空 + +## 6. 抓取与分析需求 + +### 6.1 文章抓取字段 + +对于录入的微信公众号文章,后端至少提取: + +- 公众号名称 +- 文章标题 +- 发布时间 +- 摘要 +- 正文内容 +- 原始链接 +- 最终跳转链接 + +### 6.2 内容清洗 + +需要对正文做基础清洗,包括: + +- 去掉无关关注提示 +- 去掉营销尾部文案 +- 保留正文主要段落 + +### 6.3 分析输出 + +系统需要对每篇文章输出: + +- 摘要 +- 文章类型 +- 涉及板块 +- 情绪倾向 + +### 6.4 板块识别范围 + +首期可先支持以下常见板块: + +- AI +- 算力 +- CPO +- 存储芯片 +- 半导体 +- 券商 +- 石油天然气 +- 新能源 +- 军工 +- 机器人 +- 汽车 +- 医药 + +### 6.5 情绪倾向 + +首期支持以下三类: + +- 看多 +- 看空 +- 中性 + +## 7. 前端设计需求 + +## 7.1 整体设计方向 + +- 网站不是后台管理页风格,而是偏内容型、财经资讯型页面 +- 视觉上应偏专业、克制、清晰 +- 以“内容优先”而不是“大量统计卡片优先” + +### 7.2 布局要求 + +- 桌面端采用左侧导航 + 右侧内容区的结构 +- 移动端自动切换为上下布局 +- 页面尽量避免出现明显滚动条 +- 即使需要滚动,也尽量隐藏滚动条视觉表现 + +### 7.3 信息展示要求 + +- 减少无用信息 +- 弱化不重要的说明文字 +- 缩小板块信息的占比 +- 不要重复展示相同摘要 +- 避免一屏里出现太多层级相近的卡片 + +### 7.4 各模块前端表现 + +#### 财联社新闻 + +- 顶部只保留必要的刷新时间和刷新按钮 +- 使用一段简短总述说明最近一周情况 +- 热点板块用小标签展示 +- 资讯列表直接展示标题、摘要、时间、链接 + +#### 大V观点 + +- 顶部显示日期 Tab +- 当日总结与文章列表整合到同一区域 +- 板块信息使用小标签 +- 每篇文章以卡片列表展示,突出标题和摘要 + +#### 日报录入 + +- 输入页要密集但不拥挤 +- 一个公众号一张录入卡片 +- 卡片内部支持多链接输入 +- 底部保留“保存录入”和“生成日报”两个主操作 + +## 8. 后端需求 + +### 8.1 服务能力 + +后端需要提供以下核心能力: + +- 获取公众号名单 +- 获取某天的录入内容 +- 保存某天录入内容 +- 根据某天录入内容生成日报 +- 获取某天的大V观点日报 +- 获取财联社最近一周数据 +- 手动刷新财联社数据 + +### 8.2 调度能力 + +- 财联社数据支持定时刷新 +- 大V日报按需生成 +- 后续可扩展为自动定时生成日报 + +### 8.3 容错要求 + +- 某篇文章抓取失败不影响整天日报生成 +- 失败项要记录错误原因 +- 已抓取成功的数据正常保留 + +## 9. 数据存储要求 + +### 9.1 首期存储方式 + +首期使用本地文件存储,至少包含: + +- `data/daily_inputs/{date}.json` +- `data/reports/{date}.json` +- `data/cls_news/weekly.json` + +### 9.2 数据类型 + +- 每日录入数据 +- 每日报告数据 +- 财联社缓存数据 +- 抓取错误记录 + +### 9.3 后续扩展 + +后续可迁移到数据库,支持: + +- 用户体系 +- 后台管理 +- 多人协作录入 +- 更复杂的历史检索 + +## 10. 接口建议 + +建议后端至少保留以下接口: + +- `GET /api/accounts` +- `GET /api/daily-inputs/{date}` +- `PUT /api/daily-inputs/{date}` +- `POST /api/reports/{date}/generate` +- `GET /api/reports` +- `GET /api/opinions/{date}` +- `GET /api/cls-news` +- `POST /api/cls-news/refresh` + +## 11. 非功能需求 + +### 11.1 可维护性 + +- 数据抓取、分析、展示分层清晰 +- 公众号名单可配置 +- 板块词典可扩展 + +### 11.2 可部署性 + +- 支持部署到服务器 +- 前后端可独立构建 +- 支持后续接入 Nginx / Supervisor / Docker + +### 11.3 可扩展性 + +- 后续可新增更多公众号 +- 后续可增加 AI 摘要与分析能力 +- 后续可增加定时任务和消息通知 + +## 12. 当前版本边界 + +### 12.1 当前确认纳入 + +- 财联社最近一周新闻汇总 +- 大V观点按天展示 +- 每天的手动链接录入 +- 同一公众号同一天多篇文章支持 +- 财联社 30 分钟刷新 + +### 12.2 当前不作为首期刚需 + +- 全自动发现所有公众号最新文章 +- 微信公众号平台登录态自动轮询 +- 复杂权限系统 +- 多用户后台 + +## 13. MVP 交付范围 + +首期必须完成: + +- 一个可访问的网站 +- 三个核心模块可正常使用 +- 可录入当天大V文章链接 +- 可生成并查看当日大V观点日报 +- 可查看财联社最近一周资讯 +- 页面具备基础美观度与可读性 + +## 14. 验收标准 + +### 14.1 财联社模块验收 + +- 能展示最近一周财联社资讯 +- 支持 30 分钟内缓存读取 +- 支持手动刷新 +- 页面可看到摘要、链接、板块和更新时间 + +### 14.2 大V观点模块验收 + +- 可按日期切换日报 +- 不同日期内容不混合 +- 每篇文章可看到摘要与链接 +- 每天有独立总结 + +### 14.3 日报录入模块验收 + +- 可为某一天录入多个公众号链接 +- 同一公众号可录入多篇 +- 可保存后再次编辑 +- 可点击生成日报 + +### 14.4 前端验收 + +- 页面没有明显冗余信息 +- 板块信息占比明显缩小 +- 大V摘要与文章列表已整合 +- 桌面端浏览不显得拥挤,滚动条视觉弱化 + +## 15. 开发建议 + +### 15.1 首期优先级 + +1. 先打通日报录入 -> 抓取 -> 生成日报 -> 展示 +2. 再完善财联社定时刷新和最近一周缓存 +3. 最后优化页面视觉与部署结构 + +### 15.2 推荐开发顺序 + +1. 先定义数据结构 +2. 再完成 FastAPI 接口 +3. 再开发 Vue 页面 +4. 最后补调度和部署脚本 + diff --git a/微信财经新闻分析.docx b/微信财经新闻分析.docx new file mode 100644 index 0000000..6a32133 Binary files /dev/null and b/微信财经新闻分析.docx differ