Vue.js,是,现在,最,流行的,前端框架,之一,它,简单,易学,轻量,高效,组件化,数据驱动,很,适合,开发,后台管理系统,单页应用,等等,很多,开发者,都,喜欢,用Vue.js,开发,前端项目。

我,用Vue.js,开发了,很多,项目,其中,后台管理系统,是,最,常见的,一种,今天,就来,分享一下,如何,用Vue.js,从0到1,搭建,一个,后台管理系统,包括,项目,初始化,路由,配置,布局,组件,状态管理,接口,请求,权限,控制,等等,全面,分享,希望,能,帮,Vue.js的,初学者,快速,上手,开发,后台管理系统。

一、Vue.js简介

先简单,介绍一下,Vue.js。

Vue.js,是,一套,用于,构建,用户界面的,渐进式,JavaScript框架,由,尤雨溪,开发,2014年,发布,它,的,核心,只,关注,视图层,容易,上手,也,便于,和,第三方库,或,既有项目,整合,另一方面,Vue.js,完全,有能力,为,复杂的,单页应用,提供,驱动。

Vue.js,的,特点:

  1. 简单,易学: Vue.js,的,API,很,简单,很,容易,理解,学习曲线,平缓,初学者,很容易,上手。
  2. 轻量,高效: Vue.js,的,体积,很小,压缩后,只有,几十KB,而且,性能,很好,虚拟DOM,数据驱动,更新,高效。
  3. 组件化: Vue.js,采用,组件化,的,开发,方式,把,页面,拆成,一个个,组件,组件,可复用,可维护,提高,开发效率。
  4. 数据驱动: Vue.js,采用,数据驱动,的,方式,数据,变化,视图,自动,更新,不用,手动,操作DOM,开发,更,高效。
  5. 生态,丰富: Vue.js,的,生态,很,丰富,有,官方的,路由vue-router,状态管理vuex,构建工具vue-cli,等等,还有,很多,第三方,组件库,比如,Element UI,Vuetify,等等,能,满足,各种,需求。

2017年,Vue.js,已经,很,流行了,版本,大概,是,2.x,很多,公司,都,在用,Vue.js,开发,前端项目,特别是,后台管理系统,用Vue.js + Element UI,非常,方便,高效。

二、项目初始化

首先,我们,需要,初始化,一个,Vue.js,项目,官方,提供了,vue-cli,脚手架工具,能,快速,创建,项目。

首先,安装,vue-cli:

npm install -g vue-cli

然后,用,vue-cli,创建,项目,我们,用,webpack,模板:

vue init webpack my-admin

然后,会,提示,你,输入,项目名,描述,作者,等等,按照,提示,输入,就行,然后,会,问你,要不要,安装,vue-router,ESLint,单元测试,等等,后台管理系统,一般,需要,vue-router,所以,选,Yes,其他的,根据,需要,选择。

创建,完成后,进入,项目目录,安装,依赖:

cd my-admin
npm install

然后,启动,开发服务器:

npm run dev

这样,项目,就,启动了,访问,http://localhost:8080,就能,看到,Vue.js的,欢迎页面了。

项目,的,目录结构,大概,是,这样的:

my-admin/
├── build/              # webpack配置
├── config/             # 项目配置
├── src/                # 源代码
│   ├── assets/         # 静态资源
│   ├── components/     # 公共组件
│   ├── router/         # 路由配置
│   ├── App.vue         # 根组件
│   └── main.js         # 入口文件
├── static/             # 静态文件
├── index.html          # HTML模板
└── package.json        # 项目配置

三、安装Element UI

后台管理系统,一般,需要,很多,UI组件,比如,表格,表单,按钮,对话框,菜单,等等,我们,不用,自己,从零,写,可以,用,Element UI,这是,饿了么,团队,开发的,Vue.js,组件库,专门,为,后台管理系统,设计的,组件,很,丰富,很,好用。

安装,Element UI:

npm install element-ui --save

然后,在,main.js,里,引入,Element UI:

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

Vue.use(ElementUI)
Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

这样,Element UI,就,安装,好了,我们,就,能,在,项目里,使用,Element UI的,组件了。

四、路由配置

后台管理系统,一般,有,很多,页面,比如,登录页,首页,用户管理,角色管理,菜单管理,等等,需要,配置,路由。

