后台管理系统项目

2023-10-31

1.项目名称:后台管理

2.技术栈:vue全家桶,element-ui,axios,less,eachers

3.项目亮点:性能优化;百万级项目;新旧系统更迭;权限把控

项目开发流程:

1.安装vue脚手架

2.vue create "项目名称"

3.根据自己的需要进行配置

 4.下载完成之后,不需要的文件全部删掉,引入需要的字体图标,公共样式,img等

// 导入公共重置样式
import "@/assets/css/reset.css";
// 导入公共样式
import "@/assets/css/base.css";
// 导入字体图标
import "@/assets/fonts/iconfont.css";
// 导入element依赖
import ElementUI from "element-ui";
// 导入element css样式
import "element-ui/lib/theme-chalk/index.css";

 5.根据原型图,配置需要的路由模块,首先需要做的就是登录页面

登录板块:

1.配置路由,采用路由懒加载模式,而且需要重定向,默认进来就是登录页面

// 一定要重定向 切记 而且还要给app.vue加出口
  { path: "/", redirect: "/login" },
  // 配置登录页面,配置完成之后,给重定向,就是你一进来要访问的页面
  {
    path: "/login",
    name: "",
    component: () => import("../views/Login.vue"),
  },

 2.路由配置完成之后,排版登录页面,开始布局,样式等

1)页面架构,form表单来布局,实现数据双向绑定

<template>
  <div class="loginbox">
    <div class="smallbox">
      <h3>系统登录</h3>
      <el-form ref="form" label-width="0px" size="small" :model="loginform" :rules="checkObj">
        <el-form-item prop="account">
          <!-- label就是前面提示文字 size就是改变表单输入框大小 里面三个值-->
          <el-input type="text" v-model.trim="loginform.account" placeholder="请输入用户名">
            <i slot="prefix" class="iconfont icon-yonghufill"></i>
            <!-- prefix是在前面添加  suffix是在尾部添加字体图标-->
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <!-- 动态绑定文本还是密码 三元表达式 真开眼镜就是text 闭眼就是password-->
          <el-input :type="pwdShow ? 'text': 'password'" v-model.trim="loginform.password" placeholder="请输入密码">
            <!-- 前置图标 -->
            <i slot="prefix" class="iconfont icon-mima"></i>
            <!-- 后置图标 并为i绑定点击事件 -->
            <!-- 三元表达式 判断睁眼睛还是闭眼睛 -->
            <i slot="suffix"
              :class="[ 'iconfont', pwdShow ? 'icon-yanjing_huaban1' : 'icon-05wangluoanquan_biyanjing']"
              @click="pwdShow = ! pwdShow"></i>
          </el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="login" @keyup="keyDown(e)">立即登录</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

 2)根据element-ui去找对应的form表示组件,此时登录页面,需要做验证处理

data() {
    return {
      // 这里数据根据接口文档
      // 实际开发的时候,可以根据需求文档来起名字
      loginform: {
        account: "admin",
        password: "666",
      },
      // 设置一个开关开控制点击小眼睛
      pwdShow: false,
      // 表单验证 必须要挂一个prop属性
      checkObj: {
        account: [
          // { required: true, message: '请输入正确的账号', trigger: 'blur' },
          { required: true, message: '账号不能为空', trigger: 'blur' },
          // { type: 'number', message: '账号必须为数字值' },
          { min: 3, max: 8, message: '长度在 3 到 8 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '密码不能为空', trigger: 'blur' },
          // { required: true, message: '请输入正确的密码', trigger: 'blur' },
          { min: 3, max: 8, message: '长度在 3 到 8 个字符', trigger: 'blur' }
        ],
      }
    }
  },

3)页面排版完成之后,此时需要发请求,登录账户后台,拿到后端给的api接口文档,首先需要进行二次封装axios,封装企业级axios请求,封装api接口,封装local

封装axios-->api-->local

1.在src根目录下新建2个文件夹,utils和api文件夹

 2.在utils文件夹下新建request.js和local.js

3.首先在request.js文件里面配置,封装axios

// 引入axios
import axios from "axios";
// 引入qs qs是转化成字符串
import qs from "qs";
// 需要导入自己封装的local.js
import local from "./local.js";
// 放url的公共的地址
axios.defaults.baseURL = "http://119.23.246.178:80/";
// 封装axios方法 企业级封装
// 主要是解决url地址问题 方法method 数据参数data
// 这三个参数如果没有给值 就给一个默认值
// 封装任何方法,必须传的值必须放第一个位置,如果可以没有值的,可以放后面
// axios是基于promise await async
// 响应拦截器 返回的数据要经过这里拦截,我们只需要里面的data数据就行
// Add a response interceptor
axios.interceptors.response.use(
  function (response) {
    // Do something with response data
    // 就是对response的数据拦截
    // 我们只需要data的数据,就只有data数据
    return response.data;
    // response只需要拿到data的值
  },
  function (error) {
    // Do something with response error
    return Promise.reject(error);
  }
);
// 添加请求拦截器 配置所有请求都有请求
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    // 对所有的请求进行拦截
    // 看一下当前本地存储中有没有token
    // config 配置 headers 请求头
    if (local.iskey("sell_token")) {
      config.headers.Authorization = local.get("sell_token");
      return config;
    } else {
      return config;
    }
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 第一层封装解决了method 对应参数的问题
let request = function (url, method = "get", data = {}) {
  // 返回的是一个promise 所以要return出去
  return axios({
    url,
    method,
    // 三元表达式 判断是post还是get请求
    // 因为post传参用的是data get用的是params
    // 主要是解决data和params的问题
    // data一般传的是对象{account:"admin",password:"666"},后台不需要这样的的数据
    // 而后台需要的数据是account=admin&password=666 一个字符串的形式
    // 实际开发中要看后台实际需要的形式,如果是对象不需要转,如果是字符串就需要转
    // 此时需要一个依赖,qs就是讲对象模式转化成字符串格式
    // 只对post数据有要求,对get数据没有要求你
    data: method == "post" ? qs.stringify(data) : "",
    params: method == "get" ? data : "",
  });
};
// 把封装的方法暴露出去
// 这是第二层 是解决post和get的问题
// 向外抛出get和post
export default {
  // 封装post方法
  post(url, data) {
    return request(url, "post", data);
  },
  // 封装get方法
  get(url, data) {
    return request(url, "get", data);
  },
};

 注意:封装axios,主要是解决三个问题,url问题,method问题,get和post问题,包括需要解决响应拦截和请求拦截

4.此时需要在api文件夹下新建项目对应的模块api接口,先引入自己封装的request.js文件

注意:拿用户列表模块为例,封装的api接口,后面的直接照着复制即可

// 工程化开发
// 跟用户有关的url 此时封装的url api是后台请求的接口
// 发异步请求
import request from "../utils/request";
// 调用这个方法只需要传一个data参数
// 这个是登录的url
// export const api_login = function (data) {
//    return request.post("/users/checkLogin",data);
// }
// 简写 如果只有一个返回值 省略{} 登录
export const api_login = (data) => request.post("/users/checkLogin", data);
// 查询用户列表
export const api_userList = (data) => request.get("/users/list", data);
// 添加账号
export const api_add = (data) => request.post("/users/add", data);
// 删除账号
export const api_del = (data) => request.get("/users/del", data);
// 批量删除账号
export const api_batchdel = (data) => request.get("/users/batchdel", data);
// 修改账号
export const api_edit = (data) => request.post("/users/edit", data);
// 修改密码
export const api_editpwd = (data) => request.post("/users/editpwd", data);
// 检查旧密码是否正确
export const api_checkoldpwd = (data) => request.get("/users/checkoldpwd", data);
// 获取个人账号信息
export const api_info = (data) => request.get("/users/info", data);
// 修改用户图像
export const api_avataredit = (data) => request.get("/users/avataredit", data);

5.封装localStorage,存储token值,在local.js中编辑代码

// 封装本地存储的方法
let loaclObj = {
  // getItem 需要返回值
  get(key) {
    // 携带参数
    return window.localStorage.getItem(key)
  },
  // setItem 设置不需要返回
  set(key,value) {
    window.localStorage.setItem(key,value)
  },
  // removeItem
  remove(key) {
    window.localStorage.removeItem(key)
  },
  // clear 清空
  clear() {
    window.localStorage.clear()
  },
  // 判断是否有对应的名称值
  iskey(key) {
    return window.localStorage.getItem(key) ? true : false
  }
}
//一定要暴露暴露出去 本身就是一个对象
export default loaclObj;

注意:此时你需要再main.js中进行引入和全局挂载,不然会报错

// 导入自己封装的loca
import Local from "./utils/local.js";
// 挂在在全局 原型对象
Vue.prototype.$local = Local;

6.此时再回到登录页面,开始发数据请求

1)按需导入自己封装好的axios,如果还有其他请求,后面逗号隔开,继续写即可

// 按需导入
import { api_login } from "../api/user.js"

2)发请求数据,此处是登录页面,登录成功时,需要进行设置token值,然后在request.js中引入local.js,进行验证token值

