前端基础之《Vue(24)—vue-element-admin》

发布于:2025-07-24 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、项目运行

1、vue-element-admin(Vue2项目的天花板,架构设计特别精妙)

2、起项目
要有能力把一个没有见过的项目起起来。

3、安装依赖包

(1)用yarn
npm install yarn -g
yarn install
不行,报错unable to connect to github.com

(2)用npm
npm install --registry=https://registry.npm.taobao.org
不行,显示idealTree:vue-element-admin: sill idealTree buildDeps卡住了
原因:淘宝镜像换地址了registry.npmmirror.com

(3)成功的命令
npm install --registry=https://registry.npmmirror.com

4、启动服务
npm run dev

5、浏览器访问
http://localhost:9527

二、项目研究

1、研究新项目的思路

README.md
package.json(技术栈、知道怎么安装依赖、启动项目、没见过的包)
研究各种配置文件(babel、eslint、webpack)
src
src/入口文件.js
src/App.tsx
src/路由、Layout,怎么配置新页面?
src/store状态管理文件
src/axios封装、api封装、跨域代理写在哪里?
src/翻看别人写的页面、了解编程范式(代码习惯)
把项目跑起来(安装依赖包)、边看效果边看代码

2、研究结果输出

需要帮助讲解的问题列表。
罗列不值得问、自己又不会的问题列表。

三、配置文件分析

1、package.json
项目依赖。

2、vue.config.js
整个项目的webpack配置。

3、jsconfig.json
js/ts的配置文件。

4、jest.config.js
测试的配置文件。

5、babel.config.js
babel的配置文件。

6、.travis.yml
云部署的测试文件。

7、.editorconfig
编辑器兼容性的配置文件。

四、src目录分析

1、首先看入口文件main.js

import Vue from 'vue'
// 引入cookies
import Cookies from 'js-cookie'
// 浏览器默认样式重置
import 'normalize.css/normalize.css' // a modern alternative to CSS resets
// 引入element-ui包、样式、国际化的包
import Element from 'element-ui'
import './styles/element-variables.scss'
import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
// 导入自己的全局样式
import '@/styles/index.scss' // global css
// 导入根组件,导入store实例,导入路由实例
import App from './App'
import store from './store'
import router from './router'
// 导入自己封装的icon图标库
import './icons' // icon
// 导入权限控制(管理系统的最大特色)
import './permission' // permission control

import './utils/error-log' // error log
// 导入过滤器
import * as filters from './filters' // global filters

/**
 * If you don't want to use mock-server
 * you want to use MockJs for mock api
 * you can execute: mockXHR()
 *
 * Currently MockJs will be used in the production environment,
 * please remove it before going online ! ! !
 */
if (process.env.NODE_ENV === 'production') {
  const { mockXHR } = require('../mock')
  mockXHR()
}

// 全局注册element-ui组件库,注册后在页面中可以直接使用
Vue.use(Element, {
  size: Cookies.get('size') || 'medium', // set element-ui default size
  locale: enLang // 如果使用中文,无需设置,请删除
})

// 注册全局过滤器
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

// 关闭生产环境下的vue提示
Vue.config.productionTip = false

// 创建vue响应式系统并挂载到#app上
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

在研究src源码,不能出现语法障碍。

2、研究根组件App.vue

<template>
  <div id="app">
    <!-- 视图容器 -->
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

每一个项目都有根组件。
视图容器:每次路由切换都要找到一个盒子来放我的组件。

3、研究router组件

// 导入路由并注册
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* 布局组件 */
import Layout from '@/layout'

/* 路由规则模块 */
import componentsRouter from './modules/components'
import chartsRouter from './modules/charts'
import tableRouter from './modules/table'
import nestedRouter from './modules/nested'

/**
 * Note: sub-menu only appear when route children.length >= 1
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu
 *                                if not set alwaysShow, when item has more than one children route,
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
    noCache: true                if set true, the page will no be cached(default is false)
    affix: true                  if set true, the tag will affix in the tags-view
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
  }
 */

