后台管理项目笔记
1)什么是后台管理项目
可以让用户通过一个可视化工具,实现对于数据库的增删改查的操作,根据不同的角色,操作的内容也是不同的
2)vue.config.js(全局配置文件)

3)element-ui框架的两种引入方式
第一种(直接在main.js文件注册)

第二种
# 在src下的plugin文件夹中创建 .js文件,将vue,elementui,elementuicss,引入并注册,最后main.js文件中进行 import 导入

4)全局样式设置
#在App.vue文件中设置全局样式

5)element-ui 表单验证
#prop属性代表需要验证的字段名

6)加载进度条===nprogress

7)vue.config.js(全局文件配置)

8) md5加密
第一步
yarn add js-md5
第二步(在utils文件夹下建一个md5.js文件)

第三步
在需要使用的地方进行导入,使用即可
9)axios中添加token令牌和vue添加全局方法
第一步:在创建axios的实例中,写一个方法将token信息保存到请求头中

第二步:在plugin插件文件夹中创建一个myplugin.js文件,添加vue全局方法必须在main.js引入并vue.use
import myplugin from './plugin/myplugin ' vue.use(myplugin )

第三步:判断是否登录成功,成功情况下,存储token到本地,同时调用将token存入headers的this.$setToken方法

10)竖直方向导航折叠功能
**第一步:给折叠导航添加 :collapse="isCollapse" 是否折叠属性
**第二步:isCollapse 进行状态设置
##注意事项:折叠时盒子的宽度设为auto,不折叠时设置为 固定px

11)不同环境公共接口改变
第一步
在config文件夹下创建base_url.js 和index.js
在base_url.js中写入以下代码

第二步
在index.js中进行导入base_url.js 并进行导出

第三步
在定义公共请求的js文件中 导入BASE_URL 进行使用

12) 封装axios
第一步
在util文件夹中创建一个request.js文件 定义公共的请求头和地址等
import axios from 'axios';
// 创建一个 Axios 实例
const service = axios.create({
baseURL: 'https://autumnfish.cn', // 接口的基础路径
timeout: 5000, // 请求超时时间
});
// 请求拦截器
service.interceptors.request.use(
(config) =>{
return config;
} ,
(error) => {
// 处理请求错误
console.error(error);
return Promise.reject(error);
});
// 响应拦截器
service.interceptors.response.use(
(response) =>{
return response
} ,
(error) => {
// 处理响应错误
console.error(error);
return Promise.reject(error);
});
export const request = service;
第二步
创建数据访问层
# 1.创建一个api 文件夹
# 2.创建一个具体功能的文件夹和js文件(举例:登录页面的所有请求都在此文件中)
# 3.导出每一个方法
import {request} from '@/utilis/request.js'
// 获取笑话
const getxiaohua = ()=>{
return request({
url:'/api/joke'
})
}
// 用户注册
const register = (data)=>{
return request({
url:'/api/user/register',
method:'post',
data:data
})
}
export{
getxiaohua,
register
}
第三步
# 需要用到的页面导入接口层api的方法
import {getxiaohua,register} from '../api/login/index.js'
methods:{
async handle(){
let res = await getxiaohua()
console.log(res.data);
},
async zhuche(){
let res = await register({
username:'娃娃鱼-2023'
})
console.log(res);
}
}
13)element-ui image组件使用
图片预览功能(层级问题,组件的属性中有一个覆盖Body的层级属性)
初级版
#1. 注意图片地址必须有路径才能显示,可能需要手动拼接地址
#2. fit='cover' 表示图片自动裁剪,不会变形

高级版
# 考虑到服务器可能会更改地址,需要进行变量定义
# 没有头像图片时,默认显示默认头像
第一步: 在自定义的config文件夹下,创建index.js文件,导出头像地址

第二步:在需要的页面,进行引入使用

14)屏幕缩小时,顶部导航栏变形处理
设置最小宽度,宽度小于设置最小宽度时,出现滚动条(多试几个最小宽度值)

