电商后台管理系统(各模块技术点)

登录:
在登陆页面输入账号密码,讲数据发送给服务器
服务器返回登录的结果,成功返回的数据中带有token
在得到token后,将他进行保存,存储在本地存储中。
登录成功之后,需要讲后台返回的token保存到本地存储中,
操作完毕后,需要跳转到home

login() {
      this.$refs.ruleForm.validate(async (v) => {
        if (v) {
          let res = await loginAPI.login(this.loginForm);
          console.log(res);
          if (res.data.meta.status == 200) {
            this.$message.success(res.data.meta.msg);
            localStorage.setItem("token", res.data.data.token);
            this.$router.push("/home");
          } else {
            this.$message.error(res.data.meta.msg);
          }
        }
      });
    },

进行路由鉴权,如果没有登录,不能访问/home,强制跳转到登录页面

router.beforeEach((to, from, next) => {
  if (to.path === "/") {
    return next()
  }
  let token = localStorage.getItem("token")
  if (!token) {
    return next("/")
  }
  next()
})

实现退出
在home组件添加一个退出按钮,点击直接清空本地存储中的token即可。

首页布局:
使用element-ui进行快速布局,
请求侧边栏数据

created() {
    HomeAPI.leftMensList(this.menuList).then((res) => {
      // console.log(res);
      if (res.data.meta.status == 200) {
        this.menuList = res.data.data;
      } else {
        this.$message({
          message: "菜单数据获取失败",
        });
      }
    });
  },

通过v-for 双重循环渲染左侧菜单

<el-menu
              :default-active="$route.path"
              class="el-menu-vertical-demo"
              background-color="#333744"
              text-color="#fff"
              active-text-color="#ffd04b"
              :unique-opened="true"
              router
              :collapse="isCollapse"
              :collapse-transition="false"
              @open="handleOpen"
              @close="handleClose"
            >
              <el-submenu
                :index="item.id + ''"
                v-for="item in menuList"
                :key="item.id"
              >
                <template slot="title">
                  <i :class="iconsObj[item.id]"></i>
                  <span>{{ item.authName }}</span>
                </template>
                <el-menu-item
                  v-for="item1 in item.children"
                  :key="item1.id"
                  :index="'/' + item1.path"
                >
                  <i :class="iconsObj1[item1.id]"></i>

                  {{ item1.authName }}
                </el-menu-item>
              </el-submenu>
            </el-menu>

侧边菜单栏的伸缩功能

<el-aside :width="isCollapse ? '64px' : '300px'">
        <div class="toggles" @click="toggleCollapse">|||</div>

用户列表基本结构
使用element-ui面包屑组件,完成顶部导航
使用element-ui卡片组件完成主体表格
然后讲用户数据渲染

 <div>
    <!-- 面包屑导航 -->
    <Bread></Bread>
    <!-- 卡片区域 -->
    <el-card class="box-card">
      <!-- 搜索框 -->
      <el-input
        placeholder="请输入内容"
        v-model="input"
        clearable
        class="input"
      >
        <template slot="append">
          <span class="el-icon-search" @click="userSeach"></span>
        </template>
      </el-input>
      <el-button type="primary" class="user" @click="addUser"
        >添加用户</el-button
      >
      <!-- 用户列表 -->
      <el-table :data="tableData" border style="width: 100%" class="table">
        <el-table-column type="index" width="100px" label="#">
        </el-table-column>
        <el-table-column prop="username" label="姓名"> </el-table-column>
        <el-table-column prop="email" label="邮箱"> </el-table-column>
        <el-table-column prop="mobile" label="电话"> </el-table-column>
        <el-table-column prop="role_name" label="角色"> </el-table-column>
        <el-table-column label="状态">
          <template slot-scope="scope">
            <el-switch
              v-model="scope.row.mg_state"
              @change="userEdit(scope.row)"
            ></el-switch>
          </template>
        </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <!-- 修改按钮 -->
            <el-button
              type="primary"
              icon="el-icon-edit"
              size="mini"
              @click="editState(scope.row.id)"
            ></el-button>
            <!-- 删除按钮 -->
            <el-button
              type="danger"
              icon="el-icon-delete"
              size="mini"
              @click="delUser(scope.row.id)"
            ></el-button>
            <!-- 分配权限按钮 -->
            <el-tooltip
              effect="dark"
              content="分配权限"
              placement="top"
              :enterable="false"
            >
              <el-button
                type="warning"
                icon="el-icon-setting"
                size="mini"
                @click="distribution(scope.row)"
              ></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <div class="block">
        <el-pagination
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="queryInfo.pagenum"
          :page-sizes="[1, 2, 5, 10]"
          :page-size="queryInfo.pagesize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
        >
        </el-pagination>
      </div>

请求用户列表数据:

//用户列表
    getTableData() {
      userAPI.getUserList(this.queryInfo).then((res) => {
        // console.log(res);
        if (res.data.meta.status !== 200) {
          this.$message.error(res.data.meta.msg);
        } else {
          this.$message.success(res.data.meta.msg);
          this.tableData = res.data.data.users;
          this.total = res.data.data.total;
        }
      });
    },

实现分页功能

handleSizeChange(val) {
      this.queryInfo.pagesize = val;
      // console.log(`每页 ${val} 条`);
      this.getTableData();
    },
    handleCurrentChange(val) {
      this.queryInfo.pagenum = val;
      // console.log(`当前页: ${val}`);
      this.getTableData();
    },

实现更新用户状态

 <el-switch
              v-model="scope.row.mg_state"
              @change="userEdit(scope.row)"
            ></el-switch>

发送请求完成状态的更改

userEdit(stateChange) {
      // console.log(stateChange);
      userAPI
        .editUserState(stateChange.id, stateChange.mg_state)
        .then((res) => {
          // console.log(res);
          if (res.data.meta.status == 200) {
            this.$message.success("更新用户状态成功");
          } else {
            this.$message.error("更新用户状态失败");
          }
        });
    },

实现搜索功能

 <el-input
        placeholder="请输入内容"
        v-model="input"
        clearable
        class="input"
      >
        <template slot="append">
          <span class="el-icon-search" @click="userSeach"></span>
        </template>
      </el-input>

//搜索
    userSeach() {
      this.queryInfo.query = this.input;
      this.getTableData();
    },

实现添加用户

<el-dialog :visible.sync="addDialogVisible" width="30%">
        <el-form
          :model="addForm"
          :rules="addFormRules"
          ref="addFormRef"
          label-width="100px"
          class="demo-ruleForm"
        >
          <el-form-item label="用户名" prop="username">
            <el-input v-model="addForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input v-model="addForm.password"></el-input>
          </el-form-item>
          <el-form-item label="邮箱" prop="email">
            <el-input v-model="addForm.email"></el-input>
          </el-form-item>
          <el-form-item label="电话" prop="mobile">
            <el-input v-model="addForm.mobile"></el-input>
          </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button type="primary" @click="submitUser">提交</el-button>
          <el-button @click="addDialogVisible = false">取消</el-button>
        </span>
      </el-dialog>

