diff --git a/.gitignore b/.gitignore index 2c402ae..e95a6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ website/sitemap.xml # Python bytecode cache (ui-ux-pro-max skill scripts) __pycache__/ +# 从 CHANGELOG.md 生成(scripts/site-assets.mjs),手写的那份下次发版就过期了 +website/changelog.html diff --git a/CLAUDE.md b/CLAUDE.md index 09560e8..b22149b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -272,6 +272,22 @@ SEO 与 GEO(生成式引擎优化)都落在 `` 与 `website/` 根下 改文案时记得回头看这几处:``、`meta description`、OG/Twitter 三件套、JSON-LD 里的 `description` 与 `featureList`、以及 `llms.txt`。它们不在一个文件里,最容易只改了一处。 +### 内容页与顶栏一致性 + +除首页外还有 7 个页面:`download.html`、`faq.html`、`changelog.html`(生成)、`guide/`(索引 + 三篇:行情条 / 分组 / 老板键)。它们共用首页的 token 与顶栏页脚,版式换成阅读型 —— `.page-head`(标题区,自己让出 fixed 顶栏的高度)、`.prose`(**680px** 正文列,中文每行 35–45 字最省眼,比首页那 1072px 通栏窄得多)、`.card-grid`(下载卡与指南索引)、`.release*`(更新日志)。面包屑只在 `guide/*` 出现,那是全站唯一的三级路径。 + +**顶栏、页脚、回到顶部这三段在每页各有一份**,好让每页都是「打开就能看」的独立文件。这违反本仓库平时拒绝的「同一个东西写两遍」,让它可接受的是 `site-assets.mjs` 里的校验:这三段各用 `<!-- chrome:nav:start -->` 之类的标记包起来,脚本提取后**逐字节比对**(比对前剥掉 `../`,因为子目录页面的深度不是漂移),不一致就让 CI 失败。`404.html` 与 `privacy.html` 用 `CHROME_SKIP` 排除 —— 前者要为任意深度的失效路径服务,链接只能是站点绝对路径;后者是从 `docs/` 复制来的、商店登记在册的隐私政策。 + +于是**当前页高亮不能写进顶栏**(写了就不一致了),改成挂在 `<body data-page="…">` 上加 CSS。选择器用 `href$=` 后缀匹配,这样 `guide/*` 里的 `../` 前缀不影响命中;状态收进一个 `--current` 变量,颜色与指示器都从它派生,于是「哪个 data-page 对应哪个导航项」那份清单只写一遍(拆成两块迟早对不上,而症状只是某一页的指示器没出现)。加新页面时给它一个 `data-page` 值并在那份清单里登记一行;`download` 没有条目是故意的 —— 它的入口是顶栏那颗常驻 CTA 按钮,不是导航项,硬凑一条只会是死代码。 + +**指示器的形状是有意的,不要改回「淡色底块 + 通栏下划线」。** 那是模板与生成式工具的默认答案;两个信号还互相打架(底块已经说了「就是这里」,再压一条粗线是重复),合起来会被读成一颗带下边框的按钮。现在只留一个信号:标签提亮 + 一条与文字等宽、两端渐隐、带辉光的强调色短横。三个细节各有原因:左右各 12px 正是 `.nav__link` 的内边距,于是线宽正好等于文字宽(按元素框算会宽出一截);两端渐隐,与顶栏滚动后那条 `.nav.is-scrolled::after` 发丝线同源,全站只有这两处用渐变线;辉光用 `drop-shadow` 而非 `box-shadow` —— 前者跟着渐变渲染后的 alpha 走,后者会按元素框发一圈方形的光。手机菜单里同一枚指示器改成**左侧竖条**:竖排列表用竖条、横排用横线,方向跟着布局走。 + +**`changelog.html` 由 `site-assets.mjs` 从 `CHANGELOG.md` 生成**,不是手抄的:手抄的那份下一次发版就过期,而且过期是静默的。生成器只认 `CHANGELOG.md` 实际用到的那几种写法(`##` 版本、`###` 小节、`-` 条目、行内 `**粗体**` 与 `` `代码` ``),遇到别的原样当段落输出、不抛错。它的顶栏是从 `index.html` 里**提取**出来的,所以顶栏仍然只有一份来源,而生成出来的页面同样受上面那条一致性校验约束(`readChrome` 返回的是**含标记**的整块,只返回内部内容的话生成页会因为「缺标记」而校验失败 —— 踩过)。 + +站点里另外两处「按实际内容生成」的地方:`sitemap.xml` 扫描 `website/**/*.html`(**递归**,只扫顶层会让 `guide/*` 静默漏掉:不校验、不进 sitemap,而这两件事都不会有人注意到),`lastmod` 取自 `git log`;引用校验按**页面自己所在的目录**解析相对路径 —— 按站点根解析会把 `guide/` 下的 `../styles.css` 解到仓库外面去,而且会报出一堆假缺失。 + +改动这些页面之后,`npm run site:assets` 会把四件事一次做完:资源复制、引用校验、顶栏一致性校验、sitemap 生成。 + 注意 **`ticker-dark.png` / `ticker-light.png` 只有 344×59**,必须按原尺寸展示(`.ticker-runway` 就是为此存在的)—— 其余截图是 2000px 级,可自由缩放。 Pages 现在已经跑在 GitHub Actions 上(2026-10 从 `master:/docs` 迁过来的,见下)。工作流的 `paths` 过滤器里带着 `screenshots/**` 与 `docs/privacy.html`:换一张截图、改一次隐私政策,同样应该重新发布,否则站点上还是旧的,而「只改了 screenshots/」看起来与官网无关。 diff --git a/scripts/site-assets.mjs b/scripts/site-assets.mjs index 9612a85..d85c312 100644 --- a/scripts/site-assets.mjs +++ b/scripts/site-assets.mjs @@ -106,8 +106,25 @@ function isLocalRef(ref) { return !/^([a-z][a-z0-9+.-]*:|\/|#)/i.test(ref); } +// 递归收集**所有**页面,含子目录。只扫顶层的话 guide/* 会被静默漏掉 —— +// 不校验、不进 sitemap,而这两件事都不会有人注意到。 async function publishedPages() { - return (await readdir(siteDir)).filter((f) => f.endsWith('.html')).sort(); + const found = []; + + async function walk(dir, prefix) { + for (const entry of await readdir(dir, { withFileTypes: true })) { + if (entry.name.startsWith('.')) continue; + if (entry.isDirectory()) { + if (entry.name === 'assets') continue; // 产物,不是页面 + await walk(join(dir, entry.name), `${prefix}${entry.name}/`); + } else if (entry.name.endsWith('.html')) { + found.push(prefix + entry.name); + } + } + } + + await walk(siteDir, ''); + return found.sort(); } async function verifyReferences(pages) { @@ -115,13 +132,23 @@ async function verifyReferences(pages) { for (const page of pages) { const html = await readFile(resolve(siteDir, page), 'utf8'); + // 相对引用要按**页面自己所在的目录**解析,不是站点根 —— guide/ 下的 + // ../styles.css 指的是 website/styles.css,按站点根解会解到仓库外面去。 + const baseDir = dirname(resolve(siteDir, page)); const seen = new Set(); const check = (ref) => { const clean = ref.split(/[?#]/)[0]; if (!clean || !isLocalRef(clean) || seen.has(clean)) return; seen.add(clean); - if (!existsSync(resolve(siteDir, clean))) broken.push(`${page} → ${clean}`); + + const target = resolve(baseDir, clean); + if (!target.startsWith(siteDir)) { + // 跳出站点目录的引用在本地能跑(file:// 尚可),发布后一定 404 + broken.push(`${page} → ${clean}(跳出了站点目录)`); + return; + } + if (!existsSync(target)) broken.push(`${page} → ${clean}`); }; for (const [, ref] of html.matchAll(/(?:src|href)="([^"]+)"/g)) check(ref); @@ -161,7 +188,9 @@ function lastModified(page) { async function writeSitemap(pages) { const listed = pages.filter((p) => !SITEMAP_SKIP.has(p)); const urls = listed.map((page) => { - const loc = SITE_URL + (page === ENTRY ? '' : page); + // 目录索引的规范地址是目录本身:guide/index.html → /guide/ + const slug = page === ENTRY ? '' : page.replace(/index\.html$/, ''); + const loc = SITE_URL + slug; const stamp = lastModified(page); return ` <url>\n <loc>${loc}</loc>${stamp ? `\n <lastmod>${stamp}</lastmod>` : ''}\n </url>`; }); @@ -177,6 +206,260 @@ async function writeSitemap(pages) { return listed; } +// ── 顶栏 / 页脚 / 回到顶部的一致性 ────────────────────────────── +// +// 这三段被复制进每一个页面,好让每页都是「打开就能看」的独立文件(站点没有 +// 构建步骤是它的一条底线)。这种复制正是本仓库平时拒绝的东西 —— 让它可以 +// 接受的是下面这个校验:所有页面里这三段必须逐字节相同,不一致就让 CI 失败。 +// +// 比对前剥掉 `../`:子目录页面确实需要它,而目录深度不是漂移。 +const CHROME_BLOCKS = ['nav', 'footer', 'totop']; + +// Two pages are deliberately standalone and carry no chrome: +// 404.html —— 项目站会对任意深度的失效路径返回它,所有链接只能是 +// 站点绝对路径,套上顶栏反而会把相对链接带坏 +// privacy.html —— 从 docs/ 复制来的隐私政策,商店登记在册,保持原样 +const CHROME_SKIP = new Set(['404.html', 'privacy.html']); + +const normalizeChrome = (block) => block.replace(/\.\.\//g, '').trim(); + +async function verifyChrome(pages) { + const problems = []; + let checked = 0; + + for (const name of CHROME_BLOCKS) { + const re = new RegExp( + `<!-- chrome:${name}:start -->([\\s\\S]*?)<!-- chrome:${name}:end -->` + ); + let reference = null; + let referencePage = null; + + for (const page of pages) { + if (CHROME_SKIP.has(page)) continue; + const html = await readFile(resolve(siteDir, page), 'utf8'); + const m = re.exec(html); + if (!m) { + problems.push(`${page}: 缺少 chrome:${name} 块`); + continue; + } + checked += 1; + const block = normalizeChrome(m[1]); + if (reference === null) { + reference = block; + referencePage = page; + } else if (block !== reference) { + problems.push(`chrome:${name}:${page} 与 ${referencePage} 不一致`); + } + } + } + + return { problems, checked }; +} + +// 返回整块(含标记本身)—— 生成出来的页面必须连标记一起带上, +// 否则它自己就通不过上面的一致性校验。 +async function readChrome(name) { + const html = await readFile(resolve(siteDir, ENTRY), 'utf8'); + const m = new RegExp( + `<!-- chrome:${name}:start -->[\\s\\S]*?<!-- chrome:${name}:end -->` + ).exec(html); + if (!m) throw new Error(`chrome:${name} block not found in ${ENTRY}`); + return m[0]; +} + +// ── 更新日志页 ────────────────────────────────────────────────── +// +// 从 CHANGELOG.md 生成而不是手抄一份:手抄的那份在下一次发版就过期了,而且 +// 过期是静默的 —— 只有人主动去比对才会发现。这是 site-assets 里唯一「凭内容 +// 造页面」的部分,所以范围压到最小:只认 CHANGELOG.md 实际用到的那几种写法 +// (`##` 版本、`###` 小节、`-` 条目、行内 **粗体** 与 `代码`),遇到别的一律 +// 原样当段落输出,不抛错。 +const escapeHtml = (s) => + s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + +const inline = (s) => + escapeHtml(s) + .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') + .replace(/`([^`]+)`/g, '<code>$1</code>') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>'); + +function renderChangelog(md) { + const out = []; + let list = null; + let inRelease = false; + let inSection = false; + + const closeList = () => { + if (!list) return; + out.push(`<ul>${list.join('')}</ul>`); + list = null; + }; + const closeSection = () => { + if (!inSection) return; + out.push(' </div>'); + inSection = false; + }; + const closeRelease = () => { + closeSection(); + if (!inRelease) return; + out.push('</section>'); + inRelease = false; + }; + + for (const raw of md.split(/\r?\n/)) { + const line = raw.trimEnd(); + + // 文件顶部那个 `# Changelog`,页面自己有 h1,跳过 + if (/^#\s+/.test(line)) continue; + + const head = /^##\s+(.+)$/.exec(line); + if (head) { + closeList(); + closeRelease(); + // "v1.6.1 (2026-10-09)" → 版本与日期分开排,日期用等宽数字 + const m = /^(v?[\d.]+)\s*\((\d{4}-\d{2}-\d{2})\)$/.exec(head[1].trim()); + const version = m ? m[1] : head[1].trim(); + const date = m ? m[2] : ''; + out.push( + '<section class="release">', + ' <div class="release__head">', + ` <h2 class="release__version">${escapeHtml(version)}</h2>`, + ...(date ? [` <span class="release__date">${date}</span>`] : []), + ' </div>' + ); + inRelease = true; + continue; + } + + const kind = /^###\s+(.+)$/.exec(line); + if (kind) { + closeList(); + closeSection(); + out.push( + ' <div class="release__section">', + ` <h3 class="release__kind">${escapeHtml(kind[1].trim())}</h3>` + ); + inSection = true; + continue; + } + + const item = /^\s*[-*]\s+(.+)$/.exec(line); + if (item) { + list = list || []; + list.push(`<li>${inline(item[1])}</li>`); + continue; + } + + if (!line.trim()) { + closeList(); + continue; + } + + // 兜底:认不出的行原样当段落,不猜也不抛错 + closeList(); + out.push(` <p>${inline(line)}</p>`); + } + + closeList(); + closeRelease(); + return out.join('\n'); +} + +function pageShell({ title, description, path, bodyAttrs, chrome, content }) { + const url = SITE_URL + path; + return `<!DOCTYPE html> +<html lang="zh-CN" data-theme="dark"> +<head> +<meta charset="UTF-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1.0" /> +<title>${title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +${chrome.nav} + +
+${content} +
+ +${chrome.footer} + +${chrome.totop} + + + + +`; +} + +async function writeChangelog(chrome) { + const md = await readFile(resolve(root, 'CHANGELOG.md'), 'utf8'); + const releases = renderChangelog(md); + + const content = `
+

