cmy преди 6 дни
родител
ревизия
e5ab54f62b

+ 0 - 177
src/views/churn/behavior/index.vue

@@ -1,177 +0,0 @@
-<template>
-  <div class="behavior">
-    <el-row :gutter="12" style="padding: 12px; row-gap: 12px;">
-      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-        <el-card shadow="none" style="padding: 10px 14px;">
-          <div class="top-info">
-            <div class="title">卸载洞察
-              <el-tooltip class="box-item" 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
-                    d="M4 4.702C4 4.40333 4.02333 4.09533 4.07 3.778C4.126 3.46067 4.21933 3.17133 4.35 2.91C4.49 2.64867 4.67667 2.434 4.91 2.266C5.14333 2.08867 5.45133 2 5.834 2H7.794C8.102 2 8.37267 2.06533 8.606 2.196C8.84867 2.31733 9.04467 2.476 9.194 2.672C9.35267 2.868 9.474 3.092 9.558 3.344C9.65133 3.596 9.70733 3.848 9.726 4.1C9.754 4.352 9.74467 4.59467 9.698 4.828C9.66067 5.06133 9.59533 5.26667 9.502 5.444L7.934 8.314V9.574H6.324V8.146L7.808 5.556C7.892 5.416 7.948 5.234 7.976 5.01C8.01333 4.786 8.01333 4.57133 7.976 4.366C7.948 4.15133 7.878 3.96933 7.766 3.82C7.66333 3.67067 7.514 3.596 7.318 3.596H6.408C6.24933 3.596 6.11867 3.624 6.016 3.68C5.91333 3.72667 5.82933 3.80133 5.764 3.904C5.708 3.99733 5.67067 4.114 5.652 4.254C5.63333 4.38467 5.624 4.534 5.624 4.702H4ZM7.976 12.15H6.324V10.512H7.976V12.15Z"
-                    fill="white" />
-                </svg>
-              </el-tooltip>
-            </div>
-
-            <div class="data-source-status">数据源状态:Demo数据</div>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-    <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
-      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-        <el-card shadow="none">
-          <div class="active-situation">
-            <div class="card-title">卸载设备活跃情况</div>
-            <div class="content">
-
-              <el-row :gutter="12" style="padding: 0; row-gap: 12px;">
-                <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="12">
-                  <div class="el-card" style="flex: 1; overflow: visible; padding: 24px 40px; height: 475px;">
-                    <div class="description" style="margin-bottom: 25px;">
-                      <p>安装至卸载存量时长分布</p>
-                      <p>存量时长=最近卸载日期-最近卸载前的安装日期</p>
-                      <p>用户卸载集中在安装App后:<span>90天以上</span></p>
-                    </div>
-                    <div class="content-item">
-                      <PieChart />
-                    </div>
-                  </div>
-                </el-col>
-                <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="12">
-                  <div class="el-card" style="flex: 1; overflow: visible; padding: 24px 40px; height: 475px;">
-                    <div class="description">
-                      <p>历史卸载次数分布</p>
-                      <p>当前周的卸载设备,按当前是第几次卸载App进行分布</p>
-                      <p>历史上<span>96.5%</span>卸载设备会反复卸载。</p>
-                    </div>
-                    <div class="content-item">
-                      <BarChart title="历史卸载次数" :data="historyUninstallData" />
-                    </div>
-                  </div>
-                </el-col>
-              </el-row>
-            </div>
-
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-    <el-row :gutter="12" style="padding: 0 12px 12px;">
-      <div class="tabs">
-        <div class="tab" :class="{ active: activeTab === 'before' }" @click="activeTab = 'before'">卸载前状态</div>
-        <div class="tab" :class="{ active: activeTab === 'after' }" @click="activeTab = 'after'">卸载后流向</div>
-        <div class="tab" :class="{ active: activeTab === 'system' }" @click="activeTab = 'system'">设备系统分布</div>
-      </div>
-      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-        <el-card shadow="none">
-          <BeforeUninstallStatus v-show="activeTab === 'before'" />
-          <AfterUninstallStatus v-show="activeTab === 'after'" />
-          <SystemDistribution v-show="activeTab === 'system'" />
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script lang="ts" name="churnBehavior" setup>
-import PieChart from './echarts/PieChart.vue'
-import BarChart from './echarts/BarChart.vue'
-import BeforeUninstallStatus from './components/BeforeUninstallStatus.vue'
-import AfterUninstallStatus from './components/AfterUninstallStatus.vue'
-import SystemDistribution from './components/SystemDistribution.vue'
-
-const historyUninstallData = [
-  { name: '1次', value: 45, percentage: '45.0%' },
-  { name: '2次', value: 23, percentage: '23.0%' },
-  { name: '3次', value: 15, percentage: '15.0%' },
-  { name: '4次', value: 8, percentage: '8.0%' },
-  { name: '5次', value: 100, percentage: '6.0%' },
-  { name: '6次以上', value: 3, percentage: '3.0%' }
-]
-
-const activeTab = ref('system');
-
-const formatter = (value: number) => {
-  return `${value}%`
-}
-
-</script>
-<style scoped lang="scss">
-@import './styles/common.scss';
-
-svg {
-  vertical-align: middle;
-  margin: 0 0 0 12px;
-}
-
-
-
-.behavior {
-  font-family: Source Han Sans SC;
-
-  .top-info {
-    color: rgba(18, 18, 18, 1);
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 0;
-    margin: -2.5px 0;
-
-    .title {
-      font-size: 16px;
-      font-weight: 500;
-      line-height: 20px;
-      padding: 4px 0;
-    }
-
-    .data-source-status {
-      font-weight: 400;
-      font-size: 14px;
-      line-height: 20px;
-      color: rgba(18, 18, 18, 1);
-      padding: 4px 0;
-    }
-  }
-
-  .active-situation {
-    padding: 10px 14px;
-
-    .content {}
-  }
-
-  .tabs {
-    display: flex;
-    padding: 0 6px;
-
-    .tab {
-      cursor: pointer;
-      height: 42px;
-      line-height: 42px;
-      color: rgba(100, 100, 100, 1);
-      font-weight: 500;
-      font-style: Medium;
-      font-size: 14px;
-      padding: 0 25px;
-      border-left: 1px solid rgba(221, 228, 237, 1);
-      border-top: 1px solid rgba(221, 228, 237, 1);
-      background-color: #ffffff;
-
-      &:last-child {
-        border-right: 1px solid rgba(221, 228, 237, 1);
-      }
-    }
-
-    .active {
-      color: rgba(22, 122, 240, 1);
-      background-color: rgba(232, 242, 254, 1);
-    }
-  }
-
-}
-</style>

