ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Wekan 自定义主题与 CSS 主题开发完全指南:从 Stylish 覆盖到内置主题系统

Wekan 自定义主题与 CSS 主题开发完全指南:从 Stylish 覆盖到内置主题系统 Wekan 自定义主题与 CSS 主题开发完全指南从 Stylish 覆盖到内置主题系统【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan本文是一份面向 Wekan 开发者与高级用户的主题定制实战指南。文章以官方文档 Custom-CSS-themes.md 为主体骨架结合当前仓库源码完整讲解三种主题定制路径——直接向 master 分支提交内置主题需改动 3 个核心文件、使用 Stylish/Tampermonkey 等浏览器扩展编写 CSS 覆盖层、以及理解内置的 Dark / Relax 等全站主题并深入剖析新一代 Select Color 主题分类系统flat/clear/dark/special 四类与自定义颜色机制。读完你将掌握在 Wekan 中添加、验证和提交自定义主题的完整流程以及主题颜色在客户端渲染与服务端校验的底层实现原理。一、Wekan 主题定制总览三种可选路径Wekan 的所有官方主题都直接集成在应用内All Wekan themes are directly integrated to Wekan。如果你希望新增一个主题并合入官方主线官方文档给出了清晰的协作流程先构建 Wekan 代码、以代码改动的方式添加新主题然后作为 Pull Request 提交Emoji 插件新增示例。提交后该 PR 会出现在 Wekan 的开放 PR 列表中一旦被合并新主题就会随下一个 Wekan 版本发布并登记在 CHANGELOG.md 中。对于通过 Snap 渠道安装的 Wekan新版本发布后会自动更新也可以在 Wekan Snap 页面 通过网页刷新查看版本状态或参考自动更新计划手动立即更新。除提交内置主题外官方文档还提供了两种轻量替代方案提 Issue 附带截图原型如果你希望拥有某个新主题可以在 Wekan 仓库提交新 Issue并附上主题的截图 mockup。用浏览器扩展先行验证如果可能你也可以尝试用 Stylish 浏览器扩展实现这些 CSS 改动并一同附到新 Issue 中。这正是下面 Dark 主题与 White 主题的诞生方式——它们最初都是社区用户用 Stylish / Tampermonkey 写出来的 CSS 覆盖层。注意文档中给出的 GitHub PR 列表、commit 链接、Snap 更新计划等均为文档写作时的外部参考入口实际合入状态请以当前仓库与 Wekan 官方渠道为准。二、添加内置主题必须修改的 3 个文件Wekan 主题是硬编码进应用的向 master 分支提交自定义主题时从参考提交34b2168d0dda253dedabbee47031873efa4ae446可以看出真正必需改动的是以下 3 个文件该提交中还包含其他文件改动但新增主题并不需要动它们文件作用client/components/boards/boardColors.styl定义主题的 CSS 颜色规则历史上为 Stylus现已迁移为 boardColors.cssmodels/boards.js在 Board 模型中接入新颜色名使看板可被设置该主题server/migrations.js迁移逻辑历史上位于该文件当前版本拆分于 server/migrations 目录值得注意的是这个文件清单对应的是旧版文档的目录结构。在当前仓库中样式文件已经完成 Stylus → CSS 迁移见 Converting-Meteor-Stylus-to-CSS.mdcoagmano:stylus包不再维护且与 Meteor 2.7.2 存在兼容问题因此 Wekan 已将所有.styl转为.css现在主题颜色规则的唯一来源是 client/components/boards/boardColors.css。例如.board-color-belize通过一组 CSS 变量定义完整配色.board-color-belize { --board-theme-accent: #2980b9; --board-theme-dark: #216694; --board-theme-hover: #2e90d0; --board-theme-emphasis: #b96229; --board-theme-checklist-bg: #d1e7f5; --board-theme-selected-bg: #e8f3fa; --board-theme-bright: #459cd6; }而.board-color-dark、.board-color-clearblue等主题则通过成组的选择器规则分别覆盖 header、minicard、sidebar、按钮、复选框等全部界面部件。新版扩展清单更严格的四步流程随着 Select Color 主题分类系统的落地当前仓库对新增主题色的要求已经从改 3 个文件演进为更规范的四步详见 Theme.md 第 7 节与 themeCategories.js注册颜色名将颜色名加入 config/const.js 的ALLOWED_BOARD_COLORS数组当前共 25 个主题色从belize到cleanlight。归类在 models/lib/themeCategories.js 的THEME_CATEGORIES中把它加入正确分类。这一步受 tests/themeCategories.test.cjs 的守护测试约束测试会读取config/const.js并断言分类并集与ALLOWED_BOARD_COLORS完全一致不多不少。添加 CSS在 client/components/boards/boardColors.css 中编写.board-color-name规则。若为 flat/clear 主题用 CSS 变量驱动强调色让主题的强调色引用--theme-accent/--theme-accent-2变量这样自定义颜色机制可以零成本支持该主题。三、Dark 主题社区 CSS 覆盖层的经典范例Dark 主题最初由社区用户 lonix1 在 Wekan issue #1149 中贡献文档中的截图 mockup 见注释块。lonix1 用 Stylish 编写了一组 CSS 覆盖规则主题说明指出Its not complete but Im happy with it. I work in swimlanes mode, so that is what I changed (not list mode or calendar mode)——即该覆盖主要针对泳道swimlanes模式优化。3.1 Dark 主题的设计要点配色以 VSCode 深色主题为参考实现真正的深色模式。隐藏噪元素隐藏各种无用的图标和元素。悬停才显示add card、add swimlane、add list 等链接在悬停前隐藏原话I find these very noisy——作者认为这些链接平时很吵。3.2 Dark 主题完整 CSS 覆盖代码/* HIDE PERMANENTLY -------------------------------------------------- */ /* various */ .wekan-logo, .close-card-details { display:none; } /* header text */ #header-quick-access ul li:nth-child(1) a { font-size:0; } #header-quick-access ul li:nth-child(1) a .fa-home{ font-size:12px; margin:0; } /* popup menu titles (boards, swimlanes, lists, cards, labels) */ .pop-over .header { display:none; } /* OPTIONAL card fields: received, start, due, end, members, requested, assigned I rarely use these... uncomment if you want to hide them */ /* .card-details-item.card-details-item-received, .card-details-item.card-details-item-start, .card-details-item.card-details-item-due, .card-details-item.card-details-item-end, .card-details-item.card-details-item-members, .card-details-item.card-details-item-name { display:none; } .card-details-items:empty { display:none; } */ /* HIDE UNTIL HOVER -------------------------------------------------- */ /* header button */ #header-quick-access .fa-plus { display:none; } #header-quick-access:hover .fa-plus { display:inherit; } /* add card links (use visibility rather than display so items dont jump) */ .open-minicard-composer { visibility:hidden; } .list.js-list:hover .open-minicard-composer { visibility:visible; } .list-header-menu { visibility:hidden; } .list.js-list:hover .list-header-menu { visibility:visible; } /* add list/swimlane links (use visibility rather than display so items dont jump) */ .list.js-list-composer .list-header { visibility:hidden; } .list.js-list-composer:hover .list-header { visibility:visible; } /* DARK MODE -------------------------------------------------- */ /* headers */ #header-quick-access, #header { background-color:rgba(0,0,0,.75) !important; } #header .board-header-btn:hover { background-color:rgba(255,255,255,0.3) !important; } /* backgrounds: swimlanes, lists */ .swimlane { background-color:rgba(0,0,0,1); } .swimlane .swimlane-header-wrap, .swimlane .list.js-list, .swimlane .list-composer.js-list-composer { background-color:rgba(0,0,0,.9); } /* foregrounds: swimlanes, lists */ .list .list-header, .swimlane-header { color:rgba(255,255,255,.7); } /* minicards */ .minicard { background-color:rgba(255,255,255,.4); } .minicard-wrapper.is-selected .minicard, .minicard:hover, .minicard-composer.js-composer, .open-minicard-composer:hover { background-color:rgba(255,255,255,.8) !important; color:#000; } .minicard, .minicard .badge { color:#fff; } .minicard:hover .badge, .minicard-wrapper.is-selected .badge { color:#000; } /* cards */ .card-details, .card-details .card-details-header { background-color:#ccc; } /* sidebar */ .sidebar-tongue, .sidebar { background-color:#666 !important; } .sidebar-content h3, .sidebar-content .activity-desc { color:rgba(255,255,255,.7) !important; }这段代码本身就是一份绝佳的 CSS 覆盖层教程值得注意的实战技巧包括永久隐藏 vs 悬停隐藏静态元素用display:none交互元素如 add card 链接用visibility:hidden:hover显示作者特别注释*use visibility rather than display so items dont jump*——用visibility而不是display避免元素出现时引起布局跳动。!important的合理使用覆盖框架样式时普遍需要!important才能压过原有规则优先级。可选区块注释掉的 card details 字段隐藏规则表明覆盖层可以按个人使用习惯裁剪。3.3 内置 Dark 主题的现状如今 Dark 主题早已内置进 Wekan文档描述为Dark: modified from below by lonix1 and more changes by xet7——即由 lonix1 的上述 CSS 为基础、xet7 进一步修改而成它的完整规则可以直接在 client/components/boards/boardColors.css 中查看.board-color-dark选择器组覆盖#header、#header-quick-access、minicard、pop-over、sidebar、按钮、复选框、进度条、开关等全部部件。如果对内置 Dark 主题的细节有更高要求可进一步参考 Dark-Mode.md其中整理了其他深色方案Chrome 的 Dark Mode 扩展与 Decent 主题、Firefox 桌面/移动端的 Dark Mode Webextension、Thunderbird 的 TT DeepDark 主题含字体/颜色设置步骤、Qubes OS 桌面的 Adwaita-dark、以及 Android M 的系统级深色 UI。四、White 主题Tampermonkey 脚本重设计看板社区成员 pravdomilissue #1690编写了一个 Tampermonkey 用户脚本将 Wekan 看板重设计为白色风格。文档中提到其灵感来源是一个白底示例但这个 Tampermonkey 脚本最终呈现的效果是——白底 无边框泳道 极简卡片——与我们上面分析的 Dark 覆盖层走的是完全不同的设计路线后者追求深色 隐藏噪元素。White 主题完整脚本// UserScript // name Kanban // namespace https://pravdomil.com/ // version 0.1 // match https://wekan.indie.host/* // grant none // /UserScript ;(function() { const el document.createElement(style) // languageCSS el.textContent /* white background */ body { background-color: white; } /* header bar next to top bar */ #header #header-main-bar { position: absolute; right: 70px; left: 300px; top: -3px; padding: 0; height: calc(28px 3px); } /* swimlane white background, no borders, fix ellipsis */ .swimlane { background-color: white; } .swimlane-header-wrap { border: 0 !important; } .swimlane-header { text-overflow: initial !important; } /* column header only for first row */ .swimlane .list-header { margin: 4px 12px 4px; } .swimlane .list-header-name { display: none; } div:nth-child(1 of .swimlane) .list-header { margin: 20px 12px 4px; } div:nth-child(1 of .swimlane) .list-header-name { display: inline; } /* cells no borders, fix height, less padding, no add new card */ .list { border: 0; background-color: white; flex: 300px 0 0; } .list .list-body { height: 160px; padding: 0 2px; } .list .open-minicard-composer { display: none; } .list .open-list-composer { opacity: 0; transition: opacity .2s; } .list .open-list-composer:hover { opacity: 1; } /* card style */ .minicard-wrapper { margin-bottom: 2px !important; } .minicard { box-shadow: 0 0 16px rgba(0,0,0,0.15) inset; } /* card style for first and last column */ .swimlane .list:nth-child(2) .minicard { opacity: .5 !important; } .swimlane .list:nth-last-child(2) .minicard { opacity: .1 !important; } /* card details always center, menu items tweaks */ .card-details { position: absolute; z-index: 10000 !important; top: 0; bottom: 0; left: calc(50% - 510px / 2); } .pop-over-list .js-labels { display: none } .pop-over-list .js-move-card-to-top { display: none } .pop-over-list .js-move-card-to-bottom { display: none } .pop-over-list .js-archive { color: darkred } /* not needed */ .wekan-logo, .js-member, .attachments-galery { display: none; } document.body.appendChild(el) })()这个脚本的工程化亮点纯运行时注入document.createElement(style)appendChild通过match限定只在目标域名生效grant none表示不申请特权 API。布局重构技巧#header-main-bar用绝对定位改造成顶栏旁的独立条泳道列表头只保留第一行的名称div:nth-child(1 of .swimlane)是 CSSof语法在泳道分组中的应用首尾列卡片用opacity形成视觉纵深。细节打磨transition: opacity .2s让 add list 按钮渐变出现box-shadow inset给卡片内阴影质感隐藏wekan-logo、.js-member、.attachments-galery等非核心元素。五、内置主题体系与 Select Color 分类系统5.1 当前内置主题Wekan 内置了看板颜色模式与若干完整主题。文档列出的完整主题包括Dark由 lonix1 的深色 CSS 改进而来并叠加了 xet7 的更多修改即上面的 Dark 主题。Relax浅绿色背景帮助用户在贡献者的陪伴下放松文档原文Light green background that helps Wekan users to relax in company of Wekan contributor that sent theme CSS。文档还预告了 xet7 计划添加的两个新主题Corteza以白色为主与Octopus不同配色并隐藏部分 Wekan 功能。在今天的仓库中Corteza 已实现——从 boardColors.css 可见.board-color-corteza拥有完整的规则组。5.2 新一代主题分类flat / clear / dark / special当前仓库已将主题色组织为四类单一事实来源是 models/lib/themeCategories.js并有 tests/themeCategories.test.cjs 守护其与ALLOWED_BOARD_COLORS的一致性分类主题色是否支持自定义颜色flatbelize、nephritis、pomegranate、pumpkin、wisteria、moderatepink、strongcyan、limegreen、natural1 个单一强调色clearclearblue、cleargreen、clearorange、clearpink、clearpurple、clearred2 个双色渐变色彩滑条darkmidnight、dark、moderndark、exodark、cleandark无固定specialrelax、corteza、appleglasspastel、modern、cleanlight无固定分类的合理性在于flat设计是平面上的单一强调色因此一个自定义颜色即可整体换肤clear是双色渐变slide需要两个自定义颜色dark与special是手工调校的多色设计若让用户随意改色会破坏对比度与可读性因此固定不可自定义。选择器 UI 采用两级下拉 原生取色器Category: [ flat ▼ ] - 1st-level dropdown (themeCategories order) Theme: [ belize ▼ ] - 2nd-level dropdown (colors in the chosen category) (flat) Custom color: [ color wheel] - shown only for flat (clear) Colors: [ wheel 1] [ wheel 2] - shown only for clear (dark/special) — no custom color controls — [ Preview swatch ] [ Save ] [ Default / Unset ]该选择器由共享模板 client/components/main/themeColorPicker.jade 与 client/components/main/themeColorPicker.js 实现通过data.scope区分三种使用场景board看板设置 / Change Color → 写入board.colorglobal成员设置 / Change Color → 写入用户自己的全局覆盖issue #5778admin管理面板 / 设置 / 可见性 / Change color → 站点主题多租户环境下由服务端决定取实例级还是组织级的值。从 themeColorPicker.js 的实现细节可以确认色块按分类分组展示themeGroupshelper点击色块立即生效无保存按钮取色器使用原生input typecolor即 issue #5514 中已用于列表颜色的机制并复用isHexColor/toHex来自 models/lib/contrastColor.js仅当当前分类allowsCustomColor为 true 时显示取色器flat 1 个、clear 2 个。5.3 存储模型与主题优先级自定义颜色以有序的#rrggbb十六进制数组存储长度必须匹配分类的customColorCount看板级board.customThemeColors: [String]flat 为 1 个、clear 为 2 个用户级profile.globalThemeCustomColors: [String]。一个自定义 flat/clear 主题因此表示为{ color: belize, customThemeColors: [#123456] }——命名主题决定 CSS 设计自定义颜色覆盖其强调色清空customThemeColors即恢复主题默认色。主题生效的优先级从弱到强详见 Theme.md 与 globalThemeColor.js 的注释Wekan 默认主题站点主题管理面板 / 设置 / 可见性 / Change color 设置多租户宿主上组织的取值替换实例的用户自身覆盖成员设置。看板页面上看板自己的颜色拥有页面所有权因此站点主题不会应用到看板页但用户自身的覆盖会应用——因为用户要求它在所有地方生效。5.4 自定义颜色的渲染机制在旧文档写作时每个主题都是硬编码的 CSS 类如.board-color-belize { … #2980b9 … }。当前仓库已推进到CSS 变量驱动阶段client/components/main/globalThemeColor.js 中的 Tracker 响应式逻辑按主题优先级解析出当前生效的主题名与自定义颜色通过document.documentElement.style.setProperty在:root上写入--theme-accentflat 的单一强调色、--theme-accent-2clear 渐变的第二端以及--theme-accent-fill渐变填充值并切换has-custom-theme-color/has-custom-theme-slide两个 body 类命名主题本身如board-color-name则通过applyClass挂在body上全局主题或由看板页面的#header/.board-wrapper承担看板自己的主题。client/components/main/customTheme.css 消费这些变量flat 主题将 header 双栏、主按钮、侧边栏按钮染成var(--theme-accent)clear 主题用linear-gradient(135deg, var(--theme-accent), var(--theme-accent-2))渲染双色渐变。对看板级自定义色themeColorPicker预览条直接以内联样式展示flat 显示background: 色值clear 显示linear-gradient(135deg, c1, c2)因为命名主题的静态 CSS 在 CSS 变量重构完成前无法表达任意颜色。从源码看customTheme.css目前覆盖的是最高影响面header 栏、主按钮、侧边栏按钮文档将其定义为初始阶段后续迭代方向是把覆盖面扩大到完整的 flat/clear 表面minicard、pop-over、看板画布理想方式是把这些主题规则重构为直接读取--theme-accent*变量。六、服务端校验杜绝 CSS 注入自定义颜色是用户输入最终会变成 CSS 值因此必须在服务端存储前校验。models/lib/themeCategories.js 提供的isValidCustomColors(color, customColors)强制三条规则命名color必须属于允许自定义颜色的分类仅 flat/clearcustomColors数组长度必须等于该分类的计数每个条目必须匹配^#[0-9a-fA-F]{6}$6 位十六进制。const HEX_RE /^#[0-9a-fA-F]{6}$/; function isValidCustomColors(color, customColors) { const cat categoryOf(color); const count customColorCount(cat); if (count 0) return false; // dark/special/unknown: no custom colors if (!Array.isArray(customColors) || customColors.length ! count) return false; return customColors.every(c typeof c string HEX_RE.test(c)); }两个写入方法都被此校验把关任何不合规输入都会被拒绝因此任意字符串无法作为内联样式注入看板方法 models/boards.js 中的async setColor(color, customThemeColors)合法时写入customThemeColors否则$unset清除旧值用户方法 server/models/users.js 中的async setGlobalThemeColor(color, customColors)颜色名必须先通过BOARD_COLORS.includes(color)白名单校验防止客户端注入任意 CSS 类名自定义颜色再经isValidCustomColors校验传null则清除覆盖恢复默认。#rrggbb是唯一允许的格式从而不存在 CSS 注入面Because only #rrggbb passes, there is no CSS injection surface。单元测试 tests/themeCategories.test.cjs 覆盖了全部校验场景的正反用例flat 必须恰好 1 个 hex、clear 必须恰好 2 个 hex、dark/special/unknown 一律拒绝、#fff短格式与red等非 hex 值均被拒绝并额外做源码守卫断言分类并集与config/const.js的ALLOWED_BOARD_COLORS精确相等。运行方式node tests/themeCategories.test.cjs七、总结与实操建议综合文档与当前仓库源码Wekan 主题定制已形成两条清晰的路线个人快速换肤用 Stylish / Tampermonkey 等浏览器扩展注入 CSS 覆盖层Dark 主题、White 主题就是社区先行实践的产物零改动、可随时分享到 Issue适合不打算动代码的用户。正式内置主题遵循当前仓库的四步流程——ALLOWED_BOARD_COLORS注册 →themeCategories.js归类 →boardColors.css编写.board-color-name规则 → flat/clear 主题用--theme-accent*变量驱动最后以 PR 提交。对于想要深度理解主题机制的用户建议按此顺序阅读源码config/const.js颜色名注册表→ models/lib/themeCategories.js分类与校验→ tests/themeCategories.test.cjs一致性守护→ client/components/main/themeColorPicker.js选择器逻辑→ client/components/main/globalThemeColor.jsCSS 变量应用→ client/components/main/customTheme.css自定义色消费→ client/components/boards/boardColors.css全部内置主题规则。文档层面的完整设计说明见 Theme.mdSelect Color 分类设计与 Dark-Mode.md深色方案生态。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表