feat(统计业务): 新增用户统计页面及相关组件
- 在路由中添加用户统计页面,支持按日、月、年统计用户数据 - 新增用户统计基础组件,包含数据展示和导出功能 - 实现用户统计明细表格,展示用户注册、登录等信息 - 添加用户留存率统计页面,支持选择月份并展示相关数据 - 集成数据导出功能,支持下载用户统计报表
This commit is contained in:
@@ -94,7 +94,8 @@ const mainRoutes = {
|
||||
{ path: '/reportList-lingshuFullYear', component: _import('modules/reportList/lingshuFullYear'), name: 'reportList-lingshuFullYear', meta: { title: '灵枢年度报表', isTab: true } },
|
||||
{ path: '/content-psychologicalForum', component: _import('modules/content/psychologicalForum'), name: 'content-psychologicalForum', meta: { title: '心理论坛', isTab: true } },
|
||||
{ path: '/statisticsBusiness-courseStatistics', component: _import('modules/statisticsBusiness/courseStatistics/index'), name: 'statisticsBusiness-courseStatistics', meta: { title: '课程统计', isTab: true } },
|
||||
{ path: '/statisticsBusiness-vipStatistics', component: _import('modules/statisticsBusiness/vipStatistics/index'), name: 'statisticsBusiness-vipStatistics', meta: { title: 'VIP统计', isTab: true } }
|
||||
{ path: '/statisticsBusiness-vipStatistics', component: _import('modules/statisticsBusiness/vipStatistics/index'), name: 'statisticsBusiness-vipStatistics', meta: { title: 'VIP统计', isTab: true } },
|
||||
{ path: '/statisticsBusiness-userStatistics', component: _import('modules/statisticsBusiness/userStatistics/index'), name: 'statisticsBusiness-userStatistics', meta: { title: '用户统计', isTab: true } },
|
||||
],
|
||||
beforeEnter (to, from, next) {
|
||||
let token = Vue.cookie.get('token')
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<userStatisticsBase mode="day" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userStatisticsBase from './userStatisticsBase'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userStatisticsBase
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="首页" name="homeUser" />
|
||||
<el-tab-pane label="月度" name="monthUser" />
|
||||
<el-tab-pane label="年度" name="yearUser" />
|
||||
<el-tab-pane label="留存率" name="retainUser" />
|
||||
</el-tabs>
|
||||
<keep-alive>
|
||||
<component :is="activeTab" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import homeUser from './homeUser'
|
||||
import monthUser from './monthUser'
|
||||
import yearUser from './yearUser'
|
||||
import retainUser from './retainUser'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
homeUser,
|
||||
monthUser,
|
||||
yearUser,
|
||||
retainUser
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'homeUser'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<userStatisticsBase mode="month" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userStatisticsBase from './userStatisticsBase'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userStatisticsBase
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="retain-statistics-container" 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="table-section">
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
border
|
||||
:max-height="tableHeight"
|
||||
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
>
|
||||
<el-table-column prop="dayTime" label="日期" min-width="120" />
|
||||
<el-table-column prop="count" label="新增用户数" min-width="110" />
|
||||
<el-table-column prop="nextDay" label="次日留存用户数" min-width="130" />
|
||||
<el-table-column prop="nextRate" label="次日留存率" min-width="110" />
|
||||
<el-table-column prop="day7" label="7日留存用户数" min-width="120" />
|
||||
<el-table-column prop="rate7" label="7日留存率" min-width="100" />
|
||||
<el-table-column prop="day30" label="30日留存用户数" min-width="130" />
|
||||
<el-table-column prop="rate30" label="30日留存率" min-width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
function formatMonth(date) {
|
||||
const y = date.getFullYear()
|
||||
const m = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
}
|
||||
|
||||
function normalizeNumber(value) {
|
||||
if (value === null || value === undefined || value === '') return 0
|
||||
const num = Number(value)
|
||||
return Number.isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
function formatRate(value) {
|
||||
if (value === null || value === undefined || value === '') return '0%'
|
||||
if (typeof value === 'string') {
|
||||
const text = value.trim()
|
||||
if (!text) return '0%'
|
||||
if (text.includes('%')) return text
|
||||
const num = Number(text)
|
||||
if (Number.isNaN(num)) return '0%'
|
||||
if (num <= 1) return `${(num * 100).toFixed(0)}%`
|
||||
return `${num.toFixed(0)}%`
|
||||
}
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) return '0%'
|
||||
if (num <= 1) return `${(num * 100).toFixed(0)}%`
|
||||
return `${num.toFixed(0)}%`
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
currentMonth: '',
|
||||
tableData: [],
|
||||
tableHeight: 500,
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
const now = new Date()
|
||||
return time.getTime() > now.getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
if (!this.currentMonth) {
|
||||
this.currentMonth = formatMonth(new Date())
|
||||
}
|
||||
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
|
||||
}
|
||||
},
|
||||
normalizeRows(userList = []) {
|
||||
return userList.map((item) => ({
|
||||
dayTime: item.dayTime || '--',
|
||||
count: normalizeNumber(item.count),
|
||||
nextDay: normalizeNumber(item.nextDay),
|
||||
nextRate: formatRate(item.nextRate),
|
||||
day7: normalizeNumber(item['7Day']),
|
||||
rate7: formatRate(item['7Rate']),
|
||||
day30: normalizeNumber(item['30Day']),
|
||||
rate30: formatRate(item['30Rate'])
|
||||
}))
|
||||
},
|
||||
getDataList() {
|
||||
if (!this.currentMonth) {
|
||||
this.currentMonth = formatMonth(new Date())
|
||||
}
|
||||
this.loading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessUser/getUserRegisterStayInfo'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: this.currentMonth
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.tableData = this.normalizeRows(data.userList || [])
|
||||
} else {
|
||||
this.tableData = []
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
if (!this.currentMonth) {
|
||||
this.currentMonth = formatMonth(new Date())
|
||||
}
|
||||
this.exportLoading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessUser/exportUserRegisterStayInfo'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: this.currentMonth
|
||||
}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `留存率统计报表-${this.currentMonth}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.retain-statistics-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 220px);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-section,
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,383 @@
|
||||
<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">
|
||||
.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;
|
||||
}
|
||||
|
||||
.user-statistics-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-section,
|
||||
.summary-section,
|
||||
.table-section {
|
||||
padding: 8px;
|
||||
padding-bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.query-section {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex: 1 1 180px;
|
||||
min-width: 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__label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-card__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.summary-section {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<userStatisticsBase mode="year" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userStatisticsBase from './userStatisticsBase'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userStatisticsBase
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user