KJ 9 —— 浏览器中的桌面操作系统
把完整的桌面 OS 体验搬进浏览器新标签页
1 项目信息
| 项目 | 说明 |
|---|---|
| 项目名称 | KJ 9 操作系统 |
| 作者 | 传说当中的帅锅 |
| 当前版本 | 9.1.8 |
| 最后更新 | 2026年7月8日 |
2 核心定位
我一直在做一件事:让浏览器新起始页/主页不只是快捷链接,而是一个真正自定义的、完全开放的系统。
KJ 前身为 PPT 操作系统,在B站和抖音有很多人实现自己的自制系统梦,使用 Office 和 PS 等工具进行制作,通过代码和触发器达到真实系统的效果。而帅锅是最早在2015年提出此概念,同时也是创始人, 9 是这个系列的最新一代,采用现代化设计语言,包含:
如果你喜欢本项目,请给帅锅在抖音视频❤️点赞、收藏,如果发条评论就更好了,谢谢大伙的支持!链接:https://www.douyin.com/video/7657382542038879538 
手机端扫码查看 ☝️
3 内置应用
| 应用 | 说明 | 图标颜色 |
|---|---|---|
| 文件资源管理器 | 管理虚拟文件系统 | 蓝色 |
| 记事本 | 文本编辑器 + Markdown 渲染 | 蓝色 |
| 计算器 | 计算工具 | 绿色 |
| 终端 | 命令行界面 v9.1.8 | 黑色 |
| 照片 | 图片浏览管理 | 蓝色 |
| 日历 | 日期与农历 + 年份选择器 | 蓝色 |
| 音乐 | 音乐播放(模仿 KJ 8.1.0) | 粉色 |
| 设置 | 系统个性化 + 强调色 | 灰色 |
| 任务管理器 | 进程 + JS 堆内存监控 | 绿色 |
| 关于系统 | 系统信息 + 版本历史 | 蓝色 |
| KJ展览馆 | 系统演进历程(10 个版本) | 蓝色 |
| 参赛说明 | 本页面内嵌窗口 | 渐变红 |
真实场景:我想解决什么问题?
不是空泛的"做个系统",而是开发中遇到的具体痛点
1 桌面图标自由拖动
问题:原生拖拽在多列布局下会"跳到末尾"。用户想把图标放到计算器下面,却总是跑到列表最后。
期望:支持多列自由拖放,拖动时显示横杠插入标识符。
2 高斯模糊的精细控制
问题:全局模糊会"误伤"照片应用的预览窗口主题,导致预览窗口被切换为浅色主题。
期望:提供"仅标题栏模糊"的分级控制,不影响应用内容区域。
3 网站快捷方式的完整生命周期
问题:创建后无法再次编辑、图标不可自定义、颜色固定单一。
期望:
- 支持从 FontAwesome 可视化选择图标
- 图标带中文译名,方便查找
- 支持选择图标颜色
- 右键可编辑已创建的快捷方式
4 系统可定制性
问题:用户想直接写 CSS 改系统 UI,但又要保证安全可恢复。
期望:在开发人员选项中提供 CSS 自定义板块,支持实时预览和持久化保存。
5 全局强调色单一
问题:系统所有交互元素使用固定 #187aff 蓝色,用户无法自定义。
期望:提供预设色板 + 自定义 HSV 取色器,一键改变全系统配色。
6 Bing 壁纸每次刷新都重新加载
问题:每次打开新标签页都重新请求壁纸 API,加载慢且浪费流量。
期望:按日缓存到本地,加载失败显示灰色背景,加载完成渐变显现。
7 日历交互不够友好
问题:切换月份无动画反馈,跳转到远处年份需要逐月翻页。
期望:月份切换横滑动画 + 点击标题进入年份选择器。
项目数据
代码规模与迭代历程
1 版本迭代历程
任务1:桌面图标多列拖拽
支持多列自由拖放 + 插入标识符
1 任务拆解
- 分析现有拖拽逻辑:只支持单列,
appendChild直接追加到末尾 - 计算拖拽位置:需要根据鼠标坐标计算"目标索引"
- 可视化反馈:拖动时显示横杠标识符,指示插入位置
- 多列支持:处理
elementFromPoint在多列布局下的命中问题
2 关键 Prompt
3 踩过的坑
第一版:横杠贯穿整个屏幕
CSS 用了 width: 100% 而非相对父容器,导致横杠从屏幕左边延伸到右边。
第二版:只能拖到第一列
elementFromPoint 在多列布局下命中了错误的容器,第二列之后的图标无法作为放置目标。
最终方案
遍历所有图标计算相对位置 + 限定横杠宽度为图标容器宽度,完美解决多列拖放问题。
4 衍生问题
任务2:高斯模糊分级控制
仅标题栏模糊 + 主题隔离
1 任务拆解
- 分析现有模糊:全局
backdrop-filter,影响所有窗口内容 - 新增模式:只对
.window-titlebar应用模糊,窗口内容保持清晰 - 主题隔离:照片应用预览窗口被"误判"为浅色主题,需锁定深色
- 透明度调节:标题栏透明度太低太透,需调整 rgba 值
2 关键 Prompt 链
3 踩过的坑
开关打开后无模糊显示
原因:CSS 选择器优先级被覆盖。通过提升选择器特异性解决。
照片预览窗口主题被改变
原因:模糊样式作用域过大,影响了照片应用的预览窗口。通过为预览窗口单独锁定深色主题解决。
标题栏太透
原因:rgba 透明度值设置过低。调整 alpha 通道值解决,而非调整模糊半径。
任务3:网站快捷方式完整编辑能力
最复杂的一环:图标选择 + 颜色 + 编辑回填
1 子任务拆解
| 子任务 | 难点 | 解决方式 |
|---|---|---|
| 图标可视化选择 | FontAwesome 图标数百个,如何浏览 | 分类(网站/社交/媒体/技术)+ 搜索 + 网格预览 |
| 图标中文译名 | 用户不认识英文类名 | 每个图标显示"中文(fa-xxx)"格式 |
| 图标颜色选择 | 固定颜色不够灵活 | 8 色预设色板 + data-color 持久化 |
| 编辑时回填数据 | 对话框打开后字段为空 | 调整初始化顺序:先 showDialog() 再设值 |
| 保存时更新而非新建 | 点击"创建"变成新增 | 引入 editingAppId 状态判断 |
2 关键 Prompt 链
3 踩过的坑(重点)
函数名冲突
selectIcon 同时被桌面图标选中和图标选择器使用,导致 classList.add 报 undefined。
selectShortcutIcon 解决。对话框初始化顺序
showCreatorDialog() 内部会清空字段,如果先设值再显示,值会被清空。
编辑模式判断
原代码无状态标识,保存时无法区分"新建"还是"更新"。
editingAppId 全局变量区分,保存时检查该变量是否存在。标签页切换问题
编辑代码应用时,对话框默认打开网站标签页,需手动切换。
showCreatorDialog() 接受 initialTab 参数,支持直接指定标签页。任务4:开发人员选项 - CSS 自定义板块
让用户直接写 CSS 改系统 UI
1 任务拆解
- 用户需求:可直接写 CSS 覆盖系统默认样式
- 编辑器:集成 CodeMirror(已有依赖),支持语法高亮
- 三态操作:应用 / 重置 / 保存
- 持久化:保存到 localStorage,刷新不丢失
2 关键 Prompt 链
3 踩过的坑
无法应用修改
原因:动态 <style> 标签的 ID 冲突,新样式未正确注入。
用户误解:能否直接改源代码
用户问"不能直接修改源代码吗"。TRAE 帮我解释了浏览器环境下只能通过注入 <style> 覆盖,并实现了正确的注入逻辑。
任务5:全局强调色系统
7 预设色 + HSV 自定义取色器 + CSS 变量全局联动
1 任务拆解
- CSS 变量定义:在
:root中定义--accent-color和--accent-hover - 全量替换:将 CSS 中 93+ 处硬编码
#187aff替换为var(--accent-color) - 透明度变体:使用
color-mix(in srgb, var(--accent-color) X%, transparent)替代rgba() - 预设色板:7 个预设色 + 自定义选项
- HSV 取色器:纯 JS 实现 SV 面板 + Hue 色相条 + HEX 输入
- 早期加载:在
<head>内联脚本中读取 localStorage,防止默认色闪烁
2 关键 Prompt 链
3 技术实现
CSS 变量 + color-mix() 联动
HSV 自定义取色器(纯 JS)
早期加载防闪烁
4 踩过的坑
矢量壁纸预览被改色
7 种矢量壁纸(渐变/菱形/网格/波浪等)的预览图也使用了 #187aff,替换后预览图随强调色变化。用户明确要求"应当保持蓝色"。
.wallpaper-default 预览恢复为硬编码 #187aff。拟物图标未跟随强调色
拟物风格任务栏图标使用了硬编码灰蓝色值,未使用 var(--accent-color)。
linear-gradient 全部替换为 color-mix() 变体。取色器按钮溢出 + 无高斯模糊
自定义取色器面板的"取消/应用"按钮超出面板边界,遮罩层没有 backdrop-filter。
overflow:hidden; min-width:0,遮罩加 backdrop-filter:blur(8px)。选中窗口强调色太深
纯强调色作为 active 窗口背景太刺眼。
60% accent + 40% white → 45% accent + 55% light-gray 的渐变。任务6:Bing 每日壁纸缓存策略
每日缓存 + CORS 代理 + 灰色占位 + 渐变显现
1 任务拆解
- API 选择:使用 Bing 官方
HPImageArchive.aspxAPI 获取当日壁纸 URL - CORS 绕过:通过
api.allorigins.win代理 - 每日缓存:按日期缓存 URL 到 localStorage,同一天不重复请求
- 灰色占位:加载期间显示
#555灰色背景 - 渐变显现:图片加载完成后
opacity 0→1的 0.8s 渐变 - 缓存击穿:代理 URL 加
_t=Date.now()时间戳避免缓存旧数据
2 关键 Prompt 链
3 技术实现
三层缓存策略
渐变显现
4 踩过的坑
跨浏览器壁纸不一致
原方案使用 api.dujin.org/bing/1920.php 重定向服务,不同 IP/浏览器返回不同缓存结果。
设置预览图无法显示
Bing API 返回的 URL 无法直接作为 CSS background-image 在设置预览中使用(CORS 限制)。
任务7:极简模式完整体系
隐藏全部系统 UI + 搜索 + 问候语 + 光效 + 自定义引擎
1 功能清单
| 子功能 | 存储键 | 说明 |
|---|---|---|
| 极简模式开关 | kj_minimal_mode | 隐藏任务栏/桌面图标/窗口,显示搜索界面 |
| 自定义壁纸 | kj_minimal_wallpaper | 独立于桌面壁纸 |
| 搜索框尺寸 | kj_minimal_search_size | 小/中/大三档 |
| 红蓝光效 | kj_minimal_glow | 径向渐变叠加层 |
| 搜索引擎 | kj_minimal_engine | Google/Bing/百度/自定义 |
| 固定应用 | kj_minimal_pinned | 快捷启动栏 |
| 问候语 | — | 根据时间动态显示"早上好/下午好/晚上好" |
2 关键 Prompt 链
3 踩过的坑
退出按钮悬浮不可见
background: rgba(255,59,48,0.15) 几乎透明,悬浮后文字看不清。
background: #fff 实色背景。光效按钮点击无反应
#minimal-mode-container 基础 pointer-events: none,但 .visible 状态未恢复。
#minimal-mode-container.visible { pointer-events: auto }。光效 z-index 被遮挡
body.minimal-glow::before 的 z-index: 0 被 #minimal-mode-container(z-index: 99)完全遮挡。
<div id="minimal-glow-overlay"> 作为第一个子元素,position: absolute; z-index: 0,自然位于所有内容之下。任务8:日历年份选择器 + 月份横滑动画
点击标题切换年份视图 + 月份切换左右横滑
1 任务拆解
- 月份横滑动画:切换月份时当前内容滑出,新内容从另一侧滑入
- 年份选择器:点击标题进入 10 年网格选择器(3 列布局)
- 年代翻页:左右箭头切换年代(每次 10 年)
- 双日历同步:日历 APP 和任务栏小日历独立实现,逻辑对称
- 标题高亮反馈:进入年份模式时标题变为强调色背景
2 关键 Prompt 链
3 技术实现
月份横滑动画
年份选择器(10 年网格)
4 踩过的坑
横向滚动条溢出
translateX(100%) 动画期间内容超出容器宽度,出现横向滚动条。
overflow: hidden。任务栏日历数字溢出
小日历弹窗没有 overflow: hidden,滑动动画时日期数字超出边界。
#widget-calendar 添加 overflow: hidden。任务9:飞书审核集成
把应用社区审核搬到飞书,卡片按钮一键通过/拒绝
1 任务拆解
| 子任务 | 难点 | 解决方式 |
|---|---|---|
| 飞书开放平台配置 | 机器人、事件订阅、卡片回调分散在三处 | 分别创建应用、添加机器人、配置 card.action.trigger 事件、设置回调地址 |
| 配置管理 | Option::updateOption 有 SQL 注入风险且未处理重复键 | 重写 saveConfig,手动 INSERT/UPDATE + 错误日志 |
| 消息卡片构建 | 待审核卡片有按钮,已审核卡片无按钮 | buildPendingCard + buildReviewedCard 两个静态方法 |
| Webhook 处理 | 需区分 URL 验证、事件订阅、卡片按钮回调 | handleFeishuWebhook 统一入口 + 事件类型分发 |
| 审核流程串联 | 提交审核 → 推送卡片 → 飞书审核 → 更新状态 | 四个环节环环相扣,任一环节失败都要可追溯 |
| 卡片状态同步 | 审核后卡片要立即更新为「已通过/已拒绝」 | 通过飞书回调响应返回新卡片 + API 更新双保险 |
2 关键 Prompt 链
3 后端全流程详解
以下按请求时序完整梳理后端从「入口路由」到「卡片更新」的每一个环节,涉及 6 个文件、15+ 个函数。
流程①:API 入口与路由分发
所有请求统一从 /?plugin=kj_appstore&action=xxx 进入 kj_appstore_show.php。
emlog dispatcher 加载本文件时,init.php 已执行完毕,ISLOGIN / UID / ROLE / Database 等全局变量可直接使用。
sendJSON() 内部调用 ob_clean() 清空之前的所有输出,
再 http_response_code() + echo json_encode() + exit。
这保证了即使前面有 warning 输出,响应体仍然是干净的 JSON。
流程②:用户提交审核(handleReviewSubmit)
前端 POST review_submit,body 含 type(name_change/app_submission/app_update)、data(申请详情)、authorId、authorName。
sendJSON 之前。
之前放在之后,推送抛异常时程序已输出 JSON 头部,再输出错误导致响应格式混乱,前端解析失败却误报"提交失败",
用户重复点击提交多次相同应用。
流程③:飞书推送(kj_appstore_push_review_to_feishu)
提交审核成功后,异步把审核申请推送到飞书群聊,生成一张带「通过/拒绝」按钮的交互式卡片。
流程④:Webhook 入口(handleFeishuWebhook)
飞书开放平台把所有事件(URL 验证、事件订阅、卡片按钮点击)都推送到同一个 webhook 地址。本函数是统一入口,按 payload 类型分发。
{header:{event_type:...}, event:{...}},
卡片按钮的 message_id 藏在 event.context.open_message_id 里,
而不是 event.token 或 event.message.message_id(这两个是早期版本的字段)。
流程⑤:卡片按钮回调(handleFeishuCardAction)
管理员在飞书卡片上点「通过审核」或「拒绝」后,飞书推送 card.action.trigger 事件到此函数。这是飞书审核的核心入口。
流程⑥:审核执行(approveReview)
approveReview 是审核的核心业务函数,同时被「飞书审核」和「后台审核」调用。
它根据审核类型执行不同的副作用:应用提交/更新要写应用文件和 apps 表;昵称修改要同步 emlog user 表。
approveReview 必须返回数组而不是调用 sendJSON+exit。
之前函数内直接 sendJSON,从飞书 webhook 调用时程序直接退出,无法返回飞书期望的响应,也无法更新卡片。
改为返回数组后,调用方(飞书 webhook / 后台接口)可以各自决定输出格式。
流程⑦:拒绝审核(rejectReview)
拒绝逻辑简单:只更新状态,不执行任何副作用(不写文件、不改 user 表)。
流程⑧:emlog 后台审核(handleReviewApprove / handleReviewReject)
管理员也可在 emlog 后台审核。与飞书审核的区别:需要 requireAdmin() 鉴权,审核后要调用 kj_appstore_sync_feishu_card 同步更新飞书卡片。
approveReview 只管数据库操作,不碰飞书。
飞书卡片同步由调用方负责——飞书审核走「API 更新 + toast 响应」,后台审核走「kj_appstore_sync_feishu_card」。
这样避免了之前三处同时更新卡片的冲突问题。
流程⑨:飞书卡片同步(kj_appstore_sync_feishu_card)
从 emlog 后台审核时,需要把飞书卡片从「待审核」更新为「已审核」。此函数用之前保存的 feishu_msg_id 调用飞书 API。
流程⑩:配置管理(KjAppstore_FeishuConfig)
飞书配置(app_id / app_secret / chat_id / enabled)以 JSON 存储在 emlog options 表,key 为 kj_appstore_feishu_config。
REPLACE INTO,
会先 DELETE 再 INSERT,丢失自增 ID;且未更新缓存,刷新后读到旧值(配置"变空白"的元凶)。
手动 SELECT + INSERT/UPDATE + updateCache('options') 彻底解决。
流程⑪:飞书 API 客户端(KjAppstore_FeishuClient)
封装飞书开放平台三个核心 API:获取 token、发送交互式卡片、更新卡片。所有请求走 cURL,带超时和 SSL 跳过。
流程⑫:飞书卡片构建(KjAppstore_FeishuCard)
两个静态方法分别构建「待审核」和「已审核」两种卡片。卡片结构遵循飞书消息卡片 v1 协议:config + header + elements[]。
| 方法 | header template | elements | 按钮 |
|---|---|---|---|
buildReviewCard |
按类型着色(绿/橙/蓝) | 申请类型 + 提交者 + 应用详情 | ✅ 通过审核 + ❌ 拒绝(value 带 review_id + action) |
buildReviewedCard |
approved=绿 / rejected=红 | 状态 + 审核人 + 审核时间 + 备注 | 无按钮(已审核不可再操作) |
流程⑬:数据模型层(KjAppstore_Model)
操作 4 张表:emlog_kj_apps(应用)、emlog_kj_reviews(审核)、emlog_kj_categories(分类)、emlog_kj_download_logs(下载日志)。
setReviewFeishuMsgId 在使用时自动 SHOW COLUMNS 检测,不存在则 ALTER TABLE 补字段,
实现平滑升级,老用户无感知。
流程⑭:设置页面与表单保存(kj_appstore_setting.php)
emlog 后台「插件设置」页面,含三个 Tab:审核管理、应用管理、飞书集成。飞书配置表单提交到 plugin_setting() 处理。
页面还展示「事件回调地址」(只读 + 复制按钮),方便用户填到飞书开放平台:
流程⑮:发送测试消息(handleFeishuTest)
配置保存后,管理员可点「发送测试消息」验证飞书集成是否正常。此函数逐步校验配置,发送一张测试卡片到群聊。
全流程时序图
校验 + insertReview(status=pending)
buildReviewCard → sendInteractiveCard
含「通过」「拒绝」按钮
URL验证? / 卡片回调?
取 message_id + review_id + action
setReviewStatus + 副作用
API 更新飞书卡片
只返回 toast,不返回 card
4 踩过的坑(重点)
PHP 语法兼容性导致后台打不开
现象:文件替换后 emlog 后台直接打不开,前端显示「请求失败:200」。
原因:飞书推送逻辑中使用了 PHP 7+ 的 ?? 运算符和短数组语法 [],
而服务器 PHP 版本较低,致命错误输出 HTML 错误页面,前端收到 200 状态码但内容是 HTML。
?? 替换为 isset() ? : ,
短数组 [] 替换为 array(),兼容 PHP 5.x。
配置保存后刷新变空白
现象:保存飞书配置后页面刷新,所有选项又变成空白。
原因:Option::updateOption 方法用 REPLACE INTO,
会先 DELETE 再 INSERT,丢失自增 ID;且未更新缓存,刷新后读到的还是旧值。
saveConfig,手动 SELECT 判断存在性后
INSERT 或 UPDATE,最后调用 $CACHE->updateCache('options') 刷新缓存。
提交审核提示报错但实际成功
现象:明明提交成功了,emlog 和飞书后台都能看到,但前端提示报错,且不退出界面, 导致用户重复提交多次相同应用。
原因:飞书推送逻辑在 sendJSON 之后执行,一旦推送抛异常,
程序已经输出了 JSON 头部,再输出错误信息导致响应格式混乱,前端解析失败。
sendJSON 之前,并包在 try-catch 中,
推送失败不影响主流程返回成功响应。
审核后卡片不更新
现象:飞书点击「通过审核」后,emlog 后台状态正确,但飞书卡片还是显示两个按钮。
原因:三个问题叠加:
feishu_msg_id字段未自动创建,保存失败approveReview函数内调用了sendJSON + exit,从 webhook 调用时程序直接退出,无法返回飞书响应- 飞书 v2.0 事件的
message_id在event.context.open_message_id,而不是event.token
setReviewFeishuMsgId 自动检测并 ALTER TABLE 补字段;
② 把 approveReview/rejectReview 改为返回数组,由调用方决定输出格式;
③ 从 event.context.open_message_id 正确获取 message_id。
卡片显示通过后又变回待审核
现象:点击按钮后卡片先显示「已通过」,下一秒刷新又变回待审核状态。
原因:三处同时在更新同一张卡片,导致冲突:
approveReview内的kj_appstore_sync_feishu_card(API 更新①)handleFeishuCardAction内的$client->updateCard(API 更新②)feishuCardResponse响应里的card字段(飞书直接替换③)
三次更新时序不一致,飞书最终用响应里的简单卡片替换了完整审核卡片,视觉上表现为「变回去了」。
kj_appstore_sync_feishu_card 从
approveReview/rejectReview 核心函数移出,只在后台审核接口调用。
事件订阅找不到 card.action.trigger
现象:在飞书开放平台「事件订阅」里找不到卡片按钮点击事件。
原因:用户在错误的位置查找——卡片按钮回调事件不在「事件订阅」里配置, 而是在「消息卡片配置」的回调地址里。
5 涉及文件清单
| 文件 | 作用 |
|---|---|
feishu_config.php | 飞书配置管理(读写、客户端初始化、启用判断) |
feishu_client.php | 飞书 API 客户端(token 获取、发消息、更新卡片) |
feishu_card.php | 消息卡片构建(buildPendingCard / buildReviewedCard) |
kj_appstore_show.php | 插件 API 入口,路由分发 + webhook 处理 + 审核逻辑 |
kj_appstore_model.php | 数据模型,含 feishu_msg_id 字段自动补全 |
kj_appstore_setting.php | 插件设置页面,飞书配置表单 |
任务10:应用社区前端
独立模块化应用商店,浏览/上传/安装一站式体验
window.initAppStore 挂载点与主系统解耦。
1 任务拆解
| 子任务 | 难点 | 解决方式 |
|---|---|---|
| 模块独立化 | 不能让应用商店代码污染 449KB 的 app.js | IIFE 封装 + app-init.js 检测 window.initAppStore 挂载点 |
| API 通信层 | 跨域请求 OA 后端(oa.sgguo.com) | 封装 apiGet/apiPost/apiDelete/fetchText 四个方法,credentials: include |
| 用户身份 | 无登录态,需要唯一标识用户 | genKJUserId() 本地生成 UUID 存 localStorage,首次打开自动创建 |
| 应用列表 | 分类筛选 + 关键词搜索 + 卡片网格 | renderSidebar + renderGrid,支持本地 JSON 兜底 |
| 应用上传 | 表单弹窗 + 提交审核 + 等待审核状态 | showUploadForm 全屏 overlay + uploadToCommunity 调 review_submit API |
| 用户中心 | 我的应用 + 昵称修改 + 统计数据 | renderMyPage + handleEditName + updateMyStats |
2 关键 Prompt 链
3 技术实现
模块独立化设计
#appstore-root 容器和 initAppStore 挂载点。
API 通信层封装
本地用户身份生成
应用上传流程
review_submit 后,
后端写入审核记录 + 推送飞书卡片(task9 流程②③)。
用户可在「我的应用」页面查看审核状态(pending/approved/rejected)。
4 涉及文件清单
| 文件 | 作用 |
|---|---|
js/appstore-app.js | 应用社区主逻辑(IIFE):列表渲染、用户中心、API 封装 |
js/appstore-upload.js | 应用上传模块:表单弹窗 + review_submit 提交 |
js/appstore-settings.js | 应用社区设置面板 |
css/appstore.css | 应用社区独立样式(不污染主样式表) |
data/appstore-apps.json | 本地应用数据兜底(后端不可用时降级) |
data/appstore-reviews.json | 本地审核数据兜底 |
任务11:PWA 离线缓存与版本更新
Service Worker 白名单缓存 + 版本检测 + 系统通知推送
1 任务拆解
| 子任务 | 难点 | 解决方式 |
|---|---|---|
| Service Worker 缓存 | 不能缓存所有文件(API 数据需实时) | 白名单预缓存 + 缓存优先策略 + 版本号管理 |
| 缓存版本管理 | 更新代码后旧缓存不失效 | CACHE_VERSION + activate 时删除非当前版本缓存 |
| 版本更新检测 | 用户不知道系统已更新 | fetch version.txt + 版本对比 + 清缓存重载 |
| 系统通知推送 | 每次刷新都弹通知很烦 | localStorage 记录 last_shown + 按 validFrom 时间判断 |
| 跨域请求处理 | API 请求不能走缓存 | 非同源请求 + /api/ 路径直接走网络,返回 503 兜底 |
2 技术实现
Service Worker 白名单缓存策略
fetch 拦截:三分流策略
版本更新检测机制
系统通知推送机制
validFrom 时间戳 + localStorage 记录上次显示时间,
只有当通知更新(validFrom 变化)时才再次弹出,避免每次刷新都打扰用户。
3 踩过的坑
缓存版本不更新导致代码不生效
现象:更新代码后用户刷新页面看到的还是旧版本。
原因:Service Worker 缓存了旧资源,且 CACHE_VERSION 没有递增。
CACHE_VERSION(当前 v30),
activate 事件中删除所有非当前版本的缓存。
POST 请求被缓存导致数据不更新
原因:fetch 拦截没有过滤非 GET 请求,cache.put 对 POST 抛异常。
method !== 'GET' 直接 return;
② cache.put 前二次确认 method === 'GET'。
version.txt 被浏览器缓存
原因:fetch('version.txt') 命中浏览器 HTTP 缓存,返回旧版本号。
?t=Date.now(),强制走网络。
任务12:系统功能增强合集
图标风格 / 云端备份 / 音量滑块 / 待办托盘 / 全屏 / 窗口警告 / Widget边界 / 搜索引擎
1 图标风格切换系统
支持 4 种桌面图标风格实时切换,通过 body class 控制,CSS 负责渲染差异。
| 风格 | class | 视觉效果 |
|---|---|---|
| 默认 | icon-style-default | 圆角方块 + 阴影 |
| 无界 | icon-style-borderless | 无边框,图标悬浮 |
| 液态玻璃 | icon-style-liquid | 毛玻璃背景 + 模糊 |
| 拟物 | icon-style-skeuomorphic | 立体阴影 + 渐变 |
2 系统云端备份还原
通过 emlog user_expand 插件实现云端同步,把本地配置(IndexedDB + localStorage)序列化为 JSON 上传到服务器。
clearAllConfigs() 清空 IndexedDB,
否则旧数据会覆盖新还原的值(IndexedDB 的 merge 行为不是完全替换)。
3 音量滑块强调色填充
音量滑块的填充条跟随全局强调色,并根据音量切换三种图标(静音/低音量/高音量)。
4 待办事项托盘
从任务栏托盘位置展开的待办列表,带数量徽章和展开/收缩动画。
- 数据持久化:IndexedDB 存储,跨设备不同步
- 数量徽章:托盘图标右上角显示未完成数量
- 展开动画:
max-height过渡 + opacity 渐显 - 复选交互:点击勾选,划线 + 置灰
5 全屏切换 + ESC 同步
开始菜单的全屏按钮 + 浏览器 F11 全屏状态同步。
6 窗口关闭警告
用户关闭浏览器时,如果有未关闭的应用窗口,弹出确认提示。
7 Widget 拖拽边界限制
小组件拖拽时限制在浏览器可视区域内,防止拖出屏幕。
8 自定义搜索引擎
支持自定义搜索引擎,用 {query} 占位符替换搜索词,自动识别常见引擎。
9 应用管理中心
设置 → 系统 → 应用管理,集中管理所有桌面图标,支持删除和一键恢复。
| 分类 | 说明 | 操作 |
|---|---|---|
| 自建应用 | 用户通过 KJ API 自定义的应用 | 删除 |
| 快捷方式 | 用户添加的网站快捷方式 | 删除 |
| 默认图标 | 系统预置的 16 个图标(含应用社区) | 删除 / 恢复 |
allDefaultIcons 数组里,
删除只是从 DOM 移除并保存配置,恢复时从配置表重新生成。
设置和文件管理器图标被排除(appId !== 'settings' && appId !== 'explorer'),防止用户误删后无法恢复。
10 书签栏
任务栏左侧(开始按钮旁)的书签栏入口,支持导入浏览器收藏夹,默认关闭,可在设置中开启。
| 功能 | 说明 |
|---|---|
| 默认关闭 | 需在设置 → 任务栏 → 「显示书签栏」手动开启 |
| 导入书签 | 支持导入 Chrome/Edge/Firefox 导出的 HTML 书签文件 |
| 文件夹解析 | 自动解析书签 HTML 中的 <H3> 文件夹结构,可折叠展开 |
| 手动添加 | 支持手动输入名称和 URL 添加单条书签 |
| 去重导入 | 按 URL 判断重复,已存在的书签不重复导入 |
| 数据存储 | localStorage 持久化,刷新不丢失 |
| 点击外部关闭 | 点击书签栏外区域自动收起 |
DOMParser 在浏览器端本地解析,书签数据不上传服务器,完全在本地处理。
文件架构
KJ 9 项目目录结构与各文件作用说明
1 目录结构总览
2 核心文件详解
#desktop-icons、任务栏 #taskbar、开始菜单、窗口容器 #windows-container、搜索框、小组件等。通过 <script> 标签按顺序加载 config-db.js → app.js → configs/app-init.js,并在末尾调用 initAppConfigs() 注册所有应用配置。
•
openApp() - 打开应用窗口•
makeDraggable() - 窗口拖拽(requestAnimationFrame 优化)•
setupResize() - 窗口缩放(8 方向)•
setAccentColor() / loadAccentColor() - 强调色系统•
loadBingDailyWallpaper() - Bing 壁纸缓存策略•
toggleMinimalMode() - 极简模式•
toggleCalendarYearPicker() - 日历年份选择器•
updateVolumeSliderBackground() - 音量滑块强调色填充•
addShortcut() / editWebsiteShortcut() - 快捷方式管理•
initIconSelector() - FontAwesome 图标选择器•
solarToLunar() - 农历转换(1900-2100)• 主题切换、壁纸、模糊、极简模式等所有系统功能
--accent-color、--accent-hover、--title-bar-height、--text-color、--window-bg 等)、74+ 处 color-mix() 透明度变体、三种图标风格(无界/液态/拟物)、窗口动画系统(打开/关闭/拖拽/splash)、Bing 壁纸层、音量滑块、日历年份选择器、待办 widget、极简模式、高斯模糊分级等。
ConfigDB 类,数据库名 KJOSDatabase,存储对象 userConfig。提供 init()、ensureDB() 等异步 API,用于保存自定义应用、快捷方式、主题等配置,确保用户清除浏览器缓存后数据仍不丢失。
AppConfig 类(校验 title/icon/width/height/content)和 AppConfigRegistry 注册表。全局暴露 appConfigRegistry 实例和 getAppConfig() 函数,供 openApp() 获取应用配置。
initAppConfigs() 函数内通过 appConfigRegistry.register() 注册所有应用:计算器、终端、任务管理器、设置、关于、照片、日历、音乐、记事本、文件管理器、展览馆、参赛说明等。每个应用配置包含 title、icon、iconBg、width、height、content(HTML 内容)。
v3,缓存名 kj-offline-v3。预缓存所有核心资源(index.html、app.js、style.css、configs/、css/、js/、img/),实现离线访问。在 index.html 末尾通过 navigator.serviceWorker.register() 注册。
http 模块,端口 8080。根据文件扩展名设置 MIME 类型,支持 html/css/js/json/png/jpg/gif/svg/ico/txt/m4a。用于本地开发调试。
9.1.0。系统启动时通过 HTTP 请求此文件,与本地版本对比,判断是否有新版本可用。
3 文件加载顺序
KJ 9 的文件加载有严格的依赖顺序:
config-db.js → app.js(使用 configDB)→ app-config-base.js(定义 appConfigRegistry)→ app-init.js(注册具体应用)→ initAppConfigs() 调用。
流程图
KJ 9 核心流程的可视化展示
1 系统启动流程
style.css / FontAwesome / CodeMirror
config-db.js → app.js → app-init.js
注册所有应用配置
2 打开应用窗口流程
从 appConfigRegistry 获取配置
设置宽高、位置、zIndex
显示 splash 加载动画
绑定拖拽和缩放事件
添加到任务栏
显示真实应用内容
3 网站快捷方式创建流程
显示对话框 + 初始化图标选择器
id / name / url / icon / color
持久化到 IndexedDB
绑定单击/双击打开事件
4 网站快捷方式编辑流程
name / url / icon / color
避免字段被清空
名称 / URL / 图标 / 颜色
绑定 saveShortcutEdit()
icon-img 背景 / 图标类名
持久化到 IndexedDB
5 数据持久化架构
简单配置
同步读写,快速
复杂数据
config-db.js
HTML/CSS/JS/图片
离线缓存
•
localStorage - 存储简单配置项(主题、壁纸、模糊模式、强调色等),同步读写,性能高•
IndexedDB - 存储复杂数据(自定义应用、快捷方式),通过 config-db.js 封装,异步读写,容量大•
Service Worker Cache - 缓存静态资源,实现离线访问
6 强调色系统联动流程
SV 面板 + Hue 色相条
设置 CSS 变量 + 存 localStorage
var(--accent-color)
color-mix() 74+处
渐变三态
<head> 内联脚本中提前读取 localStorage 并设置 CSS 变量,
确保页面渲染时已是用户选择的强调色,避免默认蓝色闪烁。
使用的 TRAE 能力
在 KJ 开发过程中用到的 TRAE 核心能力
1 自然语言代码搜索
用自然语言描述需求,TRAE 自动定位相关代码。例如定位 editWebsiteShortcut、loadBingDailyWallpaper、setAccentColor 等函数实现。
2 多文件协同编辑
同时修改 app.js(逻辑)、index.html(结构)、style.css(样式)、config-db.js(数据层)、configs/app-init.js(应用配置),保持五者一致性。
3 上下文感知
理解 KJ 项目的对话框初始化模式,避免破坏现有逻辑。例如知道 showCreatorDialog() 会清空字段,建议调整调用顺序。理解 offsetLeft vs getBoundingClientRect() 在滚动场景下的差异。
4 错误诊断
定位 TypeError: Cannot read properties of undefined (reading 'add') 的根因(函数名冲突),并给出重命名建议。定位 SyntaxError: Identifier 'initNetworkStatus' has already been declared 的重复声明问题。
5 增量修改
只改必要的代码段,不重写整个函数。保持代码库的稳定性和可维护性。例如替换 93+ 处 #187aff 时,逐个确认上下文是否适合替换。
6 会话压缩与长上下文
TRAE 的会话压缩让我能在长对话中保持上下文,不用重复解释项目结构。跨多轮对话完成复杂功能开发(如强调色系统涉及 CSS 变量定义、全量替换、取色器、防闪烁等多个步骤)。
7 深度代码探索
使用 SearchCodebase 语义搜索,快速找到"音量滑块填充色在哪里设置"、"Bing 壁纸缓存逻辑在哪"等跨文件问题。
8 技术方案建议
TRAE 主动建议使用 color-mix() 替代 rgba() 实现强调色透明度变体,建议使用双 requestAnimationFrame 解决 display:none 过渡问题。
关键 Prompt 汇总
开发过程中使用的核心 Prompt 集合
1 功能开发类
2 Bug 修复类
3 Prompt 技巧总结
- 具体描述现象:不说"修复拖动",而说"拖到计算器下面却跳到末尾"
- 报错信息直接贴:
TypeError: ...比"有 bug"有效得多 - 分步验证:复杂功能拆成多步,每步验证
- 保留上下文:利用 TRAE 的长上下文能力,避免重复解释
踩坑总结
开发过程中遇到的关键问题与解决方案
1 函数名冲突
现象:TypeError: Cannot read properties of undefined (reading 'add')
原因:selectIcon 同时被桌面图标选中和图标选择器使用
解决:重命名为 selectShortcutIcon,消除命名冲突
2 对话框初始化顺序
现象:编辑时输入框无法填充已有数据
原因:showCreatorDialog() 内部会清空字段,先设值再显示会导致值被清空
解决:调整顺序,先 showDialog() 再设值
3 编辑模式无状态标识
现象:点击"创建"变成新增,而非更新
原因:原代码无状态标识,无法区分"新建"还是"更新"
解决:引入 editingAppId 全局变量
4 CSS 横杠贯穿屏幕
现象:插入标识符横杠从屏幕左边延伸到右边
原因:width: 100% 相对于视口而非父容器
解决:限定宽度为图标容器宽度
5 多列拖放失效
现象:只能拖到第一列,其他列无法拖动
原因:elementFromPoint 在多列布局下命中错误容器
解决:遍历所有图标计算相对位置
6 高斯模糊误伤
现象:照片应用预览窗口被切换为浅色主题
原因:模糊样式作用域过大
解决:为预览窗口单独锁定深色主题
7 网络状态图标误报
现象:右下角网络图标显示"服务器不可达",但设置中可检测更新
原因:网络检查逻辑与更新检查逻辑不一致
解决:统一网络状态判断逻辑
8 桌面图标跨列拖拽失效
现象:第二列图标无法拖到第二行的任意位置
原因:Math.floor((x-rect.left)/94) 硬编码列宽 + getBoundingClientRect() 在滚动时 Y 坐标偏移
解决:改用 offsetLeft 分组识别列 + offsetTop + scrollTop 补偿比较 Y 位置
9 极简模式光效 z-index 遮挡
现象:红蓝光效开关打开后看不到任何效果
原因:body::before 伪元素 z-index: 0 被 #minimal-mode-container(z-index: 99)完全遮挡
解决:改为容器内部插入 <div> 元素作为第一个子元素
10 pointer-events 吞噬点击
现象:极简模式下光效开关点击无反应
原因:容器基础 pointer-events: none,.visible 状态未恢复 auto
解决:#minimal-mode-container.visible { pointer-events: auto }
11 Bing 壁纸跨浏览器不一致
现象:两台浏览器显示不同的 Bing 壁纸
原因:api.dujin.org 重定向服务按 IP 返回不同缓存
解决:移除 dujin fallback,统一用 Bing 官方 API + allorigins 代理 + 时间戳防缓存
12 display:none → transition 失效
现象:待办 widget 从隐藏切换到显示时无过渡动画
原因:display:none → display:flex 不触发 CSS transition
解决:使用双 requestAnimationFrame 技巧 — 先设 display,下一帧再设 transform/opacity
13 重复函数声明导致 SyntaxError
现象:SyntaxError: Identifier 'initNetworkStatus' has already been declared
原因:同一函数在 app.js 中存在两份定义(简版 + 完整版),JS 不允许重复声明
解决:删除简版,保留完整版。注意级联错误(AppConfigRegistry not found)会随语法错误修复而消失
版本更新日志
KJ 9 完整版本迭代记录(9.0.0 → 9.1.1)
1 v9.1.1 — 2026年6月21日
2 v9.1.0 — 2026年5月29日
3 v9.0.6 — 2026年5月23日
4 v9.0.5 — 2026年5月21日
5 v9.0.4 — 2026年5月15日
6 v9.0.3 — 2026年5月12日
7 v9.0.2 — 2026年5月7日
8 v9.0.1 — 2026年5月2日
9 v9.0.0 — 2026年4月28日
成果展示
使用 TRAE 完成的功能与改进
1 功能成果
- 桌面图标多列拖拽:支持自由拖放 + 插入标识符 + offsetLeft 列识别
- 高斯模糊分级控制:仅标题栏模糊 + 主题隔离
- 网站快捷方式完整编辑:图标选择 + 颜色 + 编辑回填
- CSS 自定义板块:开发人员选项中直接写 CSS 改 UI
- 全局强调色系统:7 预设 + HSV 取色器 + 100+ 处 CSS 变量联动
- Bing 每日壁纸:每日缓存 + CORS 代理 + 灰色占位 + 渐变显现
- 极简模式:隐藏全部系统 UI + 搜索 + 问候语 + 红蓝光效
- 日历年份选择器:10 年网格 + 月份横滑动画 + 标题高亮反馈
- 音量滑块填充:强调色渐变填充 + 三态图标切换
- 待办事项托盘:从托盘位置展开/收缩动画 + 数量徽章
- 全屏切换:开始菜单全屏按钮 + ESC 同步
- 窗口关闭警告:beforeunload 检测未关闭窗口
- Widget 拖拽边界:限制在浏览器可视区域内
- 自定义搜索引擎:支持 {query} 占位符 + 自动识别
- 系统备份/还原:修复无法还原的问题
- 应用社区前端:IIFE 独立模块 + 跨域 API 封装 + 本地 UUID 身份
- Service Worker 离线缓存:白名单预缓存 + 三分流 fetch 策略 + 版本管理
- 版本更新检测:version.txt 对比 + 自动清缓存重载
- 系统通知推送:notification.json 配置 + localStorage 去重
- 图标风格切换:4 种风格(默认/无界/液态/拟物)实时切换
- 云端备份还原:IndexedDB + localStorage 序列化上传 emlog 服务器
2 技术亮点
- CSS 变量 + color-mix() 联动:74+ 处使用
color-mix(in srgb, var(--accent-color) X%, transparent),无需预计算多色值 - HSV 自定义取色器:纯 JS 实现 SV 面板 + Hue 色相条 + HEX 输入
- offsetLeft/offsetTop 坐标系:解决 flex-wrap 多列布局下 getBoundingClientRect 滚动偏移问题
- 双 requestAnimationFrame 技巧:解决 display:none → transition 失效问题
- 三层缓存策略:localStorage(配置)+ IndexedDB(复杂数据)+ Service Worker(离线资源)
- Bing API + CORS 代理:allorigins.win + 时间戳防缓存
- FontAwesome 可视化选择器:分类 + 搜索 + 中文译名 + 网格预览
- 编辑模式状态管理:
editingAppId区分新建/更新 - 主题隔离机制:为特定窗口锁定主题,避免全局样式影响
- 三种图标风格:无界 / 液态玻璃 / 拟物,可实时切换
- 农历转换:1900-2100 年完整农历数据表
- IIFE 模块独立化:应用社区 1500+ 行代码完全不污染 app.js,通过 window 挂载点解耦
- Service Worker 三分流策略:跨域/API/非白名单走网络,白名单走缓存优先
- 本地 UUID 身份系统:无登录态下用 localStorage 生成唯一用户标识
- 通知去重机制:validFrom 时间戳 + localStorage 记录,避免重复弹窗
3 项目最终状态
KJ 9.1.9 实现了完整的桌面 OS 体验:
- 14 个内置应用 + 自定义应用创建(KJ API 开放)
- 主题/壁纸/模糊分级控制 + 全局强调色系统
- 桌面图标支持多列自由拖拽带插入标识
- 网站快捷方式支持图标+颜色+编辑全生命周期
- 极简模式:隐藏全部 UI,专注搜索体验
- Bing 每日壁纸:每日缓存 + 渐变显现
- 日历 APP:年份选择器 + 月份横滑动画 + 农历
- 开发者可通过 CSS 自定义板块深度定制系统 UI
- Service Worker 离线缓存 + IndexedDB 配置持久化
- 四种图标风格 + 拟物/液态/无界/默认实时切换
- 应用社区:独立模块化前端 + 飞书审核后端 + 云端备份
- PWA:离线缓存 + 版本检测 + 系统通知推送