需求说明
(1)商品ID:精准匹配,不超过50字,超出直接不可输入;每次仅能输入一个,查询结果为该商品ID对应数据,若有N个渠道都是此商品ID,展示一条数据,指此商品ID对应的渠道试听课订单数总和
(2)商品后台名称:根据商品名称模糊检索 不超过50字,超出直接不可输入;每次仅能输入一个 例如:输入:巴, 会把巴拉巴拉、新巴拉巴拉都搜索出来
(3)商品类型:点击‘’商品类型‘’展示下拉列表(‘’试听课‘’ ‘’主修课‘’ ‘’辅修课‘’ “全部”四个选项,筛选所需数据,四个选项只能选择一个,鼠标悬停时为阴影,选中时为蓝色标注文本颜色由黑色变为蓝色)
(4)商品现价:点击‘’商品现价‘’展示下拉列表(‘’非0元‘’ ‘’0元‘’ “全部” 三个选项,筛选所需数据,三个选项只能选择一个,鼠标悬停时为阴影,选中时为蓝色标注文本颜色由黑色变为蓝色)
(5)开课日期:选择开课日期,点击,展示符合授课时间条件的数据内容。举例:选择2020年8月30日,则展示开课时间在8.30日的数据;
(6)支付成功时间:
- 仅要求结束日期≥开始日期,手动切换,最长可查看一年范围宽度的数据。若筛选条件没有选择支付成功时间,同样展示一年范围宽度的数据。
- 结束日期=开始日期时,表示查询当天的数据;检索时选择时间范围(按天时间),点击查询,展示符合支付时间所选择范围的数据内容。
- 结束日期>开始日期,查询开始日期至结束日期时间段的订单数据。
(7)点击【查询】:展示查询结果,若没选择任意筛选项进行查询,toast提示:请选择商品ID/商品后台名称/商品类型/商品现价/开课日期/支付成功时间后再查询;
(8)点击【清空】:指清除商品ID,商品类型,商品现价,开课日期,支付成功时间,页面保持清空前状态不变;
(9)点击【导出】:
- 导出当前筛选结果数据,字段同列表字段;
- 导出格式:Excel表;
- 命名规则:商品维度统计表_商品类型_开课时间,若三者有未筛选的,则不展示,举例:三者都筛选,展示商品维度统计表_商品类型_2020.08.03,若未筛选商品类型,则展示商品维度统计表_2020.08.03,若未筛选开课时间,则展示商品维度统计表_商品类型
(10)数据过多,分页展示;每页100条;
(11)统计:展示搜索的累计订单数,订单数为当前筛选条件下所有页面数据的总和(例如有10页数据显示的是10页的试听课订单数总和),并非当前1页的数据总和。
一、页面实现
1. 核心表格的HTML
<div class="table" style="width:750px;margin-top:20px">
<el-table
:header-cell-style="{background:'#F2F6FC','text-align':'center'}"
:data="orderList"
border
fit
highlight-current-row
element-loading-text="Loading"
style="margin-top:20px"
>
<el-table-column label="商品ID" align="center" width="100">
<template slot-scope="scope">{{scope.row.goodsId}}</template>
</el-table-column>
<el-table-column label="商品类型" align="center" width="100">
<template slot-scope="scope">{{scope.row.typeName}}</template>
</el-table-column>
<el-table-column label="商品后台名称" align="center" width="200">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<el-table-column label="商品现价" align="center" width="120">
<template slot-scope="scope">{{scope.row.sellPrice}}</template>
</el-table-column>
<el-table-column label="开课日期" align="center" width="120">
<template slot-scope="scope">{{scope.row.startTime}}</template>
</el-table-column>
<el-table-column label="订单数" align="center">
<template slot-scope="scope">{{scope.row.orderCount}}</template>
</el-table-column>
</el-table>
<el-pagination
style="display:flex; justify-content:center; margin:10px"
layout="total, prev, pager, next, jumper"
:current-page="currentPage"
@current-change="currentPageChange"
:page-size="pageNum"
:total="total"
background
></el-pagination>
</div>
<div class="table" style="width:750px;margin-top:20px">
<el-table :data="acount" border :show-header="false">
<el-table-column width="100" align="center" class-name="gray bor">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<!--<el-table-column width="100" align="center" class-name="gray bor">
<template slot-scope="scope">{{ scope.row.type }}</template>
</el-table-column>
<el-table-column width="200" align="center" class-name="gray bor">
<template slot-scope="scope">{{ scope.row.goodsName }}</template>
</el-table-column>
<el-table-column width="120" align="center" class-name="gray bor">
<template slot-scope="scope">{{ scope.row.priceType }}</template>
</el-table-column>
<el-table-column width="120" align="center" class-name="gray bor">
<template slot-scope="scope">{{ scope.row.startTime }}</template>
</el-table-column>-->
<el-table-column align="center" class-name="bor">
<template slot-scope="scope">{{ scope.row.orderSum }}</template>
</el-table-column>
</el-table>
</div>
2. 数据结构概览
data() {
return {
loading: true,
isflag: true, //
isInit: false, // 初始化
noresult: false,
pageNum: 100, // 最大显示个数
currentPage: 1, // 当前页码
total: 0,
goodsId: '', // 商品ID
goodsName: '', // 商品名称
type: '', // 商品类型
priceType: '', // 商品现价
sellPrice: '',
orderList: [],
startTime: null, // 开课时间
orderNumByDay: [],
count: 0, // 统计
paymentStartTime: null, // 支付成功开始时间
paymentEndTime: null, // 支付成功结束时间
goodsTypeOptions: [ // 商品类型选项
{
type: '0',
label: '试听课',
},
{
type: '1',
label: '主修课',
},
{
type: '2',
label: '辅修课',
},
{
type: '3',
label: '全部',
},
],
goodsPriceOptions: [ // 商品价格选项
{
priceType: '0',
label: '0元',
},
{
priceType: '1',
label: '非0元',
},
{
priceType: '2',
label: '全部',
},
],
acount: [ // 下方统计表格数据结构
{
name: '统计',
type: '',
goodsName: '',
priceType: '',
startTime: '',
orderSum: 0,
},
],
search: { // 存放查询结果,便于导出
pageIndex: 1,
pageNum: 100,
goodsId: '',
goodsName: '',
isInit: true,
type: '',
priceType: '',
startTime: '',
paymentStartTime: '',
paymentEndTime: '',
},
time: [],
}
},
二、功能实现
1. 页面初始化数据
思路:在created中初始化页面数据,调用函数getOrderList() 。首次页面加载会显示当前日期的下周一的情况(这里后端已经做了处理,前端只需要计算时间戳,并传参isInit即可,此时currentpage=1默认显示第一页)使用 post请求数据即可。
接口:/admin/statistics/orderStatistics
created() {
this.getOrderList() // 页面初始化
this.isflag = true
this.$ajax.timeout = 600000
},
// methods
async getOrderList() {
// 获取当前日期的下周一时间戳
var myDate = new Date()
myDate.setDate(
myDate.getDay() == 0
? myDate.getDate() - 6
: myDate.getDate() - (myDate.getDay() - 1)
) //先获取当前日期的周一
myDate.setHours(0,0,0,0)
var nextmon = myDate.setDate(myDate.getDate() + 7) //+7代表下一个周一
var temp = new Date(nextmon)
temp = temp.toISOString();
this.startTime = temp
this.isInit = true
this.getListAjax()
},
async getListAjax() {
this.commonListAjax(1)
},
2. 清空搜索数据
// 清空按钮
async clearBtn() {
this.time = []
this.goodsId = null
this.goodsName = null
this.type = null
this.priceType = null
this.startTime = null
},
3. 页码更改
结合el-pagination进行绑定currentPageChange() 是页面通用函数
<el-pagination
style="display:flex; justify-content:center; margin:10px"
layout="total, prev, pager, next, jumper"
:current-page="currentPage"
@current-change="currentPageChange"
:page-size="pageNum"
:total="total"
background
></el-pagination>
// method实现
async currentPageChange(currentPage) {
console.log(currentPage)
this.currentPage = currentPage
this.commonListAjax(currentPage)
},
4. 查询按钮的实现
思路:先做判空的处理,然后调用getListAjax() 函数,间接调用commonListAjax(1) (1表示显示查询结果的第一页,同第一次加载页面初始化时的commonListAjax数据请求操作)。接口:/admin/statistics/orderStatistics。
注意:这里在最后有一个修改search查询数组的操作,主要用来暂存查询结果条件,便于导出。
// 搜索按钮
async searchBtn() {
// console.log('search')
if (this.time == null) this.time = []
if (
this.goodsId || this.goodsName || this.type ||
this.priceType || this.time[0] != undefined || this.startTime != null
) {
this.getListAjax()
// this.isSearched = true;
} else {
this.$message.error('请选择商品ID /商品类型 /商品后台名称 /商品现价 /开课日期后再查询')
}
this.isflag = false
},
在baseData() 中对查询的开始时间和结束时间,以及商品ID进行了处理
baseData() {
let start, end
if (this.time == null) this.time = []
if (this.time[0] == undefined) {
start = null
end = null
} else {
start = this.time[0]
end = this.time[1]
}
let goodsId = null
if (this.goodsId !== null) {
goodsId = Number(this.goodsId)
if (Number.isNaN(goodsId)) {
this.$message.error('请输入正确的商品Id')
return -1
}
}
return {
goodsId,
paymentStartTime: start,
paymentEndTime: end,
}
},
调用接口返回数据之前的判断:(1)如果20001 查询时常有问题 (2) 如果orderResultList为null,说明无查询结果,需要将orderResultList置空(注意,这里不能直接把orderResultList赋值为空对象,这样后面无法拿到对象中的数据,需要对对象中的数据进行赋空)这里使用的方法比较复杂,其实直接使用parse() 和 Stringify()也可以实现同样的效果。
注意:这里在最后有一个修改search查询数组的操作,主要用来暂存查询结果条件,便于导出。
async commonListAjax(currentPage) {
const tmpData = this.baseData()
if (tmpData === -1) {
this.loading = false
return
}
const { paymentStartTime, paymentEndTime, goodsId } = tmpData
let res = await this.$ajax.post('/admin/statistics/orderStatistics', {
pageIndex: this.currentPage,
pageNum: this.pageNum,
goodsId: this.goodsId,
goodsName: this.goodsName,
isInit: this.isInit,
type: this.type,
priceType: this.priceType,
startTime: this.startTime,
paymentStartTime: paymentStartTime,
paymentEndTime: paymentEndTime,
})
this.isInit = false
this.loading = false
let data = res.data.data
if (res.data.retcode == 20001) {
this.$message({
message: '查询时间长度不能超过一年',
type: 'warning',
})
console.log('一年')
} else if (data.orderResultList == null) {
this.orderList = []
this.total = data.count
this.acount[0].orderSum = data.orderSum
this.search.pageIndex = this.currentPage
this.search.pageNum = this.pageNum
;(this.search.goodsId = ''),
(this.search.goodsName = ''),
(this.search.isInit = false), //
(this.search.type = ''),
(this.search.priceType = ''),
(this.search.startTime = ''),
(this.search.paymentStartTime = ''),
(this.search.paymentEndTime = ''),
(this.noresult = true)
this.$message({
message: '无查询结果',
type: 'warning',
})
} else if (data.orderResultList != null) {
this.orderList = data.orderResultList.map(item => {
const time = item['startTime']
item['startTime'] = this.format(new Date(time), 'yyyy-MM-dd')
return item
})
this.total = data.count
this.acount[0].orderSum = data.orderSum
// 修改search查询数组
console.log(this.search)
this.search.pageIndex = this.currentPage
this.search.pageNum = this.pageNum
this.search.goodsId = this.goodsId
this.search.goodsName = this.goodsName
this.search.isInit = this.isflag
this.search.type = this.type
this.search.priceType = this.priceType
this.search.startTime = this.startTime
this.search.paymentStartTime = this.paymentStartTime
this.search.paymentEndTime = this.paymentEndTime
this.noresult = false
}
},
5. 导出表格
导出数据接口: '/admin/statistics/exportOrderStatistics'
// 导出为excel
async expExcel() {
if (this.noresult) {
this.$message({
message: '无导出结果',
type: 'warning',
})
} else {
var params = {
goodsId: this.search.goodsId,
goodsName: this.search.goodsName,
isInit: this.isflag,
pageIndex: this.search.pageIndex,
pageNum: this.search.pageNum,
paymentStartTime: this.search.paymentStartTime,
paymentEndTime: this.search.paymentEndTime,
priceType: this.search.priceType,
startTime: this.search.startTime,
type: this.search.type,
}
let myurl = '/admin/statistics/exportOrderStatistics'
{
this.$ajax({
url: myurl,
method: 'post',
data: params,
responseType: 'arraybuffer',
}).then(res => {
console.log(res)
let fileName = '商品维度统计表'
let date = this.format(new Date(this.startTime), 'yyyy-MM-dd')
var name
if (this.search.type == '0') {name = '试听课'
} else if (this.search.type == '1') {name = '主修课'
} else if (this.search.type == '2') {name = '辅修课'
} else {name = ''
}
if ((this.search.type == '' || this.search.type == '3') &&
this.search.startTime == null
) {
fileName = '商品维度统计表'
} else if (
(this.search.type == '' || this.search.type == '3') &&
this.search.startTime == null
) {
fileName = `商品维度统计表_${name}`
} else if (
this.search.startTime != null &&
(this.search.type == '' || this.search.type == '3')
) {
fileName = `商品维度统计表_${date}`
} else {
fileName = `商品维度统计表_${name}_${date}`
}
this.exportExcel(res.data, fileName)
})
}
}
},
在导出之前,需要按照要求对导出文件名做一定拼接(情况挺多的,我写的肯定是最笨的方法),之后进行下载,调用exportExcel()这个函数可以通用了的
// 自动下载从后端返回的excel
exportExcel(blobData, filename) {
const link = document.createElement('a')
let blob = new Blob([blobData], {
type:
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
console.log(link.href)
link.download = filename //下载的文件名 ret.data['Content-Disposition'].split(';')[1].split('filename=')[1]
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
其中使用到的日期formate()方法也是可以通用的。
format(time, fmt) {
//author: meizz
var o = {
'M+': time.getMonth() + 1, //月份
'd+': time.getDate(), //日
'h+': time.getHours(), //小时
'm+': time.getMinutes(), //分
's+': time.getSeconds(), //秒
'q+': Math.floor((time.getMonth() + 3) / 3), //季度
S: time.getMilliseconds(), //毫秒
}
if (/(y+)/.test(fmt))
fmt = fmt.replace(
RegExp.$1,
(time.getFullYear() + '').substr(4 - RegExp.$1.length)
)
for (var k in o)
if (new RegExp('(' + k + ')').test(fmt))
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1
? o[k]
: ('00' + o[k]).substr(('' + o[k]).length)
)
return fmt
},
(后记:这是第一次参与实际项目(我太菜了 勿吐槽),并且排期只给一天联调一天,做的还是战战兢兢如履薄冰的,其实记得遇到了很多问题,各种各样的小问题,(我太菜了 勿吐槽)很多还是面试常考点,当时只会背面试题,现在才算用到,(我太菜了 勿吐槽)但还好完成了,收获很大,果然只有做项目才学的更快)