我们,在,src/router/index.js,里,配置,路由:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/login/index')
    },
    {
      path: '/',
      component: () => import('@/views/layout/index'),
      redirect: '/dashboard',
      children: [
        {
          path: 'dashboard',
          name: 'Dashboard',
          component: () => import('@/views/dashboard/index'),
          meta: { title: '首页', icon: 'dashboard' }
        },
        {
          path: 'user',
          name: 'User',
          component: () => import('@/views/user/index'),
          meta: { title: '用户管理', icon: 'user' }
        },
        {
          path: 'role',
          name: 'Role',
          component: () => import('@/views/role/index'),
          meta: { title: '角色管理', icon: 'role' }
        }
      ]
    }
  ]
})

这里,我们,用了,路由懒加载,component: () => import('@/views/xxx'),这样,每个,页面,会,单独,打包,按需加载,提高,首屏,加载速度。

我们,把,路由,分成了,两部分,登录页,是,独立的,没有,布局,其他,页面,都,在,布局,组件,的,children里,这样,这些,页面,都会,有,统一的,布局,包括,侧边栏,头部,内容区,等等。

每个,路由,的,meta,里,我们,放了,title,和,icon,用来,在,侧边栏,菜单,里,显示,标题,和,图标。

五、布局组件

后台管理系统,一般,有,统一的,布局,包括,侧边栏,菜单,顶部,导航,内容区,等等,我们,创建,一个,布局,组件,src/views/layout/index.vue:

<template>
  <el-container class="layout-container">
    <!-- 侧边栏 -->
    <el-aside width="200px" class="sidebar">
      <div class="logo">后台管理系统</div>
      <el-menu
        :default-active="activeMenu"
        router
        background-color="#304156"
        text-color="#bfcbd9"
        active-text-color="#409EFF">
        <el-menu-item v-for="item in menuList" :key="item.path" :index="item.path">
          <i :class="'el-icon-' + item.meta.icon"></i>
          <span slot="title">{{ item.meta.title }}</span>
        </el-menu-item>
      </el-menu>
    </el-aside>

    <!-- 主内容区 -->
    <el-container>
      <!-- 顶部 -->
      <el-header class="header">
        <div class="header-left">
          <i class="el-icon-fold" @click="toggleSidebar"></i>
        </div>
        <div class="header-right">
          <el-dropdown>
            <span class="user-info">
              <img src="" class="avatar">
              {{ username }}
              <i class="el-icon-arrow-down"></i>
            </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>
      </el-header>

      <!-- 内容区 -->
      <el-main class="main-content">
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
export default {
  name: 'Layout',
  data() {
    return {
      username: 'admin',
      sidebarOpened: true
    }
  },
  computed: {
    activeMenu() {
      return this.$route.path
    },
    menuList() {
      // 从路由里过滤出需要显示的菜单
      return this.$router.options.routes.find(r => r.path === '/').children
    }
  },
  methods: {
    toggleSidebar() {
      this.sidebarOpened = !this.sidebarOpened
    },
    logout() {
      // 清除登录信息,跳转到登录页
      this.$router.push('/login')
    }
  }
}
</script>

<style scoped>
.layout-container {
  height: 100vh;
}
.sidebar {
  background-color: #304156;
}
.logo {
  height: 60px;
  line-height: 60px;
  text-align: center;
  color: #fff;
  font-size: 16px;
  font-weight: bold;
}
.header {
  background-color: #fff;
  border-bottom: 1px solid #e6e6e6;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.user-info {
  cursor: pointer;
}
.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  margin-right: 10px;
  vertical-align: middle;
}
.main-content {
  background-color: #f0f2f5;
  padding: 20px;
}
</style>

这个,布局,组件,包括,侧边栏,菜单,顶部,导航,用户信息,退出登录,内容区,等等,用了,Element UI的,Container,Menu,Dropdown,等,组件,很,方便。

侧边栏,菜单,是,从,路由,里,动态,生成的,这样,我们,只需要,配置,路由,菜单,就,自动,生成了,很,方便。

六、登录页

后台管理系统,一般,需要,登录,才能,访问,我们,创建,登录页,src/views/login/index.vue:

<template>
  <div class="login-container">
    <el-card class="login-card">
      <h2 class="login-title">后台管理系统登录</h2>
      <el-form :model="loginForm" :rules="rules" ref="loginForm" label-width="0">
        <el-form-item prop="username">
          <el-input v-model="loginForm.username" prefix-icon="el-icon-user" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input v-model="loginForm.password" prefix-icon="el-icon-lock" type="password" placeholder="请输入密码" @keyup.enter.native="handleLogin"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" class="login-btn" @click="handleLogin" :loading="loading">登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'Login',
  data() {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      rules: {
        username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
      },
      loading: false
    }
  },
  methods: {
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          // 这里,调用登录接口,实际项目中,替换成真实的接口
          setTimeout(() => {
            // 登录成功,保存token,跳转到首页
            localStorage.setItem('token', 'mock-token')
            this.$router.push('/')
            this.loading = false
          }, 1000)
        }
      })
    }
  }
}
</script>

