Files
nuttyreading-master-html/src/views/modules/statisticsBusiness/courseStatistics/courseStatistics.vue
chenghuan b5f280b02f style(统计业务): 统一统计页面样式和布局
- 引入公共样式文件,简化各统计页面的样式管理
- 优化统计页面的容器布局,采用flex布局以提升响应式表现
- 统一表单项的样式,确保一致性和可读性
- 移除冗余的样式定义,提升代码整洁度
2026-03-23 13:19:46 +08:00

440 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="course-statistics-container">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item>
<el-radio-group v-model="timeType" size="small" @change="handleTimeTypeChange">
<el-radio-button label="year">按年份</el-radio-button>
<el-radio-button label="month">按月份</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-date-picker
v-show="timeType === 'year'"
v-model="currentDate"
type="year"
format="yyyy"
placeholder="选择年份"
popper-append-to-body
@change="getDataList"
size="small"
/>
<el-date-picker
v-show="timeType === 'month'"
v-model="currentDate"
type="month"
format="yyyy-MM"
placeholder="选择月份"
popper-append-to-body
@change="getDataList"
size="small"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="small"
@click="getDataList"
>刷新</el-button>
</el-form-item>
<el-form-item>
<el-button
type="success"
size="small"
@click="exportData"
>下载报表</el-button>
</el-form-item>
</el-form>
<div class="table-container" ref="tableContainer">
<el-table
:data="dataList"
border
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
:max-height="tableHeight"
>
<el-table-column prop="courseTitle" label="课程名称" min-width="150" />
<el-table-column prop="catalogueTitle" label="目录名" min-width="150" />
<el-table-column prop="courseLabel" label="分类" min-width="120" />
<el-table-column
:label="timeType === 'year' ? '年销量' : '月销量'"
prop="sales"
min-width="100"
/>
<el-table-column
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行+天医币)' : '月销售额(微信+支付宝+银行+天医币)'"
min-width="280"
>
<template slot-scope="scope">
{{ scope.row.salesFee }}
</template>
</el-table-column>
<el-table-column
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行)' : '月销售额(微信+支付宝+银行)'"
min-width="250"
>
<template slot-scope="scope">
{{ scope.row.cashFee }}
</template>
</el-table-column>
<el-table-column
label="详情"
fixed="right"
min-width="100"
>
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="showDetail(scope.row)"
>详情</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
:current-page="page"
:page-size="limit"
:total="total"
layout="total, prev, pager, next, sizes"
:page-sizes="[10, 20, 30, 50]"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="margin-top: 15px; text-align: right;"
/>
<el-dialog
:visible.sync="yearDetailDialogVisible"
top="10vh"
title="课程年份明细"
width="90%"
>
<div class="dialog-header">
<span class="dialog-title">{{ yearDialogTitle }}</span>
<el-button
type="success"
size="small"
@click="exportDetailData(currentYearDetailDate, true)"
>下载详情报表</el-button>
</div>
<el-table
:data="yearDetailDataList"
border
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
:height="yearDetailTableHeight"
>
<el-table-column prop="monthTime" label="月份" min-width="100" />
<el-table-column prop="sales" label="销量" min-width="100" />
<el-table-column
label="月销售额(微信+支付宝+银行+天医币)"
min-width="280"
>
<template slot-scope="scope">
{{ scope.row.salesFee }}
</template>
</el-table-column>
<el-table-column
label="月销售额(微信+支付宝+银行)"
min-width="250"
>
<template slot-scope="scope">
{{ scope.row.cashFee }}
</template>
</el-table-column>
<el-table-column prop="wx" label="微信支付" min-width="120" />
<el-table-column prop="zfb" label="支付宝支付" min-width="120" />
<el-table-column prop="bank" label="银行支付" min-width="120" />
<el-table-column prop="point" label="天医币支付" min-width="120" />
<el-table-column prop="jf" label="积分支付" min-width="120" />
<el-table-column prop="relearnCount" label="复读人数" min-width="100" />
<el-table-column
label="复读占比(按销量)"
min-width="120"
>
<template slot-scope="scope">
{{ scope.row.sales > 0 ? ((scope.row.relearnCount / scope.row.sales * 100).toFixed(2) + '%') : '0%' }}
</template>
</el-table-column>
<el-table-column prop="miaoshaCount" label="秒杀人数" min-width="100" />
<el-table-column
label="秒杀占比(按销量)"
min-width="120"
>
<template slot-scope="scope">
{{ scope.row.sales > 0 ? ((scope.row.miaoshaCount / scope.row.sales * 100).toFixed(2) + '%') : '0%' }}
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog
:visible.sync="monthDetailDialogVisible"
top="10vh"
title="课程月份明细"
width="90%"
>
<div class="dialog-header">
<span class="dialog-title">{{ monthDialogTitle }}</span>
<el-button
type="success"
size="small"
@click="exportDetailData(currentMonthDetailDate, false)"
>下载详情报表</el-button>
</div>
<el-table
:data="monthDetailDataList"
border
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
:height="monthDetailTableHeight"
>
<el-table-column prop="time" label="时间" min-width="160" />
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="tel" label="注册电话" min-width="120" />
<el-table-column
label="课程时长1个月/3个月半年期/一年期)"
min-width="160"
>
<template slot-scope="scope">
{{ scope.row.days }}
</template>
</el-table-column>
<el-table-column
label="是否复读"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.orderType === '是' ? '是' : '否' }}
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import http from '@/utils/httpRequest'
export default {
data() {
return {
timeType: 'year',
currentDate: new Date(),
dataForm: {},
dataList: [],
yearDetailDataList: [],
monthDetailDataList: [],
yearDetailDialogVisible: false,
monthDetailDialogVisible: false,
yearDialogTitle: '',
monthDialogTitle: '',
currentYearDetailDate: '',
currentMonthDetailDate: '',
currentCatalogueId: '',
yearDetailTableHeight: '',
monthDetailTableHeight: '',
page: 1,
limit: 10,
total: 0,
tableHeight: null
}
},
activated() {
const now = new Date()
if (this.timeType === 'year') {
this.currentDate = new Date(now.getFullYear(), 0, 1)
} else {
this.currentDate = now
}
this.getDataList()
},
mounted() {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
this.calculateTableHeight()
},
calculateTableHeight() {
if (this.$refs.tableContainer) {
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
} else {
this.tableHeight = 500
}
},
handleTimeTypeChange() {
if (this.timeType === 'year') {
const now = new Date()
this.currentDate = new Date(now.getFullYear(), 0, 1)
} else {
this.currentDate = new Date()
}
this.getDataList()
},
getDataList() {
const dateStr = this.timeType === 'year'
? this.currentDate.getFullYear().toString()
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
http({
url: http.adornUrl('/master/statisticsBusiness/getCourseSaleInfoByCourse'),
method: 'post',
data: http.adornData({
date: dateStr,
page: this.page,
limit: this.limit
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.courseSaleInfo || []
this.total = data.totalSize || 0
this.calculateTableHeight()
}
})
},
handleSizeChange(val) {
this.limit = val
this.getDataList()
},
handleCurrentChange(val) {
this.page = val
this.getDataList()
},
exportData() {
const dateStr = this.timeType === 'year'
? this.currentDate.getFullYear().toString()
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
http({
url: http.adornUrl('/master/statisticsBusiness/exportCourseSaleInfoByCourse'),
method: 'post',
data: http.adornData({
date: dateStr
}),
responseType: 'blob'
}).then((response) => {
const blob = new Blob([response.data])
const fileName = `课程统计报表_按课程_${dateStr}.xlsx`
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = fileName
link.click()
URL.revokeObjectURL(link.href)
})
},
showDetail(row) {
this.currentCatalogueId = row.catalogueId
const dateStr = this.timeType === 'year'
? this.currentDate.getFullYear().toString()
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
const dialogTitle = `${row.courseTitle} - ${row.catalogueTitle}`
const isYear = this.timeType === 'year'
if (isYear) {
this.currentYearDetailDate = dateStr
this.yearDialogTitle = dialogTitle
this.yearDetailDialogVisible = true
this.$nextTick(() => {
const windowHeight = window.innerHeight
this.yearDetailTableHeight = windowHeight * 0.8 - 140
})
http({
url: http.adornUrl('/master/statisticsBusiness/getCourseYearInfo'),
method: 'post',
data: http.adornData({
date: dateStr,
catalogueId: row.catalogueId
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.yearDetailDataList = data.courseSaleInfo || []
}
})
} else {
this.currentMonthDetailDate = dateStr
this.monthDialogTitle = dialogTitle
this.monthDetailDialogVisible = true
this.$nextTick(() => {
const windowHeight = window.innerHeight
this.monthDetailTableHeight = windowHeight * 0.8 - 140
})
http({
url: http.adornUrl('/master/statisticsBusiness/getCourseMonthInfo'),
method: 'post',
data: http.adornData({
date: dateStr,
catalogueId: row.catalogueId
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.monthDetailDataList = data.courseSaleInfo || []
}
})
}
},
exportDetailData(date, isYear) {
const exportUrl = isYear
? '/master/statisticsBusiness/exportCourseYearInfo'
: '/master/statisticsBusiness/exportCourseMonthInfo'
const fileName = isYear
? `课程年份明细报表_${this.yearDialogTitle}_${date}.xlsx`
: `课程月份明细报表_${this.monthDialogTitle}_${date}.xlsx`
http({
url: http.adornUrl(exportUrl),
method: 'post',
data: http.adornData({
date: date,
catalogueId: this.currentCatalogueId
}),
responseType: 'blob'
}).then((response) => {
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = fileName
link.click()
URL.revokeObjectURL(link.href)
})
}
}
}
</script>
<style scoped lang="less">
@import '../styles/statistics-common.less';
.course-statistics-container {
.sb-page-height-with-table();
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.dialog-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.sb-form-item-ten();
.sb-dialog-body-no-top-padding();
</style>