Skip to content

项目介绍与 vue-admin 认知(二) ​

本页对应 项目介绍与 vue-admin 认知,共 7 段视频,约 1 小时 13 分。

学习目标 ​

  • 能够说明 Vuex 中 state、mutations、actions、modules 的职责,并在模块开启 namespaced 后完成模块注册、状态读取、commit 与 dispatch 调用。
  • 能够基于 axios 配置请求实例的 baseURL 与 timeout,编写请求/响应拦截器实现 token 注入、响应数据剥离和 401 处理,并确保 return 配置正确。
  • 能够依据接口文档在 api 模块中封装 GET、POST、DELETE 请求函数,正确使用 params、data 和路径参数,并补充 JSDoc 参数说明。
  • 能够解释路由表与左侧菜单的生成关系,并通过修改路由 meta 中的 title、icon、hidden 定制菜单显示。
  • 能够使用 Gitee 完成本地仓库初始化、关联远程仓库、提交代码并查看提交记录,避免直接沿用原远程绑定。
  • 能够使用 Element UI 的 form、form-item、input 组件完成登录表单非空校验,正确绑定 model、rules、prop 和 v-model,并验证 blur 触发校验效果。

知识点 ​

核心业务闭环与在线预览 ​

本节的核心是理解“后台管理系统 + 前台可视化项目”如何形成业务闭环:后台管理系统负责园区内各项数据的管理,前台可视化项目负责重点园区数据的展现。先看在线预览地址:https://fe-hmzs.itheima.net/workbench。
为什么需要:只有后台录入、维护数据,前台才有数据可展示;只有前台把重点园区数据呈现出来,后台管理的数据才有业务价值。
怎么想:把闭环拆成“后台管理数据 → 接口提供数据 → 前台请求并可视化展示 → 根据展示结果回到后台维护”。
怎么做:后台侧关注数据管理页面,前台侧关注接口请求与图表展示;项目初始化中的 Vuex、axios、API 封装、路由菜单、登录校验,都是为这个闭环打基础。
易错点:不要把后台管理和前台可视化当成两个孤立项目;它们通过接口和统一的数据结构连接。

Vuex:集中状态管理 ​

为什么需要:多个组件、甚至多个 .vue 和 .js 文件都要用的数据,如果各自维护会互相干扰,所以需要集中状态管理。
怎么想:Vuex 把共享状态集中到 store;项目用模块化时,模块要加 namespaced: true,并在 modules 中注册。
怎么做:state 放状态,mutations 同步修改状态,actions 处理异步请求并最终提交 mutation。组件里可用 this.$store.state.user.name,提交用 commit('user/xxx'),触发异步用 dispatch('user/xxx');数据多时再用 map 系列映射方法。

js
// store/modules/user.js
export default {
  namespaced: true,
  state: { name: '' },
  mutations: { setUser(state, payload) { state.name = payload } },
  actions: { getUser({ commit }) { /* 请求后 commit */ } }
}
// store/index.js
import user from './modules/user'
export default new Vuex.Store({ modules: { user } })

易错点:namespaced 容易丢;新增模块必须在 modules 注册;修改 state 要走 mutation;state 写对象还是函数要理解复用影响。

axios:请求模块的通用配置 ​

为什么需要:项目里接口很多,基地址、超时、token、响应数据处理如果每个接口都写一遍,既重复又容易漏。
怎么想:把 axios 配置分成通用配置和定制化配置;通用配置主要做实例化、请求拦截器、响应拦截器。
怎么做:axios.create({ baseURL, timeout: 5000 }) 配基地址和超时;请求拦截器统一加 token;响应拦截器做数据剥离,遇到 401 清 token 并回登录页。

js
const request = axios.create({ baseURL: '基地址', timeout: 5000 })
request.interceptors.request.use(config => {
  config.headers.token = token
  return config
})
request.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response.status === 401) { /* 清 token,跳登录 */ }
    return Promise.reject(error)
  }
)

易错点:请求拦截器漏 return config,后端可能收不到参数;响应拦截器漏 return response.data,业务层拿不到数据;params 不要写成 parents。

API:按接口封装请求函数 ​

为什么需要:每个接口单独封装成函数,调用风格统一,业务组件不直接关心 axios 细节。
怎么想:看一个函数就看三件事——参数、逻辑、返回值。参数看注释和接口文档,逻辑是调用 request,返回值是 Promise。
怎么做:从 request 导入实例,按接口写函数;GET 用 params,POST/PUT 等请求体用 data;删除类接口常把 id 拼到路径里。
易错点:params 拼写固定;URL 中不要多空格;method 用全大写如 GET;注释要写清参数类型和含义,避免别人维护时还要翻接口文档。

