文章目录
实战项目之环境准备及配置改装
项目搭建及技术选型
通过命令行创建项目
- Vue create <项目名>
Vue create vue-manage-system
- 手动配置项目,上下键移动光标后,回车即可
- 选择需要的配置
- babel转译js的新特性,兼容低版本浏览器
- CSS预处理器,设置全局变量
- ESLint检查代码写法是否规范
- 是否使用
选择使用的选项
配置项目的基本环境及项目目录结构总体介绍
-
配置项目按eslint规范格式化代码
- vscode下载ESlint , Prettier , Vetur 插件
- 打开vscode的设置,配置Prettier参考:新版vsCode配置eslint方法,配置内容如下
{ "window.zoomLevel": 1, "editor.fontSize": 16, "workbench.iconTheme": "vscode-icons", "vsicons.dontShowNewVersionMessage": true, "eslint.codeAction.showDocumentation": { "enable": true }, // 每次保存时将代码按eslint格式进行保存 "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, // 添加vue支持 "eslint.validate": [ "javascript", "javascriptreact","vue", "html" ], }
还需要在setting中配置一下这个,才能在保存文件时候自动修复
-
可自定义eslint的一些规则
- 在.eslintrc 中覆盖prettier规则即可,覆盖是为了防止冲突
- 在rules里配置
rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 添加自定义规则 'prettier/prettier': [ // eslint校验不成功后,error或2则报错,warn或1则警告,off或0则无提示 'error', { singleQuote: true, semi: false } ] },
配置如下图
- 配置完成后可运行npm run lint 格式化全部文件,或者保存后自动格式化代码
- router部分模块化
- vuex部分模块化
- 调整项目目录
- 删除多余代码
项目目录结构调整
配置scss全局变量
- 在项目的根目录下新建vue.config.js
- 新建_variable.scss 文件,内容如下
$theme-color: #33aef0;
- 在vue.config.js 文件进行如下配置
module.exports = {
// 配置项目启动端口及自动打开浏览器
devServer: {
port: 3333,
open: true
},
// 配置scss全局变量
css: {
loaderOptions: {
sass: {
// 新版本sass-loader, 将data改成prependData进行配置
prependData: `@import "@/assets/scss/_variable.scss";`
}
}
}
}
在App.vue中引入这个$theme-color,如果没有报错那么成功
<template>
<div id="app">
app
</div>
</template>
<style lang="scss">
#app {
color:$theme-color;
}
</style>
在main.js中引入reset.scss
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
// 全局配置
import '@/assets/scss/reset.scss'
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
npm run serve执行后看到app颜色改变,并且贴着左边,则证明scss配置成功
后台视频管理系统之公用部分开发
需求分析及模块划分
分析视频管理理后台需要的功能(大概只有4个页面)
- 可视化展示数据
- 视频的成交量
- 用户总量
- 订单总额
- 登录页
- 视频管理
- 上传视频
- 更新视频
- 删除视频
- 查看已有视频
- 用户管理
- 更新用户信息
- 删除用户
- 新增用户
- 权限管理
设计对应页面
- 首页用来展示数据
- 使用Echart柱状图、折线图及饼图展示
- 视频管理理页、用户管理理页
- 选用el-table及el-form展示和编辑数据
- el-dialog组件实现编辑和新增功能
路由设计及左侧公用导航菜单开发
首先在项目中安装elementUI
yarn add element-ui -S
在项目中引入element-ui,找到文件main.js,加入element-ui的头文件
...
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 挂载在Vue实例上
Vue.use(ElementUI);
...
views文件夹下新建Main.vue组件放置公共部分组件(布局),为了测试页面布局,先使用空容器
<template>
<el-container style="height: 100%">
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script>
</script>
在App.vue中设置app的div高度也为100%
<template>
<div id="app">
app
<router-view />
</div>
</template>
<style lang="scss">
#app {
height: 100vh; // vh为屏幕高度
}
</style>
现在要的效果出来了
先创建侧边栏组件,在components下创建CommonAside.vue,内容如下
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<template slot="title">分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
<el-submenu index="1-4">
<template slot="title">选项4</template>
<el-menu-item index="1-4-1">选项1</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<span slot="title">导航二</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<i class="el-icon-document"></i>
<span slot="title">导航三</span>
</el-menu-item>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<span slot="title">导航四</span>
</el-menu-item>
</el-menu>
</template>
<script>
</script>
<style lang="scss" scoped>
.el-menu {
height: 100%;
border: none;
h3 {
color: #ffffff;
text-align: center;
line-height: 48px;
}
}
</style>
替换Main.vue的内容,引入上面侧边栏的内容
<template>
<el-container style="height: 100%">
<el-aside width="200px">
<CommonAside></CommonAside>
</el-aside>
<el-container>
<el-header></el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script>
import CommonAside from '../components/CommonAside'
export default {
components: {
CommonAside
}
}
</script>
目前可以看到的效果如下
接着需要跳转侧边栏CommonAside.vue的内容,从data部分获取显示的菜单项
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item :index="item.path" v-for="item in noChildren" :key="item.path">
<i :class="'el-icon-' + item.icon"></i>
<span slot="title">{{ item.label }}</span>
</el-menu-item>
<el-submenu :index="item.label" v-for="(item, index) in hasChildren" :key="index">
<template slot="title">
<i :class="'el-icon-' + item.icon"></i>
<span slot="title">{{ item.label }}</span>
</template>
<el-menu-item-group>
<el-menu-item
:index="subItem.path"
v-for="(subItem, subIndex) in item.children"
:key="subIndex"
@click="clickMenu(subItem)"
>
<i :class="'el-icon-' + subItem.icon"></i>
<span slot="title">{{ subItem.label }}</span>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>
<script>
export default {
computed: {
noChildren() {
return this.asideMenu.filter(item => !item.children)
},
hasChildren() {
return this.asideMenu.filter(item => item.children)
},
},
data() {
return {
asideMenu: [
{
path: '/',
name: 'home',
label: '首页',
icon: 's-home'
},
{
path: '/video',
name: 'video',
label: '视频管理',
icon: 'video-play'
},
{
path: '/user',
name: 'user',
label: '用户管理',
icon: 'user'
},
{
label: '其他',
icon: 'user',
children: [
{
path: '/page1',
name: 'page1',
label: '页面1',
icon: 'setting'
},
{
path: '/page2',
name: 'page2',
label: '页面2',
icon: 'setting'
}
]
}
]
}
}
}
</script>
<style lang="scss" scoped>
.el-menu {
height: 100%;
border: none;
h3 {
color: #ffffff;
text-align: center;
line-height: 48px;
}
}
</style>
那么现在侧边栏的效果初步实现了
顶部导航菜单及与左侧导航联动的面包屑实现
实现顶部导航菜单
新建views/CommonHeader.vue,内容如下,这是官网给的下拉示例,自己调整的一点点css样式
<template>
<header>
<div class="l-content">
<el-button plain icon="el-icon-menu" size="mini"></el-button>
</div>
<div class="r-content">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
下拉菜单
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-plus">黄金糕</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-plus">狮子头</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-plus-outline">螺蛳粉</el-dropdown-item>
<el-dropdown-item icon="el-icon-check">双皮奶</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-check">蚵仔煎</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
</script>
<style lang="scss" scoped>
header {
display: flex;
height: 100%;
align-items: center;
justify-content: space-between;
}
.el-dropdown-link {
color: white;
}
</style>
在Main.vue中引入这个header组件
<template>
<el-container style="height: 100%">
<el-aside width="200px">
<CommonAside></CommonAside>
</el-aside>
<el-container>
<el-header>
<CommonHeader></CommonHeader>
</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script>
import CommonAside from '../components/CommonAside'
import CommonHeader from '../components/CommonHeader'
export default {
components: {
CommonAside,
CommonHeader
}
}
</script>
<style lang="scss" scoped>
.el-header {
background-color: #333;
}
</style>
现在的效果如下
接下来需要把右边的下拉变成图像,在assets下新建一个images文件,把头像放到文件夹中
再回去修改CommonHeader.vue,引入头像,并且从data中获取下拉选项的内容
<template>
<header>
<div class="l-content">
<el-button plain icon="el-icon-menu" size="mini"></el-button>
</div>
<div class="r-content">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<img :src="userImg" class="user" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item @click.native="logOut">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
export default {
data() {
return {
userImg: require('../assets/images/user.png')
}
},
}
</script>
<style lang="scss" scoped>
header {
display: flex;
height: 100%;
align-items: center;
justify-content: space-between;
}
.el-dropdown-link {
color: white;
}
.r-content {
.user {
width: 40px;
height: 40px;
border-radius: 50%;
}
}
</style>
现在这是右边收下拉菜单的效果
左侧导航联动的面包屑实现
首先需要修改vuex,是store文件夹下新建一个tab.js,把之前在index.js下的state、mutations和actions放过去
export default {
state: {
menu: [],
currentMenu: {},
},
mutations: {
selectMenu(state, val) {
state.currentMenu = val; // 保存左侧点击的菜单
}
},
actions: {},
}
index.js内容只需要引入tab.js
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
tab
}
})
修改CommonHeader.vue加入面包屑导航的坑
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item><a href="/">活动管理</a></el-breadcrumb-item>
<el-breadcrumb-item>活动列表</el-breadcrumb-item>
<el-breadcrumb-item>活动详情</el-breadcrumb-item>
</el-breadcrumb>
现在需要将这个面包屑导航与左侧菜单通过vues联动对应起来,只需要使用计算属性,每次去绑定current,current对应vue中state.currentMenu的内容
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
current: state => state.tab.currentMenu
})
},
data() {
return {
userImg: require('../assets/images/user.png')
}
},
}
</script>
那么此刻的面包屑导航只需要显示出来current即可
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="current.path" v-if="current">{{ current.label }}</el-breadcrumb-item>
</el-breadcrumb>
我们唯一要做的就是点击侧边栏的导航后,执行state中的selectMenu为当前的路由信息,就是之前asideMenu的内容联系起来
在CommonAside.vue中补充下面的方法,并把这个方法与侧边栏的点击绑定起来即可
methods: {
clickMenu(item) {
this.$store.commit('selectMenu', item)
}
}
绑定部分
....
<el-menu-item
:index="item.path"
v-for="item in noChildren"
:key="item.path"
@click="clickMenu(item)"
>
...
最后再处理一些细节,修改tab.js中的selectMenu,如果当前为首页,就不需要再把currentMenu赋值了
export default {
state: {
menu: [],
currentMenu: {},
},
mutations: {
selectMenu(state, val) {
if (val.name !== 'home') {
state.currentMenu = val;
} else {
state.currentMenu = null;
}
}
},
actions: {},
}
修改这里的颜色
在CommonHeader.vue中加入样式
<style lang="scss">
.el-breadcrumb__item {
.el-breadcrumb__inner {
color: #ffffff;
font-weight: normal;
}
&:last-child {
.el-breadcrumb__inner {
color: #ffffff;
}
}
}
</style>
解决问题
核心点
- 使用vuex进行传值
使用vuex实现切换tab页功能
做一个简单的小特效tab标签
在Main.vue 中引入CommonTab.vue 组件
- 在vuex里定义存取标签的tagList ,方便便非父子传递数据
- 定义vuex 中侧边栏点击后将菜单加入到tagList 中的方法
- 定义vuex 中点击标签后触发删除的⽅方法
还是从官网中找到对应的代码来填坑
<template>
<div class="tabs">
<el-tag
:key="tag"
v-for="tag in dynamicTags"
closable
:disable-transitions="false"
@close="handleClose(tag)"
>{{tag}}</el-tag>
</div>
</template>
<script>
export default {
data() {
return {
dynamicTags: ['标签一', '标签二', '标签三'],
inputVisible: false,
inputValue: ''
};
},
methods: {
handleClose(tag) {
this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
},
}
}
</script>
<style lang="scss" scoped>
.tabs {
padding: 20px;
.el-tag {
margin-right: 15px;
cursor: pointer;
}
}
</style>
现在的效果为
我们要想办法,在点击左侧菜单的时候,把对应的菜单项添加到tabs上面,点击tabs能快速进行路由跳转
在tabs.js中添加对tabsList的存储,对侧边栏点击事件进行扩展,维护一个closeTab也就是标签的关闭事件
export default {
state: {
menu: [],
currentMenu: {},
tabsList: [{
path: '/',
name: 'home',
label: '首页',
icon: 'home'
}]
},
mutations: {
selectMenu(state, val) {
if (val.name !== 'home') {
state.currentMenu = val;
let result = state.tabsList.findIndex(item => item.name === val.name)
result === -1 ? state.tabsList.push(val) : ''
} else {
state.currentMenu = null;
}
},
closeTab(state, val) {
let result = state.tabsList.findIndex(item => item.name === val.name)
state.tabsList.splice(result, 1)
},
},
actions: {},
}
回到CommonTab.vue只需要做把state.tab.tabsList通过计算属性绑定到tags中,tags又用v-for渲染到el-tag中;mapMutations将tabs.js中的关闭标签事件映射到close事件中,当点击标签的关闭时调用state中的closeTab将tab从tabslist中移走(还需要让:closable跟tag.name绑定起来,只有不是首页时的tab标签才能关闭)
<template>
<div class="tabs">
<el-tag
:key="tag.name"
v-for="(tag, index) in tags"
:closable="tag.name !== 'home'"
:disable-transitions="false"
@close="handleClose(tag, index)"
>{{tag.label}}</el-tag>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState({
tags: state => state.tab.tabsList
})
},
methods: {
// 为了能使用tab.js中的closeTab引入展开函数
...mapMutations({
close: 'closeTab'
}),
handleClose(tag) {
this.close(tag);
},
}
}
</script>
<style lang="scss" scoped>
.tabs {
padding: 20px;
.el-tag {
margin-right: 15px;
cursor: pointer;
}
}
</style>
到这里的效果为
构建页面组件,连通公共组件
- 建立每个页面组件
- 连通面包屑
- 连通侧边栏
- 连通标签栏
在views下先建立每一个需要在main中显示的页面
每个页面先占坑,比较Home.vue的内容如下
<template>
<div>Home</div>
</template>
<script></script>
<style scoped></style>
在routes/index.js修改路由(跟之前的在侧边栏CommonAside.vue中的asideMenu对应上)
在Main.vue中的main部分带上路由router-view
<template>
<el-container style="height: 100%">
<el-aside width="200px">
<CommonAside></CommonAside>
</el-aside>
<el-container>
<el-header>
<CommonHeader></CommonHeader>
</el-header>
<CommonTab></CommonTab>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</template>
因为都在Main.vue底下,所有这些页面都可以看做成子路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{
path: '/',
component: () => import('@/views/Main.vue'),
children: [{
path: "/",
name: "home",
component: () => import('@/views/Home/Home.vue'),
},
{
path: "/video",
name: "video",
component: () => import('@/views/VideoManage/VideoManage.vue'),
},
{
path: "/user",
name: "user",
component: () => import('@/views/UserManage/UserManage.vue'),
},
{
path: "/page1",
name: "page1",
component: () => import('@/views/Other/Page1.vue'),
},
{
path: "/page2",
name: "page2",
component: () => import('@/views/Other/Page2.vue'),
},
]
}, ]
const router = new VueRouter({
routes
})
export default router
修改侧边栏的点击事件,让其进行路由跳转
methods: {
clickMenu(item) {
this.$router.push({ name: item.name })
this.$store.commit('selectMenu', item)
},
},
在新版的vue-router,点击两次会造成错误提示,所以先降级
yarn add vue-router@3.0 -S
目前测试没问题,点击侧边栏和顶部导航均能正常进行路由跳转
最后再需要调整一下,点击标签能进行跳转了,在CommonTab.vue中为tab添加一个click事件,让其进行路由的push和state中selectMenu的记录(也就是处理顶部面包屑的显示)
<template>
<div class="tabs">
<el-tag
:key="tag.name"
v-for="(tag, index) in tags"
:closable="tag.name !== 'home'"
:disable-transitions="false"
@close="handleClose(tag, index)"
@click="changeMenu(tag)"
>{{ tag.label }}</el-tag
>
</div>
</template>
...
methods: {
...
changeMenu(item) {
this.$router.push({ name: item.name })
this.$store.commit('selectMenu', item)
},
...
测试一下,到这里点击标签也能跳转了
页面布局整体样式优化
- 侧边栏背景色改变
- tag选中样式优化
- 面包屑当前激活菜单样式优化
- 细节优化
修改顶部菜单栏的样式,让其激活的页面是白色,未激活为灰色
<style lang="scss">
.el-breadcrumb__item {
.el-breadcrumb__inner {
color: #666666;
font-weight: normal;
}
&:last-child {
.el-breadcrumb__inner {
color: #ffffff;
}
}
}
</style>
效果如下
再来优化一下样式,点击的tab标签进行一个颜色的高亮,修改CommonTab.vue,只需要多加一个三元表达式
<template>
...
@click="changeMenu(tag)"
:effect="$route.name === tag.name ? 'dark' : 'plain'"
...
这样效果就出来了
最后再来完成一下侧边栏的折叠效果
在tab.js上定义我们需要控制侧边栏是否展开的变量,再定义一个mutations去改变这个属性
export default {
state: {
isCollapse: false,
...
},
mutations: {
...
collapseMenu(state) {
state.isCollapse = !state.isCollapse
},
},
actions: {},
}
在侧边栏CommonAside.vue中去绑定这个属性,为了让动画更加平滑,添加一个css样式
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="isCollapse"
>
...
</template>
<script>
export default {
computed: {
...
isCollapse() {
return this.$store.state.tab.isCollapse
},
},
...
}
</script>
<style lang="scss" scoped>
...
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>
修改之前Main.vue对侧边栏的宽度变成auto
<template>
...
<el-aside width="auto">
<CommonAside></CommonAside>
...
</template>
最后在CommonHeader.vue中添加侧边栏状态改变的click事件
<template>
<header>
<div class="l-content">
<el-button
plain
icon="el-icon-menu"
size="mini"
@click="collapseMenu"
></el-button>
...
</header>
</template>
<script>
...
methods: {
collapseMenu() {
this.$store.commit('collapseMenu')
},
},
}
</script>
...
在浏览器中可以查看目前的效果
参考链接
- HTML标签
- 新版vsCode配置eslint方法
- vue cli3使用官方方法配置sass全局变量报错
- 配置 Prettier保存文件自动格式化代码不生效
- 修改文件内容重启后出现error Insert
⏎
prettier/prettier解决方法 - 出现警告:warning Replace
'xxx'
with"xxx"
prettier/prettier,关闭Prettier代码格式化工具