Skip to content

权限管理

渲染权限数据

在权限编辑页需要完成蛮多东西,首先是渲染权限数据,然后再实现增删改查的逻辑。

1、前端表格占位

html
<div style="padding: 16px">
  <div class="layui-card">
    <div class="layui-card-body">
      <table
        class="layui-hide"
        id="rights-table"
        lay-filter="rights-table"
      ></table>
    </div>
  </div>
</div>

2、请求接口渲染数据

javascript
treeTable.render({
  elem: "#rights-table",
  id: "rights-table",
  url: "/api/v1/rights/treetable",
  height: "full-35",
  toolbar: "#rights-toolbar",
  cols: [
    [
      { type: "checkbox", fixed: "left" },
      {
        field: "id",
        title: "ID",
        width: 80,
        sort: true,
        fixed: "left",
      },
      { field: "name", title: "权限名", width: 180 },
      { field: "code", title: "权限标识", width: 150 },
      {
        field: "type",
        title: "类型",
        width: 100,
        templet: (d) => {
          if (d.type === "menu") {
            return `<button type="button" class="layui-btn layui-btn-sm layui-btn-radius">菜单</button>`;
          } else if (d.type === "path") {
            return `<button type="button" class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal">路径</button>`;
          } else if (d.type === "auth") {
            return `<button type="button" class="layui-btn layui-btn-sm layui-btn-radius layui-bg-purple">权限</button>`;
          }
        },
      },
      { field: "url", title: "访问地址", width: 200 },
      {
        field: "icon_sign",
        title: "图标",
        width: 90,
        templet: (d) => {
          return `<i class="${d.icon_sign}"></i> `;
        },
      },
      { field: "open_type", title: "打开方式", width: 100 },
      { field: "sort", title: "排序", width: 100 },
      {
        field: "status",
        title: "状态",
        width: 100,
        templet: (d) => {
          return `<input type="checkbox" name="status" value="${
            d.id
          }" title="启用|禁用" lay-skin="switch" ${
            d.status ? "checked" : ""
          } lay-filter="rights-table-switch-status"/>`;
        },
      },
      {
        fixed: "right",
        title: "操作",
        width: 120,
        align: "center",
        toolbar: "#rights-tool",
      },
    ],
  ],
  page: true,
});

3、后端数据返回

权限数据使用 treetable 组件进行渲染,首先可以参考 layui 的文档,然后再进行修改。

为了适应前端的数据,先改造数据模型

python
class RightsORM(BaseORM):
    __tablename__ = "ums_rights"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), nullable=False, comment="权限名称")
    code = db.Column(db.String(30), comment="权限标识")
    type = db.Column(db.String(30), comment="权限类型")
    url = db.Column(db.String(30), comment="路径地址")

    icon = db.Column(db.String(128), comment="图标")
    status = db.Column(db.Boolean, default=True, comment="是否开启")
    sort = db.Column(db.Integer, default=1)
    open_type = db.Column(db.String(128), comment="打开方式")
    pid = db.Column(
        db.Integer,
        db.ForeignKey("ums_rights.id"),
        default=0,
        comment="父类编号",
    )

    parent = db.relationship("RightsORM", back_populates="children", remote_side=[id])  # 自关联
    children = db.relationship("RightsORM", back_populates="parent")  

    def json(self):
        return {
            "id": self.id,
            "name": self.name,
            "code": self.code,
            "type": self.type,
            "url": self.url,
            "icon_sign": self.icon,
            "status": self.status,
            "sort": self.sort,
            "open_type": self.open_type,
            "pid": self.pid,
        }

然后再新增后端接口

python
@rights_api.get('/rights/treetable')
def get_list_as_treetable():
    page = request.args.get("page", type=int, default=1)
    per_page = request.args.get("per_page", type=int, default=10)

    q = db.select(RightsORM).where(RightsORM.type == "menu")
    pages = db.paginate(q, page=page, per_page=per_page, error_out=False)

    ret = []

    # 构建树状表格的数据
    for page in pages.items:
        data = page.json()
        data["children"] = []
        for child in page.children:
            child_data = child.json()
            if child.children:
                child_data["children"] = [
                    sub_child.json() for sub_child in child.children
                ]
                child_data["isParent"] = True

            data["children"].append(child_data)
            data["isParent"] = True
        ret.append(data)
    return {"code": 0, "msg": "请求权限数据成功", "count": pages.total, "data": ret}