15)axios请求拦截与响应拦截(加载Loading配置)
import axios from 'axios'
//引入加载组件
import { Loading } from 'element-ui';
//创建axios实例
const instance = axios.create({
baseURL: 'https://api.apiopen.top/api',
timeout: 3000,
});
//定义变量
let loading;
//定义开始加载函数
function startLoading() {
//创建loading实例
loading = Loading.service({
lock: true,
text: "加载中...",
background: 'rgba(0,0,0,0.8)'
})
}
//定义关闭加载函数
function endLoading() {
loading.close()
}
//添加请求拦截
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
//请求前开启加载函数
startLoading()
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
//对响应数据做点什么
//响应之后延迟3秒触发关闭加载函数
setTimeout(() => {
endLoading()
}, 3000);
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
16) element-ui upload 上传文件(注意样式的坑 删除掉.el-upload)
# dom 结构
<el-upload
class="avatar-uploader"
//真实的上传服务器地址
action="https://lianghj.top:8888/api/private/v1/upload"
:show-file-list="false"
//上传成功时的回调函数
:on-success="handleAvatarSuccess"
//上传中的回调函数
:on-progress="onProgress"
//上传前的回调函数
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
# js内容
data() {
return {
imageUrl: "",
};
},
methods: {
//上传成功触发的函数
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
setTimeout(() => {
//myplgin给VUE添加的成功消息提示信息
this.$scuessMessage();
//myplgin给VUE添加的结束Loading加载函数
this.$endLoading();
}, 3000);
},
//上传前触发的函数
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error("上传头像图片只能是 JPG 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return isJPG && isLt2M;
},
//上传中的触发函数
onProgress() {
//myplgin给VUE添加的Loading开始加载函数
this.$startLoading();
},
},
17)elment-ui Table表格组件基本使用
第一种
// 属性说明
1. :data 关联定义的数组数据
2. prop 直接展示数组数据中的字段
3. lable 表格的头部标题
4. :sortable 是否开启排序
<el-table
:data="tableData"
class="table" border style="width: 51%">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column prop="num" label="序号" width="80"></el-table-column>
<el-table-column
prop="createDate"
:sort-method="sortmethod"
label="日期"
width="125"
:sortable="true"
></el-table-column>
<el-table-column
prop="optUserName"
label="姓名"
width="120"
:sortable="true"
></el-table-column>
</el-table>
# scope.row 整行的数据
# scope.$index 当前行的索引
# 表格开头序号生成的两种方式
1. <el-table>
type="index"
</el-table>
2. <el-table>
<template slot-scope="scope">
{{scope.$index}}
</template>
</el-table>
# 表格插入按钮
<el-table>
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</template>
</el-table>
# scope报错解决
<template slot-scope="{}"> </template>
# dom 结构
<el-table
:data="tableData"
size="mini"
style="width: 100%">
<el-table-column
#//表头
label="角色编号"
width="180">
<template slot-scope="scope">
#//相当于循环tableData中每一项的roleId
<span style="margin-left: 10px">{{ scope.row.roleId }}</span>
</template>
</el-table-column>
<el-table-column
label="角色名称"
width="180">
<template slot-scope="scope">
#//相当于循环tableData中每一项的roleName
<span style="margin-left: 10px">{{ scope.row.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
# js 代码
data() {
return {
#//表格数据
tableData: [{
roleId:1,
roleName: '王小虎',
},
{
roleId:2,
roleName: '张三',
},
{
roleId:3,
roleName: '李四',
}
],
}
},
methods: {
#//编辑函数
handleEdit(index, row) {
console.log(index, row);
},
#//删除函数
handleDelete(index, row) {
console.log(index, row);
},
}
18)element-ui from表单组件基本使用
# dom 结构
<el-form
:model="ruleForm"
status-icon
#//验证规则
:rules="rules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
>
#//验证字段admin 不写prop无法进行验证
<el-form-item label="账号" prop="admin">
<el-input
type="text"
v-model="ruleForm.admin"
autocomplete="off"
></el-input>
</el-form-item>
#//验证字段checkPass 不写prop无法进行验证
<el-form-item label="确认密码" prop="checkPass">
<el-input
type="password"
v-model="ruleForm.checkPass"
autocomplete="off"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')"
>提交</el-button
>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
# js 代码
data() {
//验证账号规则
var validateAdmin = (rule, value, callback) => {
if (!value) {
return callback(new Error("账号不能为空"));
} else {
callback();
}
};
//验证密码规则
var validatePass = (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入密码"));
} else {
callback();
}
};
return {
#//表单数据
ruleForm: {
admin: "",
checkPass: "",
},
#//验证表单规则
rules: {
#//验证字段admin 验证方法为validateAdmin 验证方式为失焦验证
admin: [{ validator: validateAdmin, trigger: "blur" }],
#//验证字段checkPass 验证方法为validatePass 验证方式为失焦验证
checkPass: [{ validator: validatePass, trigger: "blur" }],
},
};
},
methods: {
#//提交表单方法(会自动执行设置的验证规则方法,全部通过才走if)
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false;
}
});
},
#//表单重置方法(清空表单内容)
resetForm(formName) {
this.$refs[formName].resetFields();
},
},
19)element-ui drawer抽屉组件基本使用
# dom 结构
<el-drawer
title="我是标题"
#//是否展示抽屉
:visible.sync="drawer"
#//抽屉打开方向
:direction="direction"
#//关闭抽屉弹框
:before-close="handleClose">
<span>我来啦!</span>
</el-drawer>
# js 代码
data() {
return {
#//显示抽屉
drawer: true,
#//打开抽屉方向为==>从右至左
direction: "rtl",
};
},
methods: {
#//关闭抽屉弹框(必须通过done()才可以关闭弹框)
handleClose(done) {
this.$confirm("确认关闭?")
.then((_) => {
done();
})
.catch((_) => {});
},
},
20)element-ui select 下拉框组件基本使用
# dom 结构
#// clearable属性表示可清空选项
<el-select clearable v-model="value" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
# js 代码
data() {
return {
options: [{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}],
value: ''
}
}
21)element-ui Pagination 分页功能)(表格与分页联动)
# 清楚了解每个字段的含义
# 表格dom结构
<el-table
#//计算选定页数时,截取当前页的数据
:data="tableData.slice((currentPage-1)*pagesize,currentPage*pagesize)"
size="mini"
style="width: 100%">
<el-table-column
label="角色编号"
width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.roleId }}</span>
</template>
</el-table-column>
<el-table-column
label="角色名称"
width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
# 分页dom结构
<div class="block">
<el-pagination
#//开启选中背景颜色
background
#//点击选中每页多少条事件
@size-change="handleSizeChange"
#//点击选中当前多少页事件
@current-change="handleCurrentChange"
#//默认选中第几页
:current-page="currentPage"
#// 多少条/页
:page-sizes="[5,10, 15, 20]"
#//每页展示多少条
:page-size="pagesize"
#// 自定义展示需要的样式(例如总条数,上一页图标,不要哪个就去掉)
layout="total, sizes, prev, pager, next, jumper"
#// 总条数等于表格数据的总长度
:total="tableData.length">
</el-pagination>
</div>
# js 代码
data() {
return {
#//默认选中页数为第一页
currentPage: 1,
#//每页展示5条数据
pagesize:5,
#//表格数据
tableData: [{
roleId:1,
roleName: '王小虎',
},
{
roleId:2,
roleName: '张三',
},
{
roleId:3,
roleName: '李四',
},
{
roleId:4,
roleName: '博裕',
},
{
roleId:5,
roleName: '博闻',
},
{
roleId:6,
roleName: '博容',
},
{
roleId:7,
roleName: '博文',
},
{
roleId:8,
roleName: '博艺',
},
{
roleId:9,
roleName: '博雅',
},
{
roleId:10,
roleName: '承德',
},
{
roleId:11,
roleName: '朝宗',
},
{
roleId:12,
roleName: '存志',
},
{
roleId:13,
roleName: '澹雅',
},
{
roleId:14,
roleName: '得韬',
},
{
roleId:15,
roleName: '德辉',
},
{
roleId:16,
roleName: '方旭',
},
{
roleId:17,
roleName: '飞轩',
},
{
roleId:18,
roleName: '芳华',
},
{
roleId:19,
roleName: '芳蕤',
},
{
roleId:20,
roleName: '芳苓',
},
{
roleId:21,
roleName: '芳蔼',
},
{
roleId:22,
roleName: '芳菲',
},
{
roleId:23,
roleName: '管彤',
},
{
roleId:24,
roleName: '和畅',
},
{
roleId:25,
roleName: '涵畅',
},
{
roleId:26,
roleName: '涵涵',
},
],
}
},
methods: {
handleEdit(index, row) {
console.log(index, row);
},
handleDelete(index, row) {
console.log(index, row);
},
#// 每页/条 事件
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.pagesize=val
},
#// 当前选中第几页
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.currentPage=val
}
}
22)动态修改VUE网页标题
const routes = [
{
path: '/home',
//路由元信息
meta:{title:'首页'},
component: () => import( '../views/Home.vue'),
},
{
path: '/detail',
//路由元信息
meta:{title:'详情页'},
component: () => import( '../views/Detail.vue'),
},
]
router.beforeEach((to,from,next)=>{
#//进行判断是否有meta字段和title
if(to.meta && to.meta.title){
#//设置网站标题
document.title=to.meta.title
}
next()
})
23)element-ui 富文本框基本使用(富文本框可以上传图片
# 首先先在plugin文件夹下的myplugin.js文件下引入vue-quill-editor和样式
#在main.js文件中进行 导入整个myplugin.js文件,然后vue进行注册
# myplugin.js文件
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'
Vue.use(VueQuillEditor)
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
# main.js文件
import './plugin/myplugin';
# dom结构
<quill-editor v-model="content" ref="myQuillEditor" style="height: 200px">
</quill-editor>
# js 代码
data(){
return{
content:''
}
}
24) 组件内修改elementui样式(必须有一个根节点,否则不生效)
#使用deep会报错
::v-deep .el-dialog__title{
margin-left: 600px;
}
# 或者
::v-deep {
.el-dialog__title {
margin-left: 600px;
color: #fff;
}
.el-dialog__header{
background-color: #d9001b;
}
}
###### vue3中 必须 :deep(.el-table th.el-table__cell) {
background-color: #f2f2f2;
}
25)vue2导出excel表格功能(获取使用xlsx插件)
1. 安装依赖
npm install vue-json-excel 或 yarn add vue-json-excel
2. 引入组件
1.全局引入
import Vue from 'vue'
import JsonExcel from 'vue-json-excel'
Vue.component('downloadExcel', JsonExcel)
2.局部引入
import JsonExcel from "vue-json-excel";
components: {
DownloadExcel: JsonExcel,
},
3. 在模版中使用
<download-excel
class="export-btn"
:data="tableData"
:fields="jsonFields"
type="xls"
header="患者列表"
name="患者列表.xls"
>
导出
</download-excel>
#参数说明
name=“患者列表”. ------------------导出Excel文件的文件名
header="患者列表" ------------------ 这是个excel的头部
:fields = “jsonFields” ------------------Excel中表头的名称(里面的属性是excel表每一列的title,用多个词组组成的属性名(中间有空格的)要加双引号; 指定接口的json内某些数据下载,若不指定,默认导出全部数据中心全部字段)
:data = “tableData”. -------------------导出的数据
type="xls" -------------------导出Excel的文件类型,默认为xls
4.Excel表格表头的设置

