大约 9 分钟
1 创建项目
- 局部安装 vue-cli 4.5.15
pnpm init
pnpm add -D @vue/cli@4.5.15 # 指定版本 4.5.15 安装
npx vue -V
@vue/cli 4.5.15
npx vue create .
> Default ([Vue 2] babel, eslint)
配置自动打开浏览器 新建
vue.config.js
module.exports = { devServer: { open: true } } pnpm serve
https://element.eleme.cn/#/zh-CN
2 elementui 官网- 安装包
pnpm install element-ui
2.1 element官网 → 组件 → 完整引入
// 在 main.js 中写入以下内容
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
- 项目里多余的删一删, 清一清
改 components 下 HelloWorld.vue 为 Home.vue 删除
<script>, <div> <style>
中多余的内容 修改 App.vue 中<template>, <script>, <style>
组件相关部分 在 Home.vue 中加入
<el-button>hello</el-button>
2.2 element官网 → 组件 → 按需引入
pnpm add -D babel-plugin-component
- 修改 babel.config.js
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
- 在 main.js 中引入
import Button from 'element-ui'
Vue.use(Button)
- 抽取到模块 plugins/element.js (不在 src 里, 在src外)
// plugins/element.js
import Vue from 'vue'
import { Button, Tag } from 'element-ui'
Vue.use(Button)
Vue.use(Tag)
// Home.vue
<el-tag>标签</el-tag>
// main.js
import '../plugins/element'
3 预处理及样式重置
3.1 css 预处理器 sass
- 官网
- 安装
cnpm i sass-loader@7 node-sass@4 -S
pnpm add sass-loader@7 node-sass@4
- 使用
// Home.vue
<style lang="scss">
.hello {
background: yellow;
.el-button {
color: red;
}
3.2 css 预处理器 less
cnpm i less@3 less-loader@7 -S
pnpm add less@3 less-loader@7 -S
- 使用
// Home.vue
<style lang="less">
3.3 使用 reset.css 重置样式
- 从官网复制
- 创建 assets/css/reset.css , 粘贴
- 在
App.vue
使用
<style lang="scss">
@import url('./assets/css/reset.css');
4 图标库
pnpm add -D font-awesome
- 使用
// main.js
import 'font-awesome/css/font-awesome.min.css'
// Home.vue
<i class="fa fa-users"></i>
5 请求 axios
pnpm add axios
// main.js
import axios from 'axios'
Vue.prototype.axios = axios // 挂载到原型, 可以全局使用
6 路由
pnpm add vue-router@3.5.3
- 配置, 新建
src/router/index.js
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
component: Home
}
],
mode: 'history'
})
// main.js
import router from './router'
new Vue({
router,
// App.vue 注释掉 Home 组件相关, 添加如下内容
<router-view></router-view>
- 路由懒加载 与 异步组件
// src/router/index.js
component: () => import('@/components/Home') // 路由懒加载
component: resolve => require(['@/components/Home'], resolve) // 异步组件
7 登录页面
- 创建 src/components/Login.vue,
产生 template script style 找一个 form 表单, 拿一个 card 包裹
7.1 Login01.vue 简单检验和登录
<template>
<div class="login">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>通用后台管理系统</span>
</div>
<el-form label-width="80px" :model="form" ref="form">
<el-form-item label="用户名" prop="username"
:rules="[
{requie: true, message: '请输入用户名', trigger: 'blur'},
{min:4, max: 10, message: '长度在 4-10 位字符之间', trigger: 'blur'}
]">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password"
:rules="[
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, max: 12, message: '长度在 6-12 位字符', trigger: 'blur'}
]">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login('form')">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
password: '',
}
}
},
methods: {
login(form) {
this.$refs[form].validate( valid => {
if (valid) {
console.log(this.form)
this.axios.post('http://127.0.0.1:3007/login', this.form)
.then(res=> {
console.log(res) // F12 看一下返回的 data: 有 status, token, username, message
if(res.data.status === 200) {
localStorage.setItem('username', res.data.username)
this.$message({message: res.data.message, type: 'success'})
this.$router.push('/home')
}
})
.catch( err => { console.error(err)})
} else {
console.error(this.form)
}
})
}
}
}
</script>
<style lang="scss">
.login {
width: 100%;
height: 100%;
position: absolute;
background: #409EFF;
.box-card {
width: 450px;
margin: 200px auto;
.el-card__header {
font-size: 33px;
}
.el-button {
width: 100%;
}
}
}
</style>
7.2 Login.vue 检验和登录
vscode 的
any-rule
插件写正则, F1 输入'姓名', 选英文姓名另一种检验规则
<el-form label-width="80px" :model="form" ref="form" :rules="rules">
export default {
data() {
const validateName = (rule, value, callback) => {
// 请输入 4-10 位昵称
let reg = /(^[a-zA-Z][a-zA-Z1-9\s]{0,20}[a-zA-Z]$)/
if (value === '') {
callback(new Error('请输入用户名'))
} else if (!reg.test(value)) {
callback(new Error('请输入 4-10 位用户名'))
} else {
callback()
}
}
const validatePass = (rule, value, callback) => {
// 请输入 6-12 位包含大小写字母和数字以及特殊符号
let regPass = /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/
if (value === '') {
callback(new Error('请输入密码'))
} else if (!regPass.test(value)) {
callback(new Error('6-12 位密码需要包含大小写字母数字和特殊符号'))
} else {
callback()
}
}
return {
form: {
username: '',
password: '',
},
rules: {
username: [{validator: validateName, trigger: 'blur'}],
password: [{validator: validatePass, trigger: 'blur'}]
}
}
},
}
- 封装 新建
src/utils/validate.js
// 用户名匹配
export function nameRule(rule, value, callback) {
// 请输入 4-10 位昵称
let reg = /(^[a-zA-Z][a-zA-Z1-9\s]{0,20}[a-zA-Z]$)/
if (value === '') {
callback(new Error('请输入用户名'))
} else if (!reg.test(value)) {
callback(new Error('请输入 4-10 位用户名'))
} else {
callback()
}
}
// 密码正则匹配
export function passRule(rule, value, callback) {
// 请输入 6-12 位包含大小写字母和数字以及特殊符号
let regPass = /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/
if (value === '') {
callback(new Error('请输入密码'))
} else if (!regPass.test(value)) {
callback(new Error('6-12 位密码需要包含大小写字母数字和特殊符号'))
} else {
callback()
}
}
- 在
Login.vue
中导入使用
import { nameRule, passRule } from '../utils/validate'
// 使用 nameRule 作为验证规则,在输入框失去焦点时触发验证
// trigger: 规定了何时触发验证。 翻译为: 触发
// blur: 表示在失去焦点时进行验证 翻译为: 变模糊, 即失去焦点
rules: {
username: [{validator: nameRule, trigger: 'blur'}],
password: [{validator: passRule, trigger: 'blur'}]
}
7.3 token 封装
- 新建
src/utils/setToken.js
// src/utils/setToken.js
export function setToken(tokenKey, token) {
return localStorage.setItem(tokenKey, token)
}
export function getToken(tokenKey) {
return localStorage.getItem(tokenKey)
}
export function removeToken(tokenKey) {
return localStorage.removeItem(tokenKey)
}
// Login.vue
import { setToken } from '@/utils/setToken.js'
setToken("username", res.data.username)
8 postman 测试接口 和 axios 二次封装
- 配置代理跨域
// vue.config.js
devServer: {
open: true,
proxy: {
'/api': {
target: 'http://127.0.0.1:3007',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
- axios 封装, 新建
src/service.js
import axios from 'axios'
import { getToken } from './utils/setToken'
import { Message } from 'element-ui'
const service = axios.create({
baseURL: '/api', // baseURL 会自动加在请求地址上
timeout: 3000
})
// 添加请求拦截器
service.interceptors.request.use( req => {
req.headers['token'] = getToken('token') // 等同在 postman 里设置 token
return req
}, error => {
return Promise.reject(error)
})
// 添加响应拦截器
service.interceptors.response.use(res => {
let { status, message } = res.data
if (status !== 200) {
Message({message: message || 'error', type: 'warning'}) // 处理错误
}
return res
}, error => {
return Promise.reject(error)
})
export default service
// main.js
import service from './service'
Vue.prototype.service = service // 挂载到原型, 可以全局使用
- 发送请求
methods: {
login(form) {
this.$refs[form].validate( valid => {
if (valid) {
console.log(this.form)
this.service.post('/login', this.form)
.then(res => {
if (res.data.status === 200) {
setToken('username', res.data.username) // 为了在 header 中展示
setToken('token', res.data.token) // 以后每次发请求要带上
this.$message({message: res.data.message, type: 'success'})
this.$router.push('/home')
}
console.log(res)
})
} else {
console.error(this.form)
}
})
}
}
- 把登录方法封装成 api 进行调用
src/api/api.js
// 项目中都会把对应的接口请求封装成 api 来调用
import service from "../service";
// 登录接口
export function login(data) {
return service({
method: 'post',
url: '/login',
data
})
}
// Login.vue
import { login } from '@/api/api.js'
login(this.form).then(res => {
if (res.data.status === 200) {
setToken('username', res.data.username)
setToken('token', res.data.token)
this.$message({message: res.data.message, type: 'success'})
this.$router.push('/home')
}
})
- 完善一下
// E:\77vue\Vue项目实战金渡教育\项目vue2+ts+vue3源码\project-v2\src\assets 加资源图片
// Login.vue
background: url('../assets/bg.jpg') center no-repeat;
.el-card {
background: #65768557;
}
color: #fff;
.el-form .el-form-item__label {
color: #fff;
}
- 404 页面 新建
src/components/NotFound.vue
<template>
<div class="notfound">
<div class="wrapper">
<div class="big">页面不见了!</div>
<div>首页瞧瞧,点击<router-link to="/">这里</router-link>进入首页.</div>
</div>
</div>
</template>
- 路由配置
// src/router/index.js
{
path: '*',
name: 'NotFound',
component: () => import('@/components/NotFound')
},
- 404 页面样式不显示
// App.vue
html, body {
width: 100%;
height: 100%;
}
#app {
width:100%;
height: 100%;
}
9 Header 和 Footer 组件
- 在
src/components/common
下创建Header.vue
,Footer.vue
,Menu.vue
// src/componets/common/Header.vue , 其它参考这个来改
<template>
<div>
header
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
<style lang="scss" scoped>
</style>
- 主页面 Home.vue 导入组件
<template>
<div class="home">
<Header/>
<Footer/>
<Menu/>
</div>
</template>
<script>
import Header from './common/Header.vue'
import Footer from './common/Footer.vue'
import Menu from './common/Menu.vue'
export default {
components: {
Header,
Footer,
Menu
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
.home {
width: 100%;
height: 100%;
}
</style>
- elementUI Container 布局容器
9.1 Header.vue 布局
<template>
<div class="header">
<el-header>
<div class="title">通用管理系统</div>
<div>{{ name }}</div>
</el-header>
</div>
</template>
<script>
import { getToken } from '@/utils/setToken.js'
export default {
data() {
return {
name: ''
}
},
created() {
this.name = getToken('username')
}
}
</script>
<style lang="scss" scoped>
.header {
.el-header {
background: #2578b5;
color: #fff;
line-height: 60px;
display: flex;
justify-content: space-between;
.title {
width: 200px;
font-size: 24px;
}
}
}
</style>
9.2 Footer.vue 布局
<template>
<div class="footer">
<el-card>
Frontend 2023 jiyun
</el-card>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
9.3 Menu.vue 布局
<template>
<div class="menu">
<el-aside width="200px">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
background-color="#2578b5"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style lang="scss" scoped>
.menu {
.el-aside {
height: 100%;
.el-menu {
height: 100%;
}
.el-submenu .el-menu-item {
min-width: 0;
}
}
}
</style>
10 基础菜单和模块路由
- 创建其它模块 在
src/components
下
students 目录下 StudentList.vue InfoList.vue InfoLists.vue WorkList.vue WorkMent.vue dataAnalysis 目录 DataView.vue MapView.vue TravelMap.vue ScoreMap.vue users 目录 User.vue
<template>
<div>
<!-- 学生管理-->
信息列表 <!-- src/components/students/InfoList.vue -->
信息管理 <!-- src/components/students/InfoLists.vue -->
学生列表 <!-- src/components/students/StudentList.vue -->
作业列表 <!-- src/components/students/WorkList.vue -->
作业管理 <!-- src/components/students/WorkMent.vue -->
<!-- 数据分析-->
数据概览 <!-- src/components/dataAnalysis/DataView.vue -->
地图预览 <!-- src/components/dataAnalysis/MapView.vue -->
分数地图 <!-- src/components/dataAnalysis/ScoreMap.vue -->
旅游地图 <!-- src/components/dataAnalysis/TravelMap.vue -->
用户中心 <!-- src/components/users/User.vue -->
</div>
</template>
- 设计路由
{
path: '/home',
name: '学生管理',
iconClass: 'fa fa-users',
redirect: '/home/student',
component: () => import('@/components/Home'),
children: [
{
path: '/home/student',
name: '学生列表',
iconClass: 'fa fa-list',
component: () => import('@/components/students/StudentList')
},
]
},
{
path: '/home',
name: '数据分析',
iconClass: 'fa fa-bar-chart',
redirect: '/home/student',
component: () => import('@/components/Home'),
children: [
{
path: '/home/dataview',
name: '数据概览',
iconClass: 'fa fa-line-chart',
component: () => import('@/components/dataAnalysis/DataView')
},
]
},
{
path: '/users', // 这里不一定非得是 /home
name: '用户中心',
iconClass: 'fa fa-users',
redirect: '/home/student',
component: () => import('@/components/Home'),
children: [
{
path: '/users/user',
name: '权限管理',
iconClass: 'fa fa-line-chart',
component: () => import('@/components/users/User.vue')
},
]
}
- 在
Menu.vue
中
export default {
data() {
return {
menus: []
};
},
created() {
console.log(this.$router.options.routes)
this.menus = [...this.$router.options.routes]
}
};
<template v-for="(item, index) in menus">
<el-submenu :index="index +''" :key="index" v-if="!item.hidden">
<template slot="title">
<i :class="item.iconClass"></i>
<span>{{item.name}}</span>
</template>
<el-menu-item-group v-for="(child, index) in item.children" :key="index">
<el-menu-item :index="child.path">
<i :class="child.iconClass"></i>
{{child.name}}
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</template>
<el-menu
router // 设置后绑定路由, 点击菜单, url 地址会跟着变
default-active="2"
class="el-menu-vertical-demo"
background-color="#2578b5"
text-color="#fff"
active-text-color="#ffd04b">
- 路由变,并且右侧的内容也跟着变,路由出口,路由在哪显示,出口给到哪
// Home.vue
<el-main><div class="cont"><router-view/></div></el-main>
.cont {
margin: 20px 0;
}
11 菜单路由和面包屑
- 新建
common/Breadcrumb.vue
// Breadcrumb.vue
<template>
<div>
<el-card>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>活动管理</el-breadcrumb-item>
<el-breadcrumb-item>活动列表</el-breadcrumb-item>
<el-breadcrumb-item>活动详情</el-breadcrumb-item>
</el-breadcrumb>
</el-card>
</div>
</template>
// Home.vue
import Bread from "./common/Breadcrumb.vue";
components: { Bread,},
<el-main>
<Bread/>
<div class="cont">
<router-view/>
</div>
</el-main>
- 结合路由修改 Breadcrumb.vue
<template>
<div>
<el-card>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in $route.matched" :key="index">
{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</el-card>
</div>
</template>
12 学生列表, 表格数据处理, 前端分页
- 表格
test