编写完成之后,需要注册到 app。

目前接口设计只完成了有限的自己权限嵌套。

权限新增

1、新增表格头标签

点击新增按钮之后,触发 toolbar 事件, 然后进行拦截

html

<script type="text/html" id="rights-toolbar">
    <div class="layui-btn-container">
        <button class="layui-btn layui-btn-sm" lay-event="rights-toolbar-add">
            新增权限
        </button>
    </div>
</script>

2、处理新增事件

点击之后打开新增表单

javascript
treeTable.on("toolbar(rights-table)", function (obj) {
  if (obj.event === "rights-toolbar-add") {
    layer.open({
      type: 1,
      shade: false,
      content: $("#rights-form"),
      area: ["50%", "80%"],
    });
    form.render($("#rights-form"));
  }
});

3、新增表单

准备先把基础的功能实现,后续的逻辑再慢慢优化。

新增与编辑的表单直接写在当前页面,并且准备共用同一个

html
<form
  class="layui-form"
  lay-filter="rights-form"
  id="rights-form"
  style="padding: 15px; display: none"
>
  <div class="layui-form-item" style="display: none">
    <label class="layui-form-label">ID</label>
    <div class="layui-input-block">
      <input
        type="text"
        name="id"
        value="0"
        lay-verify="required"
        placeholder="请输入权限名"
        autocomplete="off"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">权限名</label>
    <div class="layui-input-block">
      <input
        type="text"
        name="name"
        lay-verify="required"
        placeholder="请输入权限名"
        autocomplete="off"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">权限标识</label>
    <div class="layui-input-block">
      <input
        type="text"
        name="code"
        autocomplete="off"
        placeholder="请输入权限标识"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">权限类型</label>
    <div class="layui-input-block">
      <input
        type="radio"
        name="type"
        value="menu"
        title="菜单"
        lay-filter="form-rights-filter"
        checked
      />
      <input
        type="radio"
        name="type"
        value="path"
        title="路由"
        lay-filter="form-rights-filter"
      />
      <input
        type="radio"
        name="type"
        value="auth"
        title="权限"
        lay-filter="form-rights-filter"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">图标</label>
    <div class="layui-input-inline">
      <input
        type="text"
        name="icon_sign"
        autocomplete="off"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item" style="display: none">
    <label class="layui-form-label">访问地址</label>
    <div class="layui-input-block">
      <input
        type="text"
        name="url"
        lay-verify=""
        placeholder="请输入访问地址"
        autocomplete="off"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">状态</label>
    <div class="layui-input-block">
      <input
        type="checkbox"
        name="status"
        lay-skin="switch"
        lay-filter="rights-row-status"
        checked
        title="启用|禁用"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">序号</label>
    <div class="layui-input-inline">
      <input
        type="text"
        name="sort"
        placeholder=""
        autocomplete="off"
        class="layui-input"
        value="1"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">打开方式</label>
    <div class="layui-input-inline">
      <select name="open_type" id="">
        <option value="_component" selected>默认方式</option>
        <option value="_blank">新窗口</option>
      </select>
    </div>
  </div>
  <div class="layui-form-item">
    <label class="layui-form-label">父元素 ID</label>
    <div class="layui-input-inline">
      <input
        type="text"
        name="pid"
        placeholder=""
        autocomplete="off"
        class="layui-input"
      />
    </div>
  </div>
  <div class="layui-form-item">
    <div class="layui-input-block">
      <button
        type="submit"
        class="layui-btn"
        lay-submit
        lay-filter="rights-form-btn"
      >
        立即提交
      </button>
      <button type="reset" class="layui-btn layui-btn-primary">重置</button>
    </div>
  </div>
</form>