+ 0 - 440
src/views/churn/overview/index.vue

@@ -1,440 +0,0 @@
-<template>
-  <div class="overview">
-    <el-row :gutter="12" style="padding: 12px; row-gap: 12px;">
-      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-        <el-card shadow="none" style="padding: 10px 14px;">
-          <div class="top-info">
-            <div class="title">流失概况</div>
-            <div class="aside">
-              <div class="data-source-status">数据源状态: Demo数据</div>
-              <el-button class="goto-smart-operation" type="primary" link>前往智能运营发短信
-                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-                  <path d="M10.6667 4.66699L13.3334 7.33366L10.6667 10.0003" stroke="#167AF0" stroke-width="1.5"
-                    stroke-linecap="round" stroke-linejoin="round" />
-                  <path d="M2.66671 12.6663V8.33301C2.66671 7.78071 3.11441 7.33301 3.66671 7.33301H13.3334"
-                    stroke="#167AF0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
-                </svg>
-              </el-button>
-            </div>
-          </div>
-          <div class="top-content">
-            <div class="el-card" style="flex: 1; overflow: visible;">
-              <div class="content-item">
-                <div class="content-item-left">
-                  <ProgressRing 
-                    :progress="0.7223"
-                    :size="111"
-                    background-color="rgba(239, 239, 239, 1)"
-                    progress-color="rgba(0, 188, 113, 1)"
-                    :stroke-width="15"
-                    :soft-corner="true"
-                  />
-                </div>
-                <div class="content-item-right">
-                  <div class="content-item-title">当周卸载流失设备数</div>
-                  <div class="content-item-value">38</div>
-                  <div class="content-item-percent">环比<span>-74.23%</span>
-                    <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-                      <mask id="mask0_611_555" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="14"
-                        height="14">
-                        <rect width="14" height="14" fill="#D9D9D9" />
-                      </mask>
-                      <g mask="url(#mask0_611_555)">
-                        <path
-                          d="M11.1702 4.43275C11.5204 4.43302 11.8046 4.71698 11.8047 5.06725L11.8041 10.7783C11.8041 11.1287 11.5201 11.4128 11.1696 11.4128L5.45857 11.4134C5.10831 11.4133 4.82435 11.1291 4.82408 10.7789C4.82408 10.4284 5.1087 10.1438 5.45916 10.1438L9.6377 10.1438L2.69566 3.20174C2.44785 2.95393 2.44785 2.55215 2.69566 2.30434C2.94348 2.05652 3.34526 2.05652 3.59307 2.30434L10.5351 9.24637L10.5351 5.06783C10.5351 4.71737 10.8197 4.43275 11.1702 4.43275Z"
-                          fill="#00BC71" />
-                      </g>
-                    </svg>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div class="el-card" style="flex: 1; overflow: visible;">
-              <div class="content-item">
-                <div class="content-item-left">
-                  <ProgressRing 
-                    :progress="0.38"
-                    :size="111"
-                    background-color="rgba(239, 239, 239, 1)"
-                    progress-color="rgba(230, 66, 66, 1)"
-                    :stroke-width="15"
-                    :soft-corner="true"
-                  />
-                </div>
-                <div class="content-item-right">
-                  <div class="content-item-title">当周卸载召回设备数</div>
-                  <div class="content-item-value">38</div>
-                  <div class="content-item-percent">
-                    环比<span>+74.23%</span>
-                    <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-                      <mask id="mask0_611_558" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="14"
-                        height="14">
-                        <rect width="14" height="14" transform="matrix(1 0 0 -1 0 14)" fill="#D9D9D9" />
-                      </mask>
-                      <g mask="url(#mask0_611_558)">
-                        <path
-                          d="M11.1702 9.56725C11.5204 9.56698 11.8046 9.28302 11.8047 8.93275L11.8041 3.22173C11.8041 2.87127 11.5201 2.58723 11.1696 2.58723L5.45857 2.58665C5.10831 2.58668 4.82435 2.87093 4.82408 3.22114C4.82408 3.5716 5.1087 3.85622 5.45916 3.85622L9.6377 3.85622L2.69566 10.7983C2.44785 11.0461 2.44785 11.4479 2.69566 11.6957C2.94348 11.9435 3.34526 11.9435 3.59307 11.6957L10.5351 4.75363L10.5351 8.93217C10.5351 9.28263 10.8197 9.56725 11.1702 9.56725Z"
-                          fill="#E64242" />
-                      </g>
-                    </svg>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-    <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
-      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-        <el-card shadow="none">
-          <div class="trend-container">
-            <div class="title">流失趋势</div>
-            <div class="tabs">
-              <div class="tabs-item" :class="{ active: activeTab === 'churnTrend' }" @click="handleTabClick('churnTrend')">卸载流失设备</div>
-              <div class="tabs-item" :class="{ active: activeTab === 'recallTrend' }" @click="handleTabClick('recallTrend')">卸载召回设备</div>
-            </div>
-            <!-- 折线图 -->
-            <div class="chart-container">
-              <LineChart :data="currentChartData" :color="'#167af0'" :title="currentChartTitle" height="270px"
-                :smooth="false" :area-style="true" />
-              <div class="echarts-name">{{currentChartTitle}}</div>
-            </div>
-            <el-divider style="margin: 30px 0; background: rgba(230, 230, 230, 1);"/>
-            <div class="table-container">
-              <div class="btn-toggle-table" :class="{ 'hide-table': hideTable }" @click="hideTable = !hideTable">
-                {{ hideTable ? '展开' : '收起' }}明细数据<svg 
-                  width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-                  <path d="M10.5 8.75L7 5.25L3.5 8.75" stroke="#167AF0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-                </svg>
-              </div>
-
-              <el-table class="statistics-table" :data="state.dataList" row-key="date" style="width: 100%"
-                border :cell-style="tableStyle.cellStyle"
-                :header-cell-style="tableStyle.headerCellStyle"
-                v-if="!hideTable"
-                >
-                <el-table-column :label="'日期'" prop="date" show-overflow-tooltip></el-table-column>
-                <el-table-column :label="'卸载流失设备'" :formatter="statusFormatter" prop="churn" show-overflow-tooltip></el-table-column>
-                <el-table-column :label="'卸载召回设备'" :formatter="statusFormatter" prop="recall" show-overflow-tooltip></el-table-column>
-              </el-table>
-
-              <pagination 
-                v-if="!hideTable"
-                @current-change="currentChangeHandle" 
-                @size-change="sizeChangeHandle" 
-                v-bind="state.pagination">
-              </pagination>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script lang="ts" name="churnOverview" setup>
-import LineChart from './echarts/LineChart.vue'
-import ProgressRing from '../echarts/ProgressRing.vue'
-import { BasicTableProps, useTable } from '/@/hooks/table';
-import { ref, computed, reactive } from 'vue'
-
-// 流失数据
-const churnData = ref([
-  { name: '流失设备', value: 38, itemStyle: { color: 'rgba(0, 188, 113, 1)' } },
-  { name: '其他', value: 62, itemStyle: { color: 'rgba(239, 239, 239, 1)' } }
-])
-
-// 召回数据
-const recallData = ref([
-  { name: '召回设备', value: 38, itemStyle: { color: 'rgba(230, 66, 66, 1)' } },
-  { name: '其他', value: 62, itemStyle: { color: 'rgba(239, 239, 239, 1)' } }
-])
-
-// 流失趋势数据
-const churnTrendData = ref([
-  { date: '2025-01-01', value: 25 },
-  { date: '2025-01-02', value: 32 },
-  { date: '2025-01-03', value: 28 },
-  { date: '2025-01-04', value: 45 },
-  { date: '2025-01-05', value: 38 },
-  { date: '2025-01-06', value: 42 },
-  { date: '2025-01-07', value: 35 },
-  { date: '2025-01-08', value: 48 },
-  { date: '2025-01-09', value: 52 },
-  { date: '2025-01-10', value: 39 },
-  { date: '2025-01-11', value: 44 },
-  { date: '2025-01-12', value: 37 },
-  { date: '2025-01-13', value: 41 },
-  { date: '2025-01-14', value: 46 }
-])
-
-// 召回趋势数据
-const recallTrendData = ref([
-  { date: '2025-01-01', value: 15 },
-  { date: '2025-01-02', value: 22 },
-  { date: '2025-01-03', value: 18 },
-  { date: '2025-01-04', value: 35 },
-  { date: '2025-01-05', value: 28 },
-  { date: '2025-01-06', value: 32 },
-  { date: '2025-01-07', value: 25 },
-  { date: '2025-01-08', value: 38 },
-  { date: '2025-01-09', value: 42 },
-  { date: '2025-01-10', value: 29 },
-  { date: '2025-01-11', value: 34 },
-  { date: '2025-01-12', value: 27 },
-  { date: '2025-01-13', value: 31 },
-  { date: '2025-01-14', value: 36 }
-])
-
-const activeTab = ref('churnTrend')
-
-// 计算当前图表数据
-const currentChartData = computed(() => {
-  return activeTab.value === 'churnTrend' ? churnTrendData.value : recallTrendData.value
-})
-
-// 计算当前图表标题
-const currentChartTitle = computed(() => {
-  return activeTab.value === 'churnTrend' ? '卸载流失设备' : '卸载召回设备'
-})
-
-const handleTabClick = (tab: string) => {
-  activeTab.value = tab
-} 
-
-// 表格数据
-const hideTable = ref(false);
-const state: BasicTableProps = reactive<BasicTableProps>({
-  queryForm: {
-    ip: '',
-  },
-  pageList: () => Promise.resolve([]),
-  pagination: {
-    current: 1,
-    size: 10,
-    total: 0,
-    pageSizes: [5, 10, 20, 50, 100]
-  },
-  dataList: [
-    {
-      date: '2025-01-01',
-      churn: 10,
-      recall: 20
-    },
-    {
-      date: '2025-01-02',
-      churn: 15,
-      recall: 25
-    },
-    {
-      date: '2025-01-03',
-      churn: 20,
-      recall: 30
-    },
-    {
-      date: '2025-01-04',
-      churn: 25,
-      recall: 35
-    }
-  ]
-});
-const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
-const statusFormatter = (row: any, column: any, cellValue: any, index: any) => {
-  return cellValue || '--';
-}
-
-</script>
-<style scoped lang="scss">
-svg {
-  vertical-align: middle;
-  margin: 0 0 0 12px;
-}
-.overview {
-  font-family: Source Han Sans SC;
-  color: rgba(18, 18, 18, 1);
-}
-
-.top-info {
-  color: rgba(18, 18, 18, 1);
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 25px;
-
-
-  .title {
-    font-size: 16px;
-    font-weight: 500;
-    line-height: 20px;
-    padding: 4px 0;
-  }
-
-  .aside {
-    display: flex;
-  }
-
-  .data-source-status {
-    font-weight: 400;
-    font-size: 14px;
-    line-height: 20px;
-    color: rgba(18, 18, 18, 1);
-    padding: 4px 16px 4px 0;
-  }
-
-  .goto-smart-operation {
-    padding: 4px 12px;
-    border-radius: 4px;
-    border: 1px solid rgba(22, 122, 240, 1);
-    font-weight: 400;
-    font-size: 14px;
-    line-height: 20px;
-    color: rgba(22, 122, 240, 1);
-  }
-
-}
-
-.top-content {
-  display: flex;
-  gap: 20px;
-
-  .content-item {
-    display: flex;
-    align-items: center;
-    gap: 0;
-    flex: 1;
-    padding: 27.5px;
-    justify-content: center;
-  }
-
-  .content-item-left {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    margin-right: 30px;
-  }
-
-  .content-item-right {
-    // flex: 1;
-  }
-
-  .content-item-title {
-    color: rgba(18, 18, 18, 1);
-    font-weight: 400;
-    font-size: 15px;
-    line-height: 20px;
-    margin-bottom: 12px;
-  }
-
-  .content-item-value {
-    font-weight: 500;
-    font-style: Medium;
-    font-size: 28px;
-    margin-bottom: 16px;
-    line-height: 41px;
-  }
-
-  .content-item-percent {
-    font-weight: 400;
-    font-size: 14px;
-    vertical-align: middle;
-    line-height: 20px;
-  }
-
-  .content-item-percent span {
-    color: rgba(0, 188, 113, 1);
-  }
-}
-
-.trend-container {
-  padding: 10px 14px;
-  .title {
-    line-height: 19px;
-    font-weight: 500;
-    font-size: 16px;
-    padding-left: 12px;
-    position: relative;
-    margin-bottom: 53px;
-    &::before {
-      content: '';
-      position: absolute;
-      left: 0;
-      top: 50%;
-      transform: translateY(-50%);
-      width: 4px;
-      height: 14px;
-      background: rgba(22, 122, 240, 1);
-    }
-  }
-
-  .tabs {
-    display: flex;
-    margin-left: 85px;
-    .tabs-item {
-      padding: 0 12px;
-      height: 32px;
-      line-height: 30px;
-      border: 1px solid rgba(22, 122, 240, 1);
-      text-align: center;
-      color: rgba(22, 122, 240, 1);
-      cursor: pointer;
-      &.active {
-        background: rgba(22, 122, 240, 1);
-        color: #ffffff;
-      }
-    }
-  }
-
-  .chart-container {
-    margin: 20px 85px 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;
-    
-    .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;
-    }
-
-  }
-
-}
-</style>