export default{
data(){
return{
jsonFields: { //导出Excel表格的表头设置
'序号': 'type',
'姓名': 'userName',
'年龄': 'age',
'手机号': 'phone',
'注册时间': 'createTime',
},
}
}
}
5.Excel表格中的数据
export default{
data(){
return{
tableData:[
{"userName":"张三","age":18,"gender":"phone":15612345612,"createTime":"2019-10-22"},
{"userName":"李四","age":17,"gender":"phone":15612345613,"createTime":"2019-10-23"},
{"userName":"王五","age":19,"gender":"phone":15612345615,"createTime":"2019-10-25"},
{"userName":"赵六","age":18,"gender":"phone":15612345618,"createTime":"2019-10-15"},
]
}
}
}
26)DatePicker日期控件
处理时间格式
<el-date-picker
format="yyyy 年 MM 月 dd 日" #日期控件显示的格式
value-format="yyyy-MM-dd"> #data数据显示的格式
</el-date-picker>
27)确认删除弹框提示
调用element-ui 对话框方法,将要操作的行为,放在.then中

28)对话框Dialog 嵌套表单

29)封装消息提示
//消息提示封装
import { Message } from 'element-ui';
function message_e(message){
Message.warning({
message: message,
})
}
function message_c(message){
Message.success({
message: message,
})
}
export{message_e,message_c}
30)日期控件基本使用
给日期控件添加 value-format="yyyy-MM-dd" 属性 在change事件中可获取到 2022-10-31 格式的值

