|
@@ -0,0 +1,1113 @@
|
|
|
+<template>
|
|
|
+ <div class="layout-padding">
|
|
|
+ <div class="!overflow-auto px-1">
|
|
|
+ <Lcard>
|
|
|
+ <div class="flex justify-start items-center">
|
|
|
+ <el-icon @click="() => selectChannel = '1'" class="ml-1" style="cursor: pointer; color: #333333; margin-right: 25px">
|
|
|
+ <ArrowLeftBold />
|
|
|
+ </el-icon>
|
|
|
+ <el-select v-model="selectChannel" style="width: 100px;">
|
|
|
+ <el-option label="全部渠道" value="1"></el-option>
|
|
|
+ <el-option label="安卓" value="2"></el-option>
|
|
|
+ <el-option label="苹果" value="3"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </Lcard>
|
|
|
+ <Lcard>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <Title left-line title="渠道趋势">
|
|
|
+ </Title>
|
|
|
+ <div class="box1">
|
|
|
+ <el-select v-model="selectVersion" style="width: 140px; margin-right: 30px;">
|
|
|
+ <el-option label="全部版本" value="1"></el-option>
|
|
|
+ <el-option label="1.0.1" value="2"></el-option>
|
|
|
+ <el-option label="1.02" value="3"></el-option>
|
|
|
+ </el-select>
|
|
|
+ <div class="box1-time">
|
|
|
+ <el-date-picker style="float: left; width: 240px; margin-right: 30px;" v-model="timeRange"
|
|
|
+ type="datetimerange" range-separator="To" start-placeholder="Start date" end-placeholder="End date" />
|
|
|
+ <el-radio-group v-model="timeGroup" style="width: 240px; margin-right: 30px;">
|
|
|
+ <el-radio-button label="week">过去七天</el-radio-button>
|
|
|
+ <el-radio-button label="month">过去三十天</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="link">导出数据
|
|
|
+ <span style="display: inline-block; transform:translateY(3px);">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <g clip-path="url(#clip0_713_1665)">
|
|
|
+ <path
|
|
|
+ d="M14 1.33301H1.99992C1.63172 1.33301 1.33324 1.63149 1.33325 1.99969L1.33358 13.9997C1.33359 14.3679 1.63207 14.6663 2.00025 14.6663H14C14.3682 14.6663 14.6667 14.3679 14.6667 13.9997V1.99967C14.6667 1.63148 14.3682 1.33301 14 1.33301Z"
|
|
|
+ stroke="#167AF0" />
|
|
|
+ <path d="M6.66968 11.3366H11.1669V6.66992" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M9.66675 8.16699L10.1667 7.66699L11.1667 6.66699L12.1667 7.66699L12.6667 8.16699"
|
|
|
+ stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M4.66675 1.33301V14.6663" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 4.67959L14.6666 4.66699" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M2.66675 1.33301H9.33341" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M2.66675 14.667H9.33341" stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M14.6667 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ </g>
|
|
|
+ <defs>
|
|
|
+ <clipPath id="clip0_713_1665">
|
|
|
+ <rect width="16" height="16" fill="white" />
|
|
|
+ </clipPath>
|
|
|
+ </defs>
|
|
|
+ </svg>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="line" style="margin: 35px -30px 40px;"></div>
|
|
|
+ <div class="flex items-center justify-between mb-2 mt-3" style="margin-top: 20px;margin-left: 60px;">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-radio-group v-model="lineChartUser">
|
|
|
+ <el-radio-button label="1">新增用户</el-radio-button>
|
|
|
+ <el-radio-button label="2">活跃用户</el-radio-button>
|
|
|
+ <el-radio-button label="3">启动次数</el-radio-button>
|
|
|
+ <el-radio-button label="4">平均单次使用时长</el-radio-button>
|
|
|
+ <el-radio-button label="5">新增次日留存率</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-select placeholder="对比分析" v-model="selectLineChannel" multiple clearable
|
|
|
+ style="width: 140px;margin: 20px 60px;">
|
|
|
+ <el-option label="时段对比" value="1"></el-option>
|
|
|
+ <el-option label="渠道对比" value="2"></el-option>
|
|
|
+ <el-option label="版本对比" value="3"></el-option>
|
|
|
+ </el-select>
|
|
|
+ <div class="relative">
|
|
|
+ <div ref="qChartRef" style="margin-left: 60px; width: calc(100% - 80px); height: 320px"></div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-3" style="margin-top: 20px;margin-left: 60px;">
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
+ <div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
|
|
|
+ {{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <el-button>导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-table v-if="showDetail1" :data="pagedTableRows" border>
|
|
|
+ <el-table-column prop="date" label="日期" min-width="140" />
|
|
|
+ <el-table-column label="新增用户(占比)" min-width="220">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="flex items-center justify-between w-full">
|
|
|
+ <span>{{ scope.row.newUsers }}</span>
|
|
|
+ <span class="text-gray-500 text-xs">{{ scope.row.ratio }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div v-if="showDetail1" class="flex justify-end mt-3">
|
|
|
+ <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" background
|
|
|
+ layout="total, prev, pager, next, sizes" :total="tableRows.length" :page-sizes="[5, 10, 20]" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Lcard>
|
|
|
+ <div class="flex justify-between">
|
|
|
+ <Lcard style="width: calc(50% - 5px);">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <Title left-line title="渠道趋势">
|
|
|
+ <el-popover class="box-item" placement="right" trigger="hover" width="250">
|
|
|
+ <template #reference>
|
|
|
+ <el-icon class="ml-1" style="color: #a4b8cf">
|
|
|
+ <InfoFilled />
|
|
|
+ </el-icon>
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <div class="ant-popover-inner-content">
|
|
|
+ 渠道活跃度,展示指定渠道用户的昨日活跃/过去7天活跃、昨日活跃/过去30天活跃的信息。<br>
|
|
|
+ 通过这两个指标,您可以了解到该渠道用户的粘着度。昨日活跃/过去30天活跃越接入100%,<br>
|
|
|
+ 用户越活跃,流失率越低,粘性越强这里的活跃用户是去重后的活跃用户
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-popover>
|
|
|
+ </Title>
|
|
|
+ <div style="font-size: 15px; color: #646464;">
|
|
|
+ 2025/08/19
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div ref="qdqsRef1" style="width: 48%; height: 220px;"></div>
|
|
|
+ <div ref="qdqsRef2" style="width: 48%; height: 220px;"></div>
|
|
|
+ </div>
|
|
|
+ </Lcard>
|
|
|
+ <Lcard style="width: calc(50% - 5px);">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <Title left-line title="新增用户留存率">
|
|
|
+ <el-popover class="box-item" placement="right" trigger="hover" width="250">
|
|
|
+ <template #reference>
|
|
|
+ <el-icon class="ml-1" style="color: #a4b8cf">
|
|
|
+ <InfoFilled />
|
|
|
+ </el-icon>
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <div class="ant-popover-inner-content">
|
|
|
+ 展示了渠道近期的留存率情况,可以帮助您了解该渠道用户的忠诚度<br>
|
|
|
+ 次日留存率: 某日的新增用户在次日启动过应用的比例<br>
|
|
|
+ 7日留存率: 某日的新增用户在7天后启动过应用的比例<br>
|
|
|
+ 14日留存率: 某日的新增用户在14天后启动过应用的比例<br>
|
|
|
+ 这里展示的是渠道前天在昨天的次日留存率、8天前在昨天的7日留存率和15天前在昨天的14日留存率
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-popover>
|
|
|
+ </Title>
|
|
|
+ <div style="font-size: 15px; color: #646464;">
|
|
|
+ 2025/08/19
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <Lprogress style="margin-top: 45px;" v-for="i in progressArray" :key="i.label" :label="i.label" :num="i.num"
|
|
|
+ :count="i.count"></Lprogress>
|
|
|
+ </Lcard>
|
|
|
+ </div>
|
|
|
+ <Lcard>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <Title left-line title="渠道新增留存明细">
|
|
|
+ </Title>
|
|
|
+ <div class="box1">
|
|
|
+ <div class="box1-time">
|
|
|
+ <el-radio-group v-model="timeGranularity" style="width: 240px; margin-right: 30px;">
|
|
|
+ <el-radio-button label="week">过去七天</el-radio-button>
|
|
|
+ <el-radio-button label="month">过去三十天</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <el-radio-group v-model="timeGranularity" style="width: 240px; margin-right: 30px;">
|
|
|
+ <el-radio-button label="week">留存率</el-radio-button>
|
|
|
+ <el-radio-button label="month">留存数</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="link">导出数据
|
|
|
+ <span style="display: inline-block; transform:translateY(3px);">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <g clip-path="url(#clip0_713_1665)">
|
|
|
+ <path
|
|
|
+ d="M14 1.33301H1.99992C1.63172 1.33301 1.33324 1.63149 1.33325 1.99969L1.33358 13.9997C1.33359 14.3679 1.63207 14.6663 2.00025 14.6663H14C14.3682 14.6663 14.6667 14.3679 14.6667 13.9997V1.99967C14.6667 1.63148 14.3682 1.33301 14 1.33301Z"
|
|
|
+ stroke="#167AF0" />
|
|
|
+ <path d="M6.66968 11.3366H11.1669V6.66992" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M9.66675 8.16699L10.1667 7.66699L11.1667 6.66699L12.1667 7.66699L12.6667 8.16699"
|
|
|
+ stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M4.66675 1.33301V14.6663" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 4.67959L14.6666 4.66699" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M2.66675 1.33301H9.33341" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M2.66675 14.667H9.33341" stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M14.6667 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ </g>
|
|
|
+ <defs>
|
|
|
+ <clipPath id="clip0_713_1665">
|
|
|
+ <rect width="16" height="16" fill="white" />
|
|
|
+ </clipPath>
|
|
|
+ </defs>
|
|
|
+ </svg>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="line" style="margin: 35px -30px 40px;"></div>
|
|
|
+ <div class="relative">
|
|
|
+ <el-table :data="currentTableData" border>
|
|
|
+ <el-table-column v-for="column in currentTableColumns" :key="column.dataIndex" :prop="column.dataIndex"
|
|
|
+ :label="column.title" :align="column.align" :min-width="column.width">
|
|
|
+ <template #default="scope">
|
|
|
+ <span :class="getCellStyle(column.dataIndex, scope.row[column.dataIndex])">
|
|
|
+ {{ scope.row[column.dataIndex] }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-end mt-2">
|
|
|
+ <el-pagination v-model:current-page="currentPage1" v-model:page-size="pageSize1" background
|
|
|
+ layout="total, prev, pager, next, sizes" :total="tableRows1.length" :page-sizes="[5, 10, 20]" />
|
|
|
+ </div>
|
|
|
+ </Lcard>
|
|
|
+ <Lcard>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <Title left-line title="渠道新增细分">
|
|
|
+ </Title>
|
|
|
+ <div class="box1">
|
|
|
+ <div class="box1-time">
|
|
|
+ <el-radio-group v-model="timeGroup" style="width: 240px; margin-right: 30px;">
|
|
|
+ <el-radio-button label="week">过去七天</el-radio-button>
|
|
|
+ <el-radio-button label="month">过去三十天</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="link">导出数据
|
|
|
+ <span style="display: inline-block; transform:translateY(3px);">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <g clip-path="url(#clip0_713_1665)">
|
|
|
+ <path
|
|
|
+ d="M14 1.33301H1.99992C1.63172 1.33301 1.33324 1.63149 1.33325 1.99969L1.33358 13.9997C1.33359 14.3679 1.63207 14.6663 2.00025 14.6663H14C14.3682 14.6663 14.6667 14.3679 14.6667 13.9997V1.99967C14.6667 1.63148 14.3682 1.33301 14 1.33301Z"
|
|
|
+ stroke="#167AF0" />
|
|
|
+ <path d="M6.66968 11.3366H11.1669V6.66992" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M9.66675 8.16699L10.1667 7.66699L11.1667 6.66699L12.1667 7.66699L12.6667 8.16699"
|
|
|
+ stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M4.66675 1.33301V14.6663" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 4.67959L14.6666 4.66699" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M2.66675 1.33301H9.33341" stroke="#167AF0" stroke-linecap="round"
|
|
|
+ stroke-linejoin="round" />
|
|
|
+ <path d="M2.66675 14.667H9.33341" stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
+ <path d="M14.6667 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ <path d="M1.33325 2.66699V6.00033" stroke="#167AF0" stroke-linecap="round" />
|
|
|
+ </g>
|
|
|
+ <defs>
|
|
|
+ <clipPath id="clip0_713_1665">
|
|
|
+ <rect width="16" height="16" fill="white" />
|
|
|
+ </clipPath>
|
|
|
+ </defs>
|
|
|
+ </svg>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="line" style="margin: 35px -30px 40px;"></div>
|
|
|
+ <div class="flex items-center justify-between mb-2 mt-3" style="margin-top: 20px;margin-left: 60px;">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-radio-group v-model="lineChartUser">
|
|
|
+ <el-radio-button label="1">设备</el-radio-button>
|
|
|
+ <el-radio-button label="2">国家/地区</el-radio-button>
|
|
|
+ <el-radio-button label="3">省市</el-radio-button>
|
|
|
+ <el-radio-button label="4">版本</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="relative">
|
|
|
+ <div ref="horizontalBarChartRef" style="margin-left: 20px; width: calc(100% - 80px); height: 320px"></div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-3" style="margin-top: 20px;margin-left: 60px;">
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
+ <div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
|
|
|
+ <!-- {{ showDetail1 ? '收起明细数据' : '展开明细数据' }} -->
|
|
|
+ 渠道明细
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <el-button>导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-table v-if="showDetail1" :data="pagedTableRows" border>
|
|
|
+ <el-table-column prop="date" label="日期" min-width="140" />
|
|
|
+ <el-table-column label="新增用户(占比)" min-width="220">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="flex items-center justify-between w-full">
|
|
|
+ <span>{{ scope.row.newUsers }}</span>
|
|
|
+ <span class="text-gray-500 text-xs">{{ scope.row.ratio }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div v-if="showDetail1" class="flex justify-end mt-3">
|
|
|
+ <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" background
|
|
|
+ layout="total, prev, pager, next, sizes" :total="tableRows.length" :page-sizes="[5, 10, 20]" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Lcard>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+// import { ref, onMounted, watch, computed, defineAsyncComponent } from 'vue';
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+// 引入组件
|
|
|
+const Lcard = defineAsyncComponent(() => import('/@/components/LYcom/Lcard/index.vue'));
|
|
|
+const Title = defineAsyncComponent(() => import('/@/components/Title/index.vue'));
|
|
|
+const Lprogress = defineAsyncComponent(() => import('/@/components/LYcom/Lprogress/index.vue'));
|
|
|
+const { t } = useI18n();
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ channel: {
|
|
|
+ type: String,
|
|
|
+ default: '1',
|
|
|
+ },
|
|
|
+});
|
|
|
+const emit = defineEmits(['update:channel'])
|
|
|
+
|
|
|
+// 渠道选择
|
|
|
+const selectChannel = computed({
|
|
|
+ get() {
|
|
|
+ return props.channel;
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ emit('update:channel', val);
|
|
|
+ console.log(val);
|
|
|
+ },
|
|
|
+})
|
|
|
+const timeGroup = ref('1')
|
|
|
+const timeRange: any = ref(null);
|
|
|
+watch(timeGroup, (newVal) => {
|
|
|
+ const now = new Date();
|
|
|
+ let start: Date;
|
|
|
+ let end: Date = new Date(now);
|
|
|
+
|
|
|
+ switch (newVal) {
|
|
|
+ case 'day':
|
|
|
+ // 设置为今天 00:00:00 到 23:59:59
|
|
|
+ start = new Date(now);
|
|
|
+ start.setHours(0, 0, 0, 0);
|
|
|
+ end.setHours(23, 59, 59, 999);
|
|
|
+ break;
|
|
|
+ case 'week':
|
|
|
+ // 设置为本周第一天(周日)到本周最后一天(周六)
|
|
|
+ start = new Date(now);
|
|
|
+ const day = start.getDay();
|
|
|
+ const diff = start.getDate() - day;
|
|
|
+ start.setDate(diff);
|
|
|
+ start.setHours(0, 0, 0, 0);
|
|
|
+ end = new Date(start);
|
|
|
+ end.setDate(start.getDate() + 6);
|
|
|
+ end.setHours(23, 59, 59, 999);
|
|
|
+ break;
|
|
|
+ case 'month':
|
|
|
+ // 设置为本月第一天到本月最后一天
|
|
|
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
+ start.setHours(0, 0, 0, 0);
|
|
|
+ end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
|
|
+ end.setHours(23, 59, 59, 999);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // 其他情况清空时间范围
|
|
|
+ timeRange.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ timeRange.value = [start, end];
|
|
|
+}, { immediate: true });
|
|
|
+
|
|
|
+const selectVersion = ref('1') // 选择版本
|
|
|
+
|
|
|
+const selectLineChannel = ref([]) // 选择线形图渠道
|
|
|
+
|
|
|
+const selectSanDian = ref('1') // 选择散点图纵坐标
|
|
|
+
|
|
|
+const radioTableDay = ref('1') // 选择表格时间
|
|
|
+const tableChannel = ref('1') // 选择表格渠道
|
|
|
+
|
|
|
+let qualityChart: echarts.ECharts | null = null;
|
|
|
+const qChartRef = ref(null);
|
|
|
+const lineChartUser = ref(null);
|
|
|
+onMounted(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ initQualityChart();
|
|
|
+ initChart1();
|
|
|
+ initChart2();
|
|
|
+ initHorizontalBarChart();
|
|
|
+ }, 500)
|
|
|
+});
|
|
|
+const qualityXAxis = ref<string[]>([
|
|
|
+ '2025-07-01',
|
|
|
+ '2025-07-08',
|
|
|
+ '2025-07-15',
|
|
|
+ '2025-07-22',
|
|
|
+ '2025-07-29',
|
|
|
+ '2025-08-05',
|
|
|
+ '2025-08-12',
|
|
|
+ '2025-08-19',
|
|
|
+ '2025-08-26',
|
|
|
+ '2025-09-02',
|
|
|
+ '2025-09-09',
|
|
|
+ '2025-09-16',
|
|
|
+]);
|
|
|
+const retentionSeries = ref<number[]>([20, 23, 27, 24, 22, 15, 5, 4, 16, 26, 25, 2]);
|
|
|
+const industryAvgSeries = ref<number[]>([16, 18, 20, 24, 25, 24, 16, 10, 15, 22, 21, 12]);
|
|
|
+const peerSameScaleSeries = ref<number[]>([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
+function initQualityChart(): void {
|
|
|
+ console.log(qChartRef.value, qChartRef);
|
|
|
+ if (!qChartRef.value) return;
|
|
|
+ if (qualityChart) qualityChart.dispose();
|
|
|
+ qualityChart = echarts.init(qChartRef.value);
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
+ tooltip: { trigger: 'axis', valueFormatter: (v) => `${v}%` },
|
|
|
+ legend: { data: ['留存率', '同行业App', '同行业同规模App'] },
|
|
|
+ grid: { left: 40, right: 20, top: 30, bottom: 30 },
|
|
|
+ xAxis: { type: 'category', data: qualityXAxis.value },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ max: 30,
|
|
|
+ axisLabel: { formatter: '{value}%' },
|
|
|
+ splitLine: { lineStyle: { color: '#f3f4f6' } },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ { name: '留存率', type: 'line', smooth: true, data: retentionSeries.value },
|
|
|
+ { name: '同行业App', type: 'line', smooth: true, data: industryAvgSeries.value, color: '#f59e0b' },
|
|
|
+ { name: '同行业同规模App', type: 'line', smooth: true, data: peerSameScaleSeries.value, color: '#60a5fa' },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ qualityChart.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+interface TableRow {
|
|
|
+ date: string;
|
|
|
+ newUsers: number;
|
|
|
+ ratio: string;
|
|
|
+}
|
|
|
+// 展开/收起明细
|
|
|
+const showDetail1 = ref(true);
|
|
|
+const pagedTableRows = computed(() => {
|
|
|
+ const startIndex = (currentPage.value - 1) * pageSize.value;
|
|
|
+ return tableRows.value.slice(startIndex, startIndex + pageSize.value);
|
|
|
+});
|
|
|
+// 表格相关(静态数据)
|
|
|
+const currentPage = ref(1);
|
|
|
+const pageSize = ref(5);
|
|
|
+const tableRows = ref<TableRow[]>(
|
|
|
+ Array.from({ length: 42 }).map((_, idx) => ({
|
|
|
+ date: `2025-08-${String(11).padStart(2, '0')}`,
|
|
|
+ newUsers: 727,
|
|
|
+ ratio: '97.45%',
|
|
|
+ }))
|
|
|
+);
|
|
|
+
|
|
|
+
|
|
|
+// 饼图进度条相关代码
|
|
|
+const qdqsRef1 = ref(null);
|
|
|
+const qdqsCount1 = ref(85);
|
|
|
+const qdqsRef2 = ref(null);
|
|
|
+const qdqsCount2 = ref(65);
|
|
|
+let chart1: echarts.ECharts | null = null;
|
|
|
+let chart2: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+// 初始化第一个饼图进度条
|
|
|
+function initChart1(): void {
|
|
|
+ if (!qdqsRef1.value) return;
|
|
|
+ if (chart1) chart1.dispose();
|
|
|
+
|
|
|
+ chart1 = echarts.init(qdqsRef1.value);
|
|
|
+
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['60%', '70%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ startAngle: 270,
|
|
|
+ endAngle: 270 - (qdqsCount1.value / 100 * 360),
|
|
|
+ silent: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#167AF0',
|
|
|
+ borderRadius: 10
|
|
|
+ },
|
|
|
+ data: [100],
|
|
|
+ label: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ disabled: true
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['60%', '70%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ startAngle: 360,
|
|
|
+ endAngle: 360,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409eff'
|
|
|
+ },
|
|
|
+ data: [65, 35],
|
|
|
+ label: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ disabled: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ graphic: {
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'percent-text',
|
|
|
+ z: 100,
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: qdqsCount1.value + '%',
|
|
|
+ fontSize: 24,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ fill: '#121212'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'label-text',
|
|
|
+ z: 100,
|
|
|
+ left: 'center',
|
|
|
+ bottom: '3%',
|
|
|
+ style: {
|
|
|
+ text: '昨日活跃/过去7天活跃',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#121212'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ chart1.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化第二个饼图进度条
|
|
|
+function initChart2(): void {
|
|
|
+ if (!qdqsRef2.value) return;
|
|
|
+ if (chart2) chart2.dispose();
|
|
|
+
|
|
|
+ chart2 = echarts.init(qdqsRef2.value);
|
|
|
+
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['60%', '70%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ startAngle: 270,
|
|
|
+ endAngle: 270 - (qdqsCount2.value / 100 * 360),
|
|
|
+ silent: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#00BC71',
|
|
|
+ borderRadius: 10
|
|
|
+ },
|
|
|
+ data: [100],
|
|
|
+ label: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ disabled: true
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['60%', '70%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ startAngle: 360,
|
|
|
+ endAngle: 360,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409eff'
|
|
|
+ },
|
|
|
+ data: [65, 35],
|
|
|
+ label: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ disabled: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ graphic: {
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'percent-text',
|
|
|
+ z: 100,
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: qdqsCount2.value + '%',
|
|
|
+ fontSize: 24,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ fill: '#121212'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'label-text',
|
|
|
+ z: 100,
|
|
|
+ left: 'center',
|
|
|
+ bottom: '3%',
|
|
|
+ style: {
|
|
|
+ text: '昨日活跃/过去30天活跃',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#121212'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ chart2.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 横向进度条相关
|
|
|
+const progressArray = ref([
|
|
|
+ {
|
|
|
+ label: '次日留存率',
|
|
|
+ num: 80,
|
|
|
+ count: 200
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '7日留存率',
|
|
|
+ num: 80,
|
|
|
+ count: 200
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '14日留存率',
|
|
|
+ num: 40,
|
|
|
+ count: 200
|
|
|
+ },
|
|
|
+])
|
|
|
+
|
|
|
+
|
|
|
+const timeGranularity = ref('week')
|
|
|
+
|
|
|
+const weekColumns = ref<any[]>([
|
|
|
+ {
|
|
|
+ title: '时间',
|
|
|
+ width: '140px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'date',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '新增用户',
|
|
|
+ width: '120px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'newUsers',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '1天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day1',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '2天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day2',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '3天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day3',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '4天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day4',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '5天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day5',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '6天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day6',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '7天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day7',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '14天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day14',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '30天后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'day30',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const dayColumns = ref<any[]>([
|
|
|
+ {
|
|
|
+ title: '时间',
|
|
|
+ width: '140px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'date',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '新增用户',
|
|
|
+ width: '120px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'newUsers',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '1周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week1',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '2周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week2',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '3周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week3',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '4周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week4',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '8周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week8',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '12周后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'week12',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const monthColumns = ref<any[]>([
|
|
|
+ {
|
|
|
+ title: '时间',
|
|
|
+ width: '140px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'date',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '新增用户',
|
|
|
+ width: '120px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'newUsers',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '1月后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'month1',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '2月后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'month2',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '3月后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'month3',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '6月后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'month6',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '12月后',
|
|
|
+ width: '100px',
|
|
|
+ align: 'center',
|
|
|
+ dataIndex: 'month12',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 动态表格数据
|
|
|
+const weekTableData = ref<any[]>([
|
|
|
+ {
|
|
|
+ date: '2025-08-01',
|
|
|
+ newUsers: 1000,
|
|
|
+ day1: '45.2%',
|
|
|
+ day2: '32.1%',
|
|
|
+ day3: '28.5%',
|
|
|
+ day4: '25.8%',
|
|
|
+ day5: '23.4%',
|
|
|
+ day6: '21.7%',
|
|
|
+ day7: '20.3%',
|
|
|
+ day14: '15.6%',
|
|
|
+ day30: '12.8%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ day4: '26.5%',
|
|
|
+ day5: '24.2%',
|
|
|
+ day6: '22.8%',
|
|
|
+ day7: '21.5%',
|
|
|
+ day14: '16.3%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ day4: '26.5%',
|
|
|
+ day5: '24.2%',
|
|
|
+ day6: '22.8%',
|
|
|
+ day7: '21.5%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ day4: '26.5%',
|
|
|
+ day5: '24.2%',
|
|
|
+ day6: '22.8%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ day4: '26.5%',
|
|
|
+ day5: '24.2%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ day4: '26.5%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ day3: '29.1%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-02',
|
|
|
+ newUsers: 950,
|
|
|
+ day1: '46.8%',
|
|
|
+ day2: '33.2%',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const dayTableData = ref<any[]>([
|
|
|
+ {
|
|
|
+ date: '2025-08-01',
|
|
|
+ newUsers: 7000,
|
|
|
+ week1: '35.2%',
|
|
|
+ week2: '28.1%',
|
|
|
+ week3: '24.5%',
|
|
|
+ week4: '22.8%',
|
|
|
+ week8: '18.4%',
|
|
|
+ week12: '15.7%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-08-08',
|
|
|
+ newUsers: 6800,
|
|
|
+ week1: '36.8%',
|
|
|
+ week2: '29.2%',
|
|
|
+ week3: '25.1%',
|
|
|
+ week4: '23.5%',
|
|
|
+ week8: '19.2%',
|
|
|
+ week12: '16.3%',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const monthTableData = ref<any[]>([
|
|
|
+ {
|
|
|
+ date: '2025-08-01',
|
|
|
+ newUsers: 30000,
|
|
|
+ month1: '25.2%',
|
|
|
+ month2: '20.1%',
|
|
|
+ month3: '18.5%',
|
|
|
+ month6: '15.8%',
|
|
|
+ month12: '12.4%',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: '2025-09-01',
|
|
|
+ newUsers: 32000,
|
|
|
+ month1: '26.8%',
|
|
|
+ month2: '21.2%',
|
|
|
+ month3: '19.1%',
|
|
|
+ month6: '16.5%',
|
|
|
+ month12: '13.2%',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 当前选中的表格数据
|
|
|
+const currentTableData = computed(() => {
|
|
|
+ switch (timeGranularity.value) {
|
|
|
+ case 'day':
|
|
|
+ return dayTableData.value;
|
|
|
+ case 'week':
|
|
|
+ return weekTableData.value;
|
|
|
+ case 'month':
|
|
|
+ return monthTableData.value;
|
|
|
+ default:
|
|
|
+ return dayTableData.value;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 当前选中的表格列
|
|
|
+const currentTableColumns = computed(() => {
|
|
|
+ switch (timeGranularity.value) {
|
|
|
+ case 'day':
|
|
|
+ return dayColumns.value;
|
|
|
+ case 'week':
|
|
|
+ return weekColumns.value;
|
|
|
+ case 'month':
|
|
|
+ return monthColumns.value;
|
|
|
+ default:
|
|
|
+ return dayColumns.value;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const formData = ref<Record<string, any>>({});
|
|
|
+const query = () => {
|
|
|
+ console.log(formData.value);
|
|
|
+};
|
|
|
+
|
|
|
+// 表格相关(静态数据)
|
|
|
+const tableRows1 = ref<TableRow[]>(
|
|
|
+ Array.from({ length: 42 }).map((_, idx) => ({
|
|
|
+ date: `2025-08-${String(11).padStart(2, '0')}`,
|
|
|
+ newUsers: 727,
|
|
|
+ hyyh: '115',
|
|
|
+ ratio: '97.45%',
|
|
|
+ }))
|
|
|
+);
|
|
|
+
|
|
|
+const currentPage1 = ref(1);
|
|
|
+const pageSize1 = ref(5);
|
|
|
+
|
|
|
+function getCellStyle(dataIndex: string, rowText: any) {
|
|
|
+ if (dataIndex === 'date' || dataIndex === 'newUsers' || !rowText) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ return 'flex justify-center items-center absolute left-0 right-0 top-0 bottom-0 bg-[#e6f7ff]';
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 渠道新增横向柱状图
|
|
|
+// 横向柱状图相关
|
|
|
+const horizontalBarChartRef = ref(null);
|
|
|
+let horizontalBarChart: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+// 初始化横向柱状图
|
|
|
+function initHorizontalBarChart(): void {
|
|
|
+ if (!horizontalBarChartRef.value) return;
|
|
|
+ if (horizontalBarChart) horizontalBarChart.dispose();
|
|
|
+
|
|
|
+ horizontalBarChart = echarts.init(horizontalBarChartRef.value);
|
|
|
+
|
|
|
+ // 数据
|
|
|
+ const channels = ['App Store', '应用宝', '华为应用市场', '小米应用商店', 'OPPO软件商店', 'vivo应用商店'];
|
|
|
+ const newUserCounts = [12500, 9800, 7600, 6800, 5400, 4200];
|
|
|
+ const retentionRates = [45.2, 38.7, 32.5, 41.3, 36.8, 34.2];
|
|
|
+
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['新增用户数', '留存率(%)'],
|
|
|
+ top: '2%'
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '新增用户数',
|
|
|
+ position: 'top',
|
|
|
+ nameLocation: 'middle',
|
|
|
+ nameGap: 30
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '留存率(%)',
|
|
|
+ min: 0,
|
|
|
+ max: 50,
|
|
|
+ position: 'bottom',
|
|
|
+ nameLocation: 'middle',
|
|
|
+ nameGap: 30,
|
|
|
+ axisLabel: {
|
|
|
+ formatter: '{value} %'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ yAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: channels,
|
|
|
+ axisTick: {
|
|
|
+ alignWithLabel: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '新增用户数',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: '20%',
|
|
|
+ data: newUserCounts,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409eff'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '留存率(%)',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: '20%',
|
|
|
+ xAxisIndex: 1,
|
|
|
+ data: retentionRates,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#67c23a'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ horizontalBarChart.setOption(option);
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+.link {
|
|
|
+ color: #167af0;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.box1 {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.line {
|
|
|
+ margin: 60px -30px 30px;
|
|
|
+ height: 1px;
|
|
|
+ background-color: #E6E6E6;
|
|
|
+}
|
|
|
+</style>
|