jcq vor 1 Woche
Ursprung
Commit
e83cf9835a

+ 19 - 3
src/views/count/churn/overview/index.vue

@@ -165,11 +165,27 @@ const paginatedData = computed(() => {
   return dataList.value.slice(start, end);
 });
 
+const getDate = (day: string, num: number) => {
+  const [year, month, date] = day.split('-').map(item => parseInt(item));
+  return formatDate(new Date(new Date(year, month-1, date - num)), 'YYYY-mm-dd');
+}
+
 const getUninstallTrend = () => {
+  const today = new Date();
+  let startDate = '';
+  let endDate = '';
+  let day = today.getDay();
+  if (day>=4) {
+    startDate = getDate(formatDate(today, 'YYYY-mm-dd'), day - 4 + 7);
+    endDate = getDate(formatDate(today, 'YYYY-mm-dd'), day - 4 + 1);
+  } else {
+    startDate = getDate(formatDate(today, 'YYYY-mm-dd'), 14 - 4 + day);
+    endDate = getDate(formatDate(today, 'YYYY-mm-dd'), 7 - 4 + day + 1);
+  }
   uninstallTrend({
-    startDate: formatDate(new Date(new Date().setDate(new Date().getDate() - 7)), 'YYYY-mm-dd'),
-    endDate: formatDate(new Date(), 'YYYY-mm-dd'),
-    timeUnit: 'day',
+    startDate: startDate,
+    endDate: endDate,
+    timeUnit: 'week',
     pageNum: state.pagination!.current,
     pageSize: state.pagination!.size,
   }).then(res => {

+ 30 - 10
src/views/count/churn/portrait/components/DataTable.vue

@@ -1,17 +1,28 @@
 <template>
-  <el-table class="table" :data="data" row-key="name" style="width: 100%" :cell-style="{
+  <el-table class="table" :data="newData" row-key="name" style="width: 100%" :cell-style="{
     ...cellStyle,
     ...props.cellStyle
   }" :header-cell-style="{
     ...headerCellStyle,
     ...props.headerCellStyle
-    }">
-    <el-table-column v-for="item in columns" :key="item.prop" :label="item.label" :prop="item.prop" show-overflow-tooltip :width="item.width || ''">
+  }">
+    <el-table-column v-for="item in columns" :key="item.prop" :label="item.label" :prop="item.prop"
+      show-overflow-tooltip :width="item.width || ''">
       <template #default="scope">
-        <div class="percent-box" v-if="item.type === 'percentDom'">
-          <div class="inner" :style="{ width: scope.row[item.prop] + '%' }"></div>
-        </div>
-        <div v-else class="ellipsis-box">{{ scope.row[item.prop] + (item.prop == 'percent' ? '%' : '') }}</div>
+        <el-tooltip placement="top" effect="light" v-if="item.type === 'percentDom' && props.showTooltip">
+          <div class="percent-box">
+            <div class="inner" :style="{ width: scope.row[item.prop] + '%' }"></div>
+          </div>
+          <template #content>
+            {{ scope.row['name'] }}<br>
+            TGI: {{ scope.row['tgi'] }}<br>
+            本特征卸载用户占比: {{ scope.row['percent'] }}%
+          </template>
+        </el-tooltip>
+        <div class="percent-box" v-if="item.type === 'percentDom' && !props.showTooltip">
+            <div class="inner" :style="{ width: scope.row[item.prop] + '%' }"></div>
+          </div>
+        <div v-if="item.type !== 'percentDom'" class="ellipsis-box">{{ scope.row[item.prop] + (item.prop == 'percent' ? '%' : '') }}</div>
       </template>
     </el-table-column>
   </el-table>
@@ -37,7 +48,7 @@ const headerCellStyle = ref({
 
 const props = defineProps({
   data: {
-    type: Array,
+    type: Array as PropType<any[]>,
     default: () => []
   },
   columns: {
@@ -51,11 +62,15 @@ const props = defineProps({
   },
   cellStyle: {
     type: Object,
-    default: () => {}
+    default: () => { }
   },
   headerCellStyle: {
     type: Object,
-    default: () => {}
+    default: () => { }
+  },
+  showTooltip: {
+    type: Boolean,
+    default: false
   }
 })
 
@@ -67,6 +82,10 @@ const state = reactive<BasicTableProps>({
   }
 })
 const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
+const newData = ref(props.data);
+watch(() => props.data, (newVal) => {
+  newData.value = newVal;
+}, { immediate: true, deep: true });
 </script>
 <style scoped lang="scss">
 .percent-box {
@@ -80,6 +99,7 @@ const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTa
     background: rgba(109, 173, 249, 1);
   }
 }
+
 .ellipsis-box {
   width: 100%;
   overflow: hidden;

+ 6 - 2
src/views/count/churn/portrait/components/UserInfo.vue

@@ -31,14 +31,14 @@
       </div>
       <div class="title">{{ title }}</div>
       <div class="tags" style="min-height: 23px;">
-        <div class="tag" v-for="tag in tags" :key="tag">{{ tag }}</div>
+        <div class="tag" v-for="tag in newTags" :key="tag">{{ tag }}</div>
       </div>
     </div>
     <div class="tips">{{ tips }}</div>
   </div>
 </template>
 <script setup lang="ts">
-defineProps({
+const props = defineProps({
   type: {
     type: Boolean,
     default: true
@@ -56,6 +56,10 @@ defineProps({
     default: ''
   }
 })  
+const newTags = ref(props.tags);
+watch(() => props.tags, () => {
+  newTags.value = props.tags;
+}, { immediate: true, deep: true });
 </script>
 <style scoped lang="scss">
 .user-info {

+ 9 - 8
src/views/count/churn/portrait/index.vue

@@ -48,13 +48,13 @@
             <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
               <el-card shadow="none">
                 <UserInfo title="易卸载用户特征" :type="true" :tags="easyTags" tips="卸载用户 vs. 活跃用户具备的明显特征,有以下特征的用户易卸载" style="margin-bottom: 24px;"></UserInfo>
-                <DataTable :data="easyData" :columns="columns"></DataTable>
+                <DataTable :data="easyData" :columns="columns" :showTooltip="true"></DataTable>
               </el-card>
             </el-col>
             <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
               <el-card shadow="none">
                 <UserInfo title="不易卸载用户特征" :type="false" :tags="hardTags" tips="活跃用户 vs. 卸载用户具备的明显差异点,有以下特征的人群卸载可能性低" style="margin-bottom: 24px;"></UserInfo>
-                <DataTable :data="hardData" :columns="columns"></DataTable>
+                <DataTable :data="hardData" :columns="columns" :showTooltip="true"></DataTable>
               </el-card>
             </el-col>
           </el-row>
@@ -141,6 +141,13 @@ const hardTags = ref<string[]>([])
 const easyTags = ref<string[]>([])
 
 const init = () => {
+  easyData.value = [];
+  hardData.value = [];
+  easyTags.value = [];
+  hardTags.value = [];
+  echartsData.value = [];
+  usageTimeData.value = [];
+  
   uninstallPortrait({
     startDate: formatDate(new Date(form.value.dateArray[0]), 'YYYY-mm-dd'),
     endDate: formatDate(new Date(form.value.dateArray[1]), 'YYYY-mm-dd'),
@@ -151,12 +158,6 @@ const init = () => {
     }
     // if (res.data?.uninstallPortraitPhaseList) {
       const newData: any[] = [];
-      easyData.value = [];
-      hardData.value = [];
-      easyTags.value = [];
-      hardTags.value = [];
-
-      usageTimeData.value = [];
       res.data?.uninstallPortraitPhaseList.forEach((item: any) => {
         newData.push({
           name: item.phase,

+ 1 - 1
src/views/count/device/device/index.vue

@@ -10,7 +10,7 @@
       <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
         <el-card shadow="none">
           <div class="device-container">
-            <div class="title">TOP10机型
+            <div class="title">TOP10{{ wayType[form.tab - 1] }}
               <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

+ 1 - 1
src/views/count/device/location/index.vue

@@ -10,7 +10,7 @@
       <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
         <el-card shadow="none">
           <div class="location-container">
-            <div class="title">TOP10省市
+            <div class="title">TOP10{{ form.tab === 1 ? '省市' : '国家/地区' }}
               <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

+ 29 - 1
src/views/count/device/network/index.vue

@@ -10,7 +10,35 @@
       <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
         <el-card shadow="none">
           <div class="device-container">
-            <div class="title">TOP10联网方式</div>
+            <div class="title">TOP10{{ form.tab === 1 ? '联网方式' : '运营商' }}
+              <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 />
+                    筛选只展示昨日及之前的数据,启动次数指标只支持昨日之前的查询。
+                  </div>
+                </template>
+              </el-tooltip>
+            </div>
             <div class="card-tabs">
               <div class="card-tab" :class="{ active: echartsTab === 'newUser' }" @click="handleTabClick('newUser')">
                 新增用户

+ 144 - 117
src/views/count/main/trend/index.vue

@@ -26,6 +26,34 @@
                      </template>
                   </el-popover>
                </Title>
+               <div style="position: relative; top: -37px">
+                  <div class="link" style="float: right;">
+                     <svg style="margin-left: 20px;transform: translateY(3px);" width="16" height="16" viewBox="0 0 16 16"
+                        fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path
+                           d="M2.66663 14.6668V2.00016C2.66663 1.63197 2.9651 1.3335 3.33329 1.3335H12.6666C13.0348 1.3335 13.3333 1.63197 13.3333 2.00016V14.6668L7.99996 11.9093L2.66663 14.6668Z"
+                           stroke="#167AF0" stroke-linejoin="round" />
+                        <path d="M7.98328 4.6499V8.6499" stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
+                        <path d="M5.98328 6.6499H9.98328" stroke="#167AF0" stroke-linecap="round"
+                           stroke-linejoin="round" />
+                     </svg>
+                     <span style="margin-left: 8px;">订阅</span>
+                  </div>
+                  <div class="link" style="float: right;">
+                     <svg style="transform: translateY(3px);" width="16" height="16" viewBox="0 0 16 16" fill="none"
+                        xmlns="http://www.w3.org/2000/svg">
+                        <path
+                           d="M13.3333 5.3335V2.00016C13.3333 1.63197 13.0348 1.3335 12.6666 1.3335H3.33329C2.9651 1.3335 2.66663 1.63197 2.66663 2.00016V14.0002C2.66663 14.3684 2.9651 14.6668 3.33329 14.6668H5.33329"
+                           stroke="#167AF0" stroke-linecap="round" stroke-linejoin="round" />
+                        <path d="M5.33337 5.3335H10" stroke="#167AF0" stroke-linecap="round" />
+                        <path d="M9 11H15" stroke="#167AF0" stroke-linecap="round" />
+                        <path d="M12 14V8" stroke="#167AF0" stroke-linecap="round" />
+                        <path d="M5.33337 8H8.00004" stroke="#167AF0" stroke-linecap="round" />
+                     </svg>
+                     <span style="margin-left: 8px;">编辑指标</span>
+                  </div>
+                  <!-- <div class="link" style="float: right;">生成AI简报</div> -->
+               </div>
                <div class="msg">
                   <el-icon class="ml-1" style="color: #a4b8cf; vertical-align: middle;">
                      <InfoFilled />
@@ -93,11 +121,8 @@
             </template>
          </Lcard>
          <Lcard>
-            <el-date-picker style="float: left;" v-model="timeRange" type="datetimerange" range-separator="To"
-               start-placeholder="Start date" end-placeholder="End date" />
-            <div class="link" style="float: right;">订阅</div>
-            <div class="link" style="float: right;">编辑指标</div>
-            <div class="link" style="float: right;">生成AI简报</div>
+            <el-date-picker style="float: left;width: 240px;height: 30px;" v-model="timeRange" type="daterange"
+               value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" />
             <div class="line"></div>
             <div class="flex items-center justify-between mb-2 mt-3">
                <el-form>
@@ -110,10 +135,10 @@
                </el-form>
                <div class="flex items-center">
                   <el-radio-group v-model="lineChartUser">
-                     <el-radio-button label="hour">新增用户</el-radio-button>
-                     <el-radio-button label="day">活跃用户</el-radio-button>
-                     <el-radio-button label="week">启动次数</el-radio-button>
-                     <el-radio-button label="month">累计用户</el-radio-button>
+                     <el-radio-button label="new">新增用户</el-radio-button>
+                     <el-radio-button label="active">活跃用户</el-radio-button>
+                     <el-radio-button label="doing">启动次数</el-radio-button>
+                     <el-radio-button label="total">累计用户</el-radio-button>
                   </el-radio-group>
                </div>
             </div>
@@ -195,6 +220,8 @@ const timeRange = ref(null);
 const qChartRef = ref<HTMLDivElement | null>(null);
 let qualityChart: echarts.ECharts | null = null;
 
+const lineChartUser = ref('new')
+
 onMounted(() => {
    setTimeout(() => {
       initQualityChart();
@@ -247,25 +274,25 @@ function initQualityChart(): void {
 
 
 interface TableRow {
-	date: string;
-	newUsers: number;
-	ratio: string;
+   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 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%',
-	}))
+   Array.from({ length: 42 }).map((_, idx) => ({
+      date: `2025-08-${String(11).padStart(2, '0')}`,
+      newUsers: 727,
+      ratio: '97.45%',
+   }))
 );
 
 
@@ -281,107 +308,107 @@ const circleEchartUser2 = ref('hour');
 
 // 初始化圆环图
 function initCircleChart1(): void {
-  if (!circleEchartRef1.value) return;
-  if (circleChart1) circleChart1.dispose();
-  
-  circleChart1 = echarts.init(circleEchartRef1.value);
-  
-  const option: echarts.EChartsOption = {
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      bottom: '0',
-      left: 'center'
-    },
-    series: [
-      {
-        name: '版本分布',
-        type: 'pie',
-        radius: ['50%', '70%'], // 设置内半径和外半径,形成圆环效果
-        avoidLabelOverlap: false,
-        padAngle: 2, // 扇区间隙
-        itemStyle: {
-          borderRadius: 5 // 扇区圆角
-        },
-        label: {
-          show: true,
-          formatter: '{b}: {d}%' // 显示标签和百分比
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 14,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: true
-        },
-        data: [
-          { value: 35, name: 'v3.2.1' },
-          { value: 25, name: 'v3.1.5' },
-          { value: 20, name: 'v3.0.8' },
-          { value: 10, name: 'v2.9.3' },
-          { value: 10, name: '其他' }
-        ]
-      }
-    ]
-  };
-  
-  circleChart1.setOption(option);
+   if (!circleEchartRef1.value) return;
+   if (circleChart1) circleChart1.dispose();
+
+   circleChart1 = echarts.init(circleEchartRef1.value);
+
+   const option: echarts.EChartsOption = {
+      tooltip: {
+         trigger: 'item'
+      },
+      legend: {
+         bottom: '0',
+         left: 'center'
+      },
+      series: [
+         {
+            name: '版本分布',
+            type: 'pie',
+            radius: ['50%', '70%'], // 设置内半径和外半径,形成圆环效果
+            avoidLabelOverlap: false,
+            padAngle: 2, // 扇区间隙
+            itemStyle: {
+               borderRadius: 5 // 扇区圆角
+            },
+            label: {
+               show: true,
+               formatter: '{b}: {d}%' // 显示标签和百分比
+            },
+            emphasis: {
+               label: {
+                  show: true,
+                  fontSize: 14,
+                  fontWeight: 'bold'
+               }
+            },
+            labelLine: {
+               show: true
+            },
+            data: [
+               { value: 35, name: 'v3.2.1' },
+               { value: 25, name: 'v3.1.5' },
+               { value: 20, name: 'v3.0.8' },
+               { value: 10, name: 'v2.9.3' },
+               { value: 10, name: '其他' }
+            ]
+         }
+      ]
+   };
+
+   circleChart1.setOption(option);
 }
 // 初始化圆环图
 function initCircleChart2(): void {
-  if (!circleEchartRef2.value) return;
-  if (circleChart2) circleChart2.dispose();
-  
-  circleChart2 = echarts.init(circleEchartRef2.value);
-  
-  const option: echarts.EChartsOption = {
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      bottom: '0',
-      left: 'center'
-    },
-    series: [
-      {
-        name: '版本分布',
-        type: 'pie',
-        radius: ['50%', '70%'], // 设置内半径和外半径,形成圆环效果
-        avoidLabelOverlap: false,
-        padAngle: 2, // 扇区间隙
-        itemStyle: {
-          borderRadius: 5 // 扇区圆角
-        },
-        label: {
-          show: true,
-          formatter: '{b}: {d}%' // 显示标签和百分比
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 14,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: true
-        },
-        data: [
-          { value: 35, name: 'v3.2.1' },
-          { value: 25, name: 'v3.1.5' },
-          { value: 20, name: 'v3.0.8' },
-          { value: 10, name: 'v2.9.3' },
-          { value: 10, name: '其他' }
-        ]
-      }
-    ]
-  };
-  
-  circleChart2.setOption(option);
+   if (!circleEchartRef2.value) return;
+   if (circleChart2) circleChart2.dispose();
+
+   circleChart2 = echarts.init(circleEchartRef2.value);
+
+   const option: echarts.EChartsOption = {
+      tooltip: {
+         trigger: 'item'
+      },
+      legend: {
+         bottom: '0',
+         left: 'center'
+      },
+      series: [
+         {
+            name: '版本分布',
+            type: 'pie',
+            radius: ['50%', '70%'], // 设置内半径和外半径,形成圆环效果
+            avoidLabelOverlap: false,
+            padAngle: 2, // 扇区间隙
+            itemStyle: {
+               borderRadius: 5 // 扇区圆角
+            },
+            label: {
+               show: true,
+               formatter: '{b}: {d}%' // 显示标签和百分比
+            },
+            emphasis: {
+               label: {
+                  show: true,
+                  fontSize: 14,
+                  fontWeight: 'bold'
+               }
+            },
+            labelLine: {
+               show: true
+            },
+            data: [
+               { value: 35, name: 'v3.2.1' },
+               { value: 25, name: 'v3.1.5' },
+               { value: 20, name: 'v3.0.8' },
+               { value: 10, name: 'v2.9.3' },
+               { value: 10, name: '其他' }
+            ]
+         }
+      ]
+   };
+
+   circleChart2.setOption(option);
 }
 
 

+ 2 - 2
src/views/login/component/password.vue

@@ -75,8 +75,8 @@ const state = reactive({
   isShowPassword: false, // 是否显示密码
   ruleForm: {
     // 表单数据
-    username: 'admin', // 用户名
-    password: '123456', // 密码
+    username: '', // 用户名
+    password: '', // 密码
     code: '', // 验证码
     randomStr: '', // 验证码随机数
   },

+ 54 - 15
src/views/marketing/config/components/push.vue

@@ -6,7 +6,7 @@
 				<div class="flex gap-2 ml-2 flex-wrap w-57vw]">
 					<el-tag v-for="(tag, index) in regionData" :key="index" size="large" closable
 						:disable-transitions="false" @close="handleClose(tag, 'region')">
-						{{ tag }}
+						{{ tag == 'All' ? '全部国家' : tag }}
 					</el-tag>
 					<ChineseRegionSelector v-if="regionInputVisible" v-model="regionData" showAll
 						@update:modelValue="handleRegionDataUpdate" />
@@ -56,9 +56,16 @@
 		<el-form ref="ruleFormRef" :model="formData" :rules="dataRules" label-width="90px"
 			class="flex flex-wrap mt-4 w-1/2">
 			<el-form-item label="推送应用" prop="pushApp" class="w-full">
+<<<<<<< HEAD
 				<el-select v-model="formData.pushApp" placeholder="请选择推送应用" multiple collapse-tags collapse-tags-tooltip
 					:max-collapse-tags="4" filterable class="w-full" :value-key="'appId'">
 					<el-option v-for="item in appOptions" :key="item.value" :label="item.label" :value="item.value" />
+=======
+				<el-select v-model="formData.pushApp" placeholder="请选择推送方式" multiple collapse-tags collapse-tags-tooltip
+					:max-collapse-tags="4" filterable remote class="w-full">
+					<el-option v-for="item in appOptions" :key="item.value" :label="item.label"
+						:value="item.value" />
+>>>>>>> dev-ly
 				</el-select>
 			</el-form-item>
 			<el-form-item label="主动推送" prop="autoPush" class="w-1/2">
@@ -163,7 +170,7 @@ const getAppListData = async () => {
 		};
 	});
 };
-
+getAppListData();
 // // 表单校验规则
 const dataRules = reactive({
 	ruleName: [{ required: true, message: '规则名称不能为空', trigger: 'blur' }],
@@ -456,19 +463,21 @@ const handleInputConfirm = (type: string) => {
 	}
 };
 
-// 规范化 pushApp 值为 { id, appId } 数组
+// 修改 normalizePushApp 函数
 function normalizePushApp(input: any[]): Array<{ id: any; appId: any }> {
-	if (!Array.isArray(input)) return [];
-	return input
-		.map((item: any) => {
-			if (item && typeof item === 'object' && 'appId' in item && 'id' in item) {
-				return { id: item.id, appId: item.appId };
-			}
-			const appId = typeof item === 'object' && 'value' in item ? item.value : item;
-			const found = appOptions.value.find((opt: any) => opt.value === appId);
-			return found ? { id: found.id, appId: found.value } : null;
-		})
-		.filter((v: any): v is { id: any; appId: any } => v !== null);
+  if (!Array.isArray(input)) return [];
+  
+  // 如果输入是字符串数组(appId),转换为对象数组
+  return input.map((item: any) => {
+    if (typeof item === 'string') {
+      // 根据 appId 查找完整信息
+      const found = appOptions.value.find((opt: any) => opt.value === item);
+      return found ? { id: found.id, appId: found.value } : { id: null, appId: item };
+    } else if (item && typeof item === 'object' && 'appId' in item) {
+      return { id: item.id, appId: item.appId };
+    }
+    return { id: null, appId: String(item) };
+  });
 }
 
 // 根据选择的 pushApp 派生 pushBundle(去重)
@@ -544,7 +553,7 @@ const onSubmit = async () => {
 		...formData.value,
 		ip: ipData.value,
 		domain: domainData.value,
-		pushAddr: regionData.value,
+		pushAddr: regionData.value.length === 1 && regionData.value[0] === '全部国家' ? ['All'] : regionData.value,
 		pushContent: getpushContent(),
 		pushType: false,
 		pushFrequency: pushFrequency.toString(),
@@ -581,6 +590,7 @@ const formatNum = (value: string | number = 0) => {
 	}
 	return '';
 };
+<<<<<<< HEAD
 
 onMounted(() => {
 	getAppListData();
@@ -603,6 +613,35 @@ watch(
 			oldUrl.value = props.rowData.pushContent;
 		}
 	}
+=======
+// 修改 watch 部分
+watch(
+  () => props.rowData,
+  async (val) => {
+    if (val) {
+      // 确保 appOptions 已加载
+      if (appOptions.value.length === 0) {
+        await getAppListData();
+      }
+      
+      formData.value = {
+        ...val,
+        action: val?.action || '1',
+        pushFrequency: formatNum(val?.pushFrequency),
+        pushApp: val.pushApp ? val.pushApp.map((app: any) => {
+          // 如果是对象,提取 appId;如果是字符串,直接使用
+          return typeof app === 'object' ? app.appId : app;
+        }) : [],
+        pushBundle: val.pushBundle || [],
+      };
+      ipData.value = val.ip || [];
+      domainData.value = val.domain || [];
+      regionData.value = val.pushAddr || ['全部国家'];
+      oldUrl.value = val.pushContent;
+    }
+  },
+  { deep: false } // 避免深度监听
+>>>>>>> dev-ly
 );
 </script>
 <style lang="scss"></style>

+ 8 - 3
src/views/marketing/data/index.vue

@@ -25,7 +25,7 @@
           <template #default="{ row }">{{ row.clientID }}</template>
         </el-table-column>
         <el-table-column label="IP" prop="updateTime" show-overflow-tooltip>
-          <template #default="{ row }">{{ getIp(row.deviceInfo || '{}') }}</template>
+          <template #default="{ row }">{{ row.clientIp }}</template>
         </el-table-column>
         <el-table-column label="设备信息" min-width="150" prop="deviceInfo" show-overflow-tooltip>
           <template #header>
@@ -124,11 +124,16 @@ const getIp = (jsonString: string) => {
 
 const getDeviceName = (jsonString: string) => {
   if(!jsonString) return '';
-  const nameMatch = jsonString.match(/"name"\s*:\s*"([^"]+)"/);
+  const nameMatch = jsonString.match(/"model"\s*:\s*"([^"]+)"/);
+  console.log(nameMatch);
+  
   const systemNameMatch = jsonString.match(/"systemName"\s*:\s*"([^"]+)"/);
+  const version = jsonString.match(/"version"\s*:\s*"([^"]+)"/);
   const name = nameMatch ? nameMatch[1] : 'unKnown';
   const systemName = systemNameMatch ? systemNameMatch[1] : 'unKnown';
-  return `设备名称:${name}; 系统名称:${systemName}`
+  const versionName = version ? version[1] : '';
+
+  return `设备名称:${name}; 系统版本:${systemName} ${versionName}`
 }
 
 const timeRange = computed({

+ 5 - 12
src/views/marketing/push-logs/index.vue

@@ -64,6 +64,9 @@
             {{ row.pushStatus ? '已推送' : '未推送' }}
           </template>
         </el-table-column>
+        <el-table-column :formatter="statusFormatter" :label="'客户端ID'" prop="clientID" min-width="120"
+          show-overflow-tooltip>
+        </el-table-column>
         <el-table-column :formatter="statusFormatter" :label="'推送详情'" prop="pushDetail" min-width="250">
           <template #default="{ row }">
             <div class="trigger-info" @click="showTriggerInfo(row)">
@@ -161,16 +164,6 @@ const disabledDate = (time: Date) => {
   return time.getTime() > Date.now();
 }
 
-interface SourceData {
-  ruleName: string;
-  triggerCondition: string;
-  triggerTime: string;
-  pushStatus: string;
-  pushContent: string;
-  pushFrequency: string;
-  pushTime: string;
-}
-
 const richContentDialogRef = ref();
 const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
 
@@ -225,8 +218,8 @@ const formatNum = (value: string | number = 0) => {
 	return '--';
 };
 
-onMounted(() => {
-  getFetchItemList();
+onMounted(async () => {
+  await getFetchItemList();
   query();
 });