路由与菜单:用 meta 驱动左侧菜单 ​

为什么需要:左侧菜单不应手写死,而应由路由表生成,改路由就能改菜单。
怎么想:菜单组件遍历 router.options.routes,每个路由对象的 meta 提供 title、icon、hidden 等展示信息。
怎么做:在路由 meta 中写 title: '园区管理'、icon: 'building';图标名来自 Element UI 图标库;hidden: true 时不在左侧显示。
易错点:meta 不要写成 meal;icon 不要写成 icen;hidden 控制隐藏;permission 是后续权限控制内容,暂不展开。

登录表单:基础校验 ​

为什么需要:账号、密码不能为空,提交前要有基础校验,减少无效请求。
怎么想:Element UI 表单由 el-form、el-form-item、el-input 配合完成:外层绑 model 和 rules,表单项用 prop 指定规则,输入框用 v-model 双向绑定。
怎么做:表单字段最好和接口字段一致,如 username、password;规则写 required: true、message、trigger: 'blur'。

js
const form = reactive({ username: '', password: '' })
const rules = {
  username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}

易错点:prop 要和 rules 的 key 匹配;model 是属性不是指令;聚焦再失焦可调试 blur 校验是否生效。

Gitee:代码管理与提交记录 ​

为什么需要:每天、每小节的代码要可追踪,复习时能看提交记录。
怎么想:先切断原远程仓库,再创建本地仓库并关联新远程。
怎么做:删除原 .git,git init,关联新仓库,首次提交并推送;提交后可将仓库开源并保存地址。
易错点:不要直接把原仓库挂到新远程;提交前确认远程关联正确。

先看周期:11天怎么拆 ​

本节笔记给出的项目周期很明确:后台部分 8 天 + 可视化前台部分 3 天 = 11 天。
为什么需要:先有周期意识,才知道哪些内容必须打牢、哪些可以后置。后台 8 天占大头,说明项目初始化、状态管理、请求封装、路由菜单、登录校验这些“地基”不能拖到前台阶段再补。
怎么想:把 11 天看成“后台主链路 8 天 + 前台展示 3 天”,后台先保证接口通、状态通、路由通、登录通;前台 3 天再做可视化呈现。转写稿没有展开前台 3 天的细节,这里不虚构,只强调后台阶段的关键文件要按顺序推进。
怎么做:按转写稿顺序,后台可串成:项目初始化与 Gitee 管理 → Vuex → request → api 封装 → 路由与菜单 → 登录表单基础校验。每天按小模块提交,方便复习提交记录。
易错点:不要把 11 天理解成“只写页面”;后台联调、配置、改错都占时间。转写稿里反复强调“每天按小模块提交”,就是为了让周期推进可追踪。

后台阶段一:项目初始化与 Gitee 管理 ​

为什么需要:项目一开始若还绑着原来的远程仓库,后续提交会混乱。转写稿明确要先切断原本的 Git 仓库,再新建本地仓库并与自己的 Gitee 远程关联。
怎么想:先“断旧关系”,再“建本地仓库”,再“关联远程”,最后提交并开源,便于查看代码。
怎么做:删除原 .git,在本地初始化仓库,执行远程关联命令,完成首次提交;刷新 Gitee 能看到代码后,把仓库设为开源并保存地址。
易错点:不要直接往原远程挂;首次提交要按提示完成绑定。转写稿还提醒:后面每小节都会提交,复习时看提交记录即可。

后台阶段二:Vuex 集中状态管理 ​

为什么需要:多个组件、甚至 .js 与 .vue 之间都要共享数据时,逐层传参很乱。转写稿的判断标准是:两个及以上地方要用,就可考虑放进 Vuex。
怎么想:Vuex 是集中状态管理工具。模块化时要加 namespaced: true,新增模块必须在 modules 中注册。state 放数据,mutations 同步修改,actions 放异步请求并最终提交 mutation。
怎么做:以 user 模块为例,转写稿的写法可整理为:

js
// store/modules/user.js
export default {
  namespaced: true,
  state: { name: '' },
  mutations: { /* 同步修改 */ },
  actions: { /* 异步请求,再 commit */ }
}
js
// store/index.js
import user from './modules/user'
export default {
  modules: { user }
}

