Vue2后台管理系统开发全流程 | 企业级前端架构实战

2025-08-12 0 772

一、项目架构设计与技术选型

本教程将基于Vue2生态系统构建一个完整的企业级后台管理系统,涵盖权限控制、数据可视化、复杂表单等核心功能。

技术栈组成:

  • 核心框架:Vue 2.6 + Composition API
  • 状态管理:Vuex 3.x
  • 路由系统:Vue-router 3.x
  • UI组件库:Element UI 2.x
  • HTTP客户端:Axios
  • 可视化库:ECharts 5.x

系统功能模块:

  1. RBAC权限控制系统
  2. 动态路由与菜单生成
  3. 多标签页导航
  4. 数据可视化仪表盘
  5. 复杂表单解决方案
  6. Excel导入导出
  7. 系统主题定制

二、项目初始化与工程配置

1. 创建Vue2项目

# 使用Vue CLI创建项目
vue create admin-system

# 添加必要依赖
cd admin-system
vue add router
vue add vuex
npm install element-ui axios echarts vue-echarts xlsx file-saver

2. 项目目录结构

src/
├── api/               # API接口管理
├── assets/            # 静态资源
├── components/        # 公共组件
│   ├── Layout/        # 布局组件
│   ├── Charts/        # 图表组件
│   └── ...           
├── directives/        # 自定义指令
├── filters/           # 全局过滤器
├── router/            # 路由配置
│   ├── index.js       # 路由入口
│   ├── modules/       # 路由模块
│   └── permission.js  # 权限控制
├── store/             # Vuex状态管理
│   ├── modules/       # 模块化store
│   └── index.js       # store入口
├── styles/            # 全局样式
├── utils/             # 工具函数
├── views/             # 页面组件
│   ├── dashboard/     # 仪表盘
│   ├── system/        # 系统管理
│   └── ...           
├── App.vue            # 根组件
└── main.js            # 应用入口

3. 全局配置 (main.js)

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

// 引入全局样式
import '@/styles/index.scss'

// 注册全局组件
import * as directives from '@/directives'
import * as filters from '@/filters'

Object.keys(directives).forEach(key => {
  Vue.directive(key, directives[key])
})

Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.use(ElementUI, {
  size: 'medium'
})

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

三、核心功能实现

1. 权限控制系统

创建路由权限控制 (router/permission.js):

import router from './index'
import store from '@/store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

NProgress.configure({ showSpinner: false })

const whiteList = ['/login', '/auth-redirect']

router.beforeEach(async (to, from, next) => {
  NProgress.start()
  
  // 确定用户是否已登录
  const hasToken = store.getters.token
  
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      // 检查用户是否已获取权限信息
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      
      if (hasRoles) {
        next()
      } else {
        try {
          // 获取用户信息
          const { roles } = await store.dispatch('user/getInfo')
          
          // 根据角色生成动态路由
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          
          // 添加路由
          router.addRoutes(accessRoutes)
          
          // 确保addRoutes完成
          next({ ...to, replace: true })
        } catch (error) {
          // 获取信息失败则退出登录
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

2. 动态路由配置

创建路由模块 (router/modules/admin.js):

const Layout = () => import('@/layout')

export default {
  path: '/admin',
  component: Layout,
  redirect: '/admin/user',
  meta: { title: '系统管理', icon: 'el-icon-s-tools' },
  children: [
    {
      path: 'user',
      component: () => import('@/views/admin/user'),
      name: 'UserManagement',
      meta: { title: '用户管理', roles: ['admin'] }
    },
    {
      path: 'role',
      component: () => import('@/views/admin/role'),
      name: 'RoleManagement',
      meta: { title: '角色管理', roles: ['admin'] }
    }
  ]
}

四、Vuex状态管理

1. 模块化状态设计

创建用户模块 (store/modules/user.js):

import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'

const state = {
  token: getToken(),
  name: '',
  avatar: '',
  roles: []
}

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}

const actions = {
  // 用户登录
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  
  // 获取用户信息
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        
        if (!data) {
          reject('验证失败,请重新登录')
        }
        
        const { roles, name, avatar } = data
        
        if (!roles || roles.length  {
        reject(error)
      })
    })
  },
  
  // 用户退出
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        commit('SET_TOKEN', '')
        commit('SET_ROLES', [])
        removeToken()
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  
  // 移除token
  resetToken({ commit }) {
    return new Promise(resolve => {
      commit('SET_TOKEN', '')
      commit('SET_ROLES', [])
      removeToken()
      resolve()
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

五、复杂组件开发

1. 多标签页导航组件

<template>
  <div class="tags-view-container">
    <scroll-pane class="tags-view-wrapper" ref="scrollPane">
      <router-link
        v-for="tag in visitedViews"
        ref="tag"
        :key="tag.path"
        :class="isActive(tag)?'active':''"
        :to="{ path: tag.path, query: tag.query }"
        class="tags-view-item"
        @click.middle.native="closeSelectedTag(tag)"
        @contextmenu.prevent.native="openMenu(tag,$event)">
        {{ tag.title }}
        <span 
          v-if="!isAffix(tag)"
          class="el-icon-close"
          @click.prevent.stop="closeSelectedTag(tag)" />
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)">刷新</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
      <li @click="closeOthersTags">关闭其他</li>
      <li @click="closeAllTags(selectedTag)">关闭所有</li>
    </ul>
  </div>
</template>

<script>
import ScrollPane from './ScrollPane'

export default {
  components: { ScrollPane },
  data() {
    return {
      visible: false,
      top: 0,
      left: 0,
      selectedTag: {},
      affixTags: []
    }
  },
  computed: {
    visitedViews() {
      return this.$store.state.tagsView.visitedViews
    }
  },
  watch: {
    $route() {
      this.addTags()
      this.moveToCurrentTag()
    },
    visible(value) {
      if (value) {
        document.body.addEventListener('click', this.closeMenu)
      } else {
        document.body.removeEventListener('click', this.closeMenu)
      }
    }
  },
  mounted() {
    this.initTags()
    this.addTags()
  },
  methods: {
    isActive(route) {
      return route.path === this.$route.path
    },
    isAffix(tag) {
      return tag.meta && tag.meta.affix
    },
    addTags() {
      const { name } = this.$route
      if (name) {
        this.$store.dispatch('tagsView/addView', this.$route)
      }
    },
    moveToCurrentTag() {
      const tags = this.$refs.tag
      if (!tags) return
      
      for (const tag of tags) {
        if (tag.to.path === this.$route.path) {
          this.$refs.scrollPane.moveToTarget(tag)
          break
        }
      }
    },
    refreshSelectedTag(view) {
      this.$store.dispatch('tagsView/delCachedView', view).then(() => {
        const { fullPath } = view
        this.$nextTick(() => {
          this.$router.replace({
            path: '/redirect' + fullPath
          })
        })
      })
    },
    closeSelectedTag(view) {
      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
        if (this.isActive(view)) {
          this.toLastView(visitedViews)
        }
      })
    },
    closeOthersTags() {
      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
        this.moveToCurrentTag()
      })
    },
    closeAllTags(view) {
      this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
        if (this.affixTags.some(tag => tag.path === view.path)) {
          return
        }
        this.toLastView(visitedViews)
      })
    },
    toLastView(visitedViews) {
      const latestView = visitedViews.slice(-1)[0]
      if (latestView) {
        this.$router.push(latestView)
      } else {
        this.$router.push('/')
      }
    },
    openMenu(tag, e) {
      this.selectedTag = tag
      this.visible = true
      this.left = e.clientX
      this.top = e.clientY
    },
    closeMenu() {
      this.visible = false
    },
    initTags() {
      const affixTags = this.affixTags = this.filterAffixTags(this.$router.options.routes)
      for (const tag of affixTags) {
        if (tag.name) {
          this.$store.dispatch('tagsView/addVisitedView', tag)
        }
      }
    },
    filterAffixTags(routes, basePath = '/') {
      let tags = []
      routes.forEach(route => {
        if (route.meta && route.meta.affix) {
          const tagPath = path.resolve(basePath, route.path)
          tags.push({
            fullPath: tagPath,
            path: tagPath,
            name: route.name,
            meta: { ...route.meta }
          })
        }
        if (route.children) {
          const childTags = this.filterAffixTags(route.children, route.path)
          if (childTags.length >= 1) {
            tags = tags.concat(childTags)
          }
        }
      })
      return tags
    }
  }
}
</script>