<style scoped>
.login-container {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #2c3e50;
}
.login-card {
  width: 400px;
  padding: 20px;
}
.login-title {
  text-align: center;
  margin-bottom: 30px;
  color: #333;
}
.login-btn {
  width: 100%;
}
</style>

登录页,用了,Element UI的,Card,Form,Input,Button,等,组件,有,表单验证,登录,按钮,loading,状态,等等,登录成功后,保存token,到localStorage,然后,跳转到,首页。

实际项目中,登录,需要,调用,真实的,接口,这里,我们,用setTimeout,模拟了,一下。

七、路由守卫和权限控制

登录,之后,我们,需要,控制,页面的,访问权限,没有,登录的,用户,不能,访问,需要,登录的,页面,我们,用,路由守卫,来,实现。

在,src/main.js,里,添加,路由守卫:

router.beforeEach((to, from, next) => {
  // 获取token
  const token = localStorage.getItem('token')
  
  // 如果,要去登录页,直接放行
  if (to.path === '/login') {
    next()
    return
  }
  
  // 如果,没有token,跳转到登录页
  if (!token) {
    next('/login')
    return
  }
  
  // 有token,放行
  next()
})

这样,没有,登录的,用户,访问,需要,登录的,页面,就,会,自动,跳转到,登录页,很,方便。

更,复杂的,权限控制,比如,不同的,角色,有,不同的,菜单,和,按钮,权限,需要,根据,用户的,角色,动态,生成,路由,和,菜单,这个,稍微,复杂一点,我们,这里,就,不,详细,讲了,初学者,先,掌握,基础的,登录,控制,就行。

八、接口请求封装

后台管理系统,需要,调用,很多,后端接口,我们,一般,会,封装,一下,axios,统一,处理,请求,和,响应,比如,添加,token,处理,错误,等等。

首先,安装,axios:

npm install axios --save

然后,创建,src/utils/request.js,封装,axios:

import axios from 'axios'
import { Message } from 'element-ui'
import router from '@/router'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // 接口的base_url
  timeout: 10000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    // 在这里,添加token
    if (localStorage.getItem('token')) {
      config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('token')
    }
    return config
  },
  error => {
    console.log(error)
    Promise.reject(error)
  }
)

