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

363 lines
10 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="user-statistics-page" v-loading="loading">
<div v-if="mode !== 'day'" class="query-section">
<el-form :inline="true" @keyup.enter.native="getDataList">
<el-form-item :label="queryLabel">
<el-date-picker
v-model="currentDate"
:type="pickerType"
:format="displayFormat"
:value-format="valueFormat"
placeholder="请选择"
size="small"
popper-append-to-body
:picker-options="pickerOptions"
@change="handleQueryChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="refreshData">刷新</el-button>
</el-form-item>
</el-form>
</div>
<div class="summary-section">
<div class="summary-card">
<div class="summary-card__label">{{ timePrefix }}登录人数</div>
<div class="summary-card__value">{{ summary.loginCount }}</div>
</div>
<div class="summary-card">
<div class="summary-card__label">{{ timePrefix }}注册人数</div>
<div class="summary-card__value">{{ summary.createCount }}</div>
</div>
<div class="summary-card">
<div class="summary-card__label">截止今日 近30天内活跃人数</div>
<div class="summary-card__value">{{ summary.userCountFor30Day }}</div>
</div>
</div>
<div class="table-section">
<div class="table-header">
<div class="table-title">用户统计明细{{ total }}</div>
<el-button
type="success"
size="small"
:loading="exportLoading"
@click="exportData"
>下载报表</el-button>
</div>
<el-table
:data="tableData"
border
:height="mode !== 'day' ? 470 : 520"
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
>
<el-table-column type="index" label="序号" width="70" :index="indexMethod" />
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="tel" label="电话" min-width="130" />
<el-table-column prop="createTime" label="注册时间" min-width="120" />
<el-table-column prop="loginCity" label="城市" min-width="100" />
<el-table-column prop="come" label="注册来源" min-width="110" />
<el-table-column prop="socialIdentity" label="社会身份" min-width="120" />
<el-table-column prop="identity" label="app用户身份" min-width="120" />
<el-table-column prop="jf" label="天医币" min-width="90" />
<el-table-column prop="point" label="积分" min-width="90" />
<el-table-column prop="beforVip" label="曾vip" min-width="100" />
<el-table-column prop="nowVip" label="现vip" min-width="100" />
<el-table-column prop="ubo" label="是否买过课程" min-width="110" />
<el-table-column prop="ucbl" label="是否复购" min-width="90" />
<el-table-column prop="ucerCount" label="课程证几张" min-width="100" />
<el-table-column prop="loginTime" label="上次登录时间" min-width="160" />
<el-table-column prop="loginMachine" label="设备" min-width="90" />
<el-table-column prop="sex" label="性别" min-width="80" />
<el-table-column prop="nowWatch" label="当日学习时长" min-width="110" />
<el-table-column prop="watch7" label="近七天学习时间" min-width="120" />
<el-table-column prop="totalWatch" label="总学习时长" min-width="100" />
</el-table>
<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;"
/>
</div>
</div>
</template>
<script>
import http from '@/utils/httpRequest'
function formatDate(date) {
const y = date.getFullYear()
const m = `${date.getMonth() + 1}`.padStart(2, '0')
const d = `${date.getDate()}`.padStart(2, '0')
return `${y}-${m}-${d}`
}
function formatMonth(date) {
const y = date.getFullYear()
const m = `${date.getMonth() + 1}`.padStart(2, '0')
return `${y}-${m}`
}
function formatYear(date) {
return `${date.getFullYear()}`
}
function normalizeText(value) {
if (value === null || value === undefined || value === '') {
return '--'
}
const text = String(value).trim()
return text || '--'
}
function normalizeNumber(value) {
if (value === null || value === undefined || value === '') {
return 0
}
const num = Number(value)
return Number.isNaN(num) ? 0 : num
}
export default {
props: {
mode: {
type: String,
required: true
}
},
data() {
return {
loading: false,
exportLoading: false,
currentDate: '',
summary: {
loginCount: 0,
createCount: 0,
userCountFor30Day: 0
},
tableData: [],
page: 1,
limit: 10,
total: 0
}
},
computed: {
queryLabel() {
if (this.mode === 'day') return '统计日期'
if (this.mode === 'month') return '统计月份'
return '统计年份'
},
pickerType() {
if (this.mode === 'day') return 'date'
if (this.mode === 'month') return 'month'
return 'year'
},
displayFormat() {
if (this.mode === 'day') return 'yyyy-MM-dd'
if (this.mode === 'month') return 'yyyy-MM'
return 'yyyy'
},
valueFormat() {
return this.displayFormat
},
timePrefix() {
if (this.mode === 'day') return '当日'
if (this.mode === 'month') return '当月'
return '当年'
},
pickerOptions() {
return {
disabledDate(time) {
const now = new Date()
return time.getTime() > now.getTime()
}
}
}
},
activated() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.getDataList()
},
methods: {
indexMethod(index) {
return (this.page - 1) * this.limit + index + 1
},
handleQueryChange() {
this.page = 1
this.getDataList()
},
refreshData() {
this.page = 1
this.getDataList()
},
handleSizeChange(val) {
this.limit = val
this.page = 1
this.getDataList()
},
handleCurrentChange(val) {
this.page = val
this.getDataList()
},
getDefaultDate() {
const now = new Date()
if (this.mode === 'day') return formatDate(now)
if (this.mode === 'month') return formatMonth(now)
return formatYear(now)
},
normalizeRows(userList = []) {
return userList.map((item) => ({
name: normalizeText(item.name),
tel: normalizeText(item.tel),
createTime: normalizeText(item.createTime),
loginCity: normalizeText(item.loginCity),
come: normalizeText(item.come),
socialIdentity: normalizeText(item.socialIdentity),
identity: normalizeText(item.identity),
jf: normalizeNumber(item.jf),
point: normalizeNumber(item.point),
beforVip: normalizeText(item.beforVip),
nowVip: normalizeText(item.nowVip),
ubo: normalizeText(item.ubo),
ucbl: normalizeText(item.ucbl),
ucerCount: normalizeNumber(item.ucerCount),
loginTime: normalizeText(item.loginTime),
loginMachine: normalizeText(item.loginMachine),
sex: normalizeText(item.sex),
nowWatch: normalizeNumber(item.nowWatch),
watch7: normalizeNumber(item['7Watch']),
totalWatch: normalizeNumber(item.totalWatch)
}))
},
getDataList() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.loading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/getUserStatistics'),
method: 'post',
data: http.adornData({
date: this.currentDate,
page: this.page,
limit: this.limit
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.summary = {
loginCount: normalizeNumber(data.loginCount),
createCount: normalizeNumber(data.createCount),
userCountFor30Day: normalizeNumber(data.userCountFor30Day)
}
this.tableData = this.normalizeRows(data.userList || [])
this.total = normalizeNumber(data.totalSize)
} else {
this.summary = { loginCount: 0, createCount: 0, userCountFor30Day: 0 }
this.tableData = []
this.total = 0
}
}).finally(() => {
this.loading = false
})
},
exportData() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.exportLoading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/exportUserStatistics'),
method: 'post',
data: http.adornData({
date: this.currentDate
}),
responseType: 'blob'
}).then((response) => {
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `用户统计报表-${this.currentDate}.xlsx`
link.click()
URL.revokeObjectURL(link.href)
}).catch(() => {
this.$message.error('下载失败,请稍后重试')
}).finally(() => {
this.exportLoading = false
})
}
}
}
</script>
<style scoped lang="less">
@import '../styles/statistics-common.less';
.sb-form-item-zero();
.sb-table-compact();
.user-statistics-page {
display: flex;
flex-direction: column;
gap: 8px;
}
.query-section,
.summary-section,
.table-section {
.sb-white-panel(8px);
padding-bottom: 0;
}
.query-section {
padding-top: 0;
}
.summary-section {
display: flex;
flex-wrap: nowrap;
gap: 12px;
}
.summary-card {
.sb-summary-card-shell(180px, auto, auto);
min-width: 180px;
}
.summary-card__label {
margin-bottom: 8px;
}
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
.sb-title-16();
}
.sb-summary-card-text();
@media (max-width: 1200px) {
.summary-section {
flex-wrap: wrap;
}
.summary-card {
min-width: 200px;
}
}
</style>