Layui项目开发模板
通过 yb-cli 快速创建的 create-layui-template,目的用于开发兼容 IE9 以下的简单页面(IE8+),最好只用 ES3 的语法,因为这里没有使用 babel 转译语法,可以在 can i use (opens new window)查看语法的兼容情况。
不过 yb-layui-extend/libs 提供了一些 polyfill:
│ │ ├── yb-layui-extend # yb-layui-extend 组件包的内容,自动从node_modules拷贝进来,请勿在此目录人为添加任何东西
│ │ │ ├── components # yb-layui-extend 提供的按需加载的layui模块
│ │ │ ├── libs # 放在yb-layui-extend里面的一些资源
│ │ │ │ ├── layui
│ │ │ │ ├── array-polyfils.js
│ │ │ │ ├── console-no-error.js
│ │ │ │ ├── promise.plyfill.js
│ │ │ │ └── url-search-params.js
2
3
4
5
6
7
8
# 技术栈
jquery v1.x + layui v2.x + scss
开发环境用 gulp + browser-sync 搭建 (封装在 yb-layui-extend/scripts)
# 安装依赖
# 安装依赖
npx yarn
2
# 命令
# 启动服务
npm run dev
# 打包
npm run build
# 打包后也启动服务
npm run serveBuild
# eslint 检测
npm run lint
2
3
4
5
6
7
8
9
10
11
# git 规范说明
开发分支:dev,稳定分支:master , 始终由 dev 向 master 合并,tag 的版本号规范遵循语义化版本号 (opens new window)
# 目录结构
├── /.husky/ # husky管理git hooks的目录,默认添加了 pre-commit 和 commit-msg 两个钩子
├── /dist/ # 项目打包输出目录,也可以用package.json的name作为打包目录,在ybEasy.config.js中配置outputDir
├── /src/ # 项目开发源码目录
│ ├── /components/ # 此工程支持按需加载的组件目录,遵循layui模块规范
│ │ ├── Api # 把后端api接口管理成模块
│ │ │ └── main.api.js # 其中一个接口api模块案例
│ │ ├── buttonShow # 一个组件目录案例,目录名称其实不重要,为了直观,最好也跟模块名称对应
│ │ │ ├── buttonShow.scss # 当前组件对应的scss,文件名必须跟模块名称对应,与模块同名就可以按需加载编译后的.css,为了避免样式类名冲突,请规范类名只应用于当前组件
│ │ │ ├── buttonShow.js # 不具备html模板写法的layui模块,文件名必须跟模块名称对应,不可以同时存在 buttonShow.js 和 buttonShow.html
│ │ │ └── buttonShow.html # 具备html模板写法的layui模块,方便使用layui的模板引擎,文件名必须跟模块名称对应,不可以同时存在 buttonShow.js 和 buttonShow.html
│ ├── /pages/ # 页面目录
│ │ ├── page1 # 页面1
│ │ │ ├── /images/ # 当前页面独有的图片资源
│ │ │ ├── index.scss # 当前页面独有的scss,当前目录可以多个.scss,文件名可以随意
│ │ │ ├── index.js # 当前页面独有的js,当前目录可以多个.js,文件名可以随意
│ │ │ └── index.html # 如果用当前目录名作为访问路径的一部分,文件名必须是index
│ │ ├── /images/ # 当前页面独有的图片资源
│ │ ├── index.scss # 当前页面独有的scss,当前目录可以多个.scss,文件名可以随意
│ │ ├── index.js # 当前页面独有的js,当前目录可以多个.js,文件名可以随意
│ │ └── index.html # pages 第一层也可以是一个页面的目录,访问路径是 / 时,文件名必须是index
│ ├── /libs/ # 多个页面共用的第三方js库,如jquery、layui
│ │ ├── yb-layui-extend # yb-layui-extend 组件包的内容,自动从node_modules拷贝进来,请勿在此目录人为添加任何东西
│ │ │ ├── components # yb-layui-extend 提供的按需加载的layui模块
│ │ │ ├── libs # 放在yb-layui-extend里面的一些资源
│ │ │ │ ├── layui
│ │ │ │ ├── array-polyfils.js
│ │ │ │ ├── console-no-error.js
│ │ │ │ ├── promise.plyfill.js
│ │ │ │ └── url-search-params.js
│ │ │ └── scss # yb-layui-extend 提供的scss,如 var.scss
│ │ └── aaa.js # 其他当前工程的不会变化的js,这些js的文件名可以随意,会全部加载
│ ├── /scss/ # 多个页面共用的scss
│ │ └── index.scss
│ ├── /images/ # 多个页面共用的图片资源
│ ├── /utils/ # 多个页面共用的js
│ │ ├── httpAjax.js # httpAjax 模块,使用$.ajax,封装后端接口返回数据统一处理等
│ │ └── index.js # utils 模块,当前工程的工具模块
├── .gitgnore # git忽略检测的配置
├── commitlint.config.js # commitlint配置
├── package.json #
├── README.md # 每个git工程要写说明
├── gulpfile.js # gulp 配置
└── ybEasy.config.js # 定义的打包脚本的配置,配置反代理等
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 开发环境说明
/pages/ 内的 html 模板应该是:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<title>事中提醒</title>
<!-- inject:libs:css -->
<!-- endinject -->
<!-- inject:components:css -->
<!-- endinject -->
<!-- inject:css -->
<!-- endinject -->
<!-- inject:libs:js -->
<!-- endinject -->
<!-- inject:_components_extend_:js -->
<!-- endinject -->
<!-- inject:utils:js -->
<!-- endinject -->
</head>
<body>
<div id="app"></div>
<!-- inject:js -->
<!-- endinject -->
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
不需要手动引入 js、css 文件,只管写 html 内容,当启动服务会自动插件对应的 js、css,也就是 html 模板中 inject 注释的作用。
如果不需要引入某部分的 js、css 文件,去掉对应的 inject 注释即可。
npm run dev 后访问页面例如:
http://localhost:2080/ ==> /pages/index.html http://localhost:2080/page1 ==> /pages/page1/index.html
# 页面独立的 js 文件的依赖关系
由于 inject 无法自动确认 js 的顺序,所以同一级目录的 js 不应该存在依赖关系,如下 a.js 和 b.js 不能有互相依赖关系。
或者通过对 js 文件名称的排序来确定依赖,例:a.js 排在前面,b.js 排在后面
├── /src/ # 项目开发源码目录
│ ├── /pages/ # 页面目录
│ │ ├── page1 # 页面1
│ │ │ ├── a.js # 当前页面js
│ │ │ ├── b.js # 当前页面js
│ │ │ └── index.html
2
3
4
5
6
跨目录的 js,目录深的.js 排前面,即始终 page1/b.js 会在 page1/deep/a.js 后面的,可以通过目录深浅形成依赖关系
├── /src/ # 项目开发源码目录
│ ├── /pages/ # 页面目录
│ │ ├── page1 # 页面1
│ │ │ ├── deep #
│ │ │ ├── ├── a.js # 当前页面js
│ │ │ ├── b.js # 当前页面js,可以依赖deep/a.js
│ │ │ └── index.html
2
3
4
5
6
7
# JS 作用域规范
这里无法使用任何的模块系统,所以一个 js 尽量暴露少的全局变量,js 内应该用闭包隔离作用域,通过 layui.define 模块声明,或者 layui.use 调用模块,或者 window 挂载少量的全局变量
├── /src/ # 项目开发源码目录
│ ├── /pages/ # 页面目录
│ │ ├── page1 # 页面1
│ │ │ ├── A-commonMethods.js # 当前页面js
│ │ │ ├── index.js # 当前页面js
│ │ │ └── index.html
2
3
4
5
6
// A-commonMethods.js
layui.define(['main.api'], function (exports) {
var mainApi = layui['main.api'];
exports('commonMethods', {
getData: function () {
return mainApi.getAllDict();
},
});
});
// (function (window) {
// var a = '666';
// function getNname() {
// return a;
// }
// function setName(arg) {
// a = arg;
// }
// // 挂载全局变量
// window.commonMethods = {
// getNname: getNname,
// setName: setName
// };
// })(window);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// index.js
layui.use(['form', 'commonMethods'], function () {
var commonMethods = layui['commonMethods'];
commonMethods.getData().then(function () {});
});
// (function (window) {
// // 调用commonMethods.js暴露的方法
// var a = window.commonMethods.getName();
// })(window);
2
3
4
5
6
7
8
9
# components 组件
components 目录无需关心文件顺序,支持按需加载到页面:
│ ├── /components/ # 此工程支持按需加载的组件目录,遵循layui模块规范
│ │ ├── Api # 把后端api接口管理成模块
│ │ │ └── main.api.js # 其中一个接口api模块案例
│ │ ├── buttonShow # 一个组件目录案例,目录名称其实不重要,为了直观,最好也跟模块名称对应
│ │ │ ├── buttonShow.scss # 当前组件对应的scss,文件名必须跟模块名称对应,与模块同名就可以按需加载编译后的.css,为了避免样式类名冲突,请规范类名只应用于当前组件
│ │ │ ├── buttonShow.js # 不具备html模板写法的layui模块,文件名必须跟模块名称对应,不可以同时存在 buttonShow.js 和 buttonShow.html
│ │ │ └── buttonShow.html # 具备html模板写法的layui模块,方便使用layui的模板引擎,文件名必须跟模块名称对应,不可以同时存在 buttonShow.js 和 buttonShow.html
2
3
4
5
6
7
# layui.define 与 exports
一个组件(.js 或者.html)至少要有一个 layui.define ,和 exports("对应文件名",{});
# html 模板块
使用 <script id="demo" type="text/html"></script>
包裹,必须要有 id 和 type ,id 不会在实际渲染中存在,主要用于构建编译组件时,在 js 代码中 'html(demo)' 获取这个模板
案例: buttonShow.html
<script id="demo" type="text/html">
<div class="button-show-wrapper layui-btn-container">
<button type="button" class="layui-btn layui-btn-primary">
{{ d.data.primaryName }}
</button>
</div>
</script>
<script>
layui.define(['laytpl', 'jquery', 'layer'], function (exports) {
var laytpl = layui.laytpl;
var layer = layui.layer;
var $ = layui.jquery;
var btnClick = function () {
layer.msg('弹窗', {
offset: 'rt',
});
};
var templates = {
// id="demo" type="text/html" 对应的 'html(demo)'
demo: 'html(demo)',
};
exports('buttonShow', {
templates: templates,
/**
*
* @param {object} options
* @param {jquery|string} options.el html容器, jquery对象或者css选择器
* @param {object} options.data 渲染数据
* @returns
*/
render: function (options) {
var elIsJquery = options.el instanceof $;
if (!elIsJquery && typeof options.el !== 'string') {
throw Error('el must be a jquery object or css selector');
}
var $el = elIsJquery ? options.el : $(options.el);
// 通过事件委托给按钮绑定事件,重复调用render就会重复绑定,这里要先移除,再绑定
$el.off('click', '.layui-btn', btnClick);
$el.on('click', '.layui-btn', btnClick);
// 编译成html
var html = laytpl(templates['demo']).render({
data: $.extend(
{
primaryName: '原始按钮',
},
options.data || {}
),
});
$el.html(html);
},
});
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 写一个组件
根据以上基础能力,每个组件应该有相同的特性,在 yb-layui-extend 提供了一个"ybCreate"模块,可以声明式的定制我们的组件
案例:ybButton.html
<script id="demo" type="text/html">
<button
type="button"
class="layui-btn layui-btn-{{d.type}} {{d.className}}"
style="{{d.style}}"
>
{{# if(d.iconType&&d.iconPosition==="before"){ }}
<i class="layui-icon {{d.iconType}}"></i>
{{# } }} {{- d.name||d.content }} {{#
if(d.iconType&&d.iconPosition==="after"){ }}
<i class="layui-icon {{d.iconType}}"></i>
{{# } }}
</button>
</script>
<script>
layui.define(['laytpl', 'jquery', 'layer', 'ybCreate'], function (exports) {
var laytpl = layui.laytpl;
var ybCreate = layui.ybCreate;
var $ = layui.jquery;
var templates = {
demo: 'html(demo)',
};
var createConfig = {
// 组件名称,与文件名对应即可
name: 'ybButton',
// 初始化组件属性
data: function () {
return {
// 定义按钮名称
name: '',
// 定义按钮样式类型, 'primary'||'normal'||'warm'||'danger'
type: 'default',
// 按钮的点击事件的回调方法
onClick: null,
// icon 位置,'before'||'after'
iconPosition: 'before',
// layui的小图标名称,为空则不显示小图标
iconType: '',
// 是否禁用
disabled: false,
};
},
// 实例创建时
created: function (opt) {
console.log(opt);
},
// 渲染视图,创建实例后和setData,会调用render
render: function () {
// 编辑模板后得到html,用 this.data 获取最新的组件属性
var props = $.extend({}, this.data);
var disabled = this.data.disabled;
if (disabled) {
props.type = 'disabled';
}
var html = laytpl(templates['demo']).render(props);
return html;
},
// render之后进行新dom挂载,执行mounted一次
mounted: function () {
this.bindClick();
},
// setData 会调用 updated
updated: function () {
this.bindClick();
},
// 重复调用render,意味着原html将被替换,绑定的事件之类需要释放,所以这里调用render之前也会自动this.destroy(),进入 destroyed 回调
destroyed: function () {
// 解绑事件
if (
!this.data.disabled &&
typeof this.data.onClick === 'function'
) {
this.$el.off('click', this.data.onClick);
}
},
methods: {
bindClick: function () {
// 绑定按钮事件
if (
!this.data.disabled &&
typeof this.data.onClick === 'function'
) {
this.$el.on('click', this.data.onClick);
}
},
},
};
exports(createConfig.name, {
// 当前组件所有模板
templates: templates,
// 组件的初始化方法
create: ybCreate.getComponentCreate(createConfig),
});
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
在另外组件中使用 ybButton
<script id="demo" type="text/html">
<div>
<div button-key="button1"></div>
<div button-key="button2"></div>
</div>
</script>
<script>
layui.define(
['laytpl', 'jquery', 'layer', 'ybCreate', 'ybButton'],
function (exports) {
var laytpl = layui.laytpl;
var ybCreate = layui.ybCreate;
// var $ = layui.jquery;
// var ybButton = layui.ybButton;
var templates = {
demo: 'html(demo)',
};
var createConfig = {
name: 'exampleButtonUse',
// 初始化组件属性
data: function () {
return {};
},
// 实例创建时
created: function (opt) {
console.log(opt);
},
// 渲染视图,创建实例后和setData,会调用render
render: function () {
var that = this;
// 编辑模板后得到html,this.data 获取最新的组件属性
var html = laytpl(templates['demo']).render(that.data);
return html;
},
mounted: function () {
var that = this;
// 按钮组件的实例存到 this.childComponents[alias], 如果没有 alias ,就是 this.childComponents['[button-key="button1"]']
// this.destroy()会对this.childComponents的实例逐个调用 this.childComponents[alias].destroy()
this.execChildCreate({
componentName: 'ybButton',
selector: '[button-key="button1"]',
alias: 'button1',
data: {
name: '第一个按钮',
iconType: 'layui-icon-addition',
// 点击后,修改第二个按钮的名称,使用组件实例的 setData 方法
onClick: that.buttonClick1,
},
});
this.execChildCreate({
componentName: 'ybButton',
selector: '[button-key="button2"]',
alias: 'button2',
data: {
name: '第二个按钮',
iconType: 'layui-icon-addition',
// 点击后,修改第一个按钮的名称,使用组件实例的 setData 方法
onClick: that.buttonClick2,
},
});
},
// 更新后的回调,setData之后
// updated:function(){},
// 销毁一些事件和子组件的事件和内容等,this.destroy()会触发回调
destroyed: function () {
// var that = this;
},
methods: {
buttonClick1: function () {
this.childComponents['button2'].setData({
name: '修改后第二个按钮名称',
});
},
buttonClick2: function () {
this.childComponents['button1'].setData({
name: '修改后第一个按钮名称',
});
},
},
};
exports(createConfig.name, {
// 当前组件所有模板
templates: templates,
// 组件的初始化方法
create: ybCreate.getComponentCreate(createConfig),
});
}
);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
组件内部可用的东西:
# this.data
类型:object
最新的组件属性
# this.execChildCreate
类型:function
执行子组件的创建
this.execChildCreate({
// 组件名称,必填
componentName: 'ybButton',
// 容器的选择器,必填
selector: '[button-key="button2"]',
// 别名,选填,用于 this.childComponents[alias] 获取子组件的实例,如果没有alias,就是this.childComponents[selector]
alias: 'button2',
// 组件create方法的其他初始属性
data: {
name: '第二个按钮',
iconType: 'layui-icon-addition',
// 点击后,修改第一个按钮的名称,使用组件实例的 setData 方法
onClick: that.buttonClick2,
},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# this.childComponents
类型:object
所有子组件的实例,凡是调用 this.execChildCreate 后存储的,用法:this.childComponents[alias||selector]
this.childComponents['button2'].setData({
name: '新按钮名称',
});
2
3
# this.childSelectors
类型:object
所有子组件容器的选择器,凡是调用 this.execChildCreate 后存储的,用法:this.childSelectors[alias||selector]
console.log(this.childSelectors['button2']);
// '[button-key="button2"]'
2