// response拦截器
service.interceptors.response.use(
  response => {
    const res = response.data
    // 这里,根据后端的,返回格式,处理
    if (res.code !== 200) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      
      // 401,未登录,或者,token过期,跳转到登录页
      if (res.code === 401) {
        localStorage.removeItem('token')
        router.push('/login')
      }
      
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error)
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

这样,我们,就,封装好了,axios,统一,添加,token,统一,处理,错误,401,自动,跳转到,登录页,等等,很,方便。

然后,我们,就,能,在,项目里,使用,这个,封装好的,request,调用,接口了,比如,创建,src/api/user.js:

import request from '@/utils/request'

// 获取用户列表
export function getUserList(params) {
  return request({
    url: '/user/list',
    method: 'get',
    params
  })
}

// 新增用户
export function addUser(data) {
  return request({
    url: '/user/add',
    method: 'post',
    data
  })
}

// 编辑用户
export function updateUser(data) {
  return request({
    url: '/user/update',
    method: 'put',
    data
  })
}

// 删除用户
export function deleteUser(id) {
  return request({
    url: '/user/delete/' + id,
    method: 'delete'
  })
}

然后,在,页面里,调用:

import { getUserList } from '@/api/user'

export default {
  data() {
    return {
      userList: [],
      loading: false
    }
  },
  created() {
    this.fetchUserList()
  },
  methods: {
    async fetchUserList() {
      this.loading = true
      try {
        const res = await getUserList({ page: 1, size: 10 })
        this.userList = res.data
      } catch (error) {
        console.error(error)
      } finally {
        this.loading = false
      }
    }
  }
}

这样,接口,请求,就,封装,好了,很,清晰,很,方便,维护。

九、用户管理页面示例

下面,我们,做一个,用户管理页面,示例,看看,后台管理系统的,页面,一般,怎么,写。

创建,src/views/user/index.vue:

<template>
  <div class="user-container">
    <!-- 搜索栏 -->
    <el-card class="search-card">
      <el-form :inline="true" :model="searchForm">
        <el-form-item label="用户名">
          <el-input v-model="searchForm.username" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchForm.status" placeholder="请选择状态">
            <el-option label="正常" :value="1"></el-option>
            <el-option label="禁用" :value="0"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 操作栏 -->
    <el-card class="table-card">
      <div class="table-header">
        <el-button type="primary" @click="handleAdd">新增用户</el-button>
      </div>

      <!-- 表格 -->
      <el-table :data="userList" v-loading="loading" border stripe>
        <el-table-column prop="id" label="ID" width="80"></el-table-column>
        <el-table-column prop="username" label="用户名"></el-table-column>
        <el-table-column prop="email" label="邮箱"></el-table-column>
        <el-table-column prop="phone" label="手机号"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template slot-scope="scope">
            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
              {{ scope.row.status === 1 ? '正常' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="create_time" label="创建时间" width="180"></el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template slot-scope="scope">
            <el-button size="mini" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页 -->
      <div class="pagination">
        <el-pagination
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="pagination.page"
          :page-sizes="[10, 20, 50, 100]"
          :page-size="pagination.size"
          layout="total, sizes, prev, pager, next, jumper"
          :total="pagination.total">
        </el-pagination>
      </div>
    </el-card>

    <!-- 新增/编辑对话框 -->
    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px">
      <el-form :model="userForm" :rules="rules" ref="userForm" label-width="80px">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="userForm.username" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="userForm.email" placeholder="请输入邮箱"></el-input>
        </el-form-item>
        <el-form-item label="手机号" prop="phone">
          <el-input v-model="userForm.phone" placeholder="请输入手机号"></el-input>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="userForm.status">
            <el-radio :label="1">正常</el-radio>
            <el-radio :label="0">禁用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="handleSubmit">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getUserList, addUser, updateUser, deleteUser } from '@/api/user'

export default {
  name: 'User',
  data() {
    return {
      loading: false,
      searchForm: {
        username: '',
        status: ''
      },
      userList: [],
      pagination: {
        page: 1,
        size: 10,
        total: 0
      },
      dialogVisible: false,
      dialogTitle: '新增用户',
      userForm: {
        id: null,
        username: '',
        email: '',
        phone: '',
        status: 1
      },
      rules: {
        username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }]
      }
    }
  },
  created() {
    this.fetchUserList()
  },
  methods: {
    // 获取用户列表
    async fetchUserList() {
      this.loading = true
      try {
        const params = {
          page: this.pagination.page,
          size: this.pagination.size,
          ...this.searchForm
        }
        const res = await getUserList(params)
        this.userList = res.data.list
        this.pagination.total = res.data.total
      } catch (error) {
        console.error(error)
      } finally {
        this.loading = false
      }
    },
    // 搜索
    handleSearch() {
      this.pagination.page = 1
      this.fetchUserList()
    },
    // 重置
    handleReset() {
      this.searchForm = {
        username: '',
        status: ''
      }
      this.handleSearch()
    },
    // 分页大小改变
    handleSizeChange(size) {
      this.pagination.size = size
      this.pagination.page = 1
      this.fetchUserList()
    },
    // 当前页改变
    handleCurrentChange(page) {
      this.pagination.page = page
      this.fetchUserList()
    },
    // 新增
    handleAdd() {
      this.dialogTitle = '新增用户'
      this.userForm = {
        id: null,
        username: '',
        email: '',
        phone: '',
        status: 1
      }
      this.dialogVisible = true
    },
    // 编辑
    handleEdit(row) {
      this.dialogTitle = '编辑用户'
      this.userForm = { ...row }
      this.dialogVisible = true
    },
    // 删除
    handleDelete(row) {
      this.$confirm('确定要删除该用户吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          await deleteUser(row.id)
          this.$message.success('删除成功')
          this.fetchUserList()
        } catch (error) {
          console.error(error)
        }
      }).catch(() => {})
    },
    // 提交
    handleSubmit() {
      this.$refs.userForm.validate(async valid => {
        if (valid) {
          try {
            if (this.userForm.id) {
              await updateUser(this.userForm)
              this.$message.success('编辑成功')
            } else {
              await addUser(this.userForm)
              this.$message.success('新增成功')
            }
            this.dialogVisible = false
            this.fetchUserList()
          } catch (error) {
            console.error(error)
          }
        }
      })
    }
  }
}
</script>

<style scoped>
.user-container {
  padding: 0;
}
.search-card {
  margin-bottom: 20px;
}
.table-card {
  margin-bottom: 20px;
}
.table-header {
  margin-bottom: 20px;
}
.pagination {
  margin-top: 20px;
  text-align: right;
}
</style>

