【JSON2WEB】01 WEB管理信息系统架构设计
【JSON2WEB】02 JSON2WEB初步UI设计
【JSON2WEB】03 go的模板包html/template的使用
【JSON2WEB】04 amis低代码前端框架介绍
【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成
【JSON2WEB】06 JSON2WEB前端框架搭建
【JSON2WEB】07 Amis可视化设计器CRUD增删改查
【JSON2WEB】08 Amis的事件和校验
【JSON2WEB】09 Amis-editor的代码移植到json2web
【JSON2WEB】10 基于 Amis 做个登录页面login.html
【JSON2WEB】11 基于 Amis 角色功能权限设置页面
管理信息系统一般注册用户较多,功能页面也很多,不同用户有不同的功能页面的操作权限,根据用户角色功能权限,生成动态的页面导航功能树是我采用的常规操作。
1 动态菜单的设计
关于数据库的表设计参阅 【REST2SQL】13 用户角色功能权限设计。
1.1 创建几个相关视图
- 角色-页面权限视图
create or replace view role_menu_v as
select m.p_id as m_id,m.s_name ,s.pf_role,r.p_id as r_id,
decode(length(s.pf_role),4,1,0) as b_yn,m.f_mod
from s_menu m
cross join s_role r --先做一个角色与功能的笛卡尔交叉,再连接角色功能表
left join s_role_menu s on s.pf_menu = m.p_id and s.pf_role = r.p_id
order by r.p_id,m.p_id
;
- 用户-角色视图
create or replace view user_role_v as
select r.p_id as r_id,r.s_name ,s.pf_user,u.p_id as u_id,
decode(length(s.pf_role),4,1,0) as b_yn
from s_role r
cross join s_user u --先做一个用户与角色的笛卡尔交叉,再连接用户角色表
left join s_user_role s on s.pf_user = u.p_id and s.pf_role = r.p_id
order by u.p_id,r.p_id
;
- 用户-角色-页面视图
create or replace view user_role_menu_v as
select distinct
m.p_id || m.s_name || nvl(m.s_note,'') as s_name,
m.s_WINp,
m.s_PARM,
m.p_id as pf_menu,
m.s_note,
--u.pf_role,
u.pf_user,
m.f_mod
from s_user_role u -- 用户 - 角色 = 功能视图
left join s_role_menu r on r.pf_role = u.pf_role
left join s_menu m on m.p_id = r.pf_menu
order by u.pf_user, m.p_id
;
- 用户-页面视图
create or replace view user_page_v as
select s_name as label,
pf_menu as url,
s_winp as schemaApi,
pf_user as userid,
decode(substr(pf_menu,2,3),'000',1,2) as layer
from USER_ROLE_MENU_V -- 用户功能页面
where f_mod = 'BS'
;
这个视图就是最后动态菜单树要呈现的内容,label,url,schemaapi是amin-admin矿建的要求,userid为用户Id,layer为导航菜单的层级,我一般只用2级。
2 后端实现
前端登录时发送过来用户ID,根据用户id获得页面权限列表和token。后端我还是用REST2SQL来实现。
2.1 创建获取用户页面的函数
代码如下:
/ 获取用户页面
func getUserPages(userId string) map[string]interface{} {
selectSQL := "select label,url,schemaApi,layer from user_page_v where userid = '" + userId + "'"
//执行 sql并返回 json 结果
logger.Alog(true, fmt.Sprint("getUserPage:", selectSQL))
result := Icrud.SelectData(selectSQL)
// json串反序列化
var dataset []map[string]interface{}
err := json.Unmarshal([]byte(result), &dataset)
if err != nil {
fmt.Println("Error:", err)
return nil
}
rows := make(map[string]interface{})
rows["rows"] = dataset
return rows
}
输入参数用户ID,返回一个map。
2.2 doToken()函数的修改
用户验证成功后,获取用户页面功能列表。
// 根据请求参数执行不同的TOKEN操作 ///
func doTOKEN(w http.ResponseWriter, req map[string]interface{}) {
// 返回数据
rw := returnMap()
rowsMap := make(map[string]interface{})
tokenMap := make(map[string]interface{})
// token操作, generate or validate
resToken := strings.ToLower(req["ResName"].(string))
switch resToken {
case "generate-token":
// w.Write([]byte("generate-token"))
var uid_pwd map[string]string = make(map[string]string)
uid_pwd["Userid"] = req["Userid"].(string)
uid_pwd["Passwd"] = req["Passwd"].(string)
// 用户名及密码验证
ret1 := uidPwdIsValid(uid_pwd)
if ret1 == 1 {
//fmt.Println(ret1)
tokenMap = token.GenerateTokenHandler(w, uid_pwd)
rw["msg"] = "恭喜您登录成功!"
// 获取功能页面列表
rowsMap = getUserPages(uid_pwd["Userid"])
//fmt.Println(rowsMap)
} else {
tokenMap["token"] = "无效用户Id:" + uid_pwd["Userid"] + "或密码" + uid_pwd["Passwd"]
rw["status"] = 401
rw["msg"] = "用户ID或密码无效!"
}
// http://127.0.0.1:5217/TOKEN/generate-token?userid=9998&passwd=8999
// curl "http://127.0.0.1:5217/TOKEN/generate-token?userid=9998&passwd=8999"
case "validate-token":
//w.Write([]byte("validate-token"))
var tokenString string = req["Authorization"].(string)
fmt.Println(tokenString)
tokenMap = token.ValidateTokenHandler(w, tokenString)
// curl http://localhost:5217/token/validate-token -H "Authorization:token"
}
// 返回数据
if tokenMap == nil {
rw["status"] = 401
rw["msg"] = "无效token"
}
dataMap := make(map[string]interface{})
dataMap["rows"] = rowsMap["rows"]
dataMap["token"] = tokenMap["token"]
rw["data"] = dataMap
// 输出到 http.ResponseWriter
httpResWriter(w, rw)
}
就是返回token和用户页面列表,Json内容如下:
{
"data": {
"rows": [
{
"LABEL": "Z000系统管理",
"LAYER": 1,
"SCHEMAAPI": null,
"URL": "Z000"
},
{
"LABEL": "Z010页面管理",
"LAYER": 2,
"SCHEMAAPI": "page.json",
"URL": "Z010"
},
{
"LABEL": "Z020角色管理",
"LAYER": 2,
"SCHEMAAPI": "role.json",
"URL": "Z020"
},
{
"LABEL": "Z030角色功能权限设置",
"LAYER": 2,
"SCHEMAAPI": "role_menu.json",
"URL": "Z030"
},
{
"LABEL": "Z040用户管理",
"LAYER": 2,
"SCHEMAAPI": "user.json",
"URL": "Z040"
},
{
"LABEL": "Z050用户角色设置",
"LAYER": 2,
"SCHEMAAPI": "user_role.json",
"URL": "Z050"
}
],
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiI1NDQ0IiwicGFzc3dkIjoiNDQ0NSIsImV4cCI6MTcxMjE1MjE4NiwiaXNzIjoiNTIxN-iCoeWKoemZoiJ9.H13dcOP6I4LV-KbCKsr8kYmtO3_jwf3QJ3uvk7Goy2k"
},
"msg": "恭喜您登录成功!",
"status": 0
}
用户页面是要按层级排序的。
3 前端实现
登录成功后,保存用户页面列表,主页获取导航site.json时加入,用户功能页面。
3.1 site.json页面
{
"status": 0,
"msg": "",
"data": {
"pages": [
{
"label": "Home",
"url": "/",
"redirect": "welcome"
},
{
"label": "导航树",
"children": [
{
"label": "Welcome to Json2Web",
"url": "welcome",
"schemaApi": "get:/pages/hello.json"
}
]
},
{
"label": "示例",
"children": [
{
"label": "REST2SQL",
"link": "https://blog.csdn.net/html5builder/article/details/135544119"
},
{
"label": "JSON2WEB",
"link": "https://blog.csdn.net/html5builder/article/details/135698948"
},
{
"label": "AMIS文档",
"link": "https://aisuda.bce.baidu.com/amis/zh-CN/docs/index"
}
]
}
]
}
}
动态页面菜单就加在【导航树】的节点下面。
3.2 login.html登录页面
只需要成功后,保存下来用户功能页面即可。api代码如下:
api: {
url: 'http://127.0.0.1:5217/token/generate-token?userid=$userId&passwd=$passWd',
method: 'get',
adaptor: function (payload) {
console.log(payload);
if (payload.status === 0) {
localStorage.setItem('token', payload.data.token);
let rows = JSON.stringify(payload.data.rows)
// console.log('rows',rows);
localStorage.setItem('rows', rows);
// localStorage.clear(); location.href = '/login.html';
return payload;
}
}
},
关键代码就两行:
let rows = JSON.stringify(payload.data.rows)
// console.log('rows',rows);
localStorage.setItem('rows', rows);
先把json对象转为json字符串,在保存到loacaStorage中。
3.2 index.html主页
原来页面导航的api如下:
api:'/pages/site.json'
增加接收适配器代码如下:
api: {
url: '/pages/site.json',
adaptor: function (payload) {
console.log('Payload', payload);
// 页面权限字符串
let rows = localStorage.getItem("rows");
// 字符串转json
let pageJson = JSON.parse(rows);
console.log('pageJson:', pageJson);
// 要插入菜单的节点
let pages = payload.data.pages[1];
console.log('pages:', pages);
// 创建动态菜单
pageJson.map(function (page) {
let layers = page.LAYER;
let labels = page.LABEL;
let schemaApis = 'get:/pages/' + page.SCHEMAAPI;
let urls = page.URL;
let l1 = pages.children.length;
if (layers == 1) {
// 插入一级菜单
pages.children[l1] = {label:labels,"children":[]};
} else {
// 插入二级菜单
let l2 = pages.children[l1 - 1].children.length;
pages.children[l1 - 1].children[l2] = {label:labels,url:urls,schemaApi:schemaApis};
};
});
// 返回
return payload;
}
}
注:先取出保存的用户页面列表Json字符串并转为json对象方便操作,再定位要插入导航节点,三采用map循环创建动态菜单(根据层级构建,一级只有label,和children[];二级还有url和schemaapi)。
4 实操演练
4.1 登录
输入用户名和密码点【提交】即可。
4.2 欢迎页面
登录成功切换到主页的欢迎页面:
4.3 动态导航菜单加载成功
动态导航菜单加载成功,测试各页面操作正常。
本文完。