# dsh-clock **Repository Path**: dragonlhp/dsh-clock ## Basic Information - **Project Name**: dsh-clock - **Description**: DSH Web 顶部常驻时钟插件:显示年月日时分秒与星期,节日/节气当天显示名称 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-01 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # clock DeepSeek Harness(DSH)Web 的**顶部常驻时钟**插件:在窗口顶部水平正中显示当前日期与时间,并在当天命中节日或节气时附带名称;该时钟胶囊同时是可点击入口,点击后从正下方展开一个日历面板,可查看整月的公历 / 农历与节日节气。 - 插件是「浏览器半 UI 插件」:界面全部由浏览器半提供,宿主半(Node)不注册工具、不提供服务。 - 零第三方运行时依赖:农历借助运行时内置的 `Intl.DateTimeFormat('zh-CN-u-ca-chinese')`,其余均为内置日期运算。 - 不写文件、不发起网络请求、不保存任何持久化状态。 - 目标宿主:DSH Web `0.2.0-rc.2`。 ## 它做什么 在任意主面板(会话 / 设置 / 插件管理)顶部正中常驻一行胶囊,内容形如: ``` 2026年9月30日 星期三 09:05:07 ``` - 日期:`YYYY年M月D日`(月、日不补零)。 - 星期:`星期X`。 - 时间:`HH:mm:ss`(24 小时制,时/分/秒补零,本地时区),每 1 秒刷新一次。 - 命中节日时在末尾追加 ` · ` 连接的名称,例如 `2026年3月20日 星期五 09:05:07 · 龙抬头 · 春分`。 - 未命中时不显示任何分隔符或占位。 - 窄窗口(宽度不超过 680px)下自动隐藏日期与星期,只保留时间与节日。 ### 点击展开日历面板 胶囊由纯展示升级为可交互入口:鼠标点击或聚焦后按 `Enter` / `Space` 即展开日历面板,胶囊本身始终可见。 **开合方式** - 展开:点击胶囊(或 `Enter` / `Space`),面板从胶囊**正下方水平居中**滑出,宽度 336px(`max-width: calc(100vw - 24px)` 适配窄屏)。 - 关闭:再次点击胶囊、点击胶囊与面板之外的区域、或按 `Esc`;`Esc` 关闭后焦点回到胶囊。 - 无障碍:胶囊带 `role="button"` / `aria-haspopup="dialog"` / `aria-expanded`,面板带 `role="dialog"`。 **面板内容** - 标题为可选的年份与月份下拉,切换后以滑动动画跳到目标年月。 - 星期表头固定 `日 一 二 三 四 五 六`(**周日为首列**)。 - 月网格固定 6 行 × 7 列 = 42 格,含相邻月补白;不属于当前月的补白格弱化显示。 - 每格主显公历日;副行按顺序取一:命中节日/节气时显示名称(沿用既有优先级阶梯中的第一个),否则显示农历日文本,农历初一显示农历月名(闰月带「闰」前缀)。 - **今天用圆形描边圈住**;今天不属于当前显示的月份时不显示该标记。 - 底部「今天」按钮:一键滑回包含今天的月份。 **滑动式月份切换**(不是瞬间替换) - 标题两侧的 `‹` / `›` 箭头按钮。 - 鼠标在日历区域按住左右拖动:跟手位移,释放时超过阈值 `max(48px, 面板宽度 × 20%)` 则吸附切月,否则回弹。 - 滑动容器内同时预渲染 上一月 / 当前月 / 下一月 三块,位移过程中相邻月可见。 - 面板聚焦时支持键盘 `←` / `→`。 - 跨年正确:1 月的上一月为上年 12 月,12 月的下一月为次年 1 月。 ### 节日规则 **公历固定节日**(按月/日匹配) | 日期 | 名称 | 日期 | 名称 | | --- | --- | --- | --- | | 1/1 | 元旦 | 7/1 | 建党节 | | 2/14 | 情人节 | 8/1 | 建军节 | | 3/8 | 妇女节 | 9/10 | 教师节 | | 3/12 | 植树节 | 10/1 | 国庆节 | | 4/1 | 愚人节 | 12/24 | 平安夜 | | 5/1 | 劳动节 | 12/25 | 圣诞节 | | 5/4 | 青年节 | 6/1 | 儿童节 | **农历节日**(按农历平月/日匹配,闰月不参与) | 农历日期 | 名称 | | --- | --- | | 正月初一 | 春节 | | 正月十五 | 元宵节 | | 二月初二 | 龙抬头 | | 五月初五 | 端午节 | | 七月初七 | 七夕节 | | 七月十五 | 中元节 | | 八月十五 | 中秋节 | | 九月初九 | 重阳节 | | 腊月初八 | 腊八节 | | 腊月廿三 | 小年 | | 次日为农历正月初一的那天 | 除夕 | **二十四节气**(全部 24 个,用「寿星公式 + sTermInfo 分钟表」计算,适用 1900–2100) 小寒、大寒、立春、雨水、惊蛰、春分、清明、谷雨、立夏、小满、芒种、夏至、小暑、大暑、立秋、处暑、白露、秋分、寒露、霜降、立冬、小雪、大雪、冬至。 **浮动节日** | 规则 | 名称 | | --- | --- | | 5 月第 2 个周日 | 母亲节 | | 6 月第 3 个周日 | 父亲节 | | 11 月第 4 个周四 | 感恩节 | ### 同日多命中 同一天可能同时命中多个名称,按下列优先级排序、去重,最多显示 2 个: ``` 除夕 / 春节 > 其它农历节日 > 节气 > 公历固定节日 > 浮动节日 ``` 例:`2026-03-20` 显示「龙抬头 · 春分」,`2026-06-21` 显示「夏至 · 父亲节」。 ## 工作原理 ### 宿主半与浏览器半 插件由两个产物组成: - **宿主半** `src/index.ts` → `lib/index.js`:由 cordis 加载器在 Node 侧激活,只有一个空的 `apply()`。它存在的唯一意义是让插件能被宿主正常加载,不注册任何工具、不提供服务。 - **浏览器半** `src/client/index.tsx` → `lib/client.js`:界面与全部逻辑都在这里,由 tsdown 包装成 `window.__ModuleLoader__.load({ id: "clock", factory })` 工厂,在浏览器中注册进 DSH Web。 纯函数日历逻辑(时间格式化、星期、节日与节气解析)独立在 `src/calendar.ts`,可被浏览器半与 Node 原生测试共用。 ### 挂载点与定位 浏览器半导出 `{ name, inject, apply }`,`inject: ['slots']`,在 `apply` 中注册到宿主插槽 `shell.overlay`: ```ts { name: 'shell.overlay', id: 'clock.display', order: 100 } ``` `shell.overlay` 是全窗口浮层、list 基数的新增式插槽。选择它是因为它天然覆盖整个窗口且支持点击穿透,能把时钟固定在窗口顶部正中,而不必依赖 `position: fixed` 的包含块假设: - 外层 `.dsh-clock-overlay`:`position: absolute; inset: 0; pointer-events: none`(铺满但点击穿透)。 - 内层胶囊:`position: absolute; top: 0; left: 50%; transform: translateX(-50%)`,因此水平居中于窗口。 ### 日历面板与滑动切换 - 挂载点不变:面板与胶囊同属 `shell.overlay` 插槽的同一组件树,随胶囊一起注册、一起回收。 - 交互恢复:外层浮层仍为 `pointer-events: none`,但胶囊与面板显式 `pointer-events: auto`,浮层其余区域保持点击穿透。 - 月网格:由 `src/calendar.ts` 的 `monthCells(year, month, today)` 生成固定 42 格(6 行 × 7 列),直接给出每格主显公历日与副行农历/节日文本。 - 滑动切换:滑动容器宽 300%,内部并排预渲染 `[上一月][当前月][下一月]` 三块(各占 1/3),静止时 `translateX(-33.3333%)` 让当前月居中;拖拽时叠加像素位移跟手,释放后吸附切月或回弹;动画结束后把位移归零并同步年月,因此可连续滑动。 - 箭头、键盘 `←` / `→`、年月下拉与「今天」共用同一套位移动画;跨多月的下拉跳转先把目标月放到侧面再滑入,视觉上仍是滑动而非瞬间替换。 - 拖拽判定抽成纯函数 `src/swipe.ts`:阈值 `max(48px, 面板宽度 × 20%)`,面板宽度未知时回退为 48px。 ### 注册与清理 所有注册都在 effect 作用域内完成:插槽注册通过 `ctx.slots.inject(...)` / `ctx.slots.register(...)` 的 disposer 回收,`setInterval` 在注册 effect 的清理函数中清除,样式经插件自有的 `