组件里读取:this.$store.state.user.name;提交:this.$store.commit('user/xxx');派发:this.$store.dispatch('user/xxx')。
易错点:namespaced 最容易丢;模块没注册就不生效;state 写对象还是函数要分清——对象写法在复用时有互相干扰风险,函数写法每次返回新对象。转写稿还提醒 Vue3 后 Vuex 逐步被 Pinia 取代,但当前项目仍要会看会改。

后台阶段三:request 与 api 封装 ​

为什么需要:每个接口都重复写 baseURL、超时、token、错误处理太蠢。request 模块负责通用配置,api 模块负责“一个接口一个函数”。
怎么想:request 分三块:axios 实例化、请求拦截器、响应拦截器。实例化配 baseURL 和 timeout;请求拦截器统一加 token;响应拦截器做数据剥离、401 处理。api 函数按“参数—逻辑—返回值”三要素看。
怎么做:

js
const request = axios.create({
  baseURL: '...',
  timeout: 5000
})
// 请求拦截:加 token
// 响应拦截:return response.data
js
export function getEnterpriseList(params) {
  return request({
    url: '/...',
    method: 'get',
    params
  })
}

易错点:转写稿强调两个 return 千万不能丢:请求拦截里丢 return,后端收不到参数;响应拦截里丢 return,前端拿不到数据。URL 不能多空格;method 不要写成 gets;params 不能改名;get 用 params,post 复杂请求用 data,路径参数要拼在 URL 上并保留斜杠。

后台阶段四:路由菜单与登录表单校验 ​

为什么需要:左侧菜单不是手写死的,而是由路由表生成;登录页要先做基础校验,避免空账号空密码提交。
怎么想:路由对象的 meta 里放 title、icon、hidden,菜单组件遍历 router.options.routes 渲染;hidden: true 就不显示。表单校验按“外层绑定 model/rules,里层 prop 指定规则,最里层 v-model 双向绑定”分工。
怎么做:改菜单标题和图标,只改路由 meta.title 和 meta.icon;图标名从 UI 组件库图标集合里取。登录表单准备 form 与 rules,规则用 required、message、trigger: 'blur',提交时聚焦再提交可看到“请输入账号/请输入密码”,输入后通过。
易错点:form 字段名最好跟接口字段一致,如 username、password,提交时可少改代码;prop 必须和规则键匹配;v-model 要绑到表单对象的具体字段上。

周期收尾:每天提交、按模块复习 ​

为什么需要:11 天周期里内容多,靠记忆不现实。
怎么想:把每天的小模块提交当成“时间线笔记”。
怎么做:每完成一个模块就提交一次,提交信息写清楚;复习时看提交记录和代码 diff。
易错点:不要攒到最后一次提交;不要把配置文件和业务代码混在一起提交,否则出问题难定位。

代码示例 ​

一、Vuex 模块化:user 模块 + store 入口注册 ​

js
// store/modules/user.js
export default {
  // ① 严格模块开发必须加命名空间
  namespaced: true,

  // ② state 放共享数据(对象写法:该模块不要复用)
  state: {
    name: ''
  },

  // ③ mutations:同步修改,且是唯一修改 state 的方式
  mutations: {
    setName(state, name) {
      state.name = name
    }
  },

  // ④ actions:异步(真实项目里:发请求 + commit 提交 mutation)
  actions: {
    setUserNameAsync({ commit }, name) {
      return new Promise((resolve) => {
        setTimeout(() => {
          commit('setName', name) // 最终还是要靠 commit 才能改 state
          resolve()
        }, 1000)
      })
    }
  }
}
js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user' // 默认导出 → 默认导入,名字可改

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user // ⑤ 新增模块必须在这里注册,key 与模块名保持一致
  }
})
js
// 组件中使用
// 读数据:模块名 + 属性名
const name = this.$store.state.user.name

// 触发 mutation:模块名/方法名
this.$store.commit('user/setName', '小明')

// 触发 action:模块名/方法名,第二个参数是传参
await this.$store.dispatch('user/setUserNameAsync', '小明')

关键行与易错点

  • namespaced: true 极易漏写,漏了就不算严格模块化,user/xxx 的写法会失效。
  • modules 里的 key(user)是 Vuex 识别模块的依据,导入的变量名可以改,但这个 key 要和模块名对应。
  • state 写成对象时该模块不要被复用;写成函数则每次返回全新状态对象,避免组件间互相干扰(与 data 必须写函数的道理相同)。
  • 修改 state 只有一条路:提交 mutation。action 里也不能直接改,必须 commit。
  • 组件里用 this.$store.state.user.name 这种写法适合"用的东西很少"的场景;用到很多数据/很多方法时再用 mapState、mapMutations、mapActions 映射。

