E-commerce background management

Table of contents

Project Introduction

Technology used in this project

2. Preparation for development

3. Development process

4. Secondary packaging axios

Five, configure api

6. Home page

7. Page Authentication

 Eight, the main page 

1. Head navigation

2. Left navigation

3. Subject content

        User Management > User List

        User Management>Role List

        Permission management, permission list

        Product management, product classification

        There are also product lists, classification parameters, order lists, and data reports. These modules use request interface rendering, and addition, deletion, modification, and query

9. Project Difficulties:



Project Introduction

This is an e-commerce background management system, the main function is to manage users, product orders, and authority management, and it is used for internal employees to record and classify products


Technology used in this project

                vue2 family bucket, element ui, axios, vue-table-with-tree-grid


1. Main modules of the project

Has a login page and a home page

        The home page has user management, authority management, commodity management, order management

2. Preparation for development

创建项目并下载项目中所需要用的第三方组件

3. Development process

  1. 看接口是否用到跨域,如果需要跨域需要到 跨域需要创建vue.config.js文件,在里面配置devServer,在devServer属性⾥⾯的proxy属性⾥⾯配置,⼀共配置三个属性,分别是代理名称 代理地址 开启跨域 重写路径,这个项目是不用跨域的
  2. Secondary encapsulation of axios and configuration api
  3. configure routing
  4. Download the components required by the project, such as element ui

4. Secondary packaging axios

        Create an http folder under src, create a request.js file under this folder, import axios in the request file, and introduce element ui to implement loading

  1. Introduce axios, and required components
    //封装axios
    import axios from "axios";
    // element ui 提示信息和loading加载
    import { Message, Loading } from 'element-ui';
    //引入路由
    import router from "../router"

  2. Encapsulate the function of loading start and end
    // 封装loading开启和结束函数
    let loading;
    function startLoading(){
      loading = Loading.service({
        lock:true,
        text:'拼命加载中...',
        background:'rgba(0,0,0,0.7)'
      })
    }
    function endLoading(){
      loading.close()
    }

  3. Create an axios instance
    //创建axios实例
    const service = axios.create({
      //基地址
      baseURL:" 接口地址",
      //baseURL:env.dev.baseUrl,
      settimeout: 5000,
    });

  4. Request interception: Request interception uses interceptors.request.use() to add request headers and enable loading before sending the request, and turn off loading if the request fails.
    //2:请求拦截
    service.interceptors.request
      .use((config) => {
        //在发送请求之前做些什么,比如验证token之类的
        if(localStorage.eleToken){
          config.headers.Authorization = localStorage.eleToken
        }
        startLoading();
        return config;
      },(error) => {
        //对错误请求做些什么
        // endLoading();
        loading.close();
        return Promise.reject(error)
      })

  5. Response interception: Response interception uses interceptors.response.use(), the response successfully returns data and closes loading, and the request fails to return the reason for the failure, such as the user needs to delete the stored token when it expires, and prompt the user to expire, and then return to the login page
    //3:响应拦截
    service.interceptors.response.use(
        //请求成功关闭loading 并返回数据
      (response) =>{
        endLoading();
        return response;
      },
      (error) => {
        //对错误请求做些什么
        const {status} = error.response
        //判断用户是否过期,如果过期了清楚token
        if(status == 401){
          Message.error('用户过期,请重新登录!')
          localStorage.removeItem("eleToken")
          router.push("/login")
        }
        //关闭loading 返回信息提示用户登录过期
        endLoading();
        console.log(error)
        Message.error(error.response.data.msg)
        return Promise.reject(error)
    })

  6. throw object
//抛出axios对象实例
export default  service;

Five, configure api

Create a new api.js file in the http folder, and import the packaged axios into the file

// 封装api
// 引入封装好的axios
import request from "./request"

// 向各个接口分发axios
export function getList (){
  return request({
      url:'', // 这个地址是去掉公共地址剩下的地址
      method:'',// 请求方式 支持多种方式  get post put delete 等等
  })
}

6. Home page

