1.布局:左边侧边栏,右边分顶部和内容区域
使用element-ui布局
左边侧边栏固定在左侧的样式:
.el-menu {
height: 100%;
border: none;
// 侧边栏固定在左侧
position: fixed;
top: 0;
bottom: 0;
}
右侧内容防止隐藏掉,需要设置样式:
.right-container {
margin-left: 200px;
}
2.动态引入头像图片:
<span>
<img :src="userImg" class="user" />
</span>
data() {
return {
userImg: require('../assets/image/user.webp')
};
}
3.采用vuex方式存储汉堡包折叠左侧的导航栏。
在store下新建文件tab.js:
export default {
namespaced: true,
state: {
// 默认展开,汉堡包折叠
isCollapse: false
},
mutations: {
collapseMenu (state) {
state.isCollapse = !state.isCollapse;
}
}
}
在store下的index.js中添加模块:
import Tab from '@/store/tab.js'
modules: {
Tab
}
在侧边导航栏组件中(展开的样式):
:collapse="isCollapse"
computed: {
// 汉堡包折叠
isCollapse() {
return this.$store.state.Tab.isCollapse;
}
}
<!-- 判断侧边导航栏是否是折叠的效果 -->
<h3>{{ isCollapse ? '后台' : '通用后台管理系统' }}</h3>
给头部按钮添加点击事件@click="handleMenu",实现折叠效果:
methods: {
//点击汉堡包图标折叠侧边导航栏
handleMenu() {
this.$store.commit('Tab/collapseMenu');
}
}
在main.js布局页面中给右侧头部和内容区域设置动态样式:
:class="{ isActive: isCollapse }
获取到vuex中的isCollapse:
computed: {
// 汉堡包折叠
isCollapse() {
return this.$store.state.Tab.isCollapse;
}
}
实现左侧侧边栏汉堡包折叠切换后的右侧头部和内容部分的扩展:
.isActive {
margin-left: 64px;
}
4.axios的post方法传JSON参数(参数是一个对象):
getAddGoods (params) {
return axios.post(base.baseUrl + base.addGoods, params)
}
5.分页组件高亮页码:
:current-page.sync="currentPage"
currentPage: {
type: Number,
default: 1
}
使用分页组件的高亮页码:
:currentPage="currentPage"
设置数据:
currentPage: 1, //选中的高亮页码
this.currentPage = 1;
6.设置导航的高亮色:
:default-active="$route.path"
每个导航中的index设置成跳转的url:
index="/"
7.语言国际化:
使用vuei18n:
在src下新建文件夹lang下新建文件index.js:
//vuei18n国际化
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// element-ui国际化
// import Element from 'element-ui'
// import enLocale from 'element-ui/lib/locale/lang/en'
// import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
Vue.use(VueI18n)
// 1.准备翻译的语言环境信息
const messages = {
// 英文
en: {
goods: {
home: 'home',
goods:'Goods Manage',
params:'Specification'
},
// 导入element-ui的国际化语法
// ...enLocale
},
// 中文
zh: {
goods: {
home: '首页',
goods:'商品管理',
params:'规格参数'
},
// 导入element-ui的国际化语法
// ...zhLocale
}
}
// 2.通过选项创建 VueI18n 实例
const i18n = new VueI18n({
locale: 'zh', // 设置地区
messages, // 语言环境
})
//兼容写法
// Vue.use(Element, {
// i18n: (key, value) => i18n.t(key, value)
// })
// 3.导出i18n
export default i18n;
全局使用,在main.js中:
//导入语言
import i18n from '@/lang/index.js'
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app')
使用vuei18n:
<!-- command 点击菜单项触发的事件回调 dropdown-item 的指令 -->
<el-dropdown @command="clickLang">
<span class="el-dropdown-link" style="color: #fff">
多语言<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<!-- command 指令 -->
<el-dropdown-item command="zh">中文</el-dropdown-item>
<el-dropdown-item command="en">English</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
clickLang(command) {
console.log('---触发语言切换---', command);
// 挂载在main.js上的可以用this.$i18n来调用
this.$i18n.locale = command;
}
需要使用多语言的文字,改成以下形式:
{{ $t('goods.home') }}
8.登录
给父路由配置路由元:
//配置路由元信息
meta:{
isLogin:true
}
import store from '@/store/index.js'
// 路由拦截
router.beforeEach((to,from,next) => {
console.log('---to---',to);
// 1.判断是否需要登录
if(to.matched.some(ele => ele.meta.isLogin)){
//2.判断当前的用户是否已经登录
let user = store.state.LoginModules.userInfo.token;
if(user){
next();
}else{
next('/login');
}
}else {
//不需要登陆
next();
}
将登录信息存储在vuex中:
export default {
namespaced:true,
state:{
userInfo:{
username:'',
token:null
}
},
mutations: {
setUser(state,payload){
state.userInfo = payload;
},
clearUser(state){
state.userInfo={
username:'',
token:null
}
}
}
}
登录界面使用登录接口:
//登录成功
// this.info = '';
//1.登录成功后存储信息 2.跳转网页 3.顶部区域显示用户信息 4.数据持久化
let obj = {
username:res.data.data.username,
token:res.data.data.password
}
this.$store.commit('LoginModules/setUser',obj);
//数据持久化
localStorage.setItem('userInfo',JSON.stringify(obj));
//跳转首页
this.$router.push('/').catch(()=>{
alert('请先登录!');
});
退出登录:
import { mapState, mapMutations } from 'vuex';
computed: {
...mapState('LoginModules', ['userInfo'])
},
methods: {
...mapMutations('LoginModules', ['clearUser'])
}
// 退出按钮
exit() {
//清空vuex中的数据
this.clearUser();
//清空本地存储
localStorage.removeItem('userInfo');
//判断路径
if (this.$router.push !== '/') {
this.$router.push('/login');
}
}
9.添加
父组件:
弹框按钮:
<el-button type="primary" @click="addGoods">弹框添加</el-button>
添加弹框组件:
<GoodsDialog ref="dialog" :title="title" :rowData="rowData" />
//引入添加弹框
import GoodsDialog from '@/views/Goods/GoodsDialog.vue';
components: {
GoodsDialog
},
data() {
return {
dialogVisible: false, //弹框显示状态
title: '添加商品',
rowData: {} //当前行的数据对象
};
},
methods: {
// 添加商品 --- 出现弹框
addGoods() {
// this.dialogVisible = true; //出现弹框
// 修改子组件变量实例
this.$refs.dialog.dialogVisible = true;
this.title = '添加商品';
}
}
弹框组件:
(添加和编辑共用一个组件)
<template>
<div>
<!--title="添加商品" :弹框标题
:visible.sync="dialogVisible":控制显示弹框 true为显示
width="70%" 宽度 大小
-->
<el-dialog
:title="title"
:visible.sync="dialogVisible"
width="70%"
:before-close="clearForm"
>
<!-- 内容区域 -->
<el-form
:model="goodsForm"
:rules="rules"
ref="rulesForm"
label-width="100px"
class="demo-goodsForm"
>
<el-form-item label="类目选择" prop="category">
<el-button type="primary" @click="innerVisible = true"
>类目选择</el-button
>
<span>{{ goodsForm.category }}</span>
</el-form-item>
<el-form-item label="商品名称" prop="title">
<el-input v-model="goodsForm.title"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="price">
<el-input v-model="goodsForm.price"></el-input>
</el-form-item>
<el-form-item label="商品数量" prop="num">
<el-input v-model="goodsForm.num"></el-input>
</el-form-item>
<el-form-item label="发布时间">
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="goodsForm.date1"
style="width: 100%"
></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
placeholder="选择时间"
v-model="goodsForm.date2"
style="width: 100%"
></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="商品卖点" prop="sellPoint">
<el-input v-model="goodsForm.sellPoint"></el-input>
</el-form-item>
<el-form-item label="商品图片" prop="image">
<el-button type="primary" @click="innerVisibleImg = true"
>上传图片</el-button
>
</el-form-item>
<el-form-item label="商品描述" prop="descs">
<WangEditor @sendEditor="sendEditor" ref="wang" />
</el-form-item>
</el-form>
<!-- 弹框底部区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="clearForm">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</span>
<!-- 1.内弹框--- 类目选择 -->
<el-dialog
width="30%"
title="类目选择"
:visible.sync="innerVisible"
append-to-body
>
<TreeGoods @sendTreeDate="sendTreeDate" />
<span slot="footer" class="dialog-footer">
<el-button @click="innerVisible = false">取 消</el-button>
<el-button type="primary" @click="showTreeData">确 定</el-button>
</span>
</el-dialog>
<!-- 2.内弹框---上传图片 -->
<el-dialog
width="40%"
title="上传图片"
:visible.sync="innerVisibleImg"
append-to-body
>
<UploadImg />
<span slot="footer" class="dialog-footer">
<el-button @click="innerVisibleImg = false">取 消</el-button>
<el-button type="primary" @click="innerVisibleImg = false"
>确 定</el-button
>
</span>
</el-dialog>
<!-- -->
</el-dialog>
</div>
</template>
<script>
import TreeGoods from '@/views/Goods/TreeGoods.vue';
import UploadImg from '@/views/Goods/UploadImg.vue';
import WangEditor from '@/views/Goods/WangEditor.vue';
export default {
// props: ['dialogVisible'],
props: {
title: {
type: String,
default: '添加商品'
},
rowData: {
type: Object,
default: function () {
return {};
}
}
},
components: {
TreeGoods,
UploadImg,
WangEditor
},
data() {
return {
dialogVisible: false, //外弹框---添加按钮显示状态
innerVisible: false, //类目名称 --内置弹框
innerVisibleImg: false, //上传图片---内置弹框
treeData: {}, //接受tree数据
goodsForm: {
//表单容器--对象
id: '', //商品ID
title: '', //商品名称
price: '',
num: '',
sellPoint: '',
image: '',
descs: '',
category: '', //商品类目
date1: '', //商品时间
date2: '',
cid: '' //商品类目id
},
rules: {
//校验规则
title: [
{ required: true, message: '请输入商品名称', trigger: 'blur' },
{ min: 2, max: 8, message: '长度在 2到 8 个字符', trigger: 'blur' }
],
price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
num: [{ required: true, message: '请输入数量', trigger: 'blur' }]
}
};
},
// 监听器
watch: {
rowData(val) {
console.log('监听到的数据变化', val);
this.goodsForm = val;
// 设置富文本编译的数据内容
this.$nextTick(() => {
this.$refs.wang.editor.txt.html(val.descs);
});
}
},
methods: {
// 操作富文本编译器,获取子组件传来的文本内容
sendEditor(val) {
this.goodsForm.descs = val;
},
// 显示tree的数据
showTreeData() {
// 内置类目名称弹框隐藏
this.innerVisible = false;
// 显示tree数据
this.goodsForm.category = this.treeData.category;
this.goodsForm.cid = this.treeData.cid;
},
//接受子组件tree的数据
sendTreeDate(val) {
console.log('---tree数据---', val);
this.treeData = val;
},
// 自定义事件--通知父亲--修改dialogVisible变量
close() {
this.$emit('changeDialog');
},
//确定按钮提交功能
submitForm() {
this.$refs.rulesForm.validate((valid) => {
if (valid) {
console.log('获取输入的信息', this.goodsForm);
let {
id,
title,
cid,
category,
sellPoint,
price,
num,
descs,
paramsInfo,
image
} = this.goodsForm;
//判断当前确定按钮
if (this.title === '添加商品') {
console.log('添加商品');
this.$api
.getAddGoods({
title,
cid,
category,
sellPoint,
price,
num,
descs,
paramsInfo,
image
})
.then((res) => {
console.log('---添加实现---', res.data);
if (res.data.success) {
//成功
this.$parent.List(1); //更新父组件列表数据
this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
this.clearForm();
} else {
this.$message.error('错了哦,这是一条错误消息');
}
});
} else {
console.log('编辑商品');
this.$api
.modifyGoods({
id,
title,
cid,
category,
sellPoint,
price,
num,
descs,
paramsInfo,
image
})
.then((res) => {
console.log('---编辑成功---', res.data);
if (res.data.success) {
//成功
this.$parent.List(1); //更新父组件列表数据
this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
this.clearForm();
} else {
this.$message.error('错了哦,这是一条错误消息');
}
});
}
} else {
console.log('error submit!!');
return false;
}
});
},
// 清空表单数据
clearForm() {
this.dialogVisible = false; //关闭弹框
//清空内容
// this.$refs.rulesForm.resetFields();
this.goodsForm = {
//表单容器--对象
id: '',
title: '', //商品名称
price: '',
num: '',
sellPoint: '',
image: '',
descs: '',
category: '', //商品类目
date1: '', //商品时间
date2: '',
cid: '' //商品类目id
};
//清空编译器中的内容
this.$refs.wang.editor.txt.clear();
}
}
};
</script>
<style lang="less" scoped>
.line {
text-align: center;
}
</style>
10.删除
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除</el-button
>
// 删除操作
handleDelete(index, row) {
console.log('---删除按钮---', index, row);
this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 请求删除接口
this.$api
.getDeleteGoods({
id: row.id
})
.then((res) => {
console.log('---删除---', res.data);
this.$message({
type: 'success',
message: '删除成功!'
});
//视图更新
this.List(1);
// 删除后当前高亮页为第一页
this.currentPage = 1;
});
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
11.编辑
父组件:
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
// 编辑操作
handleEdit(index, row) {
//row ={}
// 1.点击编辑按钮,显示弹框 2.弹框上会显示数据展示 --当前行的数据
this.$refs.dialog.dialogVisible = true;
this.title = '编辑商品';
//每次点击都是新数据,需要监听
this.rowData = { ...row };
// this.$refs.dialog.goodsForm = row;//方法一
}
子组件与添加子组件共用一个组件
12.搜索
<!-- change 仅在输入框失去焦点或用户按下回车时触发 (value: string | number) -->
<el-input
v-model="input"
placeholder="请输入内容"
@change="searchInp"
></el-input>
data() {
return {
input: '',
tableData: [], //数据列表
total: 10, //分页总条数
pageSize: 1, //分页总页数
currentPage: 1, //选中的高亮页码
};
}
// 搜索查询功能
searchInp(val) {
console.log('---搜索功能---', val);
// 搜索数据为空,回到初始页面
if (!val) {
this.List(1);
this.currentPage = 1;
return;
}
this.$api.getSearch({ searchInfo: val }).then((res) => {
console.log(res.data);
//搜索的时候每次高亮都是第一页
this.currentPage = 1;
if (res.data.success) {
this.tableData = res.data.data;
this.pageSize = res.data.pageSize;
this.total = res.data.total;
} else {
this.tableData = [];
}
});
}
13.分页
父组件:
<!-- 分页 -->
<MyPagination
:total="total"
:pageSize="pageSize"
:currentPage="currentPage"
@changePage="changePage"
/>
// 引入分页
import MyPagination from '@/components/MyPagination.vue';
components: {
MyPagination,
GoodsDialog
},
data() {
return {
total: 10, //分页总条数
pageSize: 1, //分页总页数
currentPage: 1, //选中的高亮页码
};
}
//分页的页码
changePage(num) {
this.currentPage = num;
this.List(num);
}
子组件:
<template>
<div class="page-container">
<el-pagination
background
layout="total,prev, pager, next,jumper"
:total="total"
:page-size="pageSize"
:current-page.sync="currentPage"
@current-change="changePage"
>
</el-pagination>
</div>
</template>
<script>
export default {
props: {
total: {
type: Number,
default: 100
},
pageSize: {
type: Number,
default: 10
},
currentPage: {
type: Number,
default: 1
}
},
methods: {
//点击分页,上面的内容也发生变化,自定义事件给父组件
changePage(page) {
this.$emit('changePage', page);
}
}
};
</script>
<style lang="less" scoped>
.page-container {
text-align: center;
margin: 20px;
}
</style>
14.pdf 打印合同
安装pdf: npm i vue-pdf -S
导入pdf:
import pdf from 'vue-pdf';
使用组件:
components: {
pdf
}
<!-- 属性 src:'' 文件的路径
:page 显示的页码
:rotate 旋转90的倍数
@num-pages="pageCount=$event" 获取总页码
@page-loaded="currentPage=$event" 获取当前页码
methods: print()
-->
<!-- pdf打印会出现乱码
更改node_modules\vue-pdf\src\pdfjsWrapper.js文件,
参照:
https://github.com/FranckFreiburger/vue-pdf/pull/130/commits/253f6186ff0676abf9277786087dda8d95dd8ea7 -->
<el-button @click="print">打印合同</el-button>
<pdf
:page="num"
@num-pages="pageCount = $event"
@page-loaded="currentPage = $event"
ref="mypdf"
></pdf>
methods: {
print() {
this.$refs.mypdf.print();
}
}
15.下载发票
// 下载发票
download() {
// 获取当前图片 新窗口打开图片
let url = this.$refs.img;
console.log(url.src);
window.location.href = url.src;
},
// 下载不同源发票
downs() {},
// 下载本地发票
down() {
var alink = document.createElement('a');
alink.href = this.$refs.imgs;
console.log(this.$refs.imgs);
alink.download = 'pic'; //图片名
alink.click();
}
16.导出表格
在src下新建文件夹 excel 新建两个文件 Blob.js Export2Excel.js
网上可以查到文件内容
安装:npm i file-saver xlsx -S
npm i script-loader -D
在src下新建文件夹common 下新建文件夹 js 下新建文件 util.js
17.解决表格当前行的按钮是否禁用状态
<el-table-column label="操作" width="250">
<template slot-scope="scope">
<el-button
size="mini"
:disabled="scope.row.ship === '已发货'"
@click="handleEdit(scope.$index, scope.row)"
>编辑</el-button
>
</template>
</el-table-column>
18.表格的单行颜色
控制行颜色
:row-class-name="tableRowClassName"
添加事件方法:
// 给某一行加上不同的颜色
tableRowClassName({ row, rowIndex }) {
console.log('给某一行加颜色', row, rowIndex);
if (row.status === '拒绝处理') {
return 'warning-row';
} else if (row.status === '已处理') {
return 'success-row';
} else {
return '';
}
}
颜色样式:
/deep/.el-table .warning-row {
background: red;
}
/deep/.el-table .success-row {
background: lightgreen;
}
19.表格中显示倒计时30分钟
界面显示:
<el-table-column label="倒计时三十分钟" width="150" prop="count"> </el-table-column>
表格所需要的数据:
tableData: [
{
id: 0,
number: 'YG20220424001',
time: '2022-04-26 22:51:22',
user: '王小虎',
phone: '15527398461',
address: '河南省郑州市二七区',
money: '¥220',
status: '已支付',
ship: '已发货',
count: '',
sec: 0
}
方法:
mounted() {
const now = Date.parse(new Date());
this.a(now);
this.time();
},
methods: {
//倒计时
countdown() {
this.tableData.forEach((element) => {
let m = parseInt((element.sec / 60) % 60);
m = m < 10 ? '0' + m : m;
let s = parseInt(element.sec % 60);
s = s < 10 ? '0' + s : s;
element.count = m + '分' + s + '秒';
});
},
time() {
setInterval(() => {
this.tableData.forEach((element) => {
if (element.sec <= 0) {
return;
}
element.sec -= 1;
this.countdown();
});
}, 1000);
},
a(now) {
this.tableData.forEach((element) => {
const msec = Date.parse(new Date(element.time)) + 30 * 60 * 1000;
const a = (now - msec) / 1000;
if (a > 0) {
element.sec = 0;
if (element.status === '待支付') {
element.status = '订单失效';
}
} else {
element.sec = Math.abs(a);
}
});
}
}
本文详细介绍了如何使用Vue构建后台管理系统,包括布局设定、动态引入头像、Vuex管理导航栏、Axios的POST请求、分页高亮、导航高亮、语言国际化、登录与权限控制、增删改查操作、搜索功能、PDF打印、文件下载与导出、表格交互优化等关键功能的实现。
7714

被折叠的 条评论
为什么被折叠?