methods: {
    // 请求数据
    login() {
      // 当点击登录按钮的时候,触发此时获取表单得到验证信息
      // 怎么获取dom元素
      this.$refs.form.validate(async (val) => {
        // asunc这里是一个方法
        if (val) {
          // 验证成功就要发送请求
          let res = await api_login(this.loginform)
          // //console.log(res);
          if (res.code == 0) {
            // 登录到首页
            // 简写
            this.$message.success("欢迎您,登录成功")
            // 设置token值  自己封装的set sell_token自己起的名字
            this.$local.set("sell_token", res.token)
            this.$router.push("/home")
          } else {
            // vue/element专门提供了一个提示框
            this.$message.error("登录失败")
          }
          // validate是一个函数
          // val 返回的是一个true和false
          // 如果验证成功跳转到首页 否则返回结束函数
          // this.$router.push("/home")
          // 因为只有router带push和replace和go
          // 如果有参数
          // this.$router.push({
          //   path: "/home",
          //   query:{}
          // })
        } else {
          return false
        }
      })
    },

 7.登录页面还没有完成,此时添加键盘事件,enter也可以实现登录效果

keyDown(e) {
      if (e.keyCode == 13 || e.keyCode == 0) {
        this.login()
      }
    },

//和生命周期和methods是同级同级
mounted() {
    // 绑定监听事件
    window.addEventListener("keydown", this.keyDown);
  },
  // 生命周期和methos是同级同级
  destroyed() {
    // 销毁事件
    window.removeEventListener("keydown", this.keyDown, false);
  },

最后:登录页面完成,继续下一个模块制作

主页面模块:

1.先封装组件,为了layout页面代码看起来不那么冗余,封装左侧,头部组件,然后在layout组件进行导入使用,在components下新建2个文件夹,文件名字以index.vue即可

 2.对照原型图,去element-ui里面找对应的模块,做一些代码的修改,根据自己的需要设置背景样式等

<template>
  <el-menu :default-active="$route.path" class="el-menu-vertical-demo" background-color="#304156" text-color="#fff"
    active-text-color="#1890FF" unique-opened router>
    <!-- 后台首页 -->
    <el-menu-item index="/home">
      <i class="iconfont icon-31shouyexuanzhong"></i>
      <span slot="title">后台首页</span>
    </el-menu-item>
    <!-- 订单管理 -->
    <el-menu-item index="/order/list">
      <i class="iconfont icon-dingdan1"></i>
      <span slot="title">订单管理</span>
    </el-menu-item>
    <!-- 商品管理 -->
    <el-submenu index="/goods">
      <template slot="title" class="icon">
        <i class="iconfont icon-shangpin"></i>
        <span>商品管理</span>
      </template>
      <el-menu-item index="/goods/list">商品列表</el-menu-item>
      <el-menu-item index="/goods/add">商品添加</el-menu-item>
      <el-menu-item index="/goods/type">商品分类</el-menu-item>
    </el-submenu>
    <!-- 店铺管理 -->
    <el-menu-item index="/shop">
      <i class="iconfont icon-dianpu"></i>
      <span slot="title">店铺管理</span>
    </el-menu-item>
    <!-- 账号管理 -->
    <el-submenu index="/users">
      <template slot="title">
        <i class="iconfont icon-yonghufill"></i>
        <span>账号管理</span>
      </template>
      <el-menu-item index="/users/list">账号列表</el-menu-item>
      <el-menu-item index="/users/add">账号添加</el-menu-item>
      <el-menu-item index="/users/pwd">修改密码</el-menu-item>
      <el-menu-item index="/users/personal">个人中心</el-menu-item>
      <el-menu-item index="/users/pdf">查看合同</el-menu-item>
    </el-submenu>
    <!-- 销售统计 -->
    <el-submenu index="/census">
      <template slot="title">
        <i class="iconfont icon-tongjifenxi-changguitongji"></i>
        <span>销售统计</span>
      </template>
      <el-menu-item index="/census/goodscensus">商品统计</el-menu-item>
      <el-menu-item index="/census/ordercensus">订单统计</el-menu-item>
    </el-submenu>
  </el-menu>
</template>
<script>
export default {
}
</script>

<style lang="less" scoped>
.el-menu {
  border-right: 0px;

  .el-menu-item {
    i {
      padding: 10px;
      color: #fff;
    }
  }
}
.el-submenu {
  .el-submenu__title {
    i {
      padding: 10px;
      color: #fff;
    }
  }
  .el-menu-item {
    padding: 0 55px !important;
  }
}
</style>

3.此时需要配置每一个路由模块,实现页面之间的相互切换,在router,index.js进行配置路由,展示一个模块为例:

// 全局引入 router index.js引入一个layout即可
import Layout from "../views/Layout.vue";
 // 订单管理
  {
    path: "/order",
    name: "",
    meta: { title: "订单管理" },
    component: Layout,
    children: [
      // 订单重定向
      { path: "/", redirect: "list" },
      // 订单列表
      {
        path: "list",
        meta: { title: "订单列表" },
        component: () => import("../views/order/OrderList.vue"),
      },
      // 订单详情
      {
        path: "detali",
        meta: { title: "订单详情" },
        component: () => import("../views/order/OrderDetali.vue"),
      },
    ],
  },

4.需要在src下的views下新建对应的文件夹,以及相应的vue文件,不然页面报错

5.路由配置完成之后,在自己封装左侧组件里面,设置对应的路由切换链接 

6.封装头部组件,根据原型图,找对应的面包屑

    <!-- 头部的左侧 -->
    <div class="headerLeft">
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <!-- to是跳转路由 -->
        <!-- 这边面包屑需要点击跳转到对应的链接 需要实时监听路由变化route -->
        <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item :to="{ path: item.path }" v-for="(item, index) in routeList" :key="index">{{ item.title }}
        </el-breadcrumb-item>
      </el-breadcrumb>
    </div>

注意:此时对于面包屑导航需要做进一步的处理和优化,利用循环解决,头部组件点击路由时切换对应的名字,一是解决路径问题,二是解决点击切换标题的问题

// 监听 监听点击那个链接 watch监听全局route可以省略this
  // val中含有一级二级菜单路由 但是中文没有
  // 此时需要解决中文 在路由每一个添加一个meta:{title:"中文名称"}
  watch: {
    $route: {
      // 深度监听
      deep: true,
      // 立即监听
      immediate: true,
      handler(newVal, oldVal) {
        // 监听值的变化
        // 一个新的值  一个旧的值
        // console.log(newVal, oldVal);
        // 我们要封装一个新的数组,这个数组是我们想要的title和path
        // 自己定一个数组
        let arr = []
        newVal.matched.forEach((ele) => {
          // 因为里面2个数据 定一个对象
          let obj = {}
          // 需要进行判断 meta没值的时候,就不加>,二级后面不要>符号
          if (ele.meta.title) {
            obj.path = ele.path
            obj.title = ele.meta.title
            arr.push(obj)
          }
        })
        // 把数组添加到自己定义的list,list里面有值了就可以进行循环
        this.routeList = arr
      }
    }
  },

7.头部组件完整部分代码,中间过程的数据请求,后面用vuex进行了优化

<template>
  <div class="headerbox">
    <!-- 头部的左侧 -->
    <div class="headerLeft">
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <!-- to是跳转路由 -->
        <!-- 这边面包屑需要点击跳转到对应的链接 需要实时监听路由变化route -->
        <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item :to="{ path: item.path }" v-for="(item, index) in routeList" :key="index">{{ item.title }}
        </el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <!-- 头部的右则 -->
    <div class="headerRight">
      <el-dropdown>
        <span class="el-dropdown-link">
          欢迎您,{{ $store.state.userObj.account }}<i class="el-icon-arrow-down el-icon--right"></i>
        </span>
        <el-dropdown-menu slot="dropdown">
          <!-- el-dropdown-item添加点击事件没有反应 需要native修饰 -->
          <el-dropdown-item icon="el-icon-user-solid" @click.native="toPersonal">个人中心</el-dropdown-item>
          <el-dropdown-item icon="el-icon-unlock" @click.native="toUpdatePwd">修改密码</el-dropdown-item>
          <el-dropdown-item icon="el-icon-switch-button" @click.native="toLogin">退出账号</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <!-- 图片 -->
      <div class="imgbox">
        <!-- 图片绑定动态的图片 -->
        <img :src="$store.state.userObj.imgUrl" alt="">
      </div>
    </div>
  </div>
</template>

<script>

export default {
  // 定一个data数据 数组
  // router 和 route
  // router是全局路由,一般是跳转到页面使用 push replace go
  // route一般接收路由跳转的参数  是否可以理解route从哪里跳转的
  data() {
    return {
      // 拿到了数据需要进行循环,数据循环我们只能在data里面
      // 所以需要先定一个空的数组
      routeList: [],
      // 后台拿过来的就是对象 直接定义一个对象来接收后端的值
      accountInfo: {
        account: "",
        ctime: "",
        id: "",
        imgUrl: "",
        userGroup: ""
      },
    }
  },
  mounted() {
    // 还要调用actios的方法
    this.$store.dispatch("queryUser")
  },
  methods: {
    // async getUserInfo() {
    //   let res = await api_info()
    //   // //console.log(res);
    //   // 请求的数据需要定一个来接收
    //   this.accountInfo = res.accountInfo
    // },
    // 用链接跳转的形式
    // 到个人中心
    toPersonal() {
      this.$router.push("/users/personal")
    },
    // 到修改密码
    toUpdatePwd() {
      this.$router.push("/users/pwd")
    },
    // 退出登录
    toLogin() {
      // 这个方法是退出登录
      // 清空token值
      // 跳转到登录页
      // 这个是清理指定的token
      this.$local.remove("sell_token")
      // // 全部清空 建议不要全部清空
      // localStorage.clear()
      // replace不记录历史 直接进行替换
      this.$router.replace("/login")
    },
  },
  // 监听 监听点击那个链接 watch监听全局route可以省略this
  // val中含有一级二级菜单路由 但是中文没有
  // 此时需要解决中文 在路由每一个添加一个meta:{title:"中文名称"}
  watch: {
    $route: {
      // 深度监听
      deep: true,
      // 立即监听
      immediate: true,
      handler(newVal, oldVal) {
        // 监听值的变化
        // 一个新的值  一个旧的值
        // console.log(newVal, oldVal);
        // 我们要封装一个新的数组,这个数组是我们想要的title和path
        // 自己定一个数组
        let arr = []
        newVal.matched.forEach((ele) => {
          // 因为里面2个数据 定一个对象
          let obj = {}
          // 需要进行判断 meta没值的时候,就不加>,二级后面不要>符号
          if (ele.meta.title) {
            obj.path = ele.path
            obj.title = ele.meta.title
            arr.push(obj)
          }
        })
        // 把数组添加到自己定义的list,list里面有值了就可以进行循环
        this.routeList = arr
      }
    }
  },
}
</script>

<style lang="less" scoped>
.headerbox {
  // 高度50-70之间 基本上固定的
  height: 50px;
  width: 100%;
  display: flex;
  // 两端对齐
  justify-content: space-between;
  align-items: center;

  .headerRight {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 150px;

    .imgbox {
      padding: 5px;
      width: 50px;
      height: 50px;
      cursor: pointer;
      img {
        width: 100%;
        height: 100%;
        border-radius: 50%;
      }
    }
  }
  .el-dropdown {
    .el-dropdown-link {
      cursor: pointer;
      color: #409EFF;
    }
    .el-icon-arrow-down {
      font-size: 12px;
    }
  }
}
</style>

8.根据整个原型图得知,需要再次封装一个卡片组件,发现每一个页面板块都有对应的上下两个部分,在compoents下新建文件夹;公共的代码可以抽离处理,每一个页面都会用到

<template>
  <el-card class="box-card">
    <div slot="header" class="headernav">
      <!-- 设置一个插槽 具名插槽-->
      <slot name="headerCard"></slot>
    </div>
    <div>
      <!-- 设置一个插槽 -->
      <slot name="contentCard"></slot>
    </div>
  </el-card>
</template>
<script>
  export default {
    
  }
</script>

<style lang="less" scoped>
.headernav{
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

9.组件已经封装完成,此时来完成Layout页面,主要是导入左侧组件,和头部组件即可完成;再次之前app跟组件需要配置路由出口,以及layout也需要配置路由出口,包括公共样式必须设置高是100%

<template>
  <div class="layoutbox">
    <el-container class="containerleft">
      <el-aside width="200px">
        <!-- 左侧 -->
        <h1 @click="toLogin">外卖商家中心</h1>
        <!-- 3.使用 -->
        <comm-aside></comm-aside>
        <!-- default-active 默认被选中 那一页
        background-color背景颜色
        active-text-color被选中文字的颜色
        text-color导航的文字颜色
        el-menu 是外壳
        el-menu-item是每一项
        el-submenu是二级导航
        index就是放路由的-->
        <!-- 在js中this.$router this.$route在页面this看要不要 route是临时路由 也就是说点击那个菜单 他的菜单中有idex
        index就是路由会自动的写到default-active中 
          router 自动的获取当前的this.$router-->
      </el-aside>
      <el-container>
        <!-- 头部 -->
        <el-header>
          <!-- 引入头部 -->
          <comm-header></comm-header>
        </el-header>
        <!-- 中心内容部分 -->
        <el-main>
          <!-- 配置右侧路由的出口 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
    <!-- 和app页面 承载后面所有页面的显示 -->

  </div>
</template>

<script>
// 2.引入
import commAside from "../components/commAside"
import commHeader from "../components/commHeader"

export default {
  components: {
    // 1.注册
    commAside,
    commHeader,
  },
  methods: {
    toLogin() {
      this.$router.push("/home")
    }
  },
}
</script>

<style lang="less" scoped>
.layoutbox {
  height: 100%;

  .containerleft {
    height: 100%;

    .el-aside {
      height: 100%;
      background: #304156;

      h1 {
        color: #fff;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 20px;
        font-weight: 700;
        padding: 30px;
        cursor: pointer;
      }
    }
    .el-main{
      background: #f0f2f5;
    }
  }
}
</style>

账号管理

1.账号列表

1.根据原型图得知,有页码,而且还是带左侧可以选择的表格,结构如下

 <!-- 身体部分 -->
      <template #contentCard>
        <!-- height="550" 给表格加一个高度,固定高度 -->
        <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" height="550"
          @selection-change="getSelectItem">
          <el-table-column type="selection" width="55">
          </el-table-column>
          <el-table-column prop="account" label="姓名" width="120">
          </el-table-column>
          <el-table-column prop="userGroup" label="用户组" width="120">
          </el-table-column>
          <!-- row这一行所有数据 -->
          <el-table-column label="创建时间" width="300" #default="{row  } ">
            <!-- 作用域插槽 子组件的数据不想显示,可以交给父元素 -->
            <!-- 我们需要对值进行过滤 -->
            <!-- #default="{row}" 进行解构 -->
            <!-- 想要得到数据,就 row.ctime -->
            {{row.ctime | chuliTime}}
          </el-table-column>
          <el-table-column label="操作" width="200">
            <template slot-scope="{row}">
              <!-- scope 代表这一行其中有row -->
              <el-button type="parimary" size="small" @click="huixian(row)">编辑</el-button>
              <el-button type="danger" size="small" @click="delUser(row.id  )">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 页码 -->
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
          :current-page="currentPage " :page-sizes="[1,  2,   3, 4,,5,6,7,8,9,10]" :page-size="pageSize"
          layout="total, sizes, prev, pager, next, jumper" :total="total">
        </el-pagination>

注意:row是代表这一行,用 #default="{row  } " {{row.ctime | chuliTime}}

2.在data里面定义,表单验证,页码,请求数据,实现数据的双向绑定

 data() {
    // 用户名验证
    let checkAccount = (rule, value, callback) => {
      rule = /[0-9a-zA-Z\u4e00-\u9fa5]{3,6}$/
      if (rule.test(value)) {
        callback()
      } else {
        callback(new Error("请输入正确(数字, 字母 ,中文 3-6个长度)的用户名!"))
      }
    }
    return {
      tableData: [],
      // 自己设置的默认页 和 条数 后台需要这些数据
      currentPage: 1,
      pageSize: 3,
      // 总条数
      total: 0,
      // 被选中的定义的一个数组
      checkedArr: [],
      dialogFormVisible: false,
      dis: true,
      // 定义一个对象 inuserInfo 存id account 和 用户组
      userInfo: {
        id: "",
        account: "",
        userGroup: ""
      },
      checkUserInfo: {
        account: [
          // 自定义的
          { validator: checkAccount, trigger: "blur" }
        ],
      }
    }

  },

1)在methods请求数据,数据进行初始化,按需导入自己封装好的

import { api_userList, api_del, api_edit, api_batchdel } from "../../api/user.js"
methods: {
async queryList() {
      let res = await api_userList({
        currentPage: this.currentPage,
        pageSize: this.pageSize,
      })
      // //console.log(res);
      // 拿到数据
      this.tableData = res.data
      // 拿到总条数
      this.total = res.total

    },
}

 2)在create中发请求

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

3)页码 总条数

    // 放在方法里面
    handleSizeChange(val) {
      // 触发的条数
      // console.log(`每页 ${val} 条`);
      this.pageSize = val;
      // 调用回显
      this.queryList();
    },
    handleCurrentChange(val) {
      // 触发的是第几页
      // console.log(`当前页: ${val}`);
      // 同步currentPage的值
      this.currentPage = val;
      // 重新发请求
      this.queryList();
    },

4)数据删除,需要传入参数id

 // 删除数据
    async delUser(id) {
      if (confirm("确定删除吗")) {
        let res = await api_del({ id })
        //console.log(res);
        if (res.code == 0) {
          this.$message.success("删除成功")
          this.queryList();
        } else {
          this.$message.error("删除失败")
          return false
        }
      }
    },

5)用户编辑,首先需要回显数据

 // 用户编辑
    huixian(row) {
      // 需要先回显数据 需要把数据绑定好
      // 当页面的数据存js中 显示到页面上
      this.userInfo.id = row.id
      this.userInfo.account = row.account
      this.userInfo.userGroup = row.userGroup
      // 再弹出弹出框 不然会有空白期 体验不好
      this.dialogFormVisible = true
    },

6)用户修改 先回显 再修改

 <el-row>
          <el-col :span="6">
            <!-- 弹出框修改用户信息 -->
            <!-- closeOnClickModal 不让你点击弹出框遮罩层关闭  true代表让 false不让 -->
            <el-dialog title="修改用户信息" :visible.sync="dialogFormVisible" :closeOnClickModal="false">
              <el-form label-width="100px" :rules="checkUserInfo" ref="userInfo" :model="userInfo">
                <!-- 记得要绑定modle -->
                <el-form-item label="用户名" prop="account">
                  <el-input autocomplete="off" v-model.trim="userInfo.account"></el-input>
                </el-form-item>
                <el-form-item label="用户组">
                  <el-select placeholder="请输入用户组" v-model="userInfo.userGroup">
                    <el-option label="超级管理员" value="超级管理员"></el-option>
                    <el-option label="普通管理员" value="普通管理员"></el-option>
                  </el-select>
                </el-form-item>
              </el-form>
              <div slot="footer" class="dialog-footer">
                <el-button @click="dialogFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="toUpu">确 定</el-button>
              </div>
            </el-dialog>
          </el-col>
        </el-row>

7)methods中的修改方法

 // 确定修改
    async toUpu() {
      // 发送异步请求
      let res = await api_edit(this.userInfo)
      if (res.code == 0) {
        this.$message.success("修改成功")
        // 修改完成之后或者失败 模态框要关闭
        this.dialogFormVisible = false
        this.queryList();
      } else {
        this.$message.error("修改失败")
        // 改完关闭
        this.dialogFormVisible = false
        // 改完数据刷新
        this.queryList();
      }

8)取消选择

  // 取消选择
    toggleSelection() {
      // multipleTable table自己带的 
      this.$refs.multipleTable.clearSelection();
    },

9)批量删除 以及删除前做了一个没选中的判断

 // 批量删除 获取选择的
    getSelectItem(val) {
      // console.log(val); selection-change这个方法有一个val值 打印出来是一个数组
      // 这是拿到了批量删除这行的数据 存到自己定义的数组中
      this.checkedArr = val
      this.multipleTable = val
      // 加个判断 有值的话才放开禁用
      if (this.multipleTable.length > 0) {
        this.dis = false
      } else {
        this.dis = true
      }
    },
    // 批量删除
    delAll() {
      this.$confirm('此操作将永久删除改选择的数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        // 定一个一个空数组
        let ids = []
        this.checkedArr.forEach((item) => {
          // 循环 每一项item取出来 存到自己定的空数组中
          ids.push(item.id)
        })
        // 要转数组转化成字符串JSON.stringify(ids)
        ids = JSON.stringify(ids)
        let res = await api_batchdel({ ids })
        if (res.code == 0) {
          this.$message.success(res.msg)
          this.queryList();
        } else {
          this.$message.error(res.msg)
        }
      });
    },

注意:批量删除的时候,后端要求传入ids,此时我们拿过来的数据还需要进行转化,转化成字符串的形式,要根据后端的需求进行传递参数.

10)下面是用户列表完整代码

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          账号列表
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <!-- height="550" 给表格加一个高度,固定高度 -->
        <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" height="550"
          @selection-change="getSelectItem">
          <el-table-column type="selection" width="55">
          </el-table-column>
          <el-table-column prop="account" label="姓名" width="120">
          </el-table-column>
          <el-table-column prop="userGroup" label="用户组" width="120">
          </el-table-column>
          <!-- row这一行所有数据 -->
          <el-table-column label="创建时间" width="300" #default="{row  } ">
            <!-- 作用域插槽 子组件的数据不想显示,可以交给父元素 -->
            <!-- 我们需要对值进行过滤 -->
            <!-- #default="{row}" 进行解构 -->
            <!-- 想要得到数据,就 row.ctime -->
            {{row.ctime | chuliTime}}
          </el-table-column>
          <el-table-column label="操作" width="200">
            <template slot-scope="{row}">
              <!-- scope 代表这一行其中有row -->
              <el-button type="parimary" size="small" @click="huixian(row)">编辑</el-button>
              <el-button type="danger" size="small" @click="delUser(row.id  )">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 页码 -->
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
          :current-page="currentPage " :page-sizes="[1,  2,   3, 4,,5,6,7,8,9,10]" :page-size="pageSize"
          layout="total, sizes, prev, pager, next, jumper" :total="total">
        </el-pagination>
        <!-- 取消选择 -->
        <div style="margin-top: 20px">
          <el-button type="danger" @click="delAll" :disabled="dis">批量删除</el-button>
          <el-button type="primary" @click="toggleSelection()">取消选择</el-button>
        </div>
        <el-row>
          <el-col :span="6">
            <!-- 弹出框修改用户信息 -->
            <!-- closeOnClickModal 不让你点击弹出框遮罩层关闭  true代表让 false不让 -->
            <el-dialog title="修改用户信息" :visible.sync="dialogFormVisible" :closeOnClickModal="false">
              <el-form label-width="100px" :rules="checkUserInfo" ref="userInfo" :model="userInfo">
                <!-- 记得要绑定modle -->
                <el-form-item label="用户名" prop="account">
                  <el-input autocomplete="off" v-model.trim="userInfo.account"></el-input>
                </el-form-item>
                <el-form-item label="用户组">
                  <el-select placeholder="请输入用户组" v-model="userInfo.userGroup">
                    <el-option label="超级管理员" value="超级管理员"></el-option>
                    <el-option label="普通管理员" value="普通管理员"></el-option>
                  </el-select>
                </el-form-item>
              </el-form>
              <div slot="footer" class="dialog-footer">
                <el-button @click="dialogFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="toUpu">确 定</el-button>
              </div>
            </el-dialog>
          </el-col>
        </el-row>
      </template>
    </comm-card>
  </div>