Home page style

 Method to realize:

  1. login style
    <template>
      <div class="login_container">
        <!-- 父元素 -->
        <div class="login_box">
          <!-- 头像框 -->
          <div class="avatar_box">
            <img src="../assets/logo.png" alt="" />
          </div>
          <!-- 登录 -->
          <div class="">
            <el-form
              :model="loginForm"
              :rules="loginFormRules"
              ref="loginFormRef"
              class="login_form"
            >
              <!-- 用户名 -->
              <el-form-item prop="username">
                <el-input
                  prefix-icon="el-icon-user-solid"
                  type="text"
                  v-model="loginForm.username"
                  placeholder="请输入用户名"
                ></el-input>
              </el-form-item>
              <!-- 密码 -->
              <el-form-item prop="password">
                <el-input
                  prefix-icon="el-icon-s-goods"
                  type="password"
                  v-model="loginForm.password"
                  placeholder="请输入密码"
                ></el-input>
              </el-form-item>
              <el-form-item class="btns">
                <el-button type="primary" @click="loginFormBtn('loginFormRef')" >登录</el-button>
                <el-button @click="resetForm">重置</el-button>
              </el-form-item>
            </el-form>
          </div>
        </div>
      </div>
    </template>
    <style lang="less" scoped>
    .login_container {
      background: #2b4b6b;
      height: 100%;
    }
    .login_box {
      width: 450px;
      height: 360px;
      background: #fff;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      .avatar_box {
        height: 130px;
        width: 130px;
        border: 1px solid #ccc;
        border-radius: 50%;
        position: absolute;
        padding: 10px;
        left: 50%;
        transform: translate(-50%, -50%);
        box-shadow: 0px 0px 10px #ddd;
        background: #fff;
        img {
          width: 100%;
          height: 100%;
          border-radius: 50%;
          background: #eee;
        }
      }
      .login_form {
        width: 100%;
        position: absolute;
        bottom: 60px;
        padding: 0 40px;
        .btns {
          display: flex;
          justify-content: center;
        }
      }
    }
    </style>

  2. Validation of the input box
      data() {
        return {
          loginForm: {
            username: "admin",
            password: "123456",
          },
          // 校验
          loginFormRules: {
            username: [
              { required: true, message: "请输入用户名", trigger: "blur" },
              { min: 4, max: 10, message: "用户名在4到10个字符之间" ,trigger: "blur" },
            ],
            password: [
              { required: true, message: "请输入密码", trigger: "blur" },
              { min: 6, max: 10, message: "密码在6到10个字符之间",trigger: "blur" },
            ],
          },
        };
      },

  3.  

    Configure the login interface in the api.js folder and import the api

     Click to log in to initiate a request interface    

    When the login button is clicked, the click event of the button is triggered, and a login request is sent to the background. After the request is successful, the token is parsed and stored locally. If the token information is needed in many places on the page, it needs to be stored in vuex and jump to the homepage.

    To parse the token, you can use the plug-in npm install jwt-decode to introduce it on the page that needs to be parsed

        import { xxx } from '../http/api'
    
        loginFormBtn() {
          this.$refs.loginFormRef.validate((valid) => {
            if(!valid) return
            if (valid) {
              loginForm(this.loginForm).then((res) => {
                // 判断状态码是否等于200
                if (res.data.meta.status == 200) {
                  // 获取本地中的token值
                  localStorage.setItem("eleToken", res.data.data.token);
                  let decode = jwt_decode(localStorage.eleToken)
                  this.$store.dispatch('decode', decode)
                  // 弹出提示信息
                  this.$message.success(res.data.meta.msg);
                  // 将页面跳转至首页
                  this.$router.push("/home");
                } else {
                  // 否则提示错误信息
                  this.$message.error(res.data.meta.msg);
                  return;
                }
              });
            } else {
              console.log("error submit!!");
              return false;
            }
          });
        },

    7. Page Authentication

                        In order to prevent users from jumping to the main page without logging in, you need to set up a route guard in index.js under the router folder