+ 0 - 0
src/views/churn/behavior/components/AfterUninstallStatus.vue → src/views/count/churn/behavior/components/AfterUninstallStatus.vue


+ 0 - 0
src/views/churn/behavior/components/BeforeUninstallStatus.vue → src/views/count/churn/behavior/components/BeforeUninstallStatus.vue


+ 0 - 0
src/views/churn/behavior/components/SystemDistribution.vue → src/views/count/churn/behavior/components/SystemDistribution.vue


+ 0 - 0
src/views/churn/behavior/components/SystemItem.vue → src/views/count/churn/behavior/components/SystemItem.vue


+ 0 - 0
src/views/churn/behavior/echarts/BarChart.vue → src/views/count/churn/behavior/echarts/BarChart.vue


+ 0 - 0
src/views/churn/behavior/echarts/HorizontalBarChart.vue → src/views/count/churn/behavior/echarts/HorizontalBarChart.vue


+ 0 - 0
src/views/churn/behavior/echarts/MindChart.vue → src/views/count/churn/behavior/echarts/MindChart.vue


+ 0 - 0
src/views/churn/behavior/echarts/PieChart.vue → src/views/count/churn/behavior/echarts/PieChart.vue


+ 175 - 0
src/views/count/churn/behavior/index.vue

@@ -0,0 +1,175 @@
+<template>
+   <div class="layout-padding">
+    <div class="behavior">
+      <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
+        <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+          <el-card shadow="none" style="padding: 10px 14px;">
+            <div class="top-info">
+              <div class="title">卸载洞察
+                <el-tooltip class="box-item" 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
+                      d="M4 4.702C4 4.40333 4.02333 4.09533 4.07 3.778C4.126 3.46067 4.21933 3.17133 4.35 2.91C4.49 2.64867 4.67667 2.434 4.91 2.266C5.14333 2.08867 5.45133 2 5.834 2H7.794C8.102 2 8.37267 2.06533 8.606 2.196C8.84867 2.31733 9.04467 2.476 9.194 2.672C9.35267 2.868 9.474 3.092 9.558 3.344C9.65133 3.596 9.70733 3.848 9.726 4.1C9.754 4.352 9.74467 4.59467 9.698 4.828C9.66067 5.06133 9.59533 5.26667 9.502 5.444L7.934 8.314V9.574H6.324V8.146L7.808 5.556C7.892 5.416 7.948 5.234 7.976 5.01C8.01333 4.786 8.01333 4.57133 7.976 4.366C7.948 4.15133 7.878 3.96933 7.766 3.82C7.66333 3.67067 7.514 3.596 7.318 3.596H6.408C6.24933 3.596 6.11867 3.624 6.016 3.68C5.91333 3.72667 5.82933 3.80133 5.764 3.904C5.708 3.99733 5.67067 4.114 5.652 4.254C5.63333 4.38467 5.624 4.534 5.624 4.702H4ZM7.976 12.15H6.324V10.512H7.976V12.15Z"
+                      fill="white" />
+                  </svg>
+                </el-tooltip>
+              </div>
+
+              <div class="data-source-status">数据源状态:Demo数据</div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
+        <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+          <el-card shadow="none">
+            <div class="active-situation">
+              <div class="card-title">卸载设备活跃情况</div>
+              <div class="content">
+
+                <el-row :gutter="12" style="padding: 0; row-gap: 12px;">
+                  <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="12">
+                    <div class="el-card" style="flex: 1; overflow: visible; padding: 24px 40px; height: 475px;">
+                      <div class="description" style="margin-bottom: 25px;">
+                        <p>安装至卸载存量时长分布</p>
+                        <p>存量时长=最近卸载日期-最近卸载前的安装日期</p>
+                        <p>用户卸载集中在安装App后:<span>90天以上</span></p>
+                      </div>
+                      <div class="content-item">
+                        <PieChart />
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="12">
+                    <div class="el-card" style="flex: 1; overflow: visible; padding: 24px 40px; height: 475px;">
+                      <div class="description">
+                        <p>历史卸载次数分布</p>
+                        <p>当前周的卸载设备,按当前是第几次卸载App进行分布</p>
+                        <p>历史上<span>96.5%</span>卸载设备会反复卸载。</p>
+                      </div>
+                      <div class="content-item">
+                        <BarChart title="历史卸载次数" :data="historyUninstallData" />
+                      </div>
+                    </div>
+                  </el-col>
+                </el-row>
+              </div>
+
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="12" style="padding: 0 12px 12px;">
+        <div class="tabs">
+          <div class="tab" :class="{ active: activeTab === 'before' }" @click="activeTab = 'before'">卸载前状态</div>
+          <div class="tab" :class="{ active: activeTab === 'after' }" @click="activeTab = 'after'">卸载后流向</div>
+          <div class="tab" :class="{ active: activeTab === 'system' }" @click="activeTab = 'system'">设备系统分布</div>
+        </div>
+        <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+          <el-card shadow="none">
+            <BeforeUninstallStatus v-show="activeTab === 'before'" />
+            <AfterUninstallStatus v-show="activeTab === 'after'" />
+            <SystemDistribution v-show="activeTab === 'system'" />
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" name="churnBehavior" setup>
+import PieChart from './echarts/PieChart.vue'
+import BarChart from './echarts/BarChart.vue'
+import BeforeUninstallStatus from './components/BeforeUninstallStatus.vue'
+import AfterUninstallStatus from './components/AfterUninstallStatus.vue'
+import SystemDistribution from './components/SystemDistribution.vue'
+
+const historyUninstallData = [
+  { name: '1次', value: 45, percentage: '45.0%' },
+  { name: '2次', value: 23, percentage: '23.0%' },
+  { name: '3次', value: 15, percentage: '15.0%' },
+  { name: '4次', value: 8, percentage: '8.0%' },
+  { name: '5次', value: 100, percentage: '6.0%' },
+  { name: '6次以上', value: 3, percentage: '3.0%' }
+]
+
+const activeTab = ref('before');
+
+</script>
+<style scoped lang="scss">
+@import './styles/common.scss';
+
+svg {
+  vertical-align: middle;
+  margin: 0 0 0 12px;
+}
+
+
+
+.behavior {
+  font-family: Source Han Sans SC;
+
+  .top-info {
+    color: rgba(18, 18, 18, 1);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0;
+    margin: -2.5px 0;
+
+    .title {
+      font-size: 16px;
+      font-weight: 500;
+      line-height: 20px;
+      padding: 4px 0;
+    }
+
+    .data-source-status {
+      font-weight: 400;
+      font-size: 14px;
+      line-height: 20px;
+      color: rgba(18, 18, 18, 1);
+      padding: 4px 0;
+    }
+  }
+
+  .active-situation {
+    padding: 10px 14px;
+
+    .content {}
+  }
+
+  .tabs {
+    display: flex;
+    padding: 0 6px;
+
+    .tab {
+      cursor: pointer;
+      height: 42px;
+      line-height: 42px;
+      color: rgba(100, 100, 100, 1);
+      font-weight: 500;
+      font-style: Medium;
+      font-size: 14px;
+      padding: 0 25px;
+      border-left: 1px solid rgba(221, 228, 237, 1);
+      border-top: 1px solid rgba(221, 228, 237, 1);
+      background-color: #ffffff;
+
+      &:last-child {
+        border-right: 1px solid rgba(221, 228, 237, 1);
+      }
+    }
+
+    .active {
+      color: rgba(22, 122, 240, 1);
+      background-color: rgba(232, 242, 254, 1);
+    }
+  }
+
+}
+</style>

