电商后台管理系统项目总结(下)

模块技术点

左侧动态菜单栏

<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>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值