4、监听类型切换

javascript
form.on("radio(form-rights-filter)", function (data) {
  var elem = data.elem;
  var value = elem.value;

  switch (value) {
    case "menu":
      $('[name="icon_sign"]').parent().parent().show();
      $('[name="url"]').parent().parent().hide();
      break;
    case "path":
      $('[name="icon_sign"]').parent().parent().hide();
      $('[name="url"]').parent().parent().show();
      break;
    case "auth":
      $('[name="url"]').parent().parent().hide();
      $('[name="icon_sign"]').parent().parent().hide();
      break;
  }
});

5、前端提交事件

需要区分新增与修改事件

js
// 提交事件
form.on("submit(rights-form-btn)", function (data) {
  let field = data.field;

  // 手动校验是否符合规范
  if (field.type === "path") {
    if (!/\//.test(field.url)) {
      layer.msg("url 不符合格式");
      return false;
    }
  } else if (field.type === "auth") {
    console.log(field.pid);
    if (!field.pid || field.pid === "0") {
      layer.msg("权限必须填入父级 id");
      return false;
    }
  }

  field.status = !!field.status;
  let method, url;
  if (field.id == 0) {
    field.id = null;
    method = "POST";
    url = "/api/v1/rights";
  } else {
    method = "PUT";
    url = `/api/v1/rights/${field.id}`;
  }
  $.ajax({
    url: url,
    type: method,
    contentType: "application/json",
    data: JSON.stringify(field),
    success: function (res) {
      if (!res.code) {
        layer.closeAll();
        treeTable.reloadData("rights-table");
      }
    },
  });

  return false; // 阻止默认 form 跳转
});

6、后端完成修改

python

@rights_api.post("/rights")
@jwt_required()
def create_rights():
    data = request.get_json()
    rights_obj = RightsORM(**data)
    rights_obj.save()
    return {"code": 0, "msg": "新增权限数据成功"}

权限修改

1、添加表格行编辑标签

html
<script type="text/html" id="rights-tool">
  <div class="layui-btn-container">
    <button
      class="layui-btn layui-btn-sm layui-bg-blue"
      lay-event="rights-tool-edit"
    >
      编辑
    </button>
    <button
      class="layui-btn layui-btn-sm layui-bg-red"
      lay-event="rights-tool-del"
    >
      删除
    </button>
  </div>
</script>

2、点击编辑逻辑

点击编辑按钮之后,打开编辑(新增)表单。

js
treeTable.on("tool(rights-table)", function (obj) {
  var layEvent = obj.event;
  if (layEvent === "rights-tool-edit") {
    form.val("rights-form", obj.data);
    layer.open({
      type: 1,
      shade: false,
      content: $("#rights-form"),
      area: ["50%", "80%"],
    });
  } else if (layEvent === "rights-tool-del") {
    console.log("删除权限");
  }
});

3、处理编辑提交

与新增共用同一个提交

4、后端完成修改

python

@rights_api.put("/rights/<int:rid>")
@jwt_required()
def change_rights(rid):
    data = request.get_json()
    del data['id']

    rights_obj = RightsORM.query.get(rid)
    for key, value in data.items():
        setattr(rights_obj, key, value)
    rights_obj.save()
    return {"code": 0, "msg": "修改权限数据成功"}

权限删除

1、前端部分

js
treeTable.on("tool(rights-table)", function (obj) {
    var layEvent = obj.event;
    if (layEvent === "rights-tool-edit") {
    // 编辑
    } else if (layEvent === "rights-tool-del") {
        $.ajax({
            url: `/api/v1/rights/${obj.data.id}`,
            type: "DELETE",
            contentType: "application/json",
            success: function (res) {
                if (!res.code) {
                    treeTable.reloadData("rights-table");
                }
            },
        });
    }
});

2、后端部分

python
@rights_api.delete('/rights/<int:rid>')
@jwt_required()
def delete_rights(rid):
    rights_obj = RightsORM.query.get(rid)
    rights_obj.delete()
    return {"code": 0, "msg": "删除权限数据成功"}