| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- <template>
- <div class="app-container">
- <div class="period-switch">
- <el-radio-group v-model="periodType" @change="handlePeriodChange">
- <el-radio-button label="current">本月</el-radio-button>
- <el-radio-button label="last">上月</el-radio-button>
- </el-radio-group>
- <span class="period-label">{{ periodLabel }}</span>
- <el-select v-model="monthAppVersion" placeholder="APP 版本" clearable size="small" filterable style="margin-left: 20px; width: 150px;" @change="getData">
- <el-option v-for="item in appVersionList" :key="item.versionId" :label="item.versionName" :value="item.versionCode" />
- </el-select>
- </div>
- <el-row :gutter="20" class="card-row">
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #409EFF;">
- <i class="el-icon-user"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.newUsers || 0 }}</div>
- <div class="metric-label">新增用户数</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #67C23A;">
- <i class="el-icon-s-custom"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.totalUsers || 0 }}</div>
- <div class="metric-label">累计注册用户</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #E6A23C;">
- <i class="el-icon-s-data"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.activeUsers || 0 }}</div>
- <div class="metric-label">活跃用户数</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #F56C6C;">
- <i class="el-icon-connection"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.retentionRate != null ? reportData.retentionRate + '%' : '-' }}</div>
- <div class="metric-label">留存率</div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <el-row :gutter="20" class="card-row">
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #909399;">
- <i class="el-icon-time"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.avgUseDuration != null ? reportData.avgUseDuration + ' 分钟' : '-' }}</div>
- <div class="metric-label">用户平均使用时长</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #9C27B0;">
- <i class="el-icon-coin"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.ltv != null ? '¥' + reportData.ltv : '-' }}</div>
- <div class="metric-label">用户生命周期价值(LTV)</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #FF9800;">
- <i class="el-icon-money"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.cac != null ? '¥' + reportData.cac : '-' }}</div>
- <div class="metric-label">用户获取成本(CAC)</div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="hover" class="metric-card">
- <div class="metric-icon" style="background: #795548;">
- <i class="el-icon-warning-outline"></i>
- </div>
- <div class="metric-content">
- <div class="metric-value">{{ reportData.monthlyChurnRate != null ? reportData.monthlyChurnRate + '%' : '-' }}</div>
- <div class="metric-label">月流失率</div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <el-card shadow="hover" class="daily-report-card">
- <div slot="header" class="card-header">
- <span>日报表</span>
- </div>
- <el-form :inline="true" :model="dailyQueryParams" class="query-form">
- <el-form-item label="时间范围">
- <el-date-picker
- v-model="dailyTimeRange"
- type="daterange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="yyyy-MM-dd"
- size="small"
- style="width: 260px"
- @change="handleDailyDateChange">
- </el-date-picker>
- </el-form-item>
- <el-form-item label="APP 版本">
- <el-select v-model="dailyQueryParams.appVersion" placeholder="请选择 APP 版本" clearable size="small" filterable>
- <el-option v-for="item in appVersionList" :key="item.versionId" :label="item.versionName" :value="item.versionCode" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="el-icon-search" size="mini" @click="getDailyReport">查询</el-button>
- </el-form-item>
- </el-form>
- <el-table v-loading="dailyLoading" :data="dailyReportList" border style="width: 100%">
- <el-table-column label="日期" align="center" prop="statDate" width="120" />
- <el-table-column label="新增用户数" align="center" prop="newUsers" width="120" />
- <el-table-column label="累计注册用户" align="center" prop="totalUsers" width="130" />
- <el-table-column label="活跃用户数" align="center" prop="activeUsers" width="120" />
- <el-table-column label="次日留存率(%)" align="center" prop="nextDayRetentionRate" width="130" />
- <el-table-column label="平均使用时长(分钟)" align="center" prop="avgUseDuration" width="150" />
- <el-table-column label="LTV(元)" align="center" prop="ltv" width="100" />
- <el-table-column label="CAC(元)" align="center" prop="cac" width="100" />
- <el-table-column label="日流失率(%)" align="center" prop="dailyChurnRate" width="110" />
- </el-table>
- <pagination
- v-show="dailyTotal > 0"
- :total="dailyTotal"
- :page.sync="dailyQueryParams.pageNum"
- :limit.sync="dailyQueryParams.pageSize"
- @pagination="getDailyReport"
- />
- </el-card>
- <el-card shadow="hover" class="weekly-report-card">
- <div slot="header" class="card-header">
- <span>周报表</span>
- </div>
- <el-form :inline="true" :model="weeklyQueryParams" class="query-form">
- <el-form-item label="时间范围">
- <el-date-picker
- v-model="weeklyTimeRange"
- type="daterange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="yyyy-MM-dd"
- size="small"
- style="width: 260px"
- @change="handleWeeklyDateChange">
- </el-date-picker>
- </el-form-item>
- <el-form-item label="APP 版本">
- <el-select v-model="weeklyQueryParams.appVersion" placeholder="请选择 APP 版本" clearable size="small" filterable>
- <el-option v-for="item in appVersionList" :key="item.versionId" :label="item.versionName" :value="item.versionCode" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="el-icon-search" size="mini" @click="getWeeklyReport">查询</el-button>
- </el-form-item>
- </el-form>
- <el-table v-loading="weeklyLoading" :data="weeklyReportList" border style="width: 100%">
- <el-table-column label="周期" align="center" width="180">
- <template slot-scope="scope">
- 第{{ scope.$index + 1 + (weeklyQueryParams.pageNum - 1) * weeklyQueryParams.pageSize }}周 ({{ scope.row.weekStart }} ~ {{ scope.row.weekEnd }})
- </template>
- </el-table-column>
- <el-table-column label="新增用户数" align="center" prop="newUsers" width="120" />
- <el-table-column label="累计注册用户" align="center" prop="totalUsers" width="130" />
- <el-table-column label="活跃用户数" align="center" prop="activeUsers" width="120" />
- <el-table-column label="留存率(%)" align="center" prop="retentionRate" width="110" />
- <el-table-column label="平均使用时长(分钟)" align="center" prop="avgUseDuration" width="150" />
- <el-table-column label="LTV(元)" align="center" prop="ltv" width="100" />
- <el-table-column label="CAC(元)" align="center" prop="cac" width="100" />
- <el-table-column label="周流失率(%)" align="center" prop="weeklyChurnRate" width="110" />
- </el-table>
- <pagination
- v-show="weeklyTotal > 0"
- :total="weeklyTotal"
- :page.sync="weeklyQueryParams.pageNum"
- :limit.sync="weeklyQueryParams.pageSize"
- @pagination="getWeeklyReport"
- />
- </el-card>
- </div>
- </template>
- <script>
- import request from '@/utils/request'
- export default {
- name: "AppOperationReport",
- data() {
- return {
- periodType: "current",
- reportData: {},
- year: null,
- month: null,
- monthAppVersion: null,
- appVersionList: [],
- dailyTimeRange: null,
- dailyLoading: false,
- dailyReportList: [],
- dailyTotal: 0,
- dailyQueryParams: {
- pageNum: 1,
- pageSize: 10,
- startDate: null,
- endDate: null,
- appVersion: null
- },
- weeklyTimeRange: null,
- weeklyLoading: false,
- weeklyReportList: [],
- weeklyTotal: 0,
- weeklyQueryParams: {
- pageNum: 1,
- pageSize: 10,
- startDate: null,
- endDate: null,
- appVersion: null
- }
- };
- },
- computed: {
- periodLabel() {
- return this.year + '年' + this.month + '月';
- }
- },
- created() {
- this.initPeriod();
- this.getAppVersionList();
- this.getData();
- },
- methods: {
- initPeriod() {
- const now = new Date();
- if (this.periodType === 'current') {
- this.year = now.getFullYear();
- this.month = now.getMonth() + 1;
- } else {
- const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
- this.year = lastMonth.getFullYear();
- this.month = lastMonth.getMonth() + 1;
- }
- },
- handlePeriodChange() {
- this.initPeriod();
- this.getData();
- },
- getData() {
- request({
- url: '/his/appOperationReport/monthReport',
- method: 'get',
- params: {
- year: this.year,
- month: this.month,
- appVersion: this.monthAppVersion
- }
- }).then(response => {
- this.reportData = response.data || {};
- });
- },
- getAppVersionList() {
- request({
- url: '/his/appVersion/appVersions',
- method: 'get'
- }).then(response => {
- this.appVersionList = response.data || [];
- });
- },
- handleDailyDateChange() {
- if (this.dailyTimeRange && this.dailyTimeRange.length === 2) {
- this.dailyQueryParams.startDate = this.dailyTimeRange[0];
- this.dailyQueryParams.endDate = this.dailyTimeRange[1];
- } else {
- this.dailyQueryParams.startDate = null;
- this.dailyQueryParams.endDate = null;
- }
- },
- getDailyReport() {
- if (!this.dailyQueryParams.startDate || !this.dailyQueryParams.endDate) {
- this.$message.warning('请选择时间范围');
- return;
- }
- this.dailyLoading = true;
- request({
- url: '/his/appOperationReport/dailyReport',
- method: 'get',
- params: this.dailyQueryParams
- }).then(response => {
- this.dailyReportList = response.rows || [];
- this.dailyTotal = response.total || 0;
- }).finally(() => {
- this.dailyLoading = false;
- });
- },
- handleWeeklyDateChange() {
- if (this.weeklyTimeRange && this.weeklyTimeRange.length === 2) {
- this.weeklyQueryParams.startDate = this.weeklyTimeRange[0];
- this.weeklyQueryParams.endDate = this.weeklyTimeRange[1];
- } else {
- this.weeklyQueryParams.startDate = null;
- this.weeklyQueryParams.endDate = null;
- }
- },
- getWeeklyReport() {
- if (!this.weeklyQueryParams.startDate || !this.weeklyQueryParams.endDate) {
- this.$message.warning('请选择时间范围');
- return;
- }
- this.weeklyLoading = true;
- request({
- url: '/his/appOperationReport/weeklyReport',
- method: 'get',
- params: this.weeklyQueryParams
- }).then(response => {
- this.weeklyReportList = response.rows || [];
- this.weeklyTotal = response.total || 0;
- }).finally(() => {
- this.weeklyLoading = false;
- });
- }
- }
- };
- </script>
- <style scoped>
- .period-switch {
- margin-bottom: 20px;
- display: flex;
- align-items: center;
- }
- .period-label {
- margin-left: 20px;
- font-size: 16px;
- font-weight: bold;
- color: #303133;
- }
- .card-row {
- margin-bottom: 20px;
- }
- .metric-card {
- display: flex;
- align-items: center;
- padding: 20px;
- }
- .metric-card >>> .el-card__body {
- display: flex;
- align-items: center;
- width: 100%;
- padding: 20px;
- }
- .metric-icon {
- width: 60px;
- height: 60px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .metric-icon i {
- font-size: 28px;
- color: #fff;
- }
- .metric-content {
- margin-left: 20px;
- flex: 1;
- }
- .metric-value {
- font-size: 24px;
- font-weight: bold;
- color: #303133;
- line-height: 1.2;
- }
- .metric-label {
- font-size: 13px;
- color: #909399;
- margin-top: 6px;
- }
- .daily-report-card {
- margin-top: 20px;
- }
- .card-header {
- font-size: 16px;
- font-weight: bold;
- }
- .query-form {
- margin-bottom: 15px;
- }
- .weekly-report-card {
- margin-top: 20px;
- }
- </style>
|