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
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.根据原型图得知,有页码,而且还是带左侧可以选择的表格,结构如下
<!-- 身体部分 -->
<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>
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>
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>
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>
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.根据原型图显示,店铺管理主要是显示相应的数据,,把相关数据请求过来,主要的难点在于头像,和店铺照片墙,包括店铺营业时间,需要处理和优化.下面是页面结构
<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.订单列表,订单查看,订单修改
<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")
}
<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>
<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>
<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>
<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>
<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>