feat: 重构 laytpl,增强对更多复杂模板结构的解析能力 (#2577)

* feat: 重构 laytpl,增强对更多复杂模板结构的解析能力

* Squashed commit of the following:

commit 6884f80378
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 14:45:58 2025 +0800

    release: v2.10.1

commit 8d643ad6dc
Merge: 5521e48c 213fe5a2
Author: corededitor <107152508+corededitor@users.noreply.github.com>
Date:   Wed Mar 19 14:24:50 2025 +0800

    feat: Merge pull request #2566 from layui/feat/component

    feat: 优化 component, tabs 若干功能

commit 213fe5a209
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 14:15:39 2025 +0800

    docs: 添加 component 文档中实验性选项标记

commit 5521e48c05
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 14:10:46 2025 +0800

    fix: 修复 `body` 初始 `line-height` 无效的问题 (#2569)

commit 8c7cf0f606
Author: 青崖 <33601030+bxjt123@users.noreply.github.com>
Date:   Wed Mar 19 14:10:08 2025 +0800

    优化 checkbox 标签风格选中且禁用时的显示 (#2563)

commit 23b21254d4
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 14:04:44 2025 +0800

    docs: Squashed commit of the following:

    commit 95a0503f41
    Merge: e6eb86ba 87ba4c43
    Author: 贤心 <3277200+sentsim@users.noreply.github.com>
    Date:   Wed Mar 19 14:03:13 2025 +0800

        Merge branch 'main' into 2.x

    commit e6eb86bacb
    Author: morning-star <26325820+Sight-wcg@users.noreply.github.com>
    Date:   Wed Mar 19 14:02:05 2025 +0800

        docs(slider): 修正错别字 (#2578)

    commit 46f7a9783e
    Merge: df1fc4f4 c204590a
    Author: 贤心 <3277200+sentsim@users.noreply.github.com>
    Date:   Fri Mar 14 19:07:50 2025 +0800

        Merge branch 'main' into 2.x

    commit df1fc4f419
    Author: itletu <itletu@163.com>
    Date:   Mon Mar 10 13:54:06 2025 +0800

        docs: 更正 class 公共类文档错误 (#2544)

        | layui-border-box | 设置元素及其所有子元素均为 `box-sizing: border-box` 模型的容器 |

commit 87ba4c4394
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 13:41:33 2025 +0800

    docs(version): 优化 2.9.x 锚点

commit a0f533f0fd
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 13:37:29 2025 +0800

    docs: 修复 tabs 文档示例异常问题

commit 0f0584e2ed
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 13:35:00 2025 +0800

    docs: 修复 tabs 文档中自定义事件示例重新点击 Preview 失效的问题

commit 172957d243
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Wed Mar 19 13:29:09 2025 +0800

    docs: 优化文档中的用词细节 (#2571)

commit 094be4ddcc
Author: letianpailove <113023596+letianpailove@users.noreply.github.com>
Date:   Wed Mar 19 13:28:46 2025 +0800

    fix: 更正 class 公共类文档错误 (#2562)

commit 53ded26cb9
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Sun Mar 16 00:15:25 2025 +0800

    fix: 优化 tabs 重载时未按照传入的 closable 正确渲染可关闭状态

commit bd892bf87e
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Sun Mar 16 00:15:17 2025 +0800

    feat(component): 新增 cache 原型方法,用于元素缓存操作

commit 6ccc5a453d
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Sun Mar 16 00:14:18 2025 +0800

    fix(component): 优化元素 lay-options 属性上的配置在重载时的优先级

commit 79b0a56f50
Author: 贤心 <3277200+sentsim@users.noreply.github.com>
Date:   Sun Mar 16 00:13:55 2025 +0800

    fix(component): 修复 reload 时传入的选项未正确合并的问题

* refactor(laytpl): 优化代码细节

* docs: 重写 laytpl 模块文档
This commit is contained in:
贤心 2025-03-27 23:55:04 +08:00 committed by GitHub
parent b4fbb28a04
commit 169f6ff9b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1489 additions and 656 deletions

2
dist/css/layui.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/layui.js vendored

File diff suppressed because one or more lines are too long

2
dist/layui.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ toc: true
`MOD_NAME.render(options);` `MOD_NAME.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3> <h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3>

View File

@ -48,7 +48,7 @@ layui.use(function(){
- 在元素外层设置 `class="layui-carousel"` 来定义一个轮播容器 - 在元素外层设置 `class="layui-carousel"` 来定义一个轮播容器
- 在元素内层设置属性 `carousel-item` 用来定义条目容器 - 在元素内层设置属性 `carousel-item` 用来定义条目容器
<h3 id="demo-config" lay-toc="{level: 2, hot: true}">属性配置预览</h3> <h3 id="demo-config" lay-toc="{level: 2, hot: true}">可选项预览</h3>
<pre class="layui-code" lay-options="{preview: true, codeStyle: 'height: 515px;', layout: ['preview', 'code'], tools: ['full'], done: function(obj){ <pre class="layui-code" lay-options="{preview: true, codeStyle: 'height: 515px;', layout: ['preview', 'code'], tools: ['full'], done: function(obj){
obj.render(); obj.render();

View File

@ -33,7 +33,7 @@ div[carousel-item]>*:nth-child(2n+1){background-color: #16baaa;}
`carousel.render(options);` `carousel.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法返回一个实例对象,包含操作当前实例的相关方法成员。 该方法返回一个实例对象,包含操作当前实例的相关方法成员。
@ -46,7 +46,7 @@ console.log(inst); // 得到当前实例对象
`inst.reload(options);` `inst.reload(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
通过渲染返回的实例对象,可获得重载方法,用于实现对实例的属性重载。 通过渲染返回的实例对象,可获得重载方法,用于实现对实例的属性重载。

View File

@ -12,7 +12,7 @@ toc: true
`var codeInst = layui.code(options);` `var codeInst = layui.code(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options)。 - 参数 `options` : 基础属性项。[#详见属性](#options)。
其中 `codeInst` <sup>2.8.17+</sup> 即实例返回的对象,包含对当前实例进行重载等方法成员,如: 其中 `codeInst` <sup>2.8.17+</sup> 即实例返回的对象,包含对当前实例进行重载等方法成员,如:

View File

@ -27,7 +27,7 @@ toc: true
`colorpicker.render(options);` `colorpicker.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br><sup>2.7+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br><sup>2.7+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```

View File

@ -53,7 +53,7 @@ CONST: {
<td>-</td> <td>-</td>
</tr> </tr>
<tr> <tr>
<td>isRenderWithoutElem</td> <td>isRenderWithoutElem <br><sup>实验性</sup></td>
<td> <td>
渲染是否无需指定目标元素。即无需设置 `elem` 选项,一般用于渲染即显示的组件类型。 渲染是否无需指定目标元素。即无需设置 `elem` 选项,一般用于渲染即显示的组件类型。
@ -67,7 +67,7 @@ CONST: {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>isRenderOnEvent</td> <td>isRenderOnEvent <br><sup>实验性</sup></td>
<td> <td>
渲染是否由事件触发。如 `dropdown` 这类通过点击触发的组件,那么应该设置为 `true`;而诸如 `tabs` 这类初始即展示的组件,则应该设置为 `false`。*推荐根据组件类型始终显式设置对应值*。 渲染是否由事件触发。如 `dropdown` 这类通过点击触发的组件,那么应该设置为 `true`;而诸如 `tabs` 这类初始即展示的组件,则应该设置为 `false`。*推荐根据组件类型始终显式设置对应值*。
@ -81,7 +81,7 @@ CONST: {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>isDeepReload</td> <td>isDeepReload <br><sup>实验性</sup></td>
<td> <td>
组件重载时是否允许深度重载,即对重载时选项进行深度合并。 组件重载时是否允许深度重载,即对重载时选项进行深度合并。

View File

@ -17,7 +17,7 @@ toc: true
`layui.component(options);` `layui.component(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法返回一个对象,包含用于组件对外的基础接口,如:组件渲染、重载、事件操作,及构造函数等等。用法示例: 该方法返回一个对象,包含用于组件对外的基础接口,如:组件渲染、重载、事件操作,及构造函数等等。用法示例:
@ -45,7 +45,7 @@ layui.define('component', function(exports) {
}); });
``` ```
<h3 id="options" lay-toc="{level: 2}">属性配置</h3> <h3 id="options" lay-toc="{level: 2}">属性选项</h3>
<div> <div>
{{- d.include("/component/detail/options.md") }} {{- d.include("/component/detail/options.md") }}
@ -78,7 +78,7 @@ layui.define('component', function(exports) {
| 选项 | 描述 | | 选项 | 描述 |
| --- | --- | | --- | --- |
| elem | 件渲染指定的目标元素选择器或 DOM 对象 | | elem | 件渲染指定的目标元素选择器或 DOM 对象 |
| id | 组件渲染的唯一实例 ID | | id | 组件渲染的唯一实例 ID |
| show | 是否初始即渲染组件。通常结合创建组件设定的 `isRenderOnEvent` 选项决定是否启用 | | show | 是否初始即渲染组件。通常结合创建组件设定的 `isRenderOnEvent` 选项决定是否启用 |

View File

@ -30,7 +30,7 @@ toc: true
`dropdown.render(options);` `dropdown.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```
@ -111,7 +111,7 @@ dropdown.render({
`dropdown.reload(id, options);` `dropdown.reload(id, options);`
- 参数 `id` : 组件渲染时定义的 `id` 属性值 - 参数 `id` : 组件渲染时定义的 `id` 属性值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于对下拉菜单进行完整重载,所有属性均可参与到重载中。 该方法用于对下拉菜单进行完整重载,所有属性均可参与到重载中。

View File

@ -24,7 +24,7 @@ toc: true
`util.fixbar(options);` `util.fixbar(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<h2 id="options" lay-toc="{level: 2, hot: true}">属性</h2> <h2 id="options" lay-toc="{level: 2, hot: true}">属性</h2>

View File

@ -33,7 +33,7 @@ toc: true
`flow.load(options);` `flow.load(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
信息流是分页的另一种表现形式,新加载的内容不替换原有的内容,而是随着滚动条滚动而追加显示。[#详见示例](#examples) 信息流是分页的另一种表现形式,新加载的内容不替换原有的内容,而是随着滚动条滚动而追加显示。[#详见示例](#examples)
@ -47,7 +47,7 @@ toc: true
`flow.lazyimg(options);` `flow.lazyimg(options);`
- 参数 `options` : 属性配置项。可选项见下表。 - 参数 `options` : 属性项。可选项见下表。
| 属性名 | 描述 | | 属性名 | 描述 |
| --- | --- | | --- | --- |

View File

@ -448,7 +448,7 @@ form.on('select(test)', function(data){
`form.set(options);` `form.set(options);`
- 参数 `options` : 全局属性配置项。详见下表: - 参数 `options` : 全局属性项。详见下表:
| 属性名 | 描述 | 类型 | 默认值 | | 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |

View File

@ -42,7 +42,7 @@ toc: true
`laydate.render(options);` `laydate.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```
@ -77,7 +77,7 @@ layui.use(function(){
`laydate.hint(id, opts);` `laydate.hint(id, opts);`
- 参数 `id` : 组件渲染时定义的 `id` 属性值 - 参数 `id` : 组件渲染时定义的 `id` 属性值
- 参数 `opts` : 该方法支持的属性选项,详见下表 - 参数 `opts` : 该方法支持的属性选项,详见下表
| opts | 描述 | 类型 | 默认值 | | opts | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |

View File

@ -56,7 +56,7 @@ toc: true
`layer.open(options);` `layer.open(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
打开弹层的核心方法,其他不同类型的弹出方法均为该方法的二次封装。 打开弹层的核心方法,其他不同类型的弹出方法均为该方法的二次封装。
@ -79,7 +79,7 @@ var index = layer.open({
`layer.alert(content, options, yes);` `layer.alert(content, options, yes);`
- 参数 `content` : 弹出内容 - 参数 `content` : 弹出内容
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
- 参数 `yes` : 点击确定后的回调函数 - 参数 `yes` : 点击确定后的回调函数
该方法用于弹出 `dialog` 类型信息框(`type: 0`),参数自动向左补位。 该方法用于弹出 `dialog` 类型信息框(`type: 0`),参数自动向左补位。
@ -106,7 +106,7 @@ layer.alert('不开启图标', function(index){
`layer.confirm(content, options, yes, cancel);` `layer.confirm(content, options, yes, cancel);`
- 参数 `content` : 弹出内容 - 参数 `content` : 弹出内容
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
- 参数 `yes` : 点击确定后的回调函数 - 参数 `yes` : 点击确定后的回调函数
- 参数 `cancel` : 点击第二个按钮(默认「取消」)后的回调函数 - 参数 `cancel` : 点击第二个按钮(默认「取消」)后的回调函数
@ -133,7 +133,7 @@ layer.confirm('确定吗?', function(index){
`layer.msg(content, options, end);` `layer.msg(content, options, end);`
- 参数 `content` : 弹出内容 - 参数 `content` : 弹出内容
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
- 参数 `end` : 提示框关闭后的回调函数 - 参数 `end` : 提示框关闭后的回调函数
该方法用于弹出 `dialog` 类型提示框(`type: 0`),默认 `3` 秒后自动关闭。参数自动向左补位。 该方法用于弹出 `dialog` 类型提示框(`type: 0`),默认 `3` 秒后自动关闭。参数自动向左补位。
@ -162,7 +162,7 @@ layer.msg('提示框', {
`layer.load(icon, options);` `layer.load(icon, options);`
- 参数 `icon` : 加载图标风格,支持 `0-2` 可选值 - 参数 `icon` : 加载图标风格,支持 `0-2` 可选值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于弹出 `load` 类型加载层(`type: 3`)。 该方法用于弹出 `load` 类型加载层(`type: 3`)。
@ -184,7 +184,7 @@ layer.close(index);
- 参数 `content` : 弹出内容 - 参数 `content` : 弹出内容
- 参数 `elem` : 吸附的目标元素选择器或对象 - 参数 `elem` : 吸附的目标元素选择器或对象
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于弹出 `tips` 类型贴士层(`type: 4`),默认 `3` 秒后自动关闭。 该方法用于弹出 `tips` 类型贴士层(`type: 4`),默认 `3` 秒后自动关闭。
@ -206,7 +206,7 @@ layer.tips('显示在目标元素上方', '#id', {
`layer.prompt(options, yes);` `layer.prompt(options, yes);`
- 参数 `options` : 基础属性配置项。除了支持 [基础属性](#options) 之外,还支持下表私有属性: - 参数 `options` : 基础属性项。除了支持 [基础属性](#options) 之外,还支持下表私有属性:
| 私有属性 | 描述 | 类型 | 默认值 | | 私有属性 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -243,7 +243,7 @@ layer.prompt({
`layer.photos(options);` `layer.photos(options);`
- 参数 `options` : 基础属性配置项。除了支持 [基础属性](#options) 之外,还支持下表私有属性: - 参数 `options` : 基础属性项。除了支持 [基础属性](#options) 之外,还支持下表私有属性:
| 私有属性 | 描述 | 类型 | 默认值 | | 私有属性 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -312,7 +312,7 @@ layui.use(function(){
`layer.tab(options);` `layer.tab(options);`
- 参数 `options` : 基础属性配置项。除了支持 [基础属性](#options) 之外,还支持下表私有属性: - 参数 `options` : 基础属性项。除了支持 [基础属性](#options) 之外,还支持下表私有属性:
| 私有属性 | 描述 | 类型 | 默认值 | | 私有属性 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -405,7 +405,7 @@ layer.closeLast(['dialog', 'page']); // 关闭最近一次打开的信息框或
`layer.config(options);` `layer.config(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于全局设置弹层的默认基础属性。 该方法用于全局设置弹层的默认基础属性。

View File

@ -24,7 +24,7 @@ toc: true
`laypage.render(options);` `laypage.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<h2 id="options" lay-toc="{level: 2, hot: true}">属性</h2> <h2 id="options" lay-toc="{level: 2, hot: true}">属性</h2>

View File

@ -0,0 +1,86 @@
<table class="layui-table">
<colgroup>
<col width="150">
<col>
</colgroup>
<thead>
<tr>
<th>标签</th>
<th>描述</th>
</tr>
</thead>
<tbody>{{!
<tr>
<td>{{= }}</td>
<td>
转义输出。若字段存在 HTML将进行转义。
</td>
</tr>
<tr>
<td>{{- }} <sup>2.8+</sup></td>
<td>
原文输出。即不对 HTML 字符进行转义,但需做好 XSS 防护。
</td>
</tr>
<tr>
<td>{{# }} <sup></sup></td>
<td>
**旧版本风格**`< 2.11`Scriptlet 标签一般用于流程控制
```js
{{#if (d.title) { }}
标题:{{= d.title }}
{{#} else { }}
默认标题
{{#} }}
```
</td>
</tr>
<tr>
<td>{{ }} <sup></sup><sup>2.11+</sup></td>
<td>
**新版本风格**「Scriptlet 标签」。一般用于流程控制,如:
```js
{{ if (d.title) { }}
标题:{{= d.title }}
{{ } else { }}
默认标题
{{ } }}
```
需设置 `tagStyle: 'modern'` 后生效,否则模板会报错。
</td>
</tr>
<tr>
<td>{{# }} <sup></sup><sup>2.11+</sup></td>
<td>
**新版本风格**「注释标签」。即仅在模板中显示,不在视图中输出。
需设置 `tagStyle: 'modern'` 后生效,否则会被视为旧版本的 Scriptlet 标签。
</td>
</tr>!}}
<tr>
<td>{{!{{! !}}!}}</td>
<td>
忽略标签。即该区域中的标签不会被解析,一般用于输出原始标签。如:
```js
{{! {{! 这里面的 {{= escape }} 等模板标签不会被解析 !}} !}}
```
</td>
</tr>
</tbody>
</table>

View File

@ -1,59 +1,59 @@
<pre class="layui-code" lay-options="{preview: true, layout: ['preview'], copy: false, tools: ['full'], addTools: null}"> <pre class="layui-code" lay-options="{preview: true, layout: ['preview'], codeStyle: 'max-height: 520px;', copy: false, tools: ['full'], addTools: null}">
<textarea> <textarea>
{{! {{!
<style> <style>
.laytpl-demo{border: 1px solid #eee;} .laytpl-demo{border: 1px solid #eee;}
.laytpl-demo:first-child{border-right: none;} .laytpl-demo:first-child{border-right: none;}
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;} .laytpl-demo>textarea{position: relative; display: block; width:100%; height: 320px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;} .laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;}
.laytpl-demo .layui-tabs{top: -1px;}
#ID-tpl-view-body {
max-height: 320px; overflow: auto; clear: both;
}
#ID-tpl-view-body > div {
display: none;
}
.laytpl-demo pre {
margin: 0; padding: 16px; background-color: #1F1F1F; color: #F8F9FA; font-family: 'Courier New',Consolas, monospace;
}
</style> </style>
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs6 laytpl-demo"> <div class="layui-col-xs6 laytpl-demo">
<div>模板</div> <div>
&lt;textarea id="ID-tpl-src"&gt; <div style="cursor: pointer;" id="ID-tpl-src-title">
<h3>{{= d.title }}</h3> <strong>模板(旧版本)</strong>
<ul> <i class="layui-icon layui-icon-down layui-font-12"></i>
{{# layui.each(d.list, function(index, item){ }} </div>
<li> </div>
<span>{{= item.modname }}</span> &lt;textarea id="ID-tpl-src"&gt;&lt;/textarea>
<span>{{= item.alias }}</span>
<span>{{= item.site || '' }}</span>
</li>
{{# }); }}
{{# if(d.list.length === 0){ }}
无数据
{{# } }}
</ul>
&lt;/textarea>
</div> </div>
<div class="layui-col-xs6 laytpl-demo"> <div class="layui-col-xs6 laytpl-demo">
<div>数据</div> <div><strong>数据</strong></div>
&lt;textarea id="ID-tpl-data"&gt; &lt;textarea id="ID-tpl-data"&gt;
{ {
"title": "Layui 常用模块", "title": "Layui 常用组件",
"desc": "<a style=\"color:blue;\">一段带 HTML 内容的描述</a>",
"list": [ "list": [
{ {
"modname": "弹层", "title": "弹层",
"alias": "layer", "name": "layer"
"site": "layer.domain.com"
}, },
{ {
"modname": "表单", "title": "表单",
"alias": "form" "name": "form"
}, },
{ {
"modname": "表格", "title": "表格",
"alias": "table" "name": "table"
}, },
{ {
"modname": "日期", "title": "日期选择器",
"alias": "laydate" "name": "laydate"
}, },
{ {
"modname": "上传", "title": "标签页",
"alias": "upload" "name": "tabs"
} }
] ]
} }
@ -61,75 +61,198 @@
</div> </div>
<div class="layui-col-xs12 laytpl-demo" style="border-top: none;"> <div class="layui-col-xs12 laytpl-demo" style="border-top: none;">
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs6">视图</div> <div class="layui-col-xs6 layui-tabs" id="ID-tpl-view-header">
<ul class="layui-tabs-header">
<li><strong>渲染结果</strong></li>
<li><strong>源码</strong></li>
</ul>
</div>
<div class="layui-col-xs6" style="text-align: right"> <div class="layui-col-xs6" style="text-align: right">
<span id="ID-tpl-viewtime"></span> <span class="layui-badge" id="ID-tpl-view-time"></span>
</div> </div>
</div> </div>
<div class="layui-padding-sm" id="ID-tpl-view" style="max-height: 300px; padding: 16px; overflow: auto;"></div> <div id="ID-tpl-view-body">
<div class="layui-show layui-padding-3 layui-text" id="ID-tpl-view"></div>
<div><pre id="ID-tpl-view-code" ></pre></div>
</div>
</div> </div>
</div> </div>
<div class="layui-clear"></div> <div class="layui-clear"></div>
<!-- 新版本模板 -->
<script type="text/html" id="ID-tpl-template-modern">
<p>转义输出:{{= d.desc }}</p>
<p>原文输出:{{- d.desc }}</p>
{{#注释标签 - 仅在模板中显示,不在视图中输出 }}
<p>&#123;&#123;! 忽略标签,可显示原始标签:
{{ let a = 0; }} {{= escape }} {{- source }} {{#comments }} &#123;&#123;! ignore !&#125;&#125;
!&#125;&#125;</p>
{{#标签空主体测试 }}
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
<p>
条件语句:
{{= d.list.length ? d.title : '' }}
{{ if(d.title){}} - #AAAA{{='A'}}{{ } }}
</p>
<p>循环语句:</p>
<ul>
{{ d.list.forEach(function(item) { }}
<li>
<span>{{= item.title }}</span>
<span>{{= item.name }}</span>
</li>
{{ }); }}
</ul>
{{ if (d.list.length === 0) { }}
无数据
{{} }}
</script>
<!-- 旧版本模板 -->
<script type="text/html" id="ID-tpl-template-legacy">
<p>转义输出:{{= d.desc }}</p>
<p>原文输出:{{- d.desc }}</p>
<p>
条件语句:
{{= d.list.length ? d.title : '' }}
{{#if(d.title){}} - #AAAA{{='A'}}{{#}}}
</p>
<p>循环语句:</p>
<ul>
{{#d.list.forEach(function(item) { }}
<li>
<span>{{= item.title }}</span>
<span>{{= item.name }}</span>
</li>
{{#}); }}
</ul>
{{#if (d.list.length === 0) { }}
无数据
{{#} }}
</script>
<!-- import layui --> <!-- import layui -->
<script> <script>
layui.use(function(){ layui.use(['laytpl', 'util', 'tabs', 'dropdown'], function() {
var laytpl = layui.laytpl; var laytpl = layui.laytpl;
var util = layui.util; var util = layui.util;
var tabs = layui.tabs;
var dropdown = layui.dropdown;
var $ = layui.$; var $ = layui.$;
// 默认设置
laytpl.config({
// tagStyle: 'modern' // 初始化标签风格
});
// 获取模板和数据 // 获取模板和数据
var get = function(type){ var getData = function(type) {
return { return {
template: $('#ID-tpl-src').val(), // 获取模板 template: $('#ID-tpl-src').val(), // 获取模板
data: function(){ // 获取数据 data: function(){ // 获取数据
try { try {
return JSON.parse($('#ID-tpl-data').val()); return JSON.parse($('#ID-tpl-data').val());
} catch(e){ } catch(e) {
$('#ID-tpl-view').html(e); $('#ID-tpl-view').html(e);
} }
}() }()
}; };
}; };
var data = get(); // 视图渲染
var renderView = function(html, startTime) {
// 耗时计算 timer(startTime);
var startTime = new Date().getTime(), timer = function(startTime, title){ $('#ID-tpl-view').html(html);
var endTime = new Date().getTime(); $('#ID-tpl-view-code').html(util.escape(html));
$('#ID-tpl-viewtime').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
}; };
// 渲染模板 // 生成模板
var thisTpl = laytpl(data.template); var createTemplate = function(opts) {
opts = $.extend({
tagStyle: 'legacy'
}, opts);
// 执行渲染 // 初始化模板
thisTpl.render(data.data, function(view){ var elem = $('#ID-tpl-template-'+ opts.tagStyle);
timer(startTime); $('#ID-tpl-src').val(elem.html().replace(/^\s+/g, ''));
$('#ID-tpl-view').html(view);
return opts;
};
var tplConfig = createTemplate();
var data = getData();
// 耗时计算
var timer = function(startTime, title) {
var endTime = new Date();
$('#ID-tpl-view-time').html((title || '本次渲染总耗时:')+ (endTime - startTime) + 'ms');
};
var startTime = new Date();
// 创建一个模板实例
var templateInst = laytpl(data.template, {
condense: false, // 不处理连续空白符,即保留模板原始结构
tagStyle: tplConfig.tagStyle
});
// 初始渲染
templateInst.render(data.data, function(html) {
renderView(html, startTime);
}); });
// 编辑 // 编辑
$('.laytpl-demo textarea').on('input propertychange', function(){ $('.laytpl-demo textarea').on('input', function() {
var data = get(); var data = getData();
if(!data.data) return; var startTime = new Date();
// 计算模板渲染耗时 // 若模板有变化,则重新编译模板
var startTime = new Date().getTime(); if (this.id === 'ID-tpl-src') {
templateInst.compile(data.template);
// 若模板有变化,则重新解析模板;若模板没变,数据有变化,则从模板缓存中直接渲染(效率大增)
if(this.id === 'ID-tpl-src'){
thisTpl.parse(data.template, data.data); // 解析模板
} }
// 执行渲染 // 若模板没变,数据有变化,则从模板缓存中直接渲染数据(效率大增)
thisTpl.render(data.data, function(view){ templateInst.render(data.data, function(html) {
timer(startTime); renderView(html, startTime);
$('#ID-tpl-view').html(view);
}); });
}); });
// 视图结果 tabs
tabs.render({
elem: '#ID-tpl-view-header',
body: ['#ID-tpl-view-body', '>div']
});
// 切换模板
dropdown.render({
elem: '#ID-tpl-src-title',
data: [{
title: '新版本模板',
tagStyle: 'modern'
}, {
title: '旧版本模板',
tagStyle: 'legacy'
}],
click: function(obj){
createTemplate({
tagStyle: obj.tagStyle
});
this.elem.children('strong').html(obj.title);
// 同步设置标签风格
templateInst.config.tagStyle = obj.tagStyle;
var data = getData();
var startTime = new Date();
// 重新渲染
templateInst.compile(data.template).render(data.data, function(html) {
renderView(html, startTime);
});
}
})
}); });
</script> </script>!}}</textarea>
!}}
</textarea>
</pre> </pre>

View File

@ -7,72 +7,85 @@
<tr> <tr>
<th>标签</th> <th>标签</th>
<th>描述</th> <th>描述</th>
<th>类型</th>
<th>默认值</th>
</tr> </tr>
</thead> </thead>
<tbody>{{! <tbody>{{!
<tr> <tr>
<td>{{= }}</td> <td>open</td>
<td> <td>
转义输出。若字段存在 HTML将进行转义。 用于设置起始界定符
``` </td>
<h2>{{= d.title }}</h2> <td>string</td>
``` <td>
`{{`
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{{- }} <sup>2.8+</sup></td> <td>close</td>
<td> <td>
原始输出。若字段存在 HTML将正常渲染。 用于设置结束界定符
``` </td>
<div>{{- d.content }}</div> <td>string</td>
``` <td>
该语句一般在需要正常渲染 HTML 时用到,但若字段存在 script 等标签,为防止 xss 问题,可采用 `{{= }}` 进行转义输出。 `}}`
> ### 注意
> 由于 `2.6.11` 版本对 laytpl 语句进行了重要调整,原 `{{ }}` 语法即等同 `{{- }}`升级版本时请进行相应调整。可参考https://gitee.com/layui/layui/issues/I5AXSP
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{{# }}</td> <td>cache <sup>2.11+</sup></td>
<td> <td>
JavaScript 语句。一般用于逻辑处理。 是否开启模板缓存,以便下次渲染时不重新编译模板
```
<div>
{{#
var fn = function(){
return '2017-08-18';
};
}}
{{# if(true){ }}
开始日期:{{= fn() }}
{{# } else { }}
已截止
{{# } }}
</div>
```
</td> </td>
</tr>!}} <td>boolean</td>
<tr>
<td>{{!{{! !}}!}}</td>
<td> <td>
对一段指定的模板区域进行过滤,即不解析该区域的模板。 `true`
```
{{! {{! 这里面的模板不会被解析 !}} !}}
```
</td> </td>
</tr> </tr>
</tbody> <tr>
<td>condense <sup>2.11+</sup></td>
<td>
是否压缩模板空白符,如:将多个连续的空白符压缩为单个空格
</td>
<td>boolean</td>
<td>
`true`
</td>
</tr>
<tr>
<td>tagStyle<br><sup>2.11+</sup></td>
<td>
设置标签风格。可选值:
- `legacy`: 采用 `< 2.11` 旧版本的标签风格
- `modern`: 采用 `2.11+` 新版本的标签风格
为了保持向下兼容,默认仍然采用旧版本的标签风格,但在后续版本可能会将默认值设置为 `modern`,因此,**实际使用时,建议显式设置该选项值,以免升级时产生不兼容的问题**。
</td>
<td>string</td>
<td>
`legacy`
</td>
</tr>
</tbody>!}}
</table> </table>

View File

@ -5,11 +5,11 @@ toc: true
# 模板引擎 # 模板引擎
> `laytpl` 是 Layui 的一款轻量 JavaScript 模板引擎,在字符解析上有着比较出色的表现。 > `laytpl` 是 Layui 内置的 JavaScript 模板引擎,采用原生控制流,在模板解析上有着比较出色的表现。
<h2 id="test" lay-toc="{hot: true}" style="margin-bottom: 0;">在线测试</h2> <h2 id="test" lay-toc="{hot: true}" style="margin-bottom: 0;">在线测试</h2>
在以下*模板*或*数据*中进行编辑,下方*视图*将呈现对应结果 对文本框中的*模板*或*数据*进行编辑,下方将呈现对应的*渲染结果*。注:自 <sup>2.11+</sup> 版本开始,你可以设置 `tagStyle: 'modern'` 让模板采用新的标签风格。为了保持向下兼容,默认仍然采用旧版本的标签风格
<div> <div>
{{- d.include("/laytpl/detail/demo.md") }} {{- d.include("/laytpl/detail/demo.md") }}
@ -20,135 +20,256 @@ toc: true
| API | 描述 | | API | 描述 |
| --- | --- | | --- | --- |
| var laytpl = layui.laytpl | 获得 `laytpl` 模块。 | | var laytpl = layui.laytpl | 获得 `laytpl` 模块。 |
| [laytpl(str, options).render(data, callback)](#render) | laytpl 组件渲染,核心方法。 | | [var templateInst = laytpl(template, options)](#laytpl) | 创建模板实例。 |
| [laytpl.config(options)](#config) | 配置 laytpl 全局属性 | | [laytpl.config(options)](#config) | 设置基础选项默认值 |
| [laytpl.extendVars(variables)](#variables) <sup>2.11+</sup> | 扩展模板内部变量 |
<h2 id="render" lay-toc="{level: 2, hot: true, title: '解析和渲染'}">模板解析和渲染</h2> <h3 id="laytpl" lay-toc="{level: 2, hot: true}">创建模板实例</h3>
`laytpl(str, options).render(data, callback);` `var templateInst = laytpl(template, options)`
- 参数 `str` : 模板原始字符 - 参数 `template` : 原始模板字符
- 参数 `options` <sup>2.8+</sup> : 当前模板实例的属性配置项。可选项详见:[#属性配置](#config) - 参数 `options` <sup>2.8+</sup> : 当前模板实例的选项。详见下述:[#基础选项](#options)
- 参数 `data` : 模板数据
- 参数 `callback` : 模板渲染完毕的回调函数,并返回渲染后的字符 该方法返回一个模板编译器实例,用于对模板进行数据渲染等操作,实例对象的成员有:
| 实例成员 | 描述 |
| --- | --- |
| templateInst.render(data, callback) | 给模板实例进行数据渲染,返回渲染后的 HTML 字符 |
| templateInst.compile(template) <sup>2.11+</sup> | 编译新的模板,会强制清除旧模板缓存 |
| templateInst.config <sup>2.11+</sup> | 获取当前模板实例的配置选项 |
通过将模板编译与渲染两个环节分开,我们可以在模板仅编译一次的情况下,对其渲染不同的数据,如:
{{! {{!
```js
var laytpl = layui.laytpl;
``` // 创建模板实例
layui.use('laytpl', function(){ var templateInst = laytpl('{{= d.name }}是一名{{= d.role }}');
var laytpl = layui.laytpl;
// 直接解析字符 // 数据渲染 1
laytpl('{{= d.name }}是一名前端工程师').render({ templateInst.render({
name: '张三' name: '张三',
}, function(str){ role: '全栈开发者'
console.log(str); // 张三是一名前端工程师 }, function(html) {
}); console.log(html); // 张三是一名全栈开发者
});
// 同步写法 // 数据渲染 2
var str = laytpl('{{= d.name }}是一名前端工程师').render({ var html = templateInst.render({
name: '张三' name: '王五',
}); role: '架构师'
console.log(str); // 张三是一名前端工程师
}); });
``` ```
!}}
模板字符较大,可存放在页面某个标签中,如: 每次需要对不同的模板进行编译和数据渲染,你也可以使用链式写法,如:
{{!
```js
laytpl('{{= d.name }}是一名{{= d.role }}').render({
name: '张三',
role: '全栈开发者'
}, function(html) {
console.log(html); // 张三是一名全栈开发者
});
``` ```
<script id="TPL" type="text/html"> !}}
若模板字符较大,你可以将模板存放在页面某个标签中,如:
{{!
```js
<script id="ID-demo-tpl" type="text/html">
<h3>{{= d.name }}</h3> <h3>{{= d.name }}</h3>
<p>性别:{{= d.sex ? '男' : '女' }}</p> <p>角色:{{= d.role }}</p>
</script> </script>
<div id="view"></div> <div id="ID-demo-view"></div>
<!-- import layui --> <!-- import layui -->
<script> <script>
layui.use(function(){ layui.use(function(){
var laytpl = layui.laytpl; var laytpl = layui.laytpl;
// 渲染 var template = document.getElementById('ID-demo-tpl').innerHTML; // 获取模板字符
var data = { var target = document.getElementById('ID-demo-view'); // 输出结果的目标元素
name: '张三', var data = { // 数据
sex: 1 "name": "张三",
"role": "全栈开发者"
}; };
var getTpl = document.getElementById('TPL').innerHTML; // 获取模板字符
var elemView = document.getElementById('view'); // 视图对象
// 渲染并输出结果 // 渲染并输出结果
laytpl(getTpl).render(data, function(str){ laytpl(template).render(data, function(html) {
elemView.innerHTML = str; target.innerHTML = html;
}); });
}); });
</script> </script>
``` ```
!}} !}}
实际使用时,若模板通用,而数据不同,为减少模板解析的开销,可将语句分开书写,如 实际使用时,若模板通用,而数据不同,为了避免对模板进行不必要的重复编译,推荐将创建模板实例与数据渲染分开书写
``` <h3 id="options" lay-toc="{level: 2, hot: true}">基础选项</h3>
var compile = laytpl(str); // 模板解析
compile.render(data, callback); // 模板渲染
```
<h2 id="grammar" lay-toc="{level: 2, hot: true}">标签语法</h2> 创建模板实例时,你还可以对其设置一些选项,如:
{{!
```js
// 创建模板实例
var templateInst = laytpl(`
{{ let role = d.role || '全栈开发者'; }}
{{= d.name }}是一名{{= role }}
`, {
tagStyle: 'modern' // 采用新版本的标签风格
});
var html = templateInst.render({ name: '张三' });
```
!}}
支持设置的完整选项如下:
<div> <div>
{{- d.include("/laytpl/detail/options.md") }} {{- d.include("/laytpl/detail/options.md") }}
</div> </div>
> ### 注意 <h3 id="delimiter" lay-toc="{level: 2, hot: true}">标签规则</h3>
> 开发者在使用模板语法时,需确保模板中的 JS 语句不来自于页面用户输入,即必须在页面开发者自身的可控范围内,否则请避免使用该模板引擎。
<h2 id="config" lay-toc="{level: 2}">属性配置</h2> <div>
{{- d.include("/laytpl/detail/delimiter.md") }}
</div>
#### ⚡ 请注意:
> *开发者在使用模板标签时,需确保模板中待输出的内容在开发者自身的可控范围内,尤其对于用户输入的字符要做好 XSS 防护,否则请避免使用该模板引擎,以免产生 XSS 安全隐患*
<h3 id="include" lay-toc="{level: 2, hot: true}">导入子模板 <sup>2.11+</sup></h3>
{{!
laytpl 支持在模板中通过添加 `{{- include(id, data) }}` 语句引入子模板。`include` 语句参数解释:
- `id` : 子模板 ID
- `data` : 向子模版传入的数据
为了引入的子模板不被转义,因此这里应该使用 `{{- }}`,即对子模板进行原文输出。示例:
<pre class="layui-code" lay-options="{preview: true, layout: ['code', 'preview'], codeStyle: 'max-height: 520px;', tools: ['full']}">
<textarea>
<script id="ID-demo-tpl-header" type="text/html">
<div>头部公共模板</div>
</script>
<script id="ID-demo-tpl-list" type="text/html">
<ul>
{{ d.items.forEach(function(item, index) { }}
<li>
<span>{{= item.title }}</span>
{{ if(item.children) { }}
{{- include('ID-demo-tpl-list', { items: item.children }) }}
{{ } }}
</li>
{{ }); }}
</ul>
</script>
<script id="ID-demo-tpl-main" type="text/html">
{{- include('ID-demo-tpl-header') }}
<h3>循环输出:</h3>
{{- include('ID-demo-tpl-list', { items: d.items }) }}
</script>
<div id="ID-demo-view"></div>
<!-- import layui -->
<script>
layui.use(function() {
var laytpl = layui.laytpl;
var template = document.getElementById('ID-demo-tpl-main').innerHTML; // 获取模板字符
var target = document.getElementById('ID-demo-view'); // 输出结果的目标元素
var data = {
items: [{"title": "list 1", "children": [{"title": "list 1-1", "children": [{"title": "list 1-1-1"}]}, {"title": "list 1-2"}]},{"title": "list 2", "children": [{"title": "list 2-1"}]},{"title": "list 3"}]
};
// 创建模板实例
var templateInst = laytpl(template, {
tagStyle: 'modern' // 采用新版本的标签风格
});
// 渲染并输出结果
templateInst.render(data, function(html) {
target.innerHTML = html;
});
});
</script>
</textarea>
</pre>
!}}
若在 Node.js 环境,可通过 `laytpl.extendVars()` 方法重置 `include` 语句实现模板文件的导入。
<h3 id="config" lay-toc="{level: 2}">设置选项默认值</h3>
`laytpl.config(options);` `laytpl.config(options);`
- 参数 `options` : 属性配置项。可选项详见下表 - 参数 `options`: 基础选项
| 属性 | 描述 | 你可以设置任意选项的默认值,如:
| --- | --- |
| open | 标签符前缀 |
| close | 标签符后缀 |
### 全局配置 {{!
```js
若模板默认的标签符与其他模板存在冲突,可通过该方法重新设置标签符,如:
```
laytpl.config({ laytpl.config({
open: '<%', open: '<%', // 自定义起始界定符
close: '%>' close: '%>', // 自定义起始界定符
tagStyle: 'modern' // 采用新版本的标签风格
}); });
// 模板语法将默认采用上述定义的标签符书写 // 创建模板实例
laytpl(` var templateInst = laytpl(`
<%# var job = ["前端工程师"]; %> <% var roles = ["前端工程师","全栈工程师","架构师"]; %>
<%= d.name %>是一名<%= job[d.type] %>。 <%= d.name %>是一名<%= roles[d.role] %>
`).render({ `);
// 渲染
templateInst.render({
name: '张三', name: '张三',
type: 0 role: 1
}, function(string){ }, function(string){
console.log(string); // 张三是一名前端工程师。 console.log(string); // 张三是一名全栈工程师
}); });
``` ```
!}}
### 局部配置 <sup>2.8+</sup> <h3 id="variables" lay-toc="{level: 2}">扩展模板内变量</h3>
若不想受到上述全局配置的影响,可在 `laytpl(str, options)` 方法的第二个参数中设置当前模板的局部属性,如: `laytpl.extendVars(variables)`
``` - 参数 `variables` : 扩展的变量列表,变量值通常是一个函数
laytpl('<%= d.name %>是一名前端工程师', {
open: '<%', 事实上 laytpl 内置了一些模板内部方法,如 `_escape, include`。你可以对它们进行重构,或扩展更多内部变量,如:
close: '%>'
}).render({name: '张三'}, function(string){ {{!
console.log(string); // 张三是一名前端工程师。 ```js
// 扩展模板内部变量
laytpl.extendVars({
// 重构 include 方法,实现引入模板文件
include: function(filename, data) {
// …
},
// 添加 toDataString 方法
toDataString: function(date) {
date = date || new Date();
return new Date(date).toLocaleDateString();
}
});
// 在模板中使用扩展的变量
var templateInst = laytpl('日期:{{= toDataString(d.time) }}');
templateInst.render({ time: 1742745600000 }, function(html) {
console.log(html);
}); });
``` ```
!}}
## 💖 心语
## 贴士 我们在 `2.11` 版本对 laytpl 完成了重要重构,使其能够具备应对更多复杂模板结构的解析能力。同时,为了与业界常用的 JavaScript 模板引擎 ejs 对齐,我们新增了与 ejs 相同的标签规则,这意味着同一套模板可以在 laytpl 和 ejs 中任意切换。
> Layui table 等组件的动态模板功能,均采用 laytpl 驱动。 laytpl 亦可承载单页面应用开发中的视图模板。 作为 Layui 为数不多的一个纯功能型的模块laytpl 承载了一些重要组件的功能支撑,如 table, dropdown 等使得它们也能够自定义动态模板增强了组件的可定制化。当然laytpl 也可以作为前端单页面应用及 Express 等 Web 框架的视图引擎

View File

@ -151,19 +151,19 @@ toc: true
<div class="layui-colla-item"> <div class="layui-colla-item">
<div class="layui-colla-title">杜甫</div> <div class="layui-colla-title">杜甫</div>
<div class="layui-colla-content layui-show"> <div class="layui-colla-content layui-show">
一代诗圣 唐代著名诗人,与李白齐名
</div> </div>
</div> </div>
<div class="layui-colla-item"> <div class="layui-colla-item">
<div class="layui-colla-title">李白</div> <div class="layui-colla-title">李白</div>
<div class="layui-colla-content"> <div class="layui-colla-content">
<p>一代诗仙</p> <p>唐代著名诗人,与杜甫齐名</p>
</div> </div>
</div> </div>
<div class="layui-colla-item"> <div class="layui-colla-item">
<div class="layui-colla-title">王勃</div> <div class="layui-colla-title">王勃</div>
<div class="layui-colla-content"> <div class="layui-colla-content">
<p>千古绝唱《滕王阁序》</p> <p>著有千古名篇《滕王阁序》</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -26,7 +26,7 @@ toc: true
`rate.render(options);` `rate.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```

View File

@ -47,7 +47,7 @@
滑块初始值。 滑块初始值。
- 默认可直接设置数值,如: `value: 50` - 默认可直接设置数值,如: `value: 50`
- 若滑块开启 `range: true` 区间选择,则值为数组,表示开始和结尾的区间,如: `value: [30, 60]` - 若滑块开启 `range: true` 区间选择,则值为数组,表示开始和结尾的区间,如: `value: [30, 60]`
</td> </td>
<td>number<br>array</td> <td>number<br>array</td>

View File

@ -24,13 +24,13 @@ toc: true
| var slider = layui.slider | 获得 `slider` 模块。 | | var slider = layui.slider | 获得 `slider` 模块。 |
| [var inst = slider.render(options)](#render) | slider 组件渲染,核心方法。 | | [var inst = slider.render(options)](#render) | slider 组件渲染,核心方法。 |
| [inst.setValue(value)](#setValue) | 设置滑块值 | | [inst.setValue(value)](#setValue) | 设置滑块值 |
| inst.config | 获得当前实例的属性配置项 | | inst.config | 获得当前实例的属性项 |
<h2 id="render" lay-toc="{level: 2}">渲染</h2> <h2 id="render" lay-toc="{level: 2}">渲染</h2>
`slider.render(options);` `slider.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br><sup>2.8+</sup> : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```

View File

@ -185,7 +185,7 @@ layui.use(function(){
`element.tabAdd(filter, options);` `element.tabAdd(filter, options);`
- 参数 `filter` : tab 容器(`class="layui-tab"`)的 `lay-filter` 属性值 - 参数 `filter` : tab 容器(`class="layui-tab"`)的 `lay-filter` 属性值
- 参数 `options` : 添加 tab 时的属性选项,见下表: - 参数 `options` : 添加 tab 时的属性选项,见下表:
| options | 描述 | 类型 | 默认 | | options | 描述 | 类型 | 默认 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -225,7 +225,7 @@ layui.use(function(){
`element.tab(options);` `element.tab(options);`
- 参数 `options` : 属性选项,见下表: - 参数 `options` : 属性选项,见下表:
| options | 描述 | 类型 | | options | 描述 | 类型 |
| --- | --- | --- | | --- | --- | --- |

View File

@ -155,7 +155,7 @@ table.render({
| LAY_DISABLED | 当前行是否禁止选择 | 可读可写 | | LAY_DISABLED | 当前行是否禁止选择 | 可读可写 |
| LAY_INDEX | 当前行下标。每页重新从零开始计算 | 只读 | | LAY_INDEX | 当前行下标。每页重新从零开始计算 | 只读 |
| LAY_NUM | 当前行序号 | 只读 | | LAY_NUM | 当前行序号 | 只读 |
| LAY_COL | 当前列的表头属性配置项 | 只读 | | LAY_COL | 当前列的表头属性项 | 只读 |
示例一: 在返回的数据中设置特定字段: 示例一: 在返回的数据中设置特定字段:

View File

@ -527,7 +527,7 @@ initSort: {
``` ```
table.render({ table.render({
before: function(options){ before: function(options){
console.log(options); // 当前实例属性配置 console.log(options); // 当前实例属性
options.where.abc = 123; // 修改或额外追加 where 属性 options.where.abc = 123; // 修改或额外追加 where 属性
}, },
// … // 其它属性 // … // 其它属性

View File

@ -277,7 +277,7 @@ layui.use(['table', 'dropdown'], function(){
console.log(obj); console.log(obj);
if(event === 'email-tips'){ if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), { layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项' title: '当前列属性项'
}); });
} }
}); });

View File

@ -29,7 +29,7 @@ layui.use(['table', 'dropdown', 'util'], function(){
var dataCache = obj.dataCache; // 得到当前行缓存数据,包含特定字段 --- 2.8.8+ var dataCache = obj.dataCache; // 得到当前行缓存数据,包含特定字段 --- 2.8.8+
var index = obj.index; // 得到当前行索引 var index = obj.index; // 得到当前行索引
var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象 var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
var e = obj.e; // 当前的 jQuery 事件对象 --- 2.9.14+ var e = obj.e; // 当前的 jQuery 事件对象 --- 2.9.14+
console.log('rowContextmenu', obj); // 查看返回对象的所有成员 console.log('rowContextmenu', obj); // 查看返回对象的所有成员

View File

@ -42,7 +42,7 @@ toc: true
<h3 id="set" class="ws-anchor ws-bold">全局设置</h3> <h3 id="set" class="ws-anchor ws-bold">全局设置</h3>
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法主要用于初始化设置属性默认值。实际应用时,必须先设置该方法,再执行渲染、重载等操作。 该方法主要用于初始化设置属性默认值。实际应用时,必须先设置该方法,再执行渲染、重载等操作。
@ -74,7 +74,7 @@ table 提供了以下三种渲染模式,在实际使用时,一般按情况
`table.render(options);` `table.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法返回当前实例对象,包含可操作当前表格的一些成员方法。 该方法返回当前实例对象,包含可操作当前表格的一些成员方法。
@ -119,7 +119,7 @@ layui.use(function(){
</table> </table>
``` ```
> 2.8 之前版本通过 `lay-data="{}"` 定义属性配置项;<br> > 2.8 之前版本通过 `lay-data="{}"` 定义属性项;<br>
> 2.8+ 版本推荐采用 `lay-options`,但同时兼容 `lay-data` > 2.8+ 版本推荐采用 `lay-options`,但同时兼容 `lay-data`
@ -128,7 +128,7 @@ layui.use(function(){
`table.init(filter, options);` `table.init(filter, options);`
- 参数 `filter` : `<table>` 元素对应的 `lay-filter` 属性值 - 参数 `filter` : `<table>` 元素对应的 `lay-filter` 属性值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于将已输出在页面中的静态表格内容转换为动态 table 组件。[#参考相关示例](#demo-init) 该方法用于将已输出在页面中的静态表格内容转换为动态 table 组件。[#参考相关示例](#demo-init)
@ -220,7 +220,7 @@ table 的属性众多,我们大致分为以下几种:
`table.reload(id, options, deep);` `table.reload(id, options, deep);`
- 参数 `id` : table 渲染时的 `id` 属性值 - 参数 `id` : table 渲染时的 `id` 属性值
- 参数 `options` : 为基础属性配置 - 参数 `options` : 为基础属性
- 参数 `deep` <sup>2.6+</sup> : 是否采用深度重载(即重载时始终携带初始时及上一次重载时的参数),默认 false。<div style="margin-top:5px;"><button type="button" class="layui-btn layui-btn-sm layui-btn-primary" lay-layer="{content: '#DOCS-table-reload-comp'}">2.6 之前版本的 <code>table.reload()</code> 方法兼容性说明</button></div> - 参数 `deep` <sup>2.6+</sup> : 是否采用深度重载(即重载时始终携带初始时及上一次重载时的参数),默认 false。<div style="margin-top:5px;"><button type="button" class="layui-btn layui-btn-sm layui-btn-primary" lay-layer="{content: '#DOCS-table-reload-comp'}">2.6 之前版本的 <code>table.reload()</code> 方法兼容性说明</button></div>
<div style="display: none;" id="DOCS-table-reload-comp"> <div style="display: none;" id="DOCS-table-reload-comp">
@ -482,7 +482,7 @@ table.resize('test');
`table.exportFile(id, data, opts);` `table.exportFile(id, data, opts);`
- 参数 `id` : table 渲染时的 `id` **或** 要导出的数据表头(当 `id``array` 类型时)。 - 参数 `id` : table 渲染时的 `id` **或** 要导出的数据表头(当 `id``array` 类型时)。
- 参数 `data` : 要导出的自定义数据,参数可选。 - 参数 `data` : 要导出的自定义数据,参数可选。
- 参数 `opts` <sup>2.7+</sup>: 导出数据时的属性选项,支持: `type,title` - 参数 `opts` <sup>2.7+</sup>: 导出数据时的属性选项,支持: `type,title`
该方法用于外部导出对应 table 的数据和任意自定义数据。 该方法用于外部导出对应 table 的数据和任意自定义数据。
@ -513,7 +513,7 @@ table.exportFile(['名字','性别','年龄'], [
`table.getOptions(id);` `table.getOptions(id);`
- 参数 `id` : table 渲染时的 `id` 属性值 - 参数 `id` : table 渲染时的 `id` 属性值
该方法用于外部获取对应 table 实例的属性配置项。 该方法用于外部获取对应 table 实例的属性项。
``` ```
// 渲染 // 渲染
@ -622,7 +622,7 @@ layui.use(function(){
// 头部工具栏事件 // 头部工具栏事件
table.on('toolbar(test)', function(obj){ table.on('toolbar(test)', function(obj){
var options = obj.config; // 获取当前表格属性配置 var options = obj.config; // 获取当前表格属性
var checkStatus = table.checkStatus(options.id); // 获取选中行相关数据 var checkStatus = table.checkStatus(options.id); // 获取选中行相关数据
console.log(obj); // 查看对象所有成员 console.log(obj); // 查看对象所有成员
@ -697,8 +697,8 @@ table.render({
// 表头自定义元素工具事件 // 表头自定义元素工具事件
table.on('colTool(test)', function(obj){ table.on('colTool(test)', function(obj){
var col = obj.col; // 获取当前列属性配置 var col = obj.col; // 获取当前列属性
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
var layEvent = obj.event; // 获得自定义元素对应的 lay-event 属性值 var layEvent = obj.event; // 获得自定义元素对应的 lay-event 属性值
console.log(obj); // 查看对象所有成员 console.log(obj); // 查看对象所有成员
}); });
@ -721,8 +721,8 @@ table.render({
// 列拖拽宽度后的事件 // 列拖拽宽度后的事件
table.on('colResized(test)', function(obj){ table.on('colResized(test)', function(obj){
var col = obj.col; // 获取当前列属性配置 var col = obj.col; // 获取当前列属性
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
console.log(obj); // 查看对象所有成员 console.log(obj); // 查看对象所有成员
}); });
``` ```
@ -744,8 +744,8 @@ table.render({
// 列筛选(显示或隐藏)后的事件 // 列筛选(显示或隐藏)后的事件
table.on('colToggled(test)', function(obj){ table.on('colToggled(test)', function(obj){
var col = obj.col; // 获取当前列属性配置 var col = obj.col; // 获取当前列属性
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
console.log(obj); // 查看对象所有成员 console.log(obj); // 查看对象所有成员
}); });
``` ```
@ -774,7 +774,7 @@ table.on('row(test)', function(obj) {
var dataCache = obj.dataCache; // 得到当前行缓存数据,包含特定字段 --- 2.8.8+ var dataCache = obj.dataCache; // 得到当前行缓存数据,包含特定字段 --- 2.8.8+
var index = obj.index; // 得到当前行索引 var index = obj.index; // 得到当前行索引
var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象 var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
var e = obj.e; // 当前的 jQuery 事件对象 --- 2.9.14+ var e = obj.e; // 当前的 jQuery 事件对象 --- 2.9.14+
console.log('onrow', obj); // 查看返回对象的所有成员 console.log('onrow', obj); // 查看返回对象的所有成员
@ -887,7 +887,7 @@ layui.use(function(){
var index = obj.index; // 得到当前行索引 var index = obj.index; // 得到当前行索引
var layEvent = obj.event; // 获得元素对应的 lay-event 属性值 var layEvent = obj.event; // 获得元素对应的 lay-event 属性值
var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象 var tr = obj.tr; // 得到当前行 <tr> 元素的 jQuery 对象
var options = obj.config; // 获取当前表格基础属性配置 var options = obj.config; // 获取当前表格基础属性
var col = obj.getCol(); // 得到当前列的表头配置属性 -- v2.8.3 新增 var col = obj.getCol(); // 得到当前列的表头配置属性 -- v2.8.3 新增
console.log(obj); // 查看对象所有成员 console.log(obj); // 查看对象所有成员

View File

@ -30,7 +30,9 @@
<h3 id="demo-trigger" lay-toc="{level: 2}">自定义事件</h3> <h3 id="demo-trigger" lay-toc="{level: 2}">自定义事件</h3>
<pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full']}"> <pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full'], done: function(obj){
obj.render();
}}">
<textarea> <textarea>
{{- d.include("/tabs/examples/trigger.md") }} {{- d.include("/tabs/examples/trigger.md") }}
</textarea> </textarea>
@ -40,7 +42,9 @@
切换 tabs 标签项后,地址栏同步 `hash`当页面刷新时tabs 仍保持对应的切换状态。 切换 tabs 标签项后,地址栏同步 `hash`当页面刷新时tabs 仍保持对应的切换状态。
<pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full']}"> <pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full'], done: function(obj){
obj.render();
}}">
<textarea> <textarea>
{{- d.include("/tabs/examples/hash.md") }} {{- d.include("/tabs/examples/hash.md") }}
</textarea> </textarea>
@ -48,7 +52,9 @@
<h3 id="demo-nest" lay-toc="{level: 2}">标签嵌套</h3> <h3 id="demo-nest" lay-toc="{level: 2}">标签嵌套</h3>
<pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full']}"> <pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], codeStyle: 'max-height: 520px;', tools: ['full'], done: function(obj){
obj.render();
}}">
<textarea> <textarea>
{{- d.include("/tabs/examples/nest.md") }} {{- d.include("/tabs/examples/nest.md") }}
</textarea> </textarea>

View File

@ -35,7 +35,7 @@ toc: true
`tabs.render(options)` `tabs.render(options)`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
组件支持以下三种渲染方式: 组件支持以下三种渲染方式:

View File

@ -27,7 +27,7 @@ toc: true
`transfer.render(options);` `transfer.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3> <h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3>
@ -46,7 +46,7 @@ toc: true
`transfer.reload(id, options);` `transfer.reload(id, options);`
- 参数 `id` : 对应渲染时定义的 `id` 属性值 - 参数 `id` : 对应渲染时定义的 `id` 属性值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
``` ```
var transfer = layui.transfer; var transfer = layui.transfer;
@ -89,7 +89,7 @@ var getData = transfer.getData('test');
`transfer.set(options);` `transfer.set(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法对所有的 `transfer` 实例有效,设置的属性优先级低于 `transfer.render(options)` 该方法对所有的 `transfer` 实例有效,设置的属性优先级低于 `transfer.render(options)`

View File

@ -27,7 +27,7 @@ toc: true
`tree.render(options);` `tree.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3> <h3 id="options" lay-toc="{level: 2, hot: true}">属性</h3>
@ -109,7 +109,7 @@ tree.setChecked('test', [1, 3]); // 批量勾选 id 为 1,3 的节点
`tree.reload(id, idArr);` `tree.reload(id, idArr);`
- 参数 `id` : 对应 tree 渲染时定义的 id 属性值 - 参数 `id` : 对应 tree 渲染时定义的 id 属性值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
``` ```
var tree = layui.tree; var tree = layui.tree;

View File

@ -124,7 +124,7 @@ treeTable.render({
| 属性 | 描述 | 类型 | 默认值 | | 属性 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| enable | 是否开启异步加载模式。只有开启时 `async` 的其他属性配置才有效。 **注意:** 异步加载子节点不应跟 `simpleData` 同时开启,可以是 `url+simpleData` 的方式,获取完整的简单数据进行转换。若开启异步加载模式,即表示按需异步加载子节点。 | boolean | `false` | | enable | 是否开启异步加载模式。只有开启时 `async` 的其他属性选项才有效。 **注意:** 异步加载子节点不应跟 `simpleData` 同时开启,可以是 `url+simpleData` 的方式,获取完整的简单数据进行转换。若开启异步加载模式,即表示按需异步加载子节点。 | boolean | `false` |
| url | 异步加载的接口,可以根据需要设置与顶层接口不同的接口,若相同可不设置该属性 | string | - | | url | 异步加载的接口,可以根据需要设置与顶层接口不同的接口,若相同可不设置该属性 | string | - |
| [format](#options.tree.async.format) | 用于处理异步子节点数据的回调函数,该属性优先级高于 `async.url` 属性。用法详见下文。 | function | - | | [format](#options.tree.async.format) | 用于处理异步子节点数据的回调函数,该属性优先级高于 `async.url` 属性。用法详见下文。 | function | - |
| type | 请求的接口类型,设置可缺省同上 | string | - | | type | 请求的接口类型,设置可缺省同上 | string | - |
@ -146,7 +146,7 @@ treeTable.render({
enable: true, enable: true,
async: { async: {
format: function(trData, options, callback){ format: function(trData, options, callback){
// trData 为行数据、options 为 treeTable 属性配置 // trData 为行数据、options 为 treeTable 属性
// callbacck 为子节点的渲染函数 // callbacck 为子节点的渲染函数
// 可利用该函数对子节点数据进行异步请求或其他格式化处理 // 可利用该函数对子节点数据进行异步请求或其他格式化处理
var nodeList = [ var nodeList = [

View File

@ -47,7 +47,7 @@ toc: true
`treeTable.render(options);` `treeTable.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该组件渲染的使用方式与 `table` 组件完全相同。 该组件渲染的使用方式与 `table` 组件完全相同。
@ -69,7 +69,7 @@ toc: true
| 仅数据重载 | treeTable.reloadData(id, options) | | 仅数据重载 | treeTable.reloadData(id, options) |
- 参数 `id` : treeTable 渲染时的 id 属性值 - 参数 `id` : treeTable 渲染时的 id 属性值
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
使用方式与 `table` 组件完全相同,具体用法可参考:[table 重载](../table/#reload) 使用方式与 `table` 组件完全相同,具体用法可参考:[table 重载](../table/#reload)
@ -144,7 +144,7 @@ console.log(obj);
- 参数 `id` : treeTable 渲染时的 `id` 属性值 - 参数 `id` : treeTable 渲染时的 `id` 属性值
- 参数 `filter` : 过滤函数 - 参数 `filter` : 过滤函数
- 参数 `opts` : 该方法的属性选项,详见下表: - 参数 `opts` : 该方法的属性选项,详见下表:
| 属性名 | 描述 | 类型 | 默认值 | | 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |

View File

@ -27,13 +27,13 @@ toc: true
| [var inst = upload.render(options)](#render) | upload 组件渲染,核心方法。 | | [var inst = upload.render(options)](#render) | upload 组件渲染,核心方法。 |
| [inst.upload()](#upload) | 对当前实例提交上传 | | [inst.upload()](#upload) | 对当前实例提交上传 |
| [inst.reload(options)](#reload) | 对当前实例进行重载 | | [inst.reload(options)](#reload) | 对当前实例进行重载 |
| inst.config | 获得当前实例的属性配置项 | | inst.config | 获得当前实例的属性项 |
<h3 id="render" lay-toc="{level: 2, hot: true}">渲染</h3> <h3 id="render" lay-toc="{level: 2, hot: true}">渲染</h3>
`upload.render(options);` `upload.render(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
<br>注 : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。 <br>注 : 除 `elem` 属性外,其他基础属性也可以直接写在元素的 `lay-options="{}"` 属性中。
``` ```
@ -94,7 +94,7 @@ var inst = upload.render({
`inst.reload(options);` `inst.reload(options);`
- 参数 `options` : 基础属性配置项。[#详见属性](#options) - 参数 `options` : 基础属性项。[#详见属性](#options)
该方法用于对当前的上传实例进行完整重载,所有属性均可参与到重载中。 该方法用于对当前的上传实例进行完整重载,所有属性均可参与到重载中。

View File

@ -34,7 +34,7 @@ toc: true
`util.countdown(options);` `util.countdown(options);`
- 参数 `options` <sup>2.8.9+</sup>: 属性配置项。可选项详见下表: - 参数 `options` <sup>2.8.9+</sup>: 属性项。可选项详见下表:
| 属性 | 描述 | | 属性 | 描述 |
| --- | --- | | --- | --- |
@ -101,7 +101,7 @@ var result = util.timeAgo(1672531200000); // 2023-01-01 00:00:00
- 参数 `time` : 毫秒数或日期对象 - 参数 `time` : 毫秒数或日期对象
- 参数 `format` : 日期字符格式。默认格式:`yyyy-MM-dd HH:mm:ss` 。可自定义,如: `yyyy年MM月dd日` - 参数 `format` : 日期字符格式。默认格式:`yyyy-MM-dd HH:mm:ss` 。可自定义,如: `yyyy年MM月dd日`
- 参数 `options` <sup>2.8.13+</sup> : 该方法的属性选项,详见下表: - 参数 `options` <sup>2.8.13+</sup> : 该方法的属性选项,详见下表:
| 属性名 | 描述 | 类型 | 默认值 | | 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -179,7 +179,7 @@ var str2 = util.unescape('&lt;div&gt;123&lt;/div&gt;'); // 返回: <div>123</d
`util.openWin(options);` `util.openWin(options);`
- 参数 `options` : 属性配置项。可选项详见下表 - 参数 `options` : 属性项。可选项详见下表
| 属性 | 描述 | | 属性 | 描述 |
| --- | --- | | --- | --- |

View File

@ -5,10 +5,26 @@ toc: true
# 更新日志 # 更新日志
> 导读:📑 [Layui 2.8+ 《升级指南》](/notes/2.8/upgrade-guide.html) · 📑 [Layui 新版文档站上线初衷](/notes/2.8/news.html) > 导读:📑 [Layui 2.x 系列版本主要升级变化](/notes/share/2x-major-upgrade-changes.html) · 📑 [Layui 2.8+ 《升级指南》](/notes/2.8/upgrade-guide.html) · 📑 [Layui 新版文档站上线初衷](/notes/2.8/news.html)
<h2 id="2.10+" lay-toc="{title: '2.10+'}"></h2> <h2 id="2.10+" lay-toc="{title: '2.10+'}"></h2>
<h2 id="v2.10.1" class="ws-anchor">
v2.10.1
<span class="layui-badge-rim">2025-03-19</span>
</h2>
- #### component
- 修复 `reload` 时传入的选项未正确合并的问题 #2566 @sentsim
- 优化 `lay-options` 属性上的配置在重载时的优先级 #2566 @sentsim
- #### 其他
- 优化 tabs `reload` 未按照 `closable` 正确渲染可关闭状态的问题 #2566 @sentsim
- 优化 form 的 `checkbox` 标签风格选中且禁用时的显示 #2563 @bxjt123
- 修复 body 初始 `line-height` 无效的问题 #2569 @sentsim
### 下载: [layui-v2.10.1.zip](https://gitee.com/layui/layui/attach_files/2100525/download)
---
<h2 id="v2.10.0" class="ws-anchor"> <h2 id="v2.10.0" class="ws-anchor">
v2.10.0 v2.10.0
@ -49,6 +65,10 @@ toc: true
--- ---
<h2 id="2.9.x" lay-toc="{title: '2.9.x'}"></h2>
<h2 id="v2.9.25" class="ws-anchor"> <h2 id="v2.9.25" class="ws-anchor">
v2.9.25 v2.9.25
<span class="layui-badge-rim">2025-03-13</span> <span class="layui-badge-rim">2025-03-13</span>
@ -62,8 +82,6 @@ toc: true
--- ---
<h2 id="2.9.x" lay-toc="{title: '2.9.x'}"></h2>
<h2 id="v2.9.24" class="ws-anchor"> <h2 id="v2.9.24" class="ws-anchor">
v2.9.24 v2.9.24
<span class="layui-badge-rim">2025-03-07</span> <span class="layui-badge-rim">2025-03-07</span>

View File

@ -1,74 +1,45 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>视图模板引擎 - layui</title> <title>模板引擎 - Layui</title>
<link rel="stylesheet" href="../src/css/layui.css">
<link rel="stylesheet" href="../src/css/layui.css"> <style>
<style> .laytpl-demo{border: 1px solid #eee;}
.laytpl-demo{border: 1px solid #EBEBEB;} .laytpl-demo:first-child{border-right: none;}
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;} .laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #EBEBEB; background-color: #F8F9FA;} .laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;}
</style> .laytpl-demo .layui-tabs{top: -1px;}
</head>
<body>
<div>
#ID-tpl-view-body {
height: calc(100vh - 430px); overflow: auto; clear: both;
}
#ID-tpl-view-body > div {
display: none;
}
.laytpl-demo pre {
padding: 16px; background-color: #20222A; color: #F8F9FA; font-family: 'Courier New',Consolas, monospace;
}
</style>
</head>
<body>
<div class="layui-padding-3">
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs6 laytpl-demo"> <div class="layui-col-xs6 laytpl-demo">
<div>模板</div> <div>
<textarea id="demoTPL1"><h1>{{ d.title }}</h1> <a href="javascript:;" id="ID-tpl-src-title">
<cite><strong>模板</strong></cite>
<p>转义输出(HTML){{ d.desc }}</p> <i class="layui-icon layui-icon-down layui-font-12"></i>
<p>转义输出(HTML){{= d.desc }}</p> </a>
<p>原始输出(HTML){{- d.desc }}</p> </div>
{{#}} <textarea id="ID-tpl-src"></textarea>
<div class="layui-section">
<hr>
<ul>
{{# var str = "a b c";
layui.each(d.items, function(index, item){ }}
<li class="{{ index > 0 ? 'list' : '' }}">
<strong>{{ item.title }}</strong>
{{# if(item.content){ }}
<span>{{ item.content }}</span>
{{# } }}
<span>{{ item.time || '' }}</span>
{{ str }}
</li>
{{# }); if(d.items.length === 0){ }}
无数据
{{# } }}
</ul>
<hr>
</div>
<div>
{{ d.content || '' }}
{{ }} {{}}
{{ }}
\a
'12'"""""
"哈''哈"
</div>
<p>渲染时间:{{ layui.util.toDateString(new Date()) }}</p></textarea>
</div> </div>
<div class="layui-col-xs6 laytpl-demo"> <div class="layui-col-xs6 laytpl-demo">
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs3">数据</div> <div><strong>数据</strong></div>
<div class="layui-col-xs9" style="text-align: right">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-on="createData">生成数据</button>
</div> </div>
</div> <textarea id="ID-tpl-data">
<textarea id="demoData1">
{ {
"title": "标题", "title": "标题",
"desc": "<a href=\"\" style=\"color:blue;\">一段描述</a>", "desc": "<a href=\"\" style=\"color:blue;\">一段描述</a>",
@ -92,123 +63,250 @@
] ]
}</textarea> }</textarea>
</div> </div>
<div class="layui-col-xs12 laytpl-demo"> <div class="layui-col-xs12 laytpl-demo" style="border-top: none;">
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs4">视图</div> <div class="layui-col-xs4 layui-tabs" id="ID-tpl-view-header">
<ul class="layui-tabs-header">
<li><strong>渲染结果</strong></li>
<li><strong>源码</strong></li>
</ul>
</div>
<div class="layui-col-xs4" style="text-align: center"> <div class="layui-col-xs4" style="text-align: center">
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-on="test1">性能测试</button> <button class="layui-btn layui-btn-sm layui-btn-border" lay-on="test">性能测试</button>
</div> </div>
<div class="layui-col-xs4" style="text-align: right"> <div class="layui-col-xs4" style="text-align: right">
<span id="demoViewTime"></span> <span class="layui-badge" id="ID-tpl-view-time"></span>
</div> </div>
</div> </div>
<div class="layui-padding-sm" id="demoView1"></div> <div id="ID-tpl-view-body">
<div class="layui-show layui-padding-3 layui-text" id="ID-tpl-view"></div>
<div><pre id="ID-tpl-view-code"></pre></div>
</div>
</div> </div>
</div> </div>
<script type="text/html" id="ID-tpl-template-modern">
<h2>
{{= d.title }} - {{= d.title ? '#' : '' }}
{{ if(true){ }}AAAA{{='A'}}{{ } }}
</h2>
{{- include('ID-tpl-template-common', {title: '头部'}) }}
<script type="type/html" template id="demoTplCommon"> <hr>
公共模板 - {{ d.title }}
</script>
<script type="type/html" template id="demoTplList"> <p>转义输出:{{= d.desc }}</p>
{{# if(d.items && d.items.length > 0){ }} <p>原文输出:{{- d.desc }}</p>
{{# 这是一段注释。仅在模板中显示,不在视图中输出 }}
{{!
这是一段不进行模板解析的区域,可显示原始标签:
{{ let a = 0; }}、{{= escape }}、{{- source }}、{{# comments }}、&#123;&#123;! ignore !&#125;&#125;
!}}
{{# 空主体测试 }}
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
<div>
{{- !0 ? '<br>循环输出:' : '' }}
<hr>
<ul> <ul>
{{# layui.each(d.items, function(index, item){ }} {{
<li><strong>{{ item.title }}</strong>{{ laytpl.include('demoTplList', {items: item.child}) }}</li> var str = "一级列表 a \\ b c";
{{# }); }} d.items.forEach(function(value, index) {
}}
<li class="{{= index > 0 ? 'list' : '' }}">
<strong>{{= value.title }}</strong>
{{ if(value.content){ }}
<span>{{= value.content }}</span>
{{ } }}
<span>{{= value.time || '' }}</span>
{{= str }}
{{- include('ID-tpl-template-list', { items: value.child }) }}
</li>
{{ }); }}
{{ if(d.items.length === 0){ }}
无数据
{{ } }}
</ul> </ul>
{{# } }} <hr>
</script>
<script type="type/html" template id="laytplTestTpl">
{{# for(var i = 0; i < d.items.length; i++){ }}
第{{= d.items[i].index }}个Name: {{- d.items[i].name }} Number: {{= d.items[i].number }}
{{# } }}
</script>
</div> </div>
<script src="../src/layui.js"></script> <div>
<script> {{= d.content || '' }}
layui.use(['laytpl', 'util'], function(){ \反斜杠 | '单引号' "双引号" ""''"" | "左双右单' | '左单右双"
</div>
<p>渲染时间:{{= layui.util.toDateString(new Date()) }}</p>
</script>
<script type="text/html" id="ID-tpl-template-common">
公共模板 - {{= d.title }}
</script>
<script type="text/html" id="ID-tpl-template-list">
{{ if(d.items && d.items.length > 0){ }}
<ul>
{{ layui.each(d.items, function(index, item){ }}
<li>
<strong>{{= item.title }}</strong>
{{- include('ID-tpl-template-list', {items: item.child}) }}
</li>
{{ }); }}
</ul>
{{ } }}
</script>
<script type="text/html" id="ID-tpl-template-test">
{{ for (var i = 0; i < d.items.length; i++) { }}
第 {{= d.items[i].index }} 个Name: {{- d.items[i].name }} Number: {{= d.items[i].number }}
{{ } }}
</script>
<!-- 旧版本模板 -->
<script type="text/html" id="ID-tpl-template-legacy">
<h2>
{{= d.title }} - {{= d.title ? '#' : '' }}
{{# if(true){ }}AAAA{{='A'}}{{# } }}
</h2>
<hr>
<p>转义输出:{{ d.desc }}</p>
<p>转义输出:{{= d.desc }}</p>
<p>原文输出:{{- d.desc }}</p>
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
<div>
<ul>
{{# var str = "a b c"; }}
{{# layui.each(d.items, function(index, item) { }}
<li class="{{= index > 0 ? 'list' : '' }}">
<strong>{{= item.title }}</strong>
{{# if(item.content){ }}
<span>{{= item.content }}</span>
{{# } }}
<span>{{= item.time || '' }}</span>
{{ str }}
</li>
{{# }); }}
{{# if (d.items.length === 0) { }}
无数据
{{# } }}
</ul>
<hr>
</div>
<div>
{{= d.content || '' }}
\反斜杠 | '单引号' "双引号" ""''"" | "左双右单' | '左单右双"
</div>
<p>渲染时间:{{ layui.util.toDateString(new Date()) }}</p>
</script>
</div>
<script src="../src/layui.js"></script>
<script>
layui.use(['laytpl', 'util', 'tabs', 'dropdown'], function() {
var laytpl = layui.laytpl; var laytpl = layui.laytpl;
var util = layui.util; var util = layui.util;
var tabs = layui.tabs;
var dropdown = layui.dropdown;
var $ = layui.$; var $ = layui.$;
// 默认设置
laytpl.config({
tagStyle: 'modern' // 初始采用新版标签风格
})
// 获取模板和数据 // 获取模板和数据
var get = function(type){ var getData = function(type) {
return { return {
template: $('#demoTPL1').val(), // 获取模板 template: $('#ID-tpl-src').val(), // 获取模板
data: function(){ // 获取数据 data: function(){ // 获取数据
try { try {
return JSON.parse($('#demoData1').val()); return JSON.parse($('#ID-tpl-data').val());
} catch(e){ } catch(e) {
$('#demoView1').html(e); $('#ID-tpl-view').html(e);
} }
}() }()
}; };
}; };
var data = get(); // 视图渲染
var renderView = function(html, startTime) {
// 耗时计算 timer(startTime);
var startTime = new Date().getTime(), timer = function(startTime, title){ $('#ID-tpl-view').html(html);
var endTime = new Date().getTime(); $('#ID-tpl-view-code').html(util.escape(html));
$('#demoViewTime').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
}; };
// 全局设置 // 生成模板
/*laytpl.config({ var createTemplate = function(opts) {
open: '<%', opts = $.extend({
close: '%>' tagStyle: 'modern'
});*/ }, opts);
// 渲染模板 // 初始化模板
var thisTpl = laytpl(data.template); var elem = $('#ID-tpl-template-'+ opts.tagStyle);
$('#ID-tpl-src').val(elem.html());
// 执行渲染 return opts;
thisTpl.render(data.data, function(view){ };
timer(startTime); var tplConfig = createTemplate();
$('#demoView1').html(view); var data = getData();
// 耗时计算
var timer = function(startTime, title) {
var endTime = new Date();
$('#ID-tpl-view-time').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
};
var startTime = new Date();
// 创建一个模板实例
var templateInst = laytpl(data.template, {
condense: false, // 不处理连续空白符,即保留模板原始结构
tagStyle: tplConfig.tagStyle
});
// 初始渲染
templateInst.render(data.data, function(html) {
renderView(html, startTime);
}); });
// 编辑 // 编辑
$('.laytpl-demo textarea').on('input', function(){ $('.laytpl-demo textarea').on('input', function() {
var data = get(); var data = getData();
if(!data.data) return; var startTime = new Date();
// 计算模板渲染耗时 // 若模板有变化,则重新编译模板
var startTime = new Date().getTime(); if (this.id === 'ID-tpl-src') {
templateInst.compile(data.template);
// 若模板有变化,则重新解析模板;若模板没变,数据有变化,则从模板缓存中直接渲染(效率大增)
if(this.id === 'demoTPL1'){
thisTpl.parse(data.template, data.data); // 解析模板
} }
// 执行渲染 // 若模板没变,数据有变化,则从模板缓存中直接渲染数据(效率大增)
thisTpl.render(data.data, function(view){ templateInst.render(data.data, function(html) {
timer(startTime); renderView(html, startTime);
$('#demoView1').html(view);
}); });
}); });
// 事件 // 事件
util.on({ util.on({
// 性能测试 // 性能测试
test1: function(){ test: function() {
var dataLen = 1000 // 数据量 var dataLen = 1000 // 数据量
var renderTimes = 1000; // 渲染次数 var renderTimes = 1000; // 渲染次数
// 初始化数据 // 初始化数据
var data = { var data = {
title: '性能测试', title: '性能测试',
items: function(items){ items: function(items) {
for(var i = 0; i < dataLen; i++){ for (var i = 0; i < dataLen; i++) {
items.push({ items.push({
index: i index: i,
,name: '<strong style="color: red;">张三</strong>' name: '<strong style="color: red;">张三</strong>',
,number: 100+i number: 100+i
}); });
} }
return items; return items;
@ -217,30 +315,63 @@ layui.use(['laytpl', 'util'], function(){
// 模板 // 模板
var startTime = new Date(); var startTime = new Date();
for(var j = 0; j < renderTimes; j++){ for (var j = 0; j < renderTimes; j++) {
var template = document.getElementById('laytplTestTpl').innerHTML; var template = document.getElementById('ID-tpl-template-test').innerHTML;
var html = laytpl(template).render(data); var html = laytpl(template).render(data);
} }
timer(startTime, '本次测试耗时:'); renderView(html, startTime);
$('#demoView1').html(html);
} }
}); });
// 自定义标签符 // 局部自定义标签符
laytpl(` laytpl(`
<%# var job = ["前端工程师"]; %> <% var job = ["前端工程师"]; %>
<%= d.name %>是一名<%= job[d.type] %>。 <%= d.name %>是一名<%= job[d.index] %>。
`, { `, {
open: '<%', open: '<%',
close: '%>' close: '%>'
}).render({ }).render({
name: '张三', name: '张三',
type: 0 index: 0
}, function(str){ }, function(str) {
console.log(str); // 张三是一名前端工程师。 console.log(str); // 张三是一名前端工程师。
}); });
});
</script> // 视图结果 tabs
</body> tabs.render({
elem: '#ID-tpl-view-header',
body: ['#ID-tpl-view-body', '>div']
});
// 切换模板
dropdown.render({
elem: '#ID-tpl-src-title',
data: [{
title: '新版本模板',
tagStyle: 'modern'
}, {
title: '旧版本模板',
tagStyle: 'legacy'
}],
click: function(obj){
createTemplate({
tagStyle: obj.tagStyle
});
this.elem.children('cite').html(obj.title);
// 同步设置标签风格
templateInst.config.tagStyle = obj.tagStyle;
var data = getData();
var startTime = new Date();
// 重新渲染
templateInst.compile(data.template).render(data.data, function(html) {
renderView(html, startTime);
});
}
})
});
</script>
</body>
</html> </html>

View File

@ -1,6 +1,6 @@
{ {
"name": "layui", "name": "layui",
"version": "2.10.0", "version": "2.10.1",
"description": "Classic modular Front-End UI library", "description": "Classic modular Front-End UI library",
"keywords": [ "keywords": [
"layui", "layui",

View File

@ -18,7 +18,7 @@ input,button,textarea,select,optgroup,option{font-family: inherit; font-size: in
pre{white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;} pre{white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}
/** 初始化全局标签 **/ /** 初始化全局标签 **/
body{line-height: 1.6; color: #333; color: rgba(0,0,0,.85); font: 14px Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;} body{line-height: 1.6; color: rgba(0,0,0,.85); font-size: 14px; font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
hr{height: 0; line-height: 0; margin: 10px 0; padding: 0; border: none; border-bottom: 1px solid #eee; clear: both; overflow: hidden; background: none;} hr{height: 0; line-height: 0; margin: 10px 0; padding: 0; border: none; border-bottom: 1px solid #eee; clear: both; overflow: hidden; background: none;}
a{color: #333; text-decoration: none;} a{color: #333; text-decoration: none;}
a cite{font-style: normal;} a cite{font-style: normal;}
@ -930,6 +930,8 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-checkbox-disabled > div{color: #c2c2c2!important;} .layui-checkbox-disabled > div{color: #c2c2c2!important;}
.layui-checkbox-disabled > i{border-color: #eee !important;} .layui-checkbox-disabled > i{border-color: #eee !important;}
.layui-checkbox-disabled:hover > i{color: #fff !important;} .layui-checkbox-disabled:hover > i{color: #fff !important;}
.layui-form-checkbox[lay-skin="tag"].layui-form-checked.layui-checkbox-disabled > i{color:#c2c2c2;}
.layui-form-checkbox[lay-skin="tag"].layui-form-checked.layui-checkbox-disabled:hover > i{color: #c2c2c2!important;}
/* 单选框 */ /* 单选框 */
.layui-form-radio{display: inline-block; vertical-align: middle; line-height: 28px; margin: 6px 10px 0 0; padding-right: 10px; cursor: pointer; font-size: 0;} .layui-form-radio{display: inline-block; vertical-align: middle; line-height: 28px; margin: 6px 10px 0 0; padding-right: 10px; cursor: pointer; font-size: 0;}
@ -1317,6 +1319,7 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh
.layui-tabs-header li .layui-tabs-close{position: relative; display: inline-block; width: 16px; height: 16px; line-height: 18px; margin-left: 8px; top: 0px; text-align: center; font-size: 12px; color: #959595; border-radius: 50%; font-weight: 700; transition: all .16s; -webkit-transition: all .16s;} .layui-tabs-header li .layui-tabs-close{position: relative; display: inline-block; width: 16px; height: 16px; line-height: 18px; margin-left: 8px; top: 0px; text-align: center; font-size: 12px; color: #959595; border-radius: 50%; font-weight: 700; transition: all .16s; -webkit-transition: all .16s;}
.layui-tabs-header li .layui-tabs-close:hover{ background-color: #ff5722; color: #fff;} .layui-tabs-header li .layui-tabs-close:hover{ background-color: #ff5722; color: #fff;}
.layui-tabs-header li[lay-closable="false"] .layui-tabs-close{display: none;}
.layui-tabs-body{padding: 16px 0;} .layui-tabs-body{padding: 16px 0;}
.layui-tabs-item{display: none;} .layui-tabs-item{display: none;}

View File

@ -20,7 +20,6 @@ layui.define(['jquery', 'lay'], function(exports) {
// 组件名 // 组件名
var MOD_NAME = settings.name; var MOD_NAME = settings.name;
var MOD_INDEX = 'layui_'+ MOD_NAME +'_index'; // 组件索引名
var MOD_ID = 'lay-' + MOD_NAME + '-id'; // 用于记录组件实例 id 的属性名 var MOD_ID = 'lay-' + MOD_NAME + '-id'; // 用于记录组件实例 id 的属性名
// 组件基础对外接口 // 组件基础对外接口
@ -31,7 +30,7 @@ layui.define(['jquery', 'lay'], function(exports) {
// 通用常量集,一般存放固定字符,如类名等 // 通用常量集,一般存放固定字符,如类名等
CONST: $.extend(true, { CONST: $.extend(true, {
MOD_NAME: MOD_NAME, MOD_NAME: MOD_NAME,
MOD_INDEX: MOD_INDEX, MOD_ID: MOD_ID,
CLASS_THIS: 'layui-this', CLASS_THIS: 'layui-this',
CLASS_SHOW: 'layui-show', CLASS_SHOW: 'layui-show',
@ -103,7 +102,7 @@ layui.define(['jquery', 'lay'], function(exports) {
// 重载实例 // 重载实例
Class.prototype.reload = function(options, type) { Class.prototype.reload = function(options, type) {
var that = this; var that = this;
$.extend(settings.isDeepReload, that.config, options); that.config = $.extend(settings.isDeepReload, {}, that.config, options);
that.init(true, type); that.init(true, type);
}; };
@ -124,7 +123,13 @@ layui.define(['jquery', 'lay'], function(exports) {
} }
// 合并 lay-options 属性上的配置信息 // 合并 lay-options 属性上的配置信息
$.extend(true, options, lay.options(elem[0])); var layOptions = lay.options(elem[0]);
if (rerender) {
// 若重载渲染,则重载传入的 options 配置优先
options = that.config = $.extend(layOptions, options);
} else {
$.extend(options, layOptions); // 若首次渲染,则 lay-options 配置优先
}
// 若重复执行 render则视为 reload 处理 // 若重复执行 render则视为 reload 处理
if (!rerender && elem.attr(MOD_ID)) { if (!rerender && elem.attr(MOD_ID)) {
@ -173,21 +178,39 @@ layui.define(['jquery', 'lay'], function(exports) {
Class.prototype.render = settings.render; // 渲染 Class.prototype.render = settings.render; // 渲染
Class.prototype.events = settings.events; // 事件 Class.prototype.events = settings.events; // 事件
// 元素操作缓存 /**
Class.prototype.cache = function(key, value) { * 元素缓存操作
* @param {string} key - 缓存键
* @param {*} value - 缓存值
* @param {boolean} remove - 是否删除缓存
* @returns {*} - value 未传则返回缓存值
*/
Class.prototype.cache = function(key, value, remove) {
var that = this; var that = this;
var options = that.config; var options = that.config;
var elem = options.elem; var elem = options.elem;
var MOD_CACHE_NAME = MOD_ID + '-cache';
if (!elem) return; if (!elem) return;
var CACHE_NAME = 'lay_'+ MOD_NAME + '_cache'; var cache = elem.data(MOD_CACHE_NAME) || {};
var cache = elem.data(CACHE_NAME) || {};
if (value === undefined) return cache[key]; // value 未传则获取缓存值
if (value === undefined) {
return cache[key];
}
cache[key] = value; if (remove) {
elem.data(CACHE_NAME, cache); delete cache[key]; // 删除缓存
} else {
cache[key] = value; // 设置缓存
}
elem.data(MOD_CACHE_NAME, cache);
};
// 清除缓存
Class.prototype.removeCache = function(key) {
this.cache(key, null, true);
}; };
// 缓存所有实例对象 // 缓存所有实例对象

View File

@ -1,162 +1,475 @@
/** /**
* laytpl 轻量模板引擎 * laytpl
* 轻量级通用模板引擎
*/ */
layui.define(function(exports){ (function(global) {
"use strict"; 'use strict';
// 默认属性 var MOD_NAME = 'laytpl';
var config = {
open: '{{', // 标签符前缀
close: '}}' // 标签符后缀
};
// 模板工具 // 实例接口
var tool = { var thisModule = function() {
escape: function(html){
var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
if(html === undefined || html === null) return '';
html += '';
if(!exp.test(html)) return html;
return html.replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
.replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/'/g, '&#39;').replace(/"/g, '&quot;');
}
};
// 内部方法
var inner = {
exp: function(str){
return new RegExp(str, 'g');
},
// 错误提示
error: function(e, source){
var error = 'Laytpl Error: ';
typeof console === 'object' && console.error(error + e + '\n'+ (source || ''));
return error + e;
}
};
// constructor
var Class = function(template, options){
var that = this; var that = this;
that.config = that.config || {}; var options = that.config;
that.template = template; return {
config: options,
// 简单属性合并 /**
var extend = function(obj){ * 渲染模板
for(var i in obj){ * @param {Object} data - 模板数据
that.config[i] = obj[i]; * @param {Function} callback - 回调函数
* @returns {string} 渲染后的模板
*/
render: function(data, callback) {
options.data = data
var html = that.render();
// 如果传入目标元素选择器,则直接将模板渲染到目标元素中
if (options.target) {
var elem = document.querySelector(options.target);
if (elem) {
elem.innerHTML = html;
}
}
// 返回结果
return typeof callback === 'function'
? (callback(html), this)
: html;
},
/**
* 编译新的模板
* @param {string} template - 模板
* @returns {this}
*/
compile: function(template) {
options.template = template;
delete that.compilerCache; // 清除模板缓存
// that.compile(template);
return this;
},
/**
* 模板编译错误事件
* @param {Function} callback
* @returns {this}
*/
error: function(callback) {
callback && (options.error = callback);
return this;
},
/**
* 以下为兼容旧版本相关方法
*/
// 解析并渲染模板
parse: function(template, data) {
return this.compile(template).render(data);
}
};
};
// 模板内部变量
var vars = {
// 字符转义
escape: function(html) {
var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
if (html === undefined || html === null) return '';
html = ''+ html;
if (!exp.test(html)) return html;
return html.replace(exp, function(str) {
return '&#'+ str.charCodeAt(0) + ';';
});
} }
}; };
extend(config); // 组件工具类方法
extend(options); var tools = {
regex: function(str, mod) {
return new RegExp(str, mod || 'g');
},
/**
* 错误提示
* @param {string} e - 原始错误信息
* @param {Object} opts - 自定义选项
* @param {Function} error - 错误回调
* @returns {string} - 错误提示
*/
error: function(e, opts, error) {
opts = opts || {};
opts = Object.assign({
debug: '',
message: 'Laytpl '+ (opts.type || '') +'Error: ' + e
}, opts);
// 向控制台输出错误信息
typeof console === 'object' && console.error(opts.message, '\n', opts.debug, '\n', opts);
typeof error === 'function' && error(opts); // 执行错误回调
return opts.message; // 向视图返回错误提示
}
}; };
// 标签正则 // 默认配置
Class.prototype.tagExp = function(type, _, __){ var config = {
var options = this.config; open: '{{', // 起始界定符
var types = [ close: '}}', // 结束界定符
'#([\\s\\S])+?', // js 语句 cache: true, // 是否开启模板缓存,以便下次渲染时不重新编译模板
'([^{#}])*?' // 普通字段 condense: true, // 是否压缩模板空白符,如:将多个连续的空白符压缩为单个空格
][type || 0]; tagStyle: '' // 标签风格。默认采用 < 2.11 的风格,设置 modern 则采用 2.11+ 风格
return inner.exp((_||'') + options.open + types + options.close + (__||''));
}; };
// 模版解析 // 构造器
Class.prototype.parse = function(template, data){ var Class = function(template, options) {
var that = this;
// 选项合并
options = that.config = Object.assign({
template: template
}, config, options);
// 当前实例的模板内工具
that.vars = Object.assign({
/**
* 引用外部模板若在 Node.js 环境可通过重置该方法实现模板文件导入
* @param {string} id - 模板 ID
* @param {Object} data - 模板数据
* @returns {string} 模板渲染后内容
*/
include: function(id, data) {
var elem = document.getElementById(id);
var template = elem ? elem.innerHTML : '';
return template ? that.render(template, data) : '';
}
}, vars);
// 编译模板
that.compile(options.template);
};
/**
* 渲染
* @param {Object} template - 模板
* @param {Object} data - 数据
* @returns {string} 渲染后的模板内容
*/
Class.prototype.render = function(template, data) {
var that = this;
var options = that.config;
// 获得模板渲染函数
var compiler = template ? that.compile(template) : (
that.compilerCache || that.compile(options.template)
);
// 获取渲染后的字符
var html = function() {
data = data || options.data || {};
try {
return compiler(data);
} catch(e) {
template = template || options.template;
return tools.error(e, {
debug: that.checkErrorArea(template, data),
template: template,
type: 'Render'
}, options.error);
}
}();
// 缓存编译器
if (options.cache && !template) {
that.compilerCache = compiler;
}
return html; // 返回渲染后的字符
};
/**
* 编译模板
* @param {string} template - 原始模板
* @returns {Function} 模板编译器用于后续数据渲染
*/
Class.prototype.compile = function(template) {
var that = this; var that = this;
var options = that.config; var options = that.config;
var source = template; var source = template;
var jss = inner.exp('^'+ options.open +'#', ''); var openDelimiter = options.open;
var jsse = inner.exp(options.close +'$', ''); var closeDelimiter = options.close;
var condense = options.condense;
var regex = tools.regex;
const placeholder = '\u2028'; // Unicode 行分隔符
// 模板必须为 string 类型 // console.log('compile');
if(typeof template !== 'string') return template;
// 正则解析 // 模板必须为 string 类型,且不能为空
template = template.replace(/\s+|\r|\t|\n/g, ' ') if (typeof template !== 'string' || !template) {
.replace(inner.exp(options.open +'#'), options.open +'# ') return function() {
.replace(inner.exp(options.close +'}'), '} '+ options.close).replace(/\\/g, '\\\\')
// 不匹配指定区域的内容
.replace(inner.exp(options.open + '!(.+?)!' + options.close), function(str){
str = str.replace(inner.exp('^'+ options.open + '!'), '')
.replace(inner.exp('!'+ options.close), '')
.replace(inner.exp(options.open + '|' + options.close), function(tag){
return tag.replace(/(.)/g, '\\$1')
});
return str
})
// 匹配 JS 语法
.replace(/(?="|')/g, '\\').replace(that.tagExp(), function(str){
str = str.replace(jss, '').replace(jsse, '');
return '";' + str.replace(/\\(.)/g, '$1') + ';view+="';
})
// 匹配普通输出语句
.replace(that.tagExp(1), function(str){
var start = '"+laytpl.escape(';
if(str.replace(/\s/g, '') === options.open + options.close){
return ''; return '';
};
} }
str = str.replace(inner.exp(options.open + '|' + options.close), '');
if(/^=/.test(str)){ /**
str = str.replace(/^=/, ''); * 完整标签正则
} else if(/^-/.test(str)){ * @param {string[]} cores - 标签内部核心表达式前置主体后置
str = str.replace(/^-/, ''); * @param {Object} sides - 标签两侧外部表达式
start = '"+('; * @returns {RegExp}
*/
var tagRegex = function(cores, sides) {
var arr = [
'(?:'+ openDelimiter + (cores[0] || '') +'\\s*)', // 界定符前置
'('+ (cores[1] || '[\\s\\S]') +'*?)', // 标签主体
'(?:\\s*'+ (cores[2] || '') + closeDelimiter +')' // 界定符后置
];
sides = sides || {};
sides.before && arr.unshift(sides.before); // 标签前面的表达式
sides.after && arr.push(sides.after); // 标签后面的表达式
return regex(arr.join(''));
};
// 匹配非输出类型标签两侧的换行符和空白符,避免渲染后占用一行
var sidesRegex = condense ? ['', ''] : ['(?:(?:\\n)*\\s*)', '(?:\\s*?)'];
var delimSides = {
before: sidesRegex[0],
after: sidesRegex[1]
};
/**
* 清理多余符号
* @param {string} body - 标签主体字符
* @param {boolean} nowrap - 是否强制不换行
* @returns {string} 清理后的字符
*/
var clear = function(body, nowrap) {
if (!condense) {
// 还原语句中的 Unicode 行分隔符
body = body.replace(regex(placeholder), nowrap ? '' : '\n');
} }
return start + str.replace(/\\(.)/g, '$1') + ')+"'; body = body.replace(/\\(\\|")/g, '$1'); // 去除多余反斜杠
return body;
};
// 纠正标签结构
var correct = function(tpl) {
return tpl.replace(regex('([}\\]])'+ closeDelimiter), '$1 '+ closeDelimiter);
};
// 模板解析
var parse = that.parse = function(tpl) {
tpl = tpl || '';
if (!tpl) return tpl;
// 压缩连续空白符
if (condense) {
tpl = tpl.replace(/\t/g, ' ').replace(/\s+/g, ' ');
}
// 初始整理
tpl = correct(tpl) // 纠正标签
.replace(/(?=\\|")/g, '\\') // 转义反斜杠和双引号
.replace(/\r?\n/g, condense ? '' : placeholder); // 整理换行符
// 忽略标签 - 即区域中的内容不进行标签解析
tpl = tpl.replace(tagRegex(['!', '', '!'], delimSides), function(str, body) {
body = body.replace(regex(openDelimiter + '|' + closeDelimiter), function(tag) {
return tag.replace(/(?=.)/g, '\\');
});
return body;
}); });
template = '"use strict";var view = "' + template + '";return view;'; // 模板字符拼接
var strConcatenation = function(body) {
// 通过对 20k+ 行的模板进行编译测试, 发现 Chrome `+=` 性能竟优于 `push`
// 1k 次循环 + 1k 行数据量进行模板编译+渲染,发现 `+=` 性能仍然优于 `push`
// 考虑可能是 V8 做了 Ropes 结构优化? 或跟模板采用「静态拼接」的实现有关(可能性更大)
return ['";', body, '__laytpl__+="'].join('\n');
// return ['");', body, '__laytpl__.push("'].join('\n');
};
// 解析输出标签
var output = function(str, delimiter, body) {
var _escape;
if (!body) return '';
body = clear(body, true);
// 输出方式
if (delimiter === '-') { // 原文输出,即不对 HTML 原文进行转义
_escape = '';
} else { // 转义输出
_escape = '_escape';
}
return body ? strConcatenation(
'__laytpl__+='+ _escape +'('+ body +');'
// '__laytpl__.push('+ _escape +'('+ body +'));'
) : '';
};
// 解析 Scriptlet
var statement = function(str, body) {
if (!body) return '';
body = clear(body);
return strConcatenation(body);
};
// 标签风格
if (options.tagStyle === 'modern') { // 2.11+ 版本风格
// 注释标签 - 仅在模板中显示,不进行解析,也不在视图中输出
tpl = tpl.replace(tagRegex(['#'], delimSides), '');
// 输出标签
tpl = tpl.replace(tagRegex(['(=|-)']), output);
// Scriptlet 标签
tpl = tpl.replace(tagRegex([], delimSides), statement);
} else { // < 2.11 版本风格
// Scriptlet 标签
tpl = tpl.replace(tagRegex(['#'], delimSides), statement);
// 输出标签
tpl = tpl.replace(tagRegex(['(=|-)*']), output);
}
// 恢复换行符
if (!condense) {
tpl = tpl.replace(regex(placeholder), '\\n');
}
return tpl;
};
// 创建模板编译器
var createCompiler = that.createCompiler = function(template) {
var codeBuilder = [
'function(d){',
'"use strict";',
'var __laytpl__="",'+
function() { // 内部变量
// 内部方法
var arr = [];
for (var key in that.vars) {
arr.push(((key === 'escape' ? '_' : '') + key) +'=laytpl.'+ key);
}
return arr.join(',');
}() + ';',
'__laytpl__="'+ parse(template) +'";',
'return __laytpl__;',
// '__laytpl__.push("'+ parse(template) +'");',
// 'return __laytpl__.join("");',
'};'
].join('\n');
// console.log(codeBuilder);
try {
/** /**
* 请注意: 开发者在使用模板语法时需确保模板中的 JS 语句不来自于页面用户输入 * 请注意: 开发者在使用模板语法时需确保模板中的 JS 语句不来自于页面用户输入
* 即模板中的 JS 语句必须在页面开发者自身的可控范围内否则请避免使用该模板解析 * 即模板中的 JS 语句必须在页面开发者自身的可控范围内否则请避免使用该模板解析
*/ */
that.cache = template = new Function('d, laytpl', template); return new Function('laytpl', 'return '+ codeBuilder)(that.vars);
return template(data, tool); };
try {
return createCompiler(template); // 返回编译器
} catch(e) { } catch(e) {
delete that.cache; delete that.compilerCache;
return inner.error(e, source); return function() {
return tools.error(e, {
debug: that.checkErrorArea(source),
template: source,
type: 'Compile'
}, options.error);
};
} }
}; };
// 数据渲染 /**
Class.prototype.render = function(data, callback){ * 校验出错区域
data = data || {}; * @param {string} source - 原始模板
* @param {Object} data - 数据
* @returns {string} 出错区域的模板碎片
*/
Class.prototype.checkErrorArea = function(source, data) {
var that = this; var that = this;
var result = that.cache ? that.cache(data, tool) : that.parse(that.template, data); var srcs = source.split(/\n/g);
var validLine = -1; // 有效行
// 返回渲染结果 // 逐行查找
typeof callback === 'function' && callback(result); var i = 0;
return result; var str = '';
}; var len = srcs.length;
for (; i < len; i++) {
// 创建实例 str += srcs[i];
var laytpl = function(template, options){ try {
return new Class(template, options); data
}; ? that.createCompiler(str)(data)
: new Function('"use strict";_laytpl__="'+ that.parse(str) +'";');
// 配置全局属性 validLine = i;
laytpl.config = function(options){ } catch(e) {
options = options || {}; continue;
for(var i in options){
config[i] = options[i];
} }
}
// 呈现模板出错大致区域
var errorArea = function(errLine) {
var arr = [];
var addLine = 3; // 错误行上下延伸的行数
var i = 0;
var len = srcs.length;
if (errLine < 0) errLine = 0;
if (errLine > len - 1) errLine = len - 1;
i = errLine - addLine;
if (i < 0) i = 0;
for (; i < len; i++) {
arr.push((i == errLine ? '? ' : ' ') +(i + 1)+ '| '+ srcs[i]);
if (i >= errLine + addLine) break;
}
return '\n'+ arr.join('\n');
}; };
laytpl.v = '2.0.0'; return errorArea(validLine + 1); // 有效行的下一行即为出错行
};
// export /**
exports('laytpl', laytpl); * 创建实例
}); * @param {string} template - 模板
* @param {Object} options - 选项
* @returns
*/
var laytpl = function(template, options) {
var inst = new Class(template, options);
return thisModule.call(inst);
};
/**
* 扩展模板内部变量
* @param {Object} variables - 扩展内部变量变量值通常为函数
*/
laytpl.extendVars = function(variables) {
Object.assign(vars, variables);
};
/**
* 设置默认配置
* @param {Object} options - 选项
*/
laytpl.config = laytpl.set = function(options) {
Object.assign(config, options);
};
// 输出接口
typeof module === 'object' && typeof exports === 'object'
? module.exports = laytpl // CommonJS
: ( // 浏览器
typeof layui === 'object' ? layui.define(function(exports) { // Layui
exports(MOD_NAME, laytpl);
}) : (
typeof define === 'function' && define.amd ? define(function() { // RequireJS
return laytpl;
}) : global.laytpl = laytpl // 单独引入
)
);
})(this);

View File

@ -459,7 +459,7 @@ layui.define('component', function(exports) {
var that = this var that = this
var options = that.config; var options = that.config;
if(!options.closable) return; if (!options.closable) return;
opts = opts || {}; opts = opts || {};
@ -484,17 +484,13 @@ layui.define('component', function(exports) {
var that = this; var that = this;
var options = that.config; var options = that.config;
var container = that.getContainer(); var container = that.getContainer();
var hasDel = that.cache('close');
// 是否开启关闭 // 是否开启关闭
if (options.closable) { if (options.closable) {
if (!hasDel) { container.header.items.each(function() {
container.header.items.each(function(){
that.appendClose($(this)); that.appendClose($(this));
}); });
that.cache('close', true); } else {
}
} else if(hasDel) {
container.header.items.each(function() { container.header.items.each(function() {
$(this).find('.'+ component.CONST.CLOSE).remove(); $(this).find('.'+ component.CONST.CLOSE).remove();
}); });