</template>

<script>
import { api_userList, api_del, api_edit, api_batchdel } from "../../api/user.js"
import commCard from "../../components/commCard"
export default {
  created() {
    this.queryList();
  },
  data() {
    // 用户名验证
    let checkAccount = (rule, value, callback) => {
      rule = /[0-9a-zA-Z\u4e00-\u9fa5]{3,6}$/
      if (rule.test(value)) {
        callback()
      } else {
        callback(new Error("请输入正确(数字, 字母 ,中文 3-6个长度)的用户名!"))
      }
    }
    return {
      tableData: [],
      // 自己设置的默认页 和 条数 后台需要这些数据
      currentPage: 1,
      pageSize: 3,
      // 总条数
      total: 0,
      // 被选中的定义的一个数组
      checkedArr: [],
      dialogFormVisible: false,
      dis: true,
      // 定义一个对象 inuserInfo 存id account 和 用户组
      userInfo: {
        id: "",
        account: "",
        userGroup: ""
      },
      checkUserInfo: {
        account: [
          // 自定义的
          { validator: checkAccount, trigger: "blur" }
        ],
      }
    }

  },
  methods: {
    async queryList() {
      let res = await api_userList({
        currentPage: this.currentPage,
        pageSize: this.pageSize,
      })
      // //console.log(res);
      // 拿到数据
      this.tableData = res.data
      // 拿到总条数
      this.total = res.total

    },
    // 放在方法里面
    handleSizeChange(val) {
      // 触发的条数
      // console.log(`每页 ${val} 条`);
      this.pageSize = val;
      // 调用回显
      this.queryList();
    },
    handleCurrentChange(val) {
      // 触发的是第几页
      // console.log(`当前页: ${val}`);
      // 同步currentPage的值
      this.currentPage = val;
      // 重新发请求
      this.queryList();
    },
    // 删除数据
    async delUser(id) {
      if (confirm("确定删除吗")) {
        let res = await api_del({ id })
        //console.log(res);
        if (res.code == 0) {
          this.$message.success("删除成功")
          this.queryList();
        } else {
          this.$message.error("删除失败")
          return false
        }
      }
    },
    // 用户编辑
    huixian(row) {
      // 需要先回显数据 需要把数据绑定好
      // 当页面的数据存js中 显示到页面上
      this.userInfo.id = row.id
      this.userInfo.account = row.account
      this.userInfo.userGroup = row.userGroup
      // 再弹出弹出框 不然会有空白期 体验不好
      this.dialogFormVisible = true
    },
    // 确定修改
    async toUpu() {
      // 发送异步请求
      let res = await api_edit(this.userInfo)
      if (res.code == 0) {
        this.$message.success("修改成功")
        // 修改完成之后或者失败 模态框要关闭
        this.dialogFormVisible = false
        this.queryList();
      } else {
        this.$message.error("修改失败")
        // 改完关闭
        this.dialogFormVisible = false
        // 改完数据刷新
        this.queryList();
      }
    },
    // 取消选择
    toggleSelection() {
      // multipleTable table自己带的 
      this.$refs.multipleTable.clearSelection();
    },
    // 批量删除 获取选择的
    getSelectItem(val) {
      // console.log(val); selection-change这个方法有一个val值 打印出来是一个数组
      // 这是拿到了批量删除这行的数据 存到自己定义的数组中
      this.checkedArr = val
      this.multipleTable = val
      // 加个判断 有值的话才放开禁用
      if (this.multipleTable.length > 0) {
        this.dis = false
      } else {
        this.dis = true
      }
    },
    // 批量删除
    delAll() {
      this.$confirm('此操作将永久删除改选择的数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        // 定一个一个空数组
        let ids = []
        this.checkedArr.forEach((item) => {
          // 循环 每一项item取出来 存到自己定的空数组中
          ids.push(item.id)
        })
        // 要转数组转化成字符串JSON.stringify(ids)
        ids = JSON.stringify(ids)
        let res = await api_batchdel({ ids })
        if (res.code == 0) {
          this.$message.success(res.msg)
          this.queryList();
        } else {
          this.$message.error(res.msg)
        }
      });
    },
  },
  // 注册组件
  components: {
    commCard,
  },
}
</script>

<style lang="less" scoped>
.el-select {
  width: 100%;
}
</style>

2.用户添加

1.用户添加比较简单,主要是发送请求,以及对表单需要做验证即可

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          账号添加
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <!-- element-ui分了24等分 -->
        <el-row>
          <el-col :span="6">
            <!-- 绑定你定义的数据和规则 -->
            <el-form label-width="80px" size="medium" :model="userInfo" :rules="checkUserInfo" ref="userFrom">
              <el-form-item label="账号" prop="account">
                <el-input type="text" placeholder="请输入账号" v-model.trim="userInfo.account ">
                </el-input>
              </el-form-item>
              <el-form-item label="密码" prop="password">
                <el-input type="password" placeholder="请输入密码" v-model.trim="userInfo.password">
                </el-input>
              </el-form-item>
              <el-form-item label="用户组">
                <el-select placeholder="请选择用户组" v-model="userInfo.userGroup">
                  <el-option label="" value="超级管理员">超级管理员</el-option>
                  <el-option label="" value="普通管理员">普通管理员</el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="">
                <el-button type="primary" @click="addUser">
                  添加
                </el-button>
                <el-button type="default" @click="resetfn">
                  重置
                </el-button>
              </el-form-item>
            </el-form>
          </el-col>
        </el-row>
      </template>

    </comm-card>
  </div>
</template>

<script>
import commCard from "../../components/commCard"
import { api_add } from "../../api/user.js"
export default {
  components: {
    commCard,
  },
  // data是一个方法 方法里面有返回值 方法里面可以放方法
  data() {
    // rule 规则 value要验证的值 callback回调函数
    // 用户名验证
    let checkAccount = (rule, value, callback) => {
      rule = /[0-9a-zA-Z\u4e00-\u9fa5]{3,6}$/
      if (rule.test(value)) {
        callback()
      } else {
        callback(new Error("请输入正确(数字, 字母 ,中文 3-6个长度)的用户名!"))
      }
    }
    // 密码验证
    let checkPwd = (rule, value, callback) => {
      // rule = /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/
      rule = /[0-9a-zA-Z\u4e00-\u9fa5]{3,6}$/
      if (rule.test(value)) {
        callback()
      } else {
        callback(new Error("请输入正确(数字, 字母 ,中文 3-6个长度)的密码!"))
      }
    }
    return {
      // 用户信息
      userInfo: {
        account: "",
        password: "",
        userGroup: "",//权限
      },
      // 表单验证 写在data里面
      checkUserInfo: {
        account: [
          // 自定义的
          { validator: checkAccount, trigger: "blur" }
        ],
        password: [
          { validator: checkPwd, trigger: "blur" }
        ]
      }
    }
  },
  methods: {
    addUser() {
      this.$refs.userFrom.validate(async (val) => {
        if (val) {
          let res = await api_add(this.userInfo)
          // //console.log(res);
          if (res.code == 0) {
            this.$message.success("添加成功")
            // 添加成功跳转到列表页
            this.$router.push("/users/list")
            this.resetfn()
          } else {
            this.$message.error("添加失败")
            this.resetfn()
          }
        } else {
          return false
        }
      })
    },
    // 重置信息
    resetfn() {
      // 此时需要绑定ref 在el-from上
      this.$refs.userFrom.resetFields();
    }
  },
}
</script>

<style lang="less" scoped>
.el-select {
  width: 100%;
}
</style>

3.密码修改

1.首先需要发请求验证原来密码,然后再核对新的密码,以及再次进行确认,修改完成之后,跳转到登录页面,还需要清空token值

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          修改密码
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-row>
          <el-col :span="6">
            <el-form :model="pwdObj" status-icon :rules="checkUserInfo" ref="ruleForm" label-width="80px"
              class="demo-ruleForm">
              <el-form-item label="原密码" prop="oldPwd">
                <el-input type="password" autocomplete="off" v-model.trim="pwdObj.oldPwd"></el-input>
              </el-form-item>
              <el-form-item label="新密码" prop="newPwd">
                <el-input type="password" v-model.trim="pwdObj.newPwd" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="确认密码" prop="qrNewPwd">
                <el-input type="password" v-model.trim="pwdObj.qrNewPwd" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" @click="updatePwd">确认</el-button>
                <el-button @click="resetfn()">重置</el-button>
              </el-form-item>
            </el-form>
          </el-col>
        </el-row>
      </template>

    </comm-card>
  </div>
</template>

<script>
import commCard from "../../components/commCard"
import { api_editpwd, api_checkoldpwd } from "../../api/user.js"
export default {
  components: {
    commCard,
  },
  // data是一个方法 方法里面有返回值 方法里面可以放方法
  data() {
    // rule 规则 value要验证的值 callback回调函数
    // 验证原密码是你登录之前的密码
    let checkOldPwd = async (rule, value, callback) => {
      let res = await api_checkoldpwd({ oldPwd: value })
      // //console.log(res);
      if (res.code == "00") {
        // this.$message.success("密码正确")
        callback()
      } else {
        return callback(new Error("密码输入错误"))
      }
    }
    // 密码验证
    let checkNewPwd = (rule, value, callback) => {
      rule = /^[0-9a-zA-Z_]{3,6}$/
      if (rule.test(value)) {
        callback()
      } else {
        return callback(new Error("密码输入错误,请输入正确的字母,数字组成的3-8位密码"))
      }
    }
    // 确认密码验证
    let checkQrPwd = (rule, value, callback) => {
      // 确认密码和新密码相等的
      if (value == this.pwdObj.newPwd) {
        callback()
      } else {
        return callback(new Error("两次密码不一致,请重新输入"))
      }
    }
    return {
      // 用户密码
      pwdObj: {
        oldPwd: "",
        newPwd: "",
        qrNewPwd: "",
      },
      //密码验证
      checkUserInfo: {
        oldPwd: [
          // 自定义的
          { validator: checkOldPwd, trigger: "blur" }
        ],
        newPwd: [
          { validator: checkNewPwd, trigger: "blur" }
        ],
        qrNewPwd: [
          { validator: checkQrPwd, trigger: "blur" }
        ]
      }
    }
  },
  methods: {
    updatePwd() {
      this.$refs.userFrom.validate(async (val) => {
        if (val) {
          let res = await api_editpwd({ newPwd: this.pwdObj.newPwd })
          //console.log(res);
          if (res.code == 0) {
            this.$message.success("修改成功")
            // 修改成功之后,需要清空token值
            this.$local.clear()
            //修改完成之后,跳转到登录页面,此时不需要记录历史
            this.$router.replace("/login")
            // 修改完成之后,数据清空
            this.resetfn()
          } else {
            this.$message.error("修改失败")
            this.resetfn()
          }
        } else {
          return false
        }
      })
    },
    // 重置信息
    resetfn() {
      // 此时需要绑定ref 在el-from上
      this.$refs.ruleForm.resetFields();
    }
  },
}
</script>

<style lang="less" scoped>

</style>

4.个人中心

1.这个模块因为和头部有公共的代码,包括数据请求也是需要请求2次,此时利用vuex进行变量储存,主要是做性能优化,异步请求只需要发一次

2.页面排版代码

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          管理员信息
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-row>
          <el-col :span="24">
            <template>
              <div>
                <span>管理员ID:{{ $store.state.userObj.id}}</span>
                <el-divider></el-divider>
                <span>账号:{{ $store.state.userObj.account}}</span>
                <el-divider></el-divider>
                <span>用户组:{{ $store.state.userObj.userGroup}}</span>
                <el-divider></el-divider>
                <span>创建时间:{{ $store.state.userObj.ctime | chuliDate}}</span>
                <el-divider></el-divider>
                <div class="headerImg">
                  <span>管理员头像:</span>
                  <!-- action 提交地址 -->
                  <!-- 这是远程地址 -->
                  <!-- action是上传给后台的地址路径 后端的api -->
                  <el-upload class="avatar-uploader" action="http://119.23.246.178:80/users/avatar_upload"
                    :show-file-list="false" :on-success="handleAvatarSuccess"
                    :before-upload="beforeAvatarUpload">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar">
                    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                  </el-upload>
                  <!-- 更新图像按钮 -->
                  <el-button type="primary" @click="updateHeaderImg">更新图像</el-button>
                </div>
              </div>
            </template>
          </el-col>
        </el-row>

      </template>

    </comm-card>
  </div>
</template>

3.需要注意的就是这里有图片上传,比较麻烦,格式需要转化,不然后台数据请求不到,会报错5001参数不对

<div class="headerImg">
                  <span>管理员头像:</span>
                  <!-- action 提交地址 -->
                  <!-- 这是远程地址 -->
                  <!-- action是上传给后台的地址路径 后端的api -->
                  <el-upload class="avatar-uploader" action="http://119.23.246.178:80/users/avatar_upload"
                    :show-file-list="false" :on-success="handleAvatarSuccess"
                    :before-upload="beforeAvatarUpload">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar">
                    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                  </el-upload>
                  <!-- 更新图像按钮 -->
                  <el-button type="primary" @click="updateHeaderImg">更新图像</el-button>
                </div>

1)action这个是传到后台后端的地址,地址不要搞错了

2)在上传图片之后做的事情,在data中定义imageUrl,然后拿到的图片赋值给imageUrl,

因为我们聪后端拿到的img只有名字,需要把前缀拼接上才可以res.imgUrl这是后台拿到的

 // 图片上传成功之后要做什么事
    handleAvatarSuccess(res, file) {
      // //console.log(res);
      if (res.code == 0) {
        // 拿到自己定义的图片 右边是后台给的imgUrl
        // this.imageUrl = res.imgUrl
        this.$message.success("图片上传成功")
        // 因为我们聪后端拿到的img只有名字,需要把前缀拼接上才可以res.imgUrl这是后台拿到的
        // 然后自己要把前缀加上去前缀就是http://119.23.246.178:80/upload/imgs/acc_img/
        this.imageUrl = "http://119.23.246.178:80/upload/imgs/acc_img/" + res.imgUrl;
      } else {
        this.$message.error("图片上传失败")
      }
      //console.log(res);
    },

3)再上传之前做的事情,主要格式限制,和大小要求;isJPG名字自己取

 // 再上传图片之前
    beforeAvatarUpload(file) {
      // 对图片进行文件类型的限制 png 类型继续往后写
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
      const isLt2M = file.size / 1024 / 1024 < 2;
      // 图片大小 图片格式
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },

4)更新图像的按钮

 <!-- 更新图像按钮 -->
                  <el-button type="primary" @click="updateHeaderImg">更新图像</el-button>

5)发数据请求,进行更新图片,此时图片需要格式转化,后台要这种格式,不转化的是,一个链接加数字.png

async updateHeaderImg() {
      let imgUrl = this.imageUrl.substring(this.imageUrl.lastIndexOf("/") + 1);
      console.log(imgUrl);
      // 后台只要文件名称
      let res = await api_avataredit({ imgUrl })
      //console.log(res);
      if (res.code == 0) {
        this.$message.success("更新图像成功")
        // 上传之后刷新一下
        this.$router.go(0)
      } else {
        this.$message.error("更新图像失败")
      }
    }

4.在vuex中发数据请求,个人中心页面就没有发请求了,这是store.index.js,直接进行定义图片的方法即可

 

 5.只需要在create中做图片处理

 created() {
    setTimeout(() => {
      // 这里图片显示需要做一个延迟 因为这里的数据是从store请求拿过来的
      // 是在vuex中发的请求,把公共变量存在vuex中 个人中心瞬间拿到数据,此时照片还没有更新过来\
      // 加一个延时器 等头像请求完之后,这边也同步了
      this.imageUrl = this.$store.state.userObj.imgUrl;
    },100)
  },

