首页布局一般由三个部分组成,头部(header)、左侧边栏(sidebar)、主内容(main)
一、编写首页组件
1.编写一些需要的配置
1.1 通过 Event Bus 进行组件间通信,来折叠侧边栏;在components下创建common目录,在目录下创建bus.js
bus.js
// srccomponentscommonus.js import Vue from 'vue'; // 使用 Event Bus const bus = new Vue(); export default bus;
1.2 编写全局css样式;在assets下创建css目录,在目录下创建color-dark.css和main.css
color-dark.css
1 /* srcassetscsscolor-dark.css */ 2 .header{ 3 background-color: #242f42; 4 } 5 .login-wrap{ 6 background: #324157; 7 } 8 .plugins-tips{ 9 background: #eef1f6; 10 } 11 .plugins-tips a{ 12 color: #20a0ff; 13 } 14 .el-upload--text em { 15 color: #20a0ff; 16 } 17 .pure-button{ 18 background: #20a0ff; 19 } 20 .tags-li.active { 21 border: 1px solid #409EFF; 22 background-color: #409EFF; 23 } 24 .message-title{ 25 color: #20a0ff; 26 } 27 .collapse-btn:hover{ 28 background: rgb(40,52,70); 29 }
main.css
1 /* srcassetscssmain.css */ 2 * { 3 margin: 0; 4 padding: 0; 5 } 6 7 html, 8 body, 9 #app, 10 .wrapper { 11 width: 100%; 12 height: 100%; 13 overflow: hidden; 14 } 15 16 body { 17 font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif; 18 } 19 20 a { 21 text-decoration: none 22 } 23 24 25 .content-box { 26 position: absolute; 27 left: 250px; 28 right: 0; 29 top: 60px; 30 bottom: 0; 31 padding-bottom: 30px; 32 -webkit-transition: left .3s ease-in-out; 33 transition: left .3s ease-in-out; 34 background: #f0f0f0; 35 } 36 37 .content { 38 width: auto; 39 height: 100%; 40 padding: 10px; 41 overflow-y: scroll; 42 box-sizing: border-box; 43 } 44 45 .content-collapse { 46 left: 65px; 47 } 48 49 .container { 50 padding: 30px; 51 background: #fff; 52 border: 1px solid #ddd; 53 border-radius: 5px; 54 } 55 56 .crumbs { 57 margin: 10px 0; 58 } 59 60 .el-table th { 61 background-color: #f5f7fa !important; 62 } 63 64 .pagination { 65 margin: 20px 0; 66 text-align: right; 67 } 68 69 .plugins-tips { 70 padding: 20px 10px; 71 margin-bottom: 20px; 72 } 73 74 .el-button+.el-tooltip { 75 margin-left: 10px; 76 } 77 78 .el-table tr:hover { 79 background: #f6faff; 80 } 81 82 .mgb20 { 83 margin-bottom: 20px; 84 } 85 86 .move-enter-active, 87 .move-leave-active { 88 transition: opacity .5s; 89 } 90 91 .move-enter, 92 .move-leave { 93 opacity: 0; 94 } 95 96 /*BaseForm*/ 97 98 .form-box { 99 width: 600px; 100 } 101 102 .form-box .line { 103 text-align: center; 104 } 105 106 .el-time-panel__content::after, 107 .el-time-panel__content::before { 108 margin-top: -7px; 109 } 110 111 .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) { 112 padding-bottom: 0; 113 }
1.3 修改App.vue
// srcApp.vue <template> <div id="app"> <router-view></router-view> </div> </template> <script> </script> <style> @import "./assets/css/main.css"; @import "./assets/css/color-dark.css"; </style>
2. 编写布局头部(Header),在srccomponentscommon目录下创建Header.vue文件
1 // srccomponentscommonHeader.vue 2 <template> 3 <div class="header"> 4 <!-- 折叠按钮 --> 5 <div class="collapse-btn" @click="collapseChage"> 6 <i v-if="!collapse" class="el-icon-s-fold"></i> 7 <i v-else class="el-icon-s-unfold"></i> 8 </div> 9 <div class="logo">自动化测试平台</div> 10 <div class="header-right"> 11 <div class="header-user-con"> 12 <!-- 全屏显示 --> 13 <div class="btn-fullscreen" @click="handleFullScreen"> 14 <el-tooltip effect="dark" :content="fullscreen?`取消全屏`:`全屏`" placement="bottom"> 15 <i class="el-icon-rank"></i> 16 </el-tooltip> 17 </div> 18 <!-- 用户头像 --> 19 <div class="user-avator"> 20 <!-- <img src="../../assets/img/img.jpg" /> --> 21 </div> 22 <!-- 用户名下拉菜单 --> 23 <el-dropdown class="user-name" trigger="click" @command="handleCommand"> 24 <span class="el-dropdown-link"> 25 {{username}} 26 <i class="el-icon-caret-bottom"></i> 27 </span> 28 <el-dropdown-menu slot="dropdown"> 29 <el-dropdown-item divided command="loginout">退出登录</el-dropdown-item> 30 </el-dropdown-menu> 31 </el-dropdown> 32 </div> 33 </div> 34 </div> 35 </template> 36 <script> 37 import bus from '../common/bus'; 38 export default { 39 data() { 40 return { 41 collapse: false, 42 fullscreen: false, 43 name: '普通用户', 44 message: 2 45 }; 46 }, 47 computed: { 48 username() { 49 let username = localStorage.getItem('ms_username'); 50 return username ? username : this.name; 51 } 52 }, 53 methods: { 54 // 用户名下拉菜单选择事件 55 handleCommand(command) { 56 if (command == 'loginout') { 57 localStorage.removeItem('ms_username'); 58 this.$router.push('/'); 59 } 60 }, 61 // 侧边栏折叠 62 collapseChage() { 63 this.collapse = !this.collapse; 64 bus.$emit('collapse', this.collapse); 65 }, 66 // 全屏事件 67 handleFullScreen() { 68 let element = document.documentElement; 69 if (this.fullscreen) { 70 if (document.exitFullscreen) { 71 document.exitFullscreen(); 72 } else if (document.webkitCancelFullScreen) { 73 document.webkitCancelFullScreen(); 74 } else if (document.mozCancelFullScreen) { 75 document.mozCancelFullScreen(); 76 } else if (document.msExitFullscreen) { 77 document.msExitFullscreen(); 78 } 79 } else { 80 if (element.requestFullscreen) { 81 element.requestFullscreen(); 82 } else if (element.webkitRequestFullScreen) { 83 element.webkitRequestFullScreen(); 84 } else if (element.mozRequestFullScreen) { 85 element.mozRequestFullScreen(); 86 } else if (element.msRequestFullscreen) { 87 // IE11 88 element.msRequestFullscreen(); 89 } 90 } 91 this.fullscreen = !this.fullscreen; 92 } 93 }, 94 mounted() { 95 if (document.body.clientWidth < 1500) { 96 this.collapseChage(); 97 } 98 } 99 }; 100 </script> 101 <style scoped> 102 .header { 103 position: relative; 104 box-sizing: border-box; 105 100%; 106 height: 60px; 107 font-size: 22px; 108 color: #fff; 109 } 110 .collapse-btn { 111 float: left; 112 padding: 0 21px; 113 cursor: pointer; 114 line-height: 60px; 115 } 116 .header .logo { 117 float: left; 118 250px; 119 line-height: 60px; 120 } 121 .header-right { 122 float: right; 123 padding-right: 50px; 124 } 125 .header-user-con { 126 display: flex; 127 height: 70px; 128 align-items: center; 129 } 130 .btn-fullscreen { 131 transform: rotate(45deg); 132 margin-right: 5px; 133 font-size: 24px; 134 } 135 .btn-bell, 136 .btn-fullscreen { 137 position: relative; 138 30px; 139 height: 30px; 140 text-align: center; 141 border-radius: 15px; 142 cursor: pointer; 143 } 144 .btn-bell-badge { 145 position: absolute; 146 right: 0; 147 top: -2px; 148 8px; 149 height: 8px; 150 border-radius: 4px; 151 background: #f56c6c; 152 color: #fff; 153 } 154 .btn-bell .el-icon-bell { 155 color: #fff; 156 } 157 .user-name { 158 margin-left: 10px; 159 } 160 .user-avator { 161 margin-left: 20px; 162 } 163 .user-avator img { 164 display: block; 165 40px; 166 height: 40px; 167 border-radius: 50%; 168 } 169 .el-dropdown-link { 170 color: #fff; 171 cursor: pointer; 172 } 173 .el-dropdown-menu__item { 174 text-align: center; 175 } 176 </style>
3. 编写布局左侧边栏(Sidebar),在srccomponentscommon目录下创建Sidebar.vue文件
1 // srccomponentscommonSidebar.vue 2 <template> 3 <div class="sidebar"> 4 <el-menu 5 class="sidebar-el-menu" 6 :default-active="onRoutes" 7 :collapse="collapse" 8 background-color="#324157" 9 text-color="#bfcbd9" 10 active-text-color="#20a0ff" 11 unique-opened 12 router 13 > 14 <template v-for="item in items"> 15 <template v-if="item.subs"> 16 <el-submenu :index="item.index" :key="item.index"> 17 <template slot="title"> 18 <i :class="item.icon"></i> 19 <span slot="title">{{ item.title }}</span> 20 </template> 21 <template v-for="subItem in item.subs"> 22 <el-submenu 23 v-if="subItem.subs" 24 :index="subItem.index" 25 :key="subItem.index" 26 > 27 <template slot="title">{{ subItem.title }}</template> 28 <el-menu-item 29 v-for="(threeItem,i) in subItem.subs" 30 :key="i" 31 :index="threeItem.index" 32 >{{ threeItem.title }}</el-menu-item> 33 </el-submenu> 34 <el-menu-item 35 v-else 36 :index="subItem.index" 37 :key="subItem.index" 38 >{{ subItem.title }}</el-menu-item> 39 </template> 40 </el-submenu> 41 </template> 42 <template v-else> 43 <el-menu-item :index="item.index" :key="item.index"> 44 <i :class="item.icon"></i> 45 <span slot="title">{{ item.title }}</span> 46 </el-menu-item> 47 </template> 48 </template> 49 </el-menu> 50 </div> 51 </template> 52 53 <script> 54 import bus from '../common/bus'; 55 export default { 56 data() { 57 return { 58 collapse: false, 59 items: [ 60 { 61 icon: 'el-icon-s-home', 62 index: '/dashboard', 63 title: '系统首页' 64 }, 65 ] 66 }; 67 }, 68 computed: { 69 onRoutes() { 70 return this.$route.path.replace('/', ''); 71 } 72 }, 73 created() { 74 // 通过 Event Bus 进行组件间通信,来折叠侧边栏 75 bus.$on('collapse', msg => { 76 this.collapse = msg; 77 bus.$emit('collapse-content', msg); 78 }); 79 } 80 }; 81 </script> 82 83 <style scoped> 84 .sidebar { 85 display: block; 86 position: absolute; 87 left: 0; 88 top: 60px; 89 bottom: 0; 90 overflow-y: scroll; 91 } 92 .sidebar::-webkit-scrollbar { 93 0; 94 } 95 .sidebar-el-menu:not(.el-menu--collapse) { 96 250px; 97 } 98 .sidebar > ul { 99 height: 100%; 100 } 101 </style>
4. 编写一个tab栏方便页面切换,在srccomponentscommon目录下创建Tabs.vue文件
1 // srccomponentscommonTags.vue 2 <template> 3 <div class="tags" v-if="showTags"> 4 <ul> 5 <li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index"> 6 <router-link :to="item.path" class="tags-li-title"> 7 {{item.title}} 8 </router-link> 9 <span class="tags-li-icon" @click="closeTags(index)"><i class="el-icon-close"></i></span> 10 </li> 11 </ul> 12 <div class="tags-close-box"> 13 <el-dropdown @command="handleTags"> 14 <el-button size="mini" type="primary"> 15 标签选项<i class="el-icon-arrow-down el-icon--right"></i> 16 </el-button> 17 <el-dropdown-menu size="small" slot="dropdown"> 18 <el-dropdown-item command="other">关闭其他</el-dropdown-item> 19 <el-dropdown-item command="all">关闭所有</el-dropdown-item> 20 </el-dropdown-menu> 21 </el-dropdown> 22 </div> 23 </div> 24 </template> 25 26 <script> 27 import bus from './bus'; 28 export default { 29 data() { 30 return { 31 tagsList: [] 32 } 33 }, 34 methods: { 35 isActive(path) { 36 return path === this.$route.fullPath; 37 }, 38 // 关闭单个标签 39 closeTags(index) { 40 const delItem = this.tagsList.splice(index, 1)[0]; 41 const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1]; 42 if (item) { 43 delItem.path === this.$route.fullPath && this.$router.push(item.path); 44 }else{ 45 this.$router.push('/home'); 46 } 47 }, 48 // 关闭全部标签 49 closeAll(){ 50 this.tagsList = []; 51 this.$router.push('/home'); 52 }, 53 // 关闭其他标签 54 closeOther(){ 55 const curItem = this.tagsList.filter(item => { 56 return item.path === this.$route.fullPath; 57 }) 58 this.tagsList = curItem; 59 }, 60 // 设置标签 61 setTags(route){ 62 const isExist = this.tagsList.some(item => { 63 return item.path === route.fullPath; 64 }) 65 if(!isExist){ 66 if(this.tagsList.length >= 8){ 67 this.tagsList.shift(); 68 } 69 this.tagsList.push({ 70 title: route.meta.title, 71 path: route.fullPath, 72 name: route.matched[1].components.default.name 73 }) 74 } 75 bus.$emit('tags', this.tagsList); 76 }, 77 handleTags(command){ 78 command === 'other' ? this.closeOther() : this.closeAll(); 79 } 80 }, 81 computed: { 82 showTags() { 83 return this.tagsList.length > 0; 84 } 85 }, 86 watch:{ 87 $route(newValue, oldValue){ 88 this.setTags(newValue); 89 } 90 }, 91 created(){ 92 this.setTags(this.$route); 93 // 监听关闭当前页面的标签页 94 bus.$on('close_current_tags', () => { 95 for (let i = 0, len = this.tagsList.length; i < len; i++) { 96 const item = this.tagsList[i]; 97 if(item.path === this.$route.fullPath){ 98 if(i < len - 1){ 99 this.$router.push(this.tagsList[i+1].path); 100 }else if(i > 0){ 101 this.$router.push(this.tagsList[i-1].path); 102 }else{ 103 this.$router.push('/home'); 104 } 105 this.tagsList.splice(i, 1); 106 break; 107 } 108 } 109 }) 110 } 111 } 112 113 </script> 114 115 116 <style> 117 .tags { 118 position: relative; 119 height: 30px; 120 overflow: hidden; 121 background: #fff; 122 padding-right: 120px; 123 box-shadow: 0 5px 10px #ddd; 124 top: 0; 125 } 126 127 .tags ul { 128 box-sizing: border-box; 129 100%; 130 height: 100%; 131 } 132 133 .tags-li { 134 float: left; 135 margin: 3px 5px 2px 3px; 136 border-radius: 3px; 137 font-size: 12px; 138 overflow: hidden; 139 cursor: pointer; 140 height: 23px; 141 line-height: 23px; 142 border: 1px solid #e9eaec; 143 background: #fff; 144 padding: 0 5px 0 12px; 145 vertical-align: middle; 146 color: #666; 147 -webkit-transition: all .3s ease-in; 148 -moz-transition: all .3s ease-in; 149 transition: all .3s ease-in; 150 } 151 152 .tags-li:not(.active):hover { 153 background: #f8f8f8; 154 } 155 156 .tags-li.active { 157 color: #fff; 158 } 159 160 .tags-li-title { 161 float: left; 162 max- 80px; 163 overflow: hidden; 164 white-space: nowrap; 165 text-overflow: ellipsis; 166 margin-right: 5px; 167 color: #666; 168 } 169 170 .tags-li.active .tags-li-title { 171 color: #fff; 172 } 173 174 .tags-close-box { 175 position: absolute; 176 right: 0; 177 top: 0; 178 box-sizing: border-box; 179 padding-top: 1px; 180 text-align: center; 181 110px; 182 height: 30px; 183 background: #fff; 184 box-shadow: -3px 0 15px 3px rgba(0, 0, 0, .1); 185 z-index: 10; 186 } 187 188 </style>
5. 将这些组件拼到一块,在srccomponentscommon目录下创建Home.vue文件
1 // srccomponentscommonHome.vue 2 <template> 3 <div class="wrapper"> 4 <v-head></v-head> 5 <v-sidebar></v-sidebar> 6 <div class="content-box" :class="{'content-collapse':collapse}"> 7 <v-tags></v-tags> 8 <div class="content"> 9 <transition name="move" mode="out-in"> 10 <keep-alive :include="tagsList"> 11 <router-view></router-view> 12 </keep-alive> 13 </transition> 14 <el-backtop target=".content"></el-backtop> 15 </div> 16 </div> 17 </div> 18 </template> 19 20 <script> 21 import vHead from './Header.vue'; 22 import vSidebar from './Sidebar.vue'; 23 import vTags from './Tags.vue'; 24 import bus from './bus'; 25 export default { 26 data() { 27 return { 28 tagsList: [], 29 collapse: false 30 }; 31 }, 32 components: { 33 vHead, 34 vSidebar, 35 vTags 36 }, 37 created() { 38 bus.$on('collapse-content', msg => { 39 this.collapse = msg; 40 }); 41 42 // 只有在标签页列表里的页面才使用keep-alive,即关闭标签之后就不保存到内存中了。 43 bus.$on('tags', msg => { 44 let arr = []; 45 for (let i = 0, len = msg.length; i < len; i++) { 46 msg[i].name && arr.push(msg[i].name); 47 } 48 this.tagsList = arr; 49 }); 50 } 51 }; 52 </script>
6. 编写系统首页,数据等项目完善后补全;在srccomponentspage下创建Dashboard.vue文件
1 // srccomponentspageDashboard.vue 2 <template> 3 <div> 4 <el-row :gutter="20"> 5 <el-col :span="8"> 6 <el-card shadow="hover" style="height:252px;"> 7 <div slot="header" class="clearfix"> 8 <span>用例执行概况</span> 9 </div> 10 通过<el-progress :percentage=success_rate color="#42b983"></el-progress> 11 失败<el-progress :percentage=fail_rate color="#f56c6c"></el-progress> 12 </el-card> 13 </el-col> 14 <el-col :span="16"> 15 <el-row :gutter="20" class="mgb20"> 16 <el-col :span="8"> 17 <el-card shadow="hover" :body-style="{padding: '0px'}"> 18 <div class="grid-content grid-con-1"> 19 <i class="el-icon-s-cooperation grid-con-icon"></i> 20 <div class="grid-cont-right"> 21 <div class="grid-num">{{ projects_count }}</div> 22 <div>项目数量</div> 23 </div> 24 </div> 25 </el-card> 26 </el-col> 27 <el-col :span="8"> 28 <el-card shadow="hover" :body-style="{padding: '0px'}"> 29 <div class="grid-content grid-con-2"> 30 <i class="el-icon-menu grid-con-icon"></i> 31 <div class="grid-cont-right"> 32 <div class="grid-num">{{ modulars_count }}</div> 33 <div>模块数量</div> 34 </div> 35 </div> 36 </el-card> 37 </el-col> 38 <el-col :span="8"> 39 <el-card shadow="hover" :body-style="{padding: '0px'}"> 40 <div class="grid-content grid-con-2"> 41 <i class="el-icon-s-grid grid-con-icon"></i> 42 <div class="grid-cont-right"> 43 <div class="grid-num">{{ testcases_count }}</div> 44 <div>用例数量</div> 45 </div> 46 </div> 47 </el-card> 48 </el-col> 49 <el-col :span="8"> 50 <el-card shadow="hover" :body-style="{padding: '0px'}"> 51 <div class="grid-content grid-con-2"> 52 <i class="el-icon-ship grid-con-icon"></i> 53 <div class="grid-cont-right"> 54 <div class="grid-num">{{ envs_count }}</div> 55 <div>运行环境数</div> 56 </div> 57 </div> 58 </el-card> 59 </el-col> 60 <el-col :span="8"> 61 <el-card shadow="hover" :body-style="{padding: '0px'}"> 62 <div class="grid-content grid-con-2"> 63 <i class="el-icon-message grid-con-icon"></i> 64 <div class="grid-cont-right"> 65 <div class="grid-num">{{ reports_count }}</div> 66 <div>测试报告数</div> 67 </div> 68 </div> 69 </el-card> 70 </el-col> 71 </el-row> 72 </el-col> 73 </el-row> 74 </div> 75 </template> 76 77 <script> 78 import bus from '../common/bus'; 79 export default { 80 name: 'dashboard', 81 data() { 82 return { 83 projects_count:999, 84 modulars_count:999, 85 testcases_count:999, 86 envs_count:999, 87 reports_count:999, 88 success_rate:90, 89 fail_rate:10, 90 }; 91 }, 92 }; 93 </script> 94 95 96 <style scoped> 97 .el-row { 98 margin-bottom: 20px; 99 } 100 101 .grid-content { 102 display: flex; 103 align-items: center; 104 height: 100px; 105 } 106 107 .grid-cont-right { 108 flex: 1; 109 text-align: center; 110 font-size: 14px; 111 color: #999; 112 } 113 114 .grid-num { 115 font-size: 30px; 116 font-weight: bold; 117 } 118 119 .grid-con-icon { 120 font-size: 50px; 121 100px; 122 height: 100px; 123 text-align: center; 124 line-height: 100px; 125 color: #fff; 126 } 127 128 .grid-con-1 .grid-con-icon { 129 background: rgb(45, 140, 240); 130 } 131 132 .grid-con-1 .grid-num { 133 color: rgb(45, 140, 240); 134 } 135 136 .grid-con-2 .grid-con-icon { 137 background: rgb(242, 94, 67); 138 } 139 140 .grid-con-2 .grid-num { 141 color: rgb(242, 94, 67); 142 } 143 144 </style>
二、编写组件路由
1 // src outerindex.js 2 import Vue from 'vue' 3 import Router from 'vue-router' 4 5 Vue.use(Router) 6 7 export default new Router({ 8 routes: [ 9 { 10 path: '/', 11 name: '/login', 12 component: () => import('../components/page/login.vue'), 13 meta: { title: '登录' } 14 }, 15 { 16 path: '/home', 17 redirect: '/dashboard' 18 }, 19 { 20 path: '/', 21 component: () => import('../components/common/Home.vue'), 22 meta: { title: '自述文件' }, 23 children: [ 24 { 25 // 系统主页 26 path: '/dashboard', 27 component: () => import('../components/page/Dashboard.vue'), 28 meta: { title: '系统首页' } 29 }, 30 ] 31 } 32 ] 33 })
三、测试
GitHub持续更新:https://github.com/debugf/vueplatform
转载请注明出处,商用请征得作者本人同意,谢谢!!!