/**
 * constantRoutes静态路由:是所有人(角色)都能访问的页面
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/auth-redirect',
    component: () => import('@/views/login/auth-redirect'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error-page/401'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
      }
    ]
  },
  {
    path: '/documentation',
    component: Layout,
    children: [
      {
        path: 'index',
        component: () => import('@/views/documentation/index'),
        name: 'Documentation',
        meta: { title: 'Documentation', icon: 'documentation', affix: true }
      }
    ]
  },
  {
    path: '/guide',
    component: Layout,
    redirect: '/guide/index',
    children: [
      {
        path: 'index',
        component: () => import('@/views/guide/index'),
        name: 'Guide',
        meta: { title: 'Guide', icon: 'guide', noCache: true }
      }
    ]
  },
  {
    path: '/profile',
    component: Layout,
    redirect: '/profile/index',
    hidden: true,
    children: [
      {
        path: 'index',
        component: () => import('@/views/profile/index'),
        name: 'Profile',
        meta: { title: 'Profile', icon: 'user', noCache: true }
      }
    ]
  }
]

/**
 * asyncRoutes动态路由:需要进行角色判断(权限判断)的路由规则们
 * the routes that need to be dynamically loaded based on user roles
 */
export const asyncRoutes = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, // will always show the root menu
    name: 'Permission',
    meta: {
      title: 'Permission',
      icon: 'lock',
      // 这个roles相当重要,专门用于权限设计的
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: 'Page Permission',
          // 这是二级菜单的权限
          roles: ['admin'] // or you can only set roles in sub nav
        }
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'DirectivePermission',
        meta: {
          title: 'Directive Permission'
          // if do not set roles, means: this page does not require permission
        }
      },
      {
        path: 'role',
        component: () => import('@/views/permission/role'),
        name: 'RolePermission',
        meta: {
          title: 'Role Permission',
          roles: ['admin']
        }
      }
    ]
  },

  {
    path: '/icon',
    component: Layout,
    children: [
      {
        path: 'index',
        component: () => import('@/views/icons/index'),
        name: 'Icons',
        meta: { title: 'Icons', icon: 'icon', noCache: true }
      }
    ]
  },

  /** 当某一条路由规则太长(意思是children太多),建议把它封装成路由模块 **/
  componentsRouter,
  chartsRouter,
  nestedRouter,
  tableRouter,

  {
    path: '/example',
    component: Layout,
    redirect: '/example/list',
    name: 'Example',
    meta: {
      title: 'Example',
      icon: 'el-icon-s-help'
    },
    children: [
      {
        path: 'create',
        component: () => import('@/views/example/create'),
        name: 'CreateArticle',
        meta: { title: 'Create Article', icon: 'edit' }
      },
      {
        path: 'edit/:id(\\d+)',
        component: () => import('@/views/example/edit'),
        name: 'EditArticle',
        meta: { title: 'Edit Article', noCache: true, activeMenu: '/example/list' },
        hidden: true
      },
      {
        path: 'list',
        component: () => import('@/views/example/list'),
        name: 'ArticleList',
        meta: { title: 'Article List', icon: 'list' }
      }
    ]
  },

  {
    path: '/tab',
    component: Layout,
    children: [
      {
        path: 'index',
        component: () => import('@/views/tab/index'),
        name: 'Tab',
        meta: { title: 'Tab', icon: 'tab' }
      }
    ]
  },

  {
    path: '/error',
    component: Layout,
    redirect: 'noRedirect',
    name: 'ErrorPages',
    meta: {
      title: 'Error Pages',
      icon: '404'
    },
    children: [
      {
        path: '401',
        component: () => import('@/views/error-page/401'),
        name: 'Page401',
        meta: { title: '401', noCache: true }
      },
      {
        path: '404',
        component: () => import('@/views/error-page/404'),
        name: 'Page404',
        meta: { title: '404', noCache: true }
      }
    ]
  },

  {
    path: '/error-log',
    component: Layout,
    children: [
      {
        path: 'log',
        component: () => import('@/views/error-log/index'),
        name: 'ErrorLog',
        meta: { title: 'Error Log', icon: 'bug' }
      }
    ]
  },

  {
    path: '/excel',
    component: Layout,
    redirect: '/excel/export-excel',
    name: 'Excel',
    meta: {
      title: 'Excel',
      icon: 'excel'
    },
    children: [
      {
        path: 'export-excel',
        component: () => import('@/views/excel/export-excel'),
        name: 'ExportExcel',
        meta: { title: 'Export Excel' }
      },
      {
        path: 'export-selected-excel',
        component: () => import('@/views/excel/select-excel'),
        name: 'SelectExcel',
        meta: { title: 'Export Selected' }
      },
      {
        path: 'export-merge-header',
        component: () => import('@/views/excel/merge-header'),
        name: 'MergeHeader',
        meta: { title: 'Merge Header' }
      },
      {
        path: 'upload-excel',
        component: () => import('@/views/excel/upload-excel'),
        name: 'UploadExcel',
        meta: { title: 'Upload Excel' }
      }
    ]
  },

  {
    path: '/zip',
    component: Layout,
    redirect: '/zip/download',
    alwaysShow: true,
    name: 'Zip',
    meta: { title: 'Zip', icon: 'zip' },
    children: [
      {
        path: 'download',
        component: () => import('@/views/zip/index'),
        name: 'ExportZip',
        meta: { title: 'Export Zip' }
      }
    ]
  },

  {
    path: '/pdf',
    component: Layout,
    redirect: '/pdf/index',
    children: [
      {
        path: 'index',
        component: () => import('@/views/pdf/index'),
        name: 'PDF',
        meta: { title: 'PDF', icon: 'pdf' }
      }
    ]
  },
  {
    path: '/pdf/download',
    component: () => import('@/views/pdf/download'),
    hidden: true
  },

  {
    path: '/theme',
    component: Layout,
    children: [
      {
        path: 'index',
        component: () => import('@/views/theme/index'),
        name: 'Theme',
        meta: { title: 'Theme', icon: 'theme' }
      }
    ]
  },

  {
    path: '/clipboard',
    component: Layout,
    children: [
      {
        path: 'index',
        component: () => import('@/views/clipboard/index'),
        name: 'ClipboardDemo',
        meta: { title: 'Clipboard', icon: 'clipboard' }
      }
    ]
  },

  {
    path: 'external-link',
    component: Layout,
    children: [
      {
        path: 'https://github.com/PanJiaChen/vue-element-admin',
        meta: { title: 'External Link', icon: 'link' }
      }
    ]
  },

  // 重定向页面必须是最后一条路由规则
  { path: '*', redirect: '/404', hidden: true }
]