6.个人中心完整代码

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          管理员信息
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-row>
          <el-col :span="24">
            <template>
              <div>
                <span>管理员ID:{{ $store.state.userObj.id}}</span>
                <el-divider></el-divider>
                <span>账号:{{ $store.state.userObj.account}}</span>
                <el-divider></el-divider>
                <span>用户组:{{ $store.state.userObj.userGroup}}</span>
                <el-divider></el-divider>
                <span>创建时间:{{ $store.state.userObj.ctime | chuliDate}}</span>
                <el-divider></el-divider>
                <div class="headerImg">
                  <span>管理员头像:</span>
                  <!-- action 提交地址 -->
                  <!-- 这是远程地址 -->
                  <!-- action是上传给后台的地址路径 后端的api -->
                  <el-upload class="avatar-uploader" action="http://119.23.246.178:80/users/avatar_upload"
                    :show-file-list="false" :on-success="handleAvatarSuccess"
                    :before-upload="beforeAvatarUpload">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar">
                    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                  </el-upload>
                  <!-- 更新图像按钮 -->
                  <el-button type="primary" @click="updateHeaderImg">更新图像</el-button>
                </div>
              </div>
            </template>
          </el-col>
        </el-row>

      </template>

    </comm-card>
  </div>
</template>

<script>
import commCard from "../../components/commCard"
import {  api_avataredit } from "../../api/user.js"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      //  为显示上传图标用的
      imageUrl: "",
      // 实现数据双向绑定
      accountInfo: {},
      // 为修改图像传值用的
    }
  },
  created() {
    setTimeout(() => {
      // 这里图片显示需要做一个延迟 因为这里的数据是从store请求拿过来的
      // 是在vuex中发的请求,把公共变量存在vuex中 个人中心瞬间拿到数据,此时照片还没有更新过来\
      // 加一个延时器 等头像请求完之后,这边也同步了
      this.imageUrl = this.$store.state.userObj.imgUrl;
    },100)
  },
  methods: {
    // async getUserInfo() {
    //   let res = await api_info()
    //   // //console.log(res);
    //   // 接收的是一个对象
    //   // 请求的数据需要定一个来接收
    //   this.accountInfo = res.accountInfo;
    //   this.imageUrl = res.accountInfo.imgUrl
    // },
    // 图片上传成功之后要做什么事
    handleAvatarSuccess(res, file) {
      // //console.log(res);
      if (res.code == 0) {
        // 拿到自己定义的图片 右边是后台给的imgUrl
        // this.imageUrl = res.imgUrl
        this.$message.success("图片上传成功")
        // 因为我们聪后端拿到的img只有名字,需要把前缀拼接上才可以res.imgUrl这是后台拿到的
        // 然后自己要把前缀加上去前缀就是http://119.23.246.178:80/upload/imgs/acc_img/
        this.imageUrl = "http://119.23.246.178:80/upload/imgs/acc_img/" + res.imgUrl;
      } else {
        this.$message.error("图片上传失败")
      }
      //console.log(res);
    },
    // 再上传图片之前
    beforeAvatarUpload(file) {
      // 对图片进行文件类型的限制 png 类型继续往后写
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
      const isLt2M = file.size / 1024 / 1024 < 2;
      // 图片大小 图片格式
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },
    async updateHeaderImg() {
      let imgUrl = this.imageUrl.substring(this.imageUrl.lastIndexOf("/") + 1);
      // console.log(imgUrl);
      // 后台只要文件名称
      let res = await api_avataredit({ imgUrl })
      //console.log(res);
      if (res.code == 0) {
        this.$message.success("更新图像成功")
        // 上传之后刷新一下
        this.$router.go(0)
      } else {
        this.$message.error("更新图像失败")
      }
    }
  },
}
</script>

<style lang="less" scoped>
.headerImg {
  display: flex;
  justify-content: flex-start;
  align-items: center;

  span {
    margin-right: 5px;
  }

  button {
    margin-left: 5px;
  }
}

/deep/.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;
}
</style>

5.合同模块

1.打印合同,需要把合同当做一个组件,注册,引用,标签使用,首先还是需要下载依赖,vue-pdf

<template>
  <el-dialog title="合同内容" :visible.sync="dialogVisible" width="50%" :close-on-click-modal="false">
    <!-- 一般是动态的 目前这个是静态的 -->
    <!-- 
    :src = "" 文件路径
    :page 显示的页码
    :route 旋转多少度 每次是90的倍数 number类型
    -->
    <hr>
    <el-button @click="top">上一页</el-button>
    <el-button @click="upd">下一页</el-button>
    <hr>
    <p>{{ currentPage }} / {{ pageCount }}</p>
    <pdf src="./众安保险.PDF" @num-pages="pageCount = $event" @page-loaded="currentPage = $event" ref="mypdf" :page="num">
    </pdf>
    <span slot="footer" class="dialog-footer">
      <el-button @click="dialogVisible = false">取 消</el-button>
      <el-button type="success" @click="print">打印</el-button>
    </span>
  </el-dialog>

</template>

注意:这里的src实际上是一个动态的值,

2.方法和data中的数据,需要定义页码,还有当前页,页码需要加一个判断,最后一页和第一页

<script>
// 第一步安装 第二步导入 第三步当组件使用
import pdf from "vue-pdf"
export default {
  components: {
    pdf,
  },
  data() {
    return {
      dialogVisible: false,
      num: 1,
      currentPage: 0,
      pageCount: 0,
    };
  },

  methods: {
    // 打印合同
    print() {
      this.$refs.mypdf.print()
    },
    top() {
      if (this.num > 1) {
        this.num--
      } else {
        alert("已经是第一页啦,不能再退了")
      }
    },
    upd() {
      if (this.num < 182) {
        this.num++
      } else {
        alert("已经是最后一页啦,到底啦")
      }
    }
  },
}
</script>

3.组件的注册和引入;引入pdf依赖,引入卡片组件,就是需要把pdf当做是组件使用

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div>
          查看合同
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-button type="primary" @click="look">点击查看</el-button>
        <pdf ref="myPdf"></pdf>
      </template>

    </comm-card>
  </div>
</template>

<script>
import pdf from "./index.vue"
import commCard from "../../components/commCard"
export default {
  components: {
    commCard,
    pdf,
  },
  methods: {
    look() {
      this.$refs.myPdf.dialogVisible = true
    }
  },
}
</script>

<style lang="less" scoped>

</style>

店铺管理

1.店铺管理

1.根据原型图显示,店铺管理主要是显示相应的数据,,把相关数据请求过来,主要的难点在于头像,和店铺照片墙,包括店铺营业时间,需要处理和优化.下面是页面结构

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div class="shopbox">
          <div>
            店铺管理
          </div>
          <el-button type="primary" @click="toupdateShop">保存</el-button>
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-form label-width="80px" style="width:30%" size="small" :model="shopObj" :rules="checkedShop"
          ref="goodsInfo">
          <el-form-item label="店铺名称" prop="name">
            <el-input type="text" v-model.trim="shopObj.name" placeholder="店铺名称"></el-input>
          </el-form-item>
          <el-form-item label="店铺公告" prop="bulletin">
            <el-input :rows="5" placeholder="请输入店铺公告" type="textarea" v-model.trim="shopObj.bulletin"></el-input>
          </el-form-item>
          <el-form-item label="店铺头像">
            <!-- 店铺头像不需要请求,直接给一个全路径 -->
            <el-upload class="avatar-uploader" action="http://119.23.246.178:80/shop/upload" :show-file-list="false"
              :on-success="headerSuccess" :before-upload="beforeAvatarUpload">
              <img v-if="headerImgUrl " :src="'http://119.23.246.178:80/upload/shop/' + headerImgUrl"
                class="avatar">
              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            </el-upload>
          </el-form-item>
          <el-form-item label="店铺图片">
            <!-- :file-list="fileList" 可以显示多张图片 -->
            <el-upload action="http://119.23.246.178:80/shop/upload" list-type="picture-card"
              :on-preview="handlePictureCardPreview" :on-success="imgsSuccess" :file-list="fileList"
              :on-remove="handleRemove">
              <i class="el-icon-plus"></i>
            </el-upload>
            <el-dialog :visible.sync="dialogVisible">
              <img width="100%" :src="dialogImageUrl" alt="">
            </el-dialog>
          </el-form-item>
          <el-form-item label="配送费" prop="deliveryPrice">
            <el-input type="number" v-model.trim="shopObj.deliveryPrice" placeholder="配送费"></el-input>
          </el-form-item>
          <el-form-item label="配送时间" prop="deliveryTime">
            <el-input type="number" v-model.trim="shopObj.deliveryTime" placeholder="配送时间"></el-input>
          </el-form-item>
          <el-form-item label="配送描述" prop="description">
            <el-input type="text" v-model.trim="shopObj.description" placeholder="配送描述"></el-input>
          </el-form-item>
          <el-form-item label="店铺评分" prop="score">
            <el-input type="text" v-model.trim="shopObj.score" placeholder="店铺评分"></el-input>
          </el-form-item>
          <el-form-item label="店铺销量" prop="sellCount">
            <el-input type="number" v-model.trim="shopObj.sellCount" placeholder="店铺销量"></el-input>
          </el-form-item>
          <el-form-item label="活动性质">
            <!-- 下面是一个组合 -->
            <el-checkbox-group v-model="actionList">
              <el-checkbox label="在线支付满28减5" name="type"></el-checkbox>
              <el-checkbox label="VC无限橙果汁全场8折" name="type"></el-checkbox>
              <el-checkbox label="单人精彩套餐" name="type"></el-checkbox>
              <el-checkbox label="特饮品8折抢购" name="type"></el-checkbox>
            </el-checkbox-group>
          </el-form-item>
          <el-form-item label="营业时间">
            <el-date-picker v-model="yytime" type="datetimerange" range-separator="至" start-placeholder="开始日期"
              end-placeholder="结束日期">
            </el-date-picker>
          </el-form-item>
        </el-form>
      </template>
    </comm-card>
  </div>
</template>

2.data中数据部分加相应的表单验证

data() {
    return {
      // 多张图片
      fileList: [],
      // 点击预览的路径
      dialogImageUrl: '',
      // 弹出框
      dialogVisible: false,
      headerImgUrl: "",
      // 遇到是数组的拿到外面
      // 时间集合
      yytime: [],
      // 活动集合
      actionList: [],

      shopObj: {
        id: "",
        name: "",
        // 公告
        bulletin: "",
        // 店铺图像
        avatar: "",
        // 配送费
        deliveryPrice: "",
        // 配送时间
        deliveryTime: "",
        // 描述
        description: "",
        // 评分
        score: "",
        // 销量
        sellCount: "",
        // 活动
        supports: "",
        // 店铺图
        pics: "",
        // 营业时间
        date: "",
        // 起送价格
        minPrice: "",
      },
      checkedShop: {
        name: [// 自定义的
          { required: true, message: '商品名称不能为空', trigger: 'blur' },],
        bulletin: [{ required: true, message: '店铺公告不能为空', trigger: 'blur' },],
        deliveryPrice: [{ required: true, message: '配送费不能为空', trigger: 'blur' },],
        deliveryTime: [{ required: true, message: '配送时间不能为空', trigger: 'blur' },],
        description: [{ required: true, message: '配送描述不能为空', trigger: 'blur' },],
        score: [{ required: true, message: '店铺评分不能为空', trigger: 'blur' },],
        sellCount: [{ required: true, message: '销量不能为空', trigger: 'blur' },],
      },
    }
  },

3.获取店铺信息,头像图片进行处理

