feat(统计业务): 新增月度和年度VIP统计页面及数据处理功能
- 添加月度VIP统计页面,支持选择月份并展示相关统计信息 - 添加年度VIP统计页面,支持选择年份并展示年度统计数据 - 实现数据导出功能,支持下载月度和年度VIP统计报表 - 集成表格展示VIP用户的详细信息,包括办理时间、姓名、电话等 - 新增数据处理辅助函数,支持统计数据的格式化和计算
This commit is contained in:
356
src/views/modules/statisticsBusiness/vipStatistics/monthVip.vue
Normal file
356
src/views/modules/statisticsBusiness/vipStatistics/monthVip.vue
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
<template>
|
||||||
|
<div class="month-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="currentMonth"
|
||||||
|
type="month"
|
||||||
|
format="yyyy-MM"
|
||||||
|
value-format="yyyy-MM"
|
||||||
|
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="detail-section">
|
||||||
|
<div class="detail-header">
|
||||||
|
<div class="detail-title">月度VIP明细({{ dataList.length }})</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="tableContainer" class="table-container">
|
||||||
|
<el-table
|
||||||
|
:data="dataList"
|
||||||
|
border
|
||||||
|
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||||
|
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||||
|
:max-height="tableHeight"
|
||||||
|
>
|
||||||
|
<el-table-column prop="time" label="时间" min-width="120" />
|
||||||
|
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||||
|
<el-table-column prop="tel" label="电话" min-width="140" />
|
||||||
|
<el-table-column prop="vipType" label="VIP类型" min-width="150" />
|
||||||
|
<el-table-column prop="isYan" label="是否延期" min-width="110" />
|
||||||
|
<el-table-column prop="currentYear" label="本次年限" min-width="110" />
|
||||||
|
<el-table-column prop="currentAmount" label="本次金额" min-width="130">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ formatAmountCell(scope.row.currentAmount) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="currentStartTime" label="本次开始时间" min-width="140" />
|
||||||
|
<el-table-column prop="currentEndTime" label="本次结束时间" min-width="140" />
|
||||||
|
<el-table-column prop="endTime" label="到期时间" min-width="140" />
|
||||||
|
<el-table-column prop="isReBuy" label="是否为复购" min-width="110" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import http from '@/utils/httpRequest'
|
||||||
|
import {
|
||||||
|
buildMonthVipViewData,
|
||||||
|
getCurrentMonthValue,
|
||||||
|
getPreviousMonthValue,
|
||||||
|
normalizeMonthValue
|
||||||
|
} from './monthVipDataHelper'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
exportLoading: false,
|
||||||
|
currentMonth: getCurrentMonthValue(),
|
||||||
|
summaryCards: [],
|
||||||
|
dataList: [],
|
||||||
|
tableHeight: null,
|
||||||
|
pickerOptions: {
|
||||||
|
disabledDate(time) {
|
||||||
|
const now = new Date()
|
||||||
|
return time.getTime() > new Date(now.getFullYear(), now.getMonth(), 1).getTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activated() {
|
||||||
|
this.getDataList()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.calculateTableHeight()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener('resize', this.handleResize)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.calculateTableHeight()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchMonthData(date) {
|
||||||
|
const normalizedDate = normalizeMonthValue(date)
|
||||||
|
return http({
|
||||||
|
url: http.adornUrl('/master/statisticsBusinessVip/getUserVipByMonth'),
|
||||||
|
method: 'post',
|
||||||
|
data: http.adornData({ date: normalizedDate })
|
||||||
|
}).then(({ data }) => {
|
||||||
|
if (data && data.code === 0) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}).catch(() => ({}))
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
if (!this.currentMonth) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
const currentMonth = normalizeMonthValue(this.currentMonth)
|
||||||
|
const previousMonth = getPreviousMonthValue(currentMonth)
|
||||||
|
this.currentMonth = currentMonth
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
this.fetchMonthData(currentMonth),
|
||||||
|
this.fetchMonthData(previousMonth)
|
||||||
|
]).then(([currentData, previousData]) => {
|
||||||
|
const viewData = buildMonthVipViewData(currentData, previousData)
|
||||||
|
this.summaryCards = viewData.cards
|
||||||
|
this.dataList = viewData.tableRows
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.calculateTableHeight()
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exportData() {
|
||||||
|
const currentMonth = normalizeMonthValue(this.currentMonth)
|
||||||
|
this.currentMonth = currentMonth
|
||||||
|
this.exportLoading = true
|
||||||
|
|
||||||
|
http({
|
||||||
|
url: http.adornUrl('/master/statisticsBusinessVip/exportUserVipByMonth'),
|
||||||
|
method: 'post',
|
||||||
|
data: http.adornData({ date: currentMonth }),
|
||||||
|
responseType: 'blob'
|
||||||
|
}).then((response) => {
|
||||||
|
const blob = new Blob([response.data])
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = `${currentMonth}月VIP统计报表.xlsx`
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.error('下载失败,请稍后重试')
|
||||||
|
}).finally(() => {
|
||||||
|
this.exportLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formatAmountCell(value) {
|
||||||
|
if (value === '--') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return value + '元'
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-vip-statistics {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
height: calc(100vh - 220px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-section,
|
||||||
|
.summary-section,
|
||||||
|
.detail-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 260px;
|
||||||
|
width: 260px;
|
||||||
|
min-height: 190px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1680px) {
|
||||||
|
.summary-card {
|
||||||
|
flex-basis: 240px;
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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>
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
function toNumber(value) {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isNaN(num) ? 0 : num
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAmount(value) {
|
||||||
|
return toNumber(value).toFixed(2).replace(/\.00$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRate(current, previous) {
|
||||||
|
const currentValue = toNumber(current)
|
||||||
|
const previousValue = toNumber(previous)
|
||||||
|
|
||||||
|
if (previousValue === 0) {
|
||||||
|
return currentValue === 0 ? '0.00%' : '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (((currentValue - previousValue) / previousValue) * 100).toFixed(2) + '%'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatYear(year) {
|
||||||
|
if (year === '' || year === null || year === undefined) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
return year + '年'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatText(value) {
|
||||||
|
if (value === '' || value === null || value === undefined) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(value).trim()
|
||||||
|
return text || '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDynamicKeys(...sources) {
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
sources.forEach((source) => {
|
||||||
|
Object.keys(source || {}).forEach((key) => {
|
||||||
|
const text = formatText(key)
|
||||||
|
if (text !== '--' && !keys.includes(text)) {
|
||||||
|
keys.push(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCountLines(totalLabel, totalValue, counts) {
|
||||||
|
const dynamicLines = getDynamicKeys(counts).map((key) => ({
|
||||||
|
label: key,
|
||||||
|
value: toNumber(counts[key]),
|
||||||
|
unit: '人'
|
||||||
|
}))
|
||||||
|
|
||||||
|
return [{ label: totalLabel, value: toNumber(totalValue), unit: '人' }].concat(dynamicLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRenewalLines(currentData) {
|
||||||
|
const dynamicKeys = getDynamicKeys(currentData.banCounts, currentData.yanCounts)
|
||||||
|
|
||||||
|
return dynamicKeys.map((key) => ({
|
||||||
|
label: key,
|
||||||
|
value: '开发中',
|
||||||
|
unit: ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGrowthLines(currentData, previousData) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '办理总人数环比增长率',
|
||||||
|
value: formatRate(currentData.banTotalCount, previousData.banTotalCount)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '延期总人数环比增长率',
|
||||||
|
value: formatRate(currentData.yanTotalCount, previousData.yanTotalCount)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '办理金额环比增长率',
|
||||||
|
value: formatRate(currentData.banTotalPrice, previousData.banTotalPrice)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '延期金额环比增长率',
|
||||||
|
value: formatRate(currentData.yanTotalPrice, previousData.yanTotalPrice)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCards(currentData, previousData) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '办理统计',
|
||||||
|
lines: buildCountLines('办理总人数', currentData.banTotalCount, currentData.banCounts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '延期统计',
|
||||||
|
lines: buildCountLines('延期总人数', currentData.yanTotalCount, currentData.yanCounts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '金额统计',
|
||||||
|
lines: [
|
||||||
|
{ label: '总办理金额', value: getAmount(currentData.banTotalPrice), unit: '元' },
|
||||||
|
{ label: '总延期金额', value: getAmount(currentData.yanTotalPrice), unit: '元' },
|
||||||
|
{
|
||||||
|
label: '合计总金额',
|
||||||
|
value: getAmount(toNumber(currentData.banTotalPrice) + toNumber(currentData.yanTotalPrice)),
|
||||||
|
unit: '元'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: '续费率',
|
||||||
|
// lines: buildRenewalLines(currentData)
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '环比增长率',
|
||||||
|
lines: buildGrowthLines(currentData, previousData)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTableRows(list) {
|
||||||
|
return (list || []).map((item) => {
|
||||||
|
return {
|
||||||
|
time: formatText(item.payTime),
|
||||||
|
name: formatText(item.name),
|
||||||
|
tel: formatText(item.tel),
|
||||||
|
vipType: formatText(item.vipType),
|
||||||
|
isYan: formatText(item.isYan),
|
||||||
|
currentYear: formatYear(item.year),
|
||||||
|
currentAmount: getAmount(item.price),
|
||||||
|
currentStartTime: formatText(item.uvlStartTime || item.startTime),
|
||||||
|
currentEndTime: formatText(item.uvlEndTime || item.endTime),
|
||||||
|
endTime: formatText(item.endTime || item.uvlEndTime),
|
||||||
|
isReBuy: formatText(item.isReBuy)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentMonthValue() {
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
return `${year}-${month}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeMonthValue(value) {
|
||||||
|
if (!value) {
|
||||||
|
return getCurrentMonthValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.toString.call(value) === '[object Date]' && !Number.isNaN(value.getTime())) {
|
||||||
|
const year = value.getFullYear()
|
||||||
|
const month = String(value.getMonth() + 1).padStart(2, '0')
|
||||||
|
return `${year}-${month}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(value).trim()
|
||||||
|
const matched = text.match(/^(\d{4})-(\d{1,2})/)
|
||||||
|
if (matched) {
|
||||||
|
return `${matched[1]}-${String(matched[2]).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCurrentMonthValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPreviousMonthValue(monthValue) {
|
||||||
|
const normalizedMonthValue = normalizeMonthValue(monthValue)
|
||||||
|
const parts = normalizedMonthValue.split('-')
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
return getCurrentMonthValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = Number(parts[0])
|
||||||
|
const month = Number(parts[1])
|
||||||
|
const date = new Date(year, month - 2, 1)
|
||||||
|
const prevYear = date.getFullYear()
|
||||||
|
const prevMonth = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
return `${prevYear}-${prevMonth}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildMonthVipViewData(currentData = {}, previousData = {}) {
|
||||||
|
return {
|
||||||
|
cards: buildCards(currentData, previousData),
|
||||||
|
tableRows: buildTableRows(currentData.allResultList)
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
const TYPE_ORDER = [
|
||||||
|
'中医学',
|
||||||
|
'中西汇通学',
|
||||||
|
'肿瘤学',
|
||||||
|
'针灸学',
|
||||||
|
'心理学',
|
||||||
|
'国学',
|
||||||
|
'医学超级',
|
||||||
|
'心理国学超级'
|
||||||
|
]
|
||||||
|
|
||||||
|
const TYPE_ALIAS_MAP = {
|
||||||
|
国学心理学超级: '心理国学超级'
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value) {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isNaN(num) ? 0 : num
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmount(value) {
|
||||||
|
return toNumber(value).toFixed(2).replace(/\.00$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTypeName(name) {
|
||||||
|
return TYPE_ALIAS_MAP[name] || name
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentYearValue() {
|
||||||
|
return String(new Date().getFullYear())
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeYearValue(value) {
|
||||||
|
if (!value) {
|
||||||
|
return getCurrentYearValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.toString.call(value) === '[object Date]' && !Number.isNaN(value.getTime())) {
|
||||||
|
return String(value.getFullYear())
|
||||||
|
}
|
||||||
|
|
||||||
|
const matched = String(value).trim().match(/^(\d{4})/)
|
||||||
|
if (matched) {
|
||||||
|
return matched[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCurrentYearValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviousYearValue(yearValue) {
|
||||||
|
return String(Number(normalizeYearValue(yearValue)) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDynamicTypeKeys(...sources) {
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
sources.forEach((source) => {
|
||||||
|
Object.keys(source || {}).forEach((key) => {
|
||||||
|
const normalizedKey = normalizeTypeName(key)
|
||||||
|
if (!keys.includes(normalizedKey)) {
|
||||||
|
keys.push(normalizedKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderedKeys = TYPE_ORDER.filter((key) => keys.includes(key))
|
||||||
|
const restKeys = keys.filter((key) => !TYPE_ORDER.includes(key))
|
||||||
|
return orderedKeys.concat(restKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMappedCount(source, typeName, yearKey) {
|
||||||
|
const originalKey = Object.keys(source || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||||
|
if (!originalKey) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return toNumber(((source || {})[originalKey] || {})[yearKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMappedValue(source, typeName) {
|
||||||
|
const originalKey = Object.keys(source || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||||
|
if (!originalKey) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return toNumber((source || {})[originalKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCountLines(totalLabel, totalValue, counts) {
|
||||||
|
return [{ label: totalLabel, value: toNumber(totalValue), unit: '人' }].concat(
|
||||||
|
getDynamicTypeKeys(counts).map((typeName) => ({
|
||||||
|
label: typeName,
|
||||||
|
value: getMappedValue(counts, typeName),
|
||||||
|
unit: '人'
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatYearRate(current, previous) {
|
||||||
|
const currentValue = toNumber(current)
|
||||||
|
const previousValue = toNumber(previous)
|
||||||
|
|
||||||
|
if (currentValue === 0) {
|
||||||
|
return '0.00%'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${(((currentValue - previousValue) / currentValue) * 100).toFixed(2)}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSummaryCards(currentData, previousData) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '办理统计',
|
||||||
|
lines: buildCountLines('办理总人数', currentData.banTotalCount, currentData.banTypeTotalCounts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '延期统计',
|
||||||
|
lines: buildCountLines('延期总人数', currentData.yanTotalCount, currentData.yanTypeTotalCounts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '金额统计',
|
||||||
|
lines: [
|
||||||
|
{ label: '总办理金额', value: formatAmount(currentData.banTotalPrice), unit: '元' },
|
||||||
|
{ label: '总延期金额', value: formatAmount(currentData.yanTotalPrice), unit: '元' },
|
||||||
|
{
|
||||||
|
label: '合计总金额',
|
||||||
|
value: formatAmount(toNumber(currentData.banTotalPrice) + toNumber(currentData.yanTotalPrice)),
|
||||||
|
unit: '元'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '同比增长率',
|
||||||
|
lines: [
|
||||||
|
{
|
||||||
|
label: '办理总人数同比增长率',
|
||||||
|
value: formatYearRate(currentData.banTotalCount, previousData.banTotalCount)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '延期总人数同比增长率',
|
||||||
|
value: formatYearRate(currentData.yanTotalCount, previousData.yanTotalCount)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '办理金额同比增长率',
|
||||||
|
value: formatYearRate(currentData.banTotalPrice, previousData.banTotalPrice)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '延期金额同比增长率',
|
||||||
|
value: formatYearRate(currentData.yanTotalPrice, previousData.yanTotalPrice)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHandleTableRows(currentData) {
|
||||||
|
const typeKeys = getDynamicTypeKeys(currentData.banCounts, currentData.yanCounts)
|
||||||
|
const rows = typeKeys.map((typeName) => {
|
||||||
|
const ban3 = getMappedCount(currentData.banCounts, typeName, '3')
|
||||||
|
const ban4 = getMappedCount(currentData.banCounts, typeName, '4')
|
||||||
|
const banOther = getMappedCount(currentData.banCounts, typeName, '其他')
|
||||||
|
const yan1 = getMappedCount(currentData.yanCounts, typeName, '1')
|
||||||
|
const yan3 = getMappedCount(currentData.yanCounts, typeName, '3')
|
||||||
|
const yan4 = getMappedCount(currentData.yanCounts, typeName, '4')
|
||||||
|
const yanOther = getMappedCount(currentData.yanCounts, typeName, '其他')
|
||||||
|
|
||||||
|
return {
|
||||||
|
typeName,
|
||||||
|
ban3,
|
||||||
|
ban4,
|
||||||
|
banOther,
|
||||||
|
yan1,
|
||||||
|
yan3,
|
||||||
|
yan4,
|
||||||
|
yanOther,
|
||||||
|
total: ban3 + ban4 + banOther + yan1 + yan3 + yan4 + yanOther
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalRow = rows.reduce((acc, item) => {
|
||||||
|
acc.ban3 += item.ban3
|
||||||
|
acc.ban4 += item.ban4
|
||||||
|
acc.banOther += item.banOther
|
||||||
|
acc.yan1 += item.yan1
|
||||||
|
acc.yan3 += item.yan3
|
||||||
|
acc.yan4 += item.yan4
|
||||||
|
acc.yanOther += item.yanOther
|
||||||
|
acc.total += item.total
|
||||||
|
return acc
|
||||||
|
}, {
|
||||||
|
typeName: '合计',
|
||||||
|
ban3: 0,
|
||||||
|
ban4: 0,
|
||||||
|
banOther: 0,
|
||||||
|
yan1: 0,
|
||||||
|
yan3: 0,
|
||||||
|
yan4: 0,
|
||||||
|
yanOther: 0,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return rows.concat(totalRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRatioTableRows(currentData) {
|
||||||
|
return getDynamicTypeKeys(currentData.banTypeRatios).map((typeName) => {
|
||||||
|
const originalKey = Object.keys(currentData.banTypeRatios || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||||
|
return {
|
||||||
|
typeName,
|
||||||
|
ratio: (currentData.banTypeRatios || {})[originalKey] || '0%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMonthTableRows(currentData) {
|
||||||
|
const rows = Array.from({ length: 12 }, (_, index) => {
|
||||||
|
const month = String(index + 1)
|
||||||
|
return {
|
||||||
|
monthLabel: `${month}月`,
|
||||||
|
banCount: toNumber((currentData.banMonthCounts || {})[month]),
|
||||||
|
yanCount: toNumber((currentData.yanMonthCounts || {})[month]),
|
||||||
|
amount: formatAmount((currentData.monthAmounts || {})[month])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
monthLabel: '合计',
|
||||||
|
banCount: toNumber(currentData.banTotalCount),
|
||||||
|
yanCount: toNumber(currentData.yanTotalCount),
|
||||||
|
amount: formatAmount(currentData.banTotalPrice)
|
||||||
|
})
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getCurrentYearValue,
|
||||||
|
getPreviousYearValue,
|
||||||
|
normalizeYearValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildYearVipViewData(currentData = {}, previousData = {}) {
|
||||||
|
return {
|
||||||
|
summaryCards: buildSummaryCards(currentData, previousData),
|
||||||
|
handleTableRows: buildHandleTableRows(currentData),
|
||||||
|
ratioTableRows: buildRatioTableRows(currentData),
|
||||||
|
monthTableRows: buildMonthTableRows(currentData)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user