DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • BBS

  • 订单平台

  • CRM

  • flask+layui

  • django+layui

    • 理论储备
      • 前端模版
      • 不分离CURD
      • 半分离CURD
      • 分离与半分离的区别
      • layui表单项代码
        • 移动端自适应
        • 表格数据处理
        • 字段值0和1
        • 字段值可分类别
        • 图标
        • 受到其他字段值影响
        • 表单项
        • form标签
        • 编辑页的ID
        • 普通表单项
        • 按钮启用和禁用的切换
        • 提交按钮
        • 单选框+监听类型切换
        • 图标选择器
        • 下拉框显示树 (单选
        • 下拉框选择 (单选
    • 项目编写
    • 规则怪谈
    • 一些思考
    • 代码讲解
    • 部署
  • 供应链

  • 实战
  • django+layui
DC
2024-11-11
目录

理论储备

# 前端模版

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

关于pear admin前端模版的下载和运行.

推荐通过vscode进行运行,在vscode中下载一个名为live server的插件,然后找到main.html文件,右键利用该插件运行!
- 可以直接点下载,下载指定版本
- 可以通过git命令下载 运行启动后,可以选择版本,页面会自动刷新!
1
2
3

# 不分离CURD

image-20241028144723516

[主页、列表] - 一个函数,函数中就一个get > render
1. 查询获取所有的obj,传递到Django html模版中
2. 模版中循环这些obj进行渲染 - 列表数据 + 根据obj的id反向生成编辑、删除按钮的url(url的&param

[新增]
> 跟编辑的逻辑几乎一摸一样,不同的两点在于
  - 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

# 半分离CURD

image-20241018191111359

每个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

# 分离与半分离的区别

image-20241028150954152

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
  • 无论是不分离还是半分离, 编辑和删除按钮的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

# 移动端自适应

<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

# 表格数据处理

# 字段值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
# 字段值可分类别
image-20241029094524778

-- 左侧是在表格页中的表现, 右侧是在新增、编辑页的表单中的表现

{
  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
# 图标
{
  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
# 受到其他字段值影响

-- 根据某个字段的值对另一个字段进行分类处理

{
  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

# 表单项

<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
# 编辑页的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
# 普通表单项

-- 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
# 按钮启用和禁用的切换

-- 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
# 提交按钮

-- 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
# 单选框+监听类型切换

-- 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
# 图标选择器
image-20241029090605284
<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
# 下拉框显示树 (单选
image-20241029090737364

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
# 下拉框选择 (单选
<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

权限校验
项目编写

← 权限校验 项目编写→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式