从vue到elementUI项目(三)

实战项目之环境准备及配置改装

项目搭建及技术选型

通过命令行创建项目

  • Vue create <项目名>
Vue create vue-manage-system
  • 手动配置项目,上下键移动光标后,回车即可
  • 选择需要的配置
    • babel转译js的新特性,兼容低版本浏览器
    • CSS预处理器,设置全局变量
    • ESLint检查代码写法是否规范
  • 是否使用

选择使用的选项
> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rm2zjoqw-1599480590302)(4AF600168BB045ADB2F3A215F1E75743)]

配置项目的基本环境及项目目录结构总体介绍

  • 配置项目按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中配置一下这个,才能在保存文件时候自动修复
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsTGBdtm-1599480590305)(5A17B69398BA4AD9B6E4BCBC51B2FBED)]

  • 可自定义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>
...

在浏览器中可以查看目前的效果

参考链接

全文所涉及的代码下载地址

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值