这个,用户管理页面,包含了,搜索栏,操作栏,表格,分页,新增/编辑对话框,删除确认,等等,是,后台管理系统,最,常见的,页面,类型,用了,Element UI的,Form,Table,Pagination,Dialog,Button,等,组件,很,方便,很,实用。

其他的,页面,比如,角色管理,菜单管理,等等,都是,类似的,结构,我们,可以,参照,这个,用户管理页面,来,写。

十、状态管理Vuex

如果,项目,比较,复杂,需要,共享,一些,状态,比如,用户信息,菜单,权限,等等,我们,可以,用,Vuex,来,管理,状态。

安装,Vuex(vue-cli,创建项目的时候,如果,选了,Vuex,就,已经,安装好了):

npm install vuex --save

然后,创建,src/store/index.js:

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import app from './modules/app'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user,
    app
  }
})

然后,创建,src/store/modules/user.js:

import { getUserInfo } from '@/api/user'

const state = {
  token: localStorage.getItem('token') || '',
  userInfo: null
}

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
    localStorage.setItem('token', token)
  },
  SET_USER_INFO: (state, userInfo) => {
    state.userInfo = userInfo
  },
  LOGOUT: (state) => {
    state.token = ''
    state.userInfo = null
    localStorage.removeItem('token')
  }
}

const actions = {
  // 获取用户信息
  async getUserInfo({ commit }) {
    const res = await getUserInfo()
    commit('SET_USER_INFO', res.data)
    return res.data
  },
  // 退出登录
  logout({ commit }) {
    commit('LOGOUT')
  }
}

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

然后,在,main.js,里,引入,store:

import store from './store'

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

这样,我们,就,能,在,项目里,使用,Vuex,管理,状态了,比如,在,组件里:

// 获取state
this.$store.state.user.token

// 提交mutation
this.$store.commit('user/SET_TOKEN', 'new-token')

// 分发action
this.$store.dispatch('user/getUserInfo')

Vuex,能,让,状态,管理,更,清晰,更,方便,特别是,复杂的,项目,很,有用。

十一、项目打包和部署

开发,完成后,我们,需要,打包,项目,部署到,服务器上。

打包,命令:

npm run build

打包,完成后,会,生成,dist,目录,里面,就是,打包好的,静态文件,HTML,CSS,JS,图片,等等。

然后,我们,把,dist,目录,部署到,Nginx,上,配置,Nginx:

server {
    listen 80;
    server_name your-domain.com;

    root /path/to/dist;
    index index.html;

    # 前端路由,需要配置,否则,刷新页面,会404
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 接口代理
    location /api/ {
        proxy_pass http://your-backend-server/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 静态文件缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

这里,需要,注意,Vue.js的,前端路由,是,history模式的话,刷新页面,会,404,需要,配置,try_files $uri $uri/ /index.html;,把,所有,请求,都,指向,index.html,让,前端路由,来,处理。

接口,代理,我们,用,Nginx,把,/api/,开头的,请求,代理到,后端服务器,这样,就,不会,有,跨域,问题了。

静态文件,我们,配置了,缓存,30天,提高,加载速度。

这样,项目,就,部署,好了,访问,your-domain.com,就能,看到,我们的,后台管理系统了。

十二、写在最后

Vue.js入门实战:从0到1搭建一个后台管理系统。

这篇文章,我们,从0到1,搭建了,一个,Vue.js的,后台管理系统,包括,项目,初始化,Element UI,安装,路由,配置,布局,组件,登录页,路由守卫,接口,请求,封装,用户管理页面,示例,Vuex,状态管理,项目,打包,部署,等等,基本,覆盖了,后台管理系统,开发的,全流程。

Vue.js,真的,很,简单,易学,很,适合,开发,后台管理系统,配合,Element UI,组件库,开发,效率,非常,高,能,快速,搭建,一个,功能,完善的,后台管理系统。

当然,这篇文章,只是,入门,实战,很多,细节,比如,权限,控制,动态,路由,菜单,按钮,权限,字典,管理,导入,导出,等等,都,没有,详细,讲,但是,掌握了,基础的,流程,和,方法,这些,都,能,自己,慢慢,实现。

希望,这篇文章,能,帮,Vue.js的,初学者,快速,上手,开发,后台管理系统,也,希望,大家,都能,学好,Vue.js,开发出,优秀的,前端项目。

最后,用一句话结尾:

"Vue.js,让,前端开发,变得,简单,高效,优雅,掌握了,Vue.js,你,就能,快速,构建,各种,复杂的,前端应用。"

祝大家,学习,顺利,开发,愉快!