后台UI框架开发(一)
众所周知,现在的后台管理系统的前端页面基本上都是一个样子……
那既然,每个后台管理页面的样子都是这样的,那我们能不能设计一个页面,专门写成这个样子,只需要以面向对象的方式去使用某些方法来修改标题、快捷菜单、功能菜单、导航菜单……
整体设计
我们直接来套用一下Vue的目录结构:
- src
- api
- index.js
- router
- index.js
- api
- static
- pages
- styles
- scripts
- simple1.html
- simple2.html
- child1.html
- child2.html
- child3.html
- child4.html
- child5.html
- child6.html
- index.html
- main.js
然后我们看一下每一个目录/文件都是干什么的。
目录:src/api/index.js
const host = "http://example.com";
const request = {doGet: (url, data, func) => {let ajax = new XMLHttpRequest();let params = "";for (let param in data) {params = params + param + "=" + data[param] + "&";}params = params.substr(0, params.length - 1);ajax.open('GET', host + url + "?" + params);ajax.send();ajax.onreadystatechange = function() {if (ajax.readyState == 4 && ajax.status == 200) {let json = JSON.parse(ajax.responseText);func(json);}}},doPost: (url, data, func) => {let ajax = new XMLHttpRequest();ajax.open("POST", host + url, true);ajax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');let params = "";for (let param in data) {params = params + param + "=" + data[param] + "&";}ajax.send(params);ajax.onreadystatechange = function() {if (ajax.readyState == 4 && ajax.status == 200) {let json = JSON.parse(ajax.responseText);func(json);}}}
};
const userApi = {userReg: (params, func) => request.doPost("/userApi/userReg", params, func),// 用户注册userLogin: (params, func) => request.doPost("/userApi/userLogin", params, func),// 用户登录
};
目录:src/router/index.js
const routes = [{name:"下拉标题1",children:[{to:"/child1",name:"子级菜单1",path:"/child1.html"},{to:"/child2",name:"子级菜单2",path:"/child2.html"},{to:"/child3",name:"子级菜单3",path:"/child3.html"},]},{name:"下拉标题2",children:[{to:"/child4",name:"子级菜单4",path:"/child4.html"},{to:"/child5",name:"子级菜单5",path:"/child5.html"},{to:"/child6",name:"子级菜单6",path:"/child6.html"},]},{to:"/simple1",name:"简单导航1",path:"/simple1.html"},{to:"/simple2",name:"简单导航2",path:"/simple2.html"},
]
然后我们编写一下,这个后台页面
<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title></title><link rel="stylesheet" type="text/css" href="css/main.css"/></head><body><div class="box"><div class="header-box"><div class="logo-box"><div class="logo">logo站位</div></div><div class="tool-box"><div class="control-box"></div><div class="info-box"></div></div></div><div class="content-box"><div class="nav-box"></div><div class="main-box"><object class="frame" id="obj" data="" type="text/html"></object></div></div></div></body><script src="src/router/index.js" type="text/javascript" charset="utf-8"></script><script src="main.js" type="text/javascript" charset="utf-8"></script>
</html>
现在介绍一下各个class对应的元素的意义:
-
box:包围盒
-
header-box:头部包围盒
- logo-box:Logo包围盒
- logo:Logo站位
- tool-box:快捷导航包围盒
- control-box:控制器包围盒
- info-box:信息包围盒
-
content-box:主体包围盒
- nav-box:导航菜单包围盒
- main-box:主体内容包围盒
- frame:类似于“iframe”一样的产物,进行页面内部跳转
-
然后我们需要构思,如何能够直接通过router/index.js中的数据来直接生成导航菜单呢
js献上
// 生成导航菜单
const navBox = document.querySelector(".nav-box");
// 获取导航菜单包围盒
routes.forEach(item => {if (!item.children) {// 简单导航let simple = document.createElement("div");simple.classList.add("simple-nav");simple.innerHTML = "<i class='icon'></i><span>" + item.name + "</span>";simple.dataset.to = item.to;navBox.appendChild(simple);} else {// 下拉菜单let multip = document.createElement("dl");multip.classList.add("nav-list");let dt = document.createElement("dt");dt.innerHTML = "<i class='icon'></i><span>" + item.name + "</span>";navBox.appendChild(multip);multip.appendChild(dt);item.children.forEach(child => {let dd = document.createElement("dd");dd.classList.add("multip");dd.innerHTML = "<span>" + child.name + "</span>";dd.dataset.to = child.to;multip.appendChild(dd);});}
});
// 这个操作,应该所有人都看得懂吧,我就不多于陈述了。// 菜单添加效果
const navLists = document.querySelectorAll(".nav-list>dt");
// 我们首先要把所有的下拉菜单取出
const clearElementsClass = (doms, className) => {doms.forEach(item => {item.classList.remove(className);});
};
// 定义一个方法,可以将"NodeList"中的所有元素的某一个className删除
const clearElementParentsClass = (doms, className) => {doms.forEach(item => {item.parentNode.classList.remove(className);});
};
// 定义一个方法,可以将"NodeList"中的所有元素的父级元素的某一个className删除
navLists.forEach(item => {item.addEventListener("click", e => {clearElementParentsClass(navLists, "active");item.parentNode.classList.add("active");});
});
// 现在来遍历所有的下拉菜单,清除所有下拉菜单中的"active"类,并为当前鼠标单击行添加"active"类,这里是实现效果
let martJokes = document.querySelectorAll(".multip");
// 获取所有的下拉菜单项
let coverJokes = document.querySelectorAll(".simple-nav");
// 获取所有的简单导航项
const clearJokesClass = () => {martJokes.forEach(item => {item.classList.remove("ajok");});coverJokes.forEach(item => {item.classList.remove("ajok");});
};
// 定义一个方法,可以清除掉下拉菜单项和简单导航项的"ajok"类
martJokes.forEach(item => {item.addEventListener("click", e => {clearJokesClass();item.classList.add("ajok");});
});
coverJokes.forEach(item => {item.addEventListener("click", e => {clearJokesClass();item.classList.add("ajok");});
});
// 和上面清除"active"类效果一样,清除"ajok"类,并为当前鼠标单击项添加"ajok"
至此,我们想要实现的效果就已经完成了。
那么接下来,我们就要考虑,如何在鼠标点击导航项之后,让“#obj”的内联框架进行跳转了。
js献上
const links = document.querySelectorAll("[data-to]");
// 现将上面操作中所有含有dataset.to的项取出。
const frame = document.querySelector("#obj");
// 再将内联框架取出。
const foundPath = itemTo => {let path = "";routes.forEach(item => {if (!item.children) {if (item.to == itemTo) {path = "pages" + item.path;}} else {item.children.forEach(child => {if (child.to == itemTo) {path = "pages" + child.path;}});}});return path;
};
// 定义方法,检索每个"to"所对应的"path"
links.forEach(item => {item.addEventListener("click", e => {frame.data = foundPath(item.dataset.to);});
});
// 为每个导航菜单项添加一个单击事件监听,通过标签上的"data-to"找到对应的"path",并让内联框架进行跳转。
这样一来,基本的模型就设计完成了。
但是,我们文章开头时说过,要进行面向对象的操作,也就是XXX.setXXX()和XXX.getXXX()来进行操作,但是现在并未拥有这个能力,这该怎么搞呢?
现在,我们拥有三种方法:
- 类似于JQuery一样,去修改某些模型的“prototype”原型及原型链
- 定义一个class直接在class中定义方法进行操作
- 定义一个对象,在对象中定义方法进行操作,其实这个和第二个一样,但是方便呀,所以,我们暂且称之为第三种方法,并且,我们就选择这种方法了。
既然如此,我们要将上面的js合并到一个对象中去了,js献上
const martApp = {// init nav-list.startApp:()=>{const navLists = document.querySelectorAll(".nav-list>dt");const clearElementsClass = (doms,className) =>{doms.forEach(item=>{item.classList.remove(className);});};const clearElementParentsClass = (doms,className) =>{doms.forEach(item=>{item.parentNode.classList.remove(className);});};navLists.forEach(item=>{item.addEventListener("click",e=>{clearElementParentsClass(navLists,"active");item.parentNode.classList.add("active");});});let martJokes = document.querySelectorAll(".multip");let coverJokes = document.querySelectorAll(".simple-nav");const clearJokesClass = () => {martJokes.forEach(item=>{item.classList.remove("ajok");});coverJokes.forEach(item=>{item.classList.remove("ajok");});};martJokes.forEach(item=>{item.addEventListener("click",e=>{clearJokesClass();item.classList.add("ajok");});});coverJokes.forEach(item=>{item.addEventListener("click",e=>{clearJokesClass();item.classList.add("ajok");});});},initNavList:()=>{const navBox = document.querySelector(".nav-box");routes.forEach(item=>{if(!item.children){// 简单导航let simple = document.createElement("div");simple.classList.add("simple-nav");simple.innerHTML="<i class='icon'></i><span>" + item.name + "</span>";simple.dataset.to = item.to;navBox.appendChild(simple);}else{// 下拉菜单let multip = document.createElement("dl");multip.classList.add("nav-list");let dt = document.createElement("dt");dt.innerHTML = "<i class='icon'></i><span>" + item.name + "</span>";navBox.appendChild(multip);multip.appendChild(dt);item.children.forEach(child=>{let dd = document.createElement("dd");dd.classList.add("multip");dd.innerHTML = "<span>" + child.name + "</span>";dd.dataset.to = child.to;multip.appendChild(dd);});}});},style:{setLogoBgColor:color=>{document.querySelector(".logo-box").style["backgroundColor"] = color;}},initRoutes:()=>{const links = document.querySelectorAll("[data-to]");const frame = document.querySelector("#obj");const foundPath = itemTo => {let path = "";routes.forEach(item=>{if(!item.children){if(item.to==itemTo){path = "pages" + item.path;}}else{item.children.forEach(child=>{if(child.to==itemTo){path = "pages" + child.path;}});}});return path;};links.forEach(item=>{item.addEventListener("click",e=>{frame.data = foundPath(item.dataset.to);});});}
}
我这个写的有点乱,但是,我们可以清晰的看到,如果想要调整某部分的样式,例如:Logo的背景颜色,我们只需要在script标签中这样写:
martApp.style.setLogoBgColor("#012345");
因为我们这个是初步的模型,所以,我们没有编写那么多的方法,以后会完善的,现在我们看一下初步的执行顺序:
martApp.initNavList();
martApp.initRoutes()
martApp.startApp();
这样,我们的页面就跑起来了,但是,好像忘记一个重点,没有样式呀!
css献上,因为东西都简单,所以就不适用预处理css了,大伙将就着看。
*{margin: 0;padding: 0;box-sizing: border-box;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;font-family: "comic sans ms","microsoft yahei";
}
body{width: 100vw;height: 100vh;background-color: #F0F0F0;
}
.box{width: 100%;height: 100%;
}
.header-box{width: 100%;height: 60px;display: flex;box-shadow: 0 5px 10px rgba(0,0,0,.5);background-color: #FFFFFF;
}
.logo-box{width: 200px;height: 100%;background-color: #002548;padding: 16px 28px;
}
.logo{width: 100%;height: 100%;background-color: rgba(255,255,255,.2);display: flex;align-items: center;justify-content: center;color: #FFFFFF;
}
.tool-box{width: calc(100% - 200px);height: 100%;display: flex;align-items: center;justify-content: space-between;padding: 0 24px;
}
.control-box{height: 48px;
}
.info-box{height: 48px;
}
.content-box{width: 100%;height: calc(100% - 60px);display: flex;
}
.nav-box{width: 200px;height: 100%;background-color: #002548;
}
dl.nav-list{width: 100%;height: auto;
}
dl.nav-list>dt{width: 100%;height: 40px;color: rgba(255,255,255,.65);font-size: 14px;display: flex;align-items: center;padding: 0 34px 0 24px;transition-duration: .3s;cursor: pointer;
}
dl.nav-list>dt>i.icon{display: flex;align-items: center;justify-content: center;width: 14px;height: 14px;font-size: 14px;margin-right: 10px;
}
dl.nav-list>dt:hover{color: #FFFFFF;
}
dl.nav-list.active>dt{color: #FFFFFF;
}
dl.nav-list>dd{width: 100%;height: 0;overflow: hidden;font-size: 14px;color: rgba(255,255,255,.65);display: flex;align-items: center;padding: 0 16px 0 48px;background-color: #000000;transition-duration: .3s;cursor: pointer;
}
dl.nav-list.active>dd{height: 40px;
}
dl.nav-list.active>dd:hover{color: #FFFFFF;
}
dl.nav-list.active>dd.ajok{background-color: #1890ff;color: #FFFFFF;
}
.simple-nav{width: 100%;height: 40px;font-size: 14px;display: flex;align-items: center;padding: 0 34px 0 24px;transition-duration: .3s;cursor: pointer;color: rgba(255,255,255,.65);
}
.simple-nav>i.icon{display: flex;align-items: center;justify-content: center;width: 14px;height: 14px;font-size: 14px;margin-right: 10px;
}
.simple-nav:hover{color: #FFFFFF;
}
.simple-nav.ajok{background-color: #1890ff;color: #FFFFFF;
}
.main-box{width: calc(100% - 200px);height: 100%;padding: 12px 24px;
}
.frame{width: 100%;height: 100%;background-color: #FFFFFF;box-shadow: 0 5px 10px rgba(0,0,0,.5);
}
效果图奉上:
本篇文章就先到这里,如果有什么建议,请在评论区留言,大神们勿喷,这是自己想法的一个实践。
接下来会去封装组件,配合后台去写傻瓜式组件!
感谢您的阅读,下期见!!!