内容:打造你的专属桌面
桌面上的一切——文件、快捷方式、壁纸、文化元素——都用数据描述:你写出“有什么”,组件负责“怎么显示”。判断标准很简单:新增一条普通内容不需要写 React 代码。
自定义文件系统
传入 customFileSystem 把文件和文件夹添加到桌面。每个顶层键都会变成一个桌面图标:
import { WindowsXP } from '@caoergou/windows-xp';
import '@caoergou/windows-xp/style.css';
const myFileSystem = {
'ReadMe.txt': {
type: 'file',
name: 'ReadMe.txt',
app: 'Notepad',
content: 'Welcome to my desktop!',
},
'My Projects': {
type: 'folder',
name: 'My Projects',
children: {
'Project A.txt': { type: 'file', name: 'Project A.txt', app: 'Notepad', content: '…' },
},
},
'MyApp.lnk': { type: 'app_shortcut', name: 'MyApp.lnk', app: 'Calculator', icon: 'calculator' },
};
export default function App() {
return <WindowsXP customFileSystem={myFileSystem} autoLogin skipBoot />;
}节点字段
| 字段 | 类型 | 说明 |
|---|---|---|
type | 'file' | 'folder' | 'app_shortcut' | 节点类型。 |
name | string | 显示名称。 |
icon | string(可选) | XPIcon 图标 id,如 'notepad'、'folder'。 |
app | string | 打开该节点的已注册应用 id,如 'Notepad'、'InternetExplorer'。 |
content | string(可选) | 文件内容,文本文件有效。 |
children | object(可选) | 文件夹的子节点。 |
locked / password / broken | boolean / string(可选) | 解谜属性,可把普通文件变成关卡。完整交互模型见 docs/PUZZLE-DESIGN.md。 |
文件类型与应用关联: Notepad(文本)、PhotoViewer(图片)、InternetExplorer(html/url)、WindowsMediaPlayer(音频/视频)。
合并与替换。 默认的 'merge' 模式会把你的节点叠加到默认桌面上。fileSystemMode="replace" 只保留 OS 骨架(回收站 + 一个空的我的电脑),并移除内置快捷方式、预设内容和文化快捷方式——你的 customFileSystem 就是整个世界。作品集、营销活动页面和自定义游戏都该用这个模式。
壁纸与头像
<WindowsXP
wallpapers={[{ id: 'brand', name: 'Brand', src: '/brand-wallpaper.jpg' }]}
defaultWallpaper="brand" // 或直接写 URL:"https://…/bg.jpg"
avatar="/me.png" // 或 XPIcon id
/>自定义壁纸会和内置壁纸一起出现在“显示设置”中。文化包也可以通过 CulturePackage.wallpaper 声明默认壁纸(但 defaultWallpaper prop 优先)。
文化包
文化包定义了一套完整的区域/时代体验:桌面快捷方式、开始菜单、浏览器主页、便利贴和 i18n 资源。内置文化包有 zh(2000 年代中文互联网)和 en(英文语境 2000 年代)。
使用 defineCulture() 编写文化包。这是一个工厂函数:除了提供 CulturePackage 类型提示外,它还会在开发模式下校验包里的常见错误,例如快捷方式的 app 为空、项目 id 重复、locales 不匹配,或 nameKey 在 i18n 中未定义:
import { WindowsXP, defineCulture } from '@caoergou/windows-xp';
const jpRetroCulture = defineCulture({
id: 'jp-retro',
displayName: '日本 2000s',
locales: ['ja', 'ja-JP'],
browser: { homepage: 'http://www.yahoo.co.jp' },
desktopShortcuts: [
// `app` 必须是已注册应用 id(内置应用或通过 `apps` prop 传入)。
{ id: 'nicovideo', name: 'ニコニコ動画', app: 'InternetExplorer', icon: 'ie' },
],
startMenu: {
pinned: [
{
id: 'ie',
action: 'InternetExplorer',
nameKey: 'startMenu.apps.internetExplorer',
icon: 'ie',
},
],
recent: [{ id: 'notepad', action: 'Notepad', nameKey: 'apps.notepad', icon: 'file' }],
},
stickyNote: { id: 'default', title: 'メモ', content: 'カスタム文化包のテスト' },
i18n: {
ja: {
'startMenu.apps.internetExplorer': 'Internet Explorer',
'apps.notepad': 'メモ帳',
},
},
});
<WindowsXP language="ja" cultures={[jpRetroCulture]} />;内置 QQ 客户端会从文化包的 qq 档案读取身份与登录默认值。账号默认回退到 me.number;如果虚构桌面需要展示已记住的凭据,可以声明可选的 login:
import type { QQProfile } from '@caoergou/windows-xp';
const qq: QQProfile = {
me: {
number: '123456',
nickname: '夜班列车',
avatar: 50,
status: 'online',
},
login: {
password: 'story-password',
rememberPassword: true,
},
groups: [],
buddies: [],
};这些凭据只控制模拟登录表单,并不构成真实的身份验证边界。账号和密码均非空时, 登录按钮才可用。
注意
app与action的区别。 在desktopShortcuts中使用app字段;在startMenu的pinned/recent中使用action字段。二者都应填入已注册的应用 id,只是内部类型命名不同。
第三语言文化包注意事项:
locales会考虑语言基标签。 某个项目的locales: ['ja']会匹配运行时的'ja-JP',反之亦然——基于基子标签、不区分大小写。省略项目级locales,该项目就会在文化包支持的所有语言中显示;设置locales则可以把项目限定到某个子集(例如只让ja受众看到的快捷方式)。- UI 字符串对于你在
i18n中未提供的任何 key 都会回退到英文。 - 开始菜单项只通过
nameKey解析名称,因此必须提供这些 key。 app值必须是已注册应用 id——内置应用,或通过appsprop 传入的应用。在开发模式下,未注册的 id 会在挂载时打印警告。
贡献者说明——把文化应用接入到库内部
下面的步骤是给向
windows-xp仓库本身贡献新的内置文化应用的作者看的(需要编辑src/apps/和APP_REGISTRY)。如果你是 npm 包的使用者,想在自己的项目里添加自定义应用,请使用defineApp()(见下文编写你的第一个应用),然后在文化包的desktopShortcuts中引用它的id。
文化应用完整接入流程(来自构建 en 英文语境 2000 年代文化包的经验——Winamp / Norton AntiVirus / uTorrent / iTunes / Microsoft Office):
- 在
src/apps/下构建组件(像 Winamp 这样的旗舰应用可以复用内置的示例音频片段来播放真实音频;其余应用可以做成带主题的浅层外壳,就像zh文化包中的迅雷/酷狗应用)。 - 在
APP_REGISTRY中注册它,使用locales: ['en']使其只在该文化中出现,再配置icon、窗口参数,以及一个associations项,其appField要等于快捷方式的app值。 - 把桌面快捷方式加入文化包的
desktopShortcuts——用户看到的快捷方式name也是data-english-testid="desktop-icon-<name>"选择器所看到的,因此要保持一致;app必须与注册表中的appField匹配。 - 可选地通过
nameKey把它固定到startMenu。 - 素材必须是原创或戏仿作品——禁止盗用第三方 logo(DEVELOPMENT.md §6)。
en应用的图标都是手绘 SVG。
编写你的第一个应用
defineApp() 接收一个组件和配置,返回一个类型安全的可注册应用——下面是一个完整、刷新后窗口仍能恢复的 hello-world,不到 10 行:
import { WindowsXP } from '@caoergou/windows-xp';
import { defineApp } from '@caoergou/windows-xp/registry';
const HelloApp = defineApp({
id: 'Hello',
name: 'Hello',
component: () => <div style={{ padding: 16 }}>Hello from Windows XP!</div>,
});
export default () => <WindowsXP apps={[HelloApp]} />;defineApp 会为你填充默认值(图标 app_window、400×300 窗口、非单例),并从 component 推导出 restore。通过 React ref 打开已注册应用:
import { useRef } from 'react';
import type { XPHandle } from '@caoergou/windows-xp';
const xp = useRef<XPHandle>(null);
// …
<WindowsXP ref={xp} apps={[HelloApp]} />;
xp.current?.openApp('Hello'); // 打开一个运行 HelloApp 的窗口刷新后仍能保留的 props。 窗口的 props 会被持久化,以便重新加载时重建窗口,因此它们必须是 JSON 可序列化的。defineApp 在编译期强制这一点——如果你的 props 里出现函数或元素,那会是类型错误,而不是悄无声息的刷新 bug:
const NoteApp = defineApp<{ text: string }>({
id: 'Note',
name: 'Note',
component: ({ text }) => <div>{text}</div>,
// window、nameKey、locales、lifecycle、associations 都是可选的。
});需要注意的规则:
- 通过
ref.openApp(id)打开自定义应用(见上文)——它会在包含你传入apps的合并注册表中解析。associations+getProps可以让文件系统节点的.app字段打开某个应用,但目前这条路径只解析内置应用;从宿主代码打开自定义应用请始终使用ref.openApp(id)。 - 添加
nameKey以获得翻译后的显示名称;name作为回退。 - 运行时回调应该挂在事件总线(
onEvent)或lifecycle上,绝不能放进 props。在组件内部通过import { useApp } from '@caoergou/windows-xp'使用useApp()访问窗口/会话状态。 - 需要手动构建
AppRegistryEntry?从@caoergou/windows-xp/registry导入restoreApp辅助函数,它提供与内置应用相同的unknown → props转换。
QQ 群聊与消息记录
通过内容包挂载 QQ 会话。kind: 'group' 的会话会同时驱动主面板 群/校友录入口打开的群聊窗口,以及职责独立、只读的消息记录管理器:
import type { ContentPack } from '@caoergou/windows-xp';
const storyPack: ContentPack = {
id: 'story-chat',
qqArchives: [
{
id: 'summer-2006',
conversations: [
{
id: 'classmates',
title: '周末联机小队',
kind: 'group',
memberIds: ['alice', 'bob'],
messages: [
{
id: 'classmates-1',
senderId: 'alice',
senderName: '小艾',
sentAt: '2006-08-12T17:41:00+08:00',
text: '今晚八点?',
},
],
},
],
},
],
};
<WindowsXP contentPacks={[storyPack]} />;成员 id 会从当前文化包的 qq.buddies 解析,找不到时回退到历史消息中的发送者名称。 群聊窗口中新发送的消息只在本次窗口会话中存在,不会修改作者声明的只读档案。 点击主面板入口还会发出 ui:action:appId: 'QQ'、 control: 'open-group-chat',所选会话 id 位于 value。
在桌面上搭建博客
这个桌面天然适合做作品集/博客外壳——文章作为 .md 文件在 Markdown 查看器中打开、永久链接、用 RSS + sitemap 做 SEO。它有独立的一页:在桌面上搭建博客。