// 全局路由守卫前置钩子
router.beforeEach((to,form,next)=>{
  // 判断token是否存在
  const isLogin = localStorage.eleToken ? true : false
  // 判断是否为登录页面
  if(to.path == '/Login'){
    next()
  }else{
    // 如果token值存在就让放行  否则就强制回到登录页面
    isLogin ? next() : next('/Login')
  }
})

 Eight, the main page 

        The main page is mainly divided into three modules, head navigation left navigation, theme content,

1. Head navigation

It is to display the logo, user information, log out, and log out is to clear the token stored locally and in vuex, and jump to the login page

2. Left navigation

        It is implemented with element ui components

        The data in the left menu is sent to us by the background, so we need to get the data information after the page is created, define an event in methods, call it in created (created is a function executed after creation), and put the obtained data in Go to the data, then use the v-for loop to traverse the components of the left navigation, and then configure the corresponding routing in index.js under the router folder

<!-- 侧边栏 -->
      <el-aside width="200px">
        <el-menu
          :collapse="isCollapse"
          unique-opened
          text-color="#fff"
          background-color="#333744"
          active-text-color="#409FFF"
          :default-active="activePath"
          :collapse-transition="false"
          router
        >
          <el-submenu :index="item.id + ''" v-for="item in menuList" :key="item.id">
            <template slot="title">
              <i :class="iconObj[item.id]"></i>
              <span>{
   
   { item.authName }}</span>
            </template>
            <el-menu-item
              @click="saveNavState('/' + subItem.path)"
              :index="'/' + subItem.path"
              v-for="subItem in item.children"
              :key="subItem.id"
            >
              <template slot="title">
                <i :class="iconObj[subItem.id]"></i>
                <span>{
   
   { subItem.authName }}</span>
              </template>
            </el-menu-item>
          </el-submenu>
        </el-menu>
      </el-aside>
    //data的变量 
    menuList: [],

    async getMyMenus() {
      const { data: res } = await getMenus();
      if (res.meta.status != 200) return;
      this.menuList = res.data;
    },

3. Subject content

User Management > User List

  1.   look up
          <!-- 搜索 添加 -->
          <el-row :gutter="20">
            <el-col :span="6">
              <el-input
                placeholder="请输入内容"
                v-model="queryInfo.query"
                clearable
                @clear="getUserList"
              >
                <el-button
                  slot="append"
                  icon="el-icon-search"
                  @click="getUserList"
                ></el-button>
              </el-input>
            </el-col>
            <el-col :span="4">
              <el-button type="primary" @click="addDialogVisible.show = true"
                >添加用户</el-button
              >
            </el-col>
          </el-row>
        //搜索和添加使用的是同一个接口
        async getUserList() {
          const { data: res } = await getUsers(this.queryInfo);
          if (res.meta.status !== 200) return this.$message.error("获取用户列表失败!");
          this.userlist = res.data.users;
          this.total = res.data.total;
        },

  2. Delete needs to send the id of this line to the background
        // 删除用户
        async removeUserById(id) {
          const confirmResult = await this.$confirm(
            "此操作将永久删除该用户, 是否继续?",
            "提示",
            {
              confirmButtonText: "确定",
              cancelButtonText: "取消",
              type: "warning",
            }
          ).catch((err) => err);
          // 点击确定 返回值为:confirm
          // 点击取消 返回值为: cancel
          if (confirmResult !== "confirm") {
            return this.$message.info("已取消删除");
          }
          const { data: res } = await deleteUsers(id);
          if (res.meta.status !== 200) return this.$message.error("删除用户失败!");
          this.$message.success("删除用户成功!");
          this.getUserList();
        },

  3. Add When you click Add, a modal box will open 
        <!-- 添加用户的对话框 -->
        <el-dialog
          title="添加用户"
          center
          :visible.sync="addDialogVisible.show"
          width="50%"
          @close="addDialogClosed"
        >
          <!-- 内容主体 -->
          <el-form
            :model="addUserForm"
            ref="addUserFormRef"
            :rules="addUserFormRules"
            label-width="100px"
          >
            <el-form-item label="用户名" prop="username">
              <el-input v-model="addUserForm.username"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input v-model="addUserForm.password"></el-input>
            </el-form-item>
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="addUserForm.email"></el-input>
            </el-form-item>
            <el-form-item label="手机" prop="mobile">
              <el-input v-model="addUserForm.mobile"></el-input>
            </el-form-item>
          </el-form>
          <span slot="footer" class="dialog-footer">
            <el-button @click="addDialogVisible.show = false">取 消</el-button>
            <el-button type="primary" @click="addUser">确 定</el-button>
          </span>
        </el-dialog>
        // 添加用户
        addUser() {
          // 提交请求前,表单预验证
          this.$refs.addUserFormRef.validate(async (valid) => {
            // console.log(valid)
            // 表单预校验失败
            if (!valid) return;
            const { data: res } = await addUsers(this.addUserForm);
            console.log(this.addUserForm);
            if (res.meta.status != 201) {
              this.$message.error("添加用户失败!");
              return;
            }
            this.$message.success("添加用户成功!");
            // 隐藏添加用户对话框
            this.addDialogVisible.show = false;
            // this.getUserList();
            this.$parent.getUserList();
          });
        },

  4. Edit Click Edit to open the modal
        // 编辑
        async editorCate(row) {
          this.dialogEditor = false;
          this.dialogVisible = true;
          this.dialog = {
            show: true,
            title: "编辑用户",
            option: "edit",
          };
          this.addCateForm.cat_pid = row.cat_id;
          this.addCateForm.cat_name = row.cat_name;
        },
    
    
        //点击确定
        addCate() {
          this.$refs.cateForm.validate(async (valid) => {
            if (valid) {
                const { data: res } = await editorCate(this.addCateForm);
                if (res.meta.status !== 200) {
                  return this.$message.error("编辑失败!");
                }
                this.$message.success("编辑成功!");
              
              this.dialogVisible = false;
              this.addCateForm = {
                cat_name: "", //分类名
                cat_pid: 0, //父级id
                cat_level: 0, //分类等级
              };
              this.selectedKeys = {};
            } else {
              console.log("error submit!!");
              return false;
            }
          });
        },