二、axios 请求模块 + API 接口封装 ​

js
// utils/request.js
import axios from 'axios'

// ① 实例化:通用配置 = 基地址 + 超时时间
const request = axios.create({
  baseURL: 'http://你的项目基地址.com', // 后面接口只写相对路径,axios 自动拼接
  timeout: 5000 // 超过 5 秒视为接口失败
})

// ② 请求拦截器:在参数进入后端之前统一处理(加 token 做接口鉴权)
request.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.token = token
    }
    return config // 这个 return 丢了 = 参数啥也没发出去
  },
  (error) => Promise.reject(error)
)

// ③ 响应拦截器:返回客户端之前做二次处理
request.interceptors.response.use(
  (response) => {
    // 数据剥离:业务组件里就不用再 .data 了
    return response.data
  },
  (error) => {
    // 401 = 未登录/标识失效,清掉标识回登录页
    if (error.response && error.response.status === 401) {
      localStorage.removeItem('token')
      location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export default request
js
// api/enterprise.js
import request from '@/utils/request'

/**
 * 获取企业列表
 * @param {Object} params 查询参数 { page, pageSize, name }
 */
export function getEnterpriseListAPI(params) {
  return request({
    url: '/park/enterprise', // 不能有空格,空白也是有效字符
    method: 'GET', // 注意不是 GETS,加 s 识别不了
    params // 查询参数必须叫 params
  })
}

/**
 * 删除企业
 * @param {Number} id 企业 id
 */
export function delEnterpriseAPI(id) {
  return request({
    url: `/park/enterprise/${id}`, // 路径参数用模板字符串拼接,斜杠不能丢
    method: 'DELETE'
  })
}
js
// 组件中调用:函数返回值是 Promise,可用 await 模拟同步
async function getList() {
  try {
    const res = await getEnterpriseListAPI({ page: 1, pageSize: 10 })
    console.log(res)
  } catch (e) {
    console.log(e)
  }
}

关键行与易错点

  • 两个 return 一个都不能丢:请求拦截器丢了 return config 等于没传参;响应拦截器丢了 return response.data 等于数据没了。
  • url 里千万别多空格;method 千万别加 s;params、data 这些 key 是 axios 内部认死的名字,不能改名。
  • POST/PUT 这类复杂请求的请求体必须叫 data;GET/DELETE 的查询参数用 params。
  • Content-Type: application/json 是 axios 的默认配置,不用手写。
  • 看懂一个接口函数就看三要素:参数(类型/必填字段)、逻辑(调 request 发的什么请求)、返回值(Promise,用 .then 或 await 取数据)。

三、Element 表单基础校验:三层组件配合 ​

vue
<template>
  <!-- 最外层:绑定表单对象 + 规则对象 -->
  <el-form :model="form" :rules="rules" ref="formRef">
    <!-- 中间层:用 prop 指定当前项要用规则对象里的哪条规则 -->
    <el-form-item prop="username">
      <!-- 最里层:具体表单元素,只负责双向绑定 -->
      <el-input v-model="form.username" placeholder="请输入账号" />
    </el-form-item>

    <el-form-item prop="password">
      <el-input v-model="form.password" type="password" placeholder="请输入密码" />
    </el-form-item>

    <el-button type="primary" @click="handleSubmit">登录</el-button>
  </el-form>
</template>

<script>
export default {
  // data 必须是函数
  data() {
    return {
      // 表单对象的 key 以接口字段为准,提交时省去转换
      form: {
        username: '',
        password: ''
      },
      // 规则对象的 key 必须和 form 里的 key 一一对应
      rules: {
        username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
      }
    }
  },
  methods: {
    handleSubmit() {
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          // 校验通过,走登录提交
        }
      })
    }
  }
}
</script>

关键行与易错点

  • :model 和 :rules 是属性绑定(不是 v-model),拼写固定,都绑在最外层 el-form 上。
  • el-form-item 的 prop 是"规则匹配的 key",写错或漏写,该项就不会校验、也不会出现小红星。
  • v-model 只绑在最里层 el-input 上,绑的必须是 form.xxx 的完整路径。
  • rules 每个字段是数组(可配多条规则),元素才是对象;trigger: 'blur' 表示失焦时校验。
  • 三个组件各管一件事(绑表单对象、指定规则、双向绑定),这是"单一职责"的组合式设计,缺一层校验就不生效。

