|
@@ -0,0 +1,352 @@
|
|
|
+<template>
|
|
|
+ <div class="layout-padding device">
|
|
|
+ <el-row :gutter="12" style="padding:0 12px 0; row-gap: 12px;">
|
|
|
+ <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
+ <SelectForm :tabList="tabList" :title="'地域'" :form="form" />
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="12" style="padding: 12px 12px 12px; row-gap: 12px;">
|
|
|
+ <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
+ <el-card shadow="none">
|
|
|
+ <div class="device-container">
|
|
|
+ <div class="title">TOP10省市
|
|
|
+ <el-tooltip effect="light" content="" placement="right-start">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <path
|
|
|
+ d="M7 14C8.93298 14 10.683 13.2165 11.9497 11.9497C13.2165 10.683 14 8.93298 14 7C14 5.06702 13.2165 3.31702 11.9497 2.05025C10.683 0.783503 8.93298 0 7 0C5.06702 0 3.31702 0.783503 2.05025 2.05025C0.783503 3.31702 0 5.06702 0 7C0 8.93298 0.783503 10.683 2.05025 11.9497C3.31702 13.2165 5.06702 14 7 14Z"
|
|
|
+ fill="#1B4D88" fill-opacity="0.4" />
|
|
|
+ <path fill-rule="evenodd" clip-rule="evenodd"
|
|
|
+ d="M6.99957 1.94434C7.5365 1.94434 7.97179 2.37962 7.97179 2.91656C7.97179 3.4535 7.5365 3.88878 6.99957 3.88878C6.46263 3.88878 6.02734 3.4535 6.02734 2.91656C6.02734 2.37962 6.46263 1.94434 6.99957 1.94434Z"
|
|
|
+ fill="white" />
|
|
|
+ <path
|
|
|
+ d="M6.99957 1.94434C7.5365 1.94434 7.97179 2.37962 7.97179 2.91656C7.97179 3.4535 7.5365 3.88878 6.99957 3.88878C6.46263 3.88878 6.02734 3.4535 6.02734 2.91656C6.02734 2.37962 6.46263 1.94434 6.99957 1.94434ZM6.99957 2.33322C6.6774 2.33322 6.41623 2.5944 6.41623 2.91656C6.41623 3.23872 6.6774 3.49989 6.99957 3.49989C7.32173 3.49989 7.5829 3.23872 7.5829 2.91656C7.5829 2.5944 7.32173 2.33322 6.99957 2.33322Z"
|
|
|
+ fill="white" />
|
|
|
+ <path d="M7.19477 10.8888V5.44434H6.80588H6.41699" fill="white" />
|
|
|
+ <path
|
|
|
+ d="M6.41645 10.8892V6.22255C5.98689 6.22255 5.63867 5.87432 5.63867 5.44477C5.63867 5.01522 5.98689 4.66699 6.41645 4.66699H7.19423L7.27398 4.67079C7.66608 4.71071 7.97201 5.04213 7.97201 5.44477V10.8892C7.97201 11.3188 7.70354 11.3452 7.27398 11.3452C6.84443 11.3452 6.41645 11.3188 6.41645 10.8892Z"
|
|
|
+ fill="white" />
|
|
|
+ <path
|
|
|
+ d="M8.55566 10C8.98522 10 9.33344 10.3482 9.33344 10.7778C9.33344 11.2073 8.98522 11.5556 8.55566 11.5556H5.83344C5.40389 11.5556 5.05566 11.2073 5.05566 10.7778C5.05566 10.3482 5.40389 10 5.83344 10H8.55566Z"
|
|
|
+ fill="white" />
|
|
|
+ </svg>
|
|
|
+ <template #content>
|
|
|
+ <div style="width: 300px;">
|
|
|
+ 您可以查看在指定时段(1天、7天、30天)内用户省市的分布情况,并可以进行版本、渠道和分群的交叉筛选。
|
|
|
+ <br />
|
|
|
+ 筛选只展示昨日及之前的数据,启动次数指标只支持昨日之前的查询。
|
|
|
+ <br />
|
|
|
+ 如果当日用户在A省启动之后又在B省启动了,分地域查看数据时,此用户在A、B两省都会被算为活跃用户。(按总体查看数据时不受影响)
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ <div class="card-tabs">
|
|
|
+ <div class="card-tab" :class="{ active: echartsTab === 'newUser' }" @click="handleTabClick('newUser')">
|
|
|
+ 新增用户
|
|
|
+ </div>
|
|
|
+ <div class="card-tab" :class="{ active: echartsTab === 'activeUser' }"
|
|
|
+ @click="handleTabClick('activeUser')">活跃用户
|
|
|
+ </div>
|
|
|
+ <div class="card-tab" :class="{ active: echartsTab === 'startCount' }"
|
|
|
+ @click="handleTabClick('startCount')">启动次数
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 横条图 -->
|
|
|
+ <div class="chart-container">
|
|
|
+ <HorizontalBarChart :data="newUserData" :color="'#167af0'" height="270px" :smooth="false"
|
|
|
+ :area-style="true" :title="title" :showLegend="true" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-divider style="margin: 30px 0; background: rgba(230, 230, 230, 1);" />
|
|
|
+
|
|
|
+ <div class="chart-container">
|
|
|
+ <ExportToCSV :size="10" :tableStyle="{ width: '100%', maxWidth: '1476px', margin: '0 auto' }"
|
|
|
+ :hideTable="false" :data="formatData" :columns="columns" :fileName="title + '- 地域'"
|
|
|
+ @province-click="handleProvinceClick" />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 省市详情弹窗 -->
|
|
|
+ <ProvinceDetailDialog :dialogVisible="dialogVisible" :selectedProvince="selectedProvince"
|
|
|
+ :provinceDetail="provinceDetail" @update:dialogVisible="dialogVisible = $event" />
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" name="device" setup>
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+import HorizontalBarChart from '/@/views/count/components/HorizontalBarChart.vue'
|
|
|
+import { formatDate } from '/@/utils/formatTime'
|
|
|
+import ExportToCSV from '/@/views/count/components/ExportToCSV.vue'
|
|
|
+import ProvinceDetailDialog from './dialog.vue'
|
|
|
+import SelectForm from '../components/selectForm.vue'
|
|
|
+
|
|
|
+const tabList = ref([
|
|
|
+ { label: '省份', value: 1 },
|
|
|
+ { label: '国家/地区', value: 2 }
|
|
|
+])
|
|
|
+
|
|
|
+const form = ref({
|
|
|
+ tab: 1,
|
|
|
+ version: 'all',
|
|
|
+ channel: 'all',
|
|
|
+ dateArray: [new Date(new Date().setDate(new Date().getDate() - 7)), new Date()]
|
|
|
+})
|
|
|
+
|
|
|
+const echartsTab = ref('newUser');
|
|
|
+
|
|
|
+// 弹窗相关状态
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const selectedProvince = ref('')
|
|
|
+const provinceDetail = ref<any[]>([])
|
|
|
+
|
|
|
+const handleTabClick = (tab: string) => {
|
|
|
+ echartsTab.value = tab
|
|
|
+}
|
|
|
+
|
|
|
+// 处理省市点击事件
|
|
|
+const handleProvinceClick = (province: string) => {
|
|
|
+ selectedProvince.value = province
|
|
|
+ // 根据省市名称查找对应的数据
|
|
|
+ const provinceData = newUserData.value.find(item => item.name === province)
|
|
|
+ if (provinceData) {
|
|
|
+ provinceDetail.value = [
|
|
|
+ { name: '运城市', value: 22 },
|
|
|
+ { name: '大同市', value: 18 },
|
|
|
+ { name: '太原市', value: 28 },
|
|
|
+ { name: '晋城市', value: 32 },
|
|
|
+ { name: '朔州市', value: 38 },
|
|
|
+ { name: '长治市', value: 42 },
|
|
|
+ { name: '晋中市', value: 34 },
|
|
|
+ { name: '临汾市', value: 42 },
|
|
|
+ { name: '忻州市', value: 34 },
|
|
|
+ { name: '阳泉市', value: 42 },
|
|
|
+ { name: '吕梁市', value: 34 },
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 新增用户数据
|
|
|
+const newUserData = ref([
|
|
|
+ { name: '北京', value: 22 },
|
|
|
+ { name: '上海', value: 18 },
|
|
|
+ { name: '广东', value: 28 },
|
|
|
+ { name: '浙江', value: 32 },
|
|
|
+ { name: '江苏', value: 38 },
|
|
|
+ { name: '山东', value: 42 },
|
|
|
+ { name: '四川', value: 34 },
|
|
|
+])
|
|
|
+
|
|
|
+const title = computed(() => {
|
|
|
+ const startDate = formatDate(new Date(form.value.dateArray[0]), 'YYYY-mm-dd')
|
|
|
+ const endDate = formatDate(new Date(form.value.dateArray[1]), 'YYYY-mm-dd')
|
|
|
+ return echartsTab.value === 'newUser'
|
|
|
+ ? startDate + ' - ' + endDate + ' 新增用户'
|
|
|
+ : echartsTab.value === 'activeUser'
|
|
|
+ ? startDate + ' - ' + endDate + ' 活跃用户'
|
|
|
+ : startDate + ' - ' + endDate + ' 启动次数'
|
|
|
+})
|
|
|
+
|
|
|
+// 定义表格列配置
|
|
|
+const columns = [
|
|
|
+ { prop: 'province', label: '省市' },
|
|
|
+ { prop: 'newUser', label: '新增用户' },
|
|
|
+ { prop: 'newUserPercentage', label: '新增用户占比' },
|
|
|
+ { prop: 'activeUser', label: '活跃用户' },
|
|
|
+ { prop: 'activeUserPercentage', label: '活跃用户占比' },
|
|
|
+ { prop: 'startCount', label: '启动次数' },
|
|
|
+ { prop: 'startCountPercentage', label: '启动次数占比' }
|
|
|
+];
|
|
|
+
|
|
|
+const formatData = computed(() => {
|
|
|
+ console.log(`formatData: `);
|
|
|
+ console.log(newUserData.value);
|
|
|
+ return newUserData.value.map((item: any) => {
|
|
|
+ return {
|
|
|
+ province: item.name,
|
|
|
+ newUser: item.value,
|
|
|
+ newUserPercentage: `${item.value}%`,
|
|
|
+ activeUser: item.value,
|
|
|
+ activeUserPercentage: `${item.value}%`,
|
|
|
+ startCount: item.value,
|
|
|
+ startCountPercentage: `${item.value}%`
|
|
|
+ }
|
|
|
+ })
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+@import '/@/views/count/styles/common.scss';
|
|
|
+
|
|
|
+.device {
|
|
|
+ .device-container {
|
|
|
+ padding: 10px 14px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ line-height: 19px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 16px;
|
|
|
+ padding-left: 12px;
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 40px;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 4px;
|
|
|
+ height: 14px;
|
|
|
+ background: rgba(22, 122, 240, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .export-button {
|
|
|
+ width: 60px;
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-tabs {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1476px;
|
|
|
+ margin: 0 auto 0;
|
|
|
+
|
|
|
+ .card-tab {
|
|
|
+ svg {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ // margin: 20px 85px 0;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1476px;
|
|
|
+ margin: 20px auto 0;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .echarts-name {
|
|
|
+ display: inline-block;
|
|
|
+ margin: 28px auto 0;
|
|
|
+ padding-left: 16px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 20px;
|
|
|
+ color: rgba(18, 18, 18, 1);
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background: rgba(22, 122, 240, 1);
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-container {
|
|
|
+ // padding: 0 85px;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1476px;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ .btn-toggle-table {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 20px;
|
|
|
+ color: rgba(22, 122, 240, 1);
|
|
|
+ margin-bottom: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ transition: transform 0.3s ease-in-out;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.hide-table svg {
|
|
|
+ transform: rotate(-180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-container {
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 省市详情弹窗样式
|
|
|
+ .province-detail {
|
|
|
+ .detail-card {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .detail-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #167af0;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-percentage {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-chart {
|
|
|
+ margin-top: 30px;
|
|
|
+
|
|
|
+ .chart-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-placeholder {
|
|
|
+ height: 200px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border: 1px dashed #ddd;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .placeholder-text {
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .dialog-footer {
|
|
|
+ .el-button {
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+</style>
|