User Management>Role List

        In addition to adding, deleting, modifying and checking, there is also a tree menu

 

 

Method to realize

Click on the drop-down display to use element ui

 style implementation

        <el-table-column type="expand">
          <template slot-scope="scope">
            <!-- <pre>{
   
   { scope.row }}</pre> -->
            <el-row
              :class="[i1 === 0 ? '' : 'bdtop', 'vcenter']"
              v-for="(item1, i1) in scope.row.children"
              :key="item1.id"
            >
              <!-- 一级 -->
              <el-col :span="5">
                <el-tag closable @close="removeRightById(scope.row, item1.id)">{
   
   {
                  item1.authName
                }}</el-tag>
              </el-col>
              <!-- 二级和三级 -->
              <el-col :span="19">
                <el-row
                  :class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
                  v-for="(item2, i2) in item1.children"
                  :key="item2.id"
                >
                  <el-col :span="6">
                    <el-tag
                      closable
                      @close="removeRightById(scope.row, item2.id)"
                      type="success"
                      >{
   
   { item2.authName }}</el-tag
                    >
                  </el-col>
                  <el-col :span="18">
                    <el-tag
                      v-for="item3 in item2.children"
                      :key="item3.id"
                      type="warning"
                      closable
                      @close="removeRightById(scope.row, item3.id)"
                      >{
   
   { item3.authName }}</el-tag
                    >
                  </el-col>
                </el-row>
              </el-col>
            </el-row>
          </template>
        </el-table-column>

Get data information

    //调取角色数据
    async getAllRoles() {
      let { data: res } = await getRolesList();
      this.rolesList = res.data;
    },

