理论储备
# 前端模版
Q: 关于为什么选pear admin? 它与前后端分离的layui vue前端模版有何不同?(技术选型 - 适合的才是最好的
- pear admin layui前端模版
- 需要掌握
> html + css + js
> layui
- 基于ajax进行前后端交互
- 适用于B/S开发
- 定制性相比vue前端模版差了些,但强在上手简单,学习成本低. -- 适用于简单的后台项目!
- layui vue 前端模版
- 需要掌握
> html + css + js ES5、ES6的语法
> layui
> vue2.0的生态 -- 基础vue语法、CLI、VUEX、VUE-ROUTER
vue3.0的生态 -- 基础vue语法有变化、CIL、pinia
- 基于axios进行前后端交互
- 适用于B/S 以及 C/S 开发
- 可以基于组件进行定制模块化开发! -- 适用于复杂的后台项目!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关于pear admin前端模版的下载和运行.
推荐通过vscode进行运行,在vscode中下载一个名为live server的插件,然后找到main.html文件,右键利用该插件运行!
- 可以直接点下载,下载指定版本
- 可以通过git命令下载 运行启动后,可以选择版本,页面会自动刷新!
1
2
3
2
3
# 不分离CURD
[主页、列表] - 一个函数,函数中就一个get > render
1. 查询获取所有的obj,传递到Django html模版中
2. 模版中循环这些obj进行渲染 - 列表数据 + 根据obj的id反向生成编辑、删除按钮的url(url的¶m
[新增]
> 跟编辑的逻辑几乎一摸一样,不同的两点在于
- get >
新增 form = RoleModelForm()
编辑 form = RoleModelForm(instance=obj) -- 表单自动填充
- post
新增 form = RoleModelForm(data=request.POST)
编辑 form = RoleModelForm(data=request.POST, instance=obj) -- 前者用于校验,后者是确定修改哪条记录
[编辑] - 一个函数,函数中有get和post > render、redirect
不管是请求类型是get还是post,都会根据url的参数id来判断对应的obj是否存在,不存在render500页面,存在进行以下步骤
<post表单提交是提交到当前url,所以get和post请求使用的是同一个路由!>
○ get
1. render 编辑页面,render时传递当前id对应的对象到模版中,进而实现 表单的自动填充
○ post
1. 获取到编辑页面提交的表单数据
2. 进行form表单验证
- 失败 render 错误信息到编辑页面
- 成功 redirect 重定向列表页面
[删除] - 一个函数,函数中有get和post > render、redirect
不管是请求类型是get还是post,都会根据url的参数id来判断对应的obj是否存在,不存在render500页面,存在进行以下步骤
○ get
1. render 删除页面,render时传递列表的url到模版中,进而实现 点击取消时,redirect 重定向列表页面
○ post
1. 表单提交到当前url,所以post和get使用的是同一个路由. 执行删除代码
2. redirect 重定向列表页面
1
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
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
# 半分离CURD
每个get、post都对应一个单独的视图函数!
[主页get、新增页get、编辑页get]
> 得到页面
- 后端render的页面,新增和编辑页面会加载到前端模版的iframe窗口中!
- ★★ 编辑页面的url是通过?params的形式传递id给路由对应的视图函数!
然后编辑页是通过Django的模版渲染实现的表单数据自动填充
[列表get]
> 构建符合表格的数据结构的数据并返回
[新增post、编辑post]
1. 后端接收前端传递的值
2. 若是编辑,在新增的基础上添加这第2步,判断是否存在 -3
3. 进行form表单验证
4. 规则怪谈,可在两个地方实现
- form表单验证的clean函数 -1
- 在视图函数中 -2
5. 成功 0
[删除post]
1. 后端接收前端传递的id值
2. 判断是否存在
3. 存在则删除,删除成功 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 分离与半分离的区别
ps: 可能还不止这7个路由, 比如权限管理中 还有 获取权限的父权限下拉框 的路由.. 、关于状态更改的api..
Q: 为啥新增和删除的路由里没有<int:id> ?
A: 我们从头开始理 >
在主页里的表格,我们可以通过table组件的行工具栏<有编辑和删除>轻松获取某行记录的全部信息,包括id
- 1. 点击编辑按钮时, GET请求,将id放到url的?params里,传递到了部门修改页的视图函数中
视图函数render了部门修改页,并在页面中通过django的模版渲染实现了表单的自动填充
- 2. 该编辑表单页面的id表单项是隐藏不可编辑的,表单提交时,将id放到请求体中传递给了部门编辑的视图函数!
- 3. 点击删除按钮时,主页弹出询问框,确认删除后,将id放到请求体中传递给了部门删除的视图函数!
> 不看代码咋写的,看到这个问题,就应该想到 [传递数据的载体]
- url > url参数?params 和 路由参数<int:id>
- 请求体
- 请求头
> ★★★ So,看了上面的回答,明确了传递数据的载体有哪些,将半分离跟不分离的代码逻辑作对比
不难看出,本质就是选择传递数据的载体不同罢了.
半分离选择其他数据的载体也能很好的实现这些功能!! (只是现目前的搭配可以让路由看起来够简洁!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
无论是不分离还是半分离, 编辑和删除按钮的url都在主页中实现了 (准确点说, 半分离在主页里是实现了一部分
- 不分离 - 通过obj 的id 进行url的反向解析得到 (get、post在同一路由上
- 半分离 - 基于layui table的头部工具栏、行内工具栏,自己手写html的url (get、post分别在两个路由上,两视图
ps: 当然新增的get和post是可以像不分离一样写在同一路由上的,但我拆分开了.
区别1: 渲染操作谁来
- 不分离 - html和数据都得Django后端来渲染
- 半分离 - 浏览器得DOM操作分担了Django后端的一部分渲染操作
- 半分离列表数据就是通过post请求, DOM渲染到表格中
- 当然半分离的时候, 还是可以通过Django来模版渲染, eg: 编辑时实现表单的自动填充
区别2: 返回操作是啥
不分离 - 全是render或者redirect 都会造成页面的刷新
半分离
获取html页面(主页、新增、编辑)时, 前端模版是加载到iframe窗口中的, 不会造成页面的刷新
ajax post请求, 获取返回的json数据, 也不会造成页面的刷新.. (其实进行了表格的刷新, Hhh
status状态码 - 200 ajax的success接收 - 关闭iframe窗口 - 刷新表格,即再请求一次列表数据 - 401 ajax的error接收 - code码 > -1 - code码 > -2 - code码 > -3 后来我觉得这样不妥,200成功然后根据code可以分辨出信息, status > 401 403 404 被error接收,处理权限不足的问题!
1
2
3
4
5
6
7
8
9
10
# layui表单项代码
解决console.log打印不全的问题.
console.log(JSON.stringify(数组或对象));
1
封装了点ajax请求!!
let token = "{{ csrf_token }}";
solve_csrf(token)
let url = "/api/v1/department/edit/"
let d = data.field
let reload_table = "department-treeTable"
addAndEditAjaxRequest(popup, url, d, reload_table)
return false;
layer.close(index)
let url = "/api/v1/department/del/"
let data = {id: obj.data.id}
deleteAjaxRequest(popup, obj, url, data)
let operate = obj.elem.checked ? 1 : 0;
let url = "/api/v1/rights/is_enabled/"
let data = {id: obj.value, is_enabled: operate}
let reload_table = "rights-treeTable"
switchAjaxRequest(popup, url, data, reload_table)
return false;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 移动端自适应
<meta charset="utf-8"/>
<meta name="renderer" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 表格数据处理
# 字段值0和1
// 用文字
{
field: "enable",
title: "状态",
width: 100,
templet: function (d) {
// console.log(d.enable);
if (d.enable === true) {
return "已启用";
} else {
return "未启用";
}
},
}
// 用按钮启用禁用进行切换
{
field: "status",
title: "状态",
width: 100,
align: "center",
templet: (d) => {
return `<input type="checkbox"
name="status"
value="${d.id}"
title="启用|禁用"
lay-skin="switch" ${d.status ? "checked" : ""}
lay-filter="rights-table-switch-status"/>`;
},
},
1
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
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
# 字段值可分类别
-- 左侧是在表格页中的表现, 右侧是在新增、编辑页的表单中的表现
{
field: "kind",
title: "类型",
width: 100,
align: "center",
templet: (d) => {
if (d.kind === "menu") {
return `<span class="layui-badge layui-bg-green">菜单</span>`;
} else if (d.kind === "menu-z") {
return `<span class="layui-badge layui-bg-blue">节点</span>`;
} else if (d.kind === "auth") {
return `<span class="layui-badge-rim">权限</span>`;
}
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 图标
{
field: "icon_sign",
title: "图标",
width: 90,
align: "center",
templet: (d) => {
return `<i class="${d.icon_sign}"></i> `;
},
},
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 受到其他字段值影响
-- 根据某个字段的值对另一个字段进行分类处理
{
field: "sort", title: "排序", width: 60, align: "center", templet: (d) => {
if (d.kind === "menu") {
return `<span style="color:#16baaa">${d.sort}</span>`
} else if (d.kind === "menu-z") {
return `<span style="color:#1e9fff">${d.sort}</span>`
} else {
return `<span>${d.sort}</span>`
}
}
},
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 表单项
<div class="layui-input-inline">
短一点, <div class="layui-input-block">
长一点.
# form标签
<form
class="layui-form"
lay-filter="department-form"
id="department-form"
style="padding: 15px"
>
<!-- ... ... -->
</form>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 编辑页的ID
-- value、disabled
<div class="layui-form-item">
<label class="layui-form-label">ID</label>
<div class="layui-input-block">
<input
type="text"
name="id"
autocomplete="off"
class="layui-input"
value="{{ edit_obj.id }}"
disabled
/>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 普通表单项
-- lay-verify、lay-tips、value 属性都是可选的.. 根据需求自定义用不用
<div class="layui-form-item">
<label class="layui-form-label">上级部门ID</label>
<div class="layui-input-block">
<input
type="text"
name="pid"
placeholder="请输入上级部门id"
autocomplete="off"
class="layui-input"
lay-verify="required"
lay-tips="若不填则新增部门为顶级部门,若填该ID对应的部门必须存在"
value="{{ edit_obj.pid_id|default:'' }}"
/>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 按钮启用和禁用的切换
-- checked
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<input
type="checkbox"
name="enable"
lay-skin="switch"
autocomplete="off"
title="启用|禁用"
class="layui-input"
{% if edit_obj.enable %}
checked
{% endif %}
/>
</div>
</div>
<!-- 表单提交时,转换下
data.field.status = data.field.status === "on"; -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 提交按钮
-- lay-submit、lay-filter
<div class="layui-form-item">
<div class="layui-input-block">
<button
type="submit"
class="layui-btn"
lay-submit
lay-filter="department-edit-form-btn"
>
立即提交
</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
<!-- 表单ajax提交后 记得 return false -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 单选框+监听类型切换
-- value+lay-filter 会被 单选框事件/监听类型切换 使用!
<!-- 属性 value 可设置值,否则选中时返回的默认值为 on(浏览器默认机制). 同组单选框一般设置相同值. -->
<div class="layui-form-item">
<label class="layui-form-label">权限类型</label>
<div class="layui-input-block">
<input
type="radio"
name="kind"
value="menu"
title="菜单"
lay-filter="form-rights-filter"
checked
/>
<input
type="radio"
name="kind"
value="menu-z"
title="节点"
lay-filter="form-rights-filter"
/>
<input
type="radio"
name="kind"
value="auth"
title="权限"
lay-filter="form-rights-filter"
/>
</div>
</div>
// 监听类型切换 > 根据切换隐藏显示某些表单项+控制必填
form.on("radio(form-rights-filter)", function (data) {
let elem = data.elem; // 获得 radio 原始 DOM 对象
let value = elem.value; // 获得 radio 值
switch (value) {
case "menu":
$('[name="icon_sign"]').parent().parent().show();
$('[name="code"]').parent().parent().hide();
$('[name="url"]').parent().parent().hide();
$('[name="code"]').removeAttr('lay-verify');
$('[name="url"]').removeAttr('lay-verify');
break;
case "menu-z":
$('[name="icon_sign"]').parent().parent().hide();
$('[name="code"]').parent().parent().show();
$('[name="url"]').parent().parent().show();
$('[name="code"]').attr('lay-verify', 'required');
$('[name="url"]').attr('lay-verify', 'required');
break;
case "auth":
$('[name="icon_sign"]').parent().parent().hide();
$('[name="code"]').parent().parent().show();
$('[name="url"]').parent().parent().show();
$('[name="code"]').attr('lay-verify', 'required');
$('[name="url"]').attr('lay-verify', 'required');
break;
}
});
1
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
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
# 图标选择器
<div class="layui-form-item">
<label for="" class="layui-form-label">图标</label>
<div class="layui-input-block">
<!-- {{ edit_obj.icon_sign|slice:'11:' }} 去掉前面的"layui-icon "-->
<input
type="text"
id="iconPicker"
name="icon_sign"
lay-filter="iconPicker"
value="{{ edit_obj.icon_sign|slice:'11:' }}"
style="display: none;"
>
</div>
</div>
<script src="/static/component/module_third/icon/iconPicker.js"></script>
<script>
layui.use(['iconPicker'], function () {
let iconPicker = layui.iconPicker; // 图标选择器
iconPicker.render({
// 选择器,推荐使用input
elem: '#iconPicker',
// 数据类型: fontClass/unicode,推荐使用fontClass
type: 'fontClass',
// 是否开启搜索: true/false,默认true
search: true,
// 是否开启分页: true/false,默认true
page: true,
// 每页显示数量,默认12
limit: 12,
});
})
</script>
<!-- 表单提交时,转换下
data.field.icon_sign = "layui-icon " + data.field.icon_sign -->
1
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
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
# 下拉框显示树 (单选
layui也有树, 但是使用这个combotree插件, 你不用自己构造, 该插件会根据pid自己构建树, 就很舒服..
<div class="layui-form-item">
<label class="layui-form-label">父元素 ID</label>
<div class="layui-inline">
<div id="rights-pid-select" lay-tips="规则怪谈:权限类型可为三种 - 菜单、节点、权限">
</div>
</div>
</div>
<script src="/static/component/module_third/select/combotree.js"></script>
<script>
layui.use(['combotree'], function () {
let combotree = layui.combotree; // pid 下拉选择
let pidId = "{{ edit_obj.pid_id }}"; // 这里的值将在后端渲染为对应的值
combotree.render({
elem: '#rights-pid-select'
, isMultiple: false
, expandLevel: 2
, yChkboxType: 's'
, nChkboxType: 's'
, ajaxUrl: '/api/v1/rights/get_rights_pid_select/'
, initValue: pidId // 编辑时,设置这个
})
</script>
<!-- 表单提交时,转换下
data.field.pid = data.field["rights-pid-select"]; // 给data.field新增pid字段 -->
1
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
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
# 下拉框选择 (单选
<div class="layui-form-item">
<label class="layui-form-label">打开方式</label>
<div class="layui-input-inline">
<select name="open_type" id="">
<option value="">请选择</option>
<option value="_iframe" selected>内部打开(默认)</option>
<option value="_blank">新窗口</option>
</select>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10