31)vue自带加载提示
<div id="boxs"
v-loading="sendLoading" #true 打开加载, false关闭加载
element-loading-text="网络加载中"
element-loading-spinner="el-icon-loading"
>
32) 去除menu菜单栏白边
.el-menu {
border-right: 0;
}
33) menu菜单激活背景颜色修改
.el-menu-item.is-active {
background-color: #2674c5 !important;
}
34)element中的方法是组件自带的(不可以使用错误的@+方法名)
// 正确使用组件的方法
# 举例 清除全选的方法
1.给table组件 挂载dom <el-table ref="table"></el-table>
2.获取dom const table = ref();
3.使用相应的方法 table.value.clearSelection();
35) vue3 导出excel表格功能
下载插件(0.17.0版本,过高控制台报错)
yarn add xlsx
项目引入
import XLSX from "xlsx";
封装函数
/*
* @description:
* @param {Object} json 服务端发过来的数据
* @param {String} name 导出Excel文件名字
* @param {String} titleArr 导出Excel表头
* @param {String} sheetName 导出sheetName名字
* @return:
*/
import XLSX from "xlsx";
let exportExcel = function(json, name, titleArr, sheetName) {
/* convert state to workbook */
var data = new Array();
var keyArray = new Array();
const getLength = function (obj) {
var count = 0;
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
count++;
}
}
return count;
};
for (const key1 in json) {
if (json.hasOwnProperty(key1)) {
const element = json[key1];
var rowDataArray = new Array();
for (const key2 in element) {
if (element.hasOwnProperty(key2)) {
const element2 = element[key2];
rowDataArray.push(element2);
if (keyArray.length < getLength(element)) {
keyArray.push(key2);
}
}
}
data.push(rowDataArray);
}
}
// keyArray为英文字段表头
data.splice(0, 0, keyArray, titleArr);
console.log('data', data);
const ws = XLSX.utils.aoa_to_sheet(data);
const wb = XLSX.utils.book_new();
// 此处隐藏英文字段表头
var wsrows = [{ hidden: true }];
ws['!rows'] = wsrows; // ws - worksheet
XLSX.utils.book_append_sheet(wb, ws, sheetName);
/* generate file and send to client */
XLSX.writeFile(wb, name + '.xlsx');
}
export {exportExcel}
导入使用
import {exportExcel} from '../utils/excel.js'
import { ref } from "vue";
const tableData = ref([
{
userName: "张三",
age: 18,
gender: "男",
phone: 15612345612,
createTime: "2019-10-22",
},
{
userName: "李四",
age: 17,
gender: "男",
phone: 15612345613,
createTime: "2019-10-23",
},
{
userName: "王五",
age: 19,
gender: "男",
phone: 15612345615,
createTime: "2019-10-25",
},
{
userName: "赵六",
age: 18,
gender: "男",
phone: 15612345618,
createTime: "2019-10-15",
},
]);
const titleArr =ref(['姓名','年龄','性别','电话','创建时间']) //表头中文名
exportExcel(tableData.value, '表格数据', titleArr.value, 'sheetName');
36)vue3图片下载功能
const downloadIamge = (imgsrc,name)=>{
var image = new Image();
// 解决跨域 Canvas 污染问题
image.setAttribute("crossOrigin", "anonymous");
image.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
var url = canvas.toDataURL("image/png"); //得到图片的base64编码数据
var a = document.createElement("a"); // 生成一个a元素
var event = new MouseEvent("click"); // 创建一个单击事件
a.download = name || "photo"; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
image.src = imgsrc;
}
const downs = (url,name)=>{
imgUrl.value = url;
// 调用下载函数 传入图片的服务器地址和图片名字
downloadIamge(imgUrl.value,name)
}
37 element 表格全选与非全选(批量删除,新增,修改等功能)
### 究极大坑
// 不要使用其他事件 使用@selection-change = "selectChange" 即可
38) 登录流程和权限划分
1.将登录成功后的token存储在sotrge中,用户跳转页面时,作为是否登录的标志,如果storge中存在token,表示已登录了
2.将系统所有页面,分为两类(1.需要登录才可以访问,2.不需要登录直接访问)
3.前端在每次跳转时,需要做出如下判断
-
-
前往登录页面时
1.如果已经登陆过,禁止访问登录页
2.如果没有登录,直接跳转登录页
1.验证用户输入的账号密码,如果成功则往storge中存储token
-
前往非登录页时
1.用户无需登录就能访问的页面
1.直接跳转
2.用户需要登录才能访问的页面
1.如果用户还没有登录
1.进入登录页
2.如果用户已经登录(进行权限区分)
1.如果用户拥有此权限则正常访问
2.如果用户没有权限,则提示用户无权限
-
相关问题
Q1:在vue-router的beforEach方法进行逻辑处理时,页面跳转死循环永远空白?
A1:beforEach方法内的next并不会阻止代码继续执行,如果逻辑出现偏差,会导致next无限循环,从而出现白屏报错,对每一个next进行指定的if判断同时next后使用return终止代码执行,防止next无限循环
Q2:既然你是通过storage存储的信息,那么用户手动修改storage,不就可以访问了吗?
A2:
1.在beforEach内每次跳转时,请求一个判断用户是否登录的简易接口,如果当前后台返回用户正常信息,则代表用户已经登录,走流程。
弊端:每次跳转都要请求接口,会导致跳转卡顿,影响用户体验。
2.接口进行统一封装,所有接口都会携带用户token,针对必须登录才可以访问的接口,后台会去判断用户token是否失效,如果失效,此几口会返回用户未登录(token过去)。在封装内会对信息先进行一层处理,如果发现返回信息是用户未登录,则统一提示用户,并且返回登录页
main.js相关代码
1.和APP.vue同级别目录创建permisson.js文件
// 用于处理权限
import Store from "@/store";
import router from "@/router";
const routes = [
{
path: "aaa",
name: "aaa",
component: () => import("./views/AsdView.vue"),
},
{
path: "ddd",
name: "ddd",
component: () => import("./views/BbbView.vue"),
},
];
router.beforeEach((to, from, next) => {
const token = JSON.parse(localStorage.getItem("token"));
if (token && to.name == "login") next({ name: "home" });
if (!token && to.name !== "login") next({ name: "login" });
// 动态路由加载
if (token && Store.state.router.length === 0) {
Store.dispatch("fncgetrouters", ["aaa"]).then(() => {
// 权限集合 根据后端返回的信息 筛选出全部符合的页面(此处是模拟传递的['aaa'],实际应传递token 获取路由name字段表)
console.log(Store.state.router);
const routerRule = routes.filter((item) =>
Store.state.router.includes(item.name)
);
routerRule.forEach((item) => {
/**
* addRoute(参数1.参数2)
* 参数1:父路由的name,如果不写代表当前路由是1级路由
* 参数2:路由信息
*/
router.addRoute("home", item);
});
// 无权限页面
router.addRoute("home", {
path: "*",
name: "403",
component: () => import("./views/403View.vue"),
});
next();
return;
});
}
next();
});
export default router;
2. main.js文件
import router from './permisson' // 引入权限路由js文件
3.rouer/index.js文件
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: () => import("../views/HomeView.vue"),
children: [],
},
{
path: "/login",
name: "login",
component: () => import("../views/LoginView.vue"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
export default router;
4.Store/index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
// 获取路由实例
import router from '@/router'
console.log(router);
Vue.use(Vuex)
export default new Vuex.Store({
state: {
router:[]
},
getters: {
},
mutations: {
changerouter(state,data){
state.router = data
}
},
actions: {
// 请求数据获取到路由name字段表
fncgetrouters({commit},token){
commit('changerouter',token)
}
//伪代码
/** if(data.code == 200){
commit('changerouter',token)
return;
}
用户手动修改数据信息 登录过期
localStorage.clear()
router.push({name:'login})
*/
},
modules: {
}
})
39) 富文本上传图片
<!-- 富文本 -->
<div class="quill-editor">
<!-- 图片上传组件辅助-->
<el-upload
class="avatar-uploader"
:action="uploadUrl"
name="editorfm"
:show-file-list="false"
:on-success="uploadSuccess"
:before-upload="beforeUpload"
>
</el-upload>
<!--富文本编辑器组件-->
<quill-editor
v-model="content"
:content="content"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)"
ref="QuillEditor"
style="
width: 10.6125rem;
height: 3.1125rem;
margin-left: 3.7175rem;
margin-top: 0.25rem;
"
>
</quill-editor>
</div>
<script>
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 加粗,斜体,下划线,删除线
["blockquote", "code-block"], //引用,代码块
[{ header: 1 }, { header: 2 }], // 几级标题
[{ list: "ordered" }, { list: "bullet" }], // 有序列表,无序列表
[{ script: "sub" }, { script: "super" }], // 下角标,上角标
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ direction: "rtl" }], // 文字输入方向
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 颜色选择
[
{
font: [
"SimSun",
"SimHei",
"Microsoft-YaHei",
"KaiTi",
"FangSong",
"Arial",
],
},
], // 字体
[{ align: [] }], // 居中
["clean"], // 清除样式,
["link", "image"], // 上传图片、上传视频
];
data(){
return{
uploadUrl: `/api/editor_upload.php`, // 服务器上传地址
editorOption: {
placeholder: "请在这里输入",
theme: "snow", //主题 snow/bubble
modules: {
history: {
delay: 1000,
maxStack: 50,
userOnly: false,
},
toolbar: {
container: toolbarOptions,
handlers: {
image: function (value) {
if (value) {
// 调用element的图片上传组件
document.querySelector(".avatar-uploader input").click();
} else {
this.quill.format("image", false);
}
},
},
},
},
},
methods:{
uploadSuccess(res) {
// 获取富文本组件实例
let quill = this.$refs.QuillEditor.quill;
// 如果上传成功
if (res) {
this.loading = false
console.log(res);
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片,res为服务器返回的图片链接地址
quill.insertEmbed(length, "image", res.data.filepath);
// 调整光标到最后
quill.setSelection(length + 1);
} else {
// 提示信息,需引入Message
this.$message.error("图片插入失败!");
}
},
}
}
}
</script>
40)按需引入element-ui组件
1. 下载element-ui 和 babel-plugin-component
yarn add element-ui yarn add babel-plugin-component
2.在babel.config.js文件进行如下配置
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
plugins: [
[
"component",
{
libraryName: "element-ui",
styleLibraryName: "theme-chalk"
}
]
]
}
3.建立 /src/components/element/index.js js示例代码如下
import {
Button,
Input,
Radio,
Table,
Form
} from 'element-ui'
const coms = [
Button,
Input,
Radio,
Table,
Form
]
export default {
install(Vue){
coms.map(c => {
Vue.component(c.name, c)
})
}
}
4. main.js中引入局部插件并使用
import 'element-ui/lib/theme-chalk/index.css'
//引入 element 插件
import element from './components/element'
Vue.use(element)
按需引入使用 message消息提示 和 messageBox 弹框
import { Message,MessageBox } from 'element-ui';
// 使用
Message.success('成功提示');
Message.warning('警告提示');
Message.error('错误提示');
====================
// 使用
MessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 确认操作
}).catch(() => {
// 取消操作
})
41) ...省略 + 展开功能
<template>
<div id="box">
<div>
<div class="dd" v-if="!isExpanded">
{{ content.slice(0, maxLength) }}
{{ content.length > maxLength ? "..." : "" }}
<button @click="isExpanded = true">展开</button>
</div>
<div class="dd" v-else>
{{ content }}
<button @click="isExpanded = false">收起</button>
</div>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {
isExpanded: false,
content: "231654654654asd撒大苏打",
maxLength: 6, // 超过6个字符长度 ...
};
},
methods: {},
};
</script>
<style lang="scss" scoped>
.ebox {
width: 600px;
height: 600px;
}
button {
border: none;
background: transparent;
cursor: pointer;
font-size: 14px;
color: blue;
text-decoration: underline;
}
</style>
42 el-input 修改高度和图标位置
::v-deep .el-input__inner {
height: 0.4rem;
}
::v-deep .el-input__inner {
padding: 0 0.375rem;
}
::v-deep .el-input__icon {
line-height: 100%;
}
43) el-button 修改样式
### padding 设为0
.btn{
height: 0.4rem;
width: .825rem;
padding: 0;
}
44) menu菜单栏与路由事实联动
利用计算属性 实时监听vuex的改变
<el-menu
:default-active.sync="currentActive"
</el-menu>
computed:{
currentActive(){
return this.$store.state.path;
}
}
45)前端权限控制的意义
1. 降低非法操作的可能性
2. 尽可能排除不必要请求,减轻服务器压力
3. 提高用户体验,只展示自己相关的界面
# 前端权限的控制本质上来说,就是控制前端的 (视图层的展示) 和 前端所发送的(请求)
46)前端权限控制思路
1.菜单的控制
在登录请求中,会得到权限数据,根据权限数据,展示对应的菜单,点击菜单,才能查看相关的界面。
# 登录成功将菜单栏数据 存储在vuex 然后 在菜单栏页面获取,进行渲染
- 返回的数据结构