Click to assign permissions to open the modal box, and the echo is realized by recursive method

 Modal style for assigning permissions

    <!-- 分配权限 -->
    <el-dialog
      title="分配权限"
      :visible.sync="dialogVisible"
      width="50%"
      @close="clearDefaultKeys"
    >
      <!-- defaultKeys 默认勾选数组 -->
      <el-tree
        :data="rightsTree"
        show-checkbox
        default-expand-all
        node-key="id"
        :props="treesProps"
        :default-checked-keys="defaultKeys"
        ref="treeData"
      ></el-tree>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addRights">确 定</el-button>
      </span>
    </el-dialog>
    // 打开模态框
    async showDialog(role) {
      this.roleID = role.id;
      this.dialogVisible = true;
      let { data: res } = await getRightsTree();
      if (res.meta.status !== 200) {
        return this.$message.error("获取用户权限列表失败");
      }
      this.rightsTree = res.data;
        // defaultKeys 默认勾选数组
      this.getleafKeys(role, this.defaultKeys);
      console.log(res, "treeData");
    },
    //递归遍历三级节点 如果没有chiledren就是三级节点 那么渲染数组
    getleafKeys(node, arr) {
      if (!node.children) {
        return arr.push(node.id);
      }
      node.children.forEach((item) => {
        this.getleafKeys(item, arr);
      });
    },

Permission management, permission list

The main effect of this page is to show

renderings

    <el-card>
      <el-table :data="rightsList" border stripe>
        <el-table-column type="index"></el-table-column>
        <el-table-column prop="authName" label="权限名称"></el-table-column>
        <el-table-column prop="path" label="路径"></el-table-column>
        <el-table-column prop="level" label="权限等级">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.level == 0">一级</el-tag>
            <el-tag type="success" v-else-if="scope.row.level == 1">二级</el-tag>
            <el-tag type="warning" v-else>三级</el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

Product management, product classification

Simple addition, deletion, modification and query, mainly tree structure  

This function uses a plug-in download that requires cmd to open a black window and enter vue ui to jump to the web page to download vue-table-with-tree-grid. After downloading, introduce it in main.js

      <!-- 表格 -->
      <tree-table
        :expand-type="false"
        :selection-type="false"
        show-index
        index-text="#"
        border
        :data="cateList"
        :columns="columns"
      >
      </tree-table>
      cateList: [],
      columns: [
        {
          label: "分类名称",
          prop: "cat_name",
        },
        {
          label: "是否有效",
          type: "template",
          template: "isok",
        },
        {
          label: "排序",
          type: "template",
          template: "order",
        },
        {
          label: "操作",
          type: "template",
          template: "opt",
        },
      ],
      
    async getAllCategories() {
      let { data: res } = await getCategoriesList(this.queryInfo);
      if (res.meta.status !== 200) {
        return this.$message.error("获取数据失败");
      }
      this.$message.success("获取分类成功");
      this.cateList = res.data.result;

      console.log(res, "分类数据");
    },

add category

Modal box, using cascading selectors

renderings

Implementation:

Render Cascade Option

    <!-- 添加分类弹框 -->
    <el-dialog title="dialog.title" :visible.sync="dialogVisible" width="50%">
      <el-form
        ref="cateForm"
        :model="addCateForm"
        :rules="addCateFormRules"
        label-width="100px"
      >
        <el-form-item label="分类名称:" prop="cat_name">
          <el-input
            v-model="addCateForm.cat_name"
            placeholder="请输入分类名字"
          ></el-input>
        </el-form-item>
        <!-- 
          options:数据源
          props:靶子对象{
            value:选中的属性
            label:显示的名称
            children:嵌套的结构
            ...
          }
          v-model:选中数组
          @changed:选择项发生变化时候 触发的函数
         -->
        <el-form-item label="父级分类:" v-if="dialogEditor">
          <el-cascader
            v-model="selectedKeys"
            :options="parentCateList"
            :props="cascaderProps"
            @change="parentCateListChanged"
            change-on-select
          ></el-cascader>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addCate">确 定</el-button>
      </span>
    </el-dialog>
      //添加分类表单
      addCateForm: {
        cat_name: "", //分类名
        cat_pid: 0, //父级id
        cat_level: 0, //分类等级
      },
          
          
 async showDialog() {
      this.dialog = {
        title: "添加用户",
        option: "add",
      };
      this.dialogEditor = true;
      let { data: res } = await getCategoriesList({ type: 2 });
      if (res.meta.status !== 200) {
        return this.$message.error("获取父级分类数据失败");
      }
      this.$message.success("获取父级分类数据成功!");
      this.parentCateList = res.data;
      this.dialogVisible = true;
    },

