周玉环 пре 1 дан
родитељ
комит
85615053c3

+ 1 - 0
xinkeaboard-promotion-portal/components.d.ts

@@ -24,6 +24,7 @@ declare module 'vue' {
     ElInput: typeof import('element-plus/es')['ElInput']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
     Empty: typeof import('./src/components/Empty.vue')['default']
     Home: typeof import('./src/components/Home.vue')['default']
     Item: typeof import('./src/components/competitor/item.vue')['default']

+ 21 - 4
xinkeaboard-promotion-portal/src/App.vue

@@ -1,15 +1,32 @@
 <template>
-  <router-view></router-view>
+  <router-view :key="route.path"></router-view>
 </template>
 
 <script setup lang="ts">
 import { useRoute } from 'vue-router';
 // 这里写你的业务逻辑
-// const route = useRoute();
-
-
+const route = useRoute();
 </script>
 
 <style>
 /* 全局样式 */
+.help-tip {
+  width: 540px;
+  /* height: 110px; */
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  .title {
+    font-weight: bold;
+    font-size: 16px;
+    color: #282e30;
+    margin-bottom: 10px;
+  }
+
+  .value {
+    font-weight: 400;
+    font-size: 14px;
+    color: #282e30;
+  }
+}
 </style>

+ 41 - 7
xinkeaboard-promotion-portal/src/components/competitor/RankTable.vue

@@ -2,8 +2,37 @@
   <div class="rank-table">
     <div class="rank-table-head">最有价值的关键词排名</div>
     <el-table :data="tableData">
-      <el-table-column prop="keyword" align="center" label="关键词"> </el-table-column>
+      <el-table-column prop="keyword" align="center" label="关键词">
+        <template #header>
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">关键词</span>
+              <span class="value"
+                >当您搜索一个术语或短语(又名“关键字")时,通常会有数千个结果。如果某个域名出现在前 50
+                名结果中,我们将该搜索词视为其自然(SEO) 关键字之一。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
+          关键词
+        </template>
+      </el-table-column>
       <el-table-column prop="searchVolume" align="center" label="搜索量">
+        <template #header>
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">搜索量</span>
+              <span class="value"
+                >这显示了上个月在美国 https://www.google.com/search?q=Google.com
+                上的搜索次数(或在我们其他国家/地区选项中,在所选国家/地区内用 Google
+                进行的搜索次数)。我们融合了来自多个来源的数据,以提供该关键词搜索活动的更好快照,因此它不会与
+                Google 的搜索量指标完全相同。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
+          搜索量
+        </template>
         <template #default="scope">
           {{ parseNumber(scope.row.searchVolume) }}
         </template>
@@ -11,10 +40,9 @@
       <el-table-column prop="monthly" align="center" label="SEO点击次数(变化)">
         <template #default="scope">
           <span v-if="scope.row.monthly > 0" style="color: #036eb8">{{
-            '+ ' + parseNumber(scope.row.monthly)
+            '+' + parseNumber(scope.row.monthly)
           }}</span>
-          <span v-else style="color: red">{{ '- ' + parseNumber(scope.row.monthly) }}</span>
-          {{ scope }}
+          <span v-else style="color: red">{{ parseNumber(scope.row.monthly) }}</span>
         </template>
       </el-table-column>
       <el-table-column prop="dp" align="center" label="DP(关键词难度)"> </el-table-column>
@@ -31,6 +59,7 @@
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
 import { useMainStore } from '@/store';
+import Help from '@/assets/images/help.png';
 
 const props = defineProps<{
   tableData: any[];
@@ -39,7 +68,7 @@ const props = defineProps<{
 const mainStore = useMainStore();
 
 const expanded = computed(() => mainStore.getExpanded);
-const tableData = expanded.value ? props.tableData : props.tableData.slice(0, 10);
+const tableData = computed(() => (expanded.value ? props.tableData : props.tableData.slice(0, 10)));
 
 const parseNumber = (num: number) => {
   if (num >= 1000) {
@@ -72,7 +101,11 @@ const parseNumber = (num: number) => {
 <style lang="scss" scoped>
 .rank-table {
   width: 100%;
-  min-height: 300px;
+
+  :deep(.el-table) {
+    width: 100%;
+    // min-height: 376px;
+  }
 
   &-head {
     height: 66px;
@@ -85,7 +118,7 @@ const parseNumber = (num: number) => {
 
   :deep(.el-table) {
     width: 100%;
-    height: 317px;
+    // height: 317px;
 
     .el-table__header {
       height: 52px;
@@ -111,6 +144,7 @@ const parseNumber = (num: number) => {
       height: 22px;
       position: relative;
       top: 5px;
+      cursor: pointer;
     }
   }
 }

+ 35 - 4
xinkeaboard-promotion-portal/src/components/competitor/RecommendTable.vue

@@ -2,8 +2,37 @@
   <div class="recommend-table">
     <div class="recommend-table-head">推荐关键词</div>
     <el-table :data="tableData">
-      <el-table-column prop="keywords" align="center" label="关键词"> </el-table-column>
+      <el-table-column prop="keywords" align="center" label="关键词">
+        <template #header>
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">关键词</span>
+              <span class="value"
+                >当您搜索一个术语或短语(又名“关键字")时,通常会有数千个结果。如果某个域名出现在前 50
+                名结果中,我们将该搜索词视为其自然(SEO) 关键字之一。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
+          关键词
+        </template>
+      </el-table-column>
       <el-table-column prop="searchVolume" align="center" label="搜索量">
+        <template #header>
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">搜索量</span>
+              <span class="value"
+                >这显示了上个月在美国 https://www.google.com/search?q=Google.com
+                上的搜索次数(或在我们其他国家/地区选项中,在所选国家/地区内用 Google
+                进行的搜索次数)。我们融合了来自多个来源的数据,以提供该关键词搜索活动的更好快照,因此它不会与
+                Google 的搜索量指标完全相同。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
+          搜索量
+        </template>
         <template #default="scope">
           {{
             scope.row.searchVolume >= 1000
@@ -24,6 +53,7 @@
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
 import { useMainStore } from '@/store';
+import Help from '@/assets/images/help.png';
 
 const props = defineProps<{
   tableData: any[];
@@ -32,7 +62,7 @@ const props = defineProps<{
 const mainStore = useMainStore();
 
 const expanded = computed(() => mainStore.getExpanded);
-const tableData = expanded.value ? props.tableData : props.tableData.slice(0, 10);
+const tableData = computed(() => (expanded.value ? props.tableData : props.tableData.slice(0, 10)));
 
 // const tableData = ref([
 //   {
@@ -51,7 +81,7 @@ const tableData = expanded.value ? props.tableData : props.tableData.slice(0, 10
 <style lang="scss" scoped>
 .recommend-table {
   width: 100%;
-  min-height: 300px;
+  // min-height: 300px;
 
   &-head {
     height: 66px;
@@ -64,7 +94,7 @@ const tableData = expanded.value ? props.tableData : props.tableData.slice(0, 10
 
   :deep(.el-table) {
     width: 100%;
-    height: 317px;
+    // height: 317px;
 
     .el-table__header {
       height: 52px;
@@ -90,6 +120,7 @@ const tableData = expanded.value ? props.tableData : props.tableData.slice(0, 10
       height: 22px;
       position: relative;
       top: 5px;
+      cursor: pointer;
     }
   }
 }

+ 32 - 16
xinkeaboard-promotion-portal/src/components/competitor/TrafficChart.vue

@@ -19,13 +19,18 @@ let chartInstance: any = null;
 const initChart = () => {
   if (!chartRef.value) return;
   chartInstance = echarts.init(chartRef.value);
-
+  const paidsTotal: any = props.paid.reduce((a: any, b: any) => a + b);
+  const naturalTotal: any = props.natural.reduce((a: any, b: any) => a + b);
+  console.log('paidsTotal, naturalTotal', paidsTotal, naturalTotal);
   const option = {
+    tooltip: {
+      trigger: 'axis'
+    },
     title: {
       text: '网站历史数据',
       left: 18,
       top: 0,
-      textStyle: { fontSize: 20, fontWeight: 'bold', color: '#282E30' }
+      textStyle: { fontSize: 16, fontWeight: 'bold', color: '#282E30' }
     },
     legend: {
       data: ['自然', '付费'],
@@ -34,7 +39,7 @@ const initChart = () => {
       top: 0,
       textStyle: {
         color: '#282E30', // 图例文字颜色
-        fontSize: 24
+        fontSize: 16
       }
     },
     graphic: [
@@ -119,7 +124,7 @@ const initChart = () => {
           shadowColor: 'rgba(126, 206, 244, 0.4)',
           shadowBlur: 10
         },
-        areaStyle: null,
+        areaStyle: null
       },
       {
         name: '付费',
@@ -138,13 +143,13 @@ const initChart = () => {
           shadowColor: 'rgba(3, 110, 184, 0.4)',
           shadowBlur: 10
         },
-        areaStyle: null,
+        areaStyle: null
       },
       // 白色圆形背景
       {
         type: 'pie',
         radius: ['48%'], // 半径比主环形图小一点或相同
-        center: ['50%', '50%'],
+        center: ['45%', '50%'],
         data: [{ value: 1, name: 'background' }],
         silent: true, // 不响应鼠标事件
         label: { show: false }, // 不显示文字
@@ -155,20 +160,31 @@ const initChart = () => {
       {
         type: 'pie',
         radius: ['35%', '45%'],
-        center: ['50%', '50%'],
-        data: [{ value: 100, name: props.centerText }],
+        center: ['45%', '50%'],
+        data: [
+          { value: paidsTotal, name: '付费', itemStyle: { color: '#036EB8' } },
+          { value: naturalTotal, name: '自然', itemStyle: { color: '#7ECEF4' } }
+        ],
         label: {
-          show: true,
-          position: 'center',
-          formatter: props.centerText,
-          fontSize: 16,
-          fontWeight: 'bold'
+          // show: true,
+          // position: 'center',
+          // // formatter: () => paidsTotal + naturalTotal, // 默认显示总数
+          // fontSize: 16,
+          // fontWeight: 'bold'
         },
-        itemStyle: {
-          color: '#7ECEF4'
+        emphasis: {
+          // label: {
+          //   show: true,
+          //   formatter: '{c}', // 鼠标移入时显示当前数值
+          //   fontSize: 18,
+          //   fontWeight: 'bold'
+          // }
+        },
+        tooltip: {
+          trigger: 'item',
+          // formatter: '{b}: {c}' // 付费: 120
         },
         z: 10
-        // tooltip: { show: false }
       }
     ]
   };

+ 33 - 10
xinkeaboard-promotion-portal/src/components/competitor/item.vue

@@ -20,10 +20,10 @@
       <div class="competitor-item-show__sort">
         <RankTable :tableData="rankBOList"></RankTable>
       </div>
-      <div class="competitor-item-suggestion">
-        <RecommendTable  :tableData="rankBOList"></RecommendTable>
+      <div class="competitor-item-show__suggestion">
+        <RecommendTable :tableData="recommendationBOList"></RecommendTable>
       </div>
-      <div class="competitor-item-show__bottom">立即下载报告,查看更多</div>
+      <div class="competitor-item-show__bottom" @click="download">立即下载报告,查看更多</div>
     </div>
   </div>
 </template>
@@ -35,6 +35,8 @@ import TrafficChart from './TrafficChart.vue';
 import Empty from '@/components/CommonEmpty.vue';
 import RankTable from '@/components/competitor/RankTable.vue';
 import RecommendTable from '@/components/competitor/RecommendTable.vue';
+import { downloadPDF } from '@/utils/pdf';
+
 
 import type { TrafficBO, TrafficBOItem } from '@/types';
 
@@ -42,7 +44,11 @@ const props = defineProps<{
   siteName: string;
 }>();
 
-const trafficBOData = reactive<TrafficBO>({} as TrafficBO);
+const trafficBOData = reactive<TrafficBO>({
+  x_axis: [],
+  organic: [],
+  paids: []
+} as TrafficBO);
 const rankBOList = ref<any[]>([]);
 const recommendationBOList = ref<any[]>([]);
 const loading = ref<boolean>(false);
@@ -50,26 +56,29 @@ const autoFinish = ref<boolean>(false);
 const fail = ref<boolean>(false);
 
 const mainStore = useMainStore();
-
+const download = () => {
+  downloadPDF();
+};
 const getRivalData = () => {
   loading.value = true;
   return mainStore
     .getRival(props.siteName)
-    .then((res) => {
-      const trafficBOList: TrafficBOItem[] = res.competitorBOS.trafficBOList;
+    .then((result) => {
+      const res = result.data;
+      const trafficBOList: TrafficBOItem[] = res.competitorBOS[0]?.trafficBOList ?? [];
       trafficBOList.forEach((item) => {
         trafficBOData.x_axis.push(item.x_axis);
         trafficBOData.organic.push(item.organic);
         trafficBOData.paids.push(item.paid);
       });
-      rankBOList.value = res.competitorBOS.rankBOList;
-      recommendationBOList.value = res.competitorBOS.recommendationBOList;
+      rankBOList.value = res.competitorBOS[0]?.rankBOList ?? [];
+      recommendationBOList.value = res.competitorBOS[0]?.recommendationBOList ?? [];
       autoFinish.value = true;
       setTimeout(() => {
         loading.value = false;
       }, 300);
     })
-    .catch(() => {
+    .catch((err) => {
       fail.value = true;
     });
 };
@@ -116,6 +125,20 @@ getRivalData();
       margin: 20px 0;
     }
 
+    &__sort {
+      width: 100%;
+      min-height: 376px;
+      margin-top: 20px;
+      background-color: #fff;
+    }
+
+    &__suggestion {
+      width: 100%;
+      min-height: 376px;
+      margin-top: 20px;
+      background-color: #fff;
+    }
+
     &__bottom {
       display: flex;
       justify-content: center;

+ 11 - 1
xinkeaboard-promotion-portal/src/components/keyword/search.vue

@@ -8,7 +8,16 @@
     <div class="keyword-search-line"></div>
     <div class="keyword-search-tip">
       <span>关键词难度</span>
-      <img :src="Help" />
+      <el-tooltip popper-class="help-tip" effect="light" placement="top">
+        <template #content style="padding: 20px">
+          <span class="title">关键词难度</span>
+          <span class="value"
+            >我们已经计算了在这个关键词上排名的难度。分数基于 1-100 的等级,100 是最难排名的。
+            将此数字与您定位的其他关键字进行比较,以了解如何确定 SEO 活动的优先级。</span
+          >
+        </template>
+        <img :src="Help" />
+      </el-tooltip>
     </div>
     <div class="keyword-search-bar">
       <DifficultyBar :value="competition"></DifficultyBar>
@@ -75,6 +84,7 @@ const searchVolume = computed(() => {
     img {
       width: 22px;
       height: 22px;
+      cursor: pointer;
     }
   }
 

+ 38 - 7
xinkeaboard-promotion-portal/src/components/keyword/table.vue

@@ -7,21 +7,50 @@
       v-if="loading"
     ></Empty>
     <el-table :data="renderTableData" v-else>
-      <el-table-column prop="keyword" align="right">
+      <el-table-column prop="keyword" align="center">
         <template #header>
-          <img :src="Help" />
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">关键词</span>
+              <span class="value"
+                >当您搜索一个术语或短语(又名“关键字")时,通常会有数千个结果。如果某个域名出现在前 50
+                名结果中,我们将该搜索词视为其自然(SEO) 关键字之一。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
           关键词
         </template>
       </el-table-column>
       <el-table-column prop="searchVolume" align="center">
         <template #header>
-          <img :src="Help" />
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">搜索量</span>
+              <span class="value"
+                >这显示了上个月在美国 https://www.google.com/search?q=Google.com
+                上的搜索次数(或在我们其他国家/地区选项中,在所选国家/地区内用 Google
+                进行的搜索次数)。我们融合了来自多个来源的数据,以提供该关键词搜索活动的更好快照,因此它不会与
+                Google 的搜索量指标完全相同。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
           搜索量
         </template>
       </el-table-column>
       <el-table-column prop="competition" align="center">
         <template #header>
-          <img :src="Help" />
+          <el-tooltip popper-class="help-tip" effect="light" placement="top">
+            <template #content style="padding: 20px">
+              <span class="title">关键词难度</span>
+              <span class="value"
+                >我们已经计算了在这个关键词上排名的难度。分数基于 1-100 的等级,100 是最难排名的。
+                将此数字与您定位的其他关键字进行比较,以了解如何确定 SEO 活动的优先级。</span
+              >
+            </template>
+            <img :src="Help" />
+          </el-tooltip>
           关键词难度
         </template>
       </el-table-column>
@@ -37,14 +66,15 @@ import Empty from '@/components/CommonEmpty.vue';
 
 const mainStore = useMainStore();
 
-const expanded = computed(() => mainStore.getExpanded);
+// const expanded = computed(() => mainStore.getExpanded);
 
 const tableData = computed(() => mainStore.suggestionsInfo.data);
 const loading = computed(() => mainStore.suggestionsInfo.loading);
 const autoFinish = computed(() => mainStore.suggestionsInfo.autoFinish);
 const fail = computed(() => mainStore.suggestionsInfo.fail);
+const renderTableData = computed(() => tableData.value.slice(0, 5));
 
-const renderTableData = computed(() => expanded.value ? tableData.value : tableData.value.slice(0, 5));
+// const renderTableData = computed(() => expanded.value ? tableData.value : tableData.value.slice(0, 5));
 </script>
 
 <style lang="scss" scoped>
@@ -55,7 +85,7 @@ const renderTableData = computed(() => expanded.value ? tableData.value : tableD
   height: 100%;
   :deep(.el-table) {
     width: 100%;
-    height: 317px;
+    // height: 317px;
 
     .el-table__header {
       height: 52px;
@@ -81,6 +111,7 @@ const renderTableData = computed(() => expanded.value ? tableData.value : tableD
       height: 22px;
       position: relative;
       top: 5px;
+      cursor: pointer;
     }
   }
 }

+ 5 - 2
xinkeaboard-promotion-portal/src/utils/pdf.ts

@@ -10,7 +10,7 @@ const mainStore = useMainStore();
 const expanded = computed(() => mainStore.getExpanded);
 const isLoadOver = computed(() => mainStore.getIsLoadOver);
 
-export const downloadPDF = async (pdfContent: HTMLElement) => {
+export const downloadPDF = async (pdfContent?: HTMLElement) => {
   if (!isLoadOver.value) {
     showMessage({
       type: 'warning',
@@ -29,9 +29,12 @@ export const downloadPDF = async (pdfContent: HTMLElement) => {
   }
   await nextTick();
   let canvas = null;
+  if (!pdfContent) {
+    pdfContent = document.querySelector('.record-wrap') as HTMLElement;
+  }
   try {
     canvas = await html2canvas(pdfContent, {
-      scale: 2,
+      scale: 1.5, // 提高分辨率
       useCORS: true,
       backgroundColor: '#fff' // 防止透明背景
     });