Docs 定制化完全指南:运行时主题、脚本注入与主题定制文件实战解析
2026/9/13 14:27:37 网站建设 项目流程

Docs 定制化完全指南:运行时主题、脚本注入与主题定制文件实战解析

【免费下载链接】docsDocs is an open-source text editor: web-native, made for real-time collaboration, cleanly structured documents and sub-documents with full ownership of your data. Built to scale with Django and React.项目地址: https://gitcode.com/GitHub_Trending/docs150/docs

Docs 作为一款基于 Django + React 构建的开源协同文档编辑器,为部署者提供了三层渐进式的定制能力:通过FRONTEND_CSS_URL/FRONTEND_JS_URL实现零代码的运行时主题与行为注入,通过THEME_CUSTOMIZATION_FILE_PATH指向的 JSON 主题定制文件实现图标、页脚、翻译、Waffle 服务网格等品牌化配置。本文将以官方定制指南 documentation/customization.md 为骨架,结合后端settings.py、配置接口ConfigView与前端ConfigProvider的源码实现,给出完整可落地的配置方案与参数说明。

读完本文,你将掌握:如何在不停机、不重新构建的前提下为 Docs 更换品牌皮肤与注入自定义脚本,如何通过一份 JSON 文件统一配置站点图标、多语言页脚、界面文案翻译与 Waffle 服务网格,并理解这些配置从前端请求到后端加载的完整链路。

一、定制化能力全景与工作机制

Docs 的定制化体系可以分为两条主线:

  1. 运行时注入(Runtime Injection):通过FRONTEND_CSS_URLFRONTEND_JS_URL两个环境变量,指向自托管的 CSS / JS 文件 URL。前端应用启动后会以<link rel="stylesheet"><script>标签的方式动态加载它们,无需任何代码改动或重新编译,适合快速换肤、注入第三方分析脚本、挂载自定义菜单等场景。
  2. 主题定制文件(Theme Customization File):通过THEME_CUSTOMIZATION_FILE_PATH指向一份 JSON 文件,集中配置header.icon(头部图标)、footer(页脚)、translations(翻译覆盖)、waffle(服务网格)、faviconhomehelponboarding等结构化选项,由后端在启动/运行时读取并提供给前端。

两条主线的配置最终都会汇聚到同一个入口:前端启动时请求GET /api/v1.0/config/获取公共配置,再交由ConfigProvider分发消费。这也是理解 Docs 定制化架构的关键:配置值在服务端定义,经配置 API 下发,由前端运行时应用,因此绝大部分定制都不需要重启前端或重新构建镜像。

二、运行时主题定制:FRONTEND_CSS_URL

2.1 配置方法

在 Docs 后端运行环境中设置环境变量,将其指向一个可公开访问的 CSS 文件地址:

FRONTEND_CSS_URL=http://anything/custom-style.css

设置后,Docs 前端会在页面<head>中注入<link rel="stylesheet" href="...">加载该样式表并应用到整个前端应用。从源码看,该变量在 settings.py 中被定义为可空配置项:

FRONTEND_CSS_URL = values.Value( None, environ_name="FRONTEND_CSS_URL", environ_prefix=None )

未设置时该值为None,前端不会注入任何外部样式表,行为与默认一致。

2.2 前端如何加载

在 ConfigProvider.tsx 中,前端通过 Next.js 的Head组件注入样式链接:

{conf?.FRONTEND_CSS_URL && ( <Head> <link rel="stylesheet" href={conf?.FRONTEND_CSS_URL} /> </Head> )}

也就是说,只要配置接口返回的FRONTEND_CSS_URL非空,样式表就会被插入页面头部,并按照 CSS 层叠规则覆盖默认样式。该配置字段同样出现在ConfigResponse类型定义中(见 useConfig.tsx)。

2.3 典型用例:自定义背景色

假设你想将整个应用的背景色替换为品牌色,可以创建一份自定义 CSS 文件并托管到任意静态服务器:

body { background-color: #3498db; }

然后设置FRONTEND_CSS_URL指向该文件。应用加载后,背景色即被覆盖为#3498db。由于是运行时加载,你可以随时替换 CSS 内容并在刷新后生效,无需重启任何服务。

2.4 优势与适用边界

  • 零代码定制:不需要修改或重新构建前端仓库;
  • 灵活换肤:可用任意 CSS 规则打造符合组织品牌的主题;
  • 运行时生效:修改 CSS 文件内容即可热切换主题,无需重启或重新编译。

需要注意的是,运行时 CSS 覆盖的是"样式层"而非"组件层":它能改变颜色、尺寸、布局等外观,但如果要调整 DOM 结构或交互行为,则需要配合下一节的 JavaScript 注入。

三、运行时 JavaScript 注入:FRONTEND_JS_URL

3.1 配置方法

与 CSS 类似,设置环境变量指向一个可公开访问的 JS 文件:

FRONTEND_JS_URL=http://anything/custom-script.js

Docs 前端会用 Next.js 的Script组件以afterInteractive策略加载该脚本(见 ConfigProvider.tsx):

{conf?.FRONTEND_JS_URL && ( <Script src={conf?.FRONTEND_JS_URL} strategy="afterInteractive" /> )}

afterInteractive表示脚本在页面完成水合(hydration)后执行,适合注入交互增强类逻辑。对应后端配置定义见 settings.py。

3.2 典型用例:向头部注入自定义菜单

官方文档给出的示例脚本会在应用头部追加一个自定义菜单按钮。脚本需要等待 DOM 就绪后再操作,确保目标元素存在:

(function() { 'use strict'; function initCustomMenu() { // Wait for the page to be fully loaded const header = document.querySelector('header'); if (!header) return false; // Create and inject your custom menu const customMenu = document.createElement('div'); customMenu.innerHTML = '<button>Custom Menu</button>'; header.appendChild(customMenu); console.log('Custom menu added successfully'); return true; } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initCustomMenu); } else { initCustomMenu(); } })();

设置FRONTEND_JS_URL指向该文件后,刷新页面即可在头部看到自定义菜单。同样的思路可以用于接入第三方埋点、注入辅助工具条、修改现有功能行为等,覆盖"样式无法触及"的动态定制需求。

3.3 优势与边界

  • 动态定制:无需代码改动即可修改行为与外观;
  • 高度灵活:可新增功能、改造既有特性或集成第三方服务;
  • 运行时注入:无需重启或重新编译。

边界提醒:注入脚本在浏览器端执行,属于"运行时增强"而非"后端逻辑修改",涉及数据面或权限面的功能仍需在 Django 后端实现;同时该脚本应具备幂等性,避免重复加载导致重复插入 DOM。

四、主题定制文件核心机制:THEME_CUSTOMIZATION_FILE_PATH

图标、页脚、翻译与 Waffle 配置都依托于同一套机制:主题定制文件(Theme Customization File)。这是理解 Docs 品牌化定制的钥匙。

4.1 环境变量与默认值

THEME_CUSTOMIZATION_FILE_PATH=<path>

后端定义见 settings.py:

THEME_CUSTOMIZATION_FILE_PATH = values.Value( os.path.join(BASE_DIR, "impress/configuration/theme/default.json"), environ_name="THEME_CUSTOMIZATION_FILE_PATH", environ_prefix=None, ) THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.IntegerValue( 60 * 60 * 24, environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT", environ_prefix=None, )

要点:

  • 默认路径BASE_DIR/impress/configuration/theme/default.json,即仓库中的 src/backend/impress/configuration/theme/default.json。该文件是 Docs 出厂默认的主题定制内容。
  • 缓存超时:默认86400秒(1 天)。主题定制 JSON 会被缓存,修改文件后最长可能需要一个缓存周期才会被重新读取(或清空缓存后立即生效)。
  • 若将该变量设为空字符串,后端会返回空的主题定制对象{}(见 viewsets.py)。

4.2 后端加载链路与缓存策略

主题定制文件由ConfigView._load_theme_customization()加载(见 viewsets.py),完整流程如下:

  1. THEME_CUSTOMIZATION_FILE_PATH未设置,直接返回{}
  2. 以文件路径的 slug 生成缓存键theme_customization_<slug>,先查 Django 缓存;
  3. 缓存未命中则用utf-8编码读取 JSON 文件并json.load解析;
  4. 若文件不存在(FileNotFoundError)或 JSON 非法(JSONDecodeError),记录 error 日志并返回{},不会导致服务崩溃;
  5. 解析成功后写入缓存,缓存时长为THEME_CUSTOMIZATION_CACHE_TIMEOUT

这套容错设计意味着:一个损坏或缺失的定制文件只会让定制项失效为默认值,而不会拖垮整个配置接口,相关行为也被后端测试覆盖(见 test_api_config.py 中针对不存在文件与非法 JSON 的用例)。

4.3 配置下发:GET /api/v1.0/config/

ConfigView是一个AllowAny权限、带config节流作用域的公开接口(见 viewsets.py)。它把FRONTEND_CSS_URLFRONTEND_JS_URLFRONTEND_THEMELANGUAGES等公共设置打包返回,并附上theme_customization(即主题定制文件解析结果)与RELEASE_VERSION

dict_settings["theme_customization"] = self._load_theme_customization() dict_settings["RELEASE_VERSION"] = settings.RELEASE return drf.response.Response(dict_settings)

前端通过 getConfig 请求config/并缓存到localStorage(键docs_config),随后useConfig以 5 分钟 staleTime 的 TanStack Query 持有该配置。前端ThemeCustomization接口(见 useConfig.tsx)声明了以下可用键:

类型用途
favicon{ light, dark }浅色/深色模式站点图标(<link rel="icon">属性)
footerFooterType页脚配置(支持多语言 +default兜底)
headerHeaderType头部图标/Logo 配置
help对象帮助文档 URL、支持邮箱、法务链接
home对象首页配置(如with-proconnecticon-banner
onboarding对象新手引导开关与链接
translationsResourcei18next 翻译资源覆盖
waffleLaGaufreV2PropsWaffle(La Gaufre)服务网格

ConfigProvider在拿到配置后分别处理:应用翻译覆盖、设置主题、注入 CSS/JS、设置 Sentry/PostHog、处理 favicon 与版本刷新逻辑(见 ConfigProvider.tsx)。

五、自定义 Docs 头部图标:header.icon

头部图标可以从主题定制文件中配置。以仓库自带的开发环境示例 src/helm/env.d/dev/configuration/theme/demo.json 为例:

{ "header": { "logo": {}, "icon": { "src": "/assets/icon-docs.svg", "style": { "width": "32px", "height": "auto" }, "alt": "", "withTitle": true } } }

字段说明:

  • src:图标资源路径,可指向仓库静态资源(如/assets/icon-docs.svg)或任意可访问的 URL;
  • style:行内样式,控制图标的渲染尺寸(如width/height);
  • alt:无障碍替代文本;
  • withTitle:是否连同产品标题一起展示;
  • header.logo:Logo 配置,示例中为空对象表示不启用。

该配置是可选的——如果主题定制文件中没有header.icon,前端将回退到默认图标。仓库中的默认文件 src/backend/impress/configuration/theme/default.json 使用的即是/assets/icon-docs.svg(宽高 40px)。

六、页脚配置:footer

页脚同样来自主题定制文件,且支持按语言分发的完整配置结构。

6.1 配置结构

{ "footer": { "default": { "logo": { "src": "/assets/icon-docs.svg", "width": "54px", "alt": "Docs Logo", "withTitle": true }, "externalLinks": [ { "label": "GitHub", "href": "https://github.com/suitenumerique/docs/" }, { "label": "DINUM", "href": "https://www.numerique.gouv.fr/dinum/" }, { "label": "ZenDiS", "href": "https://zendis.de/" }, { "label": "BlockNote.js", "href": "https://www.blocknotejs.org/" } ], "bottomInformation": { "label": "Unless otherwise stated, all content on this site is under", "link": { "label": "licence etalab-2.0", "href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md" } } }, "en": { "legalLinks": [ { "label": "Legal Notice", "href": "#" }, { "label": "Personal data and cookies", "href": "#" }, { "label": "Accessibility", "href": "#" } ], "bottomInformation": { "label": "Unless otherwise stated, all content on this site is under", "link": { "label": "licence MIT", "href": "https://github.com/suitenumerique/docs/blob/main/LICENSE" } } }, "fr": { "...": "同结构,法语文案" }, "de": { "...": "同结构,德语文案" }, "nl": { "...": "同结构,荷兰语文案" } } }

6.2 关键规则

  • footer.default是语言不匹配时的兜底配置:当用户当前语言在footer中没有对应键(如en/fr/de/nl)时,前端会回退到footer.default,保证任何语言环境下页脚都不会空白;
  • legalLinks:法务链接组(法律声明、隐私与 Cookie、无障碍声明等),可在各语言块内独立配置;
  • externalLinks:页脚展示的外部链接列表,label+href成对出现;
  • bottomInformation:页脚底部信息,支持label文本 + 内嵌link(文本与地址)。

官方文档同时给出了配置后的视觉效果示例(见 documentation/assets/footer-configurable.png),配置正确后页脚会按所选语言展示对应的法律链接与版权信息。

七、自定义翻译:translations

Docs 的界面文案可以部分覆盖——即基于现有翻译资源,仅对需要改动的键做增量覆盖,而不必维护整份语言文件。

7.1 配置方法

在主题定制文件中加入translations键,结构遵循 i18next 资源格式:

{ "translations": { "en": { "translation": { "Docs": "MyDocs", "New doc": "+" } } } }
  • 顶层键是语言代码(如enfrzh-CN等);
  • 语言下再套一层translation,内部是原始文案键 → 自定义文案值的映射;
  • 未覆盖的键继续使用内置翻译,因此这是一种部分覆盖机制。

7.2 前端如何应用

在 ConfigProvider.tsx 中,配置返回后调用customizeTranslations()将覆盖资源合并进当前 i18next 实例:

useEffect(() => { if (!conf?.theme_customization?.translations) { return; } customizeTranslations(conf.theme_customization.translations); }, [conf?.theme_customization?.translations, customizeTranslations]);

该逻辑位于features/language模块,与 Docs 的同步语言机制配合:当用户语言确定后,覆盖翻译会即时生效,无需刷新页面。

7.3 实战提示

  • 文案键必须与前端实际使用的 i18n key 完全一致(包括大小写与空格),例如示例中的"Docs""New doc",可通过源码中的useTranslation()调用点确认键名;
  • 由于翻译是前端 i18next 资源,覆盖范围受限于前端已声明的键,无法凭空新增翻译命名空间;
  • 多语言场景下,为每个语言代码各写一份translation块即可实现按语言的品牌化文案。

八、Waffle(La Gaufre)配置:waffle

Waffle(法语俗称 "La Gaufre",即"华夫饼")是 Docs 中展示**服务网格(services grid)**的小部件,典型用于把同一组织内的多个服务入口聚合到一个下拉/弹层网格中,方便用户在不同服务间跳转。

8.1 配置方法

在主题定制文件中使用waffle键,结构对齐前端 UI 组件库的LaGaufreV2Props。参考仓库示例 src/helm/env.d/dev/configuration/theme/demo.json:

{ "waffle": { "data": { "services": [ { "name": "Docs", "url": "https://docs.numerique.gouv.fr/", "maturity": "stable", "logo": "https://lasuite.numerique.gouv.fr/assets/products/docs.svg" }, { "name": "Visio", "url": "https://visio.numerique.gouv.fr/", "maturity": "stable", "logo": "https://lasuite.numerique.gouv.fr/assets/products/visio.svg" }, { "name": "Fichiers", "url": "https://fichiers.numerique.gouv.fr/", "maturity": "stable", "logo": "https://lasuite.numerique.gouv.fr/assets/products/fichiers.svg" } ] }, "showMoreLimit": 9 } }

8.2 可用属性(LaGaufreV2Props)

waffle的值直接对应前端 UI 组件库LaGaufreV2的 props(可在前端依赖@gouvfr-lasuite/ui-componentsLaGaufreV2.tsx中查看完整定义),常见属性包括:

属性说明
data.services静态服务列表,每个服务包含nameurlmaturitylogo等字段
showMoreLimit网格中直接展示的服务数量上限,超出部分收进"更多"折叠区(示例为 9)
apiUrl动态获取服务的数据接口地址

8.3 行为规则

官方文档明确了 Waffle 的两种数据来源行为:

  • 静态模式:如果提供了data.services,Waffle 直接展示这些服务,不发起额外请求;
  • 动态模式:如果不提供data,可以通过apiUrl属性从后端 API 端点动态拉取服务列表。

按需二选一:内部服务清单固定时用静态配置(如上例),服务由中台系统统一管理、需要实时变更时用apiUrl动态获取。

九、完整示例与实战清单

9.1 开箱即用的演示配置

仓库为 Helm 开发环境提供了完整的主题定制示例 src/helm/env.d/dev/configuration/theme/demo.json,它一次性覆盖了本文讲到的全部能力:

  • translations:把 "Docs" 文案替换为 "MyDocs"、把 "New doc" 替换为 "+";
  • footerdefault+en/fr/de/nl四语言页脚;
  • header.icon:自定义头部图标;
  • waffle:静态展示 Docs / Visio / Fichiers 三个服务;
  • 额外还包含home(首页 ProConnect 开关与横幅图标)与favicon(浅色/深色图标)。

9.2 上线前的检查清单

  1. 文件可达:确认THEME_CUSTOMIZATION_FILE_PATH指向的文件在容器/后端进程内真实存在,且可被读取(建议先手动执行python -c "import json;json.load(open('<path>'))"验证 JSON 合法性);
  2. 缓存预期:主题定制 JSON 默认缓存 1 天(THEME_CUSTOMIZATION_CACHE_TIMEOUT),上线后想立即生效可调小该值或清空缓存;
  3. 配置接口验证:部署后请求GET /api/v1.0/config/,检查theme_customization是否按预期返回;
  4. 资源可访问FRONTEND_CSS_URLFRONTEND_JS_URL以及图标/Logo 引用的资源地址必须对浏览器端可访问(注意 CSP、跨域与鉴权策略);
  5. 回退安全:任何一项配置异常(文件缺失、JSON 非法、字段缺失)都会优雅回退到默认值,不会阻断应用启动——这由后端_load_theme_customization的容错分支保证;
  6. 版本一致性ConfigProvider会比对前后端RELEASE_VERSION,不一致时自动触发一次刷新,确保定制配置与新版本代码匹配。

9.3 配置链路速查

环境变量(FRONTEND_CSS_URL / FRONTEND_JS_URL / THEME_CUSTOMIZATION_FILE_PATH ...) │ ▼ Django settings.py 定义与解析(src/backend/impress/settings.py) │ ▼ GET /api/v1.0/config/ → ConfigView 组装公共设置 + 加载主题定制 JSON(src/backend/core/api/viewsets.py#L3086-L3167,带缓存) │ ▼ 前端 useConfig 拉取并缓存到 localStorage(src/frontend/apps/impress/src/core/config/api/useConfig.tsx) │ ▼ ConfigProvider 分发:注入 CSS/JS、应用翻译、设置主题/favicon、初始化 Analytics(src/frontend/apps/impress/src/core/config/ConfigProvider.tsx)

通过这条链路,Docs 将"品牌化定制"从代码层剥离到了配置层:无论是部署在裸机、Docker Compose 还是 Kubernetes/Helm(对应模板见 src/helm/impress/templates),只需调整环境变量与主题定制 JSON,即可在不改动一行业务代码的前提下完成整套视觉与行为定制。

【免费下载链接】docsDocs is an open-source text editor: web-native, made for real-time collaboration, cleanly structured documents and sub-documents with full ownership of your data. Built to scale with Django and React.项目地址: https://gitcode.com/GitHub_Trending/docs150/docs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询