binding cascade selection

   		//父级分类
      parentCateList: [],
        //选中的数组
      selectedKeys: [],
       //级联靶子对象
      cascaderProps: {
        value: "cat_id",
        label: "cat_name",
        children: "children",
        expandTrigger: "hover",
      },


	//选择项发生变化时候触发
    parentCateListChanged() {
      console.log(this.selectedKeys,"级联选择器变化了!!!")
      if (this.selectedKeys.length > 0) {
        this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1];
        //为当前分类的等级赋值
        this.addCateForm.cat_level = this.selectedKeys.length;
        return;
      } else {
        this.addCateForm.cat_pid = 0;
        this.addCateForm.cat_level = 0;
      }
    },
        
        //点击确定
    addCate() {
      this.$refs.cateForm.validate(async (valid) => {
        if (valid) {
          if (this.dialog.option == "edit") {
            const { data: res } = await editorCate(this.addCateForm);
            if (res.meta.status !== 200) {
              return this.$message.error("编辑失败!");
            }
            this.$message.success("编辑成功!");
          } else if (this.dialog.option == "add") {
            const { data: res } = await addCateList(this.addCateForm);
            if (res.meta.status !== 201) {
              return this.$message.error("添加失败!");
            }
            this.$message.success("添加成功!");
          }
          this.getAllCategories();
          this.dialogVisible = false;
          this.addCateForm = {
            cat_name: "", //分类名
            cat_pid: 0, //父级id
            cat_level: 0, //分类等级
          };
          this.selectedKeys = {};
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },

There are also product lists, classification parameters, order lists, and data reports. These modules use request interface rendering, and addition, deletion, modification, and query

9. Project Difficulties:

  1. Difficulty: Echoing the assigned permissions of the role list,

    Implementation idea: When clicking to assign permissions, when the modal box is opened, the data is rendered and the selected object

    Implementation method: pass the selected object when clicking to open the modal box, execute the recursive function in the definition method of clicking to open the modal box, and perform judgment traversal. If there are chiledren, loop through the function itself. If there is no chiledren, it is a third-level node. render array,

  2. Difficulties: user addition of product categories, cascading selectors

    Implementation idea: when clicking to add a user category, render the cascade selector. When we select the cascade selector, we need to bind the selected id

    Implementation method: call the interface to obtain information, bind options: data source, set the target object, use v-model to bind the selected array, set the function triggered when the selection item changes, and perform if judgment when the selector changes, If the length of the selected array is greater than 0, subtract 1 from the id of the value, which means that the id of the parent of the added data is placed in the new array, and then the classification level and the added classification name are stored in the new array.

  3. Difficulty: button permissions

    Implementation idea: In the interface of a certain menu, we need to display buttons that can be operated according to the button permission data, such as delete, modify, add, etc.

    Implementation method: If we want to implement button permission control, we need to use vue's custom instructions to achieve it. First, we need to create a button permission control instruction. We define the name of this instruction as: va, inside this instruction Get the button permission data stored in vuex, get the data of the custom attribute value through binding.value, and judge whether the button permission data obtained from vuex contains the permission contained in the custom command, if not , we are setting el.style.display = "none", and use el.parentNode.removeChild(el) to remove the current button element

    Vue.directive('a', {
     beforeMount: function (el, binding) {
             //获取vuex或者本地存放的按钮数据
         let actionList = storage.getItem('actionList');
             //控制指令的按钮数据
         let value = binding.value;
             //includes()方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。
         let hasPermission = actionList.includes(value)
         //判断是否存在如有有就显示按钮,如果不存在就隐藏
         if (!hasPermission) {
         	el.style = 'display:none';
        	 setTimeout(() => {
     				el.parentNode.removeChild(el);
     			}, 0)
     		}
     	}
    })

Guess you like

Origin blog.csdn.net/dyx001007/article/details/127640030