后台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);
}
效果图奉上:
本篇文章就先到这里,如果有什么建议,请在评论区留言,大神们勿喷,这是自己想法的一个实践。
接下来会去封装组件,配合后台去写傻瓜式组件!
感谢您的阅读,下期见!!!