// 封装一个创建路由实例的工厂方法
const createRouter = () => new Router({
  // mode: 'history', // require service support
  mode: 'hash',
  scrollBehavior: () => ({ y: 0 }),
  // 在这里,目前只考虑了静态路由,那么动态路由怎么办?
  // 在全局守卫中实现的,参见src/permission.js文件
  // 知识点:router.addRoutes()可以向路由规则中手动添加新的路由规则
  routes: constantRoutes
})

// 创建路由实例
const router = createRouter()

// 封装了一个方法,用于生成新的路由实例
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

注释:
(1)constantRoutes静态路由:是所有人(角色)都能访问的页面
(2)asyncRoutes动态路由:需要进行角色判断(权限判断)的路由规则们
(3)重定向404页面必须放在最后

4、分析一条路由规则
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  }
(1)当有人访问到/redirect路径,我就显示Layout组件
(2)hidden是自定义字段,等于真,就不会显示在sidebar,界面侧边栏
(3)children二级路由显示在一级路由Layout的内部

5、路由权限

(1)Layout是一级路由所对应的视图,AppMain.vue里的router-view是二级路由对应的视图。
(2)父级路由权限,必须大于等于子级路由权限。
(3)没有写权限,说明所有人都能访问。
(4)createRouter只考虑了那些静态路由,那些有权限的动态路由规则怎么办呢?
在全局守卫中实现,参见src/permission.js文件。

6、研究permission.js
 


网站公告

今日签到

点亮在社区的每一天
去签到