六、数据可视化实现

1. ECharts集成与封装

<template>
  <div class="chart-container" ref="chart" :style="{ height: height }"></div>
</template>

<script>
import echarts from 'echarts'
import resize from './mixins/resize'

export default {
  name: 'Chart',
  mixins: [resize],
  props: {
    height: {
      type: String,
      default: '100%'
    },
    width: {
      type: String,
      default: '100%'
    },
    option: {
      type: Object,
      required: true
    },
    theme: {
      type: String,
      default: 'default'
    }
  },
  data() {
    return {
      chart: null
    }
  },
  watch: {
    option: {
      deep: true,
      handler(val) {
        this.setOptions(val)
      }
    }
  },
  mounted() {
    this.initChart()
  },
  beforeDestroy() {
    if (!this.chart) return
    this.chart.dispose()
    this.chart = null
  },
  methods: {
    initChart() {
      this.chart = echarts.init(this.$refs.chart, this.theme)
      this.setOptions(this.option)
    },
    setOptions(option) {
      if (this.chart) {
        this.chart.setOption(option, true)
      }
    }
  }
}
</script>

七、项目优化与部署

1. 生产环境优化

  • 代码分割:配置路由懒加载
  • CDN加速:外部引入公共库
  • Gzip压缩:配置nginx启用gzip
  • 缓存策略:合理配置静态资源缓存

2. Docker部署配置

# Dockerfile
FROM nginx:alpine

# 复制构建好的文件
COPY dist /usr/share/nginx/html

# 复制nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 暴露端口
EXPOSE 80

# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

# nginx.conf
server {
    listen       80;
    server_name  localhost;
    
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    
    location /api {
        proxy_pass http://backend:3000;
        proxy_set_header Host $host;
    }
    
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

八、总结与扩展

本教程详细介绍了基于Vue2的企业级后台管理系统开发:

  1. 搭建了完整的项目架构
  2. 实现了RBAC权限控制系统
  3. 开发了动态路由与多标签页
  4. 集成了数据可视化功能
  5. 优化了生产环境部署方案

扩展学习方向:

  • 微前端架构改造
  • SSR服务端渲染
  • 自动化测试方案
  • CI/CD持续集成

完整项目代码已上传GitHub:https://github.com/example/vue2-admin-system

Vue2后台管理系统开发全流程 | 企业级前端架构实战
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 vue2 Vue2后台管理系统开发全流程 | 企业级前端架构实战 https://www.taomawang.com/web/vue2/811.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务