TOOLS为非官方社区,欢迎一起建设,答疑群:717421103

版本260427.1

全站通知:

Widgets

刷 历
编

阅读

    

2026-11-05更新

    

最新编辑:Lu_23333

阅读:

  

更新日期:2026-11-05

  

最新编辑:Lu_23333

来自ToolsWIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索
页面贡献者 :
Lu_23333
迦洛翎
AI提示

Widgets 是 MediaWiki 的扩展组件,用于嵌入自定义的 HTML、CSS 和 JavaScript 代码。

出于安全限制,MediaWiki 默认禁止在普通词条中直接使用 <script>、<iframe>、<style> 等原始 HTML 标签。

Widgets 扩展通过引入独立的 Widget 命名空间、限制编辑权限来在引入HTML的同时保障安全。不过,Widget无法防止用户编写危险代码,比如盲目信任用户输入或故意引入XSS(跨站脚本攻击)漏洞。这一安全级别与 MediaWiki:Common.js 和 Extension:Gadgets 相同,是支持 JS 必然存在的安全风险。

编写时,不推荐用 Widget 默认支持的 Smarty 模板变量传参,因为简单转义无法彻底规避XSS。推荐用JS读取页面,通过DOM传参。

页面结构

Widget页面由代码与说明两部分构成,通过 MediaWiki 的包含控制标签进行划分。

<includeonly>
<div id="widget-root">
    <!-- HTML结构 -->
</div>
<style>
    /* CSS样式 */
</style>
<script>
    // JavaScript代码
</script>
</includeonly><noinclude>
== 使用说明 ==
在此处编写组件的用途、依赖结构及参数说明。

=== 参数 ===
* '''参数1''':参数说明
* '''参数2''':参数说明

