VUE3写后台管理(3)
- 1.环境
- 1.node
- 2.vite
- 3.Element-plus
- 4.vue-router
- 5.element icon
- 6.less
- 7.vuex
- 8.vue-demi
- 9.mockjs
- 10.axios
- 11.echarts
- 2.首页
- 1.布局Main
- 2.头部导航栏CommonHeader
- 3.左侧菜单栏CommonLeft
- 4.首页Home
- 1.从后端获取数据显示到前端table的三种方式
- 2.axios的二次封装使用
- 3.使用封装后的axios和fastmock获取countdata数据
- 4.使用echarts画图
- 5.头部CommonTab
- 3.用户页
- 1.使用本地mock和Table 表格
- 2.表单重置
- 3.日期
- 4.表单校验
- 5.表单编辑
- 4.other
- 1.权限管理
- 2.数据持久化问题
- 3.动态菜单路由的跳转
- 4.路由守护
- 5. 效果
项目写完代码已经放在了 仓库。
1.环境
1.node
1.电脑已安装nvm,node不会的可以自行搜索,或者看我的vue系列的第一篇。
2.vite
2.使用vite快速构建vue项目: npm create vite@latest
或者 npm create vite@latest my-vue-app -- --template vue
;在设置里面关掉eslint
启动程序:npm run dev
3.Element-plus
3.1.使用Element-plus设置UI,先安装:npm install element-plus --save
,然后全局引入:在mian.ts中
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
在组件中使用时就可以直接用:<el-button type="primary">Primary</el-button>
3.2使用Element-plus进行按需引入,首先需要额外再下载插件:npm install -D unplugin-vue-components unplugin-auto-import
,因为使用vite创建的工程,所以工程的配置文件是vite.config.ts(如果使用的是vue cli脚手架则打包工具就是webpack),在配置文件里面配置插件:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
然后就和全局引入一样,在组件中使用时就可以直接用。
3.3Element-plus手动导入,首先需要下载插件:npm install unplugin-element-plus -S
,然后在配置文件里面引入:
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'
export default defineConfig({
plugins: [
vue(),
ElementPlus()
],
})
在组件里面导入并使用:import {ElButton} from 'element-plus';
4.vue-router
使用vue-router进行路由配置,首先下载:npm install vue-router -S
,然后在src/router/index.js里面进行工程的路由配置:
import {createRouter,createWebHashHistory} from 'vue-router'//1.引入vue-router里创造路由和映射
const routes=[//2.进行路由和视图的映射关系
{
path:"/",
component:()=>import("../views/Main.vue"),
children:[
{
path:'/',
name:"home",
component:()=>import("../views/home/Home.vue"),
}
]
}
]
const router=createRouter({//3.用vue-router的方法,将2的映射关系添加进去
history:createWebHashHistory(),
routes,
})
export default router;//4.将项目的处理好的路由映射暴露在外
然后在main.js里面将配置好的router挂载到根组件App上:
const app = createApp(App)
app.use(router)
最后在根组件App.vue的template和父组件的根据路由变化的子组件部分里面使用这个组件:<router-view />
,就会在根据路由变化的部分做出相应的变化。
5.element icon
使用element做ui肯定是会用到icon的,首先是下载:npm install @element-plus/icons-vue
,然后在main.ts里全局注册到App上:
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
然后就可以在子组件里使用svg方式使用。
6.less
样式使用到了less,所以首先下载npm install -D less
,然后使用时:lang='less'
7.vuex
跨组件信息传递用到了vuex,首先是下载:npm install vuex -S
,然后在src/store/index.js里面使用vuex的createstore方法设置组件之间传递的变量和方法:
import {createStore} from 'vuex'
export default createStore({
state:{//值
isCollapse:true
},
mutations:{
updataIsCollapse(state,payload){//方法
state.isCollapse = !state.isCollapse
}
}
})
然后再将store这个组挂载到app上,在src的main.ts里面:
import store from './store/index.js'
app.use(store)
现在就可以跨组件使用了,在header组件里面用这个变量isCollapse:
<el-aside width="$store.state.isCollapse ? '64px' :'180px'">
<el-menu class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" :collapse="$store.state.isCollapse" :collapse-transition="false">
在left组件里面用这个方法updataIsCollapse:
<el-button size="small" plain @click="handleCollapse">
<script>
import {defineComponent} from "vue-demi";
import {useStore} from "vuex";
export default defineComponent({
setup(){
let store=useStore();
let handleCollapse=()=>{
store.commit("updataIsCollapse");
};
return {
handleCollapse,
};
},
})
</script>
8.vue-demi
vue-demi可以让你不用担心vue2还是vue3,安装:npm install vue-demi -S
,然后在script里面数据和方法中使用。
9.mockjs
mockjs模拟后端,生成随机数据,拦截 Ajax 请求,首先下载:npm install mockjs -S
1.本地mock拦截请求:
首先在src/api/mockData/xxx.js里面写前端方法请求的后端返回(包括访问此路由时调用的视图函数,后端返回的code和data);
然后在src/api/mock.js里面将上面的数据通过mock拦截请求的路由地址和视图函数,拦截到这些数据:Mock.mock('/home/getData',homeApi.getHomeData)
最后在main.ts里引入配置好的mock.js
2.线上fastmock拦截请求:
线上写好要返回的数据,然后复制访问链接,前端发送请求时访问这个链接拿到数据。
10.axios
异步调用请求,首先下载npm install axios -S
1.基础使用
使用axios进行异步访问,最后挂载到onmounted或者别的周期函数。
2.二次封装
首先在src/config/index.js里面写项目的环境配置文件,项目的三种环境(开发环境,测试环境,线上环境)
const env=import.meta.env.MODE || 'prod'
const EnvConfig={
development:{
baseApi:"/api",
mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
},
test:{
baseApi:"//test.future.com/api",
mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
},
pro:{
baseApi:"//future.com/api",
mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
},
}
export default{
env,
mock:true,//mock的总开关
...EnvConfig[env]//ES6语法的解构
}
然后在src/api/request.js里面对接口请求进行二次封装
import axios from 'axios'
import config from '../config'
import {ElMessage} from 'element-plus'
const NETWORK_ERROR='网络请求错误,请稍后重试...'
//1.创建一个axios实例对象
const service=axios.create({baseURL:config.baseApi})
//2.请求之前要做的一些事儿,比如自定义header,jwt-token认证
service.interceptors.request.use((req)=>{
return req;
})
//3.请求之后要做的事儿,和后端协商状态码
service.interceptors.response.use((res)=>{
const {code,data,msg} =res.data
if (code == 200){
return data;
}else{
ElMessage.error(msg || NETWORK_ERROR)
return Promise.reject(msg || NETWORK_ERROR)
}
});
//4.二次封装核心函数,
function request(options){
options.method=options.method || 'get';
//请求方法
if (options.method.toLowerCase()=='get'){
options.params=options.data;
}
//对mock的处理
let isMock=config.mock;//全局mock
if (typeof options.mock !=='undefined'){
isMock=options.mock;//这个请求的mock
}
//对线上环境的处理
if (config.env=='prod'){
service.defaults.baseURL=config.baseApi
}else{
service.defaults.baseURL=isMock?config.mockApi:config.baseApi
}
return service(options)
}
export default request;
然后在src/api/api.js里面对整个项目的api进行管理,然后在main.ts里面挂载到整个项目的后在组件里面使用。
11.echarts
用echarts画图,首先是下载:npm install echarts -S
,然后导入使用:import * as echarts from "echarts";
2.首页
1.布局Main
首先在Main.vue父组件里面完成布局,然后编写各个组件。这里面包含了三个静态子组件CommonHeader、CommonLeft和CommonTab以及动态路由子组件router-view.
<template>
<div class="common-layout">
<el-container class="lay-content">
<el-aside >
<CommonLeft/>
</el-aside>
<el-container class="r-container">
<el-header >
<CommonHeader/>
<CommonTab/>
</el-header>
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import {defineComponent} from 'vue';
import CommonHeader from '../components/CommonHeader.vue';
import CommonLeft from '../components/CommonLeft.vue';
import CommonTab from '../components/CommonTab.vue';
export default defineComponent({
components:{
CommonHeader,
CommonLeft,
CommonTab,
}
})
</script>
2.头部导航栏CommonHeader
这里有一个静态引入静态资源:
<img class="user" src="../assets/vue.svg" alt="用户头像">
也可以动态引入静态资源
<img class="user" :src="getImgSrc('vue')" alt="用户头像">
import {defineComponent} from "vue-demi";
export default defineComponent({
setup(){
let getImgSrc = (user)=>{
return new URL(`../assets/${user}.svg`,import.meta.url).href;//相对地址拼接成绝对地址****这里是反引号不是单引号****
};
return {getImgSrc,};
},
})
组件信息传递的面包屑功能:
首先是CommonLeft点击左侧菜单按钮时要触发vuex里面自定义的selectMenu方法
,并给这个方法传递一个参数item
:
import {useStore} from 'vuex';
export default{
setup(){
const store=useStore();
const clickMenu=(item)=>{
router.push({
name:item.name,
});
//vuex管理面包屑
store.commit('selectMenu',item);
};
return {//函数外部暴露
noChildren,
hasChildren,
clickMenu,
};
};
}
然后在store/index.js里面定义这个方法,并绑定一个值:
import {createStore} from 'vuex'
export default createStore({
state:{//值
currentMenu:null,
},
mutations:{
selectMenu(state,val){
val.name=='home'?(state.currentMenu=null):(state.currentMenu=val)
}
}
})
最后在commonheader里面根据这个全局值计算显示属性:
el-breadcrumb separator="/" class="bread" >
<el-breadcrumb-item :to="{ path: '/' }" >首页</el-breadcrumb-item>
<el-breadcrumb-item :to="current.path" v-if="current" >{{ current.label }}</el-breadcrumb-item>
</el-breadcrumb>
import {useStore} from "vuex";
import {computed} from "vue";
export default defineComponent({
setup(){
let store=useStore();
//面包屑的计算属性
const current=computed(()=>{
return store.state.currentMenu;
});
return {
current,
};
};
})
3.左侧菜单栏CommonLeft
1.icon
这里静态使用icon
<el-icon><Menu /></el-icon>
动态使用icon
<component class="icons" :is="item.icon"></component>
2.点击menu后的路由跳转配置:
首先建立每个路由的视图函数vue文件。
然后在commonleft里面绑定点击事件,给跳转路由的name:
<el-menu-item v-for="item in noChildren()" :key="item.path" :index="item.path" @click="clickMenu(item)">
<el-menu-item v-for="(subItem,subIndex) in item.children " :key="subIndex" :index="subItem.path" @click="clickMenu(subItem)">
<script>
import {useRouter} from 'vue-router';
export default{
setup(){
const router =useRouter();
const clickMenu=(item)=>{
router.push({
name:item.name,/给出要跳转的路由的name
})
};
return {//函数外部暴露
clickMenu,
};
}
在router/index.js里面对每个路由配置name、path和子组件路径component:
import {createRouter,createWebHashHistory} from 'vue-router'//1.引入vue-router里创造路由和映射
const routes=[//2.进行路由和视图的映射关系
{
path:"/",
component:()=>import("../views/Main.vue"),
redirect:'/home',
children:[
{
path:'/home',
name:"home",
component:()=>import("../views/home/Home.vue"),
},
{
path:'/mall',
name:"mall",
component:()=>import("../views/mall/mall.vue"),
},
{
path:'/user',
name:"user",
component:()=>import("../views/user/user.vue"),
},
{
path:'/other/page1',
name:"page1",
component:()=>import("../views/other/page1.vue"),
},
{
path:'/other/page2',
name:"page2",
component:()=>import("../views/other/page2.vue"),
},
],
}
]
const router=createRouter({//3.用vue-router的方法,将2的映射关系添加进去
history:createWebHashHistory(),
routes,
})
export default router;//4.将项目的处理好的路由映射暴露在外
4.首页Home
1.从后端获取数据显示到前端table的三种方式
A假数据写在代码里,B假数据写在本地用mock模拟后端,C假数据写在线上fastmock模拟后端。
A:假数据写在代码里
<el-table :data="tableData">
<el-table-column v-for="(val,key) in tableLabel" :key="key" :prop="key" :label="val">
</el-table-column>
</el-table>
<script>
import {defineComponent} from "vue";
export default defineComponent({
setup(){
const tableData= [
{
name: 'oppo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
...
];
const tableLabel={
name:"品牌",
todayBuy: "今日购买",
monthBuy: "本月购买",
totalBuy: "总购买",
};
return {
tableData,
tableLabel,
}
}
});
</script>
B:假数据写在本地用mock模拟后端
先写本地返回数据src/api/mockData/home.js
export default{
getHomeData:()=>{
return{
code:200,
data:{
tableData:[
{
name: 'oppo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
...
],
}
}
}
}
然后拦截这个方法的访问路由到本地src/api/mock.js
import Mock from 'mockjs'
import homeApi from "./mockData/home"
Mock.mock('/home/getData',homeApi.getHomeData)//访问路由是:home/getData; 访问后端方法homeApi.getHomeData
最后在main.ts里引入配置好的mock.js
import './api/mock.js'
在home.vue里面使用axios对这个路径和方法进行异步请求并在onmounted时运行:
import {onMounted,ref} from "vue";
import axios from "axios";
setup(){
let tableData = ref([]);
const getTableList=async ()=>{
await axios.get("/home/getData").then((res)=>{//运行这个方法就是访问这个路由
if(res.data.code==200){
tableData.value=res.data.data.tableData;
}
});
};
onMounted (()=>{getTableList();});
}
C:假数据写在线上fastmock模拟后端
在fastmcok里面写后台要返回的数据
然后复制预览接口的url,访问
import {onMounted,ref} from "vue";
import axios from "axios";
setup(){
let tableData = ref([]);
const getTableList=async ()=>{
await axios.get("https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api/home/getData").then((res)=>{
if(res.data.code==200){
tableData.value=res.data.data.tableData;
}
});
};
onMounted (()=>{getTableList();});
}
2.axios的二次封装使用
src\api\request.js
import axios from 'axios'
import config from '../config'
import {ElMessage} from 'element-plus'
const NETWORK_ERROR='网络请求错误,请稍后重试...'
//1.创建一个axios实例对象
const service=axios.create({baseURL:config.baseApi})
//2.请求之前要做的一些事儿,比如自定义header,jwt-token认证
service.interceptors.request.use((req)=>{
return req;
})
//3.请求之后要做的事儿,和后端协商状态码
service.interceptors.response.use((res)=>{
const {code,data,msg} =res.data
if (code == 200){
return data;
}else{
ElMessage.error(msg || NETWORK_ERROR)
return Promise.reject(msg || NETWORK_ERROR)
}
});
//4.二次封装核心函数,
function request(options){
options.method=options.method || 'get';
//请求方法
if (options.method.toLowerCase()=='get'){
options.params=options.data;
}
//对mock的处理
let isMock=config.mock;//全局mock
if (typeof options.mock !=='undefined'){
isMock=options.mock;//这个请求的mock
}
//对线上环境的处理
if (config.env=='prod'){
service.defaults.baseURL=config.baseApi
}else{
service.defaults.baseURL=isMock?config.mockApi:config.baseApi
}
return service(options)
}
export default request;
通过上面对项目axios的二次封装之后,在src/api/api.js里面对项目api统一管理(eg:当项目调用getTableData方法
时就会访问/home/getData
这个路由,并根据mock确定是线上还是本地去访问这个路由)。
```php
import request from './request';
export default {
getTableData(params){
return request({
url:'/home/getData',
method:'get',
data:params,
mock:true,
})
}
}
然后在main.ts里面挂载到全局
import api from './api/api.js'
app.config.globalProperties.$api = api
最后在Home.vue组件里面使用
import {getCurrentInstance} from "vue";
const {proxy}=getCurrentInstance();
const getTableList=async ()=>{
let res=await proxy.$api.getTableData();
tableData.value=res.tableData;
};
3.使用封装后的axios和fastmock获取countdata数据
1.首先在fastmock里面写后端要返回的数据
、url
和方法
(get)。
- url:/home/getCountData
2.在src/api/api.js里面添加这个路由对应的路由函数
的配置
- 配置url对应的路由函数:getCountData
getCountData(params){
return request({
url:'/home/getCountData',
method:'get',
data:params,
mock:true,
});
3.在home子组件里访问这个路由,异步访问这个路由函数,最后将函数挂载到onmounted上,将拿到的数据返回:
3.调用这个路由函数,拿到数据
import {defineComponent,onMounted,ref,getCurrentInstance} from "vue";
import axios from "axios";
export default defineComponent({
setup(){
const {proxy}=getCurrentInstance();
let countData = ref([]);
const getCountList=async ()=>{
let res=await proxy.$api.getCountData();
console.log(res);
countData.value=res;
};
onMounted (()=>{
getCountList();
});
return {
countData,
}
}
});
最后前端显示
<el-col :span="16" style="margin-top:20px;align-self: flex-start;" >
<div class="num">
<el-card :body-style="{display:'flex',padding:0}" v-for="item in countData" :key="item.name">
<component class="icons" :is="item.icon" :style="{background:item.color}"></component>
<div class="details">
<p class="num">¥{{ item.value }}</p>
<p class="txt">{{ item.name }}</p>
</div>
</el-card>
</div>
</el-col>
4.使用echarts画图
1.首先在fastmock里面给出url(/home/getChartData)后台数据
{
code:200,
data:{
"orderData": {
"date": ["20191001", "20191002", "20191003", "20191004", "20191005", "20191006", "20191007"],
"data|7": [
{
"苹果":"@integer(1000,5000)",
...
}
]
},
"videoData": [
{
name: '小米',
value: 2999
},
...
],
"userData": [
{
date: '周一',
new: 5,
active: 200
},
...
],
},
}
2.在src\api\api.js里面配置url和方法名( getChartData)
import request from './request';
export default {
...
getChartData(params){
return request({
url:'/home/getChartData',
method:'get',
data:params,
mock:true,
});
},
}
3.在Home.vue里面使用
<el-card style="height:320px">
<div ref="orderchart" style="height:300px; width:auto;"></div>
</el-card>
<div class="graph">
<el-card style="height:260px;">
<div ref="userchart" style="height:240px; width:auto;"></div>
</el-card>
<el-card style="height:260px;">
<div ref="videochart" style="height:240px;width:auto;"></div>
</el-card>
</div>
<script>
import {defineComponent,onMounted,ref,getCurrentInstance,reactive,} from "vue";
import axios from "axios";
import * as echarts from "echarts";
export default defineComponent({
setup(){
const {proxy}=getCurrentInstance();
onMounted (()=>{
getChartList();
});
//echats的渲染
//x轴的配置
let xOptions=reactive({
legend: {
// 图例文字颜色
textStyle: {
color: "#333",
},
},
grid: {
left: "20%",
},
// 提示框
tooltip: {
trigger: "axis",
},
xAxis: {
type: "category", // 类目轴
data: [],
axisLine: {
lineStyle: {
color: "#17b3a3",
},
},
axisLabel: {
interval: 0,
color: "#333",
},
},
yAxis: [
{
type: "value",
axisLine: {
lineStyle: {
color: "#17b3a3",
},
},
},
],
color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
series: [],
},)
//饼状图的配置
let pieOptions=reactive({
tooltip: {
trigger: "item",
},
color: [
"#0f78f4",
"#dd536b",
"#9462e5",
"#a6a6a6",
"#e1bb22",
"#39c362",
"#3ed1cf",
],
series: [],
})
//三个图数据的初始化
let orderData=reactive({xData:[],series:[]});
let userData=reactive({xData:[],series:[]});
let videoData=reactive({series:[]});
//通过faskmock获取数据
const getChartList=async()=>{
let res=await proxy.$api.getChartData();
console.log(res);
let orderRes=res.orderData;
let userRes=res.userData;
let videoRes=res.videoData;
//折线图
orderData.xData=orderRes.date;//折线图的横轴时间数据
const keyArray = Object.keys(orderRes.data[0]);//提取每个品牌的销售数据,为每个品牌创建一个系列
const series = keyArray.map(key => ({
name: key,
data: orderRes.data.map(item => item[key]),
type: "line"
}));
// // 3. 将提取的数据放入xOptions和orderData中,然后传递给echarts
orderData.series = series;
xOptions.xAxis.data = orderData.xData;
xOptions.series = orderData.series;
console.log(xOptions);
// // 折线图的渲染
let oEcharts = echarts.init(proxy.$refs['orderchart']);
oEcharts.setOption(xOptions);
// //柱状图
userData.xData=userRes.map((item) => item.date);
userData.series = [
{
name:"新增用户",
data:userRes.map((item) =>item.new),
type:"bar",
},
{
name:"活跃用户",
data:userRes.map((item) =>item.active),
type:"bar",
},
];
xOptions.xAxis.data = userData.xData;
xOptions.series = userData.series;
let uEcharts = echarts.init(proxy.$refs['userchart']);
uEcharts.setOption(xOptions);
// //饼状图
videoData.series=[
{data:videoRes,type:"pie"}
];
pieOptions.series = videoData.series;
let vEcharts = echarts.init(proxy.$refs['videochart']);
vEcharts.setOption(pieOptions);
};
return {
tableData,
tableLabel,
countData,
}
}
});
</script>
5.头部CommonTab
使用vuex绑定全局参数tagsList
并在点击菜单栏的事件
里面给这个参数添加val
import {createStore} from 'vuex'
export default createStore({
state:{//值
tabsList:[
{
path:"/",
name:"home",
label:"首页",
icon:"home",
}
],
},
mutations:{
selectMenu(state,val){//这个事件已经绑定到了左侧菜单栏
// val.name=='home'?(state.currentMenu=null):(state.currentMenu=val)
if(val.name=='home'){
state.currentMenu=null
}else{
state.currentMenu=val
let result=state.tabsList.findIndex(item=>item.name==val.name)
result==-1?state.tabsList.push(val):""
}
},
closeTab(state,val){//这个事件会绑定在tag的事件里
let res=state.tabsList.findIndex(item=>item.name==val.name)
state.tabsList.splice(res,1)
}
},
})
然后在CommonTab里面拿到这个全局值进行循环并判断显示
<div class="tags">
<el-tag v-for="(tag,index) in tags" :key="tag.name" :closable="tag.name!=='home'" :disable-transitions="false"
:effect="$route.name===tag.name ?'dark':'plain'" @click="changeMenu(tag)" @close="handleClose(tag,index)">
{{ tag.label }}
</el-tag>
</div>
<script>
import {useStore} from "vuex";
import {useRouter,useRoute} from "vue-router";
export default{
setup(){
const router = useRouter();
const route = useRoute();
const store=useStore();
const tags=store.state.tabsList;
const changeMenu = (item)=>{
router.push({name:item.name});
};
const handleClose=(tag,index)=>{
let length=tags.length-1;
store.commit("closeTab",tag);
if (tag.name!==route.name){
return;
}
if(index==length){
router.push({name:tags[index-1].name});
}else{
router.push({name:tags[index].name});
}
};
return {tags,changeMenu,handleClose};
},
}
</script>
3.用户页
1.使用本地mock和Table 表格
首先在src\api\mockData\user.js里面写user页面的后端模拟数据;
然后在src\api\api.js里面写前后端访问的控制,当调用此方法函数时让它访问此路由,并设置是从线上还是本地获取数据。
getUserData(params){
return request({
url:'/user/getUser',
method:'get',
data:params,
mock:false,
});
},
然后在src\api\mock.js里面使用mock拦截前端请求,让它从本地user.js里面获取数据:Mock.mock(/user\/getUser/,'get',userApi.getUserList)
最后在user.vue里面拿到数据并通过el-table显示。
2.表单重置
表单重置时首先给表单一个ref<el-form :inline="true" :model="formUser" ref="userForm">
然后通过ref拿到表单,调用表单的resetFields方法:proxy.$refs.userForm.resetFields();
3.日期
提交有日期的表单时,先要对表单的日期数据进行处理:
const timeFormat=(time)=>{//对日期格式处理
var time=new Date(time);
var year=time.getFullYear();
var month=time.getMonth()+1;
var date=time.getDate();
function add(m){
return m<10?"0"+m:m;
};
console.log(year+"-"+add(month)+"-"+add(date));
return year+"-"+add(month)+"-"+add(date);
};
const onSubmit=()=>{
proxy.$refs.userForm.validate(async(valid)=>{
if(valid){//如果表单验证成功,再提交到后端
formUser.birth=timeFormat(formUser.birth);
let res= await proxy.$api.addUser(formUser);
console.log(res);
if(res){
dialogVisible.value=false;
proxy.$refs.userForm.resetFields();//提交之后根据反向ref拿到表单,利用表单的resetFields方法,重置表单
getUserData(config);
}
}else{
ElMessage({
showClose:true,
message:'请输入正确的内容',
});
}
});
};
4.表单校验
首先对每个表单数据进行校验:<el-form-item label="年龄" prop="age" :rules="[{required:true,message:'请输入年龄'},{type:'number', message: '年龄必须是数字'}]">
最后提交的时候,也要确定验证都通过了才能提交表单到后端:proxy.$refs.userForm.validate(async(valid)=>{if(valid){}})
5.表单编辑
首先需要拿到显示的数据到放到表单再去编辑,所以用到了scope 变量,它是一个插槽(slot)的作用域参数,用于在 Vue.js 中传递当前组件实例的数据给插槽的内容。
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" size="small">删除</el-button>
</template>
然后就可以通过row拿到这些表单值,然后通过浅拷贝,将数据显示到表单:
const handleEdit=(row)=>{
console.log(row);
dialogVisible.value=true;
row.sex==0?(row.sex="男"):(row.sex="女");
proxy.$nextTick(()=>{//异步操作函数, DOM 更新完成后执行
Object.assign(formUser,row);//将row数据浅拷贝到表单formUser
});
action.value='edit';
dialogVisible.value=true;
};
4.other
1.权限管理
登录后会根据根据后台数据显示不同用户的左侧菜单,跨组件的信息传递用到了vuiex,所以在store/index.js里面定义全局变量和方法
import {createStore} from 'vuex'
export default createStore({
state:{//值
menu:[],
},
mutations:{
setMenu(state,val){
state.menu=val
localStorage.setItem('menu',JSON.stringify(val))}
},
})
然后在login里面将后台拿到的此用户的menu信息通过全局方法传递给全局值:store.commit("setMenu",res.menu);
最后在commonleft里面用这个变量显示不同的menu:const asyncList=store.state.menu;
2.数据持久化问题
因为menu给的是[],所以当用户刷新网页时,menu数据就会丢失,所以处理这个问题,就要在store里面拿到存到浏览器里面的localStorage.menu里面的值赋值给state.menu,并在main.ts里面使用这个方法刷新menu值。
addMenu(state){
if (!localStorage.getItem('menu')){
return
}
const menu=JSON.parse(localStorage.getItem('menu'))
state.menu=menu
}
//main.ts
store.commit("addMenu");
3.动态菜单路由的跳转
首先登录以后,通过addmenu方式,传递给全局的router:store.commit("addMenu",router);
然后在store里面给addmenu方法添加动态路由的列表获取:
addMenu(state,router){
if (localStorage.getItem('menu')){
// 如果menu里面有值就拿到她更新state里的menu
const menu=JSON.parse(localStorage.getItem('menu'))
state.menu=menu
//动态路由添加到router
const menuArray=[];
menu.forEach(item=>{
if (item.children){
item.children=item.children.map(item=>{
let url=`../views/${item.url}.vue`//反引号
item.component=()=>import(url)
return item
})
menuArray.push(...item.children)
}else{
let url=`../views/${item.url}.vue`
item.component=()=>import(url)
menuArray.push(item)
}
});
menuArray.forEach(item=>{
router.addRoute('home1',item)
})
};
},
最后在main.ts里面用addmenu方法时也要传递参数router:store.commit("addMenu",router);
4.路由守护
需要用到vuex存取全局token值,并用到js-cookie的Cookie,所以先下载:npm install js-cookie -S
然后在store/index里面定义值和方法:
import {createStore} from 'vuex'
import Cookie from 'js-cookie'
export default createStore({
state:{//值
token:'',
},
mutations:{//方法
//1.设置token
setToken(state,val){
state.token=val;
Cookie.set('token',val);
},
//2.获取token
getToken(state){
state.token=state.token||Cookie.get('token');
},
//3.清除token
clearToken(state){
state.token=''
Cookie.remove('token');
}
},
})
然后在登录login时,后端给一个token,通过settoken方法设置到全局token上:store.commit("setToken",res.token);
然后在main.ts里面每次去gettoken进行判断,并判断这个路由在路由列表吗,如果不在就跳转到home页面
function checkRouter(path) {
let hasCheck = router.getRoutes().filter(route => route.path == path).length;
return hasCheck;
}
router.beforeEach((to, from, next) => {
store.commit('getToken');
const token = store.state.token;
if (!token && to.name !== 'login') {
next({ name: "login" });
} else if (!checkRouter(to.path)) {
next({ name: "home" });
} else {
next();
}
});
退出时也clearToken。
5. 效果
1.首页
2.用户