添加数据绑定和校验规则:

  data() {
    //验证邮箱的正则
    var checkEmail = (rule, value, callback) => {
      //alert(value)
      var reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
      if (reg.test(value)) {
        // 格式正确返回true
        callback();
      } else {
        // 格式不正确
        return callback(new Error("邮箱格式不对"));
      }
    };
    //验证手机号的正则
    var checkMobile = (rule, value, callback) => {
      var mobileReg = /^1[3456789]\d{9}$/;
      if (mobileReg.test(value)) {
        // 格式正确返回true
        callback();
      } else {
        // 格式不正确
        return callback(new Error("手机号格式不对"));
      }
    };
    return {
      input: "",
      tableData: [],
      //获取用户列表的参数
      queryInfo: {
        query: "",
        pagenum: 1, //当前页数
        pagesize: 10, //当前每页显示多少数据
      },
      total: 0,
      addDialogVisible: false, //添加弹出框
      //添加 验证
      addFormRules: {
        username: [
          { required: true, message: "请输入活动名称", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
        ],
        password: [
          { required: true, message: "请输入活动名称", trigger: "blur" },
          { min: 6, max: 15, message: "长度在6 到 15 个字符", trigger: "blur" },
        ],
        email: [
          { required: true, message: "请输入邮箱地址", trigger: "blur" },
          { validator: checkEmail, trigger: "blur" },
        ],
        mobile: [
          { required: true, message: "请输入电话号码", trigger: "blur" },
          { validator: checkMobile, trigger: "blur" },
        ],
      },
      addForm: {
        username: "",
        password: "",
        email: "",
        mobile: "",
      },
      dialogVisible: false, //编辑弹出框
      //验证 修改
      editFormRules: {
        email: [
          { required: true, message: "请输入邮箱地址", trigger: "blur" },
          { validator: checkEmail, trigger: "blur" },
        ],
        mobile: [
          { required: true, message: "请输入电话号码", trigger: "blur" },
          { validator: checkMobile, trigger: "blur" },
        ],
      },
      editForm: {},
      //需要被分配的用户信息
      userInfo: {},
      selectedRole: "",
      //角色列表
      roleList: [],
      roleDialogVisible: false,
    };
  },

当关闭对话框时,重置表单。

修改用户信息
根据id查询需要修改的用户数据

//显示编辑对话框
    editState(id) {
      userAPI.editState(id).then((res) => {
        console.log(res);
        this.editForm = res.data.data;
      });
      this.dialogVisible = true;
    },

弹出框数据绑定及验证:

//控制修改用户对话框的显示与否
editDialogVisible: false,
//修改用户的表单数据
editForm: {
    username: '',
    email: '',
    mobile: ''
},
//修改表单的验证规则对象
editFormRules: {
    email: [
        { required: true, message: '请输入邮箱', trigger: 'blur' },
        {
        validator: checkEmail,
        message: '邮箱格式不正确,请重新输入',
        trigger: 'blur'
        }
    ],
    mobile: [
        { required: true, message: '请输入手机号码', trigger: 'blur' },
        {
        validator: checkMobile,
        message: '手机号码不正确,请重新输入',
        trigger: 'blur'
        }
    ]
}

用户点击确定按钮,验证数据成功发送请求完成修改

editUser() {
      this.$refs.editRuleForm.validate((valid) => {
        if (valid) {
          userAPI
            .editUsers(id, {
              email: this.editForm.email,
              mobile: this.editForm.mobile,
            })
            .then((res) => {
              console.log(res);
              if (res.data.meta.status == 200) {
                this.$message.success("编辑用户成功");
              } else {
                this.$message.error("编辑用户失败");
              }
              this.dialogVisible = false;
              this.getTableData();
            });
        }
      });
    },

删除用户:
根据id删除相对应的数据

delUser(id) {
      userAPI.deletUser(id).then((res) => {
        console.log(res);
        if (res.data.meta.status == 200) {
          this.$message.success("用户删除成功");
        } else {
          this.$message.error("用户删除失败");
        }
        this.getTableData();
      });
    },

权限管理
权限列表
电商后台管理系统(各模块技术点)
使用element-ui组件添加面包屑导航
显示数据

<el-table :data="rightsList" border style="width: 100%">
      <el-table-column type="index" label="#" width="60"></el-table-column>
      <el-table-column
        prop="authName"
        label="权限名称"
        width="360"
      ></el-table-column>
      <el-table-column prop="path" label="路径" width="360"></el-table-column>
      <el-table-column prop="level" label="权限等级" width="360">
        <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>

	created() {
	    roleAPI.getRightList(this.rightsList).then((res) => {
	      console.log(res);
	      this.rightsList = res.data.data;
	    });
	  },

角色列表
使用element-ui面包屑导航,进行布局
显示数据

<el-table row-key="id" :data="roleList" border>
    <!-- 添加展开列 -->
    <el-table-column type="expand"></el-table-column>
    <el-table-column type="index"></el-table-column>
    <el-table-column label="角色名称" prop="roleName"></el-table-column>
    <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
    <el-table-column label="操作" width="300px">
        <template slot-scope="scope"> 
            <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
            <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
            <el-button size="mini" type="warning" icon="el-icon-setting">分配权限</el-button>
        </template>
    </el-table-column>
</el-table>

export default {
    data(){
        return {
            roleList:[]
        }
    },created(){
        this.getRoleList();
    },methods:{
        async getRoleList(){
            const {data:res} = await this.$http.get('roles')
            console.log(res.data)
            this.roleList = res.data;
        }
    }
}

生成权限列表

<el-table-column type="expand">
          <el-row slot-scope="scope">
            <el-row
              v-for="item in scope.row.children"
              :key="item.id"
              style="border-bottom: 1px solid #eee"
            >
              <!-- 一级权限 -->
              <el-col :span="5">
                <el-tag closable @close="removeById(scope.row, item.id)">{{
                  item.authName
                }}</el-tag>
                <i class="el-icon-caret-right"></i>
              </el-col>
              <!-- 二级权限和三级权限 -->
              <el-col :span="19">
                <el-row
                  v-for="item1 in item.children"
                  :key="item1.id"
                  style="border-bottom: 1px solid #eee"
                >
                  <el-col :span="6">
                    <el-tag
                      closable
                      type="success"
                      @close="removeById(scope.row, item1.id)"
                      >{{ item1.authName }}</el-tag
                    >
                    <i class="el-icon-caret-right"></i>
                  </el-col>
                  <el-col :span="14">
                    <el-tag
                      type="warning"
                      v-for="item2 in item1.children"
                      :key="item2.id"
                      closable
                      @close="removeById(scope.row, item2.id)"
                    >
                      {{ item2.authName }}
                    </el-tag>
                  </el-col>
                </el-row>
              </el-col>
            </el-row>
          </el-row>
        </el-table-column>

添加权限删除功能

 //删除权限
    removeById(role, rightId) {
      // console.log(role,rightId);
      this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          roleAPT.removeRightsById(this.roleId,this.rightId)
            .then((res) => {
              role.children = res.data.data;
            });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

.完成树形结构弹窗
在element.js中引入Tree,注册Tree

<!-- 分配权限对话框 -->
<el-dialog title="分配权限" :visible.sync="setRightDialogVisible" width="50%" @close="setRightDialogClose">
    <!-- 树形组件
    show-checkbox:显示复选框
    node-key:设置选中节点对应的值
    default-expand-all:是否默认展开所有节点
    :default-checked-keys 设置默认选中项的数组
    ref:设置引用 -->
    <el-tree :data="rightsList" :props="treeProps" show-checkbox node-key="id" default-expand-all :default-checked-keys="defKeys" ref="treeRef"></el-tree>
    <span slot="footer" class="dialog-footer">
        <el-button @click="setRightDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="allotRights">确 定</el-button>
    </span>
</el-dialog>

<script>
export default {
  data() {
    return {
      //角色列表数据
      roleList: [],
      //控制分配权限对话框的显示
      setRightDialogVisible: false,
      //权限树数据
      rightsList: [],
      //树形控件的属性绑定对象
      treeProps: {
        //通过label设置树形节点文本展示authName
        label: 'authName',
        //设置通过children属性展示子节点信息
        children: 'children'
      },
      //设置树形控件中默认选中的内容
      defKeys: [],
      //保存正在操作的角色id
      roleId:''
    }
  },
  created() {
    this.getRoleList()
  },
  methods: {
    async getRoleList() {
      const { data: res } = await this.$http.get('roles')
      //如果返回状态为异常状态则报错并返回
      if (res.meta.status !== 200)
        return this.$message.error('获取角色列表失败')
      //如果返回状态正常,将请求的数据保存在data中
      // this.roleList = res.data
      console.log(res.data)
      this.roleList = res.data
    },
    async removeRightById(role, rightId) {
      //弹窗提示用户是否要删除
      const confirmResult = await this.$confirm(
        '请问是否要删除该权限',
        '删除提示',
        {
          confirmButtonText: '确认删除',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).catch(err => err)
      //如果用户点击确认,则confirmResult 为'confirm'
      //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'
      if (confirmResult != 'confirm') {
        return this.$message.info('已经取消删除')
      }

      //用户点击了确定表示真的要删除
      //当发送delete请求之后,返回的数据就是最新的角色权限信息
      const { data: res } = await this.$http.delete(
        `roles/${role.id}/rights/${rightId}`
      )
      if (res.meta.status !== 200)
        return this.$message.error('删除角色权限失败')

      //无需再重新加载所有权限
      //只需要对现有的角色权限进行更新即可
      role.children = res.data
      // this.getRoleList();
    },
    async showSetRightDialog(role) {
      //将role.id保存起来以供保存权限时使用
      this.roleId = role.id;  
      //获取所有权限的数据
      const { data: res } = await this.$http.get('rights/tree')
      //如果返回状态为异常状态则报错并返回
      if (res.meta.status !== 200) return this.$message.error('获取权限树失败')
      //如果返回状态正常,将请求的数据保存在data中
      this.rightsList = res.data

      //调用getLeafKeys进行递归,将三级权限添加到数组中
      this.getLeafKeys(role, this.defKeys)
      //当点击分配权限按钮时,展示对应的对话框
      this.setRightDialogVisible = true
      console.log(this.defKeys)
    },
    getLeafKeys(node, arr) {
      //该函数会获取到当前角色的所有三级权限id并添加到defKeys中
      //如果当前节点不包含children属性,则表示node为三级权限
      if (!node.children) {
        return arr.push(node.id)
      }
      //递归调用
      node.children.forEach(item => this.getLeafKeys(item, arr))
    },
    setRightDialogClose() {
      //当用户关闭树形权限对话框的时候,清除掉所有选中状态
      this.defKeys = []
    },
    async allotRights() {
      //当用户在树形权限对话框中点击确定,将用户选择的
      //权限发送请求进行更新

      //获取所有选中及半选的内容
      const keys = [
        ...this.$refs.treeRef.getCheckedKeys(),
        ...this.$refs.treeRef.getHalfCheckedKeys()
      ]
      //将数组转换为 , 拼接的字符串
      const idStr = keys.join(',')

      //发送请求完成更新
      const { data: res } = await this.$http.post(
        `roles/${this.roleId}/rights`,
        { rids:idStr }
      )
      if (res.meta.status !== 200)
        return this.$message.error('分配权限失败')

      this.$message.success("分配权限成功")
      this.getRoleList();
      //关闭对话框
      this.setRightDialogVisible = false;
    }
  }
}
</script>

商品分类
请求分类数据

data() {
    return {
      queryInfo: {
        query: "",
        // 当前的页数
        pagenum: 1,
        // 当前每页显示多少条数据
        pagesize: 10,
      },
      goodsList: [],
      total: 0,
    };
  },

getGoodsList() {
      goodAPI.getGoodsList(this.queryInfo).then((res) => {
        console.log(res);
        this.goodsList = res.data.data.goods;
        this.total = res.data.data.total;
      });
    },

完成分页功能:

// 监听 pagesize 改变的事件
    handleSizeChange(newSize) {
      this.queryInfo.pagesize = newSize;
      this.getGoodsList();
    },

    // 监听 页码值 改变的事件
    handleCurrentChange(newPage) {
      this.queryInfo.pagenum = newPage;
      this.getGoodsList();
    },

完成添加分类

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>商品管理</el-breadcrumb-item>
      <el-breadcrumb-item>商品分类</el-breadcrumb-item>
    </el-breadcrumb>

    <!-- 卡片视图区域 -->
    <el-card>
      <el-row>
        <el-col>
          <el-button type="primary" @click="showAddCateDialog">添加分类</el-button>
        </el-col>
      </el-row>

      <!-- 表格 -->
      <tree-table class="treeTable" :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border :show-row-hover="false">
        <!-- 是否有效 -->
        <template slot="isok" slot-scope="scope">
          <i class="el-icon-success" v-if="scope.row.cat_deleted === false" style="color: lightgreen;"></i>
          <i class="el-icon-error" v-else style="color: red;"></i>
        </template>
        <!-- 排序 -->
        <template slot="order" slot-scope="scope">
          <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag>
          <el-tag type="success" size="mini" v-else-if="scope.row.cat_level===1">二级</el-tag>
          <el-tag type="warning" size="mini" v-else>三级</el-tag>
        </template>
        <!-- 操作 -->
        <template slot="opt">
          <el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
          <el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
        </template>
      </tree-table>

      <!-- 分页区域 -->
      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="querInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="querInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total">
      </el-pagination>
    </el-card>

    <!-- 添加分类的对话框 -->
    <el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed">
      <!-- 添加分类的表单 -->
      <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px">
        <el-form-item label="分类名称:" prop="cat_name">
          <el-input v-model="addCateForm.cat_name"></el-input>
        </el-form-item>
        <el-form-item label="父级分类:">
          <!-- options 用来指定数据源 -->
          <!-- props 用来指定配置对象 -->
          <el-cascader expand-trigger="hover" :options="parentCateList" :props="cascaderProps" v-model="selectedKeys" @change="parentCateChanged" clearable change-on-select>
          </el-cascader>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addCateDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addCate">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import goodAPI from "../../http/API/Good"
export default {
  data() {
    return {
      // 查询条件
      querInfo: {
        type: 3,
        pagenum: 1,
        pagesize: 5
      },
      // 商品分类的数据列表,默认为空
      catelist: [],
      // 总数据条数
      total: 0,
      // 为table指定列的定义
      columns: [
        {
          label: '分类名称',
          prop: 'cat_name'
        },
        {
          label: '是否有效',
          // 表示,将当前列定义为模板列
          type: 'template',
          // 表示当前这一列使用模板名称
          template: 'isok'
        },
        {
          label: '排序',
          // 表示,将当前列定义为模板列
          type: 'template',
          // 表示当前这一列使用模板名称
          template: 'order'
        },
        {
          label: '操作',
          // 表示,将当前列定义为模板列
          type: 'template',
          // 表示当前这一列使用模板名称
          template: 'opt'
        }
      ],
      // 控制添加分类对话框的显示与隐藏
      addCateDialogVisible: false,
      // 添加分类的表单数据对象
      addCateForm: {
        // 将要添加的分类的名称
        cat_name: '',
        // 父级分类的Id
        cat_pid: 0,
        // 分类的等级,默认要添加的是1级分类
        cat_level: 0
      },
      // 添加分类表单的验证规则对象
      addCateFormRules: {
        cat_name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
      },
      // 父级分类的列表
      parentCateList: [],
      // 指定级联选择器的配置对象
      cascaderProps: {
        value: 'cat_id',
        label: 'cat_name',
        children: 'children'
      },
      // 选中的父级分类的Id数组
      selectedKeys: []
    }
  },
  created() {
    this.getCateList()
  },
  methods: {
    // 获取商品分类数据
    async getCateList() {
      const { data: res } = await goodAPI.getCateList(this.querInfo)

      if (res.meta.status !== 200) {
        return this.$message.error('获取商品分类失败!')
      }

      console.log(res.data)
      // 把数据列表,赋值给 catelist
      this.catelist = res.data.result
      // 为总数据条数赋值
      this.total = res.data.total
    },
    // 监听 pagesize 改变
    handleSizeChange(newSize) {
      this.querInfo.pagesize = newSize
      this.getCateList()
    },
    // 监听 pagenum 改变
    handleCurrentChange(newPage) {
      this.querInfo.pagenum = newPage
      this.getCateList()
    },
    // 点击按钮,展示添加分类的对话框
    showAddCateDialog() {
      // 先获取父级分类的数据列表
      this.getParentCateList()
      // 再展示出对话框
      this.addCateDialogVisible = true
    },
    // 获取父级分类的数据列表
    async getParentCateList() {
      const { data: res } = await goodAPI.getCateList( this.querInfo,{
        params: { type: 2 }
      })

      if (res.meta.status !== 200) {
        return this.$message.error('获取父级分类数据失败!')
      }

      console.log(res.data)
      this.parentCateList = res.data
    },
    // 选择项发生变化触发这个函数
    parentCateChanged() {
      console.log(this.selectedKeys)
      // 如果 selectedKeys 数组中的 length 大于0,证明选中的父级分类
      // 反之,就说明没有选中任何父级分类
      if (this.selectedKeys.length > 0) {
        // 父级分类的Id
        this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
        // 为当前分类的等级赋值
        this.addCateForm.cat_level = this.selectedKeys.length
      } else {
        // 父级分类的Id
        this.addCateForm.cat_pid = 0
        // 为当前分类的等级赋值
        this.addCateForm.cat_level = 0
      }
    },
    // 点击按钮,添加新的分类
    addCate() {
      this.$refs.addCateFormRef.validate(async valid => {
        if (!valid) return
        const { data: res } = await this.$http.post('categories', this.addCateForm)

        if (res.meta.status !== 201) {
          return this.$message.error('添加分类失败!')
        }

        this.$message.success('添加分类成功!')
        this.getCateList()
        this.addCateDialogVisible = false
      })
    },
    // 监听对话框的关闭事件,重置表单数据
    addCateDialogClosed() {
      this.$refs.addCateFormRef.resetFields()
      this.selectedKeys = []
      this.addCateForm.cat_level = 0
      this.addCateForm.cat_pid = 0
    }
  }
}
</script>

参数管理
首先完成参数管理的布局

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <Break></Break>
    <!-- 卡片区域 -->
    <el-card class="box-card">
      <!-- 警告区域 -->
      <el-alert
        title="注意:只允许为第三级分类设置相关参数!"
        type="warning"
        effect="dark"
        :closable="false"
        show-icon
      >
      </el-alert>
      <div class="selectType">
        <span>选择商品分类:</span>
        <!-- 级联选择器 -->
        <el-cascader
          v-model="selectValue"
          :options="selectOptions"
          :props="cateProps"
          @change="selectChange"
        ></el-cascader>
      </div>
      <el-tabs
        type="card"
        class="tabsCard"
        v-model="activeName"
        @tab-click="tabClick"
      >
        <!-- 动态参数 -->
        <el-tab-pane label="动态参数" name="many">
          <!-- 添加参数 -->
          <el-button
            type="primary"
            size="mini"
            class="btn"
            :disabled="btnDisabled"
            @click="addDialogVisible = true"
            >添加参数</el-button
          >
          <!-- 添加参数 表格 -->
          <el-table :data="manyParams" style="width: 100%" border>
            <el-table-column type="expand">
              <template slot-scope="scope">
                <el-tag
                  :key="index"
                  v-for="(item, index) in scope.row.attr_vals"
                  closable
                  :disable-transitions="false"
                  @close="attrHandleClose(tag)"
                >
                  {{ item }}
                </el-tag>
                <el-input
                  class="input-new-tag"
                  v-if="scope.row.inputVisible"
                  v-model="scope.row.inputValue"
                  ref="saveTagInput"
                  size="small"
                  @keyup.enter.native="handleInputConfirm(scope.row)"
                  @blur="handleInputConfirm(scope.row)"
                >
                </el-input>
                <el-button
                  v-else
                  class="button-new-tag"
                  size="small"
                  @click="showInput(scope.row)"
                  >+ New Tag</el-button
                >
              </template>
            </el-table-column>
            <el-table-column type="index" label="#"> </el-table-column>
            <el-table-column label="参数名称" prop="attr_name">
            </el-table-column>
            <el-table-column label="操作" prop="id">
              <template v-slot="scope">
                <el-button
                  type="primary"
                  size="mini"
                  icon="el-icon-edit"
                  @click="editParamsDialog(scope.row)"
                  >修改</el-button
                >
                <el-button
                  type="danger"
                  size="mini"
                  icon="el-icon-delete"
                  @click="delParams(scope.row.attr_id)"
                  >删除</el-button
                >
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <!-- 静态属性 -->
        <el-tab-pane label="静态属性" name="only">
          <!-- 添加属性 -->
          <el-button
            type="primary"
            size="mini"
            class="btn"
            :disabled="btnDisabled"
            @click="addDialogVisible = true"
            >添加属性</el-button
          >
          <!-- 添加属性表格 -->
          <el-table :data="onlyParams" style="width: 100%" border>
            <el-table-column type="expand">
              <template slot-scope="scope">
                <el-tag
                  :key="index"
                  v-for="(item, index) in scope.row.attr_vals"
                  closable
                  :disable-transitions="false"
                  @close="attrHandleClose(tag)"
                >
                  {{ item }}
                </el-tag>
                <el-input
                  class="input-new-tag"
                  v-if="scope.row.inputVisible"
                  v-model="scope.row.inputValue"
                  ref="saveTagInput"
                  size="small"
                  @keyup.enter.native="handleInputConfirm(scope.row)"
                  @blur="handleInputConfirm(scope.row)"
                >
                </el-input>
                <el-button
                  v-else
                  class="button-new-tag"
                  size="small"
                  @click="showInput(scope.row)"
                  >+ New Tag</el-button
                >
              </template>
            </el-table-column>
            <el-table-column type="index" label="#"> </el-table-column>
            <el-table-column label="参数名称" prop="attr_name">
            </el-table-column>
            <el-table-column label="操作" prop="id">
              <template v-slot="scope">
                <el-button
                  type="primary"
                  size="mini"
                  icon="el-icon-edit"
                  @click="editParamsDialog(scope.row)"
                  >修改</el-button
                >
                <el-button
                  type="danger"
                  size="mini"
                  icon="el-icon-delete"
                  @click="delParams(scope.row.attr_id)"
                  >删除</el-button
                >
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- 修改动态参数弹出框 -->
    <el-dialog
      :title="'修改' + titleText"
      :visible.sync="editManyDialogVisible"
      width="30%"
    >
      <el-form
        :model="editManyRuleForm"
        :rules="editManyRules"
        ref="edifManyRuleFormRef"
        label-width="100px"
        class="demo-ruleForm"
      >
        <el-form-item label="动态参数" prop="attr_name">
          <el-input v-model="editManyRuleForm.attr_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editManyDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="editMany">确 定</el-button>
      </span>
    </el-dialog>
    <!-- 添加参数弹出框 -->
    <el-dialog
      :title="'添加' + titleText"
      :visible.sync="addDialogVisible"
      width="30%"
    >
      <el-form
        :model="addParamsForm"
        :rules="editManyRules"
        ref="addRuleFormRef"
        label-width="100px"
        class="demo-ruleForm"
      >
        <el-form-item label="动态参数" prop="attr_name">
          <el-input v-model="addParamsForm.attr_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addParams">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

完成级联选择框

<!-- 选择商品分类区域 -->
<el-row class="cat_opt">
    <el-col>
        <span>选择商品分类:</span>
        <!-- 选择商品分类的级联选择框 -->
        <el-cascader expandTrigger='hover' v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader>
    </el-col>
    <el-col></el-col>
</el-row>
......
<script>
export default {
  data() {
    return {
        //分类列表
        cateList:[],
        //用户在级联下拉菜单中选中的分类id
        selectedCateKeys:[],
        //配置级联菜单中数据如何展示
        cateProps: {
            value: 'cat_id',
            label: 'cat_name',
            children: 'children'
        }
    }
  },
  created() {
      this.getCateList()
  },
  methods: {
      async getCateList(){
        //获取所有的商品分类列表
        const { data: res } = await this.$http.get('categories')

        if (res.meta.status !== 200) {
            return this.$message.error('获取分类数据失败')
        }
        //将数据列表赋值给cateList
        this.cateList = res.data
        // //保存总数据条数
        // this.total = res.data.total
        //   console.log(res.data);
      },
      handleChange(){
        //当用户在级联菜单中选择内容改变时触发
        console.log(this.selectedCateKeys);
      }
  }
}
</script>

展示参数
展示动态参数数据以及静态属性数据

<!-- tab页签区域 -->
<el-tabs v-model="activeName" @tab-click="handleTabClick">
  <!-- 添加动态参数的面板 将标签页改为many -->
  <el-tab-pane label="动态参数" name="many">
    <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加参数</el-button>
    <!-- 动态参数表格 -->
    <el-table :data="manyTableData" border stripe>
      <!-- 展开行 -->
      <el-table-column type="expand"></el-table-column>
      <!-- 索引列 -->
      <el-table-column type="index"></el-table-column>
      <el-table-column label="参数名称" prop="attr_name"></el-table-column>
      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
          <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-tab-pane>
  <!-- 添加静态属性的面板 将标签页改为only -->
  <el-tab-pane label="静态属性" name="only">
    <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加属性</el-button>
    <!-- 静态属性表格 -->
    <el-table :data="onlyTableData" border stripe>
      <!-- 展开行 -->
      <el-table-column type="expand"></el-table-column>
      <!-- 索引列 -->
      <el-table-column type="index"></el-table-column>
      <el-table-column label="属性名称" prop="attr_name"></el-table-column>
      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
          <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-tab-pane>

</el-tabs>


<script>
export default {
  data() {
    return {
      ......
      //tab页签激活显示的页签项
      activeName: 'many',
      //用来保存动态参数数据
      manyTableData: [],
      //用来保存静态属性数据
      onlyTableData: []  
    }
  methods: {
    .......
    async handleChange() {
      //当用户在级联菜单中选择内容改变时触发
      console.log(this.selectedCateKeys)
      //发送请求,根据用户选择的三级分类和面板获取参数数据
      const { data: res } = await this.$http.get(
        `categories/${this.cateId}/attributes`,
        { params: { sel: this.activeName } }
      )
      if (res.meta.status !== 200) {
        return this.$message.error('获取参数列表数据失败')
      }

      console.log(res.data)
      if (this.activeName === 'many') {
        //获取的是动态参数
        this.manyTableData = res.data
      } else if (this.activeName === 'only') {
        //获取的是静态属性
        this.onlyTableData = res.data
      }
    },
    handleTabClick() {
      console.log(this.activeName)
      this.handleChange()
    }
  },
  computed: {
    //添加计算属性用来获取按钮禁用与否
    isButtonDisabled() {
      return this.selectedCateKeys.length !== 3
    },
    //获取选中的三级分类id
    cateId() {
      if (this.selectedCateKeys.length === 3) {
        return this.selectedCateKeys[this.selectedCateKeys.length - 1]
      }
      return null
    }
  }

添加参数

<!-- 添加参数或属性对话框 -->
<el-dialog :title="'添加'+titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
  <!-- 添加表单 -->
  <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px">
    <el-form-item :label="titleText" prop="attr_name">
      <el-input v-model="addForm.attr_name"></el-input>
    </el-form-item>
  </el-form>
  <span slot="footer" class="dialog-footer">
    <el-button @click="addDialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addParams">确 定</el-button>
  </span>
</el-dialog>

export default {
  data() {
    return {
      .......
      //控制添加参数.属性对话框的显示或隐藏
      addDialogVisible: false,
      //添加参数的表单数据对象
      addForm: {
        attr_name: ''
      },
      //添加表单验证规则
      addFormRules: {
        attr_name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
      }
    }
  },methods: {
    .......
    addParams() {
      //当用户点击对话框中的确定时,校验表单
      this.$refs.addFormRef.validate(async valid => {
        //校验不通过,return
        if (!valid) return
        //校验通过,发送请求完成添加参数或者属性
        const { data: res } = this.$http.post(`categories/${this.cateId}/attributes`,
          { 
            attr_name: this.addForm.attr_name, 
            attr_sel: this.activeName,
            attr_vals: "a,b,c" 
          }
        )

        console.log(res)
        if (res.meta.status !== 201) {
          return this.$message.error('添加' + this.titleText + '数据失败')
        }
        this.$message.success('添加' + this.titleText + '数据成功')
        this.addDialogVisible = false
        this.getCateList()
      })
    }
  }

编辑参数

<!-- 修改参数或属性对话框 -->
<el-dialog :title="'修改'+titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
  <!-- 添加表单 -->
  <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px">
    <el-form-item :label="titleText" prop="attr_name">
      <el-input v-model="editForm.attr_name"></el-input>
    </el-form-item>
  </el-form>
  <span slot="footer" class="dialog-footer">
    <el-button @click="editDialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="editParams">确 定</el-button>
  </span>
</el-dialog>

export default {
  data() {
    return {
      .......
      //控制修改参数.属性对话框的显示或隐藏
      editDialogVisible:false,
      //修改参数.属性对话框中的表单
      editForm:{
        attr_name:''
      },
      //修改表单的验证规则
      editFormRules:{
        attr_name:[
          { required: true, message: '请输入名称', trigger: 'blur' }
        ]
      }
    }
  },methods: {
    .......
    async showEditDialog(attr_id){
      //发起请求获取需要修改的那个参数数据
      const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes/${attr_id}`,
      {params:{ attr_sel:this.activeName }})
      if (res.meta.status !== 200) {
        return this.$message.error('获取参数数据失败')
      }
      this.editForm = res.data;
      //显示修改参数.属性对话框
      this.editDialogVisible = true;
    },
    editDialogClosed(){
      //当关闭修改参数.属性对话框时
      this.$refs.editFormRef.resetFields()
    },
    editParams(){
      //验证表单
      this.$refs.editFormRef.validate(async valid => {
        if(!valid) return;

        //发送请求完成修改
        const {data:res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`,
        {attr_name:this.editForm.attr_name,attr_sel:this.activeName})

        if (res.meta.status !== 200) {
          return this.$message.error('获取参数数据失败')
        }
        this.$message.success('修改' + this.titleText + '数据成功')
        this.editDialogVisible = false
        this.handleChange();
      })
    }
  }

删除参数

给两个删除按钮添加事件
<el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button>

添加对应的事件处理函数
async removeParams(attr_id){
  //根据id删除对应的参数或属性
  //弹窗提示用户是否要删除
  const confirmResult = await this.$confirm(
    '请问是否要删除该'+this.titleText,
    '删除提示',
    {
      confirmButtonText: '确认删除',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).catch(err => err)
  //如果用户点击确认,则confirmResult 为'confirm'
  //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'
  if (confirmResult != 'confirm') {
    return this.$message.info('已经取消删除')
  }

  //没有取消就是要删除,发送请求完成删除
  const {data:res} = await this.$http.delete(`categories/${this.cateId}/attributes/${attr_id}`)

  if (res.meta.status !== 200) {
    return this.$message.error('删除参数数据失败')
  }
  this.$message.success('删除' + this.titleText + '数据成功')
  this.handleChange()
}

商品列表

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <Break></Break>
    <!-- 卡片视图区域 -->
    <el-card>
      <!-- 搜索与添加区域 -->
      <el-row :gutter="20">
        <el-col :span="8">
          <el-input placeholder="请输入内容" clearable>
            <el-button slot="append" icon="el-icon-search"></el-button>
          </el-input>
        </el-col>
        <el-col :span="4">
          <el-button type="primary" @click="addGoods">添加商品</el-button>
        </el-col>
      </el-row>

      <!-- 用户列表区域 -->
      <el-table :data="goodsList" border>
        <el-table-column label="#" type="index"></el-table-column>
        <el-table-column
          label="商品名称"
          prop="goods_name"
          width="672px"
        ></el-table-column>
        <el-table-column
          label="商品价格(元)"
          prop="goods_price"
          width="100px"
        ></el-table-column>
        <el-table-column
          label="商品重量"
          prop="goods_weight"
          width="100px"
        ></el-table-column>
        <el-table-column
          label="创建时间"
          prop="add_time"
          width="150px"
        ></el-table-column>
        <el-table-column label="操作" width="150px">
          <template slot-scope="scope">
            <!-- 修改按钮 -->
            <el-button
              type="primary"
              icon="el-icon-edit"
              size="mini"
            ></el-button>
            <!-- 删除按钮 -->
            <el-button
              type="danger"
              icon="el-icon-delete"
              size="mini"
              @click="removeGoods(scope.row.id)"
            ></el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页器 -->
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="queryInfo.pagenum"
        :page-sizes="[5, 10, 20, 50]"
        :page-size="queryInfo.pagesize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
      ></el-pagination>
    </el-card>
  </div>
</template>

<script>
import goodAPI from "../../http/API/Good"
import Break from "../bread";
export default {
  components: {
    Break,
  },
  data() {
    return {
      queryInfo: {
        query: "",
        // 当前的页数
        pagenum: 1,
        // 当前每页显示多少条数据
        pagesize: 10,
      },
      goodsList: [],
      total: 0,
    };
  },

  methods: {
    // 监听 pagesize 改变的事件
    handleSizeChange(newSize) {
      this.queryInfo.pagesize = newSize;
      this.getGoodsList();
    },

    // 监听 页码值 改变的事件
    handleCurrentChange(newPage) {
      this.queryInfo.pagenum = newPage;
      this.getGoodsList();
    },

    //获取商品列表
    getGoodsList() {
      goodAPI.getGoodsList(this.queryInfo).then((res) => {
        console.log(res);
        this.goodsList = res.data.data.goods;
        this.total = res.data.data.total;
      });
    },
 
    //添加商品
    addGoods() {
      this.$router.push("/goods/addgoods");
    },
  },

  created() {
    this.getGoodsList();
  },

  mounted() {},
};
</script> 


<style scoped>

</style>

在mian.js添加过滤器:将时间戳转换为年月日

Vue.filter('dateFormat',function(originVal){
  const dt = new Date(originVal)
  const y = dt.getFullYear()
  const m = (dt.getMonth()+1+'').padStart(2,'0')
  const d = (dt.getDate()+'').padStart(2,'0')

  const hh = (dt.getHours()+'').padStart(2,'0')
  const mm = (dt.getMinutes()+'').padStart(2,'0')
  const ss = (dt.getSeconds()+'').padStart(2,'0')

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

实现删除商品

 //删除商品
    removeGoods(id) {
      this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          goodAPI.deleteGoods(id).then((res) => {
            console.log(res);
          });
        })
        .then(() => {
          this.$message({
            type: "success",
            message: "删除成功!",
          });
          this.getGoodsList();
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

添加商品:
点击添加跳转到另一个页面

//添加商品
    addGoods() {
      this.$router.push("/goods/addgoods");
    },

在添加组件中进行布局:

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <Break></Break>
    <!-- 卡片区域 -->
    <el-card>
      <!-- 提示区 -->
      <el-alert
        title="添加商品信息"
        type="info"
        center
        show-icon
        :closable="false"
      ></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>

      <!-- tab栏区域 -->
      <el-form
        :model="addForm"
        :rules="addrules"
        ref="addGoodsForm"
        label-width="100px"
        label-position="top"
      >
        <el-tabs
          v-model="activeIndex"
          :tab-position="'left'"
          @tab-click="tabClick"
          :before-leave="beforeTabLeave"
        >
          <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" type="number"></el-input>
            </el-form-item>
            <el-form-item label="商品重量" prop="goods_weight">
              <el-input v-model="addForm.goods_weight" type="number"></el-input>
            </el-form-item>
            <el-form-item label="商品数量" prop="goods_number">
              <el-input v-model="addForm.goods_number" type="number"></el-input>
            </el-form-item>
            <el-form-item label="商品分类" prop>
              <el-cascader
                expand-trigger="hover"
                v-model="addForm.goods_cat"
                :options="cateList"
                :props="cateProps"
                @change="handleChange"
              ></el-cascader>
            </el-form-item>
          </el-tab-pane>
          <el-tab-pane label="商品参数" name="1">
            <el-form-item
              :label="item.attr_name"
              v-for="item in manyTableData"
              :key="item.attr_id"
            >
              <!-- 复选框组 -->
              <el-checkbox-group v-model="item.attr_vals">
                <el-checkbox
                  :label="cb"
                  v-for="(cb, i) in item.attr_vals"
                  :key="i"
                ></el-checkbox>
              </el-checkbox-group>
            </el-form-item>
          </el-tab-pane>
          <el-tab-pane label="商品属性" name="2">
            <el-form-item
              :label="item.attr_name"
              v-for="item in onlyTableData"
              :key="item.attr_id"
            >
              <el-input v-model="item.attr_vals"></el-input>
            </el-form-item>
          </el-tab-pane>
          <el-tab-pane label="商品图片" name="3">
            <!-- 商品图片上传
  action:指定图片上传api接口
  :on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览
  :on-remove : 当用户点击图片右上角的X号时触发执行
  :on-success:当用户点击上传图片并成功上传时触发
  list-type :设置预览图片的方式
  :headers :设置上传图片的请求头 -->
            <el-upload
              action="http://127.0.0.1:8888/api/private/v1/upload"
              :on-preview="handlePreview"
              :on-remove="handleRemove"
              :on-success="handleSuccess"
              list-type="picture"
              :headers="headerObj"
            >
              <el-button size="small" type="primary">点击上传</el-button>
            </el-upload>
          </el-tab-pane>
          <el-tab-pane label="商品内容" name="4">
            <!-- 富文本编辑器组件 -->
            <quill-editor v-model="addForm.goods_introduce"></quill-editor>
            <!-- 添加商品按钮 -->
            <el-button type="primary" class="btnAdd" @click="handAdd"
              >添加商品</el-button
            >
          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>
  </div>
</template>

<script>
import goodAPI from "../../http/API/Good"
import Break from "../bread";
export default {
  components: {
    Break,
  },
  data() {
    return {
      activeIndex: "0",
      addForm: {
        goods_name: "",
        goods_price: 0,
        goods_weight: 0,
        goods_number: 0,
        //商品所属分类数组
        goods_cat: [],
        pics: [],
        goods_introduce: "",
      },
      addrules: {
        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: [],
      cateProps: {
        label: "cat_name",
        value: "cat_id",
        children: "children",
      },
      //动态参数列表数据
      manyTableData: [],
      //静态属性列表数据
      onlyTableData: [],

      //图片上传组件的headers请求头对象
      headerObj: { Authorization: window.sessionStorage.getItem("token") },
      //保存预览图片的url地址
      previewPath: "",
      //控制预览图片对话框的显示和隐藏
      previewVisible: false,
    };
  },

  methods: {
    getGoodsList() {
      this.$http.get("categories").then((res) => {
        this.cateList = res.data.data;
      });
    },
    //级联选择器选中项变化
    handleChange() {
      if (this.addForm.goods_cat.length !== 3) {
        this.addForm.goods_cat = [];
      }
    },
    beforeTabLeave(activeName, oldActiveName) {
      if (oldActiveName === "0" && this.addForm.goods_cat.length !== 3) {
        this.$message.error("请选择商品分类");
        return false;
      }
    },
    //商品参数
    tabClick() {
      if (this.activeIndex === "1") {
        goodAPI.getParamsList({
            params: { sel: "many" },
          })
          .then((res) => {
            res.data.data.forEach((item) => {
              item.attr_vals =
                item.attr_vals.length === 0 ? [] : item.attr_vals.split(" ");
            });
            this.manyTableData = res.data.data;
          });
      } else if (this.activeIndex === "2") {
        goodAPI.getParamsList({
            params: { sel: "only" },
          })
          .then((res) => {
            console.log(res);
            this.onlyTableData = res.data.data;
          });
      }
    },
    handlePreview(file) {
      this.previewPath = file.response.data.url;

      this.previewVisible = true;
    },
    handleRemove(file) {
      const filePath = file.response.data.tmp_path;

      const index = this.addForm.pics.findIndex(
        (item) => item.pic === filePath
      );
      this.addForm.pics.splice(index, 1);
    },
    handleSuccess(response) {
      this.addForm.pics.push({ pic: response.data.tmp_path });
    },
    handAdd() {
      this.$refs.addGoodsForm.validate(async (valid) => {
        if (!valid) {
          return this.$message.error("请填写必要的选项!");
        }
        // console.log(this.addForm);
        let form = this.deepClone(this.addForm);
        form.goods_cat = form.goods_cat.join(",");
        form.attrs = [];
        this.manyTableData.forEach((item) => {
          form.attrs.push({
            attr_id: item.attr_id,
            attr_value: item.attr_vals.join(","),
          });
        });
        this.onlyTableData.forEach((item) => {
          form.attrs.push({
            attr_id: item.attr_id,
            attr_value: item.attr_vals,
          });
        });
        console.log(form);
        const { data: res } = await goodAPI.addGoods(form);
        // console.log(res);
        if (res.meta.status !== 201) return this.$message.error(res.meta.msg);
        this.$message.success("添加商品成功!");
        this.$router.push("/goods");
      });
    },
    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;
    },
  },

  created() {
    this.getGoodsList();
  },
  computed: {
    cateId() {
      if (this.addForm.goods_cat.length === 3) {
        return this.addForm.goods_cat[2];
      }
      return null;
    },
  },

  mounted() {},
};
</script> 


<style scoped>
.el-card {
  margin-top: 15px;
}
.el-steps {
  margin: 15px 0;
}
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  line-height: 178px;
  text-align: center;
}
.avatar {
  width: 178px;
  height: 178px;
  display: block;
}
.ql-editor {
  min-height: 300px;
}
.btnAdd {
  margin-top: 15px;
}
</style>

完成图片上传
使用upload组件完成图片上传
在element.js中引入upload组件,并注册
因为upload组件进行图片上传的时候并不是使用axios发送请求
所以,我们需要手动为上传图片的请求添加token,即为upload组件添加headers属性

//在页面中添加upload组件,并设置对应的事件和属性
<el-tab-pane label="商品图片" name="3">
  <!-- 商品图片上传
  action:指定图片上传api接口
  :on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览
  :on-remove : 当用户点击图片右上角的X号时触发执行
  :on-success:当用户点击上传图片并成功上传时触发
  list-type :设置预览图片的方式
  :headers :设置上传图片的请求头 -->
  <el-upload :action="uploadURL" :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" list-type="picture" :headers="headerObj">
    <el-button size="small" type="primary">点击上传</el-button>
  </el-upload>
</el-tab-pane>
//在el-card卡片视图下面添加对话框用来预览图片
<!-- 预览图片对话框 -->
<el-dialog title="图片预览" :visible.sync="previewVisible" width="50%">
  <img :src="previewPath" class="previewImg" />
</el-dialog>

//在data中添加数据
data(){
  return {
    ......
    //添加商品的表单数据对象
    addForm: {
      goods_name: '',
      goods_price: 0,
      goods_weight: 0,
      goods_number: 0,
      goods_cat: [],
      //上传图片数组
      pics: []
    },
    //上传图片的url地址
    uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload',
    //图片上传组件的headers请求头对象
    headerObj: { Authorization: window.sessionStorage.getItem('token') },
    //保存预览图片的url地址
    previewPath: '',
    //控制预览图片对话框的显示和隐藏
    previewVisible:false
  }
},
//在methods中添加事件处理函数
methods:{
  .......
  handlePreview(file) {
    //当用户点击图片进行预览时执行,处理图片预览
    //形参file就是用户预览的那个文件
    this.previewPath = file.response.data.url
    //显示预览图片对话框
    this.previewVisible = true
  },
  handleRemove(file) {
    //当用户点击X号删除时执行
    //形参file就是用户点击删除的文件
    //获取用户点击删除的那个图片的临时路径
    const filePath = file.response.data.tmp_path
    //使用findIndex来查找符合条件的索引
    const index = this.addForm.pics.findIndex(item => item.pic === filePath)
    //移除索引对应的图片
    this.addForm.pics.splice(index, 1)
  },
  handleSuccess(response) {
    this.addForm.pics.push({ pic: response.data.tmp_path })
  }
}

使用富文本插件
引入并注册vue-quill-editor

//导入vue-quill-editor(富文本编辑器)
import VueQuillEditor from 'vue-quill-editor'
//导入vue-quill-editor的样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
......
//全局注册组件
Vue.component('tree-table', TreeTable)
//全局注册富文本组件
Vue.use(VueQuillEditor)
<!-- 富文本编辑器组件 -->
<el-tab-pane label="商品内容" name="4">
  <!-- 富文本编辑器组件 -->
  <quill-editor v-model="addForm.goods_introduce"></quill-editor>
  <!-- 添加商品按钮 -->
  <el-button type="primary" class="btnAdd">添加商品</el-button>
</el-tab-pane>

//在数据中添加goods_introduce
//添加商品的表单数据对象
addForm: {
  goods_name: '',
  goods_price: 0,
  goods_weight: 0,
  goods_number: 0,
  goods_cat: [],
  //上传图片数组
  pics: [],
  //商品的详情介绍
  goods_introduce:''
}
//在global.css样式中添加富文本编辑器的最小高度
.ql-editor{
    min-height: 300px;
}
//给添加商品按钮添加间距
.btnAdd{
  margin-top:15px;
}

添加商品:

//打开Add.vue,导入lodash
<script>
//官方推荐将lodash导入为_
import _ from 'lodash'

//给添加商品按钮绑定点击事件
<!-- 添加商品按钮 -->
<el-button type="primary" class="btnAdd" @click="add">添加商品</el-button>
//编写点击事件完成商品添加
add(){
  this.$refs.addFormRef.validate(async valid=>{
    if(!valid) return this.$message.error("请填写必要的表单项!")

    //将addForm进行深拷贝,避免goods_cat数组转换字符串之后导致级联选择器报错
    const form = _.cloneDeep(this.addForm)
    //将goods_cat从数组转换为"1,2,3"字符串形式
    form.goods_cat = form.goods_cat.join(",")
    //处理attrs数组,数组中需要包含商品的动态参数和静态属性
    //将manyTableData(动态参数)处理添加到attrs
    this.manyTableData.forEach(item=>{
      form.attrs.push({ attr_id:item.attr_id, attr_value:item.attr_vals.join(" ") }) 
    })
    //将onlyTableData(静态属性)处理添加到attrs
    this.onlyTableData.forEach(item=>{
      form.attrs.push({ attr_id:item.attr_id, attr_value:item.attr_vals }) 
    })

    //发送请求完成商品的添加,商品名称必须是唯一的
    const {data:res} = await this.$http.post('goods',form)
    if(res.meta.status !== 201){
      return this.$message.error('添加商品失败')
    }
    this.$message.success('添加商品成功')
    //编程式导航跳转到商品列表
    this.$router.push('/goods')
  })
}
</script>

订单列表
实现数据展示及分页

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <Break></Break>
    <!-- 卡片视图区域 -->
    <el-card class="box-card">
      <el-row>
        <el-col :span="8">
          <el-input placeholder="请输入内容">
            <el-button slot="append" icon="el-icon-search"></el-button>
          </el-input>
        </el-col>
      </el-row>

      <!-- 订单列表数据 -->
      <el-table :data="orderList" border stripe class="tableList">
        <el-table-column type="index"></el-table-column>
        <el-table-column label="订单编号" prop="order_number"></el-table-column>
        <el-table-column label="订单价格" prop="order_price"></el-table-column>
        <el-table-column label="是否付款" prop="pay_status">
          <template slot-scope="scope">
            <el-tag type="success" v-if="scope.row.pay_status === '1'"
              >已付款</el-tag
            >
            <el-tag type="danger" v-else>未付款</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="是否发货" prop="is_send">
          <template slot-scope="scope">
            <template>
              {{ scope.row.is_send }}
            </template>
          </template>
        </el-table-column>
        <el-table-column label="下单时间" prop="create_time">
          <template slot-scope="scope">
            {{ scope.row.create_time | dataForm }}
          </template>
        </el-table-column>
        <el-table-column label="操作">
          <template>
            <el-button
              size="mini"
              type="primary"
              icon="el-icon-edit"
              @click="showEdit"
            ></el-button>
            <el-button
              size="mini"
              type="success"
              icon="el-icon-location"
              @click="showProgress"
            ></el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页区域 -->
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="queryInfo.pagenum"
        :page-sizes="[5, 10, 15]"
        :page-size="queryInfo.pagesize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
      >
      </el-pagination>
    </el-card>

    <!-- 修改地址的对话框 -->
    <el-dialog
      title="修改地址"
      :visible.sync="editDialogVisible"
      width="50%"
      @close="editDialogClosed"
    >
      <el-form
        :model="EditRuleForm"
        :rules="editFormRules"
        ref="addressFormRef"
        label-width="100px"
      >
        <el-form-item label="省市区/县" prop="address1">
          <el-cascader
            :options="cityData"
            v-model="EditRuleForm.address1"
          ></el-cascader>
        </el-form-item>
        <el-form-item label="详细地址" prop="address2">
          <el-input v-model="EditRuleForm.address2"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="editDialogVisible = false"
          >确 定</el-button
        >
      </span>
    </el-dialog>

    <!-- 展示物流进度的对话框 -->
    <el-dialog title="物流进度" :visible.sync="progressVisible" width="50%">
      <!-- 时间线 -->
      <el-timeline>
        <el-timeline-item
          v-for="(activity, index) in progressInfo"
          :key="index"
          :timestamp="activity.time"
        >
          {{ activity.context }}
        </el-timeline-item>
      </el-timeline>
    </el-dialog>
  </div>
</template>

<script>
import cityData from "./citydata.js";
import Break from "../bread";
import ordersAPI from "../../http/API/Order"
export default {
  components: {
    Break,
  },
  data() {
    return {
      queryInfo: {
        query: "",
        pagenum: 1,
        pagesize: 10,
      },
      total: 0,
      orderList: [],
      editDialogVisible: false,
      EditRuleForm: {
        address1: [],
        address2: "",
      },
      editFormRules: {
        address1: [
          { required: true, message: "请选择省市区县", trigger: "blur" },
        ],
        address2: [
          { required: true, message: "请填写详细地址", trigger: "blur" },
        ],
      },
      cityData,
      progressVisible: false,
      progressInfo: [],
    };
  },
  created() {
    this.getOrderList();
  },
  methods: {
    async getOrderList() {
      let { data: res } = await ordersAPI.getOrder(this.queryInfo);
      // console.log(res);
      if (res.meta.status !== 200) {
        return this.$message.error("获取订单列表失败!");
      }

      console.log(res);
      this.total = res.data.total;
      this.orderList = res.data.goods;
    },
    handleSizeChange(newSize) {
      this.queryInfo.pagesize = newSize;
      this.getOrderList();
    },
    handleCurrentChange(newPage) {
      this.queryInfo.pagenum = newPage;
      this.getOrderList();
    },
    showEdit() {
      this.editDialogVisible = true;
    },
    editDialogClosed() {
      this.$refs.addressFormRef.resetFields();
    },
    async showProgress() {
      let { data: res } = await ordersAPI.getOrder();

      if (res.meta.status !== 200) {
        return this.$message.error("获取物流进度失败!");
      }

      this.progressInfo = res.data;

      this.progressVisible = true;
      console.log(this.progressInfo);
    },
  },
};
</script>

<style  scoped>
.el-cascader {
  width: 100%;
}
.box-card{
    margin-top: 20px;
}
.tableList{
    margin-top: 20px;
}
</style>

数据统计
导入ECharts并使用

<template>
  <div>
    <!-- 面包屑导航区域 -->
    <Break></Break>
    <el-card>
      <div id="myChart" :style="{ width: '700px', height: '400px' }"></div>
    </el-card>
  </div>
</template>

<script>
import reoprtsAPI from "../../http/API/reports"
import Break from "../bread";
import _ from "lodash";
export default {
  components: {
    Break,
  },
  data() {
    return {
      options: {
        title: {
          text: "用户来源",
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "cross",
            label: {
              backgroundColor: "#E9EEF3",
            },
          },
        },
        grid: {
          left: "3%",
          right: "4%",
          bottom: "3%",
          containLabel: true,
        },
        xAxis: [
          {
            boundaryGap: false,
          },
        ],
        yAxis: [
          {
            type: "value",
          },
        ],
      },
    };
  },

  mounted() {
    // 基于准备好的dom,初始化echarts实例
    let myChart = this.$echarts.init(document.getElementById("myChart"));
    reoprtsAPI.getreports().then((res) => {
      this.reportsList = res.data.data;
      //准备数据和配置项
      const result = _.merge(res.data.data, this.options);
      // 展示数据
      myChart.setOption(result);
    });
  },
  methods: {},
};
</script> 


<style scoped>
.el-card {
  margin-top: 15px;
}
</style>

完成以后进行项目优化
生产打包报告,根据报告优化项目
第三方库启用cdn
element-ui组件按需引入
路由懒加载
首页内容定制
添加进度条

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import "./assets/style.css";
import "./assets/fonts/iconfont.css";
import axios from 'axios'
import './plugins/element.js'
// import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import * as echarts from 'echarts';
import TreeTable from 'vue-table-with-tree-grid';
import VueQuillEditor from 'vue-quill-editor';
axios.defaults.baseURL = 'https://www.liulongbin.top:8888/api/private/v1/'
axios.interceptors.request.use((config) => {
    //当进入request拦截器,表示发送了请求,我们就开启进度条
    NProgress.start()
    //为请求头对象,添加token验证的Authorization字段
    config.headers.Authorization = localStorage.getItem('token');
    //必须返回config
    return config
});

//在response拦截器中,隐藏进度条
axios.interceptors.response.use(config => {
    //当进入response拦截器,表示请求已经结束,我们就结束进度条
    NProgress.done()
    return config
})

执行build
打开Babel.config.js

生产打包报告
修改webpack的默认配置

```javascript
// vue.config.js
module.exports = {
    publicPath: "./", //部署应用包时的基本相对路径地址
    chainWebpack: confing => {
        //修改入口文件
        //when判断环境条件,配置
        //发布模式
        confing.when(process.env.NODE_ENV === "production", confing => {
            //entry找到当前默认的打包入口, 调用clear清空默认打包入口, 使用add添加新的打包入口
            //add方法后面参数为新的入口文件 地址
            confing.entry('app').clear().add('./src/main-prod.js');
            //在打包的时候首先检查window上是否有下列组件(CDN提前挂载),如果有的话就不在重新import
            confing.set('externals', {
                vue: 'Vue',
                'vue-router': 'VueRouter',
                axios: 'axios',
                lodash: '_',
                echarts: 'echarts',
                nprogress: 'NProgress',
                'vue-quill-editor': 'VueQuillEditor'
            });
            //插件
            confing.plugin('html').tap(args => {
                args[0].isProd = true;
                return args
            })
        });
        // 开发模式
        confing.when(process.env.NODE_ENV === "development", confing => {
            confing.entry('app').clear().add('./src/main-dev.js');
            confing.plugin('html').tap(args => {
                //判断是否为发布模式手动添加属性值为是否是发布模式
                args[0].isProd = false;
                return args
            })
        });
    }
}

加载外部cdn

module.exports = {
    chainWebpack:config=>{
        //发布模式
        config.when(process.env.NODE_ENV === 'production',config=>{
            //entry找到默认的打包入口,调用clear则是删除默认的打包入口
            //add添加新的打包入口
            config.entry('app').clear().add('./src/main-prod.js')

            //使用externals设置排除项
            config.set('externals',{
                vue:'Vue',
                'vue-router':'VueRouter',
                axios:'axios',
                lodash:'_',
                echarts:'echarts',
                nprogress:'NProgress',
                'vue-quill-editor':'VueQuillEditor'
            })
        })
        //开发模式
        config.when(process.env.NODE_ENV === 'development',config=>{
            config.entry('app').clear().add('./src/main-dev.js')
        })
    }
}

然后打开public/index.html添加外部cdn引入代码

<!DOCTYPE html>
<html lang="">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" href="favicon.ico">
    <title>电商管理系统</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.core.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.snow.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.bubble.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.1/theme-chalk/index.css">
    <script src="https://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/vue-router/3.2.0/vue-router.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.21.1/axios.min.js"></script>
    <script src="https://cdn.staticfile.org/lodash.js/4.17.11/lodash.min.js"></script>
    <script src="https://cdn.staticfile.org/echarts/5.0.2/echarts.min.js"></script>
    <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
    <script src="https://cdn.staticfile.org/quill/1.3.4/quill.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.4/dist/vue-quill-editor.js"></script>
    <script src="https://cdn.staticfile.org/element-ui/2.15.1/index.js"></script>
    <link href="css/goods.20d11ae6.css" rel="prefetch">
    <link href="css/login_home_welcome.f172da36.css" rel="prefetch">
    <link href="css/order.7d547f74.css" rel="prefetch">
    <link href="css/power.9d8f660a.css" rel="prefetch">
    <link href="css/report.d5a63329.css" rel="prefetch">
    <link href="css/user.feb292b3.css" rel="prefetch">
    <link href="js/goods.e452d00b.js" rel="prefetch">
    <link href="js/login_home_welcome.bb7e7edd.js" rel="prefetch">
    <link href="js/order.6115a44b.js" rel="prefetch">
    <link href="js/power.d3b71a1e.js" rel="prefetch">
    <link href="js/report.0e8de479.js" rel="prefetch">
    <link href="js/user.0bb9f204.js" rel="prefetch">
    <link href="css/app.599ad7ef.css" rel="preload" as="style">
    <link href="css/chunk-vendors.c470e980.css" rel="preload" as="style">
    <link href="js/app.4942fcdb.js" rel="preload" as="script">
    <link href="js/chunk-vendors.24e8034f.js" rel="preload" as="script">
    <link href="css/chunk-vendors.c470e980.css" rel="stylesheet">
    <link href="css/app.599ad7ef.css" rel="stylesheet">
</head>

<body><noscript><strong>We're sorry but vue_shop doesn't work properly without JavaScript enabled. Please enable it to
            continue.</strong></noscript>
    <div id="app"></div>
    <script src="js/chunk-vendors.24e8034f.js"></script>
    <script src="js/app.4942fcdb.js"></script>
</body>

</html>

路由懒加载

const Login = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/Login.vue')
const Home = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/Home.vue')
const Welcome = () => import( /* webpackChunkName:"login_home_welcome" */ '../components/Welcome.vue')
const Users = () => import( /* webpackChunkName:"user" */ '../components/User/User.vue')
const Rights = () => import( /* webpackChunkName:"power" */ '../components/Role/rights.vue')
const Roles = () => import( /* webpackChunkName:"power" */ '../components/Role/roles.vue')
const Cate = () => import( /* webpackChunkName:"goods" */ '../components/Good/cate.vue')
const Params = () => import( /* webpackChunkName:"goods" */ '../components/Good/params.vue')
const GoodList = () => import( /* webpackChunkName:"goods" */ '../components/Good/goods.vue')
const GoodAdd = () => import( /* webpackChunkName:"goods" */ '../components/Good/addgoods.vue')
const Order = () => import( /* webpackChunkName:"order" */ '../components/Orders/orders.vue')
const Report = () => import( /* webpackChunkName:"report" */ '../components/Reports/reports.vue')
上一篇:Mybatis进阶


下一篇:python中mysql更新字段中传参