=== 调用示例 ===
<pre>
{{#widget:Demo}}
</pre>

=== 效果预览 ===
{{#widget:Demo}}
</noinclude>

包含控制

Widgets 扩展在读取 Widget: 页面时,会移除 <noinclude>...</noinclude> 标签及其包裹的全部内容,并剥离 <includeonly> 标签本身,将其余部分送入 Smarty 引擎作为 HTML 输出。

<includeonly>标签:

  • 包裹实际的Widget代码(HTML、CSS、JavaScript)
  • Widget页面本身不会显示这部分内容,只有在Widget被调用时才会输出这部分内容

<noinclude>标签:

  • 包裹Widget的文档和使用示例
  • 用于说明Widget的用途、参数和使用方法。只在Widget页面本身显示,调用时不输出

推荐结构组成:

  1. HTML容器:使用具有唯一ID的<div>作为根元素
  2. CSS样式:使用`<style>`标签定义样式,避免污染全局样式
  3. JavaScript代码:使用立即执行函数(IIFE)封装,避免变量污染
  4. 文档说明:在<noinclude>中提供清晰的使用文档

运行环境

MediaWiki 使用 ResourceLoader 异步加载前端基础库。Widget 中的内联 <script> 在浏览器解析到该位置时会立即执行,而此时系统的 jQuery($)以及 mw.* 接口可能尚未加载完毕。如果在内联脚本中直接调用 $ 或 mw.loader,往往会触发报错。

若脚本仅使用浏览器原生 DOM API,可直接执行;若脚本依赖 jQuery 或 MediaWiki 前端模块(如 mediawiki.api、mediawiki.util),应将初始化函数推入 window.RLQ 队列,等待 ResourceLoader 就绪后执行:

(window.RLQ = window.RLQ || []).push(function() {
    mw.loader.using(['mediawiki.api']).then(function() {
        // 此处可安全使用 $ 与 mw.Api
    });
});

作用域隔离

由于 Widget 输出的样式与脚本直接处于页面全局环境中,编写组件时推荐做好命名与作用域隔离。

比如 CSS 选择器应统一添加组件专属的类名前缀(例如 .widget-demo .header),避免直接对 div、table、p 等标签设置全局样式而破坏 Wiki 皮肤。JavaScript 代码应包裹在立即执行函数表达式(IIFE,即 (function() { ... })();)中,避免向全局作用域泄漏变量。

此外,同一个 Widget 可能会在单个页面中被调用多次。如需支持,HTML、JS等需要考虑适配,如使用class而不是id等。

在模板中,如需在多次调用模板时,仅调用一次指定Widget,可以使用类似方案:

{{#if:{{#varexists:页面唯一变量名}} || {{#vardefine:页面唯一变量名 | 1 }} {{#Widget:xxx}} }}

机制介绍

渲染

页面包含 {{#widget:名称}} 时,由 Smarty引擎 读取对应Widget页面并解析为 HTML。最终插入Widget调用位置。

该输出绕过 Wikitext 解析管线,因此组件内的 [[链接]]、{{模板}}、{{{参数|}}}、{{#函数:xxx}} 等语法会作为普通文本原样输出。如需展示经 Wikitext 解析的内容,应先在外部模板中解析,再交给 Widget 处理。

权限

Widget 代码会在用户浏览器中执行,出于安全考虑,该命名空间受严格编辑权限保护。仅管理员或小部件编辑者可创建和修改 Widget 页面。

使用无限制,普通用户可正常使用。因此,如果Widget存在XSS漏洞,有编辑权限的用户即可注入任意JS代码,需要注意。

缓存

受解析器缓存(Parser Cache)控制,保存 Widget 页面后,相关调用页面不自动刷新(包括自己),会继续显示旧缓存。

更新或调试 Widget 代码后,需刷新缓存(点击页面操作菜单中的「刷新」,或在 URL 后附加 ?action=purge)。

技术选型

在开发具体功能前,应根据功能性质在 MediaWiki 提供的几种扩展机制之间做出选择,避免将仅需静态排版的逻辑放入 Widget 中:

机制 处理阶段 核心用途 适用场景
模板 Wikitext 解析 复用 Wikitext 片段与页面排版 信息框、导航、提示框、标准化词条结构
Lua 模块 Scribunto 执行 处理复杂数据逻辑并生成 Wikitext 复杂条件分支、字符串与表处理、批量生成表格或分类
Widget 输出原始 HTML + 浏览器端执行 封装页面级 HTML、CSS 与 JavaScript 嵌入外部页面(iframe)、交互式计算工具、第三方图表库组件
Gadget 浏览器端加载 提供跨页面的站点级脚本与样式 全站界面增强、编辑辅助工具、通用交互特性

静态排版与结构化内容应优先使用模板、模块实现;当功能必须引入原始 HTML 标签(如 <iframe>、<canvas>、表单控件)或包含组件专属的前端交互逻辑时,适合封装为 Widget。


使用

直接使用

在页面或模板中,通过 #widget 解析器函数调用组件。

{{#widget:Widget名称}}

若组件使用 Smarty 变量接收参数,可在名称后通过管道符 | 传入键值对:

{{#widget:Widget名称|参数1=值1|参数2=值2}}

组件名称区分大小写(首字母除外),如果 Widget 位于子页面(如 Widget:Chart/Bar),调用时需写出完整路径 {{#widget:Chart/Bar}}。

模板封装

在实际使用中,通常会将 Widget 封装在同名模板中。

这可以提供符合 Wiki 习惯的参数名和默认值处理,还能结合 Wikitext 与 HTML 属性构建安全的 DOM 数据传递结构。

数据传递

Widget 常需根据页面情况展示不同内容。

Widgets 扩展支持通过 Smarty 模板变量传参,也可以用JS读取页面 DOM 结构。两者在安全性与渲染机制上存在本质差异,新组件应优先采用 DOM 传参。

安全隐患

Smarty 参数机制(<!--{$参数名}-->)本质上是服务端对原始 HTML 源码做字符串替换。普通词条页面允许任意用户编辑,恶意用户可在参数中构造特殊字符实施跨站脚本攻击(XSS)。

虽然 Smarty 提供转义修饰符,但无法彻底规避 XSS:转义不识别参数所处的语法上下文。

DOM 传参

为了从根本上规避服务端拼接原始 HTML 带来的注入风险,推荐将「数据输出」与「脚本逻辑」分离:由普通 MediaWiki 模板输出携带 data-* 属性或内部内容的标准 HTML 元素,再由 Widget 中的 JavaScript 从 DOM 中读取数据并完成渲染。

推荐将「数据输出」与「脚本逻辑」分离,彻底规避风险。由页面Wikitext或模板携带信息,比如特定id/class的元素data-* 属性、正文承载相关信息,Widget 中的 JavaScript 从 DOM 读取数据并执行。

模板中的 HTML 经 MediaWiki 严格过滤,属性值中的危险字符会被自动转义,不允许的危险标签或属性会被剥离。同时,容器内部仍支持正常的 Wikitext 语法解析。Widget 脚本获取纯文本数据后,避免执行其内容/直接拼接HTML,从而形成完整的安全闭环。

例如,在一个折叠面板的模板中声明数据容器并调用 Widget:

<includeonly><div class="wiki-fold-panel" data-open="{{{默认展开|false}}}">
  <div class="wiki-fold-header">{{{标题|详情}}}</div>
  <div class="wiki-fold-body">{{{内容|}}}</div>
</div>{{#widget:FoldPanel}}</includeonly>

在对应的 Widget:FoldPanel 中,无需使用任何 Smarty 变量,直接通过脚本读取 DOM 并绑定交互即可。

对于体量较大或多页面共享的结构化数据,则可将 JSON 数据保存在独立的页面中,由 Widget 脚本异步读取,避免在页面源码中重复内联大量数据。

Smarty 语法

对于维护既有组件,或仅需向固定模板传入简单受控标识符(如纯数字 ID、受白名单限制的枚举项)的场景,可使用 Widgets 扩展内置的 Smarty 模板语法。使用 Smarty 参数时,必须对所有变量输出位置施加格式校验或与上下文严格匹配的转义处理,严禁直接输出未经修饰的 <!--{$参数名}-->。

定界符

为避免与 CSS 规则和 JavaScript 代码中的花括号 { } 产生语法冲突,Widgets 扩展将 Smarty 的左、右定界符分别配置为 <!--{ 和 }-->。

<!--{* Smarty 内部注释,不会输出到最终页面 HTML 中 *}-->
<div><!--{$title|default:'默认标题'|escape:'html'}--></div>

变量修饰符

修饰符通过管道符 | 附加在变量名之后,用于对变量进行默认值填充、类型验证或字符转义。多个修饰符可链式组合,按从左至右的顺序依次执行。

修饰符 语法示例 功能说明
default <!--{$width|default:'100%'}--> 当参数未传入或为空字符串时,采用指定的默认值。未定义且无默认值的变量可能触发解析警告。
validate <!--{$id|validate:'int'}--> Widgets 扩展提供的类型校验修饰符。若参数值不符合指定格式则输出空字符串。支持的类型包括 int(整数)、float(浮点数)、boolean(布尔值)、url(完整 URL)等。
escape:'html' <!--{$text|escape:'html'}--> 将 & " ' < > 转换为 HTML 实体。仅适用于 HTML 标签内部的纯文本内容或以双引号包裹的普通非 URL 属性。
escape:'url' <!--{$key|escape:'url'}--> 对字符串执行 URL 百分号编码(同 PHP rawurlencode)。适用于拼接在固定 URL 路径或查询参数值的位置。
escape:'javascript' <!--{$str|escape:'javascript'}--> 转义引号、反斜杠与换行符。仅适用于放入纯 JavaScript 单引号或双引号字符串字面量内部。

控制结构

Smarty 支持通过 if / elseif / else 进行条件分支判断。当组件需要根据参数切换不同模式或样式时,应使用条件判断在已知的固定选项之间切换,避免将参数直接拼入代码结构:

<!--{if isset($mode) && $mode == 'compact'}-->
  <div class="widget-box widget-box-compact">...</div>
<!--{else}-->
  <div class="widget-box widget-box-normal">...</div>
<!--{/if}-->

当调用 Widget 时多次传入同名参数(如 {{#widget:Gallery|item=图1|item=图2}}),扩展会将其合并为数组,可通过 foreach 循环遍历:

<ul>
<!--{foreach from=$item item=val}-->
  <li><!--{$val|escape:'html'}--></li>
<!--{/foreach}-->
</ul>

创建Widget

确保你有权限编辑Widget命名空间,这需要管理员或小部件编辑者用户组

步骤1:创建Widget页面

1. 导航至要创建的Widget页面(如https://wiki.biligame.com/tools/Widget:TestPage)

2. 点击"创建"开始编辑

步骤2:编写Widget代码

基础模板示例:

<includeonly>
<div id="my-widget-<!--{$uniqueid|default:'default'}-->">
  <h3><!--{$title|escape:'html'}--></h3>
  <p><!--{$content|escape:'html'}--></p>
</div>

<style>
#my-widget-<!--{$uniqueid|default:'default'}--> {
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background-color: #f9f9f9;
}

#my-widget-<!--{$uniqueid|default:'default'}--> h3 {
  margin-top: 0;
  color: #333;
}
</style>

<script>
(function() {
  const widget = document.getElementById('my-widget-<!--{$uniqueid|default:'default'}-->');
  if (widget) {
    console.log('Widget initialized');
    // 添加交互功能
  }
})();
</script>
</includeonly>

<noinclude>
== My Widget ==
这是一个示例Widget,展示如何创建基本的HTML组件。

=== 参数 ===
* '''title''':标题文本(必需)
* '''content''':内容文本(可选)
* '''uniqueid''':唯一标识符(可选,用于同一页面多次调用)

=== 使用示例 ===
<pre>
{{#widget:MyWidget|title=欢迎使用|content=这是Widget内容}}
</pre>

=== 效果演示 ===
{{#widget:MyWidget|title=示例标题|content=这是一段示例内容}}
</noinclude>

步骤3 参数处理

Widget使用Smarty模板引擎处理参数,语法为<!--{$参数名}-->

参数语法:

基本参数:<!--{$参数名}-->
默认值:<!--{$参数名|default:'默认值'}-->
转义处理:<!--{$参数名|escape:'html'}-->(防止XSS攻击)
条件判断:<!--{if $参数名}-->...<!--{/if}-->

安全注意事项:

  • 始终转义用户输入:使用`|escape:'html'`修饰符防止XSS攻击
  • 验证参数:使用`|validate`修饰符验证参数类型
  • 避免直接输出HTML:未经转义的参数可能导致安全漏洞

参数转义示例:

<!-- 正确:转义HTML -->
<div class="title"><!--{$title|escape:'html'}--></div>

<!-- 错误:未转义(安全风险) -->
<div class="title"><!--{$title}--></div>

<!-- URL转义 -->
<a href="<!--{$url|escape:'url'}-->">链接</a>

<!-- JavaScript转义 -->
<script>
var message = "<!--{$message|escape:'javascript'}-->";
</script>

步骤4 测试Widget

1. 保存Widget页面

2. 在Widget页面的noinclude部分添加测试调用

3. 保存页面,刷新(purge)页面。

4. 在其他页面调用测试:{{#widget:Widget名称|参数=值}}


在 Wiki 中调用 Widget 嵌入页面并通过参数调用不同页面教程

在 Wiki 的使用过程中,常常会有在页面中嵌入其他内容并根据不同参数展示不同信息的需求,以下将以具体实例为大家详细介绍相关操作方法。

示例一:第五人格选手页面嵌入选手生涯数据查询页面

<iframe name="playershow" src="https://web.homeworkkun.top/赛事数据展示/playershow#<!--{$name}-->" frameborder="0" width="100%" id="framemain" style="width:100%;height:100%"></iframe>
  • 修改选手模板,补充调用widget页面的代码,并且给出对应的传参
{{#widget:选手生涯数据|name={{{1|}}}}}

示例二:NIKKE角色页面嵌入Spine动画展示页面

<includeonly><iframe name="framemain" src="https://wiki-dev-patch-oss.oss-cn-hangzhou.aliyuncs.com/res/nikke/spine/v<!--{$m}-->.html?id=c<!--{$id}-->" frameborder="0" width="100%" id="framemain" style="width:100%;height:100%"></iframe></includeonly>
  • 修改角色模板,补充调用widget页面的代码,并且给出对应的传参
{{#widget:SpinePlayer|id={{{ID}}}|m=_m}}


通过以上步骤,我们就可以在 Wiki 页面中成功调用 Widget 嵌入页面,并通过参数调用不同的页面内容,无论是在游戏角色信息展示还是比赛数据展示等场景中都能实现灵活的展示效果。