前言
前面一段時間公司里忙得很晚痊剖,睡覺時間都不太夠了韩玩,就隔了幾天。服務端的用戶系統(tǒng)已經(jīng)完成÷侥伲現(xiàn)在來一點前端的內(nèi)容找颓。我覺得前后端分離就可以各開發(fā)各的。相互之間用某種協(xié)議進行交互叮贩,整合击狮。我的理解整個平臺會由N個服務端項目、N個前端項目益老、加一些中間件構成彪蓬。前端可能有WEB頁面、手機APP杨箭、微信公眾號頁面寞焙、H5頁面等等。
這里我們開始做PC端的后臺管理頁面互婿。我把PC端分為前臺捣郊、后臺、登錄三個項目慈参。目前先這樣呛牲,后續(xù)可能還會根據(jù)實際情況細分。幾個項目都是Vue項目驮配,就把它們合到一個工程中進行娘扩,省去共用模塊、組件的重復管理壮锻。這個工程就需要多入口的Vue工程琐旁。
創(chuàng)建項目
因為我已經(jīng)寫過Vue多入口項目創(chuàng)建的文章,這里就不再重復了猜绣。移步vue多入口項目灰殴。項目名稱huip。git地址:https://gitee.com/biboheart/huip-vue.git
創(chuàng)建完成后的目錄結(jié)構:
引入圖標
在應用型前端中掰邢,圖標會用的比較多牺陶。fontawesome圖標庫中的免費圖標是比較豐富的伟阔。只是npm中最高版本到4.7.2就沒有再更新。而官網(wǎng)已經(jīng)到了5.1.0 掰伸。所以我選擇下載到本地靜態(tài)引入的方法使用5.1.0(哪位朋友有好的方法還忘告知)皱炉。
從官網(wǎng)下載,拷貝到static目錄中狮鸭。在index.html文件中引入css
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link href="static/fontawesome/css/fontawesome-all.min.css" rel="stylesheet">
<title>huip</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
引入前端框架
我采用element-ui作為vue前端框架合搅。里面的組件比較豐富。包含了常用的管理組件了歧蕉。
cnpm i element-ui -S
在三個main.js加入element-ui庫历筝,完成后的main.js內(nèi)容如下
import Vue from 'vue'
import App from '@/components/App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ElementUI)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
界面布局
在這個工程中,各項目(除了登錄)的布局都是基本相同的廊谓。因此,需要一個公共的布局組件麻削。利用element的Container 布局容器進行開發(fā)蒸痹。
element-ui的NavMenu 導航菜單,官方提供的每個菜單項是需要自己去生成的呛哟,并不能通過樹型結(jié)構的數(shù)據(jù)生成出整個菜單叠荠。因此,我們先來寫個組件扫责,遞歸生成樹型菜單榛鼎。
以下是一個菜單項的組件代碼。
<template>
<component v-bind:is="currentItemComponent" :index="menu.path" :key="menu.path">
<i :class="menu.icon" v-if="menu.icon && !hc"></i><span v-if="!hc">{{menu.text}}</span>
<template slot="title" v-if="hc">
<i :class="menu.icon" v-if="menu.icon"></i><span slot="title">{{menu.text}}</span>
</template>
<tree-menu-item :menu="child" :key="child.name" v-for="child in menu.children" v-if="hc && child.menu"></tree-menu-item>
</component>
</template>
<script>
export default {
name: 'TreeMenuItem',
props: {
menu: Object
},
data () {
return {
hc: false
}
},
computed: {
currentItemComponent: function () {
return this.hasChildren() ? 'el-submenu' : 'el-menu-item'
}
},
methods: {
hasChildren () {
this.hc = !!this.menu.hasChildren && this.menu.children && this.menu.children.length > 0
return this.hc
}
}
}
</script>
<style scoped>
</style>
在公共布局組件中l(wèi)ayout.vue中使用它:
<script>
import TreeMenuItem from '../packages/tree-menu/index.js'
export default {
name: 'HuipLayout',
components: {
TreeMenuItem
},
props: {
hmenus: Array,
vmenus: Array,
tips: Array,
logoSrc: String,
badgeValue: [String, Number]
},
data: function () {
return {
isCollapse: false,
asideWidth: '230px',
vDefActive: this.activePath(3),
hDefActive: this.activePath(2),
defaultLogo: require('@/assets/logo.png')
}
},
watch: {
'$route': function (val) {
this.vDefActive = this.activePath(3)
this.hDefActive = this.activePath(2)
}
},
methods: {
collapseChange: function () {
this.isCollapse = !this.isCollapse
this.$emit('changeCollapse', this.isCollapse)
if (this.isCollapse) {
this.asideWidth = '65px'
} else {
this.asideWidth = '230px'
}
},
activePath: function (max) {
let pathArr = this.$route.path.split('/')
let def = ''
if (max < 2) {
def = this.$route.path
} else {
if (pathArr && pathArr.length > max) {
for (let i = 1; i < max; i++) {
def += '/' + pathArr[i]
}
} else {
def = this.$route.path
}
}
return def
},
clickLogo: function (...args) {
this.$emit('logo-click', ...args)
}
}
}
</script>
<template>
<el-container>
<el-header class="layout-main-header clear">
<!-- logo -->
<div class="logo" @click="clickLogo">
<img :src="logoSrc" v-if="logoSrc"/>
<img :src="defaultLogo" v-else/>
</div>
<!-- 右側(cè)菜單 -->
<div class="tool-body">
<div class="user-info-cell">
<el-badge :value="badgeValue" class="user-info-badge" v-if="badgeValue"></el-badge>
<slot name="user-info"></slot>
</div>
<ul class="tips-box clearfix">
<li class="tips-item" v-for="item in tips" :key="item.name" v-if="!!tips && tips.length > 0">
<a :href="item.url" :target="item.target || '_self'">{{item.text}}</a>
</li>
</ul>
</div>
<!-- 如果有頂菜單則顯示頂菜單 -->
<div class="hmenu-wrap" v-if="hmenus && hmenus.length > 0">
<el-menu
:default-active="hDefActive"
unique-opened
router
mode="horizontal"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<tree-menu-item :menu="item" :key="item.name" v-for="item in hmenus" v-if="item.menu"></tree-menu-item>
</el-menu>
</div>
</el-header>
<!-- 如果提供了左側(cè)菜單的數(shù)據(jù)則顯示左側(cè)aside -->
<el-container v-if="vmenus && vmenus.length > 0">
<el-aside class="layout-main-aside" :width="asideWidth">
<div class="aside-container">
<div class="aside-header">
<div class="tools" @click.prevent="collapseChange">
<i :class="isCollapse ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'"></i>
</div>
</div>
<div class="aside-main aside-main-hasheader">
<el-scrollbar class="full-scrollbar">
<div :class="isCollapse ? 'menu-collapsed' : 'menu-expanded'">
<el-menu
:default-active="vDefActive"
class="el-menu-vertical-demo vmenu"
unique-opened
router
:collapse="isCollapse">
<tree-menu-item :menu="item" :key="item.name" v-for="item in vmenus" v-if="item.menu"></tree-menu-item>
</el-menu>
</div>
</el-scrollbar>
</div>
</div>
</el-aside>
<el-container>
<slot></slot>
</el-container>
</el-container>
<slot v-else></slot>
</el-container>
</template>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.layout-main-header {
background-color: #545c64;
color: #333;
line-height: 60px;
text-align: left;
font-size: 12px;
}
.layout-main-aside {
color: #333;
background-color: rgb(238, 241, 246);
}
.logo {
height:60px;
width:auto;
font-size: 12px;
padding-right:20px;
border-color: rgba(238,238,238,0.3);
border-right-width: 0px;
border-right-style: solid;
color: #fff;
float: left;
box-sizing: border-box;
cursor: pointer;
}
.logo img {
width: 40px;
height: 40px;
margin: 10px 10px 10px 10px;
}
.tool-body {
height: 60px;
width: auto;
float: right;
box-sizing: border-box;
text-align: right;
padding: 0;
}
.user-info-cell {
position: relative;
height:60px;
width:auto;
font-size: 12px;
border-color: rgba(238,238,238,0.3);
border-style: solid;
border-width: 0px;
border-left-width: 1px;
color: #fff;
float: right;
box-sizing: border-box;
}
.user-info-badge {
position: absolute;
top: -10px;
right: -10px;
}
.tool-body ul,
.tool-body li {
padding: 0;
margin: 0;
list-style: outside none none;
}
.tips-box {
float: right;
display: inline;
}
.tips-box .tips-item {
box-sizing: border-box;
float: left;
height: 60px;
line-height: 60px;
margin-right: 22px;
text-align: left;
}
.tips-box .tips-item a {
font-size: 14px;
text-decoration: none;
color: #bfcbd9;
}
.tips-box .tips-item a:hover {
color: #00a2ca;
}
.hmenu-wrap {
display: block;
float: left;
height: 60px;
line-height: 60px;
}
.tools{
width: 100%;
line-height: 44px;
cursor: pointer;
text-align: center;
font-size: 14px;
background: #e4e8f1;
}
.menu-expanded .vmenu {
position: relative;
}
.menu-collapsed .vmenu {
position: fixed;
z-index: 8888;
}
</style>
完成布局
/src/project/index/pages下創(chuàng)建index.vue文件鳖孤,這是前臺頁面的主體組件
<script>
import HuipLayout from '@/components/HuipLayout'
export default {
name: 'AdminMain',
components: {
HuipLayout
},
data () {
return {
defaultLogo: require('@/assets/logo.png'),
user: {
name: 'admin'
},
tips: [{
text: '控制臺',
name: 'admin',
url: '/admin.html'
}],
hmenus: [{
path: '/dashboard',
name: 'dashboard',
text: '儀表盤',
menu: true,
icon: 'fas fa-tachometer-alt'
}, {
path: '/core',
name: 'core',
text: '系統(tǒng)配置',
menu: true,
hasChildren: true,
icon: 'fa fa-cogs',
children: [{
path: '/core/app',
name: 'core_app',
text: '版本管理',
menu: true
}, {
path: '/core/reedback',
name: 'core_reedback',
text: '用戶反饋',
menu: true
}]
}, {
path: '/user',
name: 'user',
text: '用戶系統(tǒng)',
menu: true,
hasChildren: true,
icon: 'fa fa-users',
children: [{
path: '/user/client',
name: 'user_client',
text: '客戶端管理',
menu: true
}, {
path: '/user/resource',
name: 'user_resource',
text: '資源管理',
menu: true
}, {
path: '/user/auth',
name: 'user_auth',
text: '權限管理',
menu: true,
hasChildren: true,
children: [{
path: '/user/auth/system',
name: 'user_auth_system',
text: '系統(tǒng)權限',
menu: true
}, {
path: '/user/auth/operation',
name: 'user_auth_operation',
text: '操作權限',
menu: true
}]
}, {
path: '/user/role',
name: 'user_role',
text: '角色管理',
menu: true
}, {
path: '/user/user',
name: 'user_user',
text: '用戶管理',
menu: true
}]
}]
}
}
}
</script>
<template>
<huip-layout :hmenus="hmenus" :tips="tips">
<div v-popover:userInfoPopover class="user-info clear" slot="user-info">
<img v-if="user && user.pic" :src="user.pic" class="headimg">
<img v-else :src="defaultLogo" class="headimg">
</div>
<el-popover
ref="userInfoPopover"
placement="bottom-start"
trigger="hover"
width="240"
:visible-arrow="false"
popper-class="user-info-popover">
<div class="topbar-info-dropdown-memu">
<div class="topbar-user-info">
<img v-if="user && user.pic" :src="user.pic" class="topbar-user-avatar">
<img v-else :src="defaultLogo" class="topbar-user-avatar">
<p class="topbar-user-name">{{user ? user.name : ''}}</p>
</div>
<div class="topbar-user-entrance-list">
<div class="topbar-user-entrance clear">
<i class="fas fa-clipboard-list info-prefix-icon"></i>
<span class="left-text">個人中心</span>
</div>
<div class="topbar-user-entrance clear">
<i class="fas fa-comment-dots info-prefix-icon"></i>
<span class="left-text">消息中心</span>
<span class="right-text">20</span>
</div>
</div>
<div>
<div class="user-btn-list">
<span class="user-btn-link">退出登錄</span>
</div>
</div>
</div>
</el-popover>
<router-view class="content-wrap"></router-view>
</huip-layout>
</template>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.user-info {
cursor: pointer;
color: #bfcbd9;
}
.user-info .headimg {
width: 40px;
height: 40px;
border-radius: 20px;
margin: 10px;
float: left;
}
.topbar-info-dropdown-memu {
padding: 0;
list-style: none;
background-color: #fff;
background-clip: padding-box;
font-size: 12px;
min-width: 100%;
margin: 0;
border: none;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.1);
box-shadow: 0 1px 3px rgba(0,0,0,.2);
white-space: nowrap;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.topbar-info-dropdown-memu a,
.topbar-info-dropdown-memu li,
.topbar-info-dropdown-memu p,
.topbar-info-dropdown-memu span {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
letter-spacing: .02em;
text-decoration: none;
}
.topbar-info-dropdown-memu .topbar-user-info {
text-align: center;
padding-top: 16px;
border-bottom: 1px solid #eaeaea;
}
.topbar-info-dropdown-memu .topbar-user-info .topbar-user-avatar {
width: 36px;
height: 36px;
border-radius: 18px;
vertical-align: middle;
}
.topbar-info-dropdown-memu .topbar-user-info .topbar-user-name {
margin: 8px 0;
}
.topbar-info-dropdown-memu .topbar-user-entrance-list {
overflow: hidden;
width: 240px;
}
.topbar-info-dropdown-memu .topbar-user-entrance {
cursor: pointer;
height: 20px;
line-height: 20px;
padding: 0 16px;
margin: 12px 0;
font-size: 12px;
line-height: 16px;
position: relative;
}
.topbar-info-dropdown-memu .topbar-user-entrance .info-prefix-icon {
width: 16px;
height: 16px;
vertical-align: text-bottom;
margin-right: 8px;
color: #333;
}
.topbar-info-dropdown-memu .topbar-user-entrance .right-text {
float: right;
font-size: 10px;
}
.topbar-info-dropdown-memu .user-btn-link {
cursor: pointer;
height: 50px;
line-height: 50px;
display: block;
-webkit-transition: all .15s;
transition: all .15s;
text-align: center;
color: #333;
background-color: #f5f5f6;
border-top: #eaeaea;
}
</style>
后臺(admin)中也同樣創(chuàng)建一個主體頁面組件者娱。
這里的菜單數(shù)據(jù)是臨時數(shù)據(jù),為了看布局效果苏揣,最終是根據(jù)路由文件生成菜單的黄鳍。
當前效果
前臺頁面:
后臺頁面:
總結(jié)
完成前端頁面總體布局,公共組件開發(fā)平匈。