常见错误 ​

  1. Vuex 模块调不通 现象:组件里 dispatch('user/login') 报 unknown action type,mapState 拿到 undefined。 原因:模块漏写 namespaced: true;或 store/index.js 里 import 了却没在 modules 中注册;或注册的 key 与组件里使用的模块名不一致。 修法:模块内加 namespaced: true;modules: { user };组件里的模块名必须等于 modules 的 key(import 的变量名可随意改)。

  2. 拦截器断链,接口全废 现象:登录后所有请求拿不到数据,或后端收到的参数为空。 原因:请求拦截器忘记 return config;响应拦截器成功回调忘记 return。 修法:请求拦截器必须 return config;响应拦截器成功回调 return response(或 response.data),失败回调 return Promise.reject(err)。

  3. 请求参数放错位置 / 键名写错 现象:接口 400 或后端收不到参数,GET 请求 body 为空。 原因:GET/DELETE 传了 data,POST/PUT 传了 params;或 method 写成 getS、url 中混入空格、params 拼成 param。 修法:按接口文档判断 query 还是 body;method 全大写不带 S;url 只写相对路径且不能有空格;params/data 拼写固定。

  4. 路径参数拼接出错 现象:删除接口 404,请求路径少了斜杠或多出字面量 id。 原因:url 拼接漏 /;或没用模板字符串,把 ${id} 写成了普通字符串。 修法:写成 url: `/park/${id}`,斜杠与变量名逐字核对。

  5. 表单校验不生效 现象:没有红色星号,失焦/提交都不校验。 原因:el-form 上 :model / :rules 漏了冒号或被写成 v-model;el-form-item 的 prop、rules 的 key、form 对象的字段名三者不一致。 修法:三处命名统一,且以接口字段名为准;:model 绑表单对象、:rules 绑规则对象、prop 写字段名、v-model 写 form.字段名。

  6. 菜单改了没反应 现象:改标题/图标无效,加了 hidden 仍显示。 原因:改的不是 meta 里的 title/icon;hidden 写成字符串 'true' 或拼成 hide;图标名不在 Element Plus 图标库中。 修法:只改 meta.title、meta.icon,图标名从官方图标库复制;hidden: true 用布尔值。

  7. push 到别人的仓库 现象:提交报无权限,或代码跑到原仓库。 原因:项目是 clone 下来的,origin 仍指向原地址。 修法:删除原 .git 重新 init,或 git remote remove origin 后重新 add 自己的仓库地址。

练习 ​

  1. 新建 park 模块(开启 namespaced),state 存园区列表,写 mutation 更新、action 异步拉取,并在 index.js 中注册;在组件里用 mapState / mapActions 使用它。
  2. 按接口文档封装两个接口:企业列表(GET,query 参数)、删除园区(DELETE,路径参数),均带 JSDoc 注释并说明参数类型。
  3. 给 request 补全:请求拦截器注入 token,响应拦截器做数据剥离与 401 处理,并在注释中说明每个 return 丢失后的后果。
  4. 修改某条路由的菜单标题与图标,并把「系统管理」隐藏,刷新验证左侧菜单变化。
  5. 用 el-form 完成登录表单账号/密码非空校验,要求 rules 的 key 与登录接口字段名一致,提交时在控制台打印表单对象。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1项目关键文件-vuex11:59
2请求模块-request16:58
3请求模块-api09:23
4请求模块-封装请求11:55
5路由和菜单的关系09:36
6使用gitee管理代码04:26
7登录-表单基础校验实现08:53

小结 ​

  1. Vuex 模块可用 = namespaced + modules 注册 + 命名对齐,三者缺一即失效。
  2. 改 state 只有 mutation 一条路,action 的职责是「发请求 → 再 commit」。
  3. axios 通用配置三块:实例化(baseURL/timeout)、请求拦截(加 token)、响应拦截(剥数据、401 跳登录)。
  4. 拦截器是链式的,任何一环漏 return 都会让后续环节拿不到数据。
  5. api 层一个接口一个函数,看函数只需看三要素:参数、逻辑、返回值。
  6. 参数位置由请求方法决定:GET/DELETE 用 params,POST/PUT 用 data。
  7. 左侧菜单由路由表驱动,meta.title、meta.icon、hidden 是唯一入口。
  8. 表单校验靠三层字段名对齐;接手项目先切断原 git 远程再关联自己的仓库,按模块提交便于回看。