更新日志

+

+ 每个版本改了什么。完整历史也在 + GitHub 上。 +

+
+ +
+
+${releases} +
+
+`; + + await writeFile( + resolve(siteDir, 'changelog.html'), + pageShell({ + title: 'QuantDesktop 更新日志 —— 每个版本改了什么', + description: + 'QuantDesktop 各版本的更新记录:新增功能、修复的问题与行为变更。全部版本均免费,可从 GitHub Releases 或 Microsoft Store 升级。', + path: 'changelog.html', + bodyAttrs: 'data-page="changelog"', + chrome, + content, + }) + ); +} + async function main() { const missing = []; const copied = await assemble(missing); @@ -186,6 +469,14 @@ async function main() { process.exit(1); } + // 生成的页面要先落盘,后面的校验与 sitemap 才会把它算进去。 + const chrome = { + nav: await readChrome('nav'), + footer: await readChrome('footer'), + totop: await readChrome('totop'), + }; + await writeChangelog(chrome); + const pages = await publishedPages(); const broken = await verifyReferences(pages); @@ -195,10 +486,18 @@ async function main() { process.exit(1); } + const { problems, checked } = await verifyChrome(pages); + if (problems.length > 0) { + console.error('[site] chrome blocks differ between pages:'); + for (const p of problems) console.error(` - ${p}`); + process.exit(1); + } + const listed = await writeSitemap(pages); console.log(`[site] Copied ${copied} files into website/assets/`); console.log(`[site] ${pages.length} page(s): every local reference resolves.`); + console.log(`[site] ${checked} chrome blocks match across pages.`); console.log(`[site] sitemap.xml lists ${listed.length} URL(s).`); console.log('[site] Open website/index.html in a browser to preview.'); } diff --git a/website/download.html b/website/download.html new file mode 100644 index 0000000..ef8724e --- /dev/null +++ b/website/download.html @@ -0,0 +1,234 @@ + + + + + +下载 QuantDesktop —— Windows / macOS / Linux 安装包 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

下载 QuantDesktop

+

+ 免费、开源、无需注册。Windows 安装版约 5 MB,冷启动秒开。 + 所有安装包都在 GitHub Releases, + 版本号随时是最新的。 +

+
+ +
+ +

+ 不确定下哪个?Windows 用户直接选第一个(x64-setup.exe)。 + 需要免安装、放 U 盘里带走,才选便携版。 +

+ + + +
+

系统要求

+
    +
  • Windows —— Windows 10 1809 及以上。安装版会自带 WebView2 运行时;商店版使用系统预装的。
  • +
  • macOS —— Intel 与 Apple Silicon 均可,一个通用包。
  • +
  • Linux —— glibc 2.35 及以上(Ubuntu 22.04 基线)。发行版自带的 WebKitGTK 需可用。
  • +
+

没有账号系统,也不需要任何网络配置 —— 装完直接就能用。

+ +

三步开始看盘

+
    +
  1. 安装启动 —— 首次运行自动初始化,无需任何配置,也不要求注册账号。
  2. +
  3. 添加自选 —— 点「添加股票」,输入 6 位代码(如 600519 贵州茅台)搜索添加。
  4. +
  5. 开始看盘 —— 主窗口看全局,再把行情条拖到桌面角落,让它默默播报。
  6. +
+ +

安装时遇到问题

+

Windows 提示「已保护你的电脑」

+

+ 这是 SmartScreen 对未购买代码签名证书的独立软件的统一提示,不是杀毒软件报毒。 + 点「更多信息」→「仍要运行」即可。介意的话可以从 Microsoft Store 安装,那条分发路径没有这个提示。 +

+

关掉窗口之后应用不见了

+

+ 这是有意为之:点关闭只是收进系统托盘,行情条与后台轮询继续跑。 + 要真正退出,右键托盘图标选「退出」。 +

+

怎么升级到新版本

+

+ 应用会自己检查更新并提示,点一下就装好。商店版跟随商店自动更新。 + 也可以随时回 更新日志 看每个版本改了什么。 +

+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/faq.html b/website/faq.html new file mode 100644 index 0000000..ab9cff4 --- /dev/null +++ b/website/faq.html @@ -0,0 +1,276 @@ + + + + + +QuantDesktop 常见问题 —— 免费吗、要注册吗、数据从哪来 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

常见问题

+

+ 关于数据来源、隐私、许可与安装的常见疑问。没找到答案的话,欢迎到 + GitHub Issues 提问。 +

+
+ +
+
+ +

数据与隐私

+ +

使用 QuantDesktop 需要注册账号吗?

+

不需要。应用没有账号系统,下载装完直接就能用,也没有任何统计埋点。

+ +

会收集我的数据吗?

+

不会。自选股、界面偏好、窗口位置全部保存在本机 SQLite 数据库,不上传任何服务器,卸载应用后随之删除。

+ +

行情数据从哪里来?可靠吗?

+

来自腾讯证券与新浪财经的公开行情接口,两个数据源互为备份,其中一个异常时自动回退到另一个。数据仅供参考,不构成投资建议。

+ +

卸载后本地数据会残留吗?

+

Windows 下数据在 %APPDATA%\quant-desktop,便携版在程序目录的 data 子目录下;卸载应用不会自动删除这些文件,手动删掉该目录即可彻底清除。

+ +

费用与许可

+ +

真的完全免费吗?

+

个人使用完全免费,没有内购、没有会员、没有功能墙。项目采用 PolyForm Noncommercial 非商业开源许可,商业使用需要获得作者授权。

+ +

可以用在公司或商业环境里吗?

+

不可以直接商用。许可是 PolyForm Noncommercial 1.0.0,个人使用、学习、修改、分发免费,商业使用需先取得作者授权。它不是 MIT。

+ +

有广告或弹窗推广吗?

+

没有。应用不含任何广告、推送或第三方分析 SDK,不会推销理财产品,也没有需要关闭的启动页。

+ +

系统与使用

+ +

支持哪些操作系统?

+

Windows 10 与 11、macOS(Intel 与 Apple Silicon),以及 Linux(glibc 2.35 及以上,Ubuntu 22.04 基线)。

+ +

支持哪些市场?港股美股能看吗?

+

目前覆盖沪深京三市的个股、指数与 ETF。港股与美股在规划中,尚未支持。

+ +

刷新频率是多少?可以自己调吗?

+

按交易时段自动调节,不需要手动设置:开盘探测每 2 秒、正常交易 2 秒、盘前与午休 5 到 10 秒、连续无变化或节假日降到 30 秒。

+ +

Windows 提示「已保护你的电脑」是怎么回事?

+

这是 SmartScreen 对未购买代码签名证书的独立软件的统一提示,不是报毒。点「更多信息」再点「仍要运行」即可;也可以改从 Microsoft Store 安装。

+ +

关闭窗口后应用去哪里了?

+

点关闭只是收进系统托盘,行情条和后台轮询继续运行。要彻底退出,右键托盘图标选「退出」。

+ +
+ 还有别的问题?到 + GitHub Issues + 提一个,或者看看 使用指南。 +
+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/guide/boss-key.html b/website/guide/boss-key.html new file mode 100644 index 0000000..27436d8 --- /dev/null +++ b/website/guide/boss-key.html @@ -0,0 +1,202 @@ + + + + + +老板键怎么设 —— QuantDesktop 使用指南 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

设一个老板键

+

+ 按一下,主窗口立刻消失。行情条不受影响,配置也不会被改掉 —— + 这是一次临时躲避,不是把设置动过。 +

+
+ +
+
+ +

怎么设

+

+ 打开「设置 → 通用」,找到「老板键」一行,点录制框,直接按下你想要的组合键。 + 录完立刻生效,不用重启。 +

+

+ 默认是不设置的 —— 应用不占用任何全局快捷键,除非你自己选一个。 + 想取消,把录制框清空即可,旧键会同时被注销。 +

+ +

它能按什么键

+

+ 任意组合键都可以,比如 Ctrl+Shift+H、Ctrl+Alt+Q、F9。 + 但有一个限制:裸键,或只带 Shift 的组合,不会被接受。 +

+

+ 原因是这类按键会从整个系统把字符抢走:把 H 设成老板键之后, + 在任何程序里打字都打不出 h 了。带上 Ctrl 或 Alt 就不会有这个问题。 +

+ +

按下之后

+

+ 主窗口隐藏,行情条照常显示,后台轮询也照常跑。 + 这是刻意的:如果你正靠行情条盯盘,老板键不该把它一起收走。 +

+

+ 怎么回来?点一下托盘图标,或者单击行情条 —— 两条路都会把主窗口叫回来并置于前台。 +

+ +

如果录了没反应

+

多半是这个组合已经被别的程序占用了。换一个再试。

+

+ 设置页会在这个键注册失败时给出提示,并且回滚到你原来那个键 —— + 不会出现「新的用不了、旧的也丢了」的情况。 +

+ +
+ 回到 使用指南,或者看看 + 悬浮行情条 怎么摆。 +
+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/guide/groups.html b/website/guide/groups.html new file mode 100644 index 0000000..1f70f86 --- /dev/null +++ b/website/guide/groups.html @@ -0,0 +1,208 @@ + + + + + +自选分组怎么用 —— QuantDesktop 使用指南 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

用分组把自选理清楚

+

+ 分组不是文件夹,更像标签 —— 同一只股票可以同时出现在几个分组里, + 在每个分组里的位置还互不干扰。 +

+
+ +
+
+ +

先把关系说清楚

+

+ 自选是一份全局的股票池:一只股票只存一次。分组是池子上的标签, + 标签之间互相独立。所以: +

+
    +
  • 把一只已经在「长线」里的股票加进「今天看」,它同时属于两个分组,不会从「长线」里搬走。
  • +
  • 把同一只股票重复加进它已经在的分组,什么也不会发生,它原来排第几还是第几。
  • +
  • 同一只股票在「长线」里排第 3,在「今天看」里可以排第 1 —— 两个顺序各记各的。
  • +
+

这正是它和「文件夹」的区别:挪动不会牵一发而动全身。

+ +

日常操作

+

建一个分组

+

点标签栏最右边的「+」新建,名字随手取。

+ +

改名

+

在标签上双击就地改名,不用进设置页。

+ +

调整分组顺序

+

右键标签选「上移」或「下移」,标签栏从左到右的顺序就是这里决定的。

+ +

把股票加进某个分组

+

+ 在自选表上右键那只股票,子菜单里勾选分组即可,支持多选。 + 和上面说的一样,勾选是「加上」而不是「移动」。 +

+ +

删除分组时会发生什么

+

+ 删除前会有一个确认框,把影响面算给你看: +

+
    +
  • 只属于这一个分组的股票,会并入默认分组 —— 不会丢。
  • +
  • 还留在其他分组里的股票,完全不受影响。
  • +
  • 自选本身不会被删除,删掉的只是分组这个标签。
  • +
+

+ 想彻底删掉一只股票,用自选表右键菜单里的「删除」,那条路径与分组无关。 + 另外最后一个分组不允许删除 —— 总得有个地方放自选。 +

+ +
+ 相关:摆好那枚悬浮行情条(轮播范围是跨分组挑的), + 或者回到 使用指南。 +
+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/guide/index.html b/website/guide/index.html new file mode 100644 index 0000000..c91f7a5 --- /dev/null +++ b/website/guide/index.html @@ -0,0 +1,182 @@ + + + + + +QuantDesktop 使用指南 —— 行情条、自选分组、老板键 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

使用指南

+

+ QuantDesktop 的界面刻意做得简单,但有几处只有用过才知道的细节。每篇只讲一件事。 +

+
+ +
+ + +
+

还没装?

+

+ 先去 下载页 拿一个安装包,回来照着上面几篇试一遍就行。 + 装完不需要任何配置,也没有账号要注册。 +

+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/guide/ticker.html b/website/guide/ticker.html new file mode 100644 index 0000000..63345f2 --- /dev/null +++ b/website/guide/ticker.html @@ -0,0 +1,208 @@ + + + + + +悬浮行情条怎么设置 —— QuantDesktop 使用指南 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

摆好那枚悬浮行情条

+

+ 它是这个工具最受欢迎的部分:一枚常驻桌面、始终置顶的迷你报价条。 + 这里讲怎么把它调成适合你工位的样子。 +

+
+ +
+
+ +

它默认长什么样

+

+ 首次启动后,行情条出现在屏幕右下角,无边框、不占任务栏、始终浮在其他窗口之上。 + 每 3 秒自动翻一页,默认一页显示 2 只自选,显示名称、现价和涨跌幅。 +

+

鼠标移上去它会暂停轮播,方便你停下来看清楚;移开继续。

+ +

把它拖到顺手的位置

+

+ 直接拖动行情条本体即可。位置会自动记住,下次启动还在原地。 + 如果换了显示器、或把窗口拖到了已经拔掉的屏幕上,启动时会自动拉回可见区域,不会出现「窗口不见了」的情况。 +

+

单击行情条会立刻把主窗口叫回来 —— 这也是主窗口收进托盘之后最快的恢复方式。

+ +

决定播报哪几只、每屏几只

+

+ 打开设置页的「行情条」分区,两件事都在这里: +

+
    +
  • 每屏条数 —— 预设 1 到 4,也可以用「自定义」填到 10。窗口高度会跟着内容走。
  • +
  • 轮播范围 —— 跨分组挑选要播报的股票,还能拖拽调整播报顺序。不需要的逐只关掉即可。
  • +
+

+ 轮播范围是一条跨分组的扁平列表,与你把股票放在哪个分组无关。 + 所以「只播报持仓那几只」这种需求,不需要为此单独建一个分组。 +

+ +

设成透明背景,只留数字

+

+ 在「行情条」分区里打开「透明背景」。底板、圆角与阴影会一起去掉, + 只剩数字浮在桌面上,看起来就像桌面本身的一部分。 +

+

+ 浅色桌面上用浅色主题的数字,或者干脆配一张纯色壁纸,读起来最舒服。 +

+ +

不要它了呢

+

+ 设置页「行情条」分区里关掉「显示行情条」,或者右键托盘图标选「切换行情条」。 + 两种方式效果一样,都会记住你的选择,下次启动照旧。 +

+ +
+ 下一步可以看 用分组把自选理清楚, + 或者回到 使用指南。 +
+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/website/index.html b/website/index.html index 802869b..690ba6b 100644 --- a/website/index.html +++ b/website/index.html @@ -161,21 +161,29 @@ - - - + + + + +
@@ -729,6 +738,7 @@

三步开始看盘

全部安装包都在 GitHub Releases, 也可以从 Microsoft Store 安装(随商店自动更新)。 + 不确定选哪个?看各平台文件说明。

@@ -836,12 +846,18 @@

你可能想问的

+ + +

+ 查看全部常见问题 → +

+ + +

支持

+ diff --git a/website/styles.css b/website/styles.css index c3eafde..1b8594a 100644 --- a/website/styles.css +++ b/website/styles.css @@ -324,6 +324,14 @@ button { font-size: 1rem; } +/* 区块末尾的「查看更多」。首页只放每条内容的摘要,完整版在各自的页面里, + 这个链接就是那条路径。 */ +.section__more { + margin-top: var(--space-6); + text-align: center; + font-weight: 600; +} + /* ── Buttons ── */ .btn { display: inline-flex; @@ -458,6 +466,7 @@ button { } .nav__link { + position: relative; padding: 8px 12px; border-radius: var(--radius-xs); color: var(--text-2); @@ -475,6 +484,35 @@ button { background: rgba(15, 23, 42, 0.05); } +/* 当前页指示器。所有导航项都带这一条,默认靠 --current 隐掉。 */ +.nav__link::after { + content: ""; + position: absolute; + /* 左右 12px 正是 .nav__link 的内边距 —— 于是线宽**等于文字宽**。 + 按内边距外沿算的话线会比文字宽一截,那看着像一颗带下边框的按钮。 */ + left: 12px; + right: 12px; + bottom: 4px; + height: 2px; + border-radius: 1px; + /* 两端渐隐。全站只有两处用渐变线,另一处是顶栏滚动后那条 ::after 发丝线 + (.nav.is-scrolled::after),于是这两条线互相解释。 */ + background: linear-gradient( + 90deg, + transparent, + var(--accent) 24%, + var(--accent) 76%, + transparent + ); + /* 用 drop-shadow 而不是 box-shadow:它跟着渐变渲染后的 alpha 走,于是辉光 + 只裹在可见的那段线上;box-shadow 会按元素框发一圈方形的光。 + 这一下是为了与站点已有的辉光语言对齐 —— 主按钮和回到顶部的进度环都亮着, + 指示器不亮会显得是贴上去的。 */ + filter: drop-shadow(0 0 5px var(--glow-accent)); + opacity: var(--current, 0); + transition: opacity var(--transition); +} + .nav__actions { display: flex; align-items: center; @@ -1737,6 +1775,233 @@ button { visibility: visible; } +/* ================================================================ + 内容页(faq / download / changelog / guide/*) + + 这些页面共用首页那套 token 与顶栏页脚,只是版式换成阅读型:一个标题区、 + 一条正文列、必要时加卡片网格。顶栏是 fixed 的,而内容页没有 hero 撑高度, + 所以 .page-head 自己让出 nav 的空间。 + ================================================================ */ + +.page-head { + padding: calc(var(--nav-height) + clamp(40px, 6vw, 64px)) 0 clamp(28px, 4vw, 40px); +} + +.page-head__title { + font-size: clamp(1.7rem, 3.6vw, 2.4rem); + font-weight: 700; + letter-spacing: -0.02em; +} + +.page-head__lede { + margin-top: var(--space-4); + max-width: 46em; + color: var(--text-2); + font-size: 1.02rem; +} + +/* 面包屑只出现在 guide/*:那是全站唯一的三级路径。平铺的页面用不到, + 硬加反而是噪音(见 ux 规则的 breadcrumb-web:3+ 层才用)。 */ +.crumb { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: var(--space-5); + font-family: var(--font-mono); + font-size: 0.76rem; + letter-spacing: 0.06em; + color: var(--text-2); +} + +.crumb a { color: var(--text-2); } +.crumb a:hover { color: var(--accent); } +.crumb i { font-style: normal; color: var(--hairline-strong); } + +/* 正文列。中文每行 35–45 字最省眼,680px 在 17px 下正好落在这一带 —— + 比首页那种 1072px 的通栏窄得多,那宽度读长文会来回找行首。 */ +.prose { + max-width: 680px; + font-size: 1rem; + line-height: 1.8; + color: var(--text-2); +} + +.prose > * + * { margin-top: var(--space-5); } + +.prose h2 { + margin-top: clamp(36px, 5vw, 52px); + padding-bottom: 10px; + border-bottom: 1px solid var(--hairline); + font-size: 1.22rem; + font-weight: 700; + color: var(--text-1); +} + +.prose h3 { + margin-top: var(--space-6); + font-size: 1.02rem; + font-weight: 700; + color: var(--text-1); +} + +.prose strong { color: var(--text-1); font-weight: 600; } +.prose a { border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); } +.prose ul { padding-left: 1.25em; list-style: disc; } +.prose ol { padding-left: 1.5em; list-style: decimal; } +.prose li { margin-top: 8px; } +.prose li::marker { color: var(--text-2); } + +.prose code { + padding: 2px 6px; + border: 1px solid var(--hairline); + border-radius: var(--radius-xs); + /* 用 color-mix 而不是写死的白色叠加:浅色主题下 rgba(255,255,255,.04) + 在白底上等于没有 */ + background: color-mix(in srgb, var(--text-1) 7%, transparent); + font-family: var(--font-mono); + font-size: 0.86em; + /* 正文色而不是强调色:强调色在这段正文里是**链接**的颜色,代码再用它就 + 分不清哪个能点了。而且强调色压在这层 7% 底色上,浅色主题只有 4.51:1, + 贴着 4.5 的线;正文色是 14:1。等宽字体本身已经把代码和正文分开了。 */ + color: var(--text-1); + /* 命令不能在中间断行 —— 断成两行的 `chmod` / `+x` 复制出去是错的 */ + white-space: nowrap; +} + +/* 提示块。左侧那道竖条是形状不是颜色,去掉颜色也还读得出来。 */ +.note { + padding: var(--space-5); + border: 1px solid var(--hairline); + border-left: 3px solid var(--accent); + border-radius: var(--radius-sm); + background: var(--card-fill); + box-shadow: var(--sheen); + font-size: 0.94rem; +} + +/* 卡片网格:下载页的平台卡、指南索引都用它 */ +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); + gap: var(--space-4); +} + +.card { + display: block; + padding: var(--space-6); + border: 1px solid var(--hairline); + border-radius: var(--radius-md); + background: var(--card-fill); + box-shadow: var(--sheen); + color: inherit; + transition: border-color var(--transition), background var(--transition), + transform var(--transition); +} + +a.card:hover { + border-color: var(--hairline-strong); + background: var(--card-fill-hover); + transform: translateY(-2px); +} + +.card h3 { font-size: 1.02rem; font-weight: 700; color: var(--text-1); } +.card p { margin-top: 8px; color: var(--text-2); font-size: 0.92rem; } + +.card__meta { + display: block; + margin-top: var(--space-4); + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + font-size: 0.72rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-2); +} + +/* ── 更新日志(由 site-assets.mjs 从 CHANGELOG.md 生成) ── */ +.release + .release { margin-top: clamp(36px, 4vw, 52px); } + +.release__head { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--space-3); + padding-bottom: 10px; + border-bottom: 1px solid var(--hairline); +} + +.release__version { + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + font-size: 1.05rem; + font-weight: 700; + color: var(--accent); +} + +.release__date { color: var(--text-2); font-size: 0.85rem; } +.release__section { margin-top: var(--space-5); } + +/* 版本号是 h2(层级上对),但分隔线画在 .release__head 上,这里要撤掉 + .prose h2 自带的那条,否则同一条线画两遍 */ +.prose .release__version { + margin-top: 0; + padding-bottom: 0; + border-bottom: none; +} + +.release__kind { + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-2); +} + +.release ul { margin-top: 10px; padding-left: 0; list-style: none; } + +.release li { + position: relative; + padding-left: 18px; + margin-top: 8px; + color: var(--text-2); + font-size: 0.94rem; +} + +/* 等宽短横代替默认圆点:与终端的视觉语言一致,也不依赖字体画不画得出圆点 */ +.release li::before { + content: "–"; + position: absolute; + left: 0; + font-family: var(--font-mono); + color: var(--text-3); +} + +/* ── 当前页高亮 ── + **刻意不做「淡色底块 + 通栏下划线」那一套。** 那是模板与生成式工具的默认 + 答案;而且两个信号互相打架 —— 底块已经说了「就是这里」,再压一条粗线是 + 重复,合起来还会被读成一颗带下边框的按钮。技能库里那条规则的原话也只是 + 「Highlight active nav item with color/underline」,没有底块。这里只留 + 一个信号:标签提亮 + 一条与文字等宽的渐隐短横。 + + 状态收进一个 `--current` 变量,颜色与指示器都从它派生 —— 于是「哪个 + data-page 对应哪个导航项」这份清单**只写一遍**。拆成两个选择器块的话, + 两份清单迟早对不上,而症状只是某一页的指示器没出现,没人会发现。 + + **不能写进顶栏那段 HTML**:它必须在所有页面逐字节相同,由 + site-assets.mjs 校验。所以状态挂在 上。 + + 加新页面时给它一个 data-page 值并在下面登记一行。这里没有 download: + 它的入口是顶栏那颗常驻 CTA 按钮,不是导航项,硬凑一个选择器只会是死代码。 */ +body[data-page="home"] .nav__link[href$="index.html#features"], +body[data-page="faq"] .nav__link[href$="faq.html"], +body[data-page="changelog"] .nav__link[href$="changelog.html"], +body[data-page="guide"] .nav__link[href$="guide/"] { + --current: 1; + color: var(--text-1); +} + /* ── 入场(需要 app.js 给 加 .js) ── */ .js .reveal { opacity: 0; @@ -1796,6 +2061,21 @@ button { font-size: 1rem; } + /* 手机菜单是竖排列表,横向下划线在这里既没有方向感、又会因为 + align-items: stretch 被拉成通栏。改成左侧竖条 —— 竖排列表里用竖条, + 横排里用横线,这是同一个「方向跟着布局走」的规则。 */ + .nav__link::after { + left: 0; + right: auto; + top: 50%; + bottom: auto; + width: 2px; + height: 18px; + margin-top: -9px; + border-radius: 0 1px 1px 0; + background: var(--accent); + } + .nav__burger { display: inline-flex; }