这一次的项目是基于上一次移动端的面经项目的后台管理部分,与移动端的不同,移动端主要在于对vant组件库的熟悉和学习,pc端则是在于对element-ui的熟悉和学习,整体来说,和移动端面经项目有很多的相似之处,能够从上次的项目中借鉴经验,也能借此来加深对这套架子的熟悉。
vant 项目的定位:
element 项目的定位:
接口文档: https://www.apifox.cn/apidoc/project-934563/api-19465917
接口根路径: http://interview-api-t.itheima.net/
本项目的技术栈 本项目技术栈基于 ES2015+、vue2、vuex3、vue-router3 、vue-cli5 、axios 和 element-ui
less sass stylus 都是 css 预处理器,语法上稍有差异,作用一样
都是让 css,增强能力,具备变量,函数.. 的能力
sass的语法两种语法 .sass(旧) .scss(新)
1 .sass 和 .stylus 语法很像 (了解)
要求省略 {} 和 分号, 缩进表示嵌套
2 .scss 和 .less 语法很像, 都支持嵌套, 变量...
scss 声明变量:$变量名
less 声明变量: @变量名
##调整项目目录
默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。主要是两个工作:
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
routes: []
})
export default router
src/App.vue
store/index.js 和 main.js 不用动
在 src 目录下中补充创建以下目录:
官方文档: https://element.eleme.io/#/zh-CN
全部引入, 会导入所有的组件, 但是体积会变大
yarn add element-ui
main.js
中import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
主要按钮
减轻将来打包后的包的体积
yarn add element-ui
babel-plugin-component
yarn add babel-plugin-component -D
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// 新增plugins插件节点,修改完配置文件一定重启项目
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
main.js
中import { Button } from 'element-ui'
Vue.use(Button)
由于组件的导入都书写到了main.js
中,导致main.js
代码冗余
将element-ui组件的导入和注册单独抽离到utils文件夹中
新建element.js
项目中 完整按需导入如下:
import Vue from 'vue';
import {
Popconfirm,
Avatar,
Breadcrumb,
BreadcrumbItem,
Pagination,
Dialog,
Menu,
Input,
Option,
Button,
Table,
TableColumn,
Form,
FormItem,
Icon,
Row,
Col,
Card,
Container,
Header,
Aside,
Main,
Footer,
Link,
Image,
Loading,
MessageBox,
Message,
Drawer,
MenuItem
} from 'element-ui';
Vue.use(Breadcrumb);
Vue.use(BreadcrumbItem);
Vue.use(Drawer);
Vue.use(Popconfirm);
Vue.use(Avatar);
Vue.use(Pagination);
Vue.use(Dialog);
Vue.use(Menu);
Vue.use(MenuItem);
Vue.use(Input);
Vue.use(Option);
Vue.use(Button);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Icon);
Vue.use(Row);
Vue.use(Col);
Vue.use(Card);
Vue.use(Container);
Vue.use(Header);
Vue.use(Aside);
Vue.use(Main);
Vue.use(Footer);
Vue.use(Link);
Vue.use(Image);
Vue.use(Loading.directive);
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
// 直接导入vant-ui.js
import '@/utils/element.js'
新建 styles/index.scss
// 修改主题色
$--color-primary: rgba(114,124,245,1);
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
body {
margin: 0;
padding: 0;
background: #fafbfe;
}
main.js 引入
import '@/styles/index.scss'
接口文档地址:https://www.apifox.cn/apidoc/project-934563/api-19465917
我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址等)
一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用
npm i axios
新建 utils/request.js
封装 axios 模块
利用 axios.create 创建一个自定义的 axios 来使用
http://www.axios-js.com/zh-cn/docs/#axios-create-config
/* 封装axios用于发送请求 */
import axios from 'axios'
// 创建一个新的axios实例
const request = axios.create({
baseURL: 'http://interview-api-t.itheima.net/',
timeout: 5000
})
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
export default request
新建 utils/storage.js
// 以前 token 令牌,如果存到了本地,每一次都写这么长,太麻烦
// localStorage.setItem(键, 值)
// localStorage.getItem(键)
// localStorage.removeItem(键)
const KEY = 'my-token-element-pc'
// 直接用按需导出,可以导出多个
// 但是按需导出,导入时必须 import { getToken } from '模块名导入'
// 获取
export const getToken = () => {
return localStorage.getItem(KEY)
}
// 设置
export const setToken = (newToken) => {
localStorage.setItem(KEY, newToken)
}
// 删除
export const delToken = () => {
localStorage.removeItem(KEY)
}
但凡是: 单个页面,独立展示的,都是一级路由 (登录 注册 首页架子 文章详情 …)
路由设计:
login/index.vue
我是一级登录
layout/index.vue
我是一级首页架子
dashboard/index.vue
我是二级数据看板页面
article/index.vue
我是二级文章管理页
router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Layout from '@/views/layout'
import Login from '@/views/login'
import Dashboard from '@/views/dashboard'
import Article from '@/views/article'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{ path: 'dashboard', component: Dashboard },
{ path: 'article', component: Article }
]
}
]
})
export default router
layout/index
配置二级路由出口
头部
侧边
测试路径1: http://localhost:8080/#/login
测试路径2: http://localhost:8080/#/dashboard
测试路径3: http://localhost:8080/#/article
说明:我们先学习 element-ui 表单组件的基本结构使用
需求:实现如图效果
一般情况,这种第三方的组件,为了样式控制方便,会给组件的根元素,起一个和组件名同名的类名
控制组件的样式:
如何给组件标签, 设置样式?
1. 给组件标签, 加<自定义类>
添加的类, 会自动加上渲染出来的组件的根元素上
.my-card {
width: 420px;
margin: 0 auto;
}
2. 直接使用<组件标签名>, 作为<类名>控制样式
组件库定义组件的规范: 声明的所有组件的根元素, 都有一个和组件名同名的类名(提供给你了)
默认,写在scoped中的样式,只会影响到当前组件模板中的元素内容
//加上scoped, 可以让样式, 只作用于当前组件模板(局部样式)
//默认scoped样式, 不会向下渗透, 影响到其他子组件的(除了根元素)
//如果希望样式, 可以向下渗透, 影响到下面的子孙后代, 就需要用到深度作用选择器(vue提供)
深度作用选择器:
::v-deep scss
/deep/ less
.el-card {
width: 420px;
margin: 0 auto;
// 原理: 一旦选择器前面有深度作用标识, 就会不会附加属性选择器的限制
::v-deep .el-card__header {
background-color: #727cf5;
text-align: center;
color: #fff;
}
}
深度作用选择器:向下影响到子元素的样式
::v-deep (scss)
/deep/ (less)
黑马面经运营后台
登录
重置
样式美化:
黑马面经运营后台
登 录
重 置
说明:在向后端发请求,调用接口之前,我们需要对所要传递的参数进行验证,把用户的错误扼杀在摇篮之中。
讲解内容:
element-ui的校验
el-form: model
属性, rules
规则
el-form-item: 绑定 prop
属性
el-input: 绑定 v-model
Form 组件提供了表单验证的功能
官方文档写的不好,一共有四个地方需要绑定:
:model
绑定form对象(必须), 需要通过 rules
属性传入约定的验证规则
export default {
data() {
return {
form: {
username: '',
password: ''
}
}
}
}
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
{ min: 5, max: 11, message: '长度在 5 到 11 个字符', trigger: ['blur', 'change'] }
]
}
prop
属性设置为需校验的字段名<el-form-item label="用户名:" prop="username">
<el-input v-model="form.username" placeholder="请输入手机号" />
el-form-item>
下面是常用内置的基本验证规则:其余校验规则参见 async-validator
规则 | 说明 |
---|---|
required | 必须的,例如校验内容是否非空 |
pattern | 正则表达式,例如校验手机号码格式、校验邮箱格式 |
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
{ min: 5, max: 11, message: '长度在 5 到 11 个字符', trigger: ['blur', 'change'] }
],
password: [
{ required: true, message: '请输入密码', trigger: ['blur', 'change'] },
{ pattern: /^\w{5,11}$/, message: '请输入 5 到 10 位的密码', trigger: ['blur', 'change'] }
]
}
// \d 数字 0-9
// \w 字母数字下划线
// {m,n} 前面的字符,可以出现 m次 ~ n次
不要忘了配置prop
<el-form-item prop="password">
上述已经可以完成大部分需求,如果需要更复杂业务校验需求,可以自定义校验~ (项目课程:人力资源系统会进一步讲解)
每次点击按钮, 进行ajax登录前, 应该先对整个表单内容校验, 不然还是会发送很多无效的请求!!!
要通过校验了, 才发送请求!!!
作用: ref
属性配合 $refs
可以获取 dom 元素 (或者 vue组件实例)
<hello ref="bb">hello>
this.$refs.bb.sayHi()
添加登录提交的校验
<el-form ref="form" :model="form" :rules="rules" autocomplete="off">
...
<el-button @click="login" type="primary">登 录</el-button>
methods: {
async login () {
try {
const valid = await this.$refs.form.validate()
console.log(valid)
} catch (e) {
console.log(e)
}
}
}
添加重置功能
重 置
methods: {
reset () {
this.$refs.form.resetFields()
}
}
新建 api/user.js
提供api接口函数
import request from '@/utils/request'
export const login = ({ username, password }) => {
return request.post('/auth/login', {
username,
password
})
}
发送请求获取token
methods: {
async login () {
try {
const valid = await this.$refs.form.validate()
if (valid) {
const res = await login(this.form)
console.log(res)
}
} catch (e) {
console.log(e)
}
}
}
新建 store/modules/user.js
import { getToken, setToken } from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
token: getToken()
}
},
mutations: {
setUserToken (state, payload) {
state.token = payload
setToken(payload)
}
}
}
挂载模块
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user
}
})
登录时调用
async login () {
try {
const valid = await this.$refs.form.validate()
if (valid) {
const res = await login(this.form)
this.$store.commit('user/setUserToken', res.data.token)
this.$router.push('/')
}
} catch (e) {
console.log(e)
}
},
router/index.js
没有token 且 访问的不是 登录页,就直接拦截到登录
router.beforeEach((to, from, next) => {
const { token } = store.state.user;
if (to.path !== '/login' && !token ) return next('/login')
next()
})
api/user.js
准备api接口
export const getUser = () => {
return request.get('/auth/currentUser')
}
layout/index.vue
准备结构 (已准备)
黑马面经
数据看板
面经管理
{{name}}
遇到 401 错误
utils/request.js
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const { token } = store.state.user
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
退出操作
handleConfirm () {
// this.$router.push('/login')
this.$store.commit('user/logout')
this.$router.push('/login')
}
提供mutation
import { delToken, getToken, setToken } from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
token: getToken()
}
},
mutations: {
...,
logout (state) {
state.token = null
delToken()
}
}
}
响应拦截器,处理token过期
import router from '../router'
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data
}, function (error) {
// 对响应错误做点什么 普通错误 + 401情况
// console.dir(error)
if (error.response) {
if (error.response.status === 401) {
// 给提示,清除无效token(vuex+本地),拦到登录
Message.error('尊敬的用户,当前登录状态已过期!')
// 提交清除token的mutation
store.commit('user/logout')
// 跳转到登录
router.push('/login')
} else {
// 给提示
Message.error(error.response.data.message)
}
}
return Promise.reject(error)
})
dashboard/index.vue
面经后台
数据看板
活跃用户
802
↑ 5.23% 最近一个月
平均访问量
1298
↓ 8.56% 截止最近一周
Enhance your Campaign for better outreach →
性别分布情况
浏览访问情况
设备系统访问情况
装包
yarn add echarts
导入
import * as echarts from 'echarts'
添加ref
<div ref="box" class="chart-box" style="height: 500px">div>
mounted初始化
mounted () {
const myChart = echarts.init(this.$refs.box)
// 绘制图表
myChart.setOption({
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
})
},