🌈个人主页:前端青山
🔥系列专栏:vue篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来vue篇专栏内容:Vue2后台管理:项目开发全流程(二)
目录
功能实现
8、会员用户管理
①使用数据模拟文件插入数据
②使用表格显示数据
③数据的排序、筛选和查询
④添加会员用户
⑤删除会员
⑥编辑会员信息
9、路由切换动画
统计实现
1、数据展示统计
2、统计图展示
1、折线图、柱状图
2、饼状图
3、数据标注地图
四、第三方库使用
1、数据导出
2、数据导入
3、富文本编辑器
4、markdown编辑器
五、扩展补充
1、商品管理
2、上传图片实现
3、权限判断
4、权限管理
5、共享数据存储到vuex中
6、打包上线
功能实现
8、会员用户管理
查询 get方式获取到会员用户的JSON数据 并通过UI组件库以表格方式展示,筛选、排序、表头固定。。。
添加 弹出框表单数据进行正则校验,校验通过发送请求添加会员用户
修改
删除
①使用数据模拟文件插入数据
使用fakejs编写模拟数据,插入到数据库中。
image-20230215142711128
# 执行脚本插入数据
node mock.js
②使用表格显示数据
表格组件 https://element.eleme.io/#/zh-CN/component/table
用户数据表的管理,基本数据表操作
src\views\Admin\User.vue
<template>
<div>
<!-- data 表格显示列表数据 -->
<el-table :data="parseList" style="width: 100%" stripe>
<!-- 通过index 显示行号 -->
<el-table-column type="index" label="序号" width="50" align="center">
</el-table-column>
<!-- 表格的列 字段 -->
<!-- prop 就是数据对应的key -->
<!-- label 表头的文字 -->
<el-table-column prop="username" label="姓名" align="center">
</el-table-column>
<el-table-column prop="sex" label="性别" align="center">
</el-table-column>
<el-table-column prop="age" label="年龄" align="center">
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<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>
<div style="display: flex;justify-content: center;padding: 10px;">
<!-- 分页按钮 -->
<!-- total 数据总数 -->
<el-pagination background layout="prev, pager, next,sizes" :total="list.length" @current-change="changePage"
@size-change="handleSizeChange" :page-sizes="[2, 4, 6, 8, 10]" :page-size="pageSize">
</el-pagination>
</div>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
return {
list: [],
// 本地存储当前页面 初始化从1开始
currentPage: 1,
// 每页显示几个
pageSize: 9
}
},
methods: {
changePage(value) {
// 通过点击页码按钮 将当前的页码数进行赋值修改
this.currentPage = value
},
// 修改每页显示的条数
handleSizeChange(value) {
// console.log(value);
this.pageSize = value
}
},
computed: {
parseList() {
// start end
// 第1页 0,10
// 第2页 10,20
// 第3页 20,30
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
// console.log(start, end);
// console.log(this.list);
return this.list.slice(start, end)
}
},
created() {
req.get(url.Members).then(res => {
console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
}
}
</script>
<style lang="scss" scoped></style>
翻页实现:
前端翻页:数据全部加载回来 前端切割进行分页
js截取数组
服务端翻页:传递页码参数 获取到不同页码的数据 sql 语句里的limit语法
limt(start,length)
select * from members limit 1,5
③数据的排序、筛选和查询
排序和筛选可以使用表格组件配置项实现 在前端进行操作
查询
前端搜索 根据关键字 遍历数据 匹配字符串
后端搜索 传递关键字给服务端接口 并将返回的数据进行渲染显示
src\views\Admin\User.vue
<template>
<div>
<div style="display: flex;justify-content: flex-end;">
<div style="width: 300px;padding: 20px;display: flex;justify-content: space-around;">
<el-button type="primary" @click="loadList" style="margin-right: 10px;">重置</el-button>
<el-input
placeholder="请输入手机号" v-model="phone" clearable>
</el-input>
<el-button type="primary" @click="searchPhone" style="margin-left: 10px;">搜索</el-button>
</div>
</div>
<!-- data 表格显示列表数据 -->
<el-table :data="parseList" style="width: 100%" stripe>
<!-- 通过index 显示行号 -->
<el-table-column type="index" label="序号" width="50" align="center">
</el-table-column>
<!-- 表格的列 字段 -->
<!-- prop 就是数据对应的key -->
<!-- label 表头的文字 -->
<el-table-column prop="username" label="姓名" align="center">
</el-table-column>
<!-- filters 筛选 -->
<el-table-column prop="sex" label="性别" align="center"
:filters="[{ text: '男', value: '男' }, { text: '女', value: '女' }]" :filter-method="filterSex">
</el-table-column>
<!-- sortable排序 -->
<el-table-column prop="age" label="年龄" align="center" sortable>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<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>
<div style="display: flex;justify-content: center;padding: 10px;">
<!-- 分页按钮 -->
<!-- total 数据总数 -->
<el-pagination background layout="prev, pager, next,sizes" :total="list.length" @current-change="changePage"
@size-change="handleSizeChange" :page-sizes="[2, 4, 6, 8, 9, 10, list.length]" :page-size="pageSize">
</el-pagination>
</div>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
return {
list: [],
// 本地存储当前页面 初始化从1开始
currentPage: 1,
// 每页显示几个
pageSize: 9,
// 查询手机号
phone: ''
}
},
methods: {
// 加载会员列表数据
loadList() {
req.get(url.Members).then(res => {
console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
},
changePage(value) {
// 通过点击页码按钮 将当前的页码数进行赋值修改
this.currentPage = value
},
// 修改每页显示的条数
handleSizeChange(value) {
// console.log(value);
this.pageSize = value
},
// 筛选性别
filterSex(value, row, column) {
// console.log(value);
// console.log(row);
// console.log(column);
// 字段的key
const property = column['property'];
return row[property] === value
// return row['sex'] === value
},
// 通过手机号模糊查询用户信息
searchPhone() {
req.get(url.Member, {
params: { phone: this.phone }
}).then(res => {
console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
}
},
computed: {
parseList: {
get() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.list.slice(start, end)
},
// set(value) {
// console.log(value)
// return value
// }
}
// parseList() {
// // start end
// // 第1页 0,10
// // 第2页 10,20
// // 第3页 20,30
// let start = (this.currentPage - 1) * this.pageSize
// let end = this.currentPage * this.pageSize
// // console.log(start, end);
// // console.log(this.list);
// return this.list.slice(start, end)
// }
},
created() {
this.loadList()
}
}
</script>
<style lang="scss" scoped></style>
④添加会员用户
添加按钮点击触发抽屉弹出层
弹出层中布局会员信息表单,实现校验规则
收集表单并调用添加会员接口,添加会员数据,并返回提示
<template>
<div>
<!-- 1、搜索操作区 -->
<div
style="display: flex;justify-content:space-between;background-color: #fff;margin-bottom: 20px;border-radius: 10px;">
<div style="display: flex;align-items: center;">
<el-button type="primary" @click="dialog = true" style="margin-left: 10px;">添加会员</el-button>
</div>
<div style="width: 300px;padding: 20px;display: flex;justify-content: space-around;">
<el-button type="primary" @click="loadList" style="margin-right: 10px;">重置</el-button>
<el-input placeholder="请输入手机号" v-model="phone" clearable>
</el-input>
<el-button type="primary" @click="searchPhone" style="margin-left: 10px;">搜索</el-button>
</div>
</div>
<!-- 2、表格显示 -->
<div style="padding: 15px;background-color: #fff;border-radius: 10px;">
<!-- data 表格显示列表数据 -->
<el-table :data="parseList" style="width: 100%" max-height="500" stripe>
<!-- 通过index 显示行号 -->
<el-table-column type="index" label="序号" width="50" align="center">
</el-table-column>
<!-- 表格的列 字段 -->
<!-- prop 就是数据对应的key -->
<!-- label 表头的文字 -->
<el-table-column prop="username" label="姓名" align="center">
</el-table-column>
<!-- filters 筛选 -->
<el-table-column prop="sex" label="性别" align="center"
:filters="[{ text: '男', value: '男' }, { text: '女', value: '女' }]" :filter-method="filterSex">
</el-table-column>
<!-- sortable排序 -->
<el-table-column prop="age" label="年龄" align="center" sortable>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<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>
</div>
<!-- 3、翻页组件 -->
<div style="display: flex;justify-content: center;padding: 10px;background-color: #fff;">
<!-- 分页按钮 -->
<!-- total 数据总数 -->
<el-pagination background layout="prev, pager, next,sizes" :total="list.length" @current-change="changePage"
@size-change="handleSizeChange" :page-sizes="[2, 4, 6, 7, 8, 9, 10, list.length]" :page-size="pageSize">
</el-pagination>
</div>
<!-- 抽屉表单 -->
<!-- before-close 关闭抽屉时触发 -->
<!-- visible 是否显示抽屉 -->
<!-- direction 弹出的位置 -->
<el-drawer title="添加会员" :before-close="handleClose" :visible.sync="dialog" direction="rtl" ref="drawer" size="40%">
<div class="drawer__content">
<!-- 会员表单 -->
<!-- :rules="rules" 校验规则 -->
<el-form :model="form" :rules="rules">
<el-form-item label="姓名" prop="username" :label-width="formLabelWidth">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex" :label-width="formLabelWidth">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年龄" prop="age" :label-width="formLabelWidth">
<el-input v-model="form.age" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="phone" :label-width="formLabelWidth">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div class="drawer__footer">
<el-button @click="cancelForm">取 消</el-button>
<el-button type="primary" :loading="loading" @click="save">
{{ loading ? '提交中 ...' : '确 定' }}
</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
// 检测年龄合法性
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
try {
value = Number(value)
} catch {
callback(new Error('请输入数字值'));
}
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
return {
list: [],
// 本地存储当前页面 初始化从1开始
currentPage: 1,
// 每页显示几个
pageSize: 7,
// 查询手机号
phone: '',
// 会员表单数据
form: {
username: '',
sex: '',
age: '',
phone: ''
},
// 表单的宽度
formLabelWidth: '80px',
// 抽屉是否弹出
dialog: false,
// 表单提交加载状态
loading: false,
// 表单校验规则
rules: {
username: [
{ required: true, message: '请输入会员名', trigger: 'blur' },
{ min: 2, max: 6, message: '长度在 2 到 6 个字符', trigger: 'blur' }
],
sex: [
{ required: true, message: '请选择性别', trigger: 'blur' },
],
age: [
{ required: true, message: '请输入年龄', trigger: 'blur' },
{ validator: checkAge, trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ min: 11, max: 11, message: '手机号格式错误', trigger: 'blur' }
],
}
}
},
methods: {
// 加载会员列表数据
loadList() {
req.get(url.Members).then(res => {
console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
},
changePage(value) {
// 通过点击页码按钮 将当前的页码数进行赋值修改
this.currentPage = value
},
// 修改每页显示的条数
handleSizeChange(value) {
// console.log(value);
this.pageSize = value
},
// 筛选性别
filterSex(value, row, column) {
// console.log(value);
// console.log(row);
// console.log(column);
// 字段的key
const property = column['property'];
return row[property] === value
// return row['sex'] === value
},
// 通过手机号模糊查询用户信息
searchPhone() {
req.get(url.Member, {
params: { phone: this.phone }
}).then(res => {
console.log(res);
if (res.data.code === 0) {
// 将搜索结果进行赋值
this.list = res.data.data
}
})
},
// 抽屉弹出层操作方法
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('确定要关闭表单吗?')
.then(_ => {
this.dialog = false;
})
.catch(_ => { });
},
cancelForm() {
this.loading = false;
this.dialog = false;
},
// 提交表单保存数据
save() {
this.$confirm('确定要提交表单吗?')
.then(_ => {
this.loading = true;
// 发送请求添加会员
req.post(url.Members, this.form).then(res => {
// console.log(res);
if (res.data.code === 0) {
this.$message({
message: '添加会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '添加会员失败',
duration: 1000,
type: 'error'
})
}
// 关闭抽屉弹出层
this.dialog = false
// 发送请求调用新数据
this.loadList()
})
// 动画关闭需要一定的时间
setTimeout(() => {
this.loading = false;
}, 400);
})
.catch(_ => { });
}
},
computed: {
parseList: {
get() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.list.slice(start, end)
},
// set(value) {
// console.log(value)
// return value
// }
}
// parseList() {
// // start end
// // 第1页 0,10
// // 第2页 10,20
// // 第3页 20,30
// let start = (this.currentPage - 1) * this.pageSize
// let end = this.currentPage * this.pageSize
// // console.log(start, end);
// // console.log(this.list);
// return this.list.slice(start, end)
// }
},
created() {
this.loadList()
}
}
</script>
<style lang="scss" scoped>
.drawer__content {
padding: 20px;
}
.drawer__footer {
padding-left: 20px;
}
</style>
⑤删除会员
<template>
<div>
<!-- 1、搜索操作区 -->
<div
style="display: flex;justify-content:space-between;background-color: #fff;margin-bottom: 20px;border-radius: 10px;">
<div style="display: flex;align-items: center;">
<el-button type="primary" @click="dialog = true" style="margin-left: 10px;">添加会员</el-button>
<!-- <el-button type="primary" @click="size='medium'" style="margin-left: 10px;">大表格</el-button>
<el-button
type="primary" @click="size = 'small'" style="margin-left: 10px;">小表格</el-button> -->
</div>
<div style="width: 300px;padding: 20px;display: flex;justify-content: space-around;">
<el-button type="primary" @click="loadList" style="margin-right: 10px;">重置</el-button>
<el-input placeholder="请输入手机号" v-model="phone" clearable>
</el-input>
<el-button type="primary" @click="searchPhone" style="margin-left: 10px;">搜索</el-button>
</div>
</div>
<!-- 2、表格显示 -->
<div style="padding: 15px;background-color: #fff;border-radius: 10px;">
<!-- data 表格显示列表数据 -->
<el-table :size="size" :data="parseList" style="width: 100%" max-height="500" stripe>
<!-- 通过index 显示行号 -->
<el-table-column type="index" label="序号" width="50" align="center">
</el-table-column>
<!-- 表格的列 字段 -->
<!-- prop 就是数据对应的key -->
<!-- label 表头的文字 -->
<el-table-column prop="username" label="姓名" align="center">
</el-table-column>
<!-- filters 筛选 -->
<el-table-column prop="sex" label="性别" align="center"
:filters="[{ text: '男', value: '男' }, { text: '女', value: '女' }]" :filter-method="filterSex">
</el-table-column>
<!-- sortable排序 -->
<el-table-column prop="age" label="年龄" align="center" sortable>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<template slot-scope="scope">
<el-button type="success" size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-info" icon-color="red"
title="确定删除吗?" @confirm="handleDelete(scope.$index, scope.row)">
<!-- 注意以下按钮需要使用slot插槽 不使用插槽就不显示了 -->
<el-button size="mini" type="danger" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<!-- 3、翻页组件 -->
<div style="display: flex;justify-content: center;padding: 10px;background-color: #fff;">
<!-- 分页按钮 -->
<!-- total 数据总数 -->
<el-pagination background layout="prev, pager, next,sizes" :total="list.length" @current-change="changePage"
@size-change="handleSizeChange" :page-sizes="[2, 4, 6, 7, 8, 9, 10, list.length]" :page-size="pageSize">
</el-pagination>
</div>
<!-- 抽屉表单 -->
<!-- before-close 关闭抽屉时触发 -->
<!-- visible 是否显示抽屉 -->
<!-- direction 弹出的位置 -->
<el-drawer title="添加会员" :before-close="handleClose" :visible.sync="dialog" direction="rtl" ref="drawer" size="40%">
<div class="drawer__content">
<!-- 会员表单 -->
<!-- :rules="rules" 校验规则 -->
<el-form :model="form" :rules="rules">
<el-form-item label="姓名" prop="username" :label-width="formLabelWidth">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex" :label-width="formLabelWidth">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年龄" prop="age" :label-width="formLabelWidth">
<el-input v-model="form.age" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="phone" :label-width="formLabelWidth">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div class="drawer__footer">
<el-button @click="cancelForm">取 消</el-button>
<el-button type="primary" :loading="loading" @click="save">
{{ loading ? '提交中 ...' : '确 定' }}
</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
// 检测年龄合法性
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
try {
value = Number(value)
} catch {
callback(new Error('请输入数字值'));
}
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
return {
list: [],
// 本地存储当前页面 初始化从1开始
currentPage: 1,
// 每页显示几个
pageSize: 7,
// 查询手机号
phone: '',
// 会员表单数据
form: {
username: '',
sex: '',
age: '',
phone: ''
},
// 表单的宽度
formLabelWidth: '80px',
// 抽屉是否弹出
dialog: false,
// 表单提交加载状态
loading: false,
// 表单校验规则
rules: {
username: [
{ required: true, message: '请输入会员名', trigger: 'blur' },
{ min: 2, max: 6, message: '长度在 2 到 6 个字符', trigger: 'blur' }
],
sex: [
{ required: true, message: '请选择性别', trigger: 'blur' },
],
age: [
{ required: true, message: '请输入年龄', trigger: 'blur' },
{ validator: checkAge, trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ min: 11, max: 11, message: '手机号格式错误', trigger: 'blur' }
],
},
// 表格显示大小
size: 'medium'
}
},
methods: {
// 加载会员列表数据
loadList() {
req.get(url.Members).then(res => {
console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
},
changePage(value) {
// 通过点击页码按钮 将当前的页码数进行赋值修改
this.currentPage = value
},
// 修改每页显示的条数
handleSizeChange(value) {
// console.log(value);
this.pageSize = value
},
// 筛选性别
filterSex(value, row, column) {
// console.log(value);
// console.log(row);
// console.log(column);
// 字段的key
const property = column['property'];
return row[property] === value
// return row['sex'] === value
},
// 通过手机号模糊查询用户信息
searchPhone() {
req.get(url.Member, {
params: { phone: this.phone }
}).then(res => {
console.log(res);
if (res.data.code === 0) {
// 将搜索结果进行赋值
this.list = res.data.data
}
})
},
// 抽屉弹出层操作方法
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('确定要关闭表单吗?')
.then(_ => {
this.dialog = false;
})
.catch(_ => { });
},
cancelForm() {
this.loading = false;
this.dialog = false;
},
// 提交表单保存数据
save() {
this.$confirm('确定要提交表单吗?')
.then(_ => {
this.loading = true;
// 发送请求添加会员
req.post(url.Members, this.form).then(res => {
// console.log(res);
if (res.data.code === 0) {
this.$message({
message: '添加会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '添加会员失败',
duration: 1000,
type: 'error'
})
}
// 关闭抽屉弹出层
this.dialog = false
// 发送请求调用新数据
this.loadList()
})
// 动画关闭需要一定的时间
setTimeout(() => {
this.loading = false;
}, 400);
})
.catch(_ => { });
},
// 删除会员用户
handleDelete(index, row) {
// console.log(index);
// console.log(row);
req.delete(`${url.Members}/${row.phone}`).then(res => {
console.log(res);
if (res.data.code === 0) {
this.$message({
message: '删除会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '删除会员失败',
duration: 1000,
type: 'error'
})
}
this.loadList()
// window.location.reload()
})
}
},
computed: {
parseList: {
get() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.list.slice(start, end)
},
// set(value) {
// console.log(value)
// return value
// }
}
// parseList() {
// // start end
// // 第1页 0,10
// // 第2页 10,20
// // 第3页 20,30
// let start = (this.currentPage - 1) * this.pageSize
// let end = this.currentPage * this.pageSize
// // console.log(start, end);
// // console.log(this.list);
// return this.list.slice(start, end)
// }
},
created() {
this.loadList()
}
}
</script>
<style lang="scss" scoped>
.drawer__content {
padding: 20px;
}
.drawer__footer {
padding-left: 20px;
}
</style>
⑥编辑会员信息
<template>
<div>
<!-- 1、搜索操作区 -->
<div
style="display: flex;justify-content:space-between;background-color: #fff;margin-bottom: 20px;border-radius: 10px;">
<div style="display: flex;align-items: center;">
<el-button type="primary" @click="handleAdd" style="margin-left: 10px;">添加会员</el-button>
<!-- <el-button type="primary" @click="size='medium'" style="margin-left: 10px;">大表格</el-button>
<el-button
type="primary" @click="size = 'small'" style="margin-left: 10px;">小表格</el-button> -->
</div>
<div style="width: 300px;padding: 20px;display: flex;justify-content: space-around;">
<el-button type="primary" @click="loadList" style="margin-right: 10px;">重置</el-button>
<el-input placeholder="请输入手机号" v-model="phone" clearable>
</el-input>
<el-button type="primary" @click="searchPhone" style="margin-left: 10px;">搜索</el-button>
</div>
</div>
<!-- 2、表格显示 -->
<div style="padding: 15px;background-color: #fff;border-radius: 10px;">
<!-- data 表格显示列表数据 -->
<el-table :size="size" :data="parseList" style="width: 100%" max-height="500" stripe>
<!-- 通过index 显示行号 -->
<el-table-column type="index" label="序号" width="50" align="center">
</el-table-column>
<!-- 表格的列 字段 -->
<!-- prop 就是数据对应的key -->
<!-- label 表头的文字 -->
<el-table-column prop="username" label="姓名" align="center">
</el-table-column>
<!-- filters 筛选 -->
<el-table-column prop="sex" label="性别" align="center"
:filters="[{ text: '男', value: '男' }, { text: '女', value: '女' }]" :filter-method="filterSex">
</el-table-column>
<!-- sortable排序 -->
<el-table-column prop="age" label="年龄" align="center" sortable>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-info" icon-color="red"
title="确定删除吗?" @confirm="handleDelete(scope.$index, scope.row)">
<!-- 注意以下按钮需要使用slot插槽 不使用插槽就不显示了 -->
<el-button type="danger" size="small" slot="reference" style="margin-left: 10px;">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<!-- 3、翻页组件 -->
<div style="display: flex;justify-content: center;padding: 10px;background-color: #fff;">
<!-- 分页按钮 -->
<!-- total 数据总数 -->
<el-pagination background layout="prev, pager, next,sizes" :total="list.length" @current-change="changePage"
@size-change="handleSizeChange" :page-sizes="[2, 4, 6, 7, 8, 9, 10, list.length]" :page-size="pageSize">
</el-pagination>
</div>
<!-- 抽屉表单 -->
<!-- before-close 关闭抽屉时触发 -->
<!-- visible 是否显示抽屉 -->
<!-- direction 弹出的位置 -->
<!-- <el-drawer title="添加会员" :before-close="handleClose" :visible.sync="dialog" direction="rtl" ref="drawer" size="40%"> -->
<el-drawer :title="direction === 'rtl' ? '添加会员' : '修改会员'" :visible.sync="dialog" :direction="direction" ref="drawer"
size="40%">
<div class="drawer__content">
<!-- 会员表单 -->
<!-- :rules="rules" 校验规则 -->
<el-form :model="form" :rules="rules" ref="ruleForm">
<el-form-item label="姓名" prop="username" :label-width="formLabelWidth">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex" :label-width="formLabelWidth">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年龄" prop="age" :label-width="formLabelWidth">
<el-input v-model="form.age" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="phone" :label-width="formLabelWidth">
<el-input v-model="form.phone" autocomplete="off" :disabled="direction === 'ltr'"></el-input>
</el-form-item>
</el-form>
<div class="drawer__footer">
<el-button @click="cancelForm">取 消</el-button>
<el-button type="primary" :loading="loading" @click="save">
{{ loading ? '提交中 ...' : '确 定' }}
</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
// 检测年龄合法性
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
try {
value = Number(value)
} catch {
callback(new Error('请输入数字值'));
}
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
return {
list: [],
// 本地存储当前页面 初始化从1开始
currentPage: 1,
// 每页显示几个
pageSize: 7,
// 查询手机号
phone: '',
// 会员表单数据
form: {
username: '',
sex: '',
age: '',
phone: ''
},
// 表单的宽度
formLabelWidth: '80px',
// 抽屉是否弹出
dialog: false,
// 抽屉弹出的位置
direction: 'rtl',
// 表单提交加载状态
loading: false,
// 表单校验规则
rules: {
username: [
{ required: true, message: '请输入会员名', trigger: 'blur' },
{ min: 2, max: 6, message: '长度在 2 到 6 个字符', trigger: 'blur' }
],
sex: [
{ required: true, message: '请选择性别', trigger: 'blur' },
],
age: [
{ required: true, message: '请输入年龄', trigger: 'blur' },
{ validator: checkAge, trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ min: 11, max: 11, message: '手机号格式错误', trigger: 'blur' }
],
},
// 表格显示大小
size: 'medium'
}
},
methods: {
// 加载会员列表数据
loadList() {
req.get(url.Members).then(res => {
// console.log(res);
if (res.data.code === 0) {
this.list = res.data.data
}
})
},
changePage(value) {
// 通过点击页码按钮 将当前的页码数进行赋值修改
this.currentPage = value
},
// 修改每页显示的条数
handleSizeChange(value) {
// console.log(value);
this.pageSize = value
},
// 筛选性别
filterSex(value, row, column) {
// console.log(value);
// console.log(row);
// console.log(column);
// 字段的key
const property = column['property'];
return row[property] === value
// return row['sex'] === value
},
// 通过手机号模糊查询用户信息
searchPhone() {
req.get(url.Member, {
params: { phone: this.phone }
}).then(res => {
console.log(res);
if (res.data.code === 0) {
// 将搜索结果进行赋值
this.list = res.data.data
}
})
},
// 抽屉弹出层操作方法
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('确定要关闭表单吗?')
.then(_ => {
this.dialog = false;
})
.catch(_ => { });
},
cancelForm() {
this.loading = false;
this.dialog = false;
this.loadList()
},
// 提交表单保存数据
save() {
this.$confirm('确定要提交表单吗?')
.then(_ => {
// 表单进行校验
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.loading = true;
// 根据抽屉弹出位置 确定是修改还是添加
if (this.direction === 'rtl') {
// 发送请求添加会员
req.post(url.Members, this.form).then(res => {
// console.log(res);
if (res.data.code === 0) {
this.$message({
message: '添加会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '添加会员失败',
duration: 1000,
type: 'error'
})
}
})
} else {
// 修改会员信息
// 发送请求添加会员
req.put(url.Members, this.form).then(res => {
console.log(res);
if (res.data.code === 0) {
this.$message({
message: '修改会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '修改会员失败',
duration: 1000,
type: 'error'
})
}
})
}
// 关闭抽屉弹出层
this.dialog = false
this.loading = false
// 发送请求调用新数据
this.loadList()
} else {
// 校验不通过
console.log('error submit!!');
return false;
}
})
})
},
// 删除会员用户
handleDelete(index, row) {
// console.log(index);
// console.log(row);
req.delete(`${url.Members}/${row.phone}`).then(res => {
console.log(res);
if (res.data.code === 0) {
this.$message({
message: '删除会员成功',
duration: 1000,
type: 'success'
})
} else {
this.$message({
message: '删除会员失败',
duration: 1000,
type: 'error'
})
}
this.loadList()
// window.location.reload()
})
},
// 编辑会员用户
handleEdit(index, row) {
this.direction = 'ltr'
this.dialog = true
this.form = row
},
// 添加会员用户
handleAdd() {
this.form = {
username: '',
sex: '',
age: '',
phone: ''
}
this.direction = 'rtl'
this.dialog = true
}
},
computed: {
parseList: {
get() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.list.slice(start, end)
},
}
},
created() {
this.loadList()
}
}
</script>
<style lang="scss" scoped>
.drawer__content {
padding: 20px;
}
.drawer__footer {
padding-left: 20px;
}
</style>
9、路由切换动画
<transition>
元素作为单个元素/组件的过渡效果。<transition>
只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。
src\views\Admin\Admin.vue
<!-- 路由切换过渡动画 -->
<transition name="el-zoom-in-center">
<router-view></router-view>
</transition>
统计实现
1、数据展示统计
image-20230217103817527
动画效果库
npm i animate.css
src\views\Admin\Dashboard\Dashboard.vue
<template>
<div>
<!-- <el-switch v-model="style" active-text="矩形" inactive-text="圆形">
</el-switch> -->
<div class="container">
<div :class="style ? 'card' : 'circle'" :style="{ background: item.color }" v-for="item in counts">
<div>
{{ item.name }}
</div>
<div>
{{ item.num }}
</div>
</div>
</div>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
// 导入animate.css动画库
import 'animate.css';
export default {
data() {
return {
counts: [],
// 数据统计展示的样式
style: false
}
},
created() {
this.loadCount()
},
methods: {
// 请求获取统计数据
loadCount() {
req.get(url.Counts).then(res => {
console.log(res);
this.counts = res.data
})
}
},
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
justify-content: space-around;
background-color: #fff;
margin-bottom: 20px;
border-radius: 10px;
padding: 20px;
}
.card {
width: 180px;
height: 60px;
border-radius: 10px;
border: 1px solid #ccc;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
}
.circle {
width: 150px;
height: 150px;
border-radius: 50%;
border: 1px solid #ccc;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
/* transition:width 1s;
transition:height 1s; */
/* & 当前选择器 */
&:hover{
cursor: pointer;
/* width: 160px;
height: 160px; */
/* animation:rotateIn; */
animation: heartBeat;
animation-duration: 1s;
}
}
</style>
2、统计图展示
将数据统计的结果使用图表展示,使其更加直观
实现折线图,柱状图,饼状图,地图标注等...
echarts实现图表统计 https://echarts.apache.org/zh/index.html
1、折线图、柱状图
①安装echarts
npm i echarts
②根据echarts示例实现
③调用数据,并替换图例的数据
④调整图例的样式,根据需求决定是否进行组件封装
src\views\Admin\Dashboard\components\LineMap.vue
<template>
<!-- echarts图表渲染容器 必须具有宽高 -->
<!-- v-if 判断是否渲染 请求数据返回后再渲染 -->
<div id="main" style="width: 600px;height:400px;background-color: #fff;border-radius: 10px;">
</div>
</template>
<script>
import * as echarts from 'echarts';
import url from '@/config/url';
import req from '@/utils/request'
export default {
data() {
return {
saleCount: [],
counts: 1
}
},
mounted() {
// 发送请求获取销售量统计数据
this.loadData()
// 调用折线图
// this.loadLineMap()
// 动态调用数据渲染图表
// setInterval(() => {
// this.counts++
// this.loadData()
// }, 1000)
},
methods: {
loadData() {
req.get(url.SaleCount).then(res => {
this.saleCount = res.data
// 调用折线图
// this.loadLineMap()
})
},
loadLineMap() {
// 查找渲染容器
var chartDom = document.getElementById('main');
// 初始化echarts
var myChart = echarts.init(chartDom);
var option;
// 配置项
option = {
// 标题
title: {
text: '近七日销售量趋势图'
},
// 图例
legend: {
data: ['折线图', '柱状图']
},
// 配置显示颜色
color: ['red', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
// x轴配置
xAxis: {
type: 'category',
data: this.xdata
},
yAxis: {
type: 'value'
},
series: [
{
name: '折线图',
// 统计显示的数据
data: this.ydata,
// 图表类型
type: 'line',
// 平滑曲线
smooth: true
},
{
name: '柱状图',
data: this.ydata,
type: 'bar',
smooth: true
},
]
};
// 设置echarts的配置项
option && myChart.setOption(option);
}
},
computed: {
xdata() {
// console.log(this.saleCount.map(item => item.name));
return this.saleCount.map(item => item.name)
},
ydata() {
return this.saleCount.map(item => item.num * this.counts)
}
},
watch: {
saleCount(newValue, oldValue) {
this.loadLineMap()
}
},
}
</script>
<style lang="scss" scoped></style>
2、饼状图
src\views\Admin\Dashboard\components\Pie.vue
<template>
<div id="pie" style="width: 400px;height:400px;background-color: #fff;border-radius: 10px;"></div>
</template>
<script>
import * as echarts from 'echarts';
import url from '@/config/url';
import req from '@/utils/request'
export default {
data() {
return {
sexCount: []
}
},
mounted() {
this.loadData()
},
methods: {
// 加载数据
loadData() {
req.get(url.SexCount).then(res => {
this.sexCount = res.data.reverse()
// 调用饼图
this.loadPieMap()
})
},
// 加载饼图
loadPieMap() {
var chartDom = document.getElementById('pie');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '用户性别分布情况',
padding: [
15, // 上
10, // 右
5, // 下
15, // 左
]
},
color: ['#f56c6c', '#409eff'],
// legend: {
// top: 'bottom'
// },
// 工具箱
toolbox: {
show: true,
feature: {
mark: { show: true },
// dataView: { show: true, readOnly: false },
// 还原数据
restore: { show: true },
// 下载保存为图片
saveAsImage: { show: true }
}
},
// 显示图例的具体数据值
tooltip: {
trigger: 'item'
},
series: [
{
name: '用户性别',
type: 'pie',
// 图例最小和最大显示
// radius: [40, 150],
radius: ['40%', '70%'],
// 图例显示的位置 横向 竖向
center: ['50%', '50%'],
// roseType: 'area',
itemStyle: {
// 图例显示的圆角
// borderRadius: 8,
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
// data: [
// { value: 40, name: 'rose 1' },
// { value: 38, name: 'rose 2' },
// { value: 32, name: 'rose 3' },
// { value: 30, name: 'rose 4' },
// { value: 28, name: 'rose 5' },
// { value: 26, name: 'rose 6' },
// { value: 22, name: 'rose 7' },
// { value: 18, name: 'rose 8' }
// ]
// data: [
// { value: 65, name: '女' },
// { value: 56, name: '男' }
// ]
data: this.sexCount
}
]
};
option && myChart.setOption(option);
}
},
}
</script>
<style lang="scss" scoped></style>
3、数据标注地图
①获取地图的经纬度范围 geo数据
http://datav.aliyun.com/portal/school/atlas/area_selector
②找到地图的示例
③根据配置项将地图的范围参数进行设置
④地图数据标注
src\views\Admin\Dashboard\components\Map.vue
<template>
<div id="map" style="width: 100%;height:900px;background-color: #fff;border-radius: 10px;">
</div>
</template>
<script>
import * as echarts from 'echarts'
import axios from 'axios'
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
return {
population: []
}
},
mounted() {
this.loadData()
},
methods: {
loadData() {
req.get(url.Population).then(res => {
this.population = res.data
this.loadMap()
})
},
loadMap() {
var chartDom = document.getElementById('map');
var myChart = echarts.init(chartDom);
var option;
myChart.showLoading();
axios.get('http://localhost:3000/china').then(res => {
myChart.hideLoading();
// 注册地图geo数据 经纬度
echarts.registerMap('china', res.data[0]);
// 图表设置
myChart.setOption(
(option = {
// 标题
title: {
padding: [
15, // 上
10, // 右
5, // 下
15, // 左
],
text: '全国各地区人口普查数据(2020)',
subtext: '数据来源:统计局',
},
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{c}人'
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
// 视觉映射组件
visualMap: {
min: 1,
max: 50000000,
text: ['High', 'Low'],
realtime: false,
calculable: true,
// 颜色范围
inRange: {
color: ['#409eff', 'yellow', 'red']
}
},
series: [
{
name: '全国地区人口',
type: 'map',
map: 'china',
// 地图缩放比例
zoom: 1.4,
roam: true, //是否开启平游或缩放
scaleLimit: {
//滚轮缩放的极限控制
min: 1,
max: 10
},
label: {
show: true
},
data: this.population,
}
]
})
);
})
option && myChart.setOption(option);
}
},
}
</script>
<style lang="scss" scoped></style>
四、第三方库使用
1、数据导出
数据较少时,可以选择在客户端浏览器导出,如果数据量较多时,建议由服务端导出,生成一个文件下载地址返回给客户端浏览器直接下载文件即可。
实际数据在哪儿就在哪儿导出
安装js-export-excel导出库
npm i js-export-excel
npm install dayjs
src\views\Admin\User.vue
<template>
<div>
<!-- 1、搜索操作区 -->
<div
style="display: flex;justify-content:space-between;background-color: #fff;margin-bottom: 20px;border-radius: 10px;">
<div style="display: flex;align-items: center;">
<el-button type="primary" @click="handleAdd" style="margin-left: 10px;">添加会员</el-button>
<!--数据导出按钮 绑定导出方法 -->
<el-button type="primary" @click="handleExportCurrentExcel" style="margin-left: 10px;">导出</el-button>
</div>
<div style="width: 300px;padding: 20px;display: flex;justify-content: space-around;">
<el-button type="primary" @click="loadList" style="margin-right: 10px;">重置</el-button>
<el-input placeholder="请输入手机号" v-model="phone" clearable>
</el-input>
<el-button type="primary" @click="searchPhone" style="margin-left: 10px;">搜索</el-button>
</div>
</div>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
// 导入js-export-excel
import ExportJsonExcel from 'js-export-excel'
// 引入day.js时间处理库
import dayjs from 'dayjs'
export default {
data() {
//................
return {
list: [],
//........
},
methods: {
//........
// 导出会员数据为excel表格
handleExportCurrentExcel() {
// 表格对应的字段key 对应数据
let sheetFilter = ['username', 'sex', 'age', 'phone']
// 数据表配置项
let option = {
// 导出的excel文件名称 会员用户管理-2023-06-01-11-10-23
fileName: '会员用户管理' + dayjs().format('YYYY-MM-DD-HH-mm-ss'),
// 导出的数据匹配项
datas: [
{
sheetData: this.parseList,
sheetName: 'Sheet1',
sheetFilter: sheetFilter,
// 表头
sheetHeader: ['姓名', '性别', '年龄', '手机号'],
// 列宽度
columnWidths: [8, 8, 8, 8, 8, 8]
}
]
}
var toExcel = new ExportJsonExcel(option) //new
toExcel.saveExcel() //保存
}
},
computed: {
parseList: {
get() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.list.slice(start, end)
},
}
},
}
</script>
2、数据导入
安装xlsx excel解析库
npm i xlsx
数据导入,需要先制定一个导入的excel模板,将数据填充好,再进行上传导入
src\views\Admin\User.vue
<template>
<div>
<!-- 数据导入 -->
<el-button type="primary" style="margin-left: 10px;position: absolute;left:187px">上传导入</el-button>
<input type="file" id="file" style="margin-left: 10px;width: 100px;z-index: 99;opacity: 0;cursor: pointer;"
@change="importExcel" />
</div>
</template>
<script>
//导入xlsx
import * as XLSX from 'xlsx'
export default {
methods: {
// excel导入
importExcel() {
// 获取到上传的excel表 文件对应的DOM对象
const file = document.getElementById('file')
// console.log([file]);
// console.log(file.files[0]);
const reader = new FileReader()
reader.readAsBinaryString(file.files[0]) // 转成 二进制格式
reader.onload = () => {
const workbook = XLSX.read(reader.result, { type: 'binary' })
// console.log(workbook);
const t = workbook.Sheets['Sheet1'] // 拿到表格数据
// console.log(t)
const r = XLSX.utils.sheet_to_json(t) // 转换成json格式
// console.log(r)
const result = r.map(item => ({ name: item['姓名'], age: item['年龄'], sex: item['性别'], phone: item['手机号'] }))
console.log(result);
}
}
},
}
</script>
<style lang="scss" scoped>
</style>
3、富文本编辑器
CKEditor 5是一个超现代的JavaScript富文本编辑器
https://ckeditor.com/docs/ckeditor5/latest/installation/integrations/vuejs-v2.html#quick-start
安装
npm install --save @ckeditor/ckeditor5-vue2 @ckeditor/ckeditor5-build-classic
main.js
引入注册
import Vue from 'vue';
import CKEditor from '@ckeditor/ckeditor5-vue2';
Vue.use( CKEditor );
src\views\Admin\Notice.vue
<template>
<div>
<!-- 调用显示富文本编辑器 -->
<ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
<div v-html="editorData"></div>
</div>
</template>
<script>
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
export default {
data() {
return {
editor: ClassicEditor,
// 用户输入的内容信息
editorData: '',
// 编辑器配置
editorConfig: {
// The configuration of the editor.
}
};
}
}
</script>
TinyMce
TinyMCE 是一个轻量级的,基于浏览器的,所见即所得编辑器,支持目前流行的各种浏览器,由 JavaScript 写成。功能配置灵活简单(两行代码就可以将编辑器嵌入网页中),支持 AJAX。另一特点是加载速度非常快,如果你的服务器采用的脚本语言是 PHP,那还可以进一步优化。最重要的是,TinyMCE 是一个根据 LGPL license 发布的自由软件,你可以把它用于商业应用。
https://www.tiny.cloud/docs/tinymce/6/vue-cloud/
安装
npm install --save "@tinymce/tinymce-vue@^3"
notice.vue
<template>
<main id="sample">
<Editor api-key="no-api-key" :init="{
plugins: 'lists link image table code help wordcount'
}" />
</main>
</template>
<script>
import Editor from '@tinymce/tinymce-vue'
export default {
components: {
Editor
}
}
</script>
<style lang="scss" scoped>
.tox.tox-tinymce {
width: 80% !important;
}
@media (min-width: 1024px) {
#sample {
display: flex;
flex-direction: column;
place-items: center;
width: 100vw;
}
}
</style>
wangEditor 5
开源 Web 富文本编辑器,开箱即用,配置简单
快速接入,配置简单,几行代码即可生成。集成了所有常见功能,无需二次开发。在 Vue React 也可以快速接入。
不依赖任何第三方框架,可用于 jQuery Vue React 等。wangEditor 提供了官方的 Vue React 组件。
安装
# 安装编辑器
npm install @wangeditor/editor --save
# 安装编辑器vue的组件
npm install @wangeditor/editor-for-vue --save
使用
①创建组件
src\components\MyEditor.vue
<template>
<div>
预览内容:
<div v-html="html"></div>
<div style="border: 1px solid #ccc;">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
<Editor style="height: 500px; overflow-y: hidden;" v-model="html" :defaultConfig="editorConfig" :mode="mode"
@onCreated="onCreated" />
</div>
</div>
</template>
<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default Vue.extend({
components: { Editor, Toolbar },
data() {
return {
editor: null,
html: '<p>hello</p>',
toolbarConfig: {},
editorConfig: { placeholder: '请输入内容...' },
mode: 'default', // or 'simple'
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
},
},
mounted() {
// 模拟 ajax 请求,异步渲染编辑器
// setTimeout(() => {
// this.html = '<p>模拟 Ajax 异步设置内容 HTML</p>'
// }, 1500)
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() // 组件销毁时,及时销毁编辑器
}
})
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<style lang="scss" scoped></style>
②在需要的地方进行引入使用
src\views\Admin\Notice.vue
<template>
<div>
<MyEditor></MyEditor>
</div>
</template>
<script>
import MyEditor from '@/components/MyEditor.vue';
export default {
components:{
MyEditor
}
}
</script>
<style lang="scss" scoped>
</style>
4、markdown编辑器
v-md-editor 可以在线编辑markdown语法,并实现预览效果
http://ckang1229.gitee.io/vue-markdown-editor/zh/
安装
npm i @kangc/v-md-editor -S
main.js
import Vue from 'vue';
import VueMarkdownEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js';
import '@kangc/v-md-editor/lib/theme/style/vuepress.css'
VueMarkdownEditor.use(vuepressTheme);
Vue.use(VueMarkdownEditor);
src\views\Admin\Notice.vue
<template>
<v-md-editor v-model="text" height="400px"></v-md-editor>
</template>
<script>
export default {
data() {
return {
text: '',
};
},
};
</script>
<style lang="scss" scoped></style>
五、扩展补充
1、商品管理
运营系统和管理系统中,对于一些业务管理,实际就是对应所存储的数据进行判断查询,数据添加,修改维护,删除等操作。数据筛选,搜索搜索,排大小。
商品管理中需要对应商品的图片进行管理,所以需要进行图片上传操作。其他管理和会员用户管理基本类似。
“管理功能实现步骤:
①创建路由及其组件 配置菜单
②获取商品数据展示数据列表 实现对应的查询等工作
③添加商品信息
④编辑修改商品信息
⑤删除商品信息
①商品管理列表分页展示
src\views\Admin\Goods.vue
<template>
<div>
<div></div>
<div style="background-color: white;padding: 20px;border-radius: 10px;">
<!-- 数据表格 -->
<el-table :data="parseGoodsList" style="width: 100%">
<el-table-column prop="id" label="序号" align="center">
</el-table-column>
<el-table-column prop="title" label="商品名称" align="center">
</el-table-column>
<el-table-column prop="img" label="商品图片" align="center">
<template slot-scope="scope">
<el-image style="width: 40px; height: 40px" :src="scope.row.img" :preview-src-list="[scope.row.img]"
lazy>
</el-image>
</template>
</el-table-column>
<el-table-column prop="subtitle" label="二级标题" align="center">
</el-table-column>
<el-table-column prop="price" label="商品价格" align="center">
</el-table-column>
<el-table-column prop="desc" label="商品描述" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-info" icon-color="red"
title="确定删除吗?" @confirm="handleDelete(scope.$index, scope.row)">
<!-- 注意以下按钮需要使用slot插槽 不使用插槽就不显示了 -->
<el-button type="danger" size="small" slot="reference" style="margin-left: 10px;">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px;">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[1, 2, 4, 6, 8]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="goodsList.length">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import req from '@/utils/request'
import url from '@/config/url'
export default {
data() {
return {
goodsList: [],
// 当前页码
currentPage: 1,
// 每页的条数
pageSize: 1
}
},
created() {
this.loadData()
},
methods: {
// 加载商品列表数据
loadData() {
req.get(url.Goods).then(res => {
console.log(res);
this.goodsList = res.data
})
},
// 切换当前页
handleCurrentChange(value) {
this.currentPage = value
},
// 每页显示几条切换
handleSizeChange(value) {
this.pageSize = value
}
},
computed: {
// 分页显示数据
parseGoodsList() {
let start = (this.currentPage - 1) * this.pageSize
let end = this.currentPage * this.pageSize
return this.goodsList.slice(start, end)
}
},
}
</script>
<style lang="scss" scoped></style>
②添加商品信息
<template>
<div>
<el-button type="primary" @click="add">添加商品</el-button>
<!-- 添加弹出框 -->
<el-dialog title="添加商品" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="商品名称" :label-width="formLabelWidth">
<el-input v-model="form.title" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品预览图" :label-width="formLabelWidth">
<el-input v-model="form.img" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="二级标题" :label-width="formLabelWidth">
<el-input v-model="form.subtitle" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品价格" :label-width="formLabelWidth">
<el-input v-model="form.price" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品描述" :label-width="formLabelWidth">
<el-input v-model="form.desc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// 表单是否弹出
dialogFormVisible: false,
form: {
title: '',
img: '',
subtitle: '',
price: '',
desc: '',
},
formLabelWidth: '120px'
}
},
methods: {
// 添加打开表单
add() {
// 弹出表单
this.dialogFormVisible = true
},
// 取消按钮
handleCancel() {
this.dialogFormVisible = false
this.$message({
message: '已取消',
duration: 700
})
},
// 保存数据
save() {
// console.log(this.form);
req.post(url.Goods, this.form).then(res => {
if (res) {
this.$message({
type: 'success',
message: '添加商品成功',
duration: 1000,
onClose: () => {
this.form = {
title: '',
img: '',
subtitle: '',
price: '',
desc: '',
}
this.dialogFormVisible = false
this.loadData()
}
})
}
})
}
},
}
</script>
<style lang="scss" scoped>
</style>
③编辑修改商品信息
<template>
<div>
<el-button type="primary" @click="add">添加商品</el-button>
<!-- 数据表格 -->
<el-table :data="parseGoodsList" style="width: 100%">
<el-table-column prop="id" label="序号" align="center">
</el-table-column>
<el-table-column prop="title" label="商品名称" align="center">
</el-table-column>
<el-table-column prop="img" label="商品图片" align="center">
<template slot-scope="scope">
<el-image style="width: 40px; height: 40px" :src="scope.row.img" :preview-src-list="[scope.row.img]"
lazy>
</el-image>
</template>
</el-table-column>
<el-table-column prop="subtitle" label="二级标题" align="center">
</el-table-column>
<el-table-column prop="price" label="商品价格" align="center">
</el-table-column>
<el-table-column prop="desc" label="商品描述" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-info" icon-color="red"
title="确定删除吗?" @confirm="handleDelete(scope.$index, scope.row)">
<!-- 注意以下按钮需要使用slot插槽 不使用插槽就不显示了 -->
<el-button type="danger" size="small" slot="reference" style="margin-left: 10px;">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 添加弹出框 -->
<el-dialog :title="formStatus ? '添加商品' : '编辑商品'" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="商品名称" :label-width="formLabelWidth">
<el-input v-model="form.title" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品预览图" :label-width="formLabelWidth">
<el-input v-model="form.img" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="二级标题" :label-width="formLabelWidth">
<el-input v-model="form.subtitle" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品价格" :label-width="formLabelWidth">
<el-input v-model="form.price" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品描述" :label-width="formLabelWidth">
<el-input v-model="form.desc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// 表单是否弹出
dialogFormVisible: false,
form: {
title: '',
img: '',
subtitle: '',
price: '',
desc: '',
},
formLabelWidth: '120px',
// 表单状态 true 添加表单 false 编辑表单
formStatus: true,
// 当前编辑的商品id,
id: 0
}
},
methods: {
// 添加打开表单
add() {
// 弹出表单
this.dialogFormVisible = true
},
// 取消按钮
handleCancel() {
this.dialogFormVisible = false
this.$message({
message: '已取消',
duration: 700
})
},
// 保存数据
save() {
// 判断是修改还是添加 formStatus
if (this.formStatus) {
// 添加
req.post(url.Goods, this.form).then(res => {
if (res) {
this.$message({
type: 'success',
message: '添加商品成功',
duration: 1000,
onClose: () => {
this.form = {
title: '',
img: '',
subtitle: '',
price: '',
desc: '',
}
this.dialogFormVisible = false
this.loadData()
}
})
}
})
} else {
// 修改
// this.id 点击编辑时 将当前操作数据的id 作为全局数据
req.put(url.Goods + '/' + this.id, this.form).then(res => {
if (res) {
this.$message({
type: 'success',
message: '修改商品成功',
duration: 1000,
onClose: () => {
this.form = {
title: '',
img: '',
subtitle: '',
price: '',
desc: '',
}
this.dialogFormVisible = false
this.loadData()
}
})
}
})
}
// console.log(this.form);
},
// 编辑处理
handleEdit(index, row) {
// 弹出表单
this.dialogFormVisible = true
// 修改表单状态为编辑
this.formStatus = false
// 将当前编辑的数据内容赋值表单项
this.form = row
// 设置当前修改的id
this.id = row.id
}
},
}
</script>
<style lang="scss" scoped>
</style>
④删除商品信息
<template>
<div>
<!-- 数据表格 -->
<el-table :data="parseGoodsList" style="width: 100%">
<el-table-column prop="id" label="序号" align="center">
</el-table-column>
<el-table-column prop="title" label="商品名称" align="center">
</el-table-column>
<el-table-column prop="img" label="商品图片" align="center">
<template slot-scope="scope">
<el-image style="width: 40px; height: 40px" :src="scope.row.img" :preview-src-list="[scope.row.img]"
lazy>
</el-image>
</template>
</el-table-column>
<el-table-column prop="subtitle" label="二级标题" align="center">
</el-table-column>
<el-table-column prop="price" label="商品价格" align="center">
</el-table-column>
<el-table-column prop="desc" label="商品描述" align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<!-- 作用域插槽 子传父数据 -->
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-info" icon-color="red"
title="确定删除吗?" @confirm="handleDelete(scope.$index, scope.row)">
<!-- 注意以下按钮需要使用slot插槽 不使用插槽就不显示了 -->
<el-button type="danger" size="small" slot="reference" style="margin-left: 10px;">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
methods: {
// 删除商品
handleDelete(index, row) {
req.delete(url.Goods + '/' + row.id).then(res => {
this.$message({
message: '删除商品成功',
duration: 1000,
type: 'success',
onClose: () => {
this.loadData()
}
})
})
}
},
}
</script>
<style lang="scss" scoped>
</style>
2、上传图片实现
①确认服务端接口可以通过调试工具正常上传文件
②使用代码编辑上传逻辑
<template>
<div>
<!-- 图片上传开始 -->
<!-- action 上传地址 -->
<!-- header 请求头 添加token -->
<!-- name 服务端接口上传文件的名称 -->
<el-upload action="http://localhost:5000/api/v1/upload" :headers="{Authorization: token}" name="filename" list-type="picture-card" :on-preview="handlePictureCardPreview"
:on-remove="handleRemove" :on-success="handleSuccess"
style="margin-left: 50px;margin-bottom: 10px;">
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
<!-- 图片上传结束 -->
</div>
</template>
<script>
export default {
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
// token
token: localStorage.getItem('token') ?? ''
}
},
}
</script>
<style lang="scss" scoped>
methods: {
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
// 上传成功时触发
handleSuccess(response, file, fileList) {
// console.log(response,file,fileList);
// 将上传成功返回的文件地址赋值给表单项
this.form.img = response.data.filename
}
},
</style>
3、权限判断
不同用户登录系统,应该具有不同的页面或者按钮权限。有的功能可以使用,有的功能没有权限使用。
当用户登录后,将用户具有的权限进行返回。
用户根据权限,觉得是否可以操作到某个功能。
不给用户显示不具有权限功能对应菜单项,就需要根据用户的权限来显示菜单项
需要在src\views\Admin\Admin.vue
中引入使用的菜单组件
<Menu></Menu>
export default {
components:{
Menu
},
}
src\views\Admin\components\Menu.vue
<template>
<!-- 菜单 -->
<!-- default-active 根据路由路径匹配 选中的对应的菜单高亮 -->
<el-menu router :default-active="$route.path" background-color="#001529" text-color="#ccc">
<!-- index 开启router路由模式 会作为路由跳转的路径 -->
<el-menu-item index="/admin/dashboard">
<!-- icon图标 菜单左侧 -->
<i class="el-icon-data-line"></i>
<span slot="title">控制台</span>
</el-menu-item>
<!-- 根据不同的菜单列表 显示不同的组件标签 -->
<el-menu-item v-for="item in parseMenuList" :index="item.path">
<i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</el-menu>
</template>
<script>
export default {
data() {
return {
menuList: [
{
path: '/admin/user',
icon: 'el-icon-user',
title: '用户管理'
},
{
path: '/admin/goods',
icon: 'el-icon-goods',
title: '商品管理'
},
{
path: '/admin/notice',
icon: 'el-icon-bell',
title: '公告管理'
}
]
}
},
computed: {
parseMenuList() {
// 如果管理员用户名为admin时,具有所有权限
let username = localStorage.getItem('username') ?? ''
console.log(username);
if (username !== 'admin') {
// console.log(1111);
// 当管理员用户身份不是admin时,根据实际的acl权限来进行显示菜单
// 当前登录用户所具有的权限
let acl = JSON.parse(localStorage.getItem('acl'))
let tmp = []
this.menuList.forEach((item) => {
console.log(item);
// 判断菜单项中的每一个路径是否是用户允许访问的路径
if (acl.includes(item.path)) {
tmp.push(item)
}
})
// console.log(tmp);
return tmp
} else {
return this.menuList
}
}
},
}
</script>
<style lang="scss" scoped></style>
以上操作虽然可以让用户根据菜单来访问功能,但是如果直接访问路由地址,没有进行限制情况下,还是会出现越权访问。可以通过以下两种方式,来进行路由的拦截。
方法一:路由守卫拦截 根据权限判断
src\router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import { Message } from 'element-ui';
import url from '@/config/url';
import req from '@/utils/request'
import Register from '../views/Register.vue'
import Login from '../views/Login.vue'
import Admin from '../views/Admin/Admin.vue'
import Dashboard from '../views/Admin/Dashboard/Dashboard.vue'
// import User from '../views/Admin/User.vue'
import Notice from '../views/Admin/Notice.vue'
import NotFound from '../views/NotFound.vue'
import Goods from '../views/Admin/Goods.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/register',
name: 'register',
component: Register,
meta: {
isAuth: false
}
},
{
path: '/login',
name: 'login',
component: Login,
// 路由元信息 路由传参
meta: {
isAuth: false
}
},
{
path: '/admin',
name: 'admin',
redirect: '/admin/dashboard',
// component: Admin,
component: () => import('@/views/Admin/Admin.vue'),
children: [
{
// 嵌套路由中 path 不需要写/
path: 'dashboard',
name: 'dashboard',
// component: Dashboard,
component: () => import('@/views/Admin/Dashboard/Dashboard.vue')
},
{
path: 'user',
name: 'user',
// component: User
component: () => import('@/views/Admin/User.vue')
},
{
path: 'goods',
name: 'goods',
// component:Goods
component: () => import('@/views/Admin/Goods.vue')
},
{
path: 'notice',
name: 'notice',
// component:Notice
component: () => import('@/views/Admin/Notice.vue')
}
],
// 路由 独享守卫
beforeEnter: (to, from, next) => {
//================ 根据用户权限判断是否可以跳转开始===============
// console.log(to);
// 如果访问的是控制台页面 直接放行
if (to.path === '/admin/dashboard') {
next()
} else {
// 在路由守卫中 判断用户跳转的路由是否具有 访问权限 如果没有,就拦截下来
let acl = JSON.parse(localStorage.getItem('acl')) ?? []
let username = localStorage.getItem('username') ?? ''
if (username !== 'admin' && acl.length > 0 && acl.includes(to.path)) {
// 具有权限列表并且将要跳转的路由path是在权限列表中的 具有权限
next()
} else {
// 没有访问权限 跳转到没有权限页面 创建没有权限的页面 配置路由
next('/notpermission')
}
}
//================ 根据用户权限判断是否可以跳转结束===============
// 如果本地存储未报错token 肯定没有登录
if (!localStorage.getItem('token')) {
Message({
message: '未登录,请先登录',
type: 'error',
duration: 1000,
onClose: () => {
// 跳转到登录界面
next('/login')
}
})
} else {
// 校验token的有效性
req.get(url.Profile).then(res => {
// console.log(res);
if (res.data.code === 0) {
// 存储管理员登录信息
localStorage.setItem('username', res.data.data.username)
// 存储用户具有的访问权限
localStorage.setItem('acl', JSON.stringify(res.data.data.acl))
next()
} else {
Message({
message: '登录失效,重新登录',
type: 'error',
duration: 1000,
onClose: () => {
// 跳转到登录界面
next('/login')
}
})
}
})
}
},
},
// 访问没有权限 跳转的页面
{
path: '/notpermission',
component: ()=>import('@/views/NotPermission.vue')
},
// 404 页面匹配到跳转的页面
{
path: '*',
component: NotFound
}
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (about.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
// }
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// // 全局前置守卫
// router.beforeEach((to, from, next) => {
// console.log(to, from);
// // 根据路由元信息 判断哪些路由是需要校验登录
// if (to.meta.isAuth === false) {
// next()
// } else {
// // 如果本地存储未报错token 肯定没有登录
// if (!localStorage.getItem('token')) {
// Message({
// message: '未登录,请先登录',
// type: 'error',
// duration: 1000,
// onClose: () => {
// // 跳转到登录界面
// next('/login')
// }
// })
// } else {
// // 校验token的有效性
// req.get(url.Profile).then(res => {
// console.log(res);
// if (res.data.code === 0) {
// // 存储管理员登录信息
// localStorage.setItem('username', res.data.data.username)
// } else {
// Message({
// message: '登录失效,重新登录',
// type: 'error',
// duration: 1000,
// onClose: () => {
// // 跳转到登录界面
// next('/login')
// }
// })
// }
// })
// next()
// }
// }
// })
export default router
方法二:addRouter动态添加用户的路由
4、权限管理
①配置路由和生成页面组件
②在页面组件中显示一个管理员列表,并设置权限按钮
③点击权限按钮,弹出设置权限的选择项
④选择并提交到服务端接口
src\views\Admin\Permission.vue
<template>
<div>
<!-- 管理员列表表格 -->
<el-table :data="adminList" style="width: 100%">
<el-table-column prop="_id" label="id" align="center">
</el-table-column>
<el-table-column prop="username" label="管理员名称" align="center">
</el-table-column>
<el-table-column fixed="right" label="操作" align="center">
<template slot-scope="scope">
<el-button type="success" size="small" @click="setPermission(scope.$index, scope.row)">权限</el-button>
</template>
</el-table-column>
</el-table>
<!-- 设置权限的弹出框 -->
<el-dialog title="权限设置" :visible.sync="dialogVisible" width="50%">
<!-- 设置管理员权限的穿梭框 -->
<!-- el-transfer v-model绑定数组对应的下标代表选择已哪些权限 -->
<!-- data 所有权限 -->
<el-transfer v-model="value" :data="permissionList" style="margin-left: 40px;"
:titles="['全部权限', '已有权限']"></el-transfer>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
5、共享数据存储到vuex中
将管理员用户登录的用户数据进行存储,username,acl权限列表
src\store\index.js
/***
* 组件状态共享工具
* 将一些在多个组件中都使用的到数据 进行统一存储 统一的修改方式
*
*
*/
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: JSON.parse(localStorage.getItem('userInfo')) ?? {
username: '',
acl: []
}
},
getters: {
},
mutations: {
// 设置用户权限 存储用户名和acl权限列表
saveAuth(state, payload) {
state.userInfo = payload
// vuex数据默认存储在变量中 一刷新数据就不存在了,需要存储到浏览器本地实现永久化存储
localStorage.setItem('userInfo', JSON.stringify(state.userInfo))
}
},
actions: {
},
modules: {
}
})
src\views\Login.vue
将数据存储到store,写vuex的数据
获取store中的数据,读vuex数据
管理员名称数据
src\views\Admin\Admin.vue
src\views\Admin\components\Menu.vue
6、打包上线
构建打包
npm run build
打包后可以使用http-server预览打包后的文件是否可以正常访问
npm i -g http-server
cd dist
http-server
上线
将打包好的dist文件夹,上传到服务器对应访问目录即可。一般由管理服务器的人员去操作。
“注意:路由刷新404的问题
如果路由使用的history历史路由模式,历史路由的path路径会被认为是真实存在的服务器资源地址,实际是不存在的,就会导致服务端无法对应资源,找不到(404).
解决方案思路:
第一种方案:如果服务端无法找到对应路径资源时,直接访问index.html即可。把路由切换的权限又回到了前端页面,从而可以找到历史路由路径
第二种方案:使用hash路由