-
刷新菜单栏数据丢失问题(将sessionStorage和vuex同步)
![]()
-
退出登录时,清空sessionStorage 并跳转到登录页 同时进行刷新操作清空vuex数据

2.界面的控制
如果用户没有登录,手动在地址栏敲入管理界面的地址,则需要跳转到登录界面。
如果用户已经登录,手动敲入非权限内的地址,则需要跳转404界面。
-
登录成功后需要进行 路由守卫(未登录情况下,返回登录页) 和 动态路由(登录情况下,不具备权限页面的路由要删除掉,动态的进行路由配置)
动态路由配置
-
router.js
![]()
两个地方调用 动态添加路由方法
![]()
-
3.按钮的控制
在某个菜单的界面中,还得根据权限数据,展示出可进行操作的按钮,比如删除,修改,增加。
-
在动态路由中添加元信息(将页面的具体按钮相关权限,赋值给元信息)
![]()
-
在utils创建permission.js(进行自定义指令注册)必须在main.js中导入


- 在具体的页面,添加自定义指令

4.请求和响应的控制
如果用户通过非常规操作,比如通过浏览器调试工具将某些禁用的按钮变成启用状态,此时发送的请求,应该被拦截。
47) pc端屏幕适配自适应(使用建议width:auto,其他正常px单位)
1.安装适配插件
yarn add postcss-px-to-viewport
2.项目根目录添加配置文件
postcss.config.js
3.添加配置
module.exports = {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 1920, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1920 // 横屏时使用的视口宽度
}
}
}




浙公网安备 33010602011771号