+ 0 - 0
src/views/churn/behavior/styles/common.scss → src/views/count/churn/behavior/styles/common.scss


+ 0 - 0
src/views/churn/echarts/ProgressRing.vue → src/views/count/churn/echarts/ProgressRing.vue


+ 0 - 0
src/views/churn/overview/echarts/LineChart.vue → src/views/count/churn/overview/echarts/LineChart.vue


+ 442 - 0
src/views/count/churn/overview/index.vue

@@ -0,0 +1,442 @@
+<template>
+   <div class="layout-padding">
+    <div class="overview">
+      <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
+        <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+          <el-card shadow="none" style="padding: 10px 14px;">
+            <div class="top-info">
+              <div class="title">流失概况</div>
+              <div class="aside">
+                <div class="data-source-status">数据源状态: Demo数据</div>
+                <el-button class="goto-smart-operation" type="primary" link>前往智能运营发短信
+                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M10.6667 4.66699L13.3334 7.33366L10.6667 10.0003" stroke="#167AF0" stroke-width="1.5"
+                      stroke-linecap="round" stroke-linejoin="round" />
+                    <path d="M2.66671 12.6663V8.33301C2.66671 7.78071 3.11441 7.33301 3.66671 7.33301H13.3334"
+                      stroke="#167AF0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
+                  </svg>
+                </el-button>
+              </div>
+            </div>
+            <div class="top-content">
+              <div class="el-card" style="flex: 1; overflow: visible;">
+                <div class="content-item">
+                  <div class="content-item-left">
+                    <ProgressRing 
+                      :progress="0.7223"
+                      :size="111"
+                      background-color="rgba(239, 239, 239, 1)"
+                      progress-color="rgba(0, 188, 113, 1)"
+                      :stroke-width="15"
+                      :soft-corner="true"
+                    />
+                  </div>
+                  <div class="content-item-right">
+                    <div class="content-item-title">当周卸载流失设备数</div>
+                    <div class="content-item-value">38</div>
+                    <div class="content-item-percent">环比<span>-74.23%</span>
+                      <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <mask id="mask0_611_555" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="14"
+                          height="14">
+                          <rect width="14" height="14" fill="#D9D9D9" />
+                        </mask>
+                        <g mask="url(#mask0_611_555)">
+                          <path
+                            d="M11.1702 4.43275C11.5204 4.43302 11.8046 4.71698 11.8047 5.06725L11.8041 10.7783C11.8041 11.1287 11.5201 11.4128 11.1696 11.4128L5.45857 11.4134C5.10831 11.4133 4.82435 11.1291 4.82408 10.7789C4.82408 10.4284 5.1087 10.1438 5.45916 10.1438L9.6377 10.1438L2.69566 3.20174C2.44785 2.95393 2.44785 2.55215 2.69566 2.30434C2.94348 2.05652 3.34526 2.05652 3.59307 2.30434L10.5351 9.24637L10.5351 5.06783C10.5351 4.71737 10.8197 4.43275 11.1702 4.43275Z"
+                            fill="#00BC71" />
+                        </g>
+                      </svg>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="el-card" style="flex: 1; overflow: visible;">
+                <div class="content-item">
+                  <div class="content-item-left">
+                    <ProgressRing 
+                      :progress="0.38"
+                      :size="111"
+                      background-color="rgba(239, 239, 239, 1)"
+                      progress-color="rgba(230, 66, 66, 1)"
+                      :stroke-width="15"
+                      :soft-corner="true"
+                    />
+                  </div>
+                  <div class="content-item-right">
+                    <div class="content-item-title">当周卸载召回设备数</div>
+                    <div class="content-item-value">38</div>
+                    <div class="content-item-percent">
+                      环比<span>+74.23%</span>
+                      <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <mask id="mask0_611_558" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="14"
+                          height="14">
+                          <rect width="14" height="14" transform="matrix(1 0 0 -1 0 14)" fill="#D9D9D9" />
+                        </mask>
+                        <g mask="url(#mask0_611_558)">
+                          <path
+                            d="M11.1702 9.56725C11.5204 9.56698 11.8046 9.28302 11.8047 8.93275L11.8041 3.22173C11.8041 2.87127 11.5201 2.58723 11.1696 2.58723L5.45857 2.58665C5.10831 2.58668 4.82435 2.87093 4.82408 3.22114C4.82408 3.5716 5.1087 3.85622 5.45916 3.85622L9.6377 3.85622L2.69566 10.7983C2.44785 11.0461 2.44785 11.4479 2.69566 11.6957C2.94348 11.9435 3.34526 11.9435 3.59307 11.6957L10.5351 4.75363L10.5351 8.93217C10.5351 9.28263 10.8197 9.56725 11.1702 9.56725Z"
+                            fill="#E64242" />
+                        </g>
+                      </svg>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
+        <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+          <el-card shadow="none">
+            <div class="trend-container">
+              <div class="title">流失趋势</div>
+              <div class="tabs">
+                <div class="tabs-item" :class="{ active: activeTab === 'churnTrend' }" @click="handleTabClick('churnTrend')">卸载流失设备</div>
+                <div class="tabs-item" :class="{ active: activeTab === 'recallTrend' }" @click="handleTabClick('recallTrend')">卸载召回设备</div>
+              </div>
+              <!-- 折线图 -->
+              <div class="chart-container">
+                <LineChart :data="currentChartData" :color="'#167af0'" :title="currentChartTitle" height="270px"
+                  :smooth="false" :area-style="true" />
+                <div class="echarts-name">{{currentChartTitle}}</div>
+              </div>
+              <el-divider style="margin: 30px 0; background: rgba(230, 230, 230, 1);"/>
+              <div class="table-container">
+                <div class="btn-toggle-table" :class="{ 'hide-table': hideTable }" @click="hideTable = !hideTable">
+                  {{ hideTable ? '展开' : '收起' }}明细数据<svg 
+                    width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M10.5 8.75L7 5.25L3.5 8.75" stroke="#167AF0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+                  </svg>
+                </div>
+
+                <el-table class="statistics-table" :data="state.dataList" row-key="date" style="width: 100%"
+                  border :cell-style="tableStyle.cellStyle"
+                  :header-cell-style="tableStyle.headerCellStyle"
+                  v-if="!hideTable"
+                  >
+                  <el-table-column :label="'日期'" prop="date" show-overflow-tooltip></el-table-column>
+                  <el-table-column :label="'卸载流失设备'" :formatter="statusFormatter" prop="churn" show-overflow-tooltip></el-table-column>
+                  <el-table-column :label="'卸载召回设备'" :formatter="statusFormatter" prop="recall" show-overflow-tooltip></el-table-column>
+                </el-table>
+
+                <pagination 
+                  v-if="!hideTable"
+                  @current-change="currentChangeHandle" 
+                  @size-change="sizeChangeHandle" 
+                  v-bind="state.pagination">
+                </pagination>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+   </div>
+</template>
+
+<script lang="ts" name="churnOverview" setup>
+import LineChart from './echarts/LineChart.vue'
+import ProgressRing from '../echarts/ProgressRing.vue'
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { ref, computed, reactive } from 'vue'
+
+// 流失数据
+const churnData = ref([
+  { name: '流失设备', value: 38, itemStyle: { color: 'rgba(0, 188, 113, 1)' } },
+  { name: '其他', value: 62, itemStyle: { color: 'rgba(239, 239, 239, 1)' } }
+])
+
+// 召回数据
+const recallData = ref([
+  { name: '召回设备', value: 38, itemStyle: { color: 'rgba(230, 66, 66, 1)' } },
+  { name: '其他', value: 62, itemStyle: { color: 'rgba(239, 239, 239, 1)' } }
+])
+
+// 流失趋势数据
+const churnTrendData = ref([
+  { date: '2025-01-01', value: 25 },
+  { date: '2025-01-02', value: 32 },
+  { date: '2025-01-03', value: 28 },
+  { date: '2025-01-04', value: 45 },
+  { date: '2025-01-05', value: 38 },
+  { date: '2025-01-06', value: 42 },
+  { date: '2025-01-07', value: 35 },
+  { date: '2025-01-08', value: 48 },
+  { date: '2025-01-09', value: 52 },
+  { date: '2025-01-10', value: 39 },
+  { date: '2025-01-11', value: 44 },
+  { date: '2025-01-12', value: 37 },
+  { date: '2025-01-13', value: 41 },
+  { date: '2025-01-14', value: 46 }
+])
+
+// 召回趋势数据
+const recallTrendData = ref([
+  { date: '2025-01-01', value: 15 },
+  { date: '2025-01-02', value: 22 },
+  { date: '2025-01-03', value: 18 },
+  { date: '2025-01-04', value: 35 },
+  { date: '2025-01-05', value: 28 },
+  { date: '2025-01-06', value: 32 },
+  { date: '2025-01-07', value: 25 },
+  { date: '2025-01-08', value: 38 },
+  { date: '2025-01-09', value: 42 },
+  { date: '2025-01-10', value: 29 },
+  { date: '2025-01-11', value: 34 },
+  { date: '2025-01-12', value: 27 },
+  { date: '2025-01-13', value: 31 },
+  { date: '2025-01-14', value: 36 }
+])
+
+const activeTab = ref('churnTrend')
+
+// 计算当前图表数据
+const currentChartData = computed(() => {
+  return activeTab.value === 'churnTrend' ? churnTrendData.value : recallTrendData.value
+})
+
+// 计算当前图表标题
+const currentChartTitle = computed(() => {
+  return activeTab.value === 'churnTrend' ? '卸载流失设备' : '卸载召回设备'
+})
+
+const handleTabClick = (tab: string) => {
+  activeTab.value = tab
+} 
+
+// 表格数据
+const hideTable = ref(false);
+const state: BasicTableProps = reactive<BasicTableProps>({
+  queryForm: {
+    ip: '',
+  },
+  pageList: () => Promise.resolve([]),
+  pagination: {
+    current: 1,
+    size: 10,
+    total: 0,
+    pageSizes: [5, 10, 20, 50, 100]
+  },
+  dataList: [
+    {
+      date: '2025-01-01',
+      churn: 10,
+      recall: 20
+    },
+    {
+      date: '2025-01-02',
+      churn: 15,
+      recall: 25
+    },
+    {
+      date: '2025-01-03',
+      churn: 20,
+      recall: 30
+    },
+    {
+      date: '2025-01-04',
+      churn: 25,
+      recall: 35
+    }
+  ]
+});
+const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
+const statusFormatter = (row: any, column: any, cellValue: any, index: any) => {
+  return cellValue || '--';
+}
+
+</script>
+<style scoped lang="scss">
+svg {
+  vertical-align: middle;
+  margin: 0 0 0 12px;
+}
+.overview {
+  font-family: Source Han Sans SC;
+  color: rgba(18, 18, 18, 1);
+}
+
+.top-info {
+  color: rgba(18, 18, 18, 1);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 25px;
+
+
+  .title {
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 20px;
+    padding: 4px 0;
+  }
+
+  .aside {
+    display: flex;
+  }
+
+  .data-source-status {
+    font-weight: 400;
+    font-size: 14px;
+    line-height: 20px;
+    color: rgba(18, 18, 18, 1);
+    padding: 4px 16px 4px 0;
+  }
+
+  .goto-smart-operation {
+    padding: 4px 12px;
+    border-radius: 4px;
+    border: 1px solid rgba(22, 122, 240, 1);
+    font-weight: 400;
+    font-size: 14px;
+    line-height: 20px;
+    color: rgba(22, 122, 240, 1);
+  }
+
+}
+
+.top-content {
+  display: flex;
+  gap: 20px;
+
+  .content-item {
+    display: flex;
+    align-items: center;
+    gap: 0;
+    flex: 1;
+    padding: 27.5px;
+    justify-content: center;
+  }
+
+  .content-item-left {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 30px;
+  }
+
+  .content-item-right {
+    // flex: 1;
+  }
+
+  .content-item-title {
+    color: rgba(18, 18, 18, 1);
+    font-weight: 400;
+    font-size: 15px;
+    line-height: 20px;
+    margin-bottom: 12px;
+  }
+
+  .content-item-value {
+    font-weight: 500;
+    font-style: Medium;
+    font-size: 28px;
+    margin-bottom: 16px;
+    line-height: 41px;
+  }
+
+  .content-item-percent {
+    font-weight: 400;
+    font-size: 14px;
+    vertical-align: middle;
+    line-height: 20px;
+  }
+
+  .content-item-percent span {
+    color: rgba(0, 188, 113, 1);
+  }
+}
+
+.trend-container {
+  padding: 10px 14px;
+  .title {
+    line-height: 19px;
+    font-weight: 500;
+    font-size: 16px;
+    padding-left: 12px;
+    position: relative;
+    margin-bottom: 53px;
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 50%;
+      transform: translateY(-50%);
+      width: 4px;
+      height: 14px;
+      background: rgba(22, 122, 240, 1);
+    }
+  }
+
+  .tabs {
+    display: flex;
+    margin-left: 85px;
+    .tabs-item {
+      padding: 0 12px;
+      height: 32px;
+      line-height: 30px;
+      border: 1px solid rgba(22, 122, 240, 1);
+      text-align: center;
+      color: rgba(22, 122, 240, 1);
+      cursor: pointer;
+      &.active {
+        background: rgba(22, 122, 240, 1);
+        color: #ffffff;
+      }
+    }
+  }
+
+  .chart-container {
+    margin: 20px 85px 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;
+    
+    .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;
+    }
+
+  }
+
+}
+</style>