// 获取店铺信息
    async getShopInfo() {
      let res = await api_ShopInfo()
      //console.log(res);
      // this.shopObj = res.data
      // 如果是数组的 需要进行解构再显示数据
      let { data } = res
      this.shopObj = data
      this.headerImgUrl = data.avatar
      // 图片需要进行处理
      this.fileList = data.pics.map((item) => {
        return {
          // fileList要求返回的name+url地址
          // name就是每一项 url就是上传地址加每一项
          name: item,
          url: "http://119.23.246.178:80/upload/shop/" + item
        }
        // map返回的是一个对象
      })
      this.actionList = data.supports
      this.yytime = data.date
    },
 // 这里是头像图片 自己起个名字
    headerSuccess(res, file) {
      if (res.code == 0) {
        this.$message.success(res.msg)
        // 图片显示 我们得到的图片路径是名称 我们要拼接 第一种是在js中拼接 在hetl拼接
        this.headerImgUrl = res.imgUrl
        // 讲图片路径存起来
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    beforeAvatarUpload(file) {
      // 后面||多个图片上传
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },
    // 这是店铺图片 多图片开始

4.店铺图片照片墙,多张图片

 注意:移除和上传之后需要做的事情

 // 这是店铺图片 多图片开始
    // 图片上传成功之后
    imgsSuccess(res, file) {
      if (res.code == 0) {
        // 这个是多图片成功之后要把图片放到我们的数组中
        this.fileList.push({
          name: res.imgUrl,
          url: "http://119.23.246.178:80/upload/shop/" + res.imgUrl,
        });
        // this.$message.success(res.msg)
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    // 图片移除触发事件
    handleRemove(file, filst) {
      // file 删除的图片信息
      // filst 剩下的图片信息  this.fileList 是我们自己封装的
      // 从后端拿到的数据需要进行转化
      // 删除的信息 再返回给我们封装的
      // map返回一个新数组反还给this.fileList
      console.log(file, filst);
      this.fileList = filst.map((item) => {
        return {
          name: item.name,
          url: item.url
        }
      })
    },
    // 这是看预览图的不用修改
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    // 多图片结束

 5.店铺修改信息,因为活动,时间,图片墙,都是数组,需要做进一步的处理

// 保存修改店铺信息
    async toupdateShop() {
      // 此时数组需要进行转化成一个单引号的字符串
      // 因为后端要得是一个名字 我们需要在进行转化到name
      this.shopObj.pics = JSON.stringify(this.fileList.map((item) => {
        return item.name
      }))
      // 活动
      this.shopObj.supports = JSON.stringify(this.actionList)
      // 时间
      this.shopObj.date = JSON.stringify(this.yytime)
      // 处理之后才能把值塞进去
      let res = await api_ShopEdit(this.shopObj)
      if (res.code == 0) {
        this.$message.success(res.msg)
        this.$router.go(0)
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },

6.店铺管理完整代码

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <div class="shopbox">
          <div>
            店铺管理
          </div>
          <el-button type="primary" @click="toupdateShop">保存</el-button>
        </div>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-form label-width="80px" style="width:30%" size="small" :model="shopObj" :rules="checkedShop"
          ref="goodsInfo">
          <el-form-item label="店铺名称" prop="name">
            <el-input type="text" v-model.trim="shopObj.name" placeholder="店铺名称"></el-input>
          </el-form-item>
          <el-form-item label="店铺公告" prop="bulletin">
            <el-input :rows="5" placeholder="请输入店铺公告" type="textarea" v-model.trim="shopObj.bulletin"></el-input>
          </el-form-item>
          <el-form-item label="店铺头像">
            <!-- 店铺头像不需要请求,直接给一个全路径 -->
            <el-upload class="avatar-uploader" action="http://119.23.246.178:80/shop/upload" :show-file-list="false"
              :on-success="headerSuccess" :before-upload="beforeAvatarUpload">
              <img v-if="headerImgUrl " :src="'http://119.23.246.178:80/upload/shop/' + headerImgUrl"
                class="avatar">
              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            </el-upload>
          </el-form-item>
          <el-form-item label="店铺图片">
            <!-- :file-list="fileList" 可以显示多张图片 -->
            <el-upload action="http://119.23.246.178:80/shop/upload" list-type="picture-card"
              :on-preview="handlePictureCardPreview" :on-success="imgsSuccess" :file-list="fileList"
              :on-remove="handleRemove">
              <i class="el-icon-plus"></i>
            </el-upload>
            <el-dialog :visible.sync="dialogVisible">
              <img width="100%" :src="dialogImageUrl" alt="">
            </el-dialog>
          </el-form-item>
          <el-form-item label="配送费" prop="deliveryPrice">
            <el-input type="number" v-model.trim="shopObj.deliveryPrice" placeholder="配送费"></el-input>
          </el-form-item>
          <el-form-item label="配送时间" prop="deliveryTime">
            <el-input type="number" v-model.trim="shopObj.deliveryTime" placeholder="配送时间"></el-input>
          </el-form-item>
          <el-form-item label="配送描述" prop="description">
            <el-input type="text" v-model.trim="shopObj.description" placeholder="配送描述"></el-input>
          </el-form-item>
          <el-form-item label="店铺评分" prop="score">
            <el-input type="text" v-model.trim="shopObj.score" placeholder="店铺评分"></el-input>
          </el-form-item>
          <el-form-item label="店铺销量" prop="sellCount">
            <el-input type="number" v-model.trim="shopObj.sellCount" placeholder="店铺销量"></el-input>
          </el-form-item>
          <el-form-item label="活动性质">
            <!-- 下面是一个组合 -->
            <el-checkbox-group v-model="actionList">
              <el-checkbox label="在线支付满28减5" name="type"></el-checkbox>
              <el-checkbox label="VC无限橙果汁全场8折" name="type"></el-checkbox>
              <el-checkbox label="单人精彩套餐" name="type"></el-checkbox>
              <el-checkbox label="特饮品8折抢购" name="type"></el-checkbox>
            </el-checkbox-group>
          </el-form-item>
          <el-form-item label="营业时间">
            <el-date-picker v-model="yytime" type="datetimerange" range-separator="至" start-placeholder="开始日期"
              end-placeholder="结束日期">
            </el-date-picker>
          </el-form-item>
        </el-form>
      </template>
    </comm-card>
  </div>
</template>

<script>
import { api_ShopInfo, api_ShopEdit } from "../../api/shop.js"
import commCard from "../../components/commCard"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      // 多张图片
      fileList: [],
      // 点击预览的路径
      dialogImageUrl: '',
      // 弹出框
      dialogVisible: false,
      headerImgUrl: "",
      // 遇到是数组的拿到外面
      // 时间集合
      yytime: [],
      // 活动集合
      actionList: [],

      shopObj: {
        id: "",
        name: "",
        // 公告
        bulletin: "",
        // 店铺图像
        avatar: "",
        // 配送费
        deliveryPrice: "",
        // 配送时间
        deliveryTime: "",
        // 描述
        description: "",
        // 评分
        score: "",
        // 销量
        sellCount: "",
        // 活动
        supports: "",
        // 店铺图
        pics: "",
        // 营业时间
        date: "",
        // 起送价格
        minPrice: "",
      },
      checkedShop: {
        name: [// 自定义的
          { required: true, message: '商品名称不能为空', trigger: 'blur' },],
        bulletin: [{ required: true, message: '店铺公告不能为空', trigger: 'blur' },],
        deliveryPrice: [{ required: true, message: '配送费不能为空', trigger: 'blur' },],
        deliveryTime: [{ required: true, message: '配送时间不能为空', trigger: 'blur' },],
        description: [{ required: true, message: '配送描述不能为空', trigger: 'blur' },],
        score: [{ required: true, message: '店铺评分不能为空', trigger: 'blur' },],
        sellCount: [{ required: true, message: '销量不能为空', trigger: 'blur' },],
      },
    }
  },
  methods: {
    // 获取店铺信息
    async getShopInfo() {
      let res = await api_ShopInfo()
      //console.log(res);
      // this.shopObj = res.data
      // 如果是数组的 需要进行解构再显示数据
      let { data } = res
      this.shopObj = data
      this.headerImgUrl = data.avatar
      // 图片需要进行处理
      this.fileList = data.pics.map((item) => {
        return {
          // fileList要求返回的name+url地址
          // name就是每一项 url就是上传地址加每一项
          name: item,
          url: "http://119.23.246.178:80/upload/shop/" + item
        }
        // map返回的是一个对象
      })
      this.actionList = data.supports
      this.yytime = data.date
    },
    // 保存修改店铺信息
    async toupdateShop() {
      // 此时数组需要进行转化成一个单引号的字符串
      // 因为后端要得是一个名字 我们需要在进行转化到name
      this.shopObj.pics = JSON.stringify(this.fileList.map((item) => {
        return item.name
      }))
      // 活动
      this.shopObj.supports = JSON.stringify(this.actionList)
      // 时间
      this.shopObj.date = JSON.stringify(this.yytime)
      // 处理之后才能把值塞进去
      let res = await api_ShopEdit(this.shopObj)
      if (res.code == 0) {
        this.$message.success(res.msg)
        this.$router.go(0)
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    // 这里是图像图片 自己起个名字
    headerSuccess(res, file) {
      if (res.code == 0) {
        this.$message.success(res.msg)
        // 图片显示 我们得到的图片路径是名称 我们要拼接 第一种是在js中拼接 在hetl拼接
        this.headerImgUrl = res.imgUrl
        // 讲图片路径存起来
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    beforeAvatarUpload(file) {
      // 后面||多个图片上传
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },
    // 这是店铺图片 多图片开始
    // 图片上传成功之后
    imgsSuccess(res, file) {
      if (res.code == 0) {
        // 这个是多图片成功之后要把图片放到我们的数组中
        this.fileList.push({
          name: res.imgUrl,
          url: "http://119.23.246.178:80/upload/shop/" + res.imgUrl,
        });
        // this.$message.success(res.msg)
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    // 图片移除触发事件
    handleRemove(file, filst) {
      // file 删除的图片信息
      // filst 剩下的图片信息  this.fileList 是我们自己封装的
      // 从后端拿到的数据需要进行转化
      // 删除的信息 再返回给我们封装的
      // map返回一个新数组反还给this.fileList
      console.log(file, filst);
      this.fileList = filst.map((item) => {
        return {
          name: item.name,
          url: item.url
        }
      })
    },
    // 这是看预览图的不用修改
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    // 多图片结束
  },
  created() {
    this.getShopInfo();
  },
}
</script>

订单管理

1.订单管理

1.订单列表,订单查看,订单修改

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <el-form style="width:100%" label-width="80px" size="small" :model="orderList">
          <div class="orderbox">
            <div class="orderson">
              <el-form-item label="订单号">
                <el-input type="text" placeholder="订单号" v-model="orderList.orderNo"></el-input>
              </el-form-item>
            </div>
            <div class="orderson">
              <el-form-item label="收货人">
                <el-input type="text" placeholder="收货人" v-model="orderList.consignee"></el-input>
              </el-form-item>
            </div>
            <div class="orderson">
              <el-form-item label="手机号">
                <el-input type="text" placeholder="手机号" v-model="orderList.phone"></el-input>
              </el-form-item>
            </div>
            <div class="orderson">
              <el-form-item label="订单状态">
                <el-select v-model="orderList.orderState">
                  <el-option value="已派送" label="已派送"></el-option>
                  <el-option value="已完成" label="已完成"></el-option>
                  <el-option value="已受理" label="已受理"></el-option>
                  <el-option value="派送中" label="派送中"></el-option>
                </el-select>
              </el-form-item>
            </div>
            <div class="orderson">
              <el-form-item label="选择时间">
                <el-date-picker v-model="orderTime" type="datetimerange" range-separator="至" start-placeholder="开始日期"
                  end-placeholder="结束日期">
                </el-date-picker>
              </el-form-item>
            </div>
            <div class="orderson">
              <el-form-item>
                <el-button type="success" @click="toSearch">查询</el-button>
              </el-form-item>
            </div>
            <el-form-item>
              <el-button type="primary" icon="el-icon-refresh" @click="toLoading">刷新</el-button>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="exportData">导出表格</el-button>
            </el-form-item>
          </div>
        </el-form>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-table :data="tableData" max-height="600" border style="width: 100%">
          <el-table-column prop="orderNo" label="订单号" width="200">
          </el-table-column>
          <!-- 遇到这种时间问题 用default row -->
          <el-table-column label="下单时间" width="180">
            <template #default="{ row }">
              {{ row.orderTime | filterGetTime }}
            </template>
          </el-table-column>
          <el-table-column prop="phone" label="手机号" width="150">
          </el-table-column>
          <el-table-column prop="consignee" label="收货人" width="120">
          </el-table-column>
          <el-table-column prop="deliverAddress" label="配送地址" width="300">
          </el-table-column>
          <el-table-column label="配送时间" width="200">
            <template #default="{ row }">
              {{ row.deliveryTime | filterGetTime }}
            </template>
          </el-table-column>
          <el-table-column prop="remarks" label="用户备注" width="100">
          </el-table-column>
          <el-table-column prop="orderAmount" label="订单金额" width="100">
          </el-table-column>
          <el-table-column prop="orderState" label="订单状态" width="100">
          </el-table-column>
          <el-table-column fixed="right" label="操作" width="200">
            <template #default="{ row }">
              <el-button type="primary" size="small" @click="dealis(row)">查看</el-button>
              <el-button type="success" size="small" @click="huixian(row)">编辑</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页 -->
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
          :current-page="orderList.currentPage" :page-sizes="[1, 2, 3, 5, 10, 20, 30]" :page-size="orderList.pageSize"
          layout="total, sizes, prev, pager, next, jumper" :total="total">
        </el-pagination>

        <!-- 修改订单 -->
        <el-dialog title="修改订单信息" :visible.sync="dialogFormVisible" :closeOnClickModal="false">
          <el-form style="width:30%" label-width="80px" size="small" :model="orderObj">
            <el-form-item label="商品ID:" prop="id">
              <el-input type="text" placeholder="请修改商品ID" v-model.trim="orderObj.id" :disabled="true"></el-input>
            </el-form-item>
            <el-form-item label="订单编号:" prop="orderNo">
              <el-input type="text" placeholder="请修改订单号" v-model.trim="orderObj.orderNo"></el-input>
            </el-form-item>
            <el-form-item label="下单时间:" prop="orderTime">
              <el-date-picker v-model="orderObj.orderTime" value-format="yyyy-MM-DD HH:mm:ss" type="datetimerange"
                range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :disabled="true">
              </el-date-picker>
            </el-form-item>
            <el-form-item label="联系电话:" prop="phone">
              <el-input type="text" placeholder="请修改用户电话" v-model.trim="orderObj.phone"></el-input>
            </el-form-item>
            <el-form-item label="收货人:" prop="consignee">
              <el-input type="text" placeholder="请修改收货人名称" v-model.trim="orderObj.consignee"></el-input>
            </el-form-item>
            <el-form-item label="送货地址:" prop="deliverAddress">
              <el-input type="text" placeholder="请修改收货人地址" v-model.trim="orderObj.deliverAddress"></el-input>
            </el-form-item>
            <el-form-item label="送达时间:" prop="deliveryTime">
              <el-date-picker v-model="orderObj.deliveryTime" value-format="yyyy-MM-DD HH:mm:ss" type="datetimerange"
                range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :disabled="true">
              </el-date-picker>
            </el-form-item>
            <el-form-item label="用户备注:" prop="remarks">
              <el-input type="text" placeholder="请修改用户备注" v-model.trim="orderObj.remarks"></el-input>
            </el-form-item>
            <el-form-item label="订单金额:" prop="orderAmount">
              <el-input type="text" placeholder="请修改订单金额" v-model.trim="orderObj.orderAmount"></el-input>
            </el-form-item>
            <el-form-item label="订单状态:" prop="orderState">
              <el-input type="text" placeholder="请修改订单状态" v-model.trim="orderObj.orderState"></el-input>
            </el-form-item>
            <el-button size="mini" type="primary" @click="dialogFormVisible = false">取消</el-button>
            <el-button size="mini" type="success" @click="toUpdata">确定</el-button>
          </el-form>
        </el-dialog>
        <!-- 订单详情 -->
        <el-dialog title="订单详情" :visible.sync="dialogVisible" width="30%" :closeOnClickModal="false">
          <span>订单详情内容</span>
          <span slot="footer" class="dialog-footer">
            <p>ID:{{ orderObj.id }}</p>
            <el-divider></el-divider>
            <p>订单编号:{{ orderObj.orderNo }}</p>
            <el-divider></el-divider>
            <p>下单时间:{{ orderObj.orderTime | chuliTime }}</p>
            <el-divider></el-divider>
            <p>手机号:{{ orderObj.phone }}</p>
            <el-divider></el-divider>
            <p>收货人:{{ orderObj.consignee }}</p>
            <el-divider></el-divider>
            <p>收货地址:{{ orderObj.deliverAddress }}</p>
            <el-divider></el-divider>
            <p>送达时间:{{ orderObj.deliveryTime | chuliTime }}</p>
            <el-divider></el-divider>
            <p>用户备注:{{ orderObj.remarks }}</p>
            <el-divider></el-divider>
            <p>订单金额:{{ orderObj.orderAmount }}</p>
            <el-divider></el-divider>
            <p>订单状态:{{ orderObj.orderState }}</p>
            <el-divider></el-divider>
            <el-button @click="dialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
          </span>
        </el-dialog>
      </template>
    </comm-card>
  </div>
</template>

<script>
import { api_OrderList, api_OrderEdit, } from "../../api/order.js"
import commCard from "../../components/commCard"
import moment from "moment"
import { export2Excel } from "../../common/js/util.js"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      dialogFormVisible: false,
      dialogVisible: false,
      total: 0,
      options: [],
      tableData: [],
      orderTime: [],
      deliveryTime: [],
      // 订单详情
      orderList: {
        orderNo: "",
        consignee: "",
        date: "",
        phone: "",
        orderState: "",
        currentPage: 1,
        pageSize: 5,
      },
      // 修改订单
      orderObj: {
        id: "",
        orderNo: "",
        orderTime: [],
        phone: "",
        consignee: "",
        deliverAddress: "",
        deliveryTime: [],
        remarks: "",
        orderAmount: "",
        orderState: "",
      },
      columns: [
        { title: "订单ID", key: "id" },
        { title: "订单号", key: "orderNo" },
        { title: "下单时间", key: "orderTime" },
        { title: "手机号", key: "phone" },
        { title: "联系人", key: "consignee" },
        { title: "联系地址", key: "deliverAddress" },
        { title: "送达时间", key: "deliveryTime" },
        { title: "用户备注", key: "remarks" },
        { title: "订单金额", key: "orderAmount" },
        { title: "订单状态", key: "orderState" },
      ]
    }
  },
  methods: {
    // 导出表格
    exportData() {
      export2Excel(this.columns, this.tableData, "订单列表")
    },
    // 刷新
    toLoading() {
      this.getOrderList();
    },
    // 获取订单列表
    async getOrderList() {
      let res = await api_OrderList(this.orderList)
      this.tableData = res.data
      this.total = res.total
      // //console.log(res);
    },
    //条数改变出发
    handleSizeChange(val) {
      // console.log(`每页 ${val} 条`);
      this.orderList.pageSize = val
      this.getOrderList();
    },
    //当前页改变出发
    handleCurrentChange(val) {
      // console.log(`当前页: ${val}`);
      this.orderList.currentPage = val
      this.getOrderList();
    },
    // 修改订单
    huixian(row) {
      this.orderObj.id = row.id
      this.orderObj.orderNo = row.orderNo
      // 回显的时候前面需要2个时间
      this.orderObj.orderTime[0] = moment(row.orderTime).format("yyyy-MM-DD HH:mm:ss")
      this.orderObj.orderTime[1] = moment(row.orderTime).format("yyyy-MM-DD HH:mm:ss")
      this.orderObj.deliveryTime[0] = moment(row.deliveryTime).format("yyyy-MM-DD HH:mm:ss")
      this.orderObj.deliveryTime[1] = moment(row.deliveryTime).format("yyyy-MM-DD HH:mm:ss")
      this.orderObj.phone = row.phone
      this.orderObj.consignee = row.consignee
      this.orderObj.deliverAddress = row.deliverAddress
      this.orderObj.remarks = row.remarks
      this.orderObj.orderAmount = row.orderAmount
      this.orderObj.orderState = row.orderState
      this.dialogFormVisible = true
    },
    // 订单详情
    dealis(row) {
      this.orderObj.id = row.id
      this.orderObj.orderNo = row.orderNo
      this.orderObj.orderTime = row.orderTime
      this.orderObj.deliveryTime = row.deliveryTime
      this.orderObj.phone = row.phone
      this.orderObj.consignee = row.consignee
      this.orderObj.deliverAddress = row.deliverAddress
      this.orderObj.remarks = row.remarks
      this.orderObj.orderAmount = row.orderAmount
      this.orderObj.orderState = row.orderState
      this.dialogVisible = true
    },
    // 搜索查询
    toSearch() {
      // 把时间进行格式化,然后存到data中
      this.orderObj.date = JSON.stringify(this.orderTime);
      this.orderList.data = ""
      // 查询其实就是把获取的数据刷新一下
      this.getOrderList();
    },
    // 修改数据
    async toUpdata() {
      // 传参的时候需要处理 后端只需要一个
      this.orderObj.orderTime = this.orderObj.orderTime[0]
      this.orderObj.deliveryTime = this.orderObj.deliveryTime[0]
      let res = await api_OrderEdit(this.orderObj)
      console.log(res);
      if (res.code == 0) {
        this.$message.success(res.msg)
        // this.getOrderList();
        this.dialogFormVisible = false
        // this.$router.go(0)
      } else {
        this.$message.error(res.msg)
      }
    },
  },
  created() {
    this.getOrderList();
  },
}
</script>

<style lang="less" scoped>
p {
  text-align: left;
}

.el-select {
  width: 100%;
}

.orderbox {
  width: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: center;

  .orderson {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

.el-form-item__content {
  margin-left: 0px !important;
}
</style>

2.包括excel的导出,

 注意:首先需要下载2个文件,网上可以自行下载

 3.在scr目录下新建一个文件夹,新建一个文件,封装excel方法

// export2Excel(columns, list, name)就是生成到处的excel表格的
// columns 是表头 name是导出的表格名字
export function export2Excel(columns, list, name) {
  require.ensure([], () => {
    const { export_json_to_excel } = require("../../excel/Export2Excel");
    let tHeader = [];
    let filterVal = [];
    console.log(columns);
    if (!columns) {
      return;
    }
    columns.forEach((item) => {
      tHeader.push(item.title);
      filterVal.push(item.key);
    });
    const data = list.map((v) => filterVal.map((j) => v[j]));
    export_json_to_excel(tHeader, data,name);
  });
}

Blob.js文件

/* eslint-disable */
/* Blob.js*/

/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
  plusplus: true */

/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */

(function (view) {
    "use strict";

    view.URL = view.URL || view.webkitURL;

    if (view.Blob && view.URL) {
        try {
            new Blob;
            return;
        } catch (e) { }
    }

    // Internally we use a BlobBuilder implementation to base Blob off of
    // in order to support older browsers that only have BlobBuilder
    var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function (view) {
        var
            get_class = function (object) {
                return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
            },
            FakeBlobBuilder = function BlobBuilder() {
                this.data = [];
            },
            FakeBlob = function Blob(data, type, encoding) {
                this.data = data;
                this.size = data.length;
                this.type = type;
                this.encoding = encoding;
            },
            FBB_proto = FakeBlobBuilder.prototype,
            FB_proto = FakeBlob.prototype,
            FileReaderSync = view.FileReaderSync,
            FileException = function (type) {
                this.code = this[this.name = type];
            },
            file_ex_codes = (
                "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " +
                "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
            ).split(" "),
            file_ex_code = file_ex_codes.length,
            real_URL = view.URL || view.webkitURL || view,
            real_create_object_URL = real_URL.createObjectURL,
            real_revoke_object_URL = real_URL.revokeObjectURL,
            URL = real_URL,
            btoa = view.btoa,
            atob = view.atob

            ,
            ArrayBuffer = view.ArrayBuffer,
            Uint8Array = view.Uint8Array

            ,
            origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;
        FakeBlob.fake = FB_proto.fake = true;
        while (file_ex_code--) {
            FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
        }
        // Polyfill URL
        if (!real_URL.createObjectURL) {
            URL = view.URL = function (uri) {
                var
                    uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a"),
                    uri_origin;
                uri_info.href = uri;
                if (!("origin" in uri_info)) {
                    if (uri_info.protocol.toLowerCase() === "data:") {
                        uri_info.origin = null;
                    } else {
                        uri_origin = uri.match(origin);
                        uri_info.origin = uri_origin && uri_origin[1];
                    }
                }
                return uri_info;
            };
        }
        URL.createObjectURL = function (blob) {
            var
                type = blob.type,
                data_URI_header;
            if (type === null) {
                type = "application/octet-stream";
            }
            if (blob instanceof FakeBlob) {
                data_URI_header = "data:" + type;
                if (blob.encoding === "base64") {
                    return data_URI_header + ";base64," + blob.data;
                } else if (blob.encoding === "URI") {
                    return data_URI_header + "," + decodeURIComponent(blob.data);
                }
                if (btoa) {
                    return data_URI_header + ";base64," + btoa(blob.data);
                } else {
                    return data_URI_header + "," + encodeURIComponent(blob.data);
                }
            } else if (real_create_object_URL) {
                return real_create_object_URL.call(real_URL, blob);
            }
        };
        URL.revokeObjectURL = function (object_URL) {
            if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
                real_revoke_object_URL.call(real_URL, object_URL);
            }
        };
        FBB_proto.append = function (data /*, endings*/) {
            var bb = this.data;
            // decode data to a binary string
            if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
                var
                    str = "",
                    buf = new Uint8Array(data),
                    i = 0,
                    buf_len = buf.length;
                for (; i < buf_len; i++) {
                    str += String.fromCharCode(buf[i]);
                }
                bb.push(str);
            } else if (get_class(data) === "Blob" || get_class(data) === "File") {
                if (FileReaderSync) {
                    var fr = new FileReaderSync;
                    bb.push(fr.readAsBinaryString(data));
                } else {
                    // async FileReader won't work as BlobBuilder is sync
                    throw new FileException("NOT_READABLE_ERR");
                }
            } else if (data instanceof FakeBlob) {
                if (data.encoding === "base64" && atob) {
                    bb.push(atob(data.data));
                } else if (data.encoding === "URI") {
                    bb.push(decodeURIComponent(data.data));
                } else if (data.encoding === "raw") {
                    bb.push(data.data);
                }
            } else {
                if (typeof data !== "string") {
                    data += ""; // convert unsupported types to strings
                }
                // decode UTF-16 to binary string
                bb.push(unescape(encodeURIComponent(data)));
            }
        };
        FBB_proto.getBlob = function (type) {
            if (!arguments.length) {
                type = null;
            }
            return new FakeBlob(this.data.join(""), type, "raw");
        };
        FBB_proto.toString = function () {
            return "[object BlobBuilder]";
        };
        FB_proto.slice = function (start, end, type) {
            var args = arguments.length;
            if (args < 3) {
                type = null;
            }
            return new FakeBlob(
                this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding
            );
        };
        FB_proto.toString = function () {
            return "[object Blob]";
        };
        FB_proto.close = function () {
            this.size = 0;
            delete this.data;
        };
        return FakeBlobBuilder;
    }(view));

    view.Blob = function (blobParts, options) {
        var type = options ? (options.type || "") : "";
        var builder = new BlobBuilder();
        if (blobParts) {
            for (var i = 0, len = blobParts.length; i < len; i++) {
                if (Uint8Array && blobParts[i] instanceof Uint8Array) {
                    builder.append(blobParts[i].buffer);
                } else {
                    builder.append(blobParts[i]);
                }
            }
        }
        var blob = builder.getBlob(type);
        if (!blob.slice && blob.webkitSlice) {
            blob.slice = blob.webkitSlice;
        }
        return blob;
    };

    var getPrototypeOf = Object.getPrototypeOf || function (object) {
        return object.__proto__;
    };
    view.Blob.prototype = getPrototypeOf(new view.Blob());
}(
    typeof self !== "undefined" && self ||
    typeof window !== "undefined" && window ||
    this
));

Export2Excel.js文件

/* eslint-disable */
require('script-loader!file-saver');
// require('script-loader!./Blob');
const {blob} = require('./Blob')
require('script-loader!xlsx/dist/xlsx.core.min');
function generateArray(table) {
    var out = [];
    var rows = table.querySelectorAll('tr');
    var ranges = [];
    for (var R = 0; R < rows.length; ++R) {
        var outRow = [];
        var row = rows[R];
        var columns = row.querySelectorAll('td');
        for (var C = 0; C < columns.length; ++C) {
            var cell = columns[C];
            var colspan = cell.getAttribute('colspan');
            var rowspan = cell.getAttribute('rowspan');
            var cellValue = cell.innerText;
            if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;

            //Skip ranges
            ranges.forEach(function (range) {
                if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
                    for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
                }
            });

            //Handle Row Span
            if (rowspan || colspan) {
                rowspan = rowspan || 1;
                colspan = colspan || 1;
                ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}});
            }
            ;

            //Handle Value
            outRow.push(cellValue !== "" ? cellValue : null);

            //Handle Colspan
            if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
        }
        out.push(outRow);
    }
    return [out, ranges];
};

function datenum(v, date1904) {
    if (date1904) v += 1462;
    var epoch = Date.parse(v);
    return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}

function sheet_from_array_of_arrays(data, opts) {
    var ws = {};
    var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
    for (var R = 0; R != data.length; ++R) {
        for (var C = 0; C != data[R].length; ++C) {
            if (range.s.r > R) range.s.r = R;
            if (range.s.c > C) range.s.c = C;
            if (range.e.r < R) range.e.r = R;
            if (range.e.c < C) range.e.c = C;
            var cell = {v: data[R][C]};
            if (cell.v == null) continue;
            var cell_ref = XLSX.utils.encode_cell({c: C, r: R});

            if (typeof cell.v === 'number') cell.t = 'n';
            else if (typeof cell.v === 'boolean') cell.t = 'b';
            else if (cell.v instanceof Date) {
                cell.t = 'n';
                cell.z = XLSX.SSF._table[14];
                cell.v = datenum(cell.v);
            }
            else cell.t = 's';

            ws[cell_ref] = cell;
        }
    }
    if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
    return ws;
}

function Workbook() {
    if (!(this instanceof Workbook)) return new Workbook();
    this.SheetNames = [];
    this.Sheets = {};
}

function s2ab(s) {
    var buf = new ArrayBuffer(s.length);
    var view = new Uint8Array(buf);
    for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
}

export function export_table_to_excel(id) {
    var theTable = document.getElementById(id);
    console.log('a')
    var oo = generateArray(theTable);
    var ranges = oo[1];

    /* original data */
    var data = oo[0];
    var ws_name = "SheetJS";
    console.log(data);

    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);

    /* add ranges to worksheet */
    // ws['!cols'] = ['apple', 'banan'];
    ws['!merges'] = ranges;

    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;

    var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});

    saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
}

function formatJson(jsonData) {
    console.log(jsonData)
}
export function export_json_to_excel(th, jsonData, defaultTitle) {

    /* original data */

    var data = jsonData;
    data.unshift(th);
    var ws_name = "SheetJS";

    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);


    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;

    var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
    var title = defaultTitle || '�б�'
    saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
}

店铺首页

1.店铺首页

<template>
  <div style="height:100%">
    <!-- 上边的数据 -->
    <el-row class="content">
      <home-lump v-for="(item, index) in homeList" :key="index " :lumpObj="item"></home-lump>
    </el-row>

    <!-- 下边的数据 -->
    <el-row style="height:70%;margin-top:30px;background-color:#fff;">
      <el-col :span="24 " style="height:100%">
        <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
        <div id="main" style="width: 100%;height:100%;"></div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import { api_queryHome } from "../../api/home";
import homeLump from "../../components/homeLump";
import * as echarts from "echarts";
export default {
  components: {
    homeLump
  },
  data() {
    return {
      homeList: [
        {
          icon: "iconfont icon-dingdan1",
          title: "总订单",
          totalData: 0,
          color: "#00f"
        },
        {
          icon: "iconfont icon-dangrixiaoshoue",
          title: "总销售额",
          totalData: 0,
          color: "#d4237a"
        },
        {
          icon: "iconfont icon-dingdan",
          title: "今日订单数",
          totalData: 0,
          color: "#1296db"
        },
        {
          icon: "iconfont icon-a-icon-xiaoshoue-fuben",
          title: "今日销售额",
          totalData: 0,
          color: "#1afa29"
        }
      ],
      time: [],
      orderData: [],
      amountData: []
    };
  },
  created() {
    this.queryHome();
  },
  // mouted请求不到数据,此时用监听的形式
  watch: {
    orderData: {
      deep: true,
      handler() {
        this.showEcharts();
      }
    }
  },
  methods: {
    // 初始化
    async queryHome() {
      let res = await api_queryHome();
      //console.log(res);
      let {
        todayOrder,
        totalAmount,
        totalOrder,
        totayAmount,
        amountData, //销售
        orderData, //订单
        xData //时间
      } = res;
      this.time = xData; //X轴的时间
      this.orderData = orderData; //订单的数据
      this.amountData = amountData; // 销售的数据
      // var arr = [1, 2, 3, 5]
      // arr[0]  arr[1]
      // [1, 2, 3, 5][0] [1, 2, 3, 5][1]
      this.homeList.forEach((item, index) => {
        item.totalData = [totalOrder, totalAmount, todayOrder, totayAmount][
          index
        ];
      });
    },
    // 显示图表
    showEcharts() {
      // echarts开始
      var chartDom = document.getElementById("main");
      var myChart = echarts.init(chartDom);
      // console.log(this.orderData, this.amountData);
      var option;
      option = {
        title: {
          text: "数据统计"
        },
        tooltip: {
          trigger: "axis"
        },
        legend: {
          data: ["订单", "销售额", "注册人数", "管理员人数"]
        },
        grid: {
          left: "3%",
          right: "4%",
          bottom: "3%",
          containLabel: true
        },
        toolbox: {
          feature: {
            saveAsImage: {}
          }
        },
        xAxis: {
          type: "category",
          boundaryGap: false,
          data: this.time
        },
        yAxis: {
          type: "value"
        },
        series: [
          {
            name: "订单",
            type: "line",
            stack: "Total",
            data: this.orderData
          },
          {
            name: "销售额",
            type: "line",
            stack: "Total",
            data: this.amountData
          },
          {
            name: "注册人数",
            type: "line",
            stack: "Total",
            data: [100,200,300,400,500,600,200]
          },
          {
            name: "管理员人数",
            type: "line",
            stack: "Total",
            data: [200,50,90,10,30,600,10]
          }
        ]
      };
      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
      // echarts结束
    }
  }
};
</script>

<style lang="less" scoped>
.content {
  width: 100%;
  display: flex;
  // 分散对齐
  justify-content: space-around;
}
</style>

商品管理

1.商品添加

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <span>商品添加</span>
      </template>

      <!-- 身体部分 -->
      <template #contentCard>
        <el-form label-width="80px" style="width:18%" size="small" :model="goodsObj" :rules="checkedGoods"
          ref="goodsInfo">
          <el-form-item label="商品名称" prop="name">
            <el-input type="text" v-model.trim="goodsObj.name" placeholder="请输入商品名称"></el-input>
          </el-form-item>
          <el-form-item label="商品分类">
            <el-select style="width:100%" placeholder="请选择商品分类" v-model="goodsObj.category">
              <el-option :label="item.cateName" :value="item.cateName" v-for="(item, index) in goodsTypeList "
                :key="index"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="商品价格">
            <template>
              <el-input-number :precision="2" :step="0.01" :min="0" v-model="goodsObj.price"></el-input-number>
            </template>
          </el-form-item>
          <el-form-item label="商品图片">
            <el-upload v-model="goodsObj.imgUrl" class="avatar-uploader"
              action="http://127.0.0.1:5000/goods/goods_img_upload" :show-file-list="false"
              :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
              <img v-if="imageUrl" :src="'http://127.0.0.1:5000/upload/imgs/goods_img/' + imageUrl" class="avatar">
              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            </el-upload>
          </el-form-item>
          <el-form-item label="商品描述" prop="goodsDesc">
            <el-input :rows="5" placeholder="请输入商品描述内容" type="textarea" v-model.trim="goodsObj.goodsDesc"></el-input>
          </el-form-item>
          <el-form-item label="">
            <el-button type="primary" @click="goodsAdd">商品添加</el-button>
          </el-form-item>
        </el-form>
      </template>

    </comm-card>
  </div>
</template>

