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": "删除权限数据成功"}