# dsh-ui-web

> DSH 网页端的皮肤中心：在 GUI 设置页里即时试穿、一键应用皮肤，热载入无需重启。

## Metadata

- Author: [@CAPTAIN1275](https://github.com/CAPTAIN1275)
- Repo: <https://github.com/CAPTAIN1275/dsh-ui-web.git>
- GitHub: [CAPTAIN1275/dsh-ui-web](https://github.com/CAPTAIN1275/dsh-ui-web)
- Stars: 34
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Topics: `dsh-plugin`, `dsh-plugin-market`, `dsh-plugins`
- Forks: 2
- Open Issues: 0
- Last push: 2026-08-16T18:08:27.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/skins/skin-center
```

## Wiki

## 一句话定位
DSH 网页端的皮肤中心，把皮肤列表、试穿、一键应用内嵌进 GUI 的"插件配置 → Web UI 插件"卡片组。它通过 host 半区路由写入 `~/.dsh/cordis.patch.yml`，DSH 配置 watcher 自动热载入，无需重启。

## 核心能力
- 在 GUI 设置页里集中浏览皮肤："官方默认"+ 已安装的全部皮肤（aurora / ths / xp / blue-fantasy / dragon-heir / minecraft / whale-song / trading / miku），每条带名称、tagline、强调色色卡，当前激活目标带 Active 标记。
- 真实试穿：点击"试穿"按需加载皮肤的 `lib/client.js`，走页面自己的模块加载器（`<script>` 同源注入 + `__ModuleLoader__.load`），不依赖 `eval`；亮/暗主题切换走官方 theme 服务，"退出试穿"完整还原激活皮肤的状态。
- 一键应用：host 路由 `/api/skin-center/apply` 执行与 `dsh-skin use` 等价的内嵌实现，写入 `~/.dsh/cordis.patch.yml` 的 `dsh-skin managed` 段，DSH 配置 watcher 秒级热载入后浏览器自动刷新页面，**无需重启**；`official` 目标会把所有皮肤禁用，回到 DSH 官方原貌。
- 互斥保险：试穿期间按配方暂时撤掉当前激活皮肤的视觉写面（body 属性、背景内联样式、chrome 子节点），退出后按原顺序、原快照原样恢复；同一时刻页面上永远只有一套皮肤生效。
- 背景遮挡：卡片内置 0–100 的背景遮罩滑块，把数值写到 `--dsw-skin-scrim` CSS 变量；只对会画背景插画的皮肤（蓝色幻想 / 鲸吟）即时生效，官方默认无背景图时滑块保持持久但视觉无变化。
- 跨站围栏：host 路由对 `Sec-Fetch-Site=cross-site` 与 Origin/Host 不匹配直接 403，仅同源 fetch 可调用 `/apply` 与 `/state`，防止恶意网页通过 localhost CSRF 改写用户皮肤配置。

## 技术实现
- **语言**: TypeScript + React 18（jsx: react-jsx，target es2024）
- **关键依赖**: `@deepseek-ai/cordis`（host 半区运行时）、`@deepseek-ai/dsh-client-runtime` / `dsh-client-ui-theme` / `dsh-client-ui-settings` / `dsh-client-ui-slots` / `dsh-client-locale`（client 半区 SDK）、`schemastery`（设置命名空间 Schema 定义）
- **架构模式**: cordis 双半区 bundle。`src/index.ts` 是 host 半区（cordis plugin `ui-skin-center`，注册 `/api/skin-center/{state,apply,bundle/<id>}` 路由并声明 `skin-background` 设置命名空间）；`src/client/index.ts` 是 browser 半区（DSH 客户端插件，挂载到 `web-ui.plugin.item` 槽位下的"Skin Center"卡片）；皮肤切换的 host 实现 `src/skin-switch.ts` 是 `scripts/dsh-skin` CLI 的 1:1 ESM 端口——不再依赖 `dsh-skin` 二进制在 PATH。
- **入口文件**: `src/index.ts`（host apply）+ `src/client/index.ts`（browser apply）；cordis patch 在 `cordis.patch.yml` 中插入 id `ui-skin-center`。

## 适用场景
觉得在终端里敲 `dsh-skin use <name>` 再等刷新太繁琐、想在 GUI 里直接看到皮肤实际效果再决定是否切换的人：卡片支持在真实 GUI 上"试穿 → 退出还原 → 一键应用"的完整流程，亮/暗主题在试穿时也可即时切换预览。想要在画插画背景的皮肤上加一层遮罩让面板更突出的人：卡片内置的 0–100 背景遮挡滑块只对画背景的皮肤生效。装了多个皮肤、希望把整套皮肤切换入口集中到一个地方的人：本插件同时承担"加载器 + 渲染器"职责，注册表由 `packages/skins/<id>/skin.json` 自动扫描生成，新皮肤加入即出现在卡片列表里。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| @deepseek-ai/dsh-* | ^0.1.0-rc.6 | host / client 半区都通过 SDK 注入服务；皮肤中心把对官方 DSH 的耦合吸收在内 |
| React | ^18.2.0 | peerDependency，`SkinCenter.tsx` 使用 React hooks |
| Node | ^22.19 \|\| >=24 | 仓库根 AGENTS.md 强制要求；package.json 未声明 engines，但 host 侧使用 `node:fs` / `node:os` / `node:path` |
| 平台 | 跨平台 | macOS / Windows / Linux 全部支持；Windows 用目录 junction 兜底无权限场景，DSH 主目录优先 `$DSH_HOME`，仓库根从脚本位置推导 |
| 原生模块 | 无 | 无 native binding 依赖；样式由皮肤自带 CSS 提供，lightningcss 仅在仓库根 devDependencies |

## 安装方式
```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/skins/skin-center
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| skin-background.backgroundOpacity | 0–100 | 主页背景遮罩强度；写入 body CSS 变量 `--dsw-skin-scrim`，仅对画背景插画的皮肤（蓝色幻想 / 鲸吟）即时生效，官方默认无背景图时视觉无变化但值仍持久化 | 0 |

## 常见问题

**Q: 一键应用皮肤后需要重启 DSH 吗？**

A: 不需要。点击"应用"或"恢复默认"后，host 端把目标皮肤写入 `~/.dsh/cordis.patch.yml` 的 `dsh-skin managed` 段，DSH 自带的配置 watcher 在数秒内热载入新补丁，浏览器自动刷新页面即生效。

**Q: 试穿会改动当前激活的皮肤吗？**

A: 不会。试穿仅在浏览器侧临时收回当前皮肤的 body 属性、背景内联样式和 chrome 子节点，"退出试穿"会按原快照、原顺序原样恢复；激活皮肤自身的 cordis 纤维全程不被触碰，状态完全保留。

**Q: 皮肤还没构建 lib/client.js 时试穿会怎样？**

A: 试穿会报错并自动还原激活皮肤。host 路由 `/api/skin-center/bundle/<id>` 在目标 `lib/client.js` 不存在时返回 404，前端 catch 后调用 controller 的还原分支，绝不会留下半套皮肤。

**Q: Windows 上能用吗？**

A: 可以。皮肤切换在 Windows 下用目录 junction（绝对路径）创建 profile node_modules 链接，不需要开发者模式或管理员权限；DSH 主目录优先读 `$DSH_HOME`，缺省 `~/.dsh`，仓库路径由脚本自身位置推导，不依赖 `$HOME` 或固定路径。

**Q: 跨站请求会被拒绝吗？**

A: 会。host 路由对 `Sec-Fetch-Site=cross-site` 直接返回 403，`Origin` 与 `Host` 不匹配同样拒绝，仅同源 fetch 可调用 `/api/skin-center/apply` 与 `/state`，防止恶意网页通过 localhost CSRF 改写用户的皮肤配置。

**Q: 怎么添加一款新皮肤到列表里？**

A: 在 `packages/skins/<id>/` 放一份 `skin.json`（id 匹配 `[a-z0-9-]+`，`package` 与 `wiring.id` 必填），再为该皮肤构建 `lib/client.js`。注册表由脚本扫描 `skin.json` 自动生成，无需修改皮肤中心代码；重建 `generated/skins.ts` 即可在卡片里看到新条目。

**Q: 背景遮挡滑块有什么用？**

A: 它控制主页背景的遮罩强度（0–100，默认 0），把数值写到 `document.body` 的 `--dsw-skin-scrim` CSS 变量上。仅对会画背景插画的皮肤（蓝色幻想 / 鲸吟）即时生效，官方默认无背景图时该滑块不产生视觉变化，但值仍会被持久化到下一次切到画背景的皮肤。

**Q: 官方默认也能"试穿"吗？**

A: 可以。点击官方默认卡片上的"试穿"会触发同一套收回配方，临时撤掉当前皮肤的视觉写面，但不挂载任何皮肤，页面立即回到 DSH 官方原貌；点击"退出试穿"则恢复激活皮肤。

## 上手难度
入门 — 装好后打开"插件配置 → Web UI 插件 → 皮肤中心"就能看到所有已安装皮肤，"试穿"无需任何额外配置，"应用"一键完成且自动刷新；仅当要加自定义皮肤或调试 Windows 权限问题时才需要进一步了解。

## 已知问题与限制
- host 侧 `wiredNames()` 暂时只读 `skin.json` 的 `wiring.bundleWired` 字段判定"bundle 层已注入"；若皮肤是通过 active profile 的 `dsh.profile.bundles` 注入，源码注释里标了 TODO，暂未实现对应探测（src/skin-switch.ts:245-247）。
- 一键应用对未正确解析的皮肤是硬门槛：`/api/skin-center/apply` 会先调用 `checkResolvable` 校验目标 profile 中是否存在带 `package.json` 与 host 入口的目录，缺一即抛错；这意味着 npm 聚合包里的皮肤目录如果没带可解析包元数据，`/apply` 会拒绝并报中文提示，让用户先 `dsh-skin install` 安装（src/skin-switch.ts:527-552）。
- 试穿过程按"快照当前激活皮肤 → 临时收回 → 加载新皮肤"的顺序进行；若加载中用户连续触发多次切换，前一个 in-flight 请求会被 epoch 机制识别为被超越，只清理自己挂载的资源、不会回退整张页面（src/client/try-on.ts:208-231）。
- profile node_modules 链接创建需要创建权限；Windows 上没有开发者模式时自动 fallback 到目录 junction，但仍可能因组策略受限失败，错误信息会被 `symlinkFriendly` 包装成中文提示（src/skin-switch.ts:496-506）。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-ui-web](https://deepseek-plugin.org/plugins/CAPTAIN1275/dsh-ui-web/packages/skins/skin-center)
Wiki generated by AI (model: `MiniMax-M3`)