<script>
import { api_GoodsAdd, api_categories } from "../../api/goods.js"
import commCard from "../../components/commCard"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      imageUrl: "",
      goodsObj: {
        name: "",
        category: "",
        price: "0.01",
        imgUrl: "",
        goodsDesc: ""
      },
      goodsTypeList: [],
      checkedGoods: {
        name: [// 自定义的
          { required: true, message: '商品名称不能为空', trigger: 'blur' },],
        goodsDesc: [{ required: true, message: '商品描述不能为空', trigger: 'blur' },]
      },
    }
  },
  created() {
    // 请求商品分类列表 显示到下拉框中
    this.getGoods();
  },
  methods: {
    async getGoods() {
      let res = await api_categories()
      // //console.log(res);
      this.goodsTypeList = res.categories
    },
    handleAvatarSuccess(res, file) {
      // 给一个弹框判断是否上传成功
      if (res.code == 0) {
        this.$message.success(res.msg)
        this.imageUrl = res.imgUrl
        // 图片上传之后要给data绑定赋值
        this.goodsObj.imgUrl = this.imageUrl
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    beforeAvatarUpload(file) {
      // 后面||多个图片上传
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif';
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },
    async goodsAdd() {
      let res = await api_GoodsAdd(this.goodsObj)
      // //console.log(res);
      if (res.code == 0) {
        this.$message.success(res.msg)
        this.getGoods();
        this.$router.push("/goods/list")
        this.resetfn()
      } else {
        this.$message.error(res.msg)
        return false
      }
    },
    // 重置信息
    resetfn() {
      // 此时需要绑定ref 在el-from上
      this.$refs.goodsInfo.resetFields();
    }
  }
}
</script>

<style lang="less" scoped>
// 图片没有边框给个穿透
/deep/.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;
}
</style>

2.商品列表

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <el-form ref="userFrom">
          <div class="goodsbox">
            <div class="search">
              <span>商品名称:</span>
              <el-input style="width:30%" v-model.trim="name"></el-input>
              <el-select v-model="category">
                <el-option v-for="(item, index) in goodsTypeList" :key="index" :label="item.cateName"
                  :value="item.cateName"></el-option>
              </el-select>
              <el-button type="primary" @click="findGoods">搜索</el-button>
            </div>
          </div>
        </el-form>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-table :data="tableData" style="width: 100%" height="650px">
          <el-table-column type="expand">
            <template slot-scope="props">
              <el-form label-position="left" inline class="demo-table-expand">
                <el-form-item label="商品ID:">
                  <span>{{ props.row.id }}</span>
                </el-form-item>
                <el-form-item label="商品名称:">
                  <span>{{ props.row.name }}</span>
                </el-form-item>
                <el-form-item label="所属分类:">
                  <span>{{ props.row.category }}</span>
                </el-form-item>
                <el-form-item label="商品价格:">
                  <span>{{ props.row.price }}</span>
                </el-form-item>
                <el-form-item label="商品图片">
                  <img :src="'http://127.0.0.1:5000/upload/imgs/goods_img/' + props.row.imgUrl" alt="">
                </el-form-item>
                <el-form-item label="创建时间:">
                  <span>{{ props.row.ctime | chuliTime }}</span>
                </el-form-item>
                <el-form-item label="商品评价:">
                  <span>{{ props.row.rating }}</span>
                </el-form-item>
                <el-form-item label="商品销量:">
                  <span>{{ props.row.sellCount }}</span>
                </el-form-item>
                <el-form-item label="商品描述:">
                  <span>{{ props.row.goodsDesc }}</span>
                </el-form-item>
              </el-form>
            </template>
          </el-table-column>
          <el-table-column label="商品名称" prop="name">
          </el-table-column>
          <el-table-column label="所属分类" prop="category">
          </el-table-column>
          <el-table-column label="商品价格" prop="price">
          </el-table-column>
          <el-table-column label="商品图片">
            <template #default="{ row }">
              <img :src="'http://127.0.0.1:5000/upload/imgs/goods_img/' + row.imgUrl" alt="">
            </template>
          </el-table-column>
          <el-table-column label="商品描述" prop="goodsDesc">
          </el-table-column>
          <el-table-column label="操作">
            <template slot-scope="{row}">
              <el-button size="mini" type="success" @click="huixian(row)">编辑</el-button>
              <el-button size="mini" type="danger" @click="delGoods(row.id)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 页码 -->
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage"
          :page-sizes="[1, 2, 3, 4, , 5, 6, 7, 8, 9, 10, 30, 40, 50]" :page-size="pageSize"
          layout="total, sizes, prev, pager, next, jumper" :total="total">
        </el-pagination>
        <!-- 要放在table外面 -->
        <!-- 修改商品 -->
        <el-dialog title="修改商品" :visible.sync="dialogFormVisible" :closeOnClickModal="false">
          <el-form :model="goodsList" label-width="100px" :rules="checkedGoods" ref="goodsInfo" style="width:50%">
            <el-form-item label="商品名字" prop="name">
              <el-input v-model="goodsList.name" autocomplete="off"></el-input>
            </el-form-item>
            <el-form-item label="商品分类">
              <el-select style="width:100%" placeholder="请选择商品分类" v-model="goodsList.category">
                <el-option :label="item.cateName" :value="item.cateName" v-for="(item, index) in goodsTypeList"
                  :key="index">
                </el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="商品价格">
              <template>
                <el-input-number :precision="2" :step="0.1" :min="0" v-model="goodsList.price"></el-input-number>
              </template>
            </el-form-item>
            <el-form-item label="商品图片">
              <!-- 这个是从后端拿的请求图片数据 -->
              <el-upload v-model="goodsList.imgUrl" class="avatar-uploader"
                action="http://127.0.0.1:5000/goods/goods_img_upload" :show-file-list="false"
                :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
                <img :src="'http://127.0.0.1:5000/upload/imgs/goods_img/' + goodsList.imgUrl" class="avatar">
              </el-upload>
            </el-form-item>
            <el-form-item label="商品描述" prop="goodsDesc">
              <el-input :rows="3" placeholder="请输入商品描述内容" type="textarea" v-model.trim="goodsList.goodsDesc"></el-input>
            </el-form-item>
          </el-form>
          <div slot="footer" class="dialog-footer">
            <el-button @click="dialogFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="toUpdata">确 定</el-button>
          </div>
        </el-dialog>
      </template>
    </comm-card>
  </div>
</template>
<script>
import { api_GoodsList, api_GoodsDel, api_categories, api_GoodsEdit } from "../../api/goods.js"
import commCard from "../../components/commCard"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      tableData: [],
      goodsTypeList: [],
      total: 0,
      currentPage: 1,
      pageSize: 5,
      imageUrl: "",
      name: "",
      category:"",
      goodsList: {
        // 商品id
        id: "",
        // 商品名称
        name: "",
        // 商品分类
        category: "",
        // 商品价格
        price: "",
        // 商品图片
        imgUrl: "",
        // 商品描述
        goodsDesc: "",
      },
      // 模态框开关
      dialogFormVisible: false,
      checkedGoods: {
        name: [// 自定义的
          { required: true, message: '商品名称不能为空', trigger: 'blur' },],
        goodsDesc: [{ required: true, message: '商品描述不能为空', trigger: 'blur' },]
      },
    }
  },
  methods: {
    // 初始化
    async getGoodsList() {
      let res = await api_GoodsList({
        currentPage: this.currentPage,
        pageSize: this.pageSize,
        name: this.name,
        category: this.category,
      })
      // //console.log(res);
      // 拿到数据
      this.tableData = res.data
      // 拿到总条数
      this.total = res.total
    },
    // 商品类型
    async getType() {
      let res = await api_categories()
      // 拿到分类
      this.goodsTypeList = res.categories
    },
    handleSizeChange(val) {
      // console.log(`每页 ${val} 条`);
      this.pageSize = val
      this.getGoodsList();
    },
    handleCurrentChange(val) {
      // console.log(`当前页: ${val}`);
      this.currentPage = val
      this.getGoodsList();
    },
    // 删除商品
    async delGoods(id) {
      let res = await api_GoodsDel({ id })
      // //console.log(res);
      if (confirm("确定删除这条数据吗")) {
        if (res.code == 0) {
          this.$message.success(res.msg)
          this.getGoodsList();
        } else {
          this.$message.error(res.msg)
          return false
        }
      }
    },
    // 商品回显
    huixian(row) {
      this.goodsList.id = row.id
      this.goodsList.name = row.name
      this.goodsList.category = row.category
      this.goodsList.price = row.price
      this.goodsList.imgUrl = row.imgUrl
      this.goodsList.goodsDesc = row.goodsDesc
      // 再弹出弹出框 不然会有空白期 体验不好
      this.dialogFormVisible = true
    },
    // 修改图片上传成功
    handleAvatarSuccess(res, file) {
      // 给一个弹框判断是否上传成功
      if (res.code == 0) {
        this.$message.success(res.msg)
        // 这个是拿到图片进行转化显示出来 尽量再下面进行拼接
        this.imageUrl = "http://127.0.0.1:5000/upload/imgs/goods_img/" + res.imgUrl
        // 拿到数据要把图片存到list里面
        this.goodsList.imgUrl = res.imgUrl
      } else {
        this.$message.error(res.msg)
      }
      //console.log(res);
    },
    // 上传之前
    beforeAvatarUpload(file) {
      // 后面||多个图片上传
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/webp';
      const isLt2M = file.size / 1024 / 1024 < 2;

      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/png/gif/webp 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    },
    // 修改商品
    async toUpdata() {
      let res = await api_GoodsEdit(this.goodsList)
      // //console.log(res);
      if (res.code == 0) {
        this.$message.success(res.msg)
        // 列表刷新
        this.getGoodsList();
        // 关闭模态框
        this.dialogFormVisible = false
      } else {
        this.$message.error(res.msg)
      }
    },
    // 条件查询 因为是双向绑定的 不需要塞值
    findGoods() {
      this.getGoodsList();
    },
  },
  created() {
    // 列表刷新
    this.getGoodsList();
    // 拿到分类
    this.getType()
  },
}
</script>

3.商品分类

<template>
  <div>
    <comm-card>
      <!-- 头部部分 -->
      <template #headerCard>
        <span>管理员信息</span>
        <el-button type="primary" @click="showAdd">添加分类</el-button>
        <el-dialog title="添加商品类别" :close-on-click-modal="false" :visible.sync="dialogVisible" width="30%">
          <div>
            <el-form label-width="80px" ref="goodsTypeForm" :model="goodsTypeObj" :rules="checkForm">
              <el-form-item label="分类名称" prop="cateName">
                <el-input v-model="goodsTypeObj.cateName"></el-input>
              </el-form-item>
              <el-form-item label="是否启用">
                <el-switch active-color="#13ce66" inactive-color="#ff4949" v-model="goodsTypeObj.state"></el-switch>
              </el-form-item>
            </el-form>
          </div>
          <span slot="footer" class="dialog-footer">
            <el-button @click="dialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="toSave">确 定</el-button>
          </span>
        </el-dialog>
      </template>
      <!-- 身体部分 -->
      <template #contentCard>
        <el-row>
          <el-col :span="24">
            <el-table height="550px" ref="singleTable" :data="tableData" highlight-current-row style="width: 100%">
              <el-table-column type="index" width="50" label="序号" :index="fn">
              </el-table-column>
              <el-table-column property="cateName" label="分类名称" width="180">
                <template #default="{ row }">
                  <span v-show="!row.flag">{{ row.cateName }}</span>
                  <el-input v-model="row.cateName" v-show="row.flag" type="text" :value="row.cateName"></el-input>
                </template>
              </el-table-column>
              <!-- 是否启用 这里是按钮地方 -->
              <el-table-column label="是否启用" width="100">
                <!-- 当我们需要对一行进行操作的时候,就需要记住用插槽 #default="{}" -->
                <!-- 当后台给的数据不是你想要的时候怎么办 -->
                <template #default="{ row }">
                  <el-switch :disabled="!row.flag" v-model="row.status" active-color="#13ce66" inactive-color="#ff4949">
                  </el-switch>
                </template>
              </el-table-column>
              <el-table-column label="操作">
                <template #default="{ row }">
                  <!-- handleClick  slot-scope="scope" -->
                  <el-button :type="row.flag == true ? 'success' : 'primary'" size="small" @click="toupdate(row)"
                    @input="handlerChange">
                    {{ row.flag == true ? '完成' : '修改' }}</el-button>
                  <el-button type="danger" size="small" @click="delType(row.id)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <!-- 分页吗 -->
            <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
              :current-page="listObj.currentPage" :page-sizes="[1, 2, 3, 4, 8]" :page-size="listObj.pageSize"
              layout="total, sizes, prev, pager, next, jumper" :total="total">
            </el-pagination>
          </el-col>
        </el-row>
      </template>
    </comm-card>
  </div>
</template>

<script>
import commCard from "../../components/commCard"
import { api_goodsTypeList, api_editcate, api_addcate, api_delcate } from "../../api/goods.js"
export default {
  components: {
    commCard,
  },
  data() {
    return {
      tableData: [],
      total: 0,
      id: "",
      isRewiriteEditor: false,//标志input框是否修改
      // 上面定义一个对象  下面直接this这个对象
      listObj: {
        currentPage: 1,
        pageSize: 3,
      },
      // 控制弹出框是否显示  false是代表不显示 true代表显示
      dialogVisible: false,
      goodsTypeObj: {
        cateName: "",
        state: false,
      },
      // 检验数据
      checkForm: {
        cateName: [{ required: true, message: '内容不能为空', trigger: 'blur' },
        { min: 3, max: 8, message: '长度在 3 到 8 个字符', trigger: 'blur' }]
      }
    }
  },
  created() {
    this.reqGoodsList();
  },
  methods: {
    fn(index) {
      // 第一页1234 第二页是5678 第三页 9 10 11 12
      // (当前页-1)*条数
      // 一开始index需要+1
      let num = (this.listObj.currentPage - 1) * this.listObj.pageSize + index + 1
      return num
    },
    async reqGoodsList() {
      /* 这里有一个小bug
      当删除数据的时候,页码变了
      但是数据没有变化,页面显示暂无数据
      是因为你删除了当前的数据之后瞬间发了一个请求
      异步请求请求刷新列表
      列表刷新的时候需要传一个当前页
      这里的当前页没有改变还是之前的当前页导致数据没有变
      解决方式 就是当前页减一 查询上一页的内容 */
      let res = await api_goodsTypeList(this.listObj)
      if (res.data.length == 0) {
        // 当前页减一 请求上一页的数据 否则执行其他程序
        this.listObj.currentPage--
        this.reqGoodsList();
      } else {
        // //console.log(res);
        // 获取后台数据,渲染到列表中
        // this.tableData = res.data
        // 发现后台的数据不能满足我们页面的显示 但是数据有关联
        // 需要true和false的属性 根据1 0 来 1代表true 0代表false
        // map将数组中的每一项取出来 最终返回的是一个新的数组
        this.tableData = res.data.map(function (item) {
          item.status = item.state == 1 ? true : false;
          // 我们分析得到,每一条数据都需要有一个状态属性去记录当前你是修改还是查看
          // item.flag专门记录当前你点击的是查看还是修改  来记录状态 每一行都有一个flag
          item.flag = false;
          return item
        })
        this.total = res.total
      }
    },
    // 放在方法里面
    handleSizeChange(val) {
      // 触发的条数
      // console.log(`每页 ${val} 条`);
      this.listObj.pageSize = val;
      // 调用回显
      this.reqGoodsList();
    },
    handleCurrentChange(val) {
      // 触发的是第几页
      // console.log(`当前页: ${val}`);
      // 同步currentPage的值
      this.listObj.currentPage = val;
      // 重新发请求
      this.reqGoodsList();
    },
    // 重写监听函数
    handlerChange(e) {
      console.log(e)
      this.isRewiriteEditor = true;
    },
    async toupdate(row) {
      // 进来这里就需要判断 row.flag当前是true还是false
      // 如果是true 需要使用接口修改数据库的值
      // 如果是false 刚点击修改按钮 让输入框显示 让开关可以修改
      if (row.flag) {
        let rule = /^[0-9a-zA-Z\u4E00-\u9FA5]{1,}$/
        if (!rule.test(row.cateName)) {
          this.$message.error("请输入正确的分类名称,至少有一个数字字母,中文组成")
          return;
        }
        let obj = {
          id: row.id,
          cateName: row.cateName,
          state: row.status
        }
        // 验证之后发请求
        let res = await api_editcate(obj)
        if (res.code == 0) {
          this.$message.success(res.msg)
          // 成功之后把状态改回来
          row.flag = false
        } else {
          this.$message.error(res.msg)
          return
        }
      } else {
        row.flag = true
      }
    },
    // 模态框
    showAdd() {
      this.dialogVisible = true
    },
    // 保存修改
    toSave() {
      // 如果设计到验证的都需要这么操作if进行判断
      //我们写到发现需要验证输入框的值是不是想要的
      this.$refs.goodsTypeForm.validate(async str => {
        if (str) {
          let res = await api_addcate(this.goodsTypeObj)
          //console.log(res);
          if (res.code == 0) {
            this.$message.success(res.msg)
            // 关闭模态框加数据刷新
            this.dialogVisible = false
            // 数据刷新
            this.reqGoodsList();
            // 数据清空
            this.resetfn()
          } else {
            this.$message.error(res.msg)
            return false
          }
        } else {
          return false
        }
      })
    },
    // 重置信息
    resetfn() {
      // 此时需要绑定ref 在el-from上
      this.$refs.goodsTypeForm.resetFields();
    },
    // 删除分类
    async delType(id) {
      if (confirm("确定删除这条数据吗")) {
        let res = await api_delcate({ id })
        // //console.log(res);
        if (res.code == 0) {
          this.$message.success(res.msg)
          this.reqGoodsList();
        } else {
          this.$message.error(res.msg)
          return false
        }
      }
    }
  },
}
</script>

<style lang="less" scoped>

</style>

销售统计

1.订单统计

<template>
  <div class="orderCensusbox">
    <div class="headerbox">
      <span>时间范围:</span>
      <!-- 时间框 年月日时分秒 -->
      <el-date-picker v-model="dateTime" type="datetimerange" range-separator="至" start-placeholder="开始日期"
        end-placeholder="结束日期" size="small">
      </el-date-picker>
      <el-button type="primary" size="small" @click="query">查询</el-button>
    </div>
    <!-- 图标区域 -->
    <div class="content" id="main"></div>
  </div>
</template>

<script>
import { api_ordertotal } from "../../api/report.js"
import * as echarts from 'echarts';
import moment from "moment";
export default {
  data() {
    return {
      dateTime: [],
    }
  },
  methods: {
    query() {
      // 查询就是重新调用一下数据
      // 因为数据已经双向绑定了
      // 你在改时间的时候js中的datetime已经同步了
      this.$nextTick(() => {
        this.showEcharts();
      });

    },
    async queryOrderCensus() {
      let res = await api_ordertotal({
        // 后端要得是字符型类型的数组 需要进行转化
        // 讲数组转化成字符串
        date: JSON.stringify(this.dateTime)
      })
      console.log(res);
      this.showEcharts(res.data)
    },
    showEcharts(orderdata) {
      var chartDom = document.getElementById('main');
      var myChart = echarts.init(chartDom);
      var option;

      option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'cross',
            crossStyle: {
              color: '#999'
            }
          }
        },
        toolbox: {
          feature: {
            dataView: { show: true, readOnly: false },
            magicType: { show: true, type: ['line', 'bar'] },
            restore: { show: true },
            saveAsImage: { show: true }
          }
        },
        legend: {
          data: ['订单量', '降水量', '平均温度']
        },
        xAxis: [
          {
            type: 'category',
            // data: ['', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            data: orderdata.map((ele) => {
              return moment(ele.orderTime).format("YYYY-MM-DD HH:mm:ss");
            }),
            axisPointer: {
              type: 'shadow'
            }
          }
        ],
        yAxis: [
          {
            type: 'value',
            name: '订单数量',
            min: 0,
            max: 300,
            // 订单数量高度
            interval: 100,
            // 订单数量间距
            axisLabel: {
              formatter: '{value} 个'
            }
          },
          {
            type: 'value',
            name: '数量',
            min: 0,
            max: 300,
            interval: 100,
            axisLabel: {
              formatter: '{value} 个'
            }
          }
        ],
        series: [
          {
            name: '订单数据',
            type: 'bar',
            tooltip: {
              valueFormatter: function (value) {
                return value + '个';
              }
            },
            // data: [
            //   2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3
            // ]
            data: orderdata.map((ele) => {
              // 后端请求的数据进行改造就可以显示
              return ele.orderAmount
            })
          },
          {
            name: 'Precipitation',
            type: 'bar',
            tooltip: {
              valueFormatter: function (value) {
                return value + ' ml';
              }
            },
            data: [
              2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3
            ]
          },
          {
            name: '订单数据',
            type: 'line',
            yAxisIndex: 1,
            tooltip: {
              valueFormatter: function (value) {
                return value + ' 个 ';
              }
            },
            // data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2]
            data: orderdata.map((ele) => {
              return ele.orderAmount;
            }),
          }
        ]
      };

      option && myChart.setOption(option);

    }
  },
  created() {
    this.queryOrderCensus();
    this.showEcharts();
  },
}
</script>

<style lang="less" scoped>
.orderCensusbox {

  // padding: 20px;
  span {
    margin-right: 10px;
    color: #000;
  }

  .el-button {
    margin-left: 20px;
  }
}

.content {
  height: 550px;
  margin-top: 50px;
  background-color: #fff;
}
</style>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

后台管理系统项目 的相关文章

  • 如何通过单击图像预览上的“x”从文件输入中删除图像?

    我目前有一个文件输入 一旦用户上传图像 就会显示图像预览 在图像预览上 有一个 x 可以从列表中删除图像预览 单击此 x 后 有什么方法可以从输入中的文件集中删除图像吗
  • React-native:将场景绑定到导航栏

    我正在整理这个提问 回答应用程序 并遇到了这个障碍 我想从导航栏触发场景中的功能 与登录应用程序类似 我在导航栏中有一个用于提交答案的按钮 RightButton route navigator index navState if rout
  • 如何更改传单中功能集的样式?

    我正在看等值区域的例子 https leafletjs com examples choropleth https leafletjs com examples choropleth 这是他们使用的数据源 type Feature prop
  • 访问sendBeacon发送的数据

    文档表明sendBeacon通过发送其数据HTTP POST request 但在 PHP 中 POST变量似乎是一个空数组 这是我的 JavaScript 代码 navigator sendBeacon beacon log php My
  • 动态速度计 javascript 或 jquery 插件

    我希望有动态ajax插件在页面上显示速度计 一个想法是我设置一个背景并旋转针 有人知道相关插件吗 这里有一些供您参考 http bernii github com gauge js http bernii github com gauge
  • 如何在ASP.NET Webform中使用Jquery表单插件?

    我遇到了这个插件 http malsup com jquery form getting started http malsup com jquery form getting started 我想知道如何在 ASP NET WebForm
  • 非 DOM 对象上的 jQuery 自定义事件

    我最近阅读了一些代码 其功能如下 bob name Bob Smith rank 7 bob bind nameChanged function bob trigger nameChanged 这似乎有效 但我在 jQuery 文档或源代码
  • 在网页上的文本框中键入内容时删除所有空格

    我如何在用户打字时即时删除输入到文本框中的空格 function var txt myTextbox var func function txt val txt val replace s g txt keyup func blur fun
  • jQuery 选择 # id 以单词为前缀,计数器为后缀

    有没有办法用 jQuery 选择所有带有前缀 my 和后缀 0 9 的 id 像这样的 my 1 4 还是可以用循环来实现 div div div div div div div div div div 第一个想法 似乎效果很好 div i
  • 如何按照编写的顺序迭代 javascript 对象属性

    我发现了代码中的一个错误 我希望通过最少的重构工作来解决该错误 此错误发生在 Chrome 和 Opera 浏览器中 问题 var obj 23 AA 12 BB iterating through obj s properties for
  • 即使我可以监视其他方法,也无法监视事件处理程序

    我想使用 Jest Jasmine Enzyme 测试 React 中的事件处理程序 MyComponent js import React from react class MyComponent extends React Compon
  • 检查 touchend 是否在拖动后出现

    我有一些代码可以更改表的类 在手机上 有时表格对于屏幕来说太宽 用户将拖动 滚动来查看内容 但是 当他们触摸并拖动表格时 每次拖动都会触发 touchend 如何测试触摸端是否是触摸拖动的结果 我尝试跟踪dragstart和dragend
  • jquery window.open 在 ajax 成功中被阻止

    尝试在我的 ajax 成功调用中打开一个新的浏览器窗口 但是 它被阻止为弹出窗口 我做了一些搜索 发现用户事件需要绑定到 window open 才能避免这种情况发生 我还找到了这个解决方案 您可以在 ajax 之前打开一个空白窗口 然后在
  • Javascript - 将值从下拉框传递到 Google Maps API

    我正在使用 Google 地图 API 为一家出租车公司创建报价表 目前 用户在 2 个文本框中输入出发点和接载点 API 会计算两点之间的距离以及行程费用 我正在尝试添加两个具有设定位置的下拉框 以便用户可以选择这些位置之一或使用文本框输
  • 如何使JavaScript函数在Eclipse“大纲视图”中可见?

    我有这样的代码 但如果它在匿名函数中定义 则无法打开函数大纲 类没有问题 我该如何概述something2 请分享一些提示 我可以将所有函数标记为构造函数 但这是无效的方法 start of track event required deb
  • 淡出和循环一组 div 的最佳方式

    假设我有以下 div div class a You are funny div div class b You are smart div div class c You are cool div 最好的展示方式是什么div a持续 5
  • 查询为空 Node Js Sequelize

    我正在尝试更新 Node js 应用程序中的数据 我和邮递员测试过 我的开发步骤是 从数据库 MySQL 获取ID为10的数据进行更新 gt gt 未处理的拒绝SequelizeDatabaseError 查询为空 我认识到 我使用了错误的
  • Nodejs mysql 获取正确的时间戳格式

    我在用着mysqljs https github com mysqljs mysql得到结果后sql我变得不同TimeStamp格式如下 created at Sat Jul 16 2016 23 52 54 GMT 0430 IRDT 但
  • 数据表日期范围过滤器

    如何添加日期范围过滤器 like From To 我开始进行常规搜索和分页等工作 但我不知道如何制作日期范围过滤器 我正在使用数据表 1 10 11 版本 My code var oTable function callFilesTable
  • 用于 C# XNA 的 Javascript(或类似)游戏脚本

    最近我准备用 XNA C 开发另一个游戏 上次我在 XNA C 中开发游戏时 遇到了必须向游戏中添加地图和可自定义数据的问题 每次我想添加新内容或更改游戏角色的某些值或其他内容时 我都必须重建整个游戏或其他内容 这可能需要相当长的时间 有没

随机推荐

  • 基于springboot的在线考试系统

    本系统和现在有的考试系统有以下几种优势 a 和现在有的系统比较起来 本系统有科目 章节 老师 学生 班级等信息的管理 还有批阅试卷查看已批阅试卷等 传统的考试系统划分并不细 业务功能简单 b 和学校的考试系统还有外面的考试系统比较起来 本系
  • 【配置文档】配置使用CGAL库的经验分享

    诸多经验贴都建议参考CGAL官方网站的步骤一步一步配置 因为我懒得看英文所以找的都是中文博客和问答 结果走了不少弯路 这里开一篇经验贴 记录错误也方便以后的查阅 本文是基于VS2017的配置和使用 这里是CGAL的使用手册 文章目录 一 需
  • Python案例篇2-pycharm import cx_Oracle模块引发的No module named ‘custom_exceptions‘

    一 问题描述 最近在自学python 然后用到Oracle数据库 于是开始学习cx Oracle模块 代码 import cx Oracle dbConnect host cx Oracle makedsn mylocalhost mypo
  • Linux防火墙查看及白名单添加

    一 临时白名单添加 执行即生效 重启防火墙后失效 查看防火墙状态 service iptables status 查看白名单列表 sudo iptables nL 添加白名单 sudo iptables I INPUT m state st
  • Spring AOP 源码分析 - 拦截器链的执行过程

    1 简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章 在前面的两篇文章中 我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器 以及如何创建代理对象的过程 现在我们的得到了 bean 的代理对象 且通知也以合
  • java:无法从静态上下文中引用非静态方法

    编辑以下代码 public class t public int i public void fun public static void main String args i 3 fun 编译 javac t java 得到以下报错 原因
  • c++svd算法_2020DCIC智能算法赛智慧海洋建设TOP1方案

    大家好 我是来自团队Pursuing the Past Youth的Ethan 天池ID是GrandRookie 和队友青禹小生 wbbhcb Chauncy YAO经过2个多月的 征途 最终在本届智能算法赛部分拿到了线上Top1的成绩 下
  • 蓝桥杯第十届青少年Python组省赛试题

    ns 1 3 5 8 cnt 0 for a in ns for b in ns for c in ns if a b and a c and b c print a 100 b 10 c cnt 1 print cnt for i in
  • SpringSecurity详解

    一 Spring Security简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架 Spring Security致力于为Java应用程序提供身份验证和授权的能力 像所有Spring项目一样 Sprin
  • Lora无线终端工作原理及优缺点

    LoRa 数据传输终端是一种基于LoRa 扩频技术的无线数据传输终端 利用 LoRa 网络为用户提供无线数据传输功能 该产品采用高性能的工业级 LoRa 方案 以嵌入式实时操作系统为软件支撑平台 同时提供 RS232 和 RS485 或 R
  • mipsel-openwrt-linux交叉编译libwebsockets

    mipsel openwrt linux交叉编译libwebsockets mipsel openwrt linux交叉编译libwebsockets 1 下载libwebsockets 2 准备条件 3 编译安装libwebsockets
  • 数据库——实体联系模型

    文章目录 1 实体 2 属性 3 联系 4 实体 联系图 5 弱实体集 1 实体 1 实体 客观存在并且可以相互区分的任何事物 可以是实际对象 也可以是抽象概念 2 属性 实体所代表的事物具有的某种特性 每个实体都可以用一组属性来刻画 例如
  • 思维导图怎么变成ppt?4个思维导图一键生成ppt的方法

    做好的思维导图如何变成一份ppt 本文罗列了4个可行方法 一起来看看吧 一 直接复制粘贴 这是最简单的方法 虽然这样可能会花费一些时间 但可以确保内容排版和布局与你想要的一致 当然 我们大可使用更高效的方法 二 导出为图片格式 大多数思维导
  • 矩阵相关定义性质全总结

    矩阵相关定义性质全总结 0 前言 矩阵是线性代数中的核心内容 所以我写这篇文章对矩阵 研究生以下阶段 进行一个完整的叙述 虽然是主要说矩阵 但是我也会将行列式 向量 线性方程组三个方面也包含在内 不过是概述的形式 具体的叙述会另外展开写 能
  • C++沉思录读书笔记1.如何定义一个完整的类

    C 沉思录 Ruminations On C 读书笔记1 如何定义一个完整的类 作者 2006 4 27 12 19 C 哲学 只为用到的东西付出代价 定义一个类时必须搞清楚的几个问题 需要构造函数吗 如果答案为 no 那么很可能你需要定义
  • 关于转义字符&

    1 情况是这样的 就是前段传的xml参数里存在 这种特殊字符 所以前端需要转义后再传给后端 也就是 转义为 后传给后端 但是后端接收但这个参数时 会拼接url 就像下面这样的 http www xx com path api gender
  • 【Colab】基本操作【LeNet】【MNIST】训练测试

    文章目录 1 介绍 2 查看基本配置 2 1查看pytorch版本 2 2查看是否可以使用cuda 2 3查看显卡配置 3 挂载 31 挂载谷歌云盘 3 2更改运行目录 4 训练 5 Reference Colab 官网初始界面 1 介绍
  • python函数用法之numpy.mgrid

    参考链接 python笔记 numpy中mgrid的用法 布衣小张 CSDN博客 mgrid numpy中的mgrid函数 KangLongWang的博客 CSDN博客 mgrid函数 mgrid函数返回多维结构 np mgrid 第1维
  • ISP图像处理流程

    文章目录 前言 ISP图像处理流程 总结 参考 前言 因工作需要 今天看了ISP图像处理的基本流程 为了检验自己的理解情况 这里根据自己的理解写下这篇文章 如有错误 敬请原谅 ISP图像处理流程 ISP Image Sensor Proce
  • 后台管理系统项目

    1 项目名称 后台管理 2 技术栈 vue全家桶 element ui axios less eachers 3 项目亮点 性能优化 百万级项目 新旧系统更迭 权限把控 项目开发流程 1 安装vue脚手架 2 vue create 项目名称