feat(统计业务): 新增月度和年度VIP统计页面及数据处理功能
- 添加月度VIP统计页面,支持选择月份并展示相关统计信息 - 添加年度VIP统计页面,支持选择年份并展示年度统计数据 - 实现数据导出功能,支持下载月度和年度VIP统计报表 - 集成表格展示VIP用户的详细信息,包括办理时间、姓名、电话等 - 新增数据处理辅助函数,支持统计数据的格式化和计算
This commit is contained in:
348
src/views/modules/statisticsBusiness/vipStatistics/yearVip.vue
Normal file
348
src/views/modules/statisticsBusiness/vipStatistics/yearVip.vue
Normal file
@@ -0,0 +1,348 @@
|
||||
<template>
|
||||
<div class="year-vip-statistics" v-loading="loading">
|
||||
<div class="query-section">
|
||||
<el-form :inline="true" @keyup.enter.native="getDataList">
|
||||
<el-form-item label="统计年份">
|
||||
<el-date-picker
|
||||
v-model="currentYear"
|
||||
type="year"
|
||||
format="yyyy"
|
||||
value-format="yyyy"
|
||||
placeholder="选择年份"
|
||||
size="small"
|
||||
popper-append-to-body
|
||||
:picker-options="pickerOptions"
|
||||
@change="getDataList"
|
||||
/>
|
||||
</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"
|
||||
:loading="exportLoading"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="summary-section">
|
||||
<div
|
||||
v-for="(card, index) in summaryCards"
|
||||
:key="card.title + index"
|
||||
:class="[
|
||||
'summary-card',
|
||||
{
|
||||
'summary-card--two-columns': isTwoColumnCard(card)
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div class="summary-card__title">{{ card.title }}</div>
|
||||
<div class="summary-card__body">
|
||||
<div
|
||||
v-for="line in card.lines"
|
||||
:key="card.title + line.label"
|
||||
class="summary-line"
|
||||
>
|
||||
<span class="summary-line__label">{{ line.label }}</span>
|
||||
<span class="summary-line__value">{{ line.value }}{{ line.unit || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--orange">
|
||||
<div class="table-title">VIP办理情况统计</div>
|
||||
<el-table
|
||||
:data="handleTableRows"
|
||||
border
|
||||
:header-cell-style="orangeTableHeaderStyle"
|
||||
:cell-style="orangeTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="typeName" label="" min-width="140" />
|
||||
<el-table-column prop="ban3" label="首次办理三年" min-width="120" />
|
||||
<el-table-column prop="ban4" label="首次办理四年" min-width="120" />
|
||||
<el-table-column prop="banOther" label="首次办理其他" min-width="120" />
|
||||
<el-table-column prop="yan1" label="延期一年" min-width="110" />
|
||||
<el-table-column prop="yan3" label="延期三年" min-width="110" />
|
||||
<el-table-column prop="yan4" label="延期四年" min-width="110" />
|
||||
<el-table-column prop="yanOther" label="延期办理其他" min-width="120" />
|
||||
<el-table-column prop="total" label="合计" min-width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--yellow">
|
||||
<div class="table-title">VIP分类年度办理人数占比</div>
|
||||
<el-table
|
||||
:data="ratioTableRows"
|
||||
border
|
||||
:header-cell-style="yellowTableHeaderStyle"
|
||||
:cell-style="yellowTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="typeName" label="分类" min-width="180" />
|
||||
<el-table-column prop="ratio" label="年度办理人数占比" min-width="180" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--blue">
|
||||
<div class="table-title">每月明细</div>
|
||||
<el-table
|
||||
:data="monthTableRows"
|
||||
border
|
||||
:header-cell-style="blueTableHeaderStyle"
|
||||
:cell-style="blueTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="monthLabel" label="办理时间" min-width="140" />
|
||||
<el-table-column prop="banCount" label="办理人数" min-width="140" />
|
||||
<el-table-column prop="yanCount" label="延期人数" min-width="140" />
|
||||
<el-table-column prop="amount" label="缴费金额" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.amount }}元
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
import {
|
||||
buildYearVipViewData,
|
||||
getCurrentYearValue,
|
||||
getPreviousYearValue,
|
||||
normalizeYearValue
|
||||
} from './yearVipDataHelper'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
currentYear: getCurrentYearValue(),
|
||||
summaryCards: [],
|
||||
handleTableRows: [],
|
||||
ratioTableRows: [],
|
||||
monthTableRows: [],
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
const now = new Date()
|
||||
return time.getFullYear() > now.getFullYear()
|
||||
}
|
||||
},
|
||||
orangeTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
orangeTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
yellowTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
yellowTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
blueTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
blueTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
}
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
fetchYearData(year) {
|
||||
const normalizedYear = normalizeYearValue(year)
|
||||
return http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/getUserVipByYear'),
|
||||
method: 'post',
|
||||
data: http.adornData({ year: normalizedYear })
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
return data
|
||||
}
|
||||
return {}
|
||||
}).catch(() => ({}))
|
||||
},
|
||||
getDataList() {
|
||||
const currentYear = normalizeYearValue(this.currentYear)
|
||||
const previousYear = getPreviousYearValue(currentYear)
|
||||
this.currentYear = currentYear
|
||||
this.loading = true
|
||||
|
||||
Promise.all([
|
||||
this.fetchYearData(currentYear),
|
||||
this.fetchYearData(previousYear)
|
||||
]).then(([currentData, previousData]) => {
|
||||
const viewData = buildYearVipViewData(currentData, previousData)
|
||||
this.summaryCards = viewData.summaryCards
|
||||
this.handleTableRows = viewData.handleTableRows
|
||||
this.ratioTableRows = viewData.ratioTableRows
|
||||
this.monthTableRows = viewData.monthTableRows
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
const year = normalizeYearValue(this.currentYear)
|
||||
this.currentYear = year
|
||||
this.exportLoading = true
|
||||
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/exportUserVipByYear'),
|
||||
method: 'post',
|
||||
data: http.adornData({ year }),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `${year}年VIP统计报表.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
isTwoColumnCard(card) {
|
||||
return ['办理统计', '延期统计'].includes(card.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.year-vip-statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-section,
|
||||
.summary-section,
|
||||
.table-section {
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex: 1 1 260px;
|
||||
width: 260px;
|
||||
flex-direction: column;
|
||||
min-height: 180px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
|
||||
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
|
||||
}
|
||||
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 360px;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.summary-card__title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-card__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 30px;
|
||||
}
|
||||
|
||||
.summary-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.summary-line__label {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-line__value {
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: calc(50% - 6px);
|
||||
width: calc(50% - 6px);
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user