用Element-UI
做了两个后台,时间长了有点视觉劳累,挑来挑去,入选了某东的AT-UI
,搭建了一个雏形的后台骨架,下面记录一下使用方法(此时应附地址:官网传送门)。
本人是在vue2.0
的基础上搭建,所以,谨记:先搭建好初步的项目文件,再来看下面的介绍。
安装
npm install at-ui
npm install at-ui-style
复制代码
是的,它需要安装两个,而Element
只需要安装一个,AT-UI
的css依赖文件也需要安装, 然后在main.js
中引入就可以了。
import AtComponents from 'at-ui'
import 'at-ui-style' // 引入组件样式
Vue.use(AtComponents)
Vue.config.productionTip = false
复制代码
它封装的组件,嵌套不是很多,有的也是直接在原dom元素上直接添加class的方式,所以从控制台也不难找到其样式的。
由于AT-UI只是基本封装,比如,日期选择器datePicker、树形组件tree等就没有,所以有的控件就需要自行安装,推荐一个vue组件网站:vueExamples。搭建时所需插件及使用方法,会在后面写到。
搭建及插件引入
页面文件建好,本人习惯如下图:
view
目录以文件夹分类svg
目录放入svg图标,图标去阿里矢量库
、fontawesome
等网站找就好了。- 模拟数据的话,图方便,没有装
mock
,在static
目录下新建一个data.js
,把json数据扔进去就好,记得export
出来哦。 - 路由的话,如果涉及到菜单的权限,最好还是用动态路由比较好。
svg
安装
npm i vue-svg-icon -D
复制代码
引入
在main.js
中引入vue-svg-icon
。
import Icon from 'vue-svg-icon/Icon.vue';
Vue.component('icon', Icon);
复制代码
不用管为什么有个Icon.vue路径
,插件封装的时候,通过它导出的。 然后就是目录图中的svg文件夹了,注意它的位置,和assets
并列,新建一个search.svg
, 放入找好的svg
代码:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" id="icon-search" width="100%" height="100%"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"></path></svg>
复制代码
然后在使用的地方直接用就好了。
<icon name="search" scale="18"></icon>
复制代码
jquery
从理论上讲,vue框架理应不需要jquery的,但是为了处理某些特殊的地方,还是引一下吧(除非确实不需要)。
安装
cnpm install jquery -S
复制代码
引入
- 修改
main.js
import $ from 'jquery'
复制代码
- 修改
webpack.base.conf.js
var webpack = require('webpack') //最上面并列添加
module.exports = {
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'jquery':'jquery' //添加该项
}
},
plugins:[ //添加一个plugin是webpack.ProvidePlugin,写到resolve并列的位置
new webpack.ProvidePlugin({
$: 'jquery',
jquery: 'jquery'
})
],
}
复制代码
重启
重启之后,使用方法和原jquery一样。
less
安装
npm i less less-loader --save
npm i sass-resources-loader
复制代码
修改文件
//build/utils.js
function lessResourceLoader() { // 增加全局使用less函数
var loaders = [
cssLoader,
'less-loader',
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, '../src/assets/css/common.less'), //定义全局变量的文件路径
]
}
}
];
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
return {
css: generateLoaders(),
postcss: generateLoaders(),
// less: generateLoaders('less'),//原配置
less: lessResourceLoader(), //更改后的配置
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
复制代码
注意:lessResourceLoader()
要和generateLoaders()
同级!
重启项目
然后就可以在src/assets/css/common.less
文件里写全局less了。
datepicker
安装
npm install vue-datepicker-local
复制代码
使用
<template>
<vue-datepicker-local v-model="time" />
</template>
<script>
import VueDatepickerLocal from 'vue-datepicker-local'
export default {
components: {
VueDatepickerLocal
},
data () {
return {
time: new Date()
}
}
}
</script>
复制代码
具体使用查看详细文档:github传送门
开始
路由初始化
先给一个默认的路由配置:
import Vue from 'vue'
import Router from 'vue-router'
import login from '@/view/login/login.vue';
import notFound from '@/view/404/404.vue';
import layout from '@/view/layout/layout.vue';
Vue.use(Router)
const router = new Router({
routes: [{
path: '/login',
name: '登录',
component: login,
},{
path: '/',
name: '',
component: layout,
children:[{
path:'404',
name:'404',
component:notFound
}]
}, {
path: '*',
redirect: '/404'
}]
})
export default router;
复制代码
由于是使用动态路由,所以就需要动态引入的写法:
- 一般来说,后台管理系统都是左侧菜单控制路由跳转,所以,
src/components/
新建一个leftSlider.vue - 引入的路由格式应该和
router.js
保持一致,也是下面的格式:
{
path: '/',
name: '',
component: layout,
children:[{
path:跳转路径,
name:路由名称,
component:组件
}]
}
复制代码
所以,模拟一个json数据使用就好,后期换成接口读取就行了。
左侧菜单封装如下:
<template>
<at-menu :activeName="$route.name" inline-collapsed router theme="dark">
<template v-for="(item,index) in routeList">
<!--有子路由-->
<at-submenu v-if="item.children.length>1" :key="index" >
<template slot="title">
<i class="icon icon-life-buoy"></i>
{{item.name}}
</template>
<at-menu-item v-for="(it,inde) in item.children" :key="inde" :to="it.path" >
{{it.name}}
</at-menu-item>
</at-submenu>
<!--无子路由-->
<at-menu-item :to="item.children[0].path" v-else :key="index">
<i class="icon icon-life-buoy"></i>
{{item.children[0].name}}
</at-menu-item>
</template>
</at-menu>
</template>
<script>
export default {
computed: {
routeList() {
return this.$store.state.global.routes
},
},
mounted(){
this.openedSubmenu()
},
watch: {
'$route' (to, from) {
this.openedSubmenu()
}
},
methods:{
openedSubmenu(){
let list = this.$store.state.global.routes;
for(var key in list){
for(var jey in list[key].children){
if(list[key].children[jey].name == this.$route.name){
$(".at-menu.at-menu--dark.at-menu--inline>li:eq("+key+")")
.addClass('at-menu__submenu--active at-menu__submenu--opened')
.children('ul').show();
$(".at-menu.at-menu--dark.at-menu--inline>li:eq("+key+")").siblings()
.removeClass('at-menu__submenu--active at-menu__submenu--opened')
.children('ul').hide();
return;
}
}
}
},
}
}
</script>
<style lang="less" scoped>
.at-menu.at-menu--dark.at-menu--inline {
width: 240px;
position: fixed;
height: 100%;
transition: width .28s;
top: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
</style>
复制代码
这边不想用jquery的,但是AT-UI
有一个坑,就是opened
属性不可以动态展开收起,没办法,想要这个效果,所以用了jquery控制它的类名模拟这个效果。
注意一点,路由引入只是数据引入,然而组件是需要引入文件而不是单纯的数据的, 所以:src/utils
新建lazyload.js
和leftSliderRoute.js
。
//lazyload.js
export default (folder,name) => () => import(`@/view/${folder}/${name}.vue`)
//leftSliderRoute.js
import lazyload from './lazyload.js';
export default (routes, data) => {
generaMenu(routes, data)
}
function generaMenu(routers, data) {
if (data) {
data.forEach((item) => {
let menu = Object.assign({}, item);
let folder = componentFromFolder(menu.component);
menu.component = lazyload(folder, menu.component)
if (item.children) {
menu.children = []
generaMenu(menu.children, item.children)
}
routers.push(menu)
})
}
}
function componentFromFolder(val) {
switch (val) {
case 'agentList':
case 'agency':
case 'agency2':
case 'agency3':
return 'agencyManage';
case 'agentApply':
case 'customList':
return 'customManage';
case 'financeOverview':
case 'withdrawApply':
case 'withdrawCancel':
case 'withdrawSuccess':
return 'financeManage';
case 'home':
return 'home';
case 'layout':
return 'layout'
}
}
复制代码
通过文件匹配文件夹,然后import
进来组件就好。记得在登录的时候调用一下:
import leftSliderRoute from "@/utils/leftSliderRoute.js";
var rou = [];
export default {
data() {
return {
username: '',
password: '',
}
},
methods: {
login() {
if (!this.username) {
this.$Message.error("请输入账号");
return;
}
if (!this.password) {
this.$Message.error("请输入密码");
return;
}
window.sessionStorage.setItem('routeList', JSON.stringify(routes));
this.$store.commit('addRouteList',routes);//写到vuex里
leftSliderRoute(rou,routes);
this.$router.addRoutes(rou);
this.$router.push('/home');
this.$Message.success('登录成功');
}
}
}
复制代码
然后需要注意一点,路由是在登录之后从vuex里拿的,但是用户刷新页面,不可能再读取接口然后再塞进vuex里,所以得存到缓存中,防止用户刷新,改造main.js
:
import store from './store';
import leftSliderRoute from './utils/leftSliderRoute.js';
let routeList = JSON.parse(window.sessionStorage.getItem('routeList'));
if (routeList) {
//这里是防止用户手动刷新页面,整个app要重新加载,动态新增的路由,会消失,所以我们重新add一次
let routes = [];
leftSliderRoute(routes, routeList);
router.addRoutes(routes);
store.commit('addRouteList', routes);
} else {
router.push('/login');
}
复制代码
至此,动态引入路由就完成了。
面包屑导航
其实这个东西还是依赖路由那个data,因为要读取children
,还是内部的,要么改造数据,要么就老老实实写:
//main.js
router.beforeEach((to, from, next) => {
//面包屑导航开始
let arr = [{
name: '首页',
path: '/home'
}];
to.matched.map(item => {
item.path != '/home' && item.path && arr.push({
name: item.name,
path: item.path
})
})
store.commit('setBread', arr) //写进vuex
next()
//导航结束
})
复制代码
然后使用就好了。
<at-breadcrumb>
<at-breadcrumb-item :to="index == breadList.length-1 ?'':item.path" v-for="(item,index) in breadList" :key="index">{{item.name}}</at-breadcrumb-item>
</at-breadcrumb>
复制代码
覆盖样式
很多时候框架内部的样式不符合使用,所以需要这样:
.operate {
min-width: 100px;
display: flex;
align-items: center;
/deep/ input {
font-size: 13px;
border: none;
border-bottom: 1px solid #d9d9d9;
border-radius: 0;
margin: 0 8px;
}
}
复制代码
使用/deep/
可以修改组件自带样式,但是一定要上一层一定要有class
。
清空表单
在有table
的组件里,查询是必不可少的,经常会有清空查询条件的操作,有两个方法:
- 通过
JSON.parse(JSON.stringify(data))
的格式 - 通过
Object.assign({},data)
的格式
两个都是通过深拷贝,在created
的时候赋值一个默认数据,作为备用,在清空的时候赋值就好了。
要注意:每次赋值都应该是深拷贝。
table
<template>
<div class="withdrawApply">
<div class="searchForm">
<form class="row at-row" ref="searchForm">
<at-select v-model="form.select" class="col-md-2" size="large" clearable placeholder="地区">
<at-option value="1">深圳</at-option>
</at-select>
<at-input class="col-md-3" v-model="form.keyword" placeholder="关键字" ></at-input>
<vue-datepicker-local class="col-md-3" placeholder="开始日期" v-model="form.beginTime" clearable format="YYYY-MM-DD HH:mm:ss"/>
<vue-datepicker-local class="col-md-3" placeholder="结束日期" v-model="form.endTime" clearable format="YYYY-MM-DD HH:mm:ss"/>
<at-button class="col-md-1" type="primary" @click="$Message('查询')">查询</at-button>
<at-button class="col-md-1" type="warning" @click="clearForm">清空</at-button>
</form>
</div>
<at-table
:columns="[{title:'id',key:'id'},{title:'提现金额',key:'a'},{title:'申请时间',key:'b'},{title:'机构名称',key:'c'},{title:'申请人',key:'d'},
{title:'银行卡号',key:'e'},{title:'分行',key:'f'},
{title:'状态', render:(h, params) => {
return h('span',{},filterState(params.item))
}},
{title:'操作', render: (h, params) => {
return h('div', [
h('AtButton', {
props: {
size: 'smaller',
type:'error'
},
style,
on: {
click: this.refuse
}
}, '拒绝'),
h('AtButton', {
props,
style,
on: {
click: this.checkPay
}
}, '线下打款')
])
}}]"
:data="data" border pagination :page-size="8"></at-table>
</div>
</template>
<script>
import tableData from '../../../static/tableData.js'; //表格数据存放
import VueDatepickerLocal from 'vue-datepicker-local'
import {mapState} from 'vuex';
export default {
components:{
VueDatepickerLocal
},
data() {
return {
data:tableData,
defaultForm:null,//用来保留空表单
form:{
select:'',
keyword:'',
begTime:'',
endTime:''
},
}
},
computed:{
//写了一个默认属性配置,懒得一次一次写,就扔vuex了
...mapState({
props:state=>state.table.props,
style:state=>state.table.style
})
},
created(){
//赋值默认
this.defaultForm = Object.assign({},this.form);
},
methods: {
filterState(val){
//类型变换使用
return val ? 'return 2':'return 1'
},
clearForm(){
this.form = Object.assign({},this.defaultForm);
},
refuse(){
this.$Modal.confirm({
title: '提示',
content: '是否拒绝?',
cancelText:'取消',
okText:'确定',
}).then(() => {
this.$Message.success('拒绝成功')
}).catch(() => {
})
},
checkPay(){
this.$Modal.confirm({
title: '提示',
content: '您确认审核打款么',
cancelText:'取消',
okText:'确定',
}).then(() => {
this.$Message.success('打款成功')
}).catch(() => {
})
}
}
}
</script>
复制代码
放出来一个组件,应该没什么问题了。
附加
- 还是
layout
组件吧:
<!--嗯,承重墙就长这个样子。-->
<template>
<div class="layout">
<leftSlider></leftSlider>
<div class="layout-container">
<headerTop></headerTop>
<router-view class="main"></router-view>
</div>
</div>
</template>
<script>
import headerTop from '@/components/header.vue';
import leftSlider from '@/components/leftSlider.vue';
export default {
components: {
leftSlider,
headerTop,
}
}
</script>
复制代码
- 添加了全屏效果,借鉴网上的。
function fullScreen() {
console.log(123)
var el = document.documentElement;
var rfs = el.requestFullScreen || el.webkitRequestFullScreen ||
el.mozRequestFullScreen || el.msRequestFullScreen;
if (typeof rfs != "undefined" && rfs) {
rfs.call(el);
} else if (typeof window.ActiveXObject != "undefined") {
//for IE,这里其实就是模拟了按下键盘的F11,使浏览器全屏
var wscript = new ActiveXObject("WScript.Shell");
if (wscript != null) {
wscript.SendKeys("{F11}");
}
}
}
function exitFullScreen() {
console.log(456)
var el = document;
var cfs = el.cancelFullScreen || el.webkitCancelFullScreen ||
el.mozCancelFullScreen || el.exitFullScreen;
if (typeof cfs != "undefined" && cfs) {
cfs.call(el);
} else if (typeof window.ActiveXObject != "undefined") {
//for IE,这里和fullScreen相同,模拟按下F11键退出全屏
var wscript = new ActiveXObject("WScript.Shell");
if (wscript != null) {
wscript.SendKeys("{F11}");
}
}
}
复制代码
- 如果你报错
Failed to mount component: template or render function not defined.
不妨可以看看报错指南
额外使用
1.常用过滤,用来前端搜索
searchInputEvent() {
let result = this.searchInput ?
this.querySearchArr.filter(item => {
return (item.name.toLowerCase().indexOf(this.searchInput.toLowerCase()) > -1)
}) : this.searchData;
this.searchData = result;
}
复制代码
结尾
差不多一个后台也就这么多东西了,其他的就是按业务需求来改。17年出的框架,但是18年也就更新了一次……?,总体感觉AT-UI
东西不多,不过体验还不错。