第一步:先对项目做一个基本的项目配置
1.在src目录下面创建utils文件,在创建request.js文件,进行对axios的二次封装
import axios from 'axios'
// axios的二次封装
const service = axios.create({
// baseURL:"/api",
// timeout:3000
})
// axios请求拦截
service.interceptors.request.use((config) => {
// 判断一下登录是否成功,请求头中添加token
// if(store.getters.token){
//让每个请求携带token ['token']为自定义key 请根据实际情况自己修改
// config.headers['token']
// }
return config
})
// axios响应拦截
service.interceptors.response.use((config) => {
//可以写一些逻辑
return config
})
// let Request=(params)=>{
// return new Promise((resolve,reject)=>{
// // axios({
// // url: baseURL+url,
// // method: "get" || "post" ,
// // // get传参
// // params:{
// // username:"admin",
// // passwrod:"admin"
// // },
// // //post传参
// // data:{
// // username:"admin",
// // passwrod:"admin"
// // }
// // })
// service({
// ...params //es6对象的结构赋值
// }).then((res)=>{
// resolve(res)
// }).catch((err)=>{
// reject(err)
// })
// })
// }
export { service }
2.跨域请求数据
(1).创建好基本的项目,然后打开config的index.js ,找到proxyTable ,设置一下代码
在target里卖弄设置一下跨域接口的端口号,然后配置changeOrigin:true允许跨域,最后在pathRewrite里面设置api
proxyTable: { //跨域代理
"/api": { //地址
target: "http://admintest.happymmall.com/manage/", //基地址
changeOrigin: true, //允许转发
pathRewrite: { //路径重写
"^/api": ""
}
}
},
(2).打开main.js 继续设置 添加下面的代码,安装axios
axios.defaults.baseURL='/api'
cnpm install axios --save
(3).重新启动项目,配置文件之后,必须要重新启动项目
npm run dev
(4).请求接口
this.$axios.get('/地址).then(res=>{
console.log(res)
})
3.在main.js中配置全局路由,设置标题的切换和登录时长过期以及没登陆跳转到登陆页面
// 全局路由钩子 brforeEach() afterEach()
router.beforeEach((to, from, next) => {
// 功能一:实现title的切换
document.title = `${to.meta.title}`
const role = localStorage.getItem('admin')
// 字符转为对象
const val = JSON.parse(role)
console.log(val);
// 功能二:设置登录过期
if (val !== null) {
const timeEnd = Date.now() - val.time;
if (timeEnd > val.expire) {
localStorage.removeItem('admin')
next('/')
return null
}
}
// 功能三:没有登陆,跳转到登录页
if (!val && to.path !== '/') {
next('/')
}
next()
})
4.配置element-ui插件,在utils文件下创建element.js文件,在里面导入局部组件,然后再main.js里面挂载
import './utils/element'
5.对数据的接口进行统一管理
在src目录下创建utils文件夹request文件,配置代码
import { service } from "../utils/request"
在这里先配置我们所有的请求数据的接口
// 登录页面接口
export const login = (obj) => service({
url: "/api/user/login.do",
method: 'post',
params: obj
})
// 主页数据接口
export const home = () => service({
url: "/api/statistic/base_count.do",
method: "get",
})
// 用户数据接口
export const users = (obj) => service({
url: "/api/user/list.do",
method: 'post',
params: obj
})
//商品管理接口
export const goods = (obj) => service({
url: "/api/product/list.do",
method: 'post',
params: obj
})
// 商品上架,下架接口
export const go = (data) => service({
url: "/api/product/set_sale_status.do",
method: 'post',
params: data
})
// 商品id或者商品名称搜索
export const search = (data) => service({
url: "/api/product/search.do",
method: 'post',
params: data
})
// 商品详情
export const detail = (productId) => service({
url: `/api/product/detail.do?productId=${productId}`,
method: 'get',
})
// 商品编辑
export const edit = (obj) => service({
url: "/api/product/save.do",
method: 'post',
params: obj
})
// 商品添加
export const add = (obj) => service({
url: "/api/product/save.do?" + obj,
method: 'get'
})
// 品类接口
// 一级品类
export const pl = () => service({
url: "/api/category/get_category.do?categoryId=0",
method: 'get'
})
// 二级品类
export const pl2 = (categoryId) => service({
url: `/api/category/get_category.do?categoryId=${categoryId}`,
method: 'get'
})
// 品类修改
export const plEdits = (obj) => service({
url: "/api/category/set_category_name.do",
method: 'post',
params: obj
})
// 品类添加
export const plAdds = (obj) => service({
url: "/api/category/add_category.do",
method: 'post',
params: obj
})
// 查看子品类
export const plSearchs = (categoryId) => service({
url: `/api/category/get_category.do?categoryId=${categoryId}`,
method: 'get'
})
// 订单接口
export const orders = (obj) => service({
url: "/api/order/list.do",
method: 'post',
params: obj
})
// 订单搜索接口
export const ordersSearch = (data) => service({
url: "/api/order/search.do",
method: 'post',
params: data
})
// 订单详情接口
export const ordersDetail = (orderNo) => service({
url: `/api/order/detail.do?orderNo=${orderNo}`,
method: 'get'
})
第二步:实现对每个模块的功能的实现
首先分析页面,就是这个项目主要有两个大的页面,一个是登录页面的路由跳转,还有一个就是首页,在首页里面会实现一个排版,路由也会用到二级路由
一、登录
1.先配置登录页面的路由
2.根据页面布局实现页面的排版
3.接下来就是实现登录页面的功能
先在script里面引入登录的接口,然后在data里面定义用户名(username),密码(password)变量,在input框中分别用v-model绑定username和password
接着给登录按钮绑定点击事件:login()
然后再script的methods中定义登录的方法,第一步就是先惊醒非空判断,内容为空不能登录,接着定义一个对象form,吧username和password存在里面然后触发登录的接口,把请求的书韩剧赋值给num,请求成功的话,就跳转到首页,还要实现一个弹框提示,在登录的时候设置登录过期时间的话,就要在本地存储过期时间,我们还要引入一个qs包,把登录的数据存储起来
const obj = {
time: Date.now(),
expire: 1000 * 60 * 30, //30分钟登陆时间过期
data: this.username
};
import qs from "querystring";
localStorage.setItem("admin", JSON.stringify(obj));
二、首页
1.实现基本的页面布局,然后首页里面有首页部分,商品模块,订单模块,还有用户模块
(一).首页部分
首页布局:有左侧菜单栏的数据的渲染,顶部退出登录的按钮,右下角就是我们点击左侧菜单栏的时候页面进行切换
退出登录功能,给退出按钮添加点击事件,用this.$router.push('/login'),跳转到登录页面
首页功能:首页是有三个盒子,分别是用户总数,商品总数,订单总数,然后点击对应的盒子,然后跳转到相应的页面,然后再根据接口请求数据,在渲染到页面上
<ul>
<li style="background:#f0ad4e;" @click="$router.push('/user')">
<p style="">{{list.userCount}}</p>
<span>
<i class="el-icon-user"></i> 用户总数
</span>
</li>
<li style="background:green;" @click="$router.push('/shop')">
<p style="">{{list.productCount}}</p>
<span>
<i class="el-icon-s-operation"></i>商品总数
</span>
</li>
<li style="background: #4cb1cf" @click="$router.push('/dingdan')">
<p style="">{{list.orderCount}}</p>
<span>
<i class="el-icon-finished"></i> 订单总数
</span>
</li>
</ul>
<script>
import axios from 'axios'
export default {
data() {
return {
list: []
};
},
mounted() {
this.shuju();
},
methods: {
shuju() {
axios.get('http://adminv2.happymmall.com/manage/statistic/base_count.do')
.then(res=>{
console.log(res);
this.list = res.data.data;
}).catch(err=>{
console.log(err);
})
}
}
};
</script>
(二).商品模块
商品这一模块有搜索功能(根据id查询,更具用户名查找),添加,上架和下架的状态切换,编辑,和查看详情,数据分页器等功能
1.首先先对表格的数据渲染:先在data里面定义一个空数组,存放请求接口的数据,然后把list里面的数据遍历到表格上,把total=rea.data.data.total,顺便拿到分页的总条数
2.数据分页器:从element-ui里面赋值分页的结构,然后再data里面定义pageNum(数据的页数默认为1),pageSize(每页的条数默认为10条),total(总条数为0)
然后再结构里面会触发两个事件,handleSizeChange和handleCurrentChange分别是每页条数发生变化的时候触发的方法和页数发生变化的时候触发的方法,设置:page-size="pageSize",
:total="total",然后再methods里面触发方法,并且传一个值val,让当前的每页的条数等于val,然后请求接口,把pagesize:this.pageSize传上去,最后让this.list = res.data.data.list,
获取每页的条数和页码的变化都是一样的方法,页码传的是pageNum: this.pageNum,这样就可以实现我们的分页器数据的效果了
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
data() {
return {
tableDate: [],
pageSize: 10,
pageNum: 1,
total: 0
};
},
页码
handleCurrentChange(val) {
this.pageNum = val;
this.getList();
users({ pageNum: this.pageNum }).then(res => {
this.tableDate = res.data.data.list;
});
},
条数
handleSizeChange(val) {
this.pageSize = val;
this.getList();
users({ pageSize: this.pageSize }).then(res => {
this.tableDate = res.data.data.list;
});
}
3.分页器封装成组件:先在src目录下创建一个common文件夹,在创建一个页面,存放分页的结构,然后通过import Table from "@/common/Table"导入,然后通过components: { Table }接收,左后在页面上以标签的形式显示
童谣的道理在data里面定义pageNum(数据的页数默认为1),pageSize(每页的条数默认为10条),total(总条数为0)
<Table :total="total" :pagesize="pageSize" :pagenum="pageNum" @sendPageSize="handleCurrentChange1" @sendPageNum="handleCurrentChange2"></Table>
//引入分页组件
import Table from "@/common/Table";
// pagesize
handleCurrentChange1(psize){
users({ pageSize: psize }).then(res => {
this.tableDate = res.data.data.list;
});
},
// pagenum
handleCurrentChange2(cpage){
users({pageNum:cpage}).then(res=>{
this.tableDate=res.data.data.list
})
}
让后在子组件中通过props属性接收:props: ["total", "pageSize", "pageNum"],在方法中通过this.$emit('')来触发事件
//当前页面选择的每页数据
handleSizeChange(psize) {
this.$emit('sendPageSize',psize)
},
// 当前页面选择的页码
handleCurrentChange(cpage) {
this.$emit("sendPageNum",cpage)
}
4.搜索:复制element-ui下拉的样式,在data里面定义的options的数据遍历到页面上,一种是以id的形式搜索,一种是以用户名的形式搜索,首先,给搜索按钮添加点击事件,然后给input框帮订一个input值,然后再data里面定义一下input,接着再methods中先进性判断先判断是否有值,如果有值就执行代码,没有的话就执行更新页面的数据接口,然后再嵌套一个判断,如果选择用id搜索,那么就让type是商品的id,否则的话就让type是商品的名称
search() {
if (this.value) {
// 先判断是否有值
if (this.value == "ID") {
// 如果是根据id判断,就是id,否则是name
this.type = "productId";
} else {
this.type = "productName";
}
search({ [this.type]: this.input, pageNum: this.pageNum }).then(
res => {
console.log(res);
this.list = res.data.data.list;
this.total = res.data.data.total;
}
);
} else {
// 重新更新页面
goods({ pageNum: this.pageNum }).then(res1 => {
console.log(res1);
this.list = res1.data.data.list;
this.total = res1.data.data.total;
});
}
}
5.商品状态的切换,上架下架:首先在表格布局里面设置插槽绑定slot-scope属性,然后分别绑定types和types2让后给切换状态的按钮绑定点击事件,然后再methods里面触发定义的方法,再element-ui里面复制一个确认弹框的代码,然后再then里面调用接口,而且还要传我们需要的值,商品id,状态,而状态要进行一个三元表达式,如果符合执行1,否则执行2,status状态的切换,还要用过滤器filters来筛选一下,types的时候,传一个val,返回val的值为1,如果是1的话,就为在售的状态,否则为已下架,types2的时候,传一个val,返回val的值为2,如果是2,就为上架状态,否则为下架状态
<el-table-column
prop="status"
label="状态">
<template slot-scope="scope">
<span>{{scope.row.status | types}}</span>
<el-button size="mini" type="warning" style="float:right" class="btnstatus" @click.prevent="zhuangtai(scope.row)">
{{scope.row.status | types2}}
</el-button>
</template>
</el-table-column>
// 修改上架,下架的状态
zhuangtai(v) {
console.log(v.id, v.status);
this.$confirm("是否要上架商品?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
go({ productId: v.id, status: v.status == 1 ? 2 : 1 }).then(res => {
console.log(res.data);
goods({ pageNum: this.pageNum }).then(res1 => {
console.log(res1);
this.list = res1.data.data.list;
this.total = res1.data.data.total;
});
});
this.$message({
type: "success",
message: "修改状态成功"
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消"
});
});
}
// 过滤器
filters: {
types(val) {
return val == 1 ? "在售" : "已下架";
},
types2(val) {
return val == 2 ? "上架" : "下架";
}
}
6.商品详情:点击查看,带着当前的商品id跳转到详情页面,在详情页面在接收一下商品id,然后引入商品详情的接口,然后,在methods方法中调用商品详情的接口,在data里面定义一个list空数组,把请求到的数据放到list数组里面,然后再页面中循环遍历list,渲染数据,注意的一点就是,商品 图片要进行一个拼接
<img :src="form.imageHost + form.mainImage" alt="" class="img" />
还有一级品类和二级品类的数据渲染:先写两个下拉菜单,然后给v-model绑定一个val1,val2,再data里面也定义一个val1和val2,给key绑定为id,lable绑定为name,value绑定为id,还要在data中定义两个数组,obj1和obj2,引入一级品类和二级品类的接口,然后再methods中调用接口,再一级品类中先筛选返回的数据,把item里面的id赋值给当前的商品 id,然后把这个数据放在obj1中,然后当前的val1就是这个对象里面的name值,二级品类就是传一级品类的id,然后惊醒相似的操作,筛选数据,让item.id给当前的商品id,然后把数据存在obj2中,当前的内容就是obj2中的name
detail(this.id).then((res) => {
// console.log(res);
this.form=res.data.data
console.log(this.form);
// 一级品类的渲染
pl().then(res=>{
console.log(res);
var box=res.data.data.filter(item=>{
// console.log(item);
return item.id==this.form.parentCategoryId
})
this.obj1=box
console.log(this.obj1);
this.val1=this.obj1[0].name
})
// 二级品类的渲染
pl2(res.data.data.parentCategoryId).then(res=>{
console.log(res);
var box=res.data.data.filter(item=>{
return item.id==this.form.categoryId
})
this.obj2=box
// console.log(obj2);
this.val2=this.obj2[0].name
})
});
7.商品编辑:在首页里面点击编辑,带着当前的商品id跳转到商品编辑的页面,然后再详情页面接收商品id,接着先在data里面定义商品名称,描述,分类,价格,库存,图片一级详情内容的变量
引入商品详情,一级品类,二级品类,商品添加的接口,在mounted中调用详情的接口,然后给这些变量赋值,达到数据回填的效果,接着在methods中触发编辑的方法,定义一个变量obj,里面存放在data里面定义的变量数据,这里需要引入一个qs包,引入import qs from "querystring";然后调用添加的接口,把obj中村的数据重新添加进去,如果返回的状态为0的话,就说明编辑成功,然后跳转回到商品列表页面,编辑一级品类和二级品类的时候,分别封装两个方法,获取数据,得到数据之后将数据分别存在对应的空数组中,在获取二级品类的时候,要先展示一级品类,所以在一级品类的方法中调用二级品类的方法让在选完一级品类的时候,二级品类出现,在页面初始化的时候,就在mounted里面调用一级品类没让页面一打开就显示一级品类
一级品类
getCategory() {
pl().then(res => {
console.log(res);
this.list1 = res.data.data;
this.gettwo()
});
},
二级品类
gettwo() {
console.log(this.categoryId0);
pl2(this.categoryId0).then(res => {
console.log(res);
this.list2 = res.data.data;
});
},
商品详情的内容是有富文本编辑器实现的,在布局的时候使用editor来进行数据的编辑,在页面里面引入import { VueEditor } from "vue2-editor";用components接收:components: { VueEditor }
<vue-editor v-model="detail" useCustomImageHandler @image-added="handleImageAdded"></vue-editor>
在里面绑定detail值,在methods里面触发handleImageAdded方法
async handleImageAdded(file, Editor, cursorLocation, resetUploader) {
console.log(file);
console.log(Editor);
console.log(cursorLocation);
console.log(resetUploader);
// 一般情况下,我们都是提交json数据,但是现在要上传文件所以要使用FormData方式上传表单
var formData = new FormData();
formData.append("file", file);
// 发请求
let res = await upload(this.formData);
// 把图片放在光标所在的位置
Editor.insertEmbed(cursorLocation, "image", res.data.url);
resetUploader();
}
商品的图片上传:
<el-upload
class="upload-demo"
action="/api/product/upload.do"
:on-success="success"
name="upload_file"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<img :src="a" alt="" style="width:100px">
</el-upload>
success(res) {
console.log(res);
this.subImages = res.data.uri;
this.a = res.data.url;
},
8.商品添加:点击添加商品的按钮,跳转到添加页面,然后商品的添加和编辑操作都差不多,都是需要引入接口,在data里面定义变量,在定义一个对象存储我们当前要输入的数据,然后请求接口,获取数据,然后将数据循环遍历到页面上
data() {
return {
categoryId: "",
categoryId0: "",
name: "",
subtitle: "",
subImages: "",
detail: "",
price: "",
stock: "",
status: 1,
list1: [],
list2: [],
a: ""
};
},
add() {
// console.log(this.content);
// 非空判断
if (
this.name == "" ||
this.subtitle == "" ||
this.price == "" ||
this.stock == ""
) {
return this.$message.error("内容不能为空");
}
var obj = {
categoryId: this.categoryId,
name: this.name,
subtitle: this.subtitle,
subImages: this.subImages,
detail: this.detail,
price: this.price,
stock: this.stock,
status: 1
};
console.log(qs.stringify(obj));
// 调用接口
add(qs.stringify(obj)).then(res => {
console.log(res);
if (res.data.status == 0) {
this.$message.success(res.data.data);
this.$router.push('/shop')
}
});
},
(三).品类模块
通过接口获取到数据之后,渲染到页面上,然后点击修改品类名称的时候,弹出一个模态框,
点击确定时,上传categoryId , categoryName 这两个参数,然后通过调用相应的接口,点击其自己分类传入 的categoryId ,通过这个id调用接口获取子到子孩子的数据,并渲染到页面,点击添加分类会有一个下拉框,这个下拉框里有两个值,如果是/所有,就是把数据添加到一级分类,第二种就是/所有/这个是添加到二级分类,点击提交传入下拉框一级或者二级分类的id还categoryName 调用接口完成品类的添加
(四).订单模块
订单里面的搜索和数据查看和商品里面的搜索和查看功能的操作都是一样的,还有就是请求接口对表格进行渲染数据,还有就是引入分页器的组件,实现分页的功能
(五).用户模块
用户模块算是最简单的了,就只是请求数据接口,渲染一下表格数据,再有就是复用分页器的组件,实现分页的功能