@v587d/capital-generation 2.6.3 → 2.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +16 -7
- package/capital-config/client.js +8 -2
- package/capital-config/client.src.cjs +74 -1
- package/capital-config/package.json +1 -1
- package/capital-watchlist/client.js +56 -19
- package/capital-watchlist/client.src.cjs +195 -44
- package/capital-watchlist/index.js +73 -22
- package/capital-watchlist/package.json +1 -1
- package/chart-ui/package.json +1 -1
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@
|
|
|
7
7
|
- **第三位(patch)**:不破坏既有工具 / 配置 / 会话的新增与修复(如 2.1.1、2.1.2)。
|
|
8
8
|
- **第二位(minor)**:有需要用户知晓的行为变更,且段内附迁移说明(如 2.1.0 的图表呈现通道重做、2.2.0 的数据源与检索来源扩容、2.4.0 的**必须换宿主版本**——本插件自己的工具与数据契约没变,所以只抬第二位)。
|
|
9
9
|
|
|
10
|
+
## [2.6.4] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
自选股**搜索与下拉**的一轮修复加放开:**工具入参、数据能力、四把密钥与凭据引用名、配置字段、
|
|
13
|
+
会话日志格式与自选股存储 schema 一律没动**,宿主仍要求 `0.2.0-rc.2`,无需迁移。按第三位(patch)
|
|
14
|
+
口径——变更全部落在自选股面板自身的输入与展示里,不破坏既有工具 / 配置 / 会话。
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A 股搜索的大小写**(用户报"港美股/指数必须严格大写",实测根因在 A 股那一路):Fuyao
|
|
19
|
+
`ticker_search` 的 `q` 是**区分大小写的子串匹配**——实测 `tcl` 0 条 / `TCL` 3 条、`etf` 0 条 /
|
|
20
|
+
`ETF` 50 条、`600519.sh` 0 条 / `600519.SH` 1 条。所以 `q` 含 ASCII 小写时**原样 + 全大写两笔
|
|
21
|
+
并发**、按 canonical 键去重(原样那份在前);不含小写仍只发一笔,不白烧配额。全量 8708 条
|
|
22
|
+
A 股 / 指数 / ETF 名称里含小写拉丁字母的只有 8 条、且全是 `.TI` 概念指数(候选阶段就被挡掉)。
|
|
23
|
+
港美股那两路实测**不区分大小写**(`aapl` / `AAPL` / `apple` 回包逐字节相同),不跟着多问。
|
|
24
|
+
- **点一下「添加」下拉就没了**(连续添加被这一下打断):候选行用的是真 `disabled`,浏览器随即把
|
|
25
|
+
焦点踢到 body,搜索段的 `onBlur` 拿到 `relatedTarget: null` 就判"点到框外"收起下拉。改用
|
|
26
|
+
`aria-disabled`(不动焦点)+ `onClick` guard 承担"禁止点击",CSS 里的 `.option:disabled` 一并删掉
|
|
27
|
+
——留着它就是留了一条会复发的路。
|
|
28
|
+
- **"还有更多"那句不再冻结、也不再说假话**:tip 从浮层里搬进**滚动区末尾**(滚到底才看得见),
|
|
29
|
+
文案改成「没有更多,请输入准确的证券代码或名称。」——候选已经一行不落地画出来,"候选不止这些"
|
|
30
|
+
此刻是假话。判据也只剩一种 truthful 形态:某一路回满**它自己那一档**(按笔算;两笔并起来超档时
|
|
31
|
+
我们自己砍的那一刀同样算"被砍")。
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **候选池放开到 70 条、不再切片**:上游没有第二页——smartbox 每笔硬顶 10 条(8 种抬条数写法全
|
|
36
|
+
无效,`p=` / `offset=` 等 7 种分页写法回包与基线逐字节相同),Fuyao `limit` 取官方上限 50、
|
|
37
|
+
`offset` 被忽略。原先那刀"倒逼缩小输入"的 20 条切掉的全是我们本来能给的行(实测 `q=etf` 回
|
|
38
|
+
20 条全是 `.SZ`,港美一行都不露)。上界 = 三路上游硬顶之和 `50+10+10=70`,回给浏览器约 9.6 KB。
|
|
39
|
+
- **下拉顶部筛选头「全部 / CN / HK / US」**:默认全部、带各市场真实条数("怎么全是 A 股,港股美股
|
|
40
|
+
呢"的答案就在 `HK 10 / US 10` 这几个数字里);切档**只改显示、不出网、不收下拉**,空档另说
|
|
41
|
+
「这个市场下没有匹配的标的」。合并顺序仍按 A 股 → 港 → 美不变,"让用户自己筛"落在这个头上。
|
|
42
|
+
- **候选行右侧两态胶囊「添加 / 已添加」**:「添加」沿用「刷新报价」那颗 verb 按钮的配方(state 色
|
|
43
|
+
12% 淡底 + 25% 描边 + 同色字),整行唯一带色的元素;「已添加」透明底 + tertiary 字、点了不再出网。
|
|
44
|
+
类型徽标挪到代码右边(与自选股清单第一列同款排法);行的 `title` 改成**证券全称**——旧版写的是
|
|
45
|
+
「添加」,而美股长名会被截断,全称只有原生 tooltip 给得全。
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **一屏从 3 行到 5~6 行**:下拉 `max-height` 与清单那格的 `min-height` **同值** 246 → **320**
|
|
50
|
+
(不等就被卡片 `overflow: hidden` 裁掉一截,测试里有同值断言),候选行 `padding` 由 10px 14px
|
|
51
|
+
收到 6px 12px、行高约 47px。两个数是配套的:只抬浮层不压行高,多出来的空间会被行高吃掉。
|
|
52
|
+
- **加完一条不收下拉、不清输入**:`lastQuery` 不重置 ⇒ 同一个词不会被重发;在途只拦它自己那一条
|
|
53
|
+
(并发添加是安全的,服务端把清单读写串在一条队列上),`searchError` / `searchPartial` 也不擦
|
|
54
|
+
——下拉还开着,把真话擦掉就是让它失效。
|
|
55
|
+
|
|
10
56
|
## [2.6.3] - 2026-10-07
|
|
11
57
|
|
|
12
58
|
自选股并入**港股与美股**,同时补上此前整块缺失的**港美股指数点位**(面板与 Agent 用同一颗能力取数)。
|
package/README.md
CHANGED
|
@@ -15,18 +15,19 @@
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
|
|
17
17
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License"></a>
|
|
18
|
-
<a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.6.
|
|
18
|
+
<a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.6.4-9cf" alt="Version"></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
# Capital Generation
|
|
22
22
|
|
|
23
23
|
> Next-Gen AI-Driven Capital Generation.
|
|
24
24
|
|
|
25
|
-
Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/deepseek-ai/deepseek-harness) 里的证券研究插件,面向关注 A
|
|
25
|
+
Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/deepseek-ai/deepseek-harness) 里的证券研究插件,面向关注 A股/港股/美股 的个人投资者。安装后,在新会话中选择 **Capital 模式**,就可以用自然语言查行情、财务数据、公告和新闻,整理分析并生成图表。它不是独立应用,需要先安装 DSH。
|
|
26
26
|
|
|
27
27
|
**🎞️视频演示:**[安装、配置与使用](https://www.bilibili.com/video/BV1fzaZ6CE9J/)。
|
|
28
28
|
|
|
29
|
-
例如,你可以问:「复盘 XXXX 最近 20 个交易日的走势,结合公告和财务数据说明主要变化,并画一张量价图。」
|
|
29
|
+
例如,你可以问:「复盘 XXXX 最近 20 个交易日的走势,结合公告和财务数据说明主要变化,并画一张量价图。」
|
|
30
|
+
[样例:假期后研判报告(20261007)](docs/sample/假期后研判报告.md)。
|
|
30
31
|
|
|
31
32
|
> [!IMPORTANT]
|
|
32
33
|
> 本项目仍在探索中,不提供金融服务或投资建议,不保证数据完整、及时或准确,也不承诺投资回报。请自行核对来源并承担投资风险。
|
|
@@ -36,7 +37,8 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
|
|
|
36
37
|
> [!NOTE]
|
|
37
38
|
> 适配 DSH`@0.2.0-rc.2`。已在 Linux 的 Web profile 和 Windows 桌面端验证;其他环境尚未充分测试。建议使用 `Deepseek/deepseek-flash` ,思考推理能力为 High ;GPT / Claude 尚未充分测试。
|
|
38
39
|
|
|
39
|
-
1. **安装插件。**
|
|
40
|
+
1. **安装插件。**
|
|
41
|
+
Linux / macOS 的 Web 或 CLI profile 在终端执行,下面两条 **任选一条** :
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
dsh plugin --profile web add @v587d/capital-generation
|
|
@@ -44,7 +46,11 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
|
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
两条装完的功能一样:前者取 npm 上已发布的版本,后者取本仓库当前的代码。
|
|
47
|
-
安装后重启该 profile。
|
|
49
|
+
安装后重启该 profile。
|
|
50
|
+
|
|
51
|
+
DSH 桌面端在 「添加插件」 中输入:
|
|
52
|
+
`https://github.com/v587d/capital-generation` 或 `@v587d/capital-generation`
|
|
53
|
+
桌面端自带 DSH 宿主,不需要单独用 npm 安装宿主。本仓库已包含构建产物,普通用户不需要克隆仓库或运行构建命令。
|
|
48
54
|
|
|
49
55
|
2. **选填密钥。** 打开 DSH 「插件」 → **Capital Generation** -> 「配置」。
|
|
50
56
|
|
|
@@ -56,7 +62,10 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
|
|
|
56
62
|
| [PaddleOCR AIStudio](https://aistudio.baidu.com/paddleocr) | 解析 PDF 和图片中的文字 |
|
|
57
63
|
|
|
58
64
|
这些都是第三方服务的密钥,需分别向服务方申请;额度和条款以各服务方为准。
|
|
59
|
-
|
|
65
|
+
|
|
66
|
+
> [!NOTE]
|
|
67
|
+
> **本插件所有密钥均不影响插件核心功能**,即缺哪个密钥,仅表示对应能力就不可用,其他功能照常。
|
|
68
|
+
|
|
60
69
|
保存后**新建 Capital 模式会话**即可生效,无需重启。
|
|
61
70
|
也可以将密钥写入 `~/.dsh/.credentials.yaml`,名称依次为 `FUYAO_API_KEY`、`ANYSEARCH_API_KEY`、`WIND_API_KEY`、`PADDLE_OCR_TOKEN`。
|
|
62
71
|
密钥由 DSH 凭据服务保存,不会显示在会话配置中。
|
|
@@ -77,7 +86,7 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
|
|
|
77
86
|
|
|
78
87
|
## 能做什么
|
|
79
88
|
|
|
80
|
-
- **查数据和资料:** 覆盖A
|
|
89
|
+
- **查数据和资料:** 覆盖A股、港股、美股,由 Agents 按需获取行情、财务、指数、基金与宏观行业数据,检索公告、新闻和研报。共计 **94** 个数据收集能力和 **14** 个网页检索能力,部分公开数据源无需额外密钥;具体覆盖范围见 [数据收集能力表](docs/data-collector-capabilities.md) 和 [网页检索能力表](docs/web-retriever-capabilities.md)。
|
|
81
90
|
- **数据分析:** 所有 Agents 均不直接接触原始结构数据,Agents 按需读取数据、校验数据,并写脚本挖掘数据背后含义。
|
|
82
91
|
- **可视化:** 将数据交给不同角色处理,再生成可交互图表。图表会作为本轮交付物出现在会话中,可在侧栏查看或离线打开。了解处理流程可看 [Agent 角色说明](docs/agent-roles.md)。
|
|
83
92
|
- **管理自选股:** 在 Capital 会话中输入 `/` 或点击输入框旁的 `+`,从「指令」中打开「自选股」。A股、港股、美股共用这一个入口:直接敲代码或名称,候选列表会写清它属于哪个市场;清单里每一行都带市场标注,指数另标一行。某一个市场取数失败时,其余市场照常刷新,页面会说明失败的是哪一路;整体刷新失败时已有报价仍显示为旧快照,并标明时间,请勿当作实时价格。
|
package/capital-config/client.js
CHANGED
|
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var module = { exports: {} }
|
|
6
6
|
var exports = module.exports
|
|
7
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
8
|
-
"use strict";var{createElement:i,useEffect:
|
|
8
|
+
"use strict";var{createElement:i,useEffect:E,useRef:A,useState:S}=require("react"),{createPortal:R}=require("react-dom"),{SettingsForm:_,SettingsSecretField:D,SettingsFormModel:N,Switch:H,IconRightUpOutlineRegular:P}=require("@deepseek-ai/dsh-client-ui-primitives"),g="capital-config",y="@v587d/capital-generation",b="settings.capital",x="plugins.detail.badge",O="https://github.com/v587d/capital-generation",T=`https://www.npmjs.com/package/${y}`,I="FUYAO_API_KEY",U="ANYSEARCH_API_KEY",j="WIND_API_KEY",K="PADDLE_OCR_TOKEN",f={fuyao:"https://fuyao.aicubes.cn/docs/",anysearch:"https://www.anysearch.com/docs/auth",wind:"https://market.windalice.com/",paddleocr:"https://aistudio.baidu.com/paddleocr"},o=["fuyao","anysearch","wind","paddleocr"],r={fuyao:I,anysearch:U,wind:j,paddleocr:K},W=()=>Object.fromEntries(o.map(a=>[a,{ref:r[a],configured:!1,writable:!0}])),z={title:"\u914D\u7F6E",fuyaoLabel:"Fuyao API Key",fuyaoHint:"\u540C\u82B1\u987A Fuyao \u7ED3\u6784\u5316\u6570\u636E\u63A5\u53E3\u3002\u4FDD\u5B58\u540E\u65B0 Capital \u6A21\u5F0F\u4F1A\u8BDD\u751F\u6548\u3002",anysearchLabel:"AnySearch API Key",anysearchHint:"AnySearch \u7F51\u9875\u641C\u7D22 / \u63D0\u53D6\u63A5\u53E3\u3002\u4FDD\u5B58\u540E\u65B0 Capital \u6A21\u5F0F\u4F1A\u8BDD\u751F\u6548\u3002",localFetchLabel:"\u5141\u8BB8\u542F\u52A8\u672C\u5730\u63D0\u53D6\u7F51\u9875\u5185\u5BB9",localFetchHint:"\u5F00\u542F\u540E\u672C\u5730\u6293\u53D6\u515C\u5E95\uFF08\u56DE\u6267\u6765\u6E90 local-http\uFF09\u3002\u4FDD\u5B58\u540E\u65B0 Capital \u6A21\u5F0F\u4F1A\u8BDD\u751F\u6548\u3002",windLabel:"Wind Alice API Key",windHint:"Wind \u91D1\u878D\u4FE1\u62AB\u7C7B\u6587\u6863\u68C0\u7D22\u63A5\u53E3\u3002\u4FDD\u5B58\u540E\u65B0 Capital \u6A21\u5F0F\u4F1A\u8BDD\u751F\u6548\u3002",paddleocrLabel:"PaddleOCR \u6587\u6863\u89E3\u6790 Token",paddleocrHint:"\u914D\u7F6E Token \u540E\u81EA\u52A8\u89E3\u6790 PDF\u3001\u56FE\u7247\u3002\u4FDD\u5B58\u540E\u65B0 Capital \u6A21\u5F0F\u4F1A\u8BDD\u751F\u6548\u3002",openDocs:"\u5B98\u65B9\u6587\u6863",configured:"\u5DF2\u914D\u7F6E\u5BC6\u94A5\u3002",notConfigured:"\u672A\u914D\u7F6E\u5BC6\u94A5\u3002",save:"\u4FDD\u5B58",saving:"\u4FDD\u5B58\u4E2D\u2026",readOnly:"\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",saveFailed:"\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002",unavailable:"\u8BE5\u63D2\u4EF6\u5F53\u524D\u672A\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u914D\u7F6E\u3002",githubLabel:"GitHub \u4ED3\u5E93",npmLabel:"npm \u5305\u9875\u9762"},Y={title:"Configuration",fuyaoLabel:"Fuyao API Key",fuyaoHint:"Tonghuashun Fuyao structured-data API. Takes effect in new Capital mode sessions.",anysearchLabel:"AnySearch API Key",anysearchHint:"AnySearch web search / extract interface. Takes effect in new Capital mode sessions.",localFetchLabel:"Allow local web page extraction",localFetchHint:"When on, a failed fetch falls back to local direct fetching (receipt via: local-http). Takes effect in new Capital mode sessions.",windLabel:"Wind Alice API Key",windHint:"Wind financial disclosure document retrieval interface. Takes effect in new Capital mode sessions.",paddleocrLabel:"PaddleOCR document token",paddleocrHint:"Configure the token to parse PDFs and images automatically. Takes effect in new Capital mode sessions.",openDocs:"Official docs",configured:"A key is configured.",notConfigured:"No key is configured.",save:"Save",saving:"Saving\u2026",readOnly:"This deployment stores settings read-only.",saveFailed:"The deployment did not accept these values; they were left for you to correct.",unavailable:"This plugin is not loaded, so it cannot be configured right now.",githubLabel:"GitHub repository",npmLabel:"npm package page"};function h(a,e){return typeof a=="string"&&a.length>0?a:e}function M(a,e){return e==="fuyao"?h(a.value?.fuyaoCredentialRef,r.fuyao):e==="wind"?h(a.value?.retriever?.windDocs?.credentialRef,r.wind):e==="paddleocr"?h(a.value?.retriever?.paddleOcr?.credentialRef,r.paddleocr):h(a.value?.retriever?.credentialRef,r.anysearch)}function $(){if(typeof document>"u"||document.querySelector("style[data-capital-config]"))return()=>{};let a=document.createElement("style");return a.dataset.capitalConfig="true",a.textContent=`
|
|
9
9
|
.capital-config-card { display: flex; flex-direction: column; gap: 12px; }
|
|
10
10
|
.capital-config-title { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }
|
|
11
11
|
.capital-config-secret-row { display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -22,7 +22,13 @@ window.__ModuleLoader__.load({
|
|
|
22
22
|
.capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
|
|
23
23
|
.capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
24
24
|
.capital-config-write-failed { margin: 0; color: var(--dsw-alias-label-error); font-size: 12px; line-height: 18px; }
|
|
25
|
-
|
|
25
|
+
/* \u6807\u9898\u53F3\u4FA7\u7684\u4E24\u679A\u5916\u94FE\uFF1A\u5B98\u65B9\u90A3\u4E00\u683C\u662F flex-wrap + gap:8px\uFF0C\u6574\u7EC4\u81EA\u5DF1\u6536\u6210\u4E00\u4E2A inline-flex \u9879\uFF0C
|
|
26
|
+
\u514D\u5F97\u6807\u9898\u53D8\u957F\u65F6\u88AB\u6362\u884C\u62C6\u6210\u4E24\u622A\u3002\u989C\u8272\u53EA\u8D70 --dsw-* \u53D8\u91CF\uFF0C\u660E\u6697\u4E24\u5957\u90FD\u8DDF\u7740\u5BBF\u4E3B\u3002 */
|
|
27
|
+
.capital-config-plugin-links { flex: none; align-items: center; gap: 2px; display: inline-flex; }
|
|
28
|
+
.capital-config-plugin-link { border-radius: var(--dsw-radius-sm); width: 24px; height: 24px; color: var(--dsw-alias-label-tertiary); justify-content: center; align-items: center; display: inline-flex; text-decoration: none; }
|
|
29
|
+
.capital-config-plugin-link:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
30
|
+
.capital-config-plugin-link:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-brand-primary)); outline-offset: 1px; }
|
|
31
|
+
`,document.head.appendChild(a),()=>a.remove()}var w=class{constructor(e,t){this.scope=e,this.ctx=t,this.status=W(),this.localFetchWriting=!1,this.localFetchFailed=!1,this.model=new N(e,[],o.map(n=>({field:n,write:s=>this.writeKey(n,s)}))),this.store=this.model.bind(()=>this.projection()),this.unsubscribe=e.subscribe(()=>{this.refreshRefs(),this.store.set(this.projection()),this.readCredentials()}),this.refreshRefs(),this.readCredentials()}dispose(){this.unsubscribe?.(),this.model.dispose()}projection(){let e=this.scope.getSnapshot();return{...this.model.shell(),...Object.fromEntries(o.map(t=>[t,{...this.model.field(t),configured:this.status[t].configured,writable:this.status[t].writable}])),localFetch:{on:e.value?.retriever?.localFetch?.enabled!==!1,writing:this.localFetchWriting,failed:this.localFetchFailed}}}refreshRefs(){let e=this.scope.getSnapshot();for(let t of o){let n=M(e,t);this.status[t].ref!==n&&(this.status[t]={ref:n,configured:!1,writable:!0},this.model.edit(t,""))}}async readCredentials(){let e=o.map(t=>this.status[t].ref);try{let t=await this.ctx.remote.credentials.describe(e);if(!t?.ok||o.some((s,l)=>e[l]!==this.status[s].ref))return;let n=t.value??{};for(let s of o){let l=this.status[s],c=n[l.ref];c!==void 0&&(this.status[s]={ref:l.ref,configured:c.configured===!0,writable:c.writable!==!1})}this.store.set(this.projection())}catch{}}refresh(e){o.some(t=>e===this.status[t].ref)&&this.readCredentials()}async writeKey(e,t){try{return await this.ctx.remote.credentials.set(this.status[e].ref,t.trim()),await this.readCredentials(),this.status[e].configured===!0}catch{return!1}}async toggleLocalFetch(e){if(this.localFetchWriting)return;let t=this.scope.getSnapshot();if(t.status!=="ready"||!t.writable)return;this.localFetchWriting=!0,this.localFetchFailed=!1,this.store.set(this.projection());try{await this.scope.mutate([{op:"set",path:["retriever","localFetch","enabled"],value:e}])}catch{}this.localFetchWriting=!1,this.scope.getSnapshot().value?.retriever?.localFetch?.enabled===e||(this.localFetchFailed=!0),this.store.set(this.projection())}inject(){return{hooks:{capitalCard:this.store},...this.model.actions(),toggleLocalFetch:e=>this.toggleLocalFetch(e)}}};function p({field:a,label:e,hint:t,docsUrl:n,state:s,disabled:l,onEdit:c,t:d}){let m=A(null),[v,C]=S(null);E(()=>{let L=m.current,u=L?L.querySelector("label"):null;u&&u.parentElement&&C(u.parentElement)},[]);let F=i("a",{href:n,target:"_blank",rel:"noopener noreferrer","aria-label":`${e} ${d("openDocs")}`,"data-capital-config-doc":"true",className:"capital-config-doc-link"},d("openDocs"),i(P,{size:12}));return i("div",{className:"capital-config-secret-row",key:a,ref:m},i(D,{id:`capital-config-${a}-key`,label:e,hint:t,text:s.text,configured:s.configured,stateLabel:s.configured?d("configured"):d("notConfigured"),disabled:l,onEdit:c}),v?R(F,v):F)}function q(a){let e=a.t,t=a.useCapitalCard(l=>l),n=!t.writable,s=i(_,{labels:{unavailable:e("unavailable"),readOnly:e("readOnly"),saveFailed:e("saveFailed"),save:e("save"),saving:e("saving")},state:t,onSave:a.save,onDiscard:a.discard},i(p,{field:"fuyao",label:e("fuyaoLabel"),hint:e("fuyaoHint"),docsUrl:f.fuyao,state:t.fuyao,disabled:n||t.saving||!t.fuyao.writable,onEdit:l=>a.edit("fuyao",l),t:e}),i(p,{field:"anysearch",label:e("anysearchLabel"),hint:e("anysearchHint"),docsUrl:f.anysearch,state:t.anysearch,disabled:n||t.saving||!t.anysearch.writable,onEdit:l=>a.edit("anysearch",l),t:e}),i(p,{field:"wind",label:e("windLabel"),hint:e("windHint"),docsUrl:f.wind,state:t.wind,disabled:n||t.saving||!t.wind.writable,onEdit:l=>a.edit("wind",l),t:e}),i(p,{field:"paddleocr",label:e("paddleocrLabel"),hint:e("paddleocrHint"),docsUrl:f.paddleocr,state:t.paddleocr,disabled:n||t.saving||!t.paddleocr.writable,onEdit:l=>a.edit("paddleocr",l),t:e}),i("div",{className:"capital-config-field"},i("div",{className:"capital-config-switch-row"},i("div",{className:"capital-config-switch-text"},i("span",{className:"capital-config-switch-label"},e("localFetchLabel")),i("p",{className:"capital-config-hint"},e("localFetchHint"))),i(H,{label:e("localFetchLabel"),checked:t.localFetch?.on!==!1,disabled:n||t.saving||t.localFetch?.writing===!0,onChange:a.toggleLocalFetch})),t.localFetch?.failed?i("p",{role:"status",className:"capital-config-write-failed"},e("saveFailed")):null));return i("div",{className:"capital-config-card"},i("h4",{className:"capital-config-title"},e("title")),s)}var G={github:"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",npm:"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"};function k({kind:a,href:e,label:t}){return i("a",{className:"capital-config-plugin-link",href:e,target:"_blank",rel:"noopener noreferrer","aria-label":t,title:t,"data-capital-plugin-link":a},i("svg",{viewBox:"0 0 24 24",width:16,height:16,fill:"currentColor","aria-hidden":"true",focusable:"false"},i("path",{d:G[a]})))}function B({subject:a,t:e}){return a?.kind!=="bundle"||a.pkg?.name!==y?null:i("span",{className:"capital-config-plugin-links"},i(k,{kind:"github",href:O,label:e("githubLabel")}),i(k,{kind:"npm",href:T,label:e("npmLabel")}))}function V(a){a.effect($,"capital-config: styles"),a.effect(()=>a.locale.register(b,{zh:z,en:Y}),"capital-config: dictionaries");let e=new w(a.configForms.get(g),a);a.effect(()=>()=>e.dispose(),"capital-config: form subscription"),a.effect(()=>a.remote.$on("credentials/reference-updated",t=>e.refresh(t)),"capital-config: credential invalidations"),a.effect(()=>a.configForms.whileServed([g],()=>a.slots.inject("plugins.bundle.config",()=>a.slots.register({name:"plugins.bundle.config",key:y,locale:b,inject:()=>e.inject()},q))),"capital-config: page"),a.effect(()=>a.slots.inject(x,()=>a.slots.register({name:x,id:`${g}-plugin-links`,locale:b},B)),"capital-config: plugin links"),a.logger?.info?.("capital-config: settings card + \u6807\u9898\u5916\u94FE mounted")}exports.name="capital-config";exports.inject=["slots","locale","remote","remote.credentials","configForms"];exports.apply=V;
|
|
26
32
|
return module.exports
|
|
27
33
|
},
|
|
28
34
|
})
|
|
@@ -28,6 +28,21 @@ const BUNDLE_NAME = '@v587d/capital-generation'
|
|
|
28
28
|
/** 文案字典的命名空间(与配置条目 id 是两回事,按官方 settings 页的 `settings.*` 惯例)。 */
|
|
29
29
|
const LOCALE_NS = 'settings.capital'
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* 详情页标题旁那两枚外链坐的是槽 `plugins.detail.badge`(list,`scope: root`):页面把它渲染在
|
|
33
|
+
* `.titleRow` 里**官方自己画的版本 / beta / 问题标签之后**,正是标题右侧那一格。座位是 list,
|
|
34
|
+
* 注册必须带 `id`(同 id 二次注册直接抛错),且组件收到的是 owner props `subject`——
|
|
35
|
+
* 同一格在**行详情页**与**官方插件页**也会渲染,所以 `subject.kind` 与包名两道都要过,
|
|
36
|
+
* 否则会在别人的页面上挂自己的链接。
|
|
37
|
+
*
|
|
38
|
+
* 两个地址的真值各只有一处:`REPO_URL` 逐字等于根 `package.json` 的 `repository.url`(去掉
|
|
39
|
+
* `git+` 前缀与 `.git` 后缀),`NPM_URL` 由 `BUNDLE_NAME` 拼出。两条同字链由
|
|
40
|
+
* `test/capital-config.test.mjs` 钉住。
|
|
41
|
+
*/
|
|
42
|
+
const BADGE_SLOT = 'plugins.detail.badge'
|
|
43
|
+
const REPO_URL = 'https://github.com/v587d/capital-generation'
|
|
44
|
+
const NPM_URL = `https://www.npmjs.com/package/${BUNDLE_NAME}`
|
|
45
|
+
|
|
31
46
|
const FUYAO_DEFAULT_REF = 'FUYAO_API_KEY'
|
|
32
47
|
const ANYSEARCH_DEFAULT_REF = 'ANYSEARCH_API_KEY'
|
|
33
48
|
const WIND_DEFAULT_REF = 'WIND_API_KEY'
|
|
@@ -74,6 +89,8 @@ const zh = {
|
|
|
74
89
|
readOnly: '本部署的设置为只读。',
|
|
75
90
|
saveFailed: '本部署没有接受这些值,已保留供你修改。',
|
|
76
91
|
unavailable: '该插件当前未加载,暂时无法配置。',
|
|
92
|
+
githubLabel: 'GitHub 仓库',
|
|
93
|
+
npmLabel: 'npm 包页面',
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
const en = {
|
|
@@ -96,6 +113,8 @@ const en = {
|
|
|
96
113
|
readOnly: 'This deployment stores settings read-only.',
|
|
97
114
|
saveFailed: 'The deployment did not accept these values; they were left for you to correct.',
|
|
98
115
|
unavailable: 'This plugin is not loaded, so it cannot be configured right now.',
|
|
116
|
+
githubLabel: 'GitHub repository',
|
|
117
|
+
npmLabel: 'npm package page',
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
function text(value, fallback) {
|
|
@@ -130,6 +149,12 @@ function installStyles() {
|
|
|
130
149
|
.capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
|
|
131
150
|
.capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
132
151
|
.capital-config-write-failed { margin: 0; color: var(--dsw-alias-label-error); font-size: 12px; line-height: 18px; }
|
|
152
|
+
/* 标题右侧的两枚外链:官方那一格是 flex-wrap + gap:8px,整组自己收成一个 inline-flex 项,
|
|
153
|
+
免得标题变长时被换行拆成两截。颜色只走 --dsw-* 变量,明暗两套都跟着宿主。 */
|
|
154
|
+
.capital-config-plugin-links { flex: none; align-items: center; gap: 2px; display: inline-flex; }
|
|
155
|
+
.capital-config-plugin-link { border-radius: var(--dsw-radius-sm); width: 24px; height: 24px; color: var(--dsw-alias-label-tertiary); justify-content: center; align-items: center; display: inline-flex; text-decoration: none; }
|
|
156
|
+
.capital-config-plugin-link:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
157
|
+
.capital-config-plugin-link:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-brand-primary)); outline-offset: 1px; }
|
|
133
158
|
`
|
|
134
159
|
document.head.appendChild(style)
|
|
135
160
|
return () => style.remove()
|
|
@@ -365,6 +390,47 @@ function CapitalCard(props) {
|
|
|
365
390
|
)
|
|
366
391
|
}
|
|
367
392
|
|
|
393
|
+
/**
|
|
394
|
+
* 官方 primitives 的图标表里没有 GitHub / npm 字形(整表核过),所以两枚图标自带 path 数据,
|
|
395
|
+
* 取 simple-icons(CC0)。`fill="currentColor"` + 外层 `color: var(--dsw-*)` ⇒ 明暗两套都跟
|
|
396
|
+
* 宿主,功能 CSS 不写主题选择器。
|
|
397
|
+
*/
|
|
398
|
+
const GLYPHS = {
|
|
399
|
+
github: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
|
|
400
|
+
npm: 'M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z',
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function PluginLink({ kind, href, label }) {
|
|
404
|
+
return h('a', {
|
|
405
|
+
className: 'capital-config-plugin-link',
|
|
406
|
+
href,
|
|
407
|
+
target: '_blank',
|
|
408
|
+
rel: 'noopener noreferrer',
|
|
409
|
+
'aria-label': label,
|
|
410
|
+
title: label,
|
|
411
|
+
'data-capital-plugin-link': kind,
|
|
412
|
+
}, h('svg', {
|
|
413
|
+
viewBox: '0 0 24 24',
|
|
414
|
+
width: 16,
|
|
415
|
+
height: 16,
|
|
416
|
+
fill: 'currentColor',
|
|
417
|
+
'aria-hidden': 'true',
|
|
418
|
+
focusable: 'false',
|
|
419
|
+
}, h('path', { d: GLYPHS[kind] })))
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 标题右侧的外链组。同一格座位在**行详情页**与**官方插件页**也会渲染,所以 `subject.kind`
|
|
424
|
+
* 与包名两道闸门都要过;不匹配就 `return null`(list 座位的"我对这个主题没话说")。
|
|
425
|
+
*/
|
|
426
|
+
function PluginLinks({ subject, t }) {
|
|
427
|
+
if (subject?.kind !== 'bundle' || subject.pkg?.name !== BUNDLE_NAME) return null
|
|
428
|
+
return h('span', { className: 'capital-config-plugin-links' },
|
|
429
|
+
h(PluginLink, { kind: 'github', href: REPO_URL, label: t('githubLabel') }),
|
|
430
|
+
h(PluginLink, { kind: 'npm', href: NPM_URL, label: t('npmLabel') }),
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
368
434
|
function apply(ctx) {
|
|
369
435
|
ctx.effect(installStyles, 'capital-config: styles')
|
|
370
436
|
ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'capital-config: dictionaries')
|
|
@@ -380,7 +446,14 @@ function apply(ctx) {
|
|
|
380
446
|
locale: LOCALE_NS,
|
|
381
447
|
inject: () => card.inject(),
|
|
382
448
|
}, CapitalCard))), 'capital-config: page')
|
|
383
|
-
|
|
449
|
+
// 外链与配置段是同一页面的两格,但**门控不同**:链接是这个插件的属性,Host 有没有在服务
|
|
450
|
+
// `capital-config` 这条配置条目与它无关,所以不进 whileServed。
|
|
451
|
+
ctx.effect(() => ctx.slots.inject(BADGE_SLOT, () => ctx.slots.register({
|
|
452
|
+
name: BADGE_SLOT,
|
|
453
|
+
id: `${ENTRY_ID}-plugin-links`,
|
|
454
|
+
locale: LOCALE_NS,
|
|
455
|
+
}, PluginLinks)), 'capital-config: plugin links')
|
|
456
|
+
ctx.logger?.info?.('capital-config: settings card + 标题外链 mounted')
|
|
384
457
|
}
|
|
385
458
|
|
|
386
459
|
exports.name = 'capital-config'
|
|
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var module = { exports: {} }
|
|
6
6
|
var exports = module.exports
|
|
7
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
8
|
-
"use strict";var{createElement:e,useEffect:
|
|
8
|
+
"use strict";var{createElement:e,useEffect:K,useState:_}=require("react"),{createSnapshotStore:B}=require("@deepseek-ai/dsh-client-store"),{IconChecklistOutlineRegular:Y,IconCloseOutlineRegular:C,IconEllipsisOutlineRegular:W,IconGoalOutlineRegular:G,IconListPenOutlineRegular:J,IconPinOutlineRegular:V,IconRefreshOutlineRegular:X,IconSearchOutlineRegular:Z,IconTrashOutlineRegular:aa,Menu:ta,Modal:ea}=require("@deepseek-ai/dsh-client-ui-primitives"),f="capital-watchlist",v="capital.watchlist",E="conversation.input.overlay",$="/capital-watchlist",ia="capital-generation",ra=300,A="capital-watchlist-css",la={"command.label":"\u81EA\u9009\u80A1","command.description":"\u6DFB\u52A0 / \u67E5\u770B / \u5220\u9664\u81EA\u9009\u80A1",title:"\u81EA\u9009\u80A1",close:"\u5173\u95ED",searchPlaceholder:"\u8F93\u5165\u8BC1\u5238\u4EE3\u7801\u6216\u540D\u79F0\uFF0C\u652F\u6301A\u80A1/\u6E2F\u80A1/\u7F8E\u80A1",add:"\u6DFB\u52A0",added:"\u5DF2\u6DFB\u52A0",refreshing:"\u5237\u65B0\u4E2D\u2026",refresh:"\u5237\u65B0\u62A5\u4EF7",loading:"\u6B63\u5728\u8BFB\u53D6\u6E05\u5355\u2026",empty:"\u8FD8\u6CA1\u6709\u81EA\u9009\u80A1\uFF0C\u7528\u4E0A\u9762\u7684\u8F93\u5165\u6846\u6DFB\u52A0\u3002",colName:"\u540D\u79F0",colQuote:"\u6700\u65B0\u4EF7 / \u6DA8\u8DCC\u5E45",colAction:"\u64CD\u4F5C",updated:"\u66F4\u65B0\u65F6\u95F4",notRefreshed:"\u672A\u5237\u65B0",stale:"\u4E0A\u4E00\u6B21\u5FEB\u7167",exchangeTime:"\u4EA4\u6613\u6240\u65F6\u95F4",noChangePct:"\u6682\u65E0\u6DA8\u8DCC\u6570\u636E",typeAShare:"A\u80A1",typeHkStock:"\u6E2F\u80A1",typeUsStock:"\u7F8E\u80A1",typeAIndex:"A\u6307",typeHkIndex:"\u6E2F\u6307",typeUsIndex:"\u7F8E\u6307",kindStock:"\u80A1\u7968",kindIndex:"\u6307\u6570",kindEtf:"ETF",remove:"\u79FB\u9664",more:"\u66F4\u591A",pin:"\u7F6E\u9876",predict:"\u9884\u6D4B",predictTip:"AI\u9884\u6D4B\u8BE5\u8BC1\u5238\u6807\u7684",review:"\u590D\u76D8",reviewTip:"AI\u590D\u76D8\u8BE5\u8BC1\u5238\u6807\u7684",predictPromptPrefix:"\u8BF7\u9884\u6D4B ",predictPromptSuffix:"\uFF1A\u672A\u6765 5 \u4E2A\u4EA4\u6613\u65E5\u7684\u8D70\u52BF\u4E0E\u5173\u952E\u70B9\u4F4D\uFF0C\u8BF4\u660E\u4F9D\u636E\uFF0C\u5E76\u7ED9\u51FA\u5224\u65AD\u7684\u4E0D\u786E\u5B9A\u6027\u3002",reviewPromptPrefix:"\u8BF7\u590D\u76D8 ",reviewPromptSuffix:"\uFF1A\u4ECA\u65E5\u8D70\u52BF\u4E0E\u91CF\u4EF7\u3001\u8FD1 20 \u65E5\u8D8B\u52BF\u3001\u57FA\u7840\u9762\u3001\u6D88\u606F\u9762\uFF0C\u7ED9\u51FA\u7ED3\u8BBA\u548C\u5FC5\u8981\u98CE\u9669\u8BC4\u4F30\u3002",searching:"\u641C\u7D22\u4E2D\u2026",composingHint:"\u8F93\u5165\u5B8C\u6210\u540E\u81EA\u52A8\u641C\u7D22",clearInput:"\u6E05\u7A7A\u8F93\u5165",confirmTitle:"\u786E\u8BA4\u79FB\u9664\uFF1F",confirmDescPrefix:"\u786E\u5B9A\u5C06 ",confirmDescSuffix:" \u4ECE\u81EA\u9009\u4E2D\u79FB\u9664\u5417\uFF1F",confirmCancel:"\u53D6\u6D88",confirmOk:"\u786E\u8BA4\u79FB\u9664",truncated:"\u6CA1\u6709\u66F4\u591A\uFF0C\u8BF7\u8F93\u5165\u51C6\u786E\u7684\u8BC1\u5238\u4EE3\u7801\u6216\u540D\u79F0\u3002",filterAll:"\u5168\u90E8",filterLabel:"\u6309\u5E02\u573A\u7B5B\u9009\u5019\u9009",filterNoHit:"\u8FD9\u4E2A\u5E02\u573A\u4E0B\u6CA1\u6709\u5339\u914D\u7684\u6807\u7684\u3002",noHit:"\u6CA1\u6709\u5339\u914D\u7684\u6807\u7684\uFF08A\u80A1 / \u6E2F\u80A1 / \u7F8E\u80A1\uFF0C\u542B\u6307\u6570\u4E0E\u573A\u5185\u57FA\u91D1\uFF09\u3002","error.invalid_query":"\u8F93\u5165\u4E0D\u88AB\u63A5\u53D7","error.not_found":"\u6CA1\u6709\u627E\u5230\u8BE5\u6807\u7684","error.ambiguous":"\u547D\u4E2D\u591A\u6761\uFF0C\u8BF7\u4ECE\u5019\u9009\u91CC\u9009\u4E00\u6761","error.list_full":"\u81EA\u9009\u80A1\u6700\u591A {max} \u6761\uFF0C\u5148\u5220\u4E00\u6761\u518D\u52A0","error.list_full_noMax":"\u81EA\u9009\u80A1\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u5148\u5220\u4E00\u6761\u518D\u52A0","error.credential_missing":"\u672A\u914D\u7F6E Fuyao \u5BC6\u94A5\uFF1A\u5728\u300C\u63D2\u4EF6\u300D\u9875\u7684 Capital \u6A21\u5F0F\u5361\u7247\u91CC\u586B API Key\uFF08\u53EA\u5F71\u54CD A \u80A1\u90A3\u4E00\u8DEF\uFF0C\u6E2F\u7F8E\u80A1\u7167\u5E38\u7528\uFF09","error.rate_limited":"\u884C\u60C5\u670D\u52A1\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5","error.fuyao_unavailable":"A \u80A1\u884C\u60C5\u6682\u65F6\u53D6\u4E0D\u5230\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5","error.tencent_unavailable":"\u6E2F\u7F8E\u80A1\u884C\u60C5\u6682\u65F6\u53D6\u4E0D\u5230\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5","error.quote_unavailable":"\u8BE5\u6807\u7684\u6CA1\u6709\u53EF\u7528\u62A5\u4EF7","error.refresh_timeout":"\u5237\u65B0\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5","error.store_unavailable":"\u81EA\u9009\u80A1\u5B58\u50A8\u4E0D\u53EF\u7528","error.unauthorized":"\u767B\u5F55\u72B6\u6001\u5DF2\u5931\u6548\uFF1A\u8BF7\u5148\u56DE\u5230\u5BBF\u4E3B\u7A97\u53E3\u91CD\u65B0\u767B\u5F55",errorSuffix:"\uFF08\u5176\u4ED6\u6807\u7684\u4ECD\u662F\u4E0A\u4E00\u6B21\u6210\u529F\u5FEB\u7167\uFF09","partial.a-share":"A \u80A1\u68C0\u7D22\u4E0D\u53EF\u7528\uFF1A","partial.hk":"\u6E2F\u80A1\u68C0\u7D22\u4E0D\u53EF\u7528\uFF1A","partial.us":"\u7F8E\u80A1\u68C0\u7D22\u4E0D\u53EF\u7528\uFF1A"},sa={"command.label":"Watchlist","command.description":"Add / view / remove watched symbols",title:"Watchlist",close:"Close",searchPlaceholder:"Symbol code or name \u2014 A-share / HK / US",add:"Add",added:"Added",refreshing:"Refreshing\u2026",refresh:"Refresh quotes",loading:"Loading list\u2026",empty:"No symbols yet \u2014 use the input above to add one.",colName:"Symbol",colQuote:"Last / change",colAction:"Actions",updated:"Updated",notRefreshed:"Not refreshed",stale:"last snapshot",exchangeTime:"exchange time",noChangePct:"No change data yet",typeAShare:"A-Share",typeHkStock:"HK",typeUsStock:"US",typeAIndex:"A-Index",typeHkIndex:"HK-Index",typeUsIndex:"US-Index",kindStock:"stock",kindIndex:"index",kindEtf:"ETF",remove:"Remove",more:"More",pin:"Pin to top",predict:"Predict",predictTip:"AI prediction for this symbol",review:"Review",reviewTip:"AI review of this symbol",predictPromptPrefix:"Predict ",predictPromptSuffix:" over the next 5 sessions: the likely path and key levels, the evidence behind the call, and how uncertain it is.",reviewPromptPrefix:"Review ",reviewPromptSuffix:" with today's trend and volume, the last 20 sessions, fundamental, news, then a conclusion and the necessary risks analysis.",searching:"Searching\u2026",composingHint:"Search runs when you finish typing",clearInput:"Clear input",confirmTitle:"Remove this symbol?",confirmDescPrefix:"Remove ",confirmDescSuffix:" from your watchlist?",confirmCancel:"Cancel",confirmOk:"Remove",truncated:"No more candidates \u2014 enter an accurate code or name.",filterAll:"All",filterLabel:"Filter candidates by market",filterNoHit:"No matching symbol in this market.",noHit:"No matching symbol (A-share / HK / US, including indices and listed funds).","error.invalid_query":"Input not accepted","error.not_found":"Symbol not found","error.ambiguous":"Multiple hits \u2014 pick one candidate","error.list_full":"Watchlist holds at most {max} symbols \u2014 remove one first","error.list_full_noMax":"Watchlist is full \u2014 remove one first","error.credential_missing":"No Fuyao API key: set it on the Capital card in the Plugins page (A-share quotes only \u2014 HK and US keep working)","error.rate_limited":"Quote service is rate limited \u2014 try again later","error.fuyao_unavailable":"A-share quotes unavailable \u2014 try again later","error.tencent_unavailable":"HK / US quotes unavailable \u2014 try again later","error.quote_unavailable":"No quote for this symbol","error.refresh_timeout":"Refresh timed out \u2014 try again later","error.store_unavailable":"Watchlist storage unavailable","error.unauthorized":"Session expired \u2014 sign back in to the host first",errorSuffix:" (other symbols still show their last good snapshot)","partial.a-share":"A-share search unavailable: ","partial.hk":"HK search unavailable: ","partial.us":"US search unavailable: "},na={"a-share":"typeAShare","hk-stock":"typeHkStock","us-stock":"typeUsStock","a-share-index":"typeAIndex","hk-index":"typeHkIndex","us-index":"typeUsIndex","fund-etf":"typeAShare"},oa={"a-share":"kindStock","hk-stock":"kindStock","us-stock":"kindStock","a-share-index":"kindIndex","hk-index":"kindIndex","us-index":"kindIndex","fund-etf":"kindEtf"};function ca(l,t){let a=na[l];return a===void 0?String(l??""):t(a)}function da(l,t){let a=oa[l];return a===void 0?String(l??""):t(a)}var pa=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u00AD\u200B-\u200F\u2028\u2029\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/gu;function k(l){return String(l??"").replace(pa,"")}function b(l){return Array.isArray(l)?l.map(t=>t===null||typeof t!="object"?t:{...t,name:k(t.name)}):l}var ha={"a-share":"capital-watchlist-tag-cn","a-share-index":"capital-watchlist-tag-cn","fund-etf":"capital-watchlist-tag-cn","hk-stock":"capital-watchlist-tag-hk","hk-index":"capital-watchlist-tag-hk","us-stock":"capital-watchlist-tag-us","us-index":"capital-watchlist-tag-us"},R=[{id:"all"},{id:"cn",label:"CN"},{id:"hk",label:"HK"},{id:"us",label:"US"}],P=l=>l.exchange==="HK"?"hk":l.exchange==="US"?"us":"cn",ua=`
|
|
9
9
|
/* \u5BBD 420 / \u5706\u89D2 18 / \u65E0\u5185\u8FB9\u8DDD\uFF08\u5185\u8FB9\u8DDD\u4F4F\u5728\u5404\u6BB5\u81EA\u5DF1\u5934\u4E0A\uFF0C\u597D\u8BA9\u8868\u5934\u4E0E\u5206\u9694\u7EBF\u5BF9\u9F50\uFF09\u3002
|
|
10
10
|
\u9650\u9AD8\u6309\u5B98\u65B9\u53E3\u5F84\u8D70 max-height: 100%\uFF08\u5BF9 .root \u7684 padding box\uFF09\uFF0C\u4E0D\u81EA\u5DF1\u7B97 vh\u3002 */
|
|
11
11
|
.capital-watchlist-dialog.capital-watchlist-dialog { width: min(420px, 92vw); max-height: 100%; padding: 0; gap: 0; border-radius: 18px; }
|
|
@@ -36,23 +36,54 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
\u76F4\u63A5\u900F\u89C6\u4E0A\u6765\uFF08\u5B98\u65B9\u6EDA\u52A8\u6D6E\u5C42 .media \u9762\u677F\u4E0E MenuSurface .material \u90FD\u662F\u8FD9\u4E00\u5BF9\uFF09\u3002border: 0 +
|
|
37
37
|
elevation-prominent \u5206\u79BB\uFF1B\u63CF\u8FB9\u6D45\u8272\u91CD\u7ED1 l1\uFF0C\u6DF1\u8272\u7531\u5BBF\u4E3B [data-menu-material] \u89C4\u5219\u7FFB\u6210 l3\u3002 */
|
|
38
38
|
/* \u5019\u9009\u4E0B\u62C9\u662F**\u5361\u7247\u5185**\u7684\u6D6E\u5C42\uFF0C\u800C\u5361\u7247 overflow: hidden\uFF08\u5706\u89D2\u4E0E\u6E05\u5355\u6EDA\u52A8\u90FD\u9760\u5B83\uFF09\uFF1A\u6D6E\u5C42\u4E00\u65E6\u6BD4
|
|
39
|
-
\u5361\u7247\u5269\u4F59\u7684\u7A7A\u95F4\u9AD8\uFF0C\u591A\u51FA\u6765\u7684\u90E8\u5206\u5C31\u88AB\u65E0\u58F0\u88C1\u6389\
|
|
40
|
-
\
|
|
41
|
-
|
|
42
|
-
\
|
|
43
|
-
|
|
39
|
+
\u5361\u7247\u5269\u4F59\u7684\u7A7A\u95F4\u9AD8\uFF0C\u591A\u51FA\u6765\u7684\u90E8\u5206\u5C31\u88AB\u65E0\u58F0\u88C1\u6389\u3002\u6240\u4EE5\u5B83\u7684 max-height \u5FC5\u987B\u4E0E\u4E0B\u9762
|
|
40
|
+
capital-watchlist-list-open \u90A3\u683C\u7684 min-height **\u540C\u503C**\uFF08\u4E0B\u62C9\u6B63\u597D\u94FA\u6EE1\u6E05\u5355\u90A3\u4E00\u683C\uFF1A\u591F\u9AD8\uFF0C\u53C8\u538B\u4E0D\u5230
|
|
41
|
+
\u9875\u811A\uFF09\u2014\u2014246px \u2192 **320px**\uFF082026-10-08 \u7528\u6237\u70B9\u540D"\u4E00\u5C4F\u53EA\u80FD\u663E\u793A 3 \u4E2A\u6807\u7684"\uFF1A\u884C\u9AD8\u538B\u5230\u7EA6 47px\u3001
|
|
42
|
+
\u6D6E\u5C42\u62AC\u5230 320\uFF0C\u4E00\u5C4F 5~6 \u884C\uFF1B\u8FD9\u4E24\u4E2A\u6570\u4E00\u6539\u5FC5\u987B\u4E00\u8D77\u6539\uFF0C\u6D4B\u8BD5\u91CC\u6709\u540C\u503C\u65AD\u8A00\uFF09\u3002
|
|
43
|
+
\u8FD9\u4E00\u5C42\u81EA\u5DF1\u4E0D\u6EDA\uFF1A\u91CC\u9762 .capital-watchlist-options \u624D\u662F\u6EDA\u52A8\u533A\u2014\u2014\u7B5B\u9009\u5934\u9489\u5728\u9876\u4E0A\uFF08"\u9501\u5B9A\u8FD9\u4E2A\u5934"\uFF09\uFF0C
|
|
44
|
+
"\u6CA1\u6709\u66F4\u591A"\u90A3\u53E5\u4F4F\u5728**\u6EDA\u52A8\u533A\u672B\u5C3E**\uFF08\u6EDA\u5230\u5E95\u624D\u770B\u5F97\u89C1\uFF0C\u4E0D\u51BB\u7ED3\u5728\u5E95\u90E8\uFF09\uFF0C\u5176\u4F59 hint \u8D34\u5728\u9009\u9879\u4E4B\u540E\u3002
|
|
45
|
+
\u5019\u9009\u6C60\u662F 50+10+10=70 \u884C\uFF08\u4E09\u8DEF\u4E0A\u6E38\u786C\u9876\u4E4B\u548C\uFF0C\u670D\u52A1\u7AEF\u4E0D\u5207\u7247\uFF09\u3002 */
|
|
46
|
+
.capital-watchlist-dropdown { position: absolute; top: calc(100% - 8px); left: 20px; right: 20px; z-index: 20; display: flex; flex-direction: column; max-height: 320px; overflow: hidden; background: var(--dsw-specific-menu); backdrop-filter: var(--dsw-menu-backdrop-filter); border: 0; border-radius: 12px; --dsw-elevation-stroke-color: var(--dsw-alias-border-l1); box-shadow: var(--dsw-elevation-prominent); animation: capital-watchlist-drop .18s var(--ds-ease-in-out); }
|
|
47
|
+
/* \u7B5B\u9009\u5934\u4E0E\u90A3\u51E0\u53E5"\u6B64\u523B\u600E\u4E48\u4E86"\u7684 hint \u90FD\u662F flex: none\uFF0C\u53EA\u6709\u9009\u9879\u5C42 flex: 0 1 auto \u53EF\u6536\u7F29\u3001\u53EF\u6EDA\uFF1A
|
|
48
|
+
\u5934\u6C38\u8FDC\u5728\u9876\uFF0Chint \u5728\u9009\u9879\u4E4B\u540E\uFF08\u5185\u5BB9\u77ED\u65F6\u7D27\u8D34\u6700\u540E\u4E00\u884C\uFF0C\u5185\u5BB9\u957F\u65F6\u4E5F\u4E0D\u4F1A\u88AB\u6324\u6CA1\uFF09\u3002 */
|
|
49
|
+
.capital-watchlist-filter { flex: none; display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
50
|
+
.capital-watchlist-options { flex: 0 1 auto; min-height: 0; overflow-y: auto; }
|
|
51
|
+
.capital-watchlist-filterbtn { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border: none; border-radius: 999px; corner-shape: round; background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); font-family: inherit; font-size: 12px; line-height: 16px; cursor: pointer; }
|
|
52
|
+
.capital-watchlist-filterbtn:hover { background: var(--dsw-alias-interactive-bg-hover-accent); color: var(--dsw-alias-label-primary); }
|
|
53
|
+
/* \u9009\u4E2D\u6001\u8BFB aria-pressed\uFF1A\u72B6\u6001\u4F4F\u5728\u5C5E\u6027\u91CC\uFF0C\u6837\u5F0F\u53EA\u662F\u5B83\u7684\u6295\u5F71\uFF08\u8BFB\u5C4F\u4E5F\u542C\u5F97\u89C1"\u5DF2\u9009\u4E2D"\uFF09\u3002
|
|
54
|
+
\u538B\u5728\u534A\u900F\u660E\u83DC\u5355\u6750\u8D28\u4E0A\u7528 color-mix \u7684\u900F\u660E\u6DE1\u67D3\u2014\u2014\u5B83\u4E0D\u5199\u80CC\u666F\u5B9E\u5E95\uFF0C\u5E95\u4E0B\u7684\u83DC\u5355\u6A21\u7CCA\u7167\u5E38\u900F\u4E0A\u6765\u3002 */
|
|
55
|
+
.capital-watchlist-filterbtn[aria-pressed="true"] { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); color: var(--dsw-alias-label-primary); }
|
|
56
|
+
.capital-watchlist-filtercount { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }
|
|
44
57
|
@keyframes capital-watchlist-drop { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
|
|
45
58
|
@keyframes capital-watchlist-breathe { 0% { opacity: 1; } 40% { opacity: .6; } 80%, 100% { opacity: 1; } }
|
|
46
59
|
@media (prefers-reduced-motion: reduce) { .capital-watchlist-dropdown { animation: none; } .capital-watchlist-skeleton { animation: none; } }
|
|
47
|
-
|
|
60
|
+
/* \u5019\u9009\u884C\u9AD8\u5EA6\uFF082026-10-08 \u7528\u6237\u70B9\u540D"\u51CF\u5C11\u6BCF\u4E2A\u6807\u7684\u5C55\u793A\u9AD8\u5EA6"\uFF09\uFF1Apadding 6 + \u540D\u79F0 18 + \u95F4\u9699 1 + \u4EE3\u7801\u884C 15
|
|
61
|
+
\u2248 47px\u3002\u4E0E\u4E0A\u9762\u90A3\u6761"\u6D6E\u5C42 320px"\u4E00\u8D77\u7B97\uFF1A(320 \u2212 \u7B5B\u9009\u5934\u7EA6 32) \xF7 47 \u2248 6 \u884C\u4E00\u5C4F\uFF08\u6539\u524D\u662F 3 \u884C\uFF09\u3002
|
|
62
|
+
\u4E24\u4E2A\u6570\u662F\u4E00\u5BF9\u2014\u2014\u53EA\u62AC\u6D6E\u5C42\u4E0D\u538B\u884C\u9AD8\uFF0C\u591A\u51FA\u6765\u7684\u7A7A\u95F4\u4F1A\u88AB\u884C\u9AD8\u5403\u6389\uFF1B\u53EA\u538B\u884C\u9AD8\u4E0D\u62AC\u6D6E\u5C42\uFF0C\u4E00\u5C4F\u8FD8\u662F 3 \u884C\u3002 */
|
|
63
|
+
.capital-watchlist-option { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 12px; border: none; border-bottom: .5px solid var(--dsw-alias-border-l2); background: transparent; color: inherit; font-family: inherit; text-align: left; cursor: pointer; }
|
|
48
64
|
.capital-watchlist-option:last-of-type { border-bottom: none; }
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
/* \u300C\u5DF2\u6DFB\u52A0\u300D\u4E0E"\u5728\u9014"\u8FD9\u4E00\u6863\u8BFB\u7684\u662F **aria-disabled \u800C\u4E0D\u662F :disabled**\uFF08\u771F disabled \u4F1A\u628A\u7126\u70B9\u8E22\u5230
|
|
66
|
+
body\uFF0C\u641C\u7D22\u6BB5\u7684 onBlur \u5C31\u5224"\u70B9\u5230\u6846\u5916"\u6536\u8D77\u4E0B\u62C9 \u2014\u2014 \u89C1\u6E32\u67D3\u5904\u90A3\u6BB5\uFF09\uFF0C\u6240\u4EE5\u89C6\u89C9\u964D\u6863\u4E5F\u8DDF\u7740\u8BFB\u5B83\uFF1A
|
|
67
|
+
\u6574\u884C opacity .45\u3001\u6307\u9488 not-allowed\uFF0C\u7126\u70B9\u7167\u65E7\u7559\u5728\u8FD9\u4E00\u884C\u3002 */
|
|
68
|
+
.capital-watchlist-option:hover:not([aria-disabled="true"]) { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent); }
|
|
69
|
+
.capital-watchlist-option[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
|
|
51
70
|
/* \u952E\u76D8\u7126\u70B9\u8D70\u5B98\u65B9\u8868\u8FBE\u5F0F\uFF1A\u73AF\u8272\u7531\u5BBF\u4E3B\u6309\u4E3B\u9898\u4E0E\u8F93\u5165\u6A21\u6001\u7BA1\u597D\uFF08\u6307\u9488\u6A21\u6001\u81EA\u52A8\u900F\u660E\uFF09\uFF0C\u4E0D\u81EA\u5DF1\u9020\u73AF\u3002 */
|
|
52
|
-
.capital-watchlist-close:focus-visible, .capital-watchlist-clear:focus-visible, .capital-watchlist-option:focus-visible, .capital-watchlist-more:focus-visible, .capital-watchlist-predict:focus-visible, .capital-watchlist-review:focus-visible, .capital-watchlist-refresh:focus-visible, .capital-watchlist-confirmbtn:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 1px; }
|
|
53
|
-
.capital-watchlist-optionmain { display: flex; flex-direction: column; gap:
|
|
54
|
-
.capital-watchlist-optionname { font-size: 14px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
55
|
-
.capital-watchlist-optioncode { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
71
|
+
.capital-watchlist-close:focus-visible, .capital-watchlist-clear:focus-visible, .capital-watchlist-option:focus-visible, .capital-watchlist-filterbtn:focus-visible, .capital-watchlist-more:focus-visible, .capital-watchlist-predict:focus-visible, .capital-watchlist-review:focus-visible, .capital-watchlist-refresh:focus-visible, .capital-watchlist-confirmbtn:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 1px; }
|
|
72
|
+
.capital-watchlist-optionmain { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
73
|
+
.capital-watchlist-optionname { font-size: 14px; line-height: 18px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
74
|
+
.capital-watchlist-optioncode { font-size: 12px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
75
|
+
/* \u53F3\u4FA7\u90A3\u9897\u300C\u6DFB\u52A0\u300D\u662F**\u6574\u884C\u8FD9\u4E2A button \u7684\u53EF\u89C1\u52A8\u8BCD**\uFF0C\u4E0D\u662F\u7B2C\u4E8C\u4E2A\u53EF\u805A\u7126\u63A7\u4EF6\uFF08button \u91CC\u5957 button
|
|
76
|
+
\u662F\u975E\u6CD5 HTML\uFF09\uFF1A\u6240\u4EE5\u5B83\u53EA\u662F\u4E00\u9897\u80F6\u56CA\u2014\u2014\u5E38\u6001\u7070\u5E95\u3001hover \u8DDF\u7740\u6574\u884C\u52A0\u6DF1\uFF1B\u300C\u5DF2\u6DFB\u52A0\u300D\u90A3\u6863\u7531\u6574\u884C disabled
|
|
77
|
+
\u7684 opacity \u7EDF\u4E00\u964D\u4E0B\u53BB\uFF0C\u4E0D\u518D\u5F80\u4EE3\u7801\u884C\u91CC\u585E\u300C\xB7 \u5DF2\u6DFB\u52A0\u300D\uFF08\u4EE3\u7801 + \u5FBD\u6807\u624D\u662F\u7B2C\u4E8C\u884C\u8981\u8BFB\u7684\u4E24\u6837\uFF09\u3002 */
|
|
78
|
+
/* \u53F3\u4FA7\u90A3\u9897\u300C\u6DFB\u52A0\u300D\u662F**\u6574\u884C\u8FD9\u4E2A button \u7684\u53EF\u89C1\u52A8\u8BCD**\uFF0C\u4E0D\u662F\u7B2C\u4E8C\u4E2A\u53EF\u805A\u7126\u63A7\u4EF6\uFF08button \u91CC\u5957 button
|
|
79
|
+
\u662F\u975E\u6CD5 HTML\uFF09\u3002\u4E24\u6001\uFF082026-10-08 \u7B2C\u4E09\u8F6E\u7528\u6237\u70B9\u540D\uFF09\uFF1A
|
|
80
|
+
- **\u6DFB\u52A0 = \u9192\u76EE**\uFF1A\u6CBF\u7528\u300C\u5237\u65B0\u62A5\u4EF7\u300D\u90A3\u9897 verb \u6309\u94AE\u7684\u914D\u65B9\uFF08state \u8272 12% \u6DE1\u5E95 + 25% \u63CF\u8FB9 + \u540C\u8272\u5B57\uFF09\uFF0C
|
|
81
|
+
\u6574\u884C\u91CC\u552F\u4E00\u5E26\u8272\u7684\u5143\u7D20\u5C31\u662F\u5B83\uFF0C\u4E00\u773C\u77E5\u9053\u70B9\u54EA\u513F\uFF1B
|
|
82
|
+
- **\u5DF2\u6DFB\u52A0 = \u6697**\uFF1A\u900F\u660E\u5E95 + l1 \u63CF\u8FB9 + tertiary \u5B57\uFF08data-state="added"\uFF09\uFF0C\u8DDF\u6574\u884C\u7684 aria-disabled
|
|
83
|
+
\u4E00\u8D77\u964D\u4E0B\u53BB\uFF0C\u5E76\u4E14\u70B9\u4E86\u4E5F\u4E0D\u518D\u51FA\u7F51\uFF08guard \u5728\u6E32\u67D3\u5904\uFF09\u3002 */
|
|
84
|
+
.capital-watchlist-addbtn { flex: none; padding: 3px 9px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 25%, transparent); border-radius: 999px; corner-shape: round; background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent); color: var(--dsw-alias-state-business-primary); font-size: 12px; line-height: 15px; font-weight: 500; }
|
|
85
|
+
.capital-watchlist-option:hover:not([aria-disabled="true"]) .capital-watchlist-addbtn { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 20%, transparent); }
|
|
86
|
+
.capital-watchlist-addbtn[data-state="added"] { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-tertiary); font-weight: 400; }
|
|
56
87
|
.capital-watchlist-tag { flex: none; display: inline-block; padding: 2px 7px; border-radius: 6px; font-size: 11px; line-height: 14px; font-weight: 500; }
|
|
57
88
|
/* \u7C7B\u578B\u5FBD\u6807 = \u5B98\u65B9\u72B6\u6001\u5FBD\u6807\u90A3\u5BF9\u5199\u6CD5\uFF1A\u5E95\u8272\u7528 state-*-tertiary\uFF08\u4E3B\u9898\u5305\u4E3A"\u72B6\u6001\u8272\u7684\u5E95"\u51C6\u5907\u7684\u5B9E\u5FC3\u6863\uFF0C
|
|
58
89
|
\u81EA\u5DF1 color-mix \u51FA\u6765\u7684\u900F\u660E\u6863\u538B\u5728\u767D\u5361\u7247\u4E0A\u7B49\u4E8E\u6CA1\u6DF7\uFF09\uFF0C\u6587\u5B57\u7528\u540C\u8272\u7CFB primary\uFF1B
|
|
@@ -80,9 +111,13 @@ window.__ModuleLoader__.load({
|
|
|
80
111
|
\u8FD9\u4E00\u683C\u88C5\u7740\u8FD9\u4E00\u884C\u7684\u4E09\u4E2A\u52A8\u4F5C\uFF1A\u300C\u9884\u6D4B\u300D\u300C\u590D\u76D8\u300D\u4E0E\u300C\u66F4\u591A\u300D\uFF0C\u8868\u5934\u8D34\u7684\u5C31\u662F\u5B83\u4EEC\u5171\u540C\u90A3\u6761\u53F3\u7F18\u3002 */
|
|
81
112
|
.capital-watchlist-headaction { text-align: right; }
|
|
82
113
|
.capital-watchlist-list { max-height: 300px; padding: 0 12px 8px; overflow-y: auto; }
|
|
83
|
-
/* \u4E0B\u62C9\u6253\u5F00\u65F6\u628A\u8FD9\u4E00\u683C\u6491\u5230\u6D6E\u5C42\u7684\u6700\u5927\u9AD8\u5EA6\uFF08\u89C1\u4E0A\u9762 capital-watchlist-dropdown \u90A3\u4E00\u6BB5\u7684\u7406\u7531\uFF09\
|
|
84
|
-
\
|
|
85
|
-
|
|
114
|
+
/* \u4E0B\u62C9\u6253\u5F00\u65F6\u628A\u8FD9\u4E00\u683C\u6491\u5230\u6D6E\u5C42\u7684\u6700\u5927\u9AD8\u5EA6\uFF08\u89C1\u4E0A\u9762 capital-watchlist-dropdown \u90A3\u4E00\u6BB5\u7684\u7406\u7531\uFF09\uFF1A
|
|
115
|
+
**\u8FD9\u4E24\u4E2A\u6570\u5FC5\u987B\u540C\u503C**\u2014\u2014\u4E00\u4E2A\u6539\u4E86\u53E6\u4E00\u4E2A\u4E0D\u6539\uFF0C\u4E0B\u62C9\u5C31\u4F1A\u88AB\u5361\u7247\u88C1\u6389\u4E00\u622A\uFF08\u6216\u8005\u6E05\u5355\u88AB\u9876\u51FA\u5361\u7247\u5916\uFF09\u3002
|
|
116
|
+
2026-10-08 \u4E00\u8D77\u4ECE 246 \u62AC\u5230 320\uFF08\u7528\u6237\u70B9\u540D"\u4E00\u5C4F\u53EA\u80FD\u663E\u793A 3 \u4E2A\u6807\u7684"\uFF09\uFF0C\u6D4B\u8BD5\u91CC\u6709\u540C\u503C\u65AD\u8A00\u3002
|
|
117
|
+
\u53EA\u5728\u6253\u5F00\u65F6\u6491\uFF1A\u5E73\u65F6 1-2 \u884C\u7684\u77ED\u6E05\u5355\u4E0D\u8BE5\u5E73\u767D\u591A\u51FA\u4E00\u622A\u7A7A\u767D\u3002
|
|
118
|
+
\u26A0\uFE0F \u6253\u5F00\u65F6 min-height(320) > \u4E0A\u9762\u90A3\u6761 max-height(300)\uFF0C\u800C CSS \u91CC min \u6052\u80DC max \u21D2 \u8FD9\u4E00\u683C\u6B64\u65F6\u6309
|
|
119
|
+
320 \u9AD8\u3001\u5185\u90E8\u7167\u65E7 overflow-y: auto \u81EA\u5DF1\u6EDA\uFF1B\u6536\u8D77\u540E max-height 300 \u6062\u590D\u751F\u6548\u3002 */
|
|
120
|
+
.capital-watchlist-list-open { min-height: 320px; }
|
|
86
121
|
.capital-watchlist-row { padding: 11px 8px; border-radius: 10px; transition: background .15s var(--ds-ease-in-out); }
|
|
87
122
|
.capital-watchlist-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
88
123
|
.capital-watchlist-namewrap { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
@@ -146,7 +181,9 @@ window.__ModuleLoader__.load({
|
|
|
146
181
|
.capital-watchlist-refresh:disabled { opacity: .45; cursor: not-allowed; }
|
|
147
182
|
/* 34px = \u5237\u65B0\u6309\u94AE\u7684\u9AD8\u5EA6\uFF1A\u4E0E\u5B83\u540C\u4E00\u884C\u7684\u65F6\u95F4\u6309\u6309\u94AE\u9AD8\u5EA6\u884C\u9AD8\u5C45\u4E2D\uFF0C\u5DE6\u5217\u591A\u51FA\u5931\u8D25\u884C\u4E5F\u4E0D\u53D7\u5F71\u54CD\u3002 */
|
|
148
183
|
.capital-watchlist-updated { font-size: 12px; line-height: 34px; color: var(--dsw-alias-label-tertiary); }
|
|
149
|
-
|
|
184
|
+
/* flex: none\uFF1A2026-10-08 \u8D77 hint \u662F\u4E0B\u62C9\u8FD9\u4E2A flex \u5217\u7684\u76F4\u63A5\u5B50\u5143\u7D20\uFF08\u6EDA\u52A8\u533A\u53EA\u5269\u9009\u9879\u90A3\u4E00\u5C42\uFF09\uFF0C
|
|
185
|
+
\u4E0D\u9489\u4F4F\u7684\u8BDD\u7A7A\u95F4\u4E00\u7D27\u5B83\u5C31\u4F1A\u88AB\u538B\u6241\u2014\u2014hint \u6CA1\u6709 overflow\uFF0C\u538B\u6241\u5C31\u662F\u6587\u5B57\u88AB\u88C1\u3002 */
|
|
186
|
+
.capital-watchlist-hint { flex: none; padding: 8px 14px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
150
187
|
/* \u9EC4 = \u52A8\u4F5C\u6CA1\u6210\u3002\u4E24\u6761\u9EC4\u5B57\u90FD\u7528 warn-label\uFF1A\u5B98\u65B9"\u72B6\u6001\u8272\u7528\u4F5C\u6587\u5B57"\u7ED9\u7684\u662F label \u90A3\u4E00\u6863\uFF0C
|
|
151
188
|
warn-primary \u538B\u5728\u6D45\u5E95\u4E0A\u53EA\u6709 2:1\uFF0C\u62A5\u9519\u6587\u5B57\u53CD\u800C\u662F\u6700\u4E0D\u8BE5\u770B\u4E0D\u89C1\u7684\u4E00\u884C\u3002 */
|
|
152
189
|
/* \u641C\u7D22\u5931\u8D25\uFF1A\u5360\u4E0B\u62C9\u7684 hint \u4F4D\uFF08\u4E0E\u300C\u641C\u7D22\u4E2D\u2026\u300D\u300C\u6CA1\u6709\u5339\u914D\u7684\u6807\u7684\u300D\u540C\u4E00\u4E2A\u69FD\uFF0C\u66FF\u6362\u800C\u975E\u5E76\u5217\uFF09\u3002
|
|
@@ -181,8 +218,8 @@ window.__ModuleLoader__.load({
|
|
|
181
218
|
.capital-watchlist-confirmbtn:hover { background: var(--dsw-alias-interactive-bg-hover-accent); }
|
|
182
219
|
.capital-watchlist-confirmdanger { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 25%, transparent); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); color: var(--dsw-alias-state-error-primary); }
|
|
183
220
|
.capital-watchlist-confirmdanger:hover { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 25%, transparent); }
|
|
184
|
-
`;function
|
|
185
|
-
`);return await navigator.clipboard.writeText(t),t}dispose(){this.timer!==null&&clearTimeout(this.timer),this.searchController!==null&&this.searchController.abort()}};function $(r){if(typeof r!="number"||!Number.isFinite(r))return"";let t=new Date(r),a=s=>String(s).padStart(2,"0");return`${a(t.getMonth()+1)}-${a(t.getDate())} ${a(t.getHours())}:${a(t.getMinutes())}`}function E({assetType:r,t}){return e("span",{className:`capital-watchlist-tag ${la[r]??"capital-watchlist-tag-cn"}`},ea(r,t))}function oa(r,t,a){let s=y(r.name);return`${t(`${a}PromptPrefix`)}${s} (${r.thscode} \xB7 ${ia(r.asset_type,t)})${t(`${a}PromptSuffix`)}`}function ca({quote:r,failureCode:t,refreshing:a,t:s}){let i=t!==void 0;if(r==null)return a===!0?e("div",{className:"capital-watchlist-quote"},e("span",{className:"capital-watchlist-skeleton capital-watchlist-skeleton-price"}),e("span",{className:"capital-watchlist-skeleton capital-watchlist-skeleton-change"})):i?e("div",{className:"capital-watchlist-quote",title:s(`error.${t}`)},e("div",{className:"capital-watchlist-price capital-watchlist-quotefailed"},"\u2014")):e("div",{className:"capital-watchlist-quote"},e("div",{className:"capital-watchlist-price"},s("notRefreshed")));let p=typeof r.change_pct=="number"&&Number.isFinite(r.change_pct),n=p?`${r.change_pct>0?"+":""}${r.change_pct.toFixed(2)}%`:"\u2014",o=i?"capital-watchlist-stale":p?r.change_pct>0?"capital-watchlist-up":r.change_pct<0?"capital-watchlist-down":"capital-watchlist-flat":"capital-watchlist-nodata",c=typeof r.source_time=="string"&&r.source_time.length>0?`${s("exchangeTime")} ${r.source_time}`:"",d=[];return i?d.push(`${s(`error.${t}`)} \xB7 ${s("stale")} ${$(r.captured_at)}`):p||d.push(s("noChangePct")),c.length>0&&d.push(c),e("div",{className:"capital-watchlist-quote",title:d.length===0?void 0:d.join(" \xB7 ")},e("div",{className:i?"capital-watchlist-price capital-watchlist-stale":"capital-watchlist-price"},r.price.toFixed(2)),e("div",{className:`capital-watchlist-change ${o}`},n))}function pa(r,t){if(t.code!=="list_full")return r(`error.${t.code}`);let a=t.limit;return typeof a!="number"||!Number.isFinite(a)?r("error.list_full_noMax"):r("error.list_full").replace("{max}",String(a))}function da({useDialog:r,surface:t,t:a,inputActions:s}){let i=r(l=>l),[p,n]=H(!1),o=i.pendingRemove??null;if(j(()=>()=>t.dispose(),[t]),!i.open)return null;let c=i.dropdownOpen===!0,d=o!==null,u=l=>{if(l.key==="Escape"){if(d){l.stopPropagation(),t.setPendingRemove(null);return}c&&(l.stopPropagation(),t.hideDropdown())}},P=l=>{if(!c)return;let h=l.relatedTarget;h!=null&&l.currentTarget?.contains?.(h)===!0||t.hideDropdown()},w=i.error,A=w==null?null:e("p",{className:"capital-watchlist-notice",role:"alert","data-code":w.code},`${a(`error.${w.code}`)}${w.partial===!0?a("errorSuffix"):""}`),v=(l,h)=>{s.insertText(oa(l,a,h),s.captureInsertion())===!0&&t.close()},I=c?e("div",{className:"capital-watchlist-dropdown",role:"listbox","aria-label":a("title"),"data-menu-material":"translucent"},i.candidates.map(l=>e("button",{key:l.thscode,type:"button",role:"option",className:"capital-watchlist-option",title:l.in_list===!0?a("added"):a("add"),disabled:l.in_list===!0||p,onClick:()=>{n(!0),t.add(l).finally(()=>n(!1))}},e("span",{className:"capital-watchlist-optionmain"},e("span",{className:"capital-watchlist-optionname"},l.name),e("span",{className:"capital-watchlist-optioncode"},l.in_list===!0?`${l.thscode} \xB7 ${a("added")}`:l.thscode)),e(E,{assetType:l.asset_type,t:a}))),i.composing===!0?e("div",{className:"capital-watchlist-hint"},a("composingHint")):i.searching===!0||i.pendingSearch===!0?e("div",{className:"capital-watchlist-hint"},a("searching")):i.searchError!==null&&i.searchError!==void 0?e("div",{className:"capital-watchlist-hint capital-watchlist-searcherror",role:"alert","data-code":i.searchError.code},a(`error.${i.searchError.code}`)):i.candidates.length===0?e("div",{className:"capital-watchlist-hint"},a("noHit")):null,...(i.searchPartial??[]).map(l=>e("div",{key:`partial-${l.market}`,className:"capital-watchlist-hint capital-watchlist-searcherror",role:"status","data-market":l.market,"data-code":l.code},`${a(`partial.${l.market}`)}${a(`error.${l.code}`)}`)),i.truncated?e("div",{className:"capital-watchlist-hint"},a("truncated")):null):null,R=i.items.length===0?null:e("div",{className:"capital-watchlist-grid capital-watchlist-head"},e("span",null,a("colName")),e("span",{className:"capital-watchlist-quote"},a("colQuote")),e("span",{className:"capital-watchlist-headaction"},a("colAction"))),T=i.loadError!==null&&i.loadError!==void 0?e("p",{className:"capital-watchlist-empty capital-watchlist-loaderror",role:"alert","data-code":i.loadError.code},a(`error.${i.loadError.code}`)):e("p",{className:"capital-watchlist-empty"},a("empty")),z=i.loading?e("p",{className:"capital-watchlist-empty"},a("loading")):i.items.length===0?T:e("div",{className:c?"capital-watchlist-list capital-watchlist-list-open":"capital-watchlist-list"},i.items.map((l,h)=>e("div",{key:l.thscode,className:"capital-watchlist-grid capital-watchlist-row","data-thscode":l.thscode},e("span",{className:"capital-watchlist-namewrap"},e("span",{className:"capital-watchlist-name",title:`${l.name} ${l.ticker}`},l.name),e("span",{className:"capital-watchlist-codeline"},e("span",{className:"capital-watchlist-code"},l.ticker),e(E,{assetType:l.asset_type,t:a}))),e(ca,{quote:l.quote,failureCode:i.failures[l.thscode],refreshing:i.refreshing,t:a}),e("span",{className:"capital-watchlist-rowactions"},e("button",{type:"button",className:"capital-watchlist-predict",disabled:p,title:a("predictTip"),"aria-label":`${a("predict")} ${l.name}`,onClick:()=>v(l,"predict")},e(F,{size:14})),e("button",{type:"button",className:"capital-watchlist-review",disabled:p,title:a("reviewTip"),"aria-label":`${a("review")} ${l.name}`,onClick:()=>v(l,"review")},e(Q,{size:14})),e(W,{open:i.rowMenu===l.thscode,anchor:e("button",{type:"button",className:"capital-watchlist-more",disabled:p,title:a("more"),"aria-label":`${a("more")} ${l.name}`,"aria-haspopup":"menu","aria-expanded":i.rowMenu===l.thscode,onClick:()=>t.toggleRowMenu(l.thscode)},e(q,{size:14})),items:[{id:"pin",label:a("pin"),icon:e(L,{size:14}),disabled:h===0},{id:"remove",label:a("remove"),icon:e(Y,{size:14}),danger:!0}],onSelect:O=>{t.closeRowMenu(),O==="pin"?t.pin(l.thscode):t.setPendingRemove(l)},onClose:()=>t.closeRowMenu(),align:"end",portal:!0,className:"capital-watchlist-actions"}))))),M=e("div",{className:d?"capital-watchlist-confirm capital-watchlist-confirm-open":"capital-watchlist-confirm","aria-hidden":d?void 0:"true"},e("div",{className:"capital-watchlist-confirmbox",role:d?"alertdialog":void 0,"aria-label":a("confirmTitle")},e("div",{className:"capital-watchlist-confirmtitle"},a("confirmTitle")),d?e("p",{className:"capital-watchlist-confirmdesc"},a("confirmDescPrefix"),e("strong",null,`${o.name} \xB7 ${o.ticker}`),a("confirmDescSuffix")):null,e("div",{className:"capital-watchlist-confirmactions"},e("button",{type:"button",className:"capital-watchlist-confirmbtn",onClick:()=>t.setPendingRemove(null)},a("confirmCancel")),e("button",{type:"button",className:"capital-watchlist-confirmbtn capital-watchlist-confirmdanger",onClick:()=>{let l=o;t.setPendingRemove(null),l!==null&&t.remove(l.thscode)}},a("confirmOk")))));return e(G,{open:!0,onClose:()=>t.close(),title:a("title"),headless:!0,shortcutModal:"watchlist",className:"capital-watchlist-dialog"},e("div",{className:"capital-watchlist-card",onKeyDown:u},e("div",{className:"capital-watchlist-header"},e("h2",{className:"capital-watchlist-title"},a("title")),e("button",{type:"button",className:"capital-watchlist-close","aria-label":a("close"),onClick:()=>t.close()},e(k,{size:14}))),e("div",{className:"capital-watchlist-search",onBlur:P},e("div",{className:"capital-watchlist-field"},e("span",{className:"capital-watchlist-searchicon"},e(B,{size:16})),e("input",{className:"capital-watchlist-input","data-modal-autofocus":!0,value:i.query,placeholder:a("searchPlaceholder"),onChange:l=>t.onQueryChange(l.target.value,l.isComposing),onCompositionStart:()=>t.beginComposition(),onCompositionEnd:l=>t.endComposition(l.target.value)}),i.query.length>0?e("button",{type:"button",className:"capital-watchlist-clear",title:a("clearInput"),"aria-label":a("clearInput"),onMouseDown:l=>l.preventDefault(),onClick:()=>t.onQueryChange("",!1)},e(k,{size:12})):null),I),i.addError!==null&&i.addError!==void 0?e("p",{className:"capital-watchlist-adderror",role:"status","data-code":i.addError.code},pa(a,i.addError)):null,R,z,e("div",{className:"capital-watchlist-footer"},e("div",{className:"capital-watchlist-footcol"},e("button",{type:"button",className:"capital-watchlist-refresh",disabled:i.refreshing||p,onClick:()=>{t.refresh()}},e(K,{size:14}),i.refreshing?a("refreshing"):a("refresh")),A),e("span",{className:"capital-watchlist-updated"},`${a("updated")} ${i.refreshed_at===null||i.refreshed_at===void 0?a("notRefreshed"):$(i.refreshed_at)}`)),M))}function ha(r){r.effect(na,`${f}: styles`),r.effect(()=>r.locale.register(b,{zh:X,en:Z}),`${f}: dictionaries`);let t=new Map,a=n=>{let o=t.get(n);return o===void 0&&(o=new x,t.set(n,o)),o};r.effect(()=>()=>{for(let n of t.values())n.dispose();t.clear()},`${f}: per-session surfaces`);let s=n=>{let c=r.sessions?.list?.getSnapshot?.().byId?.[n]?.projectionValues?.agentPreset;return typeof c=="string"?c:void 0},i=new Set,p=n=>{let o=s(n);return o!==void 0?o===J:(i.has(n)||(i.add(n),Promise.resolve(r.sessions?.refreshProjections?.(n)).catch(()=>{})),!1)};r.slots.inject(S,()=>r.slots.register({name:S,id:"capital-watchlist-dialog",order:3,locale:b,inject:n=>{let o=a(n);return{hooks:{dialog:o.state},surface:o}}},da)),r.inject(["commandUi"],n=>{let o=r.locale.bind(b);n.effect(()=>n.commandUi.register({name:"watchlist",label:()=>o("command.label"),description:()=>o("command.description"),icon:D,available:c=>p(c.sessionId),ui:{kind:"action",run:c=>{a(c.sessionId).open()}}}),`${f}: /watchlist contribution`)}),r.logger?.info?.(`${f}: \u5BA2\u6237\u7AEF\u534A\u8FB9\u5DF2\u6302\u8F7D\uFF08/watchlist \u83DC\u5355\u884C + \u81EA\u9009\u80A1\u5F39\u7A97\uFF09`)}exports.name=f;exports.inject=["slots","locale","commandUi","sessions"];exports.apply=ha;
|
|
221
|
+
`;function ma(){let l=globalThis.document;if(!l||typeof l.createElement!="function"||l.querySelector(`style[data-${A}]`))return;let t=l.createElement("style");t.setAttribute(`data-${A}`,""),t.textContent=ua;let a=l.head??l.body;if(!(a===void 0||typeof a.appendChild!="function"))return a.appendChild(t),()=>t.remove()}async function m(l,{query:t,body:a,signal:s}={}){let i=t!==void 0?`${$}${l}?${t}`:`${$}${l}`,d=a===void 0?{method:"GET",cache:"no-store",credentials:"same-origin",signal:s}:{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(a),credentials:"same-origin",signal:s},n=await fetch(i,d),o=await n.text(),c={};try{c=o.length>0?JSON.parse(o):{}}catch{c={}}if(n.ok===!0&&c.ok===!0)return c;let p=typeof c.code=="string"&&c.code.length>0?c.code:n.status===401||n.status===403?"unauthorized":"fuyao_unavailable",h=new Error(p);throw h.code=p,h.candidates=c.candidates,h.limit=c.limit,h}var I=()=>({open:!1,loading:!1,refreshing:!1,searching:!1,items:[],candidates:[],truncated:!1,marketFilter:"all",query:"",dropdownOpen:!1,rowMenu:null,pendingRemove:null,refreshed_at:null,error:null,searchError:null,searchPartial:null,addError:null,loadError:null,failures:{},composing:!1,pendingSearch:!1}),y=class{constructor(){this.store=B(I()),this.searchController=null,this.timer=null,this.pending=null,this.lastQuery=null}get state(){return this.store}get snapshot(){return this.store.getSnapshot()}patch(t){this.store.update(a=>Object.assign(a,t))}open(){this.patch({...I(),open:!0,loading:!0}),this.load().then(()=>this.refresh(),()=>this.patch({loading:!1}))}close(){this.timer!==null&&clearTimeout(this.timer),this.searchController!==null&&this.searchController.abort(),this.lastQuery=null,this.patch({open:!1,rowMenu:null,composing:!1,pendingSearch:!1})}async load(){try{let t=await m("/list");this.patch({items:b(t.items)??[],seeded_at:t.seeded_at,loading:!1,loadError:null})}catch(t){throw this.patch({loading:!1,loadError:{code:t.code??"fuyao_unavailable"}}),t}}refresh(){if(this.pending!==null)return this.pending;this.patch({refreshing:!0});let t=m("/refresh",{body:{}}).then(a=>{let s={};for(let d of a.failures??[])s[d.thscode]=d.code;let i=b(a.items)??this.snapshot.items;this.patch({items:i,refreshed_at:a.refreshed_at??this.snapshot.refreshed_at,failures:s,error:a.error?{code:a.error.code,partial:i.length>0&&i.some(d=>s[d.thscode]===void 0)}:null})}).catch(a=>{this.patch({error:{code:a.code??"fuyao_unavailable",partial:this.snapshot.items.some(s=>s.quote!==null)}})}).finally(()=>{this.patch({refreshing:!1}),this.pending=null});return this.pending=t,t}onQueryChange(t,a){let s=t.trim(),i=a===void 0?this.snapshot.composing===!0:a===!0;if(this.patch({query:t,searchError:null,composing:i,dropdownOpen:s.length>0}),this.timer!==null&&clearTimeout(this.timer),s.length===0){this.searchController!==null&&this.searchController.abort(),this.lastQuery=null,this.patch({candidates:[],truncated:!1,searching:!1,pendingSearch:!1});return}s===this.lastQuery&&this.snapshot.pendingSearch===!1||(this.patch({candidates:[],truncated:!1,pendingSearch:!i}),i!==!0&&(this.timer=setTimeout(()=>this.search(s),ra)))}beginComposition(){this.timer!==null&&clearTimeout(this.timer),this.patch({composing:!0})}endComposition(t){this.onQueryChange(t,!1)}hideDropdown(){this.patch({dropdownOpen:!1})}setMarketFilter(t){R.some(a=>a.id===t)&&this.patch({marketFilter:t})}toggleRowMenu(t){this.patch({rowMenu:this.snapshot.rowMenu===t?null:t})}closeRowMenu(){this.snapshot.rowMenu!==null&&this.patch({rowMenu:null})}setPendingRemove(t){this.patch({pendingRemove:t??null})}async search(t){this.lastQuery=t,this.searchController!==null&&this.searchController.abort();let a=new AbortController;this.searchController=a,this.patch({searching:!0,searchError:null,searchPartial:null});let s=()=>this.snapshot.query.trim()===t;try{let i=await m("/search",{query:`q=${encodeURIComponent(t)}`,signal:a.signal});if(!s()){this.patch({searching:!1});return}this.patch({candidates:b(i.items)??[],truncated:i.truncated===!0,searching:!1,pendingSearch:!1,searchPartial:Array.isArray(i.partial)&&i.partial.length>0?i.partial:null})}catch(i){if(i.name==="AbortError"||(this.lastQuery===t&&(this.lastQuery=null),!s()))return;this.patch({candidates:[],truncated:!1,searching:!1,pendingSearch:!1,searchError:{code:i.code??"fuyao_unavailable"},searchPartial:null})}}async add(t){try{await m("/add",{body:{thscode:t.thscode}})}catch(s){this.patch({addError:{code:s.code??"fuyao_unavailable",candidates:s.candidates,limit:s.limit}}),s.candidates!==void 0&&this.patch({candidates:s.candidates,truncated:!1});return}this.patch({addError:null,candidates:this.snapshot.candidates.map(s=>s.thscode===t.thscode?{...s,in_list:!0}:s)}),await this.load().catch(()=>{}),await this.refresh();let a=this.snapshot.items.find(s=>s.thscode===t.thscode);a!==void 0&&a.quote===null&&this.snapshot.failures[t.thscode]===void 0&&await this.refresh()}async remove(t){let a=this.snapshot.items;this.patch({items:a.filter(s=>s.thscode!==t),rowMenu:null});try{await m("/remove",{body:{thscode:t}}),this.snapshot.addError?.code==="list_full"&&this.patch({addError:null})}catch{this.patch({items:a})}}async pin(t){let a=this.snapshot.items,s=a.find(i=>i.thscode===t);if(s!==void 0){this.patch({items:[s,...a.filter(i=>i.thscode!==t)],rowMenu:null});try{let i=await m("/pin",{body:{thscode:t}});Array.isArray(i.items)&&this.patch({items:b(i.items)})}catch{this.patch({items:a})}}}async copyToClipboard(){let t=this.snapshot.items.map(a=>[a.thscode,k(a.name),a.asset_type,a.quote?.price??"",a.quote?.change_pct??"",a.quote?.captured_at??""].join(" ")).join(`
|
|
222
|
+
`);return await navigator.clipboard.writeText(t),t}dispose(){this.timer!==null&&clearTimeout(this.timer),this.searchController!==null&&this.searchController.abort()}};function z(l){if(typeof l!="number"||!Number.isFinite(l))return"";let t=new Date(l),a=s=>String(s).padStart(2,"0");return`${a(t.getMonth()+1)}-${a(t.getDate())} ${a(t.getHours())}:${a(t.getMinutes())}`}function T({assetType:l,t}){return e("span",{className:`capital-watchlist-tag ${ha[l]??"capital-watchlist-tag-cn"}`},ca(l,t))}function fa(l,t,a){let s=k(l.name);return`${t(`${a}PromptPrefix`)}${s} (${l.thscode} \xB7 ${da(l.asset_type,t)})${t(`${a}PromptSuffix`)}`}function wa({quote:l,failureCode:t,refreshing:a,t:s}){let i=t!==void 0;if(l==null)return a===!0?e("div",{className:"capital-watchlist-quote"},e("span",{className:"capital-watchlist-skeleton capital-watchlist-skeleton-price"}),e("span",{className:"capital-watchlist-skeleton capital-watchlist-skeleton-change"})):i?e("div",{className:"capital-watchlist-quote",title:s(`error.${t}`)},e("div",{className:"capital-watchlist-price capital-watchlist-quotefailed"},"\u2014")):e("div",{className:"capital-watchlist-quote"},e("div",{className:"capital-watchlist-price"},s("notRefreshed")));let d=typeof l.change_pct=="number"&&Number.isFinite(l.change_pct),n=d?`${l.change_pct>0?"+":""}${l.change_pct.toFixed(2)}%`:"\u2014",o=i?"capital-watchlist-stale":d?l.change_pct>0?"capital-watchlist-up":l.change_pct<0?"capital-watchlist-down":"capital-watchlist-flat":"capital-watchlist-nodata",c=typeof l.source_time=="string"&&l.source_time.length>0?`${s("exchangeTime")} ${l.source_time}`:"",p=[];return i?p.push(`${s(`error.${t}`)} \xB7 ${s("stale")} ${z(l.captured_at)}`):d||p.push(s("noChangePct")),c.length>0&&p.push(c),e("div",{className:"capital-watchlist-quote",title:p.length===0?void 0:p.join(" \xB7 ")},e("div",{className:i?"capital-watchlist-price capital-watchlist-stale":"capital-watchlist-price"},l.price.toFixed(2)),e("div",{className:`capital-watchlist-change ${o}`},n))}function ga(l,t){if(t.code!=="list_full")return l(`error.${t.code}`);let a=t.limit;return typeof a!="number"||!Number.isFinite(a)?l("error.list_full_noMax"):l("error.list_full").replace("{max}",String(a))}function ba({useDialog:l,surface:t,t:a,inputActions:s}){let i=l(r=>r),[d,n]=_(!1),[o,c]=_(null),p=i.pendingRemove??null;if(K(()=>()=>t.dispose(),[t]),!i.open)return null;let h=i.dropdownOpen===!0,w=p!==null,M=r=>{if(r.key==="Escape"){if(w){r.stopPropagation(),t.setPendingRemove(null);return}h&&(r.stopPropagation(),t.hideDropdown())}},H=r=>{if(!h)return;let u=r.relatedTarget;u!=null&&r.currentTarget?.contains?.(u)===!0||t.hideDropdown()},g=i.error,O=g==null?null:e("p",{className:"capital-watchlist-notice",role:"alert","data-code":g.code},`${a(`error.${g.code}`)}${g.partial===!0?a("errorSuffix"):""}`),N=(r,u)=>{s.insertText(fa(r,a,u),s.captureInsertion())===!0&&t.close()},x=i.marketFilter==="all"?i.candidates:i.candidates.filter(r=>P(r)===i.marketFilter),S={all:i.candidates.length,cn:0,hk:0,us:0};for(let r of i.candidates)S[P(r)]+=1;let F=i.candidates.length===0?null:e("div",{className:"capital-watchlist-filter",role:"group","aria-label":a("filterLabel")},R.map(r=>e("button",{key:r.id,type:"button",className:"capital-watchlist-filterbtn","aria-pressed":i.marketFilter===r.id,onClick:()=>t.setMarketFilter(r.id)},e("span",{className:"capital-watchlist-filterlabel"},r.id==="all"?a("filterAll"):r.label),e("span",{className:"capital-watchlist-filtercount"},String(S[r.id]))))),j=h?e("div",{className:"capital-watchlist-dropdown","aria-label":a("title"),"data-menu-material":"translucent"},F,e("div",{className:"capital-watchlist-options",role:"listbox","aria-label":a("title")},x.map(r=>e("button",{key:r.thscode,type:"button",role:"option",className:"capital-watchlist-option",title:r.name,"aria-disabled":r.in_list===!0||o===r.thscode,onClick:()=>{r.in_list===!0||o===r.thscode||(n(!0),c(r.thscode),t.add(r).finally(()=>{n(!1),c(null)}))}},e("span",{className:"capital-watchlist-optionmain"},e("span",{className:"capital-watchlist-optionname"},r.name),e("span",{className:"capital-watchlist-codeline"},e("span",{className:"capital-watchlist-optioncode"},r.thscode),e(T,{assetType:r.asset_type,t:a}))),e("span",{className:"capital-watchlist-addbtn","data-state":r.in_list===!0?"added":"idle"},r.in_list===!0?a("added"):a("add")))),i.truncated===!0&&x.length>0?e("div",{className:"capital-watchlist-hint capital-watchlist-endhint"},a("truncated")):null),i.composing===!0?e("div",{className:"capital-watchlist-hint"},a("composingHint")):i.searching===!0||i.pendingSearch===!0?e("div",{className:"capital-watchlist-hint"},a("searching")):i.searchError!==null&&i.searchError!==void 0?e("div",{className:"capital-watchlist-hint capital-watchlist-searcherror",role:"alert","data-code":i.searchError.code},a(`error.${i.searchError.code}`)):i.candidates.length===0?e("div",{className:"capital-watchlist-hint"},a("noHit")):x.length===0?e("div",{className:"capital-watchlist-hint","data-filter":i.marketFilter},a("filterNoHit")):null,...(i.searchPartial??[]).map(r=>e("div",{key:`partial-${r.market}`,className:"capital-watchlist-hint capital-watchlist-searcherror",role:"status","data-market":r.market,"data-code":r.code},`${a(`partial.${r.market}`)}${a(`error.${r.code}`)}`))):null,U=i.items.length===0?null:e("div",{className:"capital-watchlist-grid capital-watchlist-head"},e("span",null,a("colName")),e("span",{className:"capital-watchlist-quote"},a("colQuote")),e("span",{className:"capital-watchlist-headaction"},a("colAction"))),D=i.loadError!==null&&i.loadError!==void 0?e("p",{className:"capital-watchlist-empty capital-watchlist-loaderror",role:"alert","data-code":i.loadError.code},a(`error.${i.loadError.code}`)):e("p",{className:"capital-watchlist-empty"},a("empty")),L=i.loading?e("p",{className:"capital-watchlist-empty"},a("loading")):i.items.length===0?D:e("div",{className:h?"capital-watchlist-list capital-watchlist-list-open":"capital-watchlist-list"},i.items.map((r,u)=>e("div",{key:r.thscode,className:"capital-watchlist-grid capital-watchlist-row","data-thscode":r.thscode},e("span",{className:"capital-watchlist-namewrap"},e("span",{className:"capital-watchlist-name",title:`${r.name} ${r.ticker}`},r.name),e("span",{className:"capital-watchlist-codeline"},e("span",{className:"capital-watchlist-code"},r.ticker),e(T,{assetType:r.asset_type,t:a}))),e(wa,{quote:r.quote,failureCode:i.failures[r.thscode],refreshing:i.refreshing,t:a}),e("span",{className:"capital-watchlist-rowactions"},e("button",{type:"button",className:"capital-watchlist-predict",disabled:d,title:a("predictTip"),"aria-label":`${a("predict")} ${r.name}`,onClick:()=>N(r,"predict")},e(G,{size:14})),e("button",{type:"button",className:"capital-watchlist-review",disabled:d,title:a("reviewTip"),"aria-label":`${a("review")} ${r.name}`,onClick:()=>N(r,"review")},e(J,{size:14})),e(ta,{open:i.rowMenu===r.thscode,anchor:e("button",{type:"button",className:"capital-watchlist-more",disabled:d,title:a("more"),"aria-label":`${a("more")} ${r.name}`,"aria-haspopup":"menu","aria-expanded":i.rowMenu===r.thscode,onClick:()=>t.toggleRowMenu(r.thscode)},e(W,{size:14})),items:[{id:"pin",label:a("pin"),icon:e(V,{size:14}),disabled:u===0},{id:"remove",label:a("remove"),icon:e(aa,{size:14}),danger:!0}],onSelect:Q=>{t.closeRowMenu(),Q==="pin"?t.pin(r.thscode):t.setPendingRemove(r)},onClose:()=>t.closeRowMenu(),align:"end",portal:!0,className:"capital-watchlist-actions"}))))),q=e("div",{className:w?"capital-watchlist-confirm capital-watchlist-confirm-open":"capital-watchlist-confirm","aria-hidden":w?void 0:"true"},e("div",{className:"capital-watchlist-confirmbox",role:w?"alertdialog":void 0,"aria-label":a("confirmTitle")},e("div",{className:"capital-watchlist-confirmtitle"},a("confirmTitle")),w?e("p",{className:"capital-watchlist-confirmdesc"},a("confirmDescPrefix"),e("strong",null,`${p.name} \xB7 ${p.ticker}`),a("confirmDescSuffix")):null,e("div",{className:"capital-watchlist-confirmactions"},e("button",{type:"button",className:"capital-watchlist-confirmbtn",onClick:()=>t.setPendingRemove(null)},a("confirmCancel")),e("button",{type:"button",className:"capital-watchlist-confirmbtn capital-watchlist-confirmdanger",onClick:()=>{let r=p;t.setPendingRemove(null),r!==null&&t.remove(r.thscode)}},a("confirmOk")))));return e(ea,{open:!0,onClose:()=>t.close(),title:a("title"),headless:!0,shortcutModal:"watchlist",className:"capital-watchlist-dialog"},e("div",{className:"capital-watchlist-card",onKeyDown:M},e("div",{className:"capital-watchlist-header"},e("h2",{className:"capital-watchlist-title"},a("title")),e("button",{type:"button",className:"capital-watchlist-close","aria-label":a("close"),onClick:()=>t.close()},e(C,{size:14}))),e("div",{className:"capital-watchlist-search",onBlur:H},e("div",{className:"capital-watchlist-field"},e("span",{className:"capital-watchlist-searchicon"},e(Z,{size:16})),e("input",{className:"capital-watchlist-input","data-modal-autofocus":!0,value:i.query,placeholder:a("searchPlaceholder"),onChange:r=>t.onQueryChange(r.target.value,r.isComposing),onCompositionStart:()=>t.beginComposition(),onCompositionEnd:r=>t.endComposition(r.target.value)}),i.query.length>0?e("button",{type:"button",className:"capital-watchlist-clear",title:a("clearInput"),"aria-label":a("clearInput"),onMouseDown:r=>r.preventDefault(),onClick:()=>t.onQueryChange("",!1)},e(C,{size:12})):null),j),i.addError!==null&&i.addError!==void 0?e("p",{className:"capital-watchlist-adderror",role:"status","data-code":i.addError.code},ga(a,i.addError)):null,U,L,e("div",{className:"capital-watchlist-footer"},e("div",{className:"capital-watchlist-footcol"},e("button",{type:"button",className:"capital-watchlist-refresh",disabled:i.refreshing||d,onClick:()=>{t.refresh()}},e(X,{size:14}),i.refreshing?a("refreshing"):a("refresh")),O),e("span",{className:"capital-watchlist-updated"},`${a("updated")} ${i.refreshed_at===null||i.refreshed_at===void 0?a("notRefreshed"):z(i.refreshed_at)}`)),q))}function xa(l){l.effect(ma,`${f}: styles`),l.effect(()=>l.locale.register(v,{zh:la,en:sa}),`${f}: dictionaries`);let t=new Map,a=n=>{let o=t.get(n);return o===void 0&&(o=new y,t.set(n,o)),o};l.effect(()=>()=>{for(let n of t.values())n.dispose();t.clear()},`${f}: per-session surfaces`);let s=n=>{let c=l.sessions?.list?.getSnapshot?.().byId?.[n]?.projectionValues?.agentPreset;return typeof c=="string"?c:void 0},i=new Set,d=n=>{let o=s(n);return o!==void 0?o===ia:(i.has(n)||(i.add(n),Promise.resolve(l.sessions?.refreshProjections?.(n)).catch(()=>{})),!1)};l.slots.inject(E,()=>l.slots.register({name:E,id:"capital-watchlist-dialog",order:3,locale:v,inject:n=>{let o=a(n);return{hooks:{dialog:o.state},surface:o}}},ba)),l.inject(["commandUi"],n=>{let o=l.locale.bind(v);n.effect(()=>n.commandUi.register({name:"watchlist",label:()=>o("command.label"),description:()=>o("command.description"),icon:Y,available:c=>d(c.sessionId),ui:{kind:"action",run:c=>{a(c.sessionId).open()}}}),`${f}: /watchlist contribution`)}),l.logger?.info?.(`${f}: \u5BA2\u6237\u7AEF\u534A\u8FB9\u5DF2\u6302\u8F7D\uFF08/watchlist \u83DC\u5355\u884C + \u81EA\u9009\u80A1\u5F39\u7A97\uFF09`)}exports.name=f;exports.inject=["slots","locale","commandUi","sessions"];exports.apply=xa;
|
|
186
223
|
return module.exports
|
|
187
224
|
},
|
|
188
225
|
})
|
|
@@ -131,9 +131,21 @@ const zh = {
|
|
|
131
131
|
confirmDescSuffix: ' 从自选中移除吗?',
|
|
132
132
|
confirmCancel: '取消',
|
|
133
133
|
confirmOk: '确认移除',
|
|
134
|
-
/**
|
|
135
|
-
*
|
|
136
|
-
|
|
134
|
+
/**
|
|
135
|
+
* `truncated`:候选已经**一行不落地全画出来了**(服务端不切片),所以这句里没有"候选不止这些"
|
|
136
|
+
* ——那半句说的是"还有没显示的",此刻是假话。2026-10-08 用户改写成"没有更多":它讲的是
|
|
137
|
+
* **上游那一路回满了自己那一档**(A 股 50 / 港 10 / 美 10),而它自己就住在滚动区末尾,
|
|
138
|
+
* 滚到底才看得见(不做成钉在底部的常驻条)。也**故意不写 N**:数字的上游是宿主常量,
|
|
139
|
+
* 抄进两套字典就迟早只改红一边(`error.list_full` 那条教训)。
|
|
140
|
+
*/
|
|
141
|
+
truncated: '没有更多,请输入准确的证券代码或名称。',
|
|
142
|
+
/**
|
|
143
|
+
* 候选下拉的筛选头(全部 / CN / HK / US,默认全部):**只在客户端筛已经拿全的候选**,
|
|
144
|
+
* 不重新出网、也不改变上游入参(`asset_type` 白名单那条纪律讲的是入参收窄,不是这里的显示筛选)。
|
|
145
|
+
*/
|
|
146
|
+
filterAll: '全部',
|
|
147
|
+
filterLabel: '按市场筛选候选',
|
|
148
|
+
filterNoHit: '这个市场下没有匹配的标的。',
|
|
137
149
|
noHit: '没有匹配的标的(A股 / 港股 / 美股,含指数与场内基金)。',
|
|
138
150
|
'error.invalid_query': '输入不被接受',
|
|
139
151
|
'error.not_found': '没有找到该标的',
|
|
@@ -214,7 +226,10 @@ const en = {
|
|
|
214
226
|
confirmDescSuffix: ' from your watchlist?',
|
|
215
227
|
confirmCancel: 'Cancel',
|
|
216
228
|
confirmOk: 'Remove',
|
|
217
|
-
truncated: '
|
|
229
|
+
truncated: 'No more candidates — enter an accurate code or name.',
|
|
230
|
+
filterAll: 'All',
|
|
231
|
+
filterLabel: 'Filter candidates by market',
|
|
232
|
+
filterNoHit: 'No matching symbol in this market.',
|
|
218
233
|
noHit: 'No matching symbol (A-share / HK / US, including indices and listed funds).',
|
|
219
234
|
'error.invalid_query': 'Input not accepted',
|
|
220
235
|
'error.not_found': 'Symbol not found',
|
|
@@ -316,6 +331,16 @@ function cleanRows(rows) {
|
|
|
316
331
|
'us-index': 'capital-watchlist-tag-us',
|
|
317
332
|
}
|
|
318
333
|
|
|
334
|
+
/**
|
|
335
|
+
* 候选筛选头的四档(2026-10-08 用户点名:**默认「全部」**,三市都摆出来让用户自己筛)。
|
|
336
|
+
* 只在客户端筛**已经拿全的候选**:A 股 50 + 港 10 + 美 10 一次回包,切换不重新出网,
|
|
337
|
+
* 也不碰上游入参(`asset_type` 白名单那条纪律讲的是入参收窄,与这里的显示筛选是两件事)。
|
|
338
|
+
* `marketIdOf` 是这一层唯一的判据:候选的 `exchange` 存的是**市场**(`SH`/`SZ`/`HK`/`US`,
|
|
339
|
+
* 美股的具体交易所留在 canonical 代码里),所以 A 股那一档由"不是 HK 也不是 US"给出。
|
|
340
|
+
*/
|
|
341
|
+
const MARKET_FILTERS = [{ id: 'all' }, { id: 'cn', label: 'CN' }, { id: 'hk', label: 'HK' }, { id: 'us', label: 'US' }]
|
|
342
|
+
const marketIdOf = (candidate) => (candidate.exchange === 'HK' ? 'hk' : candidate.exchange === 'US' ? 'us' : 'cn')
|
|
343
|
+
|
|
319
344
|
/**
|
|
320
345
|
* 面板几何。
|
|
321
346
|
*
|
|
@@ -380,23 +405,54 @@ const CSS = `
|
|
|
380
405
|
直接透视上来(官方滚动浮层 .media 面板与 MenuSurface .material 都是这一对)。border: 0 +
|
|
381
406
|
elevation-prominent 分离;描边浅色重绑 l1,深色由宿主 [data-menu-material] 规则翻成 l3。 */
|
|
382
407
|
/* 候选下拉是**卡片内**的浮层,而卡片 overflow: hidden(圆角与清单滚动都靠它):浮层一旦比
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
.capital-watchlist-
|
|
408
|
+
卡片剩余的空间高,多出来的部分就被无声裁掉。所以它的 max-height 必须与下面
|
|
409
|
+
capital-watchlist-list-open 那格的 min-height **同值**(下拉正好铺满清单那一格:够高,又压不到
|
|
410
|
+
页脚)——246px → **320px**(2026-10-08 用户点名"一屏只能显示 3 个标的":行高压到约 47px、
|
|
411
|
+
浮层抬到 320,一屏 5~6 行;这两个数一改必须一起改,测试里有同值断言)。
|
|
412
|
+
这一层自己不滚:里面 .capital-watchlist-options 才是滚动区——筛选头钉在顶上("锁定这个头"),
|
|
413
|
+
"没有更多"那句住在**滚动区末尾**(滚到底才看得见,不冻结在底部),其余 hint 贴在选项之后。
|
|
414
|
+
候选池是 50+10+10=70 行(三路上游硬顶之和,服务端不切片)。 */
|
|
415
|
+
.capital-watchlist-dropdown { position: absolute; top: calc(100% - 8px); left: 20px; right: 20px; z-index: 20; display: flex; flex-direction: column; max-height: 320px; overflow: hidden; background: var(--dsw-specific-menu); backdrop-filter: var(--dsw-menu-backdrop-filter); border: 0; border-radius: 12px; --dsw-elevation-stroke-color: var(--dsw-alias-border-l1); box-shadow: var(--dsw-elevation-prominent); animation: capital-watchlist-drop .18s var(--ds-ease-in-out); }
|
|
416
|
+
/* 筛选头与那几句"此刻怎么了"的 hint 都是 flex: none,只有选项层 flex: 0 1 auto 可收缩、可滚:
|
|
417
|
+
头永远在顶,hint 在选项之后(内容短时紧贴最后一行,内容长时也不会被挤没)。 */
|
|
418
|
+
.capital-watchlist-filter { flex: none; display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
419
|
+
.capital-watchlist-options { flex: 0 1 auto; min-height: 0; overflow-y: auto; }
|
|
420
|
+
.capital-watchlist-filterbtn { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border: none; border-radius: 999px; corner-shape: round; background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); font-family: inherit; font-size: 12px; line-height: 16px; cursor: pointer; }
|
|
421
|
+
.capital-watchlist-filterbtn:hover { background: var(--dsw-alias-interactive-bg-hover-accent); color: var(--dsw-alias-label-primary); }
|
|
422
|
+
/* 选中态读 aria-pressed:状态住在属性里,样式只是它的投影(读屏也听得见"已选中")。
|
|
423
|
+
压在半透明菜单材质上用 color-mix 的透明淡染——它不写背景实底,底下的菜单模糊照常透上来。 */
|
|
424
|
+
.capital-watchlist-filterbtn[aria-pressed="true"] { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); color: var(--dsw-alias-label-primary); }
|
|
425
|
+
.capital-watchlist-filtercount { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }
|
|
388
426
|
@keyframes capital-watchlist-drop { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
|
|
389
427
|
@keyframes capital-watchlist-breathe { 0% { opacity: 1; } 40% { opacity: .6; } 80%, 100% { opacity: 1; } }
|
|
390
428
|
@media (prefers-reduced-motion: reduce) { .capital-watchlist-dropdown { animation: none; } .capital-watchlist-skeleton { animation: none; } }
|
|
391
|
-
|
|
429
|
+
/* 候选行高度(2026-10-08 用户点名"减少每个标的展示高度"):padding 6 + 名称 18 + 间隙 1 + 代码行 15
|
|
430
|
+
≈ 47px。与上面那条"浮层 320px"一起算:(320 − 筛选头约 32) ÷ 47 ≈ 6 行一屏(改前是 3 行)。
|
|
431
|
+
两个数是一对——只抬浮层不压行高,多出来的空间会被行高吃掉;只压行高不抬浮层,一屏还是 3 行。 */
|
|
432
|
+
.capital-watchlist-option { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 12px; border: none; border-bottom: .5px solid var(--dsw-alias-border-l2); background: transparent; color: inherit; font-family: inherit; text-align: left; cursor: pointer; }
|
|
392
433
|
.capital-watchlist-option:last-of-type { border-bottom: none; }
|
|
393
|
-
|
|
394
|
-
|
|
434
|
+
/* 「已添加」与"在途"这一档读的是 **aria-disabled 而不是 :disabled**(真 disabled 会把焦点踢到
|
|
435
|
+
body,搜索段的 onBlur 就判"点到框外"收起下拉 —— 见渲染处那段),所以视觉降档也跟着读它:
|
|
436
|
+
整行 opacity .45、指针 not-allowed,焦点照旧留在这一行。 */
|
|
437
|
+
.capital-watchlist-option:hover:not([aria-disabled="true"]) { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent); }
|
|
438
|
+
.capital-watchlist-option[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
|
|
395
439
|
/* 键盘焦点走官方表达式:环色由宿主按主题与输入模态管好(指针模态自动透明),不自己造环。 */
|
|
396
|
-
.capital-watchlist-close:focus-visible, .capital-watchlist-clear:focus-visible, .capital-watchlist-option:focus-visible, .capital-watchlist-more:focus-visible, .capital-watchlist-predict:focus-visible, .capital-watchlist-review:focus-visible, .capital-watchlist-refresh:focus-visible, .capital-watchlist-confirmbtn:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 1px; }
|
|
397
|
-
.capital-watchlist-optionmain { display: flex; flex-direction: column; gap:
|
|
398
|
-
.capital-watchlist-optionname { font-size: 14px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
399
|
-
.capital-watchlist-optioncode { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
440
|
+
.capital-watchlist-close:focus-visible, .capital-watchlist-clear:focus-visible, .capital-watchlist-option:focus-visible, .capital-watchlist-filterbtn:focus-visible, .capital-watchlist-more:focus-visible, .capital-watchlist-predict:focus-visible, .capital-watchlist-review:focus-visible, .capital-watchlist-refresh:focus-visible, .capital-watchlist-confirmbtn:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 1px; }
|
|
441
|
+
.capital-watchlist-optionmain { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
442
|
+
.capital-watchlist-optionname { font-size: 14px; line-height: 18px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
443
|
+
.capital-watchlist-optioncode { font-size: 12px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
444
|
+
/* 右侧那颗「添加」是**整行这个 button 的可见动词**,不是第二个可聚焦控件(button 里套 button
|
|
445
|
+
是非法 HTML):所以它只是一颗胶囊——常态灰底、hover 跟着整行加深;「已添加」那档由整行 disabled
|
|
446
|
+
的 opacity 统一降下去,不再往代码行里塞「· 已添加」(代码 + 徽标才是第二行要读的两样)。 */
|
|
447
|
+
/* 右侧那颗「添加」是**整行这个 button 的可见动词**,不是第二个可聚焦控件(button 里套 button
|
|
448
|
+
是非法 HTML)。两态(2026-10-08 第三轮用户点名):
|
|
449
|
+
- **添加 = 醒目**:沿用「刷新报价」那颗 verb 按钮的配方(state 色 12% 淡底 + 25% 描边 + 同色字),
|
|
450
|
+
整行里唯一带色的元素就是它,一眼知道点哪儿;
|
|
451
|
+
- **已添加 = 暗**:透明底 + l1 描边 + tertiary 字(data-state="added"),跟整行的 aria-disabled
|
|
452
|
+
一起降下去,并且点了也不再出网(guard 在渲染处)。 */
|
|
453
|
+
.capital-watchlist-addbtn { flex: none; padding: 3px 9px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 25%, transparent); border-radius: 999px; corner-shape: round; background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent); color: var(--dsw-alias-state-business-primary); font-size: 12px; line-height: 15px; font-weight: 500; }
|
|
454
|
+
.capital-watchlist-option:hover:not([aria-disabled="true"]) .capital-watchlist-addbtn { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 20%, transparent); }
|
|
455
|
+
.capital-watchlist-addbtn[data-state="added"] { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-tertiary); font-weight: 400; }
|
|
400
456
|
.capital-watchlist-tag { flex: none; display: inline-block; padding: 2px 7px; border-radius: 6px; font-size: 11px; line-height: 14px; font-weight: 500; }
|
|
401
457
|
/* 类型徽标 = 官方状态徽标那对写法:底色用 state-*-tertiary(主题包为"状态色的底"准备的实心档,
|
|
402
458
|
自己 color-mix 出来的透明档压在白卡片上等于没混),文字用同色系 primary;
|
|
@@ -424,9 +480,13 @@ const CSS = `
|
|
|
424
480
|
这一格装着这一行的三个动作:「预测」「复盘」与「更多」,表头贴的就是它们共同那条右缘。 */
|
|
425
481
|
.capital-watchlist-headaction { text-align: right; }
|
|
426
482
|
.capital-watchlist-list { max-height: 300px; padding: 0 12px 8px; overflow-y: auto; }
|
|
427
|
-
/* 下拉打开时把这一格撑到浮层的最大高度(见上面 capital-watchlist-dropdown
|
|
428
|
-
|
|
429
|
-
|
|
483
|
+
/* 下拉打开时把这一格撑到浮层的最大高度(见上面 capital-watchlist-dropdown 那一段的理由):
|
|
484
|
+
**这两个数必须同值**——一个改了另一个不改,下拉就会被卡片裁掉一截(或者清单被顶出卡片外)。
|
|
485
|
+
2026-10-08 一起从 246 抬到 320(用户点名"一屏只能显示 3 个标的"),测试里有同值断言。
|
|
486
|
+
只在打开时撑:平时 1-2 行的短清单不该平白多出一截空白。
|
|
487
|
+
⚠️ 打开时 min-height(320) > 上面那条 max-height(300),而 CSS 里 min 恒胜 max ⇒ 这一格此时按
|
|
488
|
+
320 高、内部照旧 overflow-y: auto 自己滚;收起后 max-height 300 恢复生效。 */
|
|
489
|
+
.capital-watchlist-list-open { min-height: 320px; }
|
|
430
490
|
.capital-watchlist-row { padding: 11px 8px; border-radius: 10px; transition: background .15s var(--ds-ease-in-out); }
|
|
431
491
|
.capital-watchlist-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
432
492
|
.capital-watchlist-namewrap { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
@@ -490,7 +550,9 @@ const CSS = `
|
|
|
490
550
|
.capital-watchlist-refresh:disabled { opacity: .45; cursor: not-allowed; }
|
|
491
551
|
/* 34px = 刷新按钮的高度:与它同一行的时间按按钮高度行高居中,左列多出失败行也不受影响。 */
|
|
492
552
|
.capital-watchlist-updated { font-size: 12px; line-height: 34px; color: var(--dsw-alias-label-tertiary); }
|
|
493
|
-
|
|
553
|
+
/* flex: none:2026-10-08 起 hint 是下拉这个 flex 列的直接子元素(滚动区只剩选项那一层),
|
|
554
|
+
不钉住的话空间一紧它就会被压扁——hint 没有 overflow,压扁就是文字被裁。 */
|
|
555
|
+
.capital-watchlist-hint { flex: none; padding: 8px 14px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
494
556
|
/* 黄 = 动作没成。两条黄字都用 warn-label:官方"状态色用作文字"给的是 label 那一档,
|
|
495
557
|
warn-primary 压在浅底上只有 2:1,报错文字反而是最不该看不见的一行。 */
|
|
496
558
|
/* 搜索失败:占下拉的 hint 位(与「搜索中…」「没有匹配的标的」同一个槽,替换而非并列)。
|
|
@@ -581,6 +643,12 @@ const blank = () => ({
|
|
|
581
643
|
items: [],
|
|
582
644
|
candidates: [],
|
|
583
645
|
truncated: false,
|
|
646
|
+
/**
|
|
647
|
+
* 候选筛选头(`all` / `cn` / `hk` / `us`,**默认 `all`**)。住 store 而不是组件 `useState`:
|
|
648
|
+
* 两条复位路径各归各的——面板重开(`open()` 的 `...blank()`)回到「全部」;换词**不**掰回
|
|
649
|
+
* (用户筛了港,下一个词他还想看港),清空输入也留着(下拉收起时它本来看不见)。
|
|
650
|
+
*/
|
|
651
|
+
marketFilter: 'all',
|
|
584
652
|
query: '',
|
|
585
653
|
/** 下拉是否盖在清单上:由输入打开、由选中/点到框外收起(Esc 也收,见组件的 onKeyDown)。 */
|
|
586
654
|
dropdownOpen: false,
|
|
@@ -770,6 +838,16 @@ class WatchlistSurface {
|
|
|
770
838
|
this.patch({ dropdownOpen: false })
|
|
771
839
|
}
|
|
772
840
|
|
|
841
|
+
/**
|
|
842
|
+
* 筛选头(全部 / CN / HK / US):只换**显示**哪几行,不出网——候选是一次拿全的,
|
|
843
|
+
* 切档也不该把下拉收起(它就在下拉顶上)。
|
|
844
|
+
* 未知档名直接忽略:这一格的取值只有 `MARKET_FILTERS` 那四个,拼错的值不许让下拉变空白。
|
|
845
|
+
*/
|
|
846
|
+
setMarketFilter(filter) {
|
|
847
|
+
if (!MARKET_FILTERS.some((entry) => entry.id === filter)) return
|
|
848
|
+
this.patch({ marketFilter: filter })
|
|
849
|
+
}
|
|
850
|
+
|
|
773
851
|
/** 行内「更多」键:再点一次就收起(同一个键既开又关,不用去别处找关闭入口)。 */
|
|
774
852
|
toggleRowMenu(thscode) {
|
|
775
853
|
this.patch({ rowMenu: this.snapshot.rowMenu === thscode ? null : thscode })
|
|
@@ -827,8 +905,16 @@ class WatchlistSurface {
|
|
|
827
905
|
// try 里:`/add` 已经落库、紧接着清单读回来 503,面板就报"自选股已达上限"式的黄字,
|
|
828
906
|
// 用户照着再点一次——同一个标的被加了第二遍)。读清单的失败归 `loadError`(列表那
|
|
829
907
|
// 一片),刷报价的失败归 `error`(页脚那条红字),各自有位置,都不占添加这句话。
|
|
830
|
-
|
|
831
|
-
|
|
908
|
+
// ⛔ 成功之后**不再收下拉、不清输入**(2026-10-08 用户点名"能在下拉框里连续添加"):
|
|
909
|
+
// 收起 = 每加一条都得重敲一遍词,"连续添加"根本无从谈起。这一行当场标 `已添加` 并随行禁用
|
|
910
|
+
// (幂等,点不进第二次),其余候选、筛选档与输入框里的词都原样留着。`lastQuery` 因此保持原值
|
|
911
|
+
// ⇒ 同一个词不会被重发(三条闸里"同一个词不重发"那条正是靠它)。
|
|
912
|
+
// ⛔ `searchError` / `searchPartial` 也**不清**:它们说的是"这次搜索有一路没参与",下拉还开着,
|
|
913
|
+
// 把真话擦掉就是让它失效。
|
|
914
|
+
this.patch({
|
|
915
|
+
addError: null,
|
|
916
|
+
candidates: this.snapshot.candidates.map((row) => (row.thscode === candidate.thscode ? { ...row, in_list: true } : row)),
|
|
917
|
+
})
|
|
832
918
|
await this.load().catch(() => {})
|
|
833
919
|
// 添加是一次完整动作:读完清单顺手刷一次报价,新行当场就有价。停在「未刷新」
|
|
834
920
|
// 等于把用户刚加的东西留成半成品——开面板那一步本来就是同一套 load→refresh。
|
|
@@ -990,6 +1076,12 @@ function addErrorText(t, addError) {
|
|
|
990
1076
|
function WatchlistDialog({ useDialog, surface, t, inputActions }) {
|
|
991
1077
|
const state = useDialog((value) => value)
|
|
992
1078
|
const [busy, setBusy] = useState(false)
|
|
1079
|
+
/**
|
|
1080
|
+
* 在途的那一次「添加」落在哪一行(null = 没有)。⛔ 与 `busy` 分开:`busy` 关的是**清单那半边**
|
|
1081
|
+
* (预测 / 复盘 / 更多 / 刷新按钮),候选行只许禁它自己那一条——全禁的话,"连续添加"会被每一次
|
|
1082
|
+
* 在途请求打断(第二条正灰着、点不进去)。并发添加是安全的:服务端把清单读写串在一条队列上。
|
|
1083
|
+
*/
|
|
1084
|
+
const [addingThscode, setAddingThscode] = useState(null)
|
|
993
1085
|
/**
|
|
994
1086
|
* 待移除的那一行,走面板内的二次确认层——不是 `window.confirm`:那是宿主窗口级别的系统弹窗,
|
|
995
1087
|
* 样式与语言都不归插件管,而且会把"从自选中移除"这件事从面板里搬出去。
|
|
@@ -1046,28 +1138,84 @@ function WatchlistDialog({ useDialog, surface, t, inputActions }) {
|
|
|
1046
1138
|
surface.close()
|
|
1047
1139
|
}
|
|
1048
1140
|
|
|
1049
|
-
|
|
1141
|
+
/**
|
|
1142
|
+
* 筛选头只筛**已经拿全**的候选(A 股 50 + 港 10 + 美 10 一次回包):切档不出网、也不收下拉。
|
|
1143
|
+
* 计数按市场算,「全部」那一档是三市之和——用户那句"怎么全是 A 股,港股美股呢",答案就写在
|
|
1144
|
+
* 这三个数字里(港 10 / 美 10,不是 0),不用他逐档点过去找。档名 `CN` / `HK` / `US` 中英文同一套。
|
|
1145
|
+
*/
|
|
1146
|
+
const visibleCandidates = state.marketFilter === 'all'
|
|
1147
|
+
? state.candidates
|
|
1148
|
+
: state.candidates.filter((candidate) => marketIdOf(candidate) === state.marketFilter)
|
|
1149
|
+
const filterCounts = { all: state.candidates.length, cn: 0, hk: 0, us: 0 }
|
|
1150
|
+
for (const candidate of state.candidates) filterCounts[marketIdOf(candidate)] += 1
|
|
1151
|
+
const filterBar = state.candidates.length === 0
|
|
1050
1152
|
? null
|
|
1051
|
-
: h('div', { className: 'capital-watchlist-
|
|
1052
|
-
|
|
1053
|
-
key:
|
|
1153
|
+
: h('div', { className: 'capital-watchlist-filter', role: 'group', 'aria-label': t('filterLabel') },
|
|
1154
|
+
MARKET_FILTERS.map((entry) => h('button', {
|
|
1155
|
+
key: entry.id,
|
|
1054
1156
|
type: 'button',
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
title: candidate.in_list === true ? t('added') : t('add'),
|
|
1060
|
-
disabled: candidate.in_list === true || busy,
|
|
1061
|
-
onClick: () => { setBusy(true); surface.add(candidate).finally(() => setBusy(false)) },
|
|
1157
|
+
className: 'capital-watchlist-filterbtn',
|
|
1158
|
+
// 选中态只写在 `aria-pressed` 上、样式读它:状态先进属性,读屏也听得见"已选中"。
|
|
1159
|
+
'aria-pressed': state.marketFilter === entry.id,
|
|
1160
|
+
onClick: () => surface.setMarketFilter(entry.id),
|
|
1062
1161
|
},
|
|
1063
|
-
h('span', { className: 'capital-watchlist-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1162
|
+
h('span', { className: 'capital-watchlist-filterlabel' }, entry.id === 'all' ? t('filterAll') : entry.label),
|
|
1163
|
+
h('span', { className: 'capital-watchlist-filtercount' }, String(filterCounts[entry.id])))))
|
|
1164
|
+
|
|
1165
|
+
const dropdown = !showDropdown
|
|
1166
|
+
? null
|
|
1167
|
+
: h('div', { className: 'capital-watchlist-dropdown', 'aria-label': t('title'), 'data-menu-material': 'translucent' },
|
|
1168
|
+
// 筛选头**不进滚动区**:`role="listbox"` 因此跟着选项搬到里面那层(列表框里只许摆 option,
|
|
1169
|
+
// 头自己是 group),滚动的也只有选项那一层——头由 CSS 钉在下拉顶部,即用户要的"锁定这个头"。
|
|
1170
|
+
filterBar,
|
|
1171
|
+
h('div', { className: 'capital-watchlist-options', role: 'listbox', 'aria-label': t('title') },
|
|
1172
|
+
visibleCandidates.map((candidate) => h('button', {
|
|
1173
|
+
key: candidate.thscode,
|
|
1174
|
+
type: 'button',
|
|
1175
|
+
role: 'option',
|
|
1176
|
+
className: 'capital-watchlist-option',
|
|
1177
|
+
// hover 出来的是**证券全称**(2026-10-08 用户点名):旧版这一格写的是 `添加`,那句
|
|
1178
|
+
// "这个按钮能干嘛"已经由右侧那颗「添加」说了;美股长名会被 CSS 截断,全称只有这个
|
|
1179
|
+
// 原生 tooltip 给得全。
|
|
1180
|
+
title: candidate.name,
|
|
1181
|
+
// ⛔ 这一格用 `aria-disabled` 而不是真 `disabled`(2026-10-08 第三轮用户点名"点一下添加、
|
|
1182
|
+
// 下拉就没了"):真 `disabled` 会让浏览器**立刻把焦点从这一行踢到 body**,搜索段的 onBlur
|
|
1183
|
+
// 于是拿到 `relatedTarget: null` → 判"点到框外" → 收起下拉,连续添加被这一下打断。
|
|
1184
|
+
// `aria-disabled` 不动焦点(行照样可聚焦,读屏也听得见"不可用"),"禁止点击"由下面的 guard 承担。
|
|
1185
|
+
'aria-disabled': candidate.in_list === true || addingThscode === candidate.thscode,
|
|
1186
|
+
onClick: () => {
|
|
1187
|
+
// 幂等 + 防在途双击:已添加的那条点多少次都不再出网,在途那条也拦掉。
|
|
1188
|
+
if (candidate.in_list === true || addingThscode === candidate.thscode) return
|
|
1189
|
+
setBusy(true)
|
|
1190
|
+
setAddingThscode(candidate.thscode)
|
|
1191
|
+
surface.add(candidate).finally(() => { setBusy(false); setAddingThscode(null) })
|
|
1192
|
+
},
|
|
1193
|
+
},
|
|
1194
|
+
h('span', { className: 'capital-watchlist-optionmain' },
|
|
1195
|
+
h('span', { className: 'capital-watchlist-optionname' }, candidate.name),
|
|
1196
|
+
// ⛔ 第二行画**完整 canonical 代码**,不是裸码:`00700` 一次同时命中港个股与
|
|
1197
|
+
// `000700.SZ` 模塑科技,搜「恒生指数」还会同时回 `HSI` / `HSIGTR` / `HSINTR` 三条真指数——
|
|
1198
|
+
// 裸码并列时用户没有可核对的信息,点错就是加错票。
|
|
1199
|
+
// 类型徽标挪到代码右边(`codeline` 直接复用清单第一列那个类 ⇒ 排法与清单同款,用户
|
|
1200
|
+
// 2026-10-08 点名"类似自选股列表第一列的样式"),右侧因此腾出来放「添加」。
|
|
1201
|
+
h('span', { className: 'capital-watchlist-codeline' },
|
|
1202
|
+
h('span', { className: 'capital-watchlist-optioncode' }, candidate.thscode),
|
|
1203
|
+
h(TypeTag, { assetType: candidate.asset_type, t }))),
|
|
1204
|
+
// 右侧那颗「添加」是**整行这个 button 的可见动词**,不是第二个可聚焦控件(button 里再
|
|
1205
|
+
// 套 button 是非法 HTML);已在清单里就换 `已添加`,随行一起禁用(点了也不会重复写库)。
|
|
1206
|
+
// 两态(2026-10-08 第三轮用户点名):**添加 = 醒目**(沿用「刷新报价」那颗 verb 按钮的配方,
|
|
1207
|
+
// 整行里唯一带色的就是它 ⇒ 一眼知道点哪儿)、**已添加 = 暗**(透明底 + l1 描边 + tertiary 字,
|
|
1208
|
+
// 配合整行 aria-disabled 的 opacity 一起降)。状态只写在 `data-state` 上,样式读它。
|
|
1209
|
+
h('span', { className: 'capital-watchlist-addbtn', 'data-state': candidate.in_list === true ? 'added' : 'idle' },
|
|
1210
|
+
candidate.in_list === true ? t('added') : t('add')))),
|
|
1211
|
+
// ⛔ "没有更多"那句住在**滚动区末尾**(2026-10-08 用户点名"不要冻结"):它跟着内容一起滚,
|
|
1212
|
+
// 滚到底才看得见——钉在浮层底部就成了一条常驻横幅,还挡着最后一行候选。
|
|
1213
|
+
// 筛选档里一行都不剩时不说它(那一格由下面的 `filterNoHit` 负责)。
|
|
1214
|
+
state.truncated === true && visibleCandidates.length > 0
|
|
1215
|
+
? h('div', { className: 'capital-watchlist-hint capital-watchlist-endhint' }, t('truncated'))
|
|
1216
|
+
: null),
|
|
1217
|
+
// 下面这几句 hint **不进滚动区**:它们说的是"这一屏此刻怎么了"(正在查 / 查不到 / 有路挂了),
|
|
1218
|
+
// 被内容滚走就等于没人看见——所以留在浮层里、贴在选项之后。
|
|
1071
1219
|
state.composing === true
|
|
1072
1220
|
// ⛔ 合成期(拼音还没上屏)不许报「没有匹配的标的」:那是把"还没查"说成"查了,没有",
|
|
1073
1221
|
// 用户会以为自己的输入法打错了字。这一格说的是下一步会发生什么。
|
|
@@ -1082,7 +1230,11 @@ function WatchlistDialog({ useDialog, surface, t, inputActions }) {
|
|
|
1082
1230
|
? h('div', { className: 'capital-watchlist-hint capital-watchlist-searcherror', role: 'alert', 'data-code': state.searchError.code }, t(`error.${state.searchError.code}`))
|
|
1083
1231
|
: state.candidates.length === 0
|
|
1084
1232
|
? h('div', { className: 'capital-watchlist-hint' }, t('noHit'))
|
|
1085
|
-
:
|
|
1233
|
+
: visibleCandidates.length === 0
|
|
1234
|
+
// 筛选把候选滤空了(总数 > 0):这句与 noHit 说的是两件事——票是有的,只是不在
|
|
1235
|
+
// 这一档里。说成"没有匹配"就是把用户的筛选说成他查错了。
|
|
1236
|
+
? h('div', { className: 'capital-watchlist-hint', 'data-filter': state.marketFilter }, t('filterNoHit'))
|
|
1237
|
+
: null,
|
|
1086
1238
|
// 某一路挂了而其余路照常给候选:这一行说的是**那一路**(R12)。它与上面的 hint 各说
|
|
1087
1239
|
// 各的事——上面那句讲"这一屏有没有匹到",这句讲"有一路今天没参与",不互相顶替。
|
|
1088
1240
|
...(state.searchPartial ?? []).map((branch) => h('div', {
|
|
@@ -1091,8 +1243,7 @@ function WatchlistDialog({ useDialog, surface, t, inputActions }) {
|
|
|
1091
1243
|
role: 'status',
|
|
1092
1244
|
'data-market': branch.market,
|
|
1093
1245
|
'data-code': branch.code,
|
|
1094
|
-
}, `${t(`partial.${branch.market}`)}${t(`error.${branch.code}`)}`))
|
|
1095
|
-
state.truncated ? h('div', { className: 'capital-watchlist-hint' }, t('truncated')) : null)
|
|
1246
|
+
}, `${t(`partial.${branch.market}`)}${t(`error.${branch.code}`)}`)))
|
|
1096
1247
|
|
|
1097
1248
|
const head = state.items.length === 0
|
|
1098
1249
|
? null
|
|
@@ -50,15 +50,20 @@ export const DOMAIN_NAME = 'capital_watchlist'
|
|
|
50
50
|
export const MAX_ITEMS = 30
|
|
51
51
|
/** ETF 扇出的并发路数:30 个单只请求压成约 8 波,正常网络一次刷新几秒内落地。 */
|
|
52
52
|
export const REFRESH_CONCURRENCY = 4
|
|
53
|
-
/** 搜索候选上限(一屏给这么多,倒逼用户缩小输入,不做分页)。 */
|
|
54
|
-
export const SEARCH_LIMIT = 20
|
|
55
53
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* 每一路自己能拿到几条——**这就是候选池的全部**(2026-10-08 起不再做显示切片:上游没有第二页,
|
|
55
|
+
* 用户点名"既然翻不了页,就直接放出来"):`50 + 10 + 10 = 70` 是三路上游硬顶之和,天然有界,
|
|
56
|
+
* 不会出现"搜个词回几千行"。
|
|
57
|
+
*
|
|
58
|
+
* `truncated` 那句"还有更多"**只按这一表判**:某一路回满自己那一档,就是上游那侧被砍了;
|
|
59
|
+
* 我们这边一行都没藏,所以那句 tip 里也不再有"候选不止这些"(只留下"请输入更准确的…"那半句)。
|
|
60
|
+
*
|
|
61
|
+
* 数字的来路:Fuyao `ticker_search` 的 `limit` 官方取值 1~50(默认 10),这里取到**官方上限**;
|
|
62
|
+
* smartbox 实测**硬顶 10 条**(`n=` / `count=` / `size=` / `limit=` / `max=` / `hits=` / `num=` /
|
|
63
|
+
* `rows=` 八种抬条数的写法全无效,分页的 `p=` / `page=` / `offset=` / `start=` / `from=` / `pn=` /
|
|
64
|
+
* `o=` 七种回包与基线**逐字节相同**;2026-10-07 与 10-08 两轮实测),多要也不会多给。
|
|
60
65
|
*/
|
|
61
|
-
export const SEARCH_BRANCH_LIMITS = { 'a-share':
|
|
66
|
+
export const SEARCH_BRANCH_LIMITS = { 'a-share': 50, hk: 10, us: 10 }
|
|
62
67
|
/** A 股那一路的搜索白名单(Fuyao 与报价端点双端都有可靠支撑);场外基金 / 北交所不进候选。
|
|
63
68
|
* 港美股那两路各有自己的白名单(`SMARTBOX_STOCK_TYPES` + `SMARTBOX_INDEX_TYPE`),互不通用。 */
|
|
64
69
|
export const SEARCH_ASSET_TYPES = 'a-share,a-share-index,fund-etf'
|
|
@@ -570,8 +575,10 @@ export function createWatchlistService(options = {}) {
|
|
|
570
575
|
}
|
|
571
576
|
|
|
572
577
|
/**
|
|
573
|
-
* 一次输入三路并发:Fuyao `ticker_search`
|
|
574
|
-
*
|
|
578
|
+
* 一次输入三路并发:Fuyao `ticker_search` **一笔**(`asset_type` 白名单照旧,`limit` 取**官方上限
|
|
579
|
+
* 50**——参数面 1~50,不是我们自己放宽的)\+ smartbox 两笔(`t=hk` / `t=us`,各硬顶 10 条)。
|
|
580
|
+
* 例外在 `searchAShare`:`q` 含 ASCII 小写字母时 A 股那一路**问两笔**(原样 + 全大写),
|
|
581
|
+
* 因为上游对 `q` 是区分大小写的子串匹配——那是"三路"之外唯一会多出一笔的地方。
|
|
575
582
|
* 为什么 smartbox 不共用一次 `t=all`:实测 `t=all&q=富通`
|
|
576
583
|
* 回 9 条 A 股 + 1 条 hk + **0 条 us**,`q=00700` 只回 1 条 hk——共用一次会让"美股这一档根本没进
|
|
577
584
|
* 候选"从偶发变成常态,那是"入口统一"最直接的反面(设计文档 P17 / §3.2)。
|
|
@@ -598,32 +605,76 @@ export function createWatchlistService(options = {}) {
|
|
|
598
605
|
const merged = mergeCandidates(branches.filter((branch) => branch.ok === true).flatMap((branch) => branch.rows), q)
|
|
599
606
|
const result = {
|
|
600
607
|
ok: true,
|
|
601
|
-
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
608
|
+
// **一行不落地全画出来**(2026-10-08 用户点名"既然没有翻页就直接放出来"):候选池的上界
|
|
609
|
+
// 由三路上游各自的硬顶定死(A 股 50 + 港 10 + 美 10 = 70),不需要再切一刀给"一屏"。
|
|
610
|
+
items: merged.map((row) => ({ ...row, in_list: items.get(row.thscode) !== undefined })),
|
|
611
|
+
// "还有更多"只剩一种 truthful 形态:**某一路回满了它自己那一档**(上游那侧被砍)。
|
|
612
|
+
// 合并后的总条数不再参与判断——我们一行没藏,条数多不等于还有没显示的;
|
|
613
|
+
// "回满自己那档"由每一路在**它自己的那一笔**上算好带回来(A 股可能问了两笔,两笔各 50
|
|
614
|
+
// 合起来 100 时我们只留 50,那也算被砍,`saturated` 里已经记了)。
|
|
615
|
+
truncated: branches.some((branch) => branch.ok === true && branch.saturated === true),
|
|
605
616
|
}
|
|
606
617
|
if (failures.length > 0) result.partial = failures.map((failure) => ({ market: failure.market, code: failure.code }))
|
|
607
618
|
return result
|
|
608
619
|
}
|
|
609
620
|
|
|
610
|
-
/**
|
|
621
|
+
/**
|
|
622
|
+
* A 股那一路。⛔ 不许拿港美股那两条白名单顺手再过滤一遍:这边已有 `asset_type` + 键形状两道闸。
|
|
623
|
+
*
|
|
624
|
+
* **大小写要问两遍**(2026-10-08 真上游实测):Fuyao 的 `q` 是区分大小写的子串匹配——
|
|
625
|
+
* `q=tcl` 0 条而 `q=TCL` 3 条、`q=etf` 0 条而 `q=ETF` 50 条、`q=a50` 0 条而 `q=A50` 50 条、
|
|
626
|
+
* `q=600519.sh` 0 条而 `q=600519.SH` 1 条。而全量 8708 条 A 股 / 指数 / ETF 名称里含**小写**
|
|
627
|
+
* 拉丁字母的只有 8 条、且全是 `.TI` 概念指数(下一行就过滤掉),含大写的 2092 条——
|
|
628
|
+
* 所以大写那一支是现实的主力,原样那一支是"不丢小写专有命中"的保险:`q` 与 `q.toUpperCase()`
|
|
629
|
+
* 不同时**两笔并发**,按 canonical 键去重、原样那份排前面(保持"上游顺序"的既有口径);
|
|
630
|
+
* 两者相同(数字 / 中文 / 已经全大写)就只发一笔,不白烧一次配额。
|
|
631
|
+
*
|
|
632
|
+
* `saturated` 讲的是"**这一路被砍在自己那一档**",按笔 + 按并集一起判:某一笔回满 50 是上游
|
|
633
|
+
* 砍的;两笔并起来超过 50 时我们自己也会砍一刀(见下),那同样是"还有没显示的"。反过来说,
|
|
634
|
+
* 两笔各 15 条、并起来 30 条并不说明上游还有货——按合并后的条数判就会凭空给一句假 tip。
|
|
635
|
+
*
|
|
636
|
+
* 半边失败:**拿到候选就算这一路答上了**——`partial` 那行的文案是"A股搜索不可用",
|
|
637
|
+
* 与同屏的 A 股候选并排是自相矛盾;一支候选都没拿到时才走整路失败,绝不把"这一支没答上来"
|
|
638
|
+
* 说成"查无此票"(大写那支挂掉时,我们并不知道该词在 A 股到底有没有票)。
|
|
639
|
+
*/
|
|
611
640
|
async function searchAShare(q) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
641
|
+
const variants = [q, q.toUpperCase()].filter((value, index, all) => all.indexOf(value) === index)
|
|
642
|
+
const limit = SEARCH_BRANCH_LIMITS['a-share']
|
|
643
|
+
const outcomes = await Promise.all(variants.map(async (variant) => {
|
|
644
|
+
try {
|
|
645
|
+
const envelope = await callFuyao(ENDPOINTS.search, { q: variant, asset_type: SEARCH_ASSET_TYPES, limit }, ['q', 'asset_type', 'limit'])
|
|
646
|
+
// 后缀也要收口:实测 `q=宁德时代` 会带回 `885789.TI 宁德时代概念`(同花顺指数),
|
|
647
|
+
// 它既不在本版报价范围、也过不了域 schema——不挡掉就是"添加"那一下 500。
|
|
648
|
+
const rows = fuyaoItems(envelope).map(candidateOf).filter((row) => ASSET_TYPES.includes(row.asset_type) && normalizeThscode(row.thscode) !== undefined)
|
|
649
|
+
return { rows, saturated: rows.length >= limit, code: undefined }
|
|
650
|
+
} catch (error) {
|
|
651
|
+
return { rows: [], saturated: false, code: watchlistErrorCode(error) }
|
|
652
|
+
}
|
|
653
|
+
}))
|
|
654
|
+
const union = []
|
|
655
|
+
const seen = new Set()
|
|
656
|
+
for (const outcome of outcomes) {
|
|
657
|
+
for (const row of outcome.rows) {
|
|
658
|
+
if (seen.has(row.thscode)) continue
|
|
659
|
+
seen.add(row.thscode)
|
|
660
|
+
union.push(row)
|
|
661
|
+
}
|
|
619
662
|
}
|
|
663
|
+
const codes = outcomes.filter((outcome) => outcome.code !== undefined).map((outcome) => outcome.code)
|
|
664
|
+
if (union.length === 0 && codes.length > 0) return { ok: false, market: 'a-share', code: worstErrorCode(codes) }
|
|
665
|
+
// 并集也只留这一档的量:两笔各 50、几乎不重叠时 A 股这一路最多画 50 行,候选池上界因此是
|
|
666
|
+
// 50 + 10 + 10 = 70(`SEARCH_BRANCH_LIMITS` 那条注释里的算法)。自己砍掉的那几行同样算"被砍",
|
|
667
|
+
// 否则 truncated 会在还有没显示的行时说"就这些"。
|
|
668
|
+
const saturated = outcomes.some((outcome) => outcome.saturated) || union.length > limit
|
|
669
|
+
return { ok: true, market: 'a-share', rows: union.slice(0, limit), saturated }
|
|
620
670
|
}
|
|
621
671
|
|
|
622
672
|
/** smartbox 那两路之一:类型白名单 + 键形状一起收,衍生品(窝轮 `QZ`)不进候选。 */
|
|
623
673
|
async function searchOffshore(q, market) {
|
|
624
674
|
try {
|
|
625
675
|
const found = await fetchSmartbox(q, market, AbortSignal.timeout(timeoutMs))
|
|
626
|
-
|
|
676
|
+
const rows = found.map((row) => offshoreCandidateOf(row, market)).filter((row) => row !== undefined)
|
|
677
|
+
return { ok: true, market, rows, saturated: rows.length >= SEARCH_BRANCH_LIMITS[market] }
|
|
627
678
|
} catch (error) {
|
|
628
679
|
return { ok: false, market, code: watchlistErrorCode(error, { source: 'tencent' }) }
|
|
629
680
|
}
|
package/chart-ui/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* between validation and connect. Closing that window would require a custom
|
|
7
7
|
* dispatcher; this module keeps the same explicit trade-off as dsh-search-first.
|
|
8
8
|
*/
|
|
9
|
-
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.6.
|
|
9
|
+
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.6.4";
|
|
10
10
|
export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
11
11
|
export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
|
|
12
12
|
export declare class LocalFetchError extends Error {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* dispatcher; this module keeps the same explicit trade-off as dsh-search-first.
|
|
8
8
|
*/
|
|
9
9
|
import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
|
|
10
|
-
export const LOCAL_FETCH_CLIENT_VERSION = '2.6.
|
|
10
|
+
export const LOCAL_FETCH_CLIENT_VERSION = '2.6.4';
|
|
11
11
|
export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
12
12
|
export class LocalFetchError extends Error {
|
|
13
13
|
code;
|
|
@@ -52,7 +52,7 @@ export interface WindCallResult {
|
|
|
52
52
|
* `test/chart-composition.test.mjs` 守着(会因漂移而失败),这里原本是写死的第二处,
|
|
53
53
|
* 一次发版就得记得改两遍。所以本常量由测试一并核对(见 `test/wind-client.test.mjs`)。
|
|
54
54
|
*/
|
|
55
|
-
export declare const WIND_CLIENT_VERSION = "2.6.
|
|
55
|
+
export declare const WIND_CLIENT_VERSION = "2.6.4";
|
|
56
56
|
export interface WindClient {
|
|
57
57
|
callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
|
|
58
58
|
}
|
|
@@ -35,7 +35,7 @@ export const WIND_SERVER_ENDPOINTS = {
|
|
|
35
35
|
* `test/chart-composition.test.mjs` 守着(会因漂移而失败),这里原本是写死的第二处,
|
|
36
36
|
* 一次发版就得记得改两遍。所以本常量由测试一并核对(见 `test/wind-client.test.mjs`)。
|
|
37
37
|
*/
|
|
38
|
-
export const WIND_CLIENT_VERSION = '2.6.
|
|
38
|
+
export const WIND_CLIENT_VERSION = '2.6.4';
|
|
39
39
|
function errorText(error) {
|
|
40
40
|
return error instanceof Error ? error.message : String(error);
|
|
41
41
|
}
|