模块技术点
左侧动态菜单栏
<template>
<el-container>
<!-- 头部区域 -->
<el-header>
<div class="logo-wrapper">
<img src="@/assets/logo2.png" alt />
<span>电商后台管理系统</span>
</div>
<!-- 退出按钮 -->
<el-button type="info" @click="logout">退出</el-button>
</el-header>
<el-container>
<!-- 左侧菜单 -->
<el-aside width="200px" id="aside">
<div class="toggle-menu" @click="changeMenu">|||</div>
<el-menu
:collapse="isCollapse"
:default-active="$router.path"
unique-opened
:collapse-transition="false"
class="el-menu-vertical-demo"
background-color="#333744"
text-color="#fff"
active-text-color="#ffd04b"
router
>
<el-submenu :index="item.id.toString()" v-for="(item, index) in menus" :key="item.id">
<template slot="title">
<i :class="icons[index]"></i>
<span>{{ item.authName }}</span>
</template>
<el-menu-item
:index="`/admin/${item.path}`"
v-for="item in item.children"
:key="item.id"
>
<i class="el-icon-menu"></i>
<span slot="title">{{ item.authName }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 右侧区域 -->
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</template>
<script>
import HomesAPI from '@/api/rights';
export default {
data() {
return {
// 菜单导航
menus: [],
// 图标
icons: [
"el-icon-s-custom",
"el-icon-s-claim",
"el-icon-s-shop",
"el-icon-s-order",
"el-icon-s-data"
],
// 折叠
isCollapse: false
};
},
mounted() {
// 获取左侧菜单导航的数据
HomesAPI.leftMensList().then(res => {
// console.log(res);
this.menus = res.data;
});
},
methods: {
// 退出登录
logout() {
// 消息提示框
this.$confirm("确定退出登录?, 是否退出?", "退出登录提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
// 黄色感叹号
type: "warning"
})
.then(() => {
window.sessionStorage.removeItem("token");
this.$message({
type: "success",
message: "退出登录成功!"
});
this.$router.push("/Login");
})
.catch(() => {
this.$message({
type: "info",
message: "你已取消退出登录了!"
});
});
},
// 点击菜单折叠
changeMenu() {
this.isCollapse = !this.isCollapse;
if (this.isCollapse == true) {
// 折叠的状态
// aside.style.width = "64px !important";
aside.className = "el-aside close";
} else {
// 展开的状态
aside.className = "el-aside open";
}
}
},
computed: {},
filters: {},
watch: {}
};
</script>
<style scoped>
.el-container {
height: 100%;
}
.el-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #373d41;
font-size: 22px;
color: #fff;
}
.logo-wrapper {
display: flex;
align-items: center;
}
.logo-wrapper span {
margin-left: 15px;
}
.el-aside {
background-color: #333744;
color: #333;
text-align: center;
line-height: 200px;
}
.toggle-menu {
width: 100%;
line-height: 24px;
background-color: #4a5064;
font-size: 12px;
text-align: center;
color: #fff;
cursor: pointer;
}
.el-menu {
border-right: 0;
}
.el-aside {
background-color: #333744;
transition: all 1s ease;
}
.el-main {
background-color: #eaedf1;
color: #333;
}
.open {
width: 200px !important;
}
.close {
width: 64px !important;
}
.el-menu-item{
padding: 0 0;
}
</style>
数据报表
<template>
<div>
<!-- 面包屑导航 -->
<bread />
<!-- 卡片内容区域 -->
<el-card>
<div id="main" style="width: 900px;height:500px;"></div>
</el-card>
</div>
</template>
<script>
import ReportsAPI from '@/api/reports.js';
import bread from "@/components/bread";
export default {
props: [],
components: { bread },
data() {
return {
echartsData: {}
};
},
mounted() {
// 获取统计的数据
this.getEcharts();
},
methods: {
// 获取统计的数据
getEcharts() {
ReportsAPI.getreports().then(res => {
// console.log(res);
this.echartsData = res.data;
// 获取数据后初始化echarts
this.initEcharts();
});
},
// 初始化echarts
initEcharts() {
// 基于准备好的dom,初始化echarts实例
var myChart = this.$echarts.init(document.getElementById("main"));
// 指定图表的配置项和数据
var option = {
title: {
text: "用户来源"
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
label: {
backgroundColor: "#6a7985"
}
}
},
legend: this.echartsData.legend,
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: this.echartsData.xAxis,
yAxis: this.echartsData.yAxis,
series: this.echartsData.series
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
},
computed: {},
filters: {},
watch: {}
};
</script>
整体项目中的难点
商品列表的添加商品
<template>
<div>
<!-- 面包屑导航 -->
<bread />
<!-- 卡片内容区域 -->
<el-card>
<!-- 消息提示 -->
<el-alert title="添加商品信息" type="info" center show-icon></el-alert>
<!-- 步骤条 -->
<el-steps :space="200" :active="activeIndex - 0" finish-status="success" align-center>
<el-step title="基本信息"></el-step>
<el-step title="商品参数"></el-step>
<el-step title="商品属性"></el-step>
<el-step title="商品图片"></el-step>
<el-step title="商品内容"></el-step>
<el-step title="完成"></el-step>
</el-steps>
<!-- 标签页 -->
<el-form
:model="addForm"
:rules="rules"
ref="addFormRef"
label-position="top"
label-width="100px"
>
<el-tabs
v-model="activeIndex"
tab-position="left"
style="height: 600px;"
:before-leave="handleLeave"
@tab-click="handleClick"
>
<el-tab-pane label="基本信息" name="0">
<el-form-item label="商品名称" prop="goods_name">
<el-input v-model="addForm.goods_name"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="goods_price">
<el-input v-model="addForm.goods_price"></el-input>
</el-form-item>
<el-form-item label="商品重量" prop="goods_weight">
<el-input v-model="addForm.goods_weight"></el-input>
</el-form-item>
<el-form-item label="商品数量" prop="goods_number">
<el-input v-model="addForm.goods_number"></el-input>
</el-form-item>
<el-form-item label="商品分类" prop="goods_cat">
<el-cascader
v-model="addForm.goods_cat"
:options="cateList"
:props="{
expandTrigger: 'hover',
value: 'cat_id',
label: 'cat_name',
children: 'children'
}"
@change="handleChange"
></el-cascader>
</el-form-item>
</el-tab-pane>
<!-- 商品参数 -->
<el-tab-pane label="商品参数" name="1">
<el-form-item v-for="item in manyTableDate" :key="item.attr_id" :label="item.attr_name">
<!-- 多选框 -->
<el-checkbox-group v-model="item.attr_vals">
<el-checkbox
v-for="(itemAttr, index) in item.attr_vals"
:key="index"
:label="itemAttr"
border
></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-tab-pane>
<!-- 商品属性 -->
<el-tab-pane label="商品属性" name="2">
<el-form-item v-for="item in onlyTableDate" :key="item.attr_id" :label="item.attr_name">
<el-input v-model="item.attr_vals"></el-input>
</el-form-item>
</el-tab-pane>
<!-- 商品图片 -->
<el-tab-pane label="商品图片" name="3">
<!-- 图片 -->
<el-upload
class="upload-demo"
action="https://www.liulongbin.top:8888/api/private/v1/upload"
:on-preview="handlePreview"
:on-remove="handleRemove"
list-type="picture"
:headers="headers"
:on-success="handleSuccess"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-tab-pane>
<el-tab-pane label="商品内容" name="4">
<quill-editor v-model="addForm.goods_introduce"></quill-editor>
<el-button class="btn" type="primary" @click="handleAdd">添加商品</el-button>
</el-tab-pane>
</el-tabs>
</el-form>
<!-- 预览图片的弹出框 -->
<el-dialog :visible.sync="imgDialogVisible" width="80%">
<img :src="previewImg" alt />
</el-dialog>
</el-card>
</div>
</template>
<script>
import GoodsAddAPI from "@/api/goods.js";
import bread from "@/components/bread";
export default {
props: [],
components: { bread },
data() {
return {
activeIndex: "0",
addForm: {
// 商品名称
goods_name: "",
// 价格
goods_price: 0,
// 重量
goods_weight: 0,
// 数量
goods_number: 0,
// 分类
goods_cat: [],
// 图片
pics: [],
// 介绍
goods_introduce: ""
},
// 验证规则
rules: {
goods_name: [
{ required: true, message: "请输入商品名称", trigger: "blur" }
],
goods_price: [
{ required: true, message: "请输入商品价格", trigger: "blur" }
],
goods_weight: [
{ required: true, message: "请输入商品重量", trigger: "blur" }
],
goods_number: [
{ required: true, message: "请输入商品数量", trigger: "blur" }
],
goods_cat: [
{ required: true, message: "请输入商品数量", trigger: "blur" }
]
},
// 商品分类数据
cateList: [],
// 动态参数
manyTableDate: [],
// 静态属性
onlyTableDate: [],
// token值
headers: {
Authorization: window.sessionStorage.getItem("token")
},
// 预览图片的路径
previewImg: "",
// 预览图片弹出框的状态
imgDialogVisible: false
};
},
mounted() {
this.getCateList();
},
methods: {
// 商品分类数据
getCateList() {
this.$axios.get("categories").then(res => {
// console.log(res);
this.cateList = res.data;
});
},
handleChange() {
if (this.addForm.goods_cat.length !== 3) {
this.addForm.goods_cat = [];
}
},
// 判断是否有分类参数
handleLeave(activeName, oldActiveName) {
if (activeName !== 0 && this.addForm.goods_cat.length !== 3) {
this.$message.error("请选择分类参数");
return false;
}
},
// 点击tabs
handleClick() {
// 动态参数
if (this.activeIndex === "1" && this.manyTableDate.length === 0) {
this.$axios
.get(`categories/${this.addForm.goods_cat[2]}/attributes`, {
params: { sel: "many" }
})
.then(res => {
// console.log(res);
res.data.forEach(item => {
item.attr_vals = item.attr_vals.split(",");
});
this.manyTableDate = res.data;
// console.log(this.manyTableDate);
});
}
// 静态属性
if (this.activeIndex === "2" && this.onlyTableDate.length === 0) {
this.$axios
.get(`categories/${this.addForm.goods_cat[2]}/attributes`, {
params: { sel: "only" }
})
.then(res => {
// console.log(res);
this.onlyTableDate = res.data;
// console.log(this.onlyTableDate);
});
}
},
// 预览图片
handlePreview(preFile) {
// console.log(preFile);
// 得到图片的路径
this.previewImg = preFile.response.data.url;
// console.log(this.previewImg);
// 弹出预览图片的弹出框
this.imgDialogVisible = true;
},
// 删除图片
handleRemove(delFile) {
// console.log(delFile.response.data.tmp_path);
// 找到对应的图片根据index删除
let index = this.addForm.pics.findIndex(item => {
return item.pic == delFile.response.data.tmp_path;
});
// console.log(index);
this.addForm.pics.splice(index, 1);
// console.log(this.addForm.pics);
},
handleSuccess(res) {
// console.log(res);
this.addForm.pics.push({
pic: res.data.tmp_path
});
// console.log(this.addForm.pics);
},
// 添加数据
handleAdd() {
// 验证
this.$refs["addFormRef"].validate(valid => {
if (valid) {
// console.log(this.addForm);
let form = this.deepClone(this.addForm);
// 转换成字符串
form.goods_cat = form.goods_cat.join(",");
// console.log(form);
form.attrs = [];
this.manyTableDate.forEach((item) => {
form.attrs.push({
attr_id: item.attr_id,
attr_value: item.attr_vals.join(",")
})
})
this.onlyTableDate.forEach((item) => {
form.attrs.push({
attr_id: item.attr_id,
attr_value: item.attr_vals
})
})
// console.log(form);
this.$axios.post("goods",form).then(res => {
// console.log(res);
if(res.meta.status == 201){
// 提示添加成功信息
this.$message.success(res.meta.msg);
// 添加成功后跳转到商品列表
this.$router.push('/admin/goods');
}else{
// 提示添加失败信息
this.$message.error(res.meta.msg);
}
})
} else {
this.$message.error("请填写必要的选项!");
return false;
}
});
},
// 深拷贝对象
deepClone(obj) {
if (typeof obj !== "object" || obj == null) {
return obj;
}
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = this.deepClone(obj[key]);
}
}
return result;
}
},
computed: {},
filters: {},
watch: {}
};
</script>
<style scoped>
.el-steps {
margin-top: 15px;
}
.el-tabs {
margin-top: 15px;
}
.btn {
margin-top: 10px;
}
</style>
封装面包屑
<template>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/admin/index' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in breadList" :key="index">{{ item }}</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
props: [],
components: {},
data() {
return {
breadList: []
};
},
mounted() {
this.breadList = this.$route.meta.bread;
},
methods: {},
computed: {},
filters: {},
watch: {}
};
</script>
在main.js中配置
import Vue from 'vue'
// import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Login = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/admin/Login.vue');
const Home = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/admin/home.vue');
const Welcome = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/admin/index.vue');
const Users = () => import( /* webpackChunkName:"users" */ '../components/Users/Users.vue');
const Roles = () => import( /* webpackChunkName:"prower" */ '../components/Power/Roles.vue');
const Rights = () => import( /* webpackChunkName:"prower" */ '../components/Power/Rights.vue');
const Goods = () => import( /* webpackChunkName:"goods" */ '../components/Goods/Goods.vue');
const GoodsAdd = () => import( /* webpackChunkName:"goods" */ '../components/Goods/GoodsAdd.vue');
const Params = () => import( /* webpackChunkName:"goods" */ '../components/Goods/Params.vue');
const Categories = () => import( /* webpackChunkName:"goods" */ '../components/Goods/Categories.vue');
const Orders = () => import( /* webpackChunkName:"orders" */ '../components/Orders/orders.vue');
const Reports = () => import( /* webpackChunkName:"report" */ '../components/Reports/reports.vue');
const routes = [{
path: '/',
redirect: "/Login"
},
{
path: '/Login',
component: Login
},
{
path: '/home',
component: Home,
children: [
// 首页
{
path: '/admin/index',
component: Welcome
},
// 用户列表
{
path: '/admin/users',
name: 'users',
component: Users,
meta: {
bread: ["用户管理", "用户列表"]
}
},
// 角色列表
{
path: '/admin/roles',
name: 'roles',
component: Roles,
meta: {
bread: ["角色管理", "角色列表"]
}
},
// 权限列表
{
path: '/admin/rights',
name: 'rights',
component: Rights,
meta: {
bread: ["权限管理", "权限列表"]
}
},
// 商品列表
{
path: '/admin/goods',
name: 'goods',
component: Goods,
meta: {
bread: ["商品管理", "商品列表"]
}
},
// 添加商品
{
path: '/admin/goods/add',
name: 'goodsAdd',
component: GoodsAdd,
meta: {
bread: ["商品管理", "添加商品"]
}
},
// 分类参数
{
path: '/admin/params',
name: 'params',
component: Params,
meta: {
bread: ["商品管理", "参数列表"]
}
},
// 商品分类
{
path: '/admin/categories',
name: 'categories',
component: Categories,
meta: {
bread: ["用户管理", "用户列表"]
}
},
// 订单列表
{
path: '/admin/orders',
name: 'orders',
component: Orders,
meta: {
bread: ["订单管理", "订单列表"]
}
},
{
path: '/admin/reports',
name: 'reports',
component: Reports,
meta: {
bread: ["数据统计", "数据报表"]
}
}
]
}
]
const router = new VueRouter({
routes
})
//路由守卫 登录鉴权
//to,去哪 from,从来来 next是否进入
router.beforeEach((to, from, next) => {
// console.log(to);
let token = window.sessionStorage.getItem("token");
if (to.path === "/Login") return token ? next("/home") : next();
if (!token) return next("/Login");
next();
});
export default router
每次使用时直接引入使用
<template>
<div>
<!-- 面包屑导航 -->
<bread />
</div>
</template>
<script>
import bread from "@/components/bread";
export default {
props: [],
components: { bread },
data() {
return {}
};
</script>
版权声明:本文为weixin_56499671原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。