Bläddra i källkod

✨ feat<TopSearchTerm Table>: 更改日期选择方式

WanGxC 11 månader sedan
förälder
incheckning
189f115dbb

+ 0 - 11
src/views/keyword/topSearchTermTable/api.ts

@@ -1,11 +0,0 @@
-import { request } from '/@/utils/service';
-
-const apiPrefix = '/api/searchterm/';
-
-export function getTopSearchTermTable(query: any) {
-  return request({
-    url: apiPrefix + 'topsearchtermTable/',
-    method: 'GET',
-    params: query,
-  });
-}

+ 0 - 0
src/views/keyword/rootWordManage/api.ts → src/views/searchTerm/rootWordManage/api.ts


+ 0 - 0
src/views/keyword/rootWordManage/components/root-word-manage-table.vue → src/views/searchTerm/rootWordManage/components/root-word-manage-table.vue


+ 0 - 0
src/views/keyword/rootWordManage/index.vue → src/views/searchTerm/rootWordManage/index.vue


+ 0 - 0
src/views/keyword/topSearchTermRank/api.ts → src/views/searchTerm/topSearchTermRank/api.ts


+ 1 - 1
src/views/keyword/topSearchTermRank/column-chart.vue → src/views/searchTerm/topSearchTermRank/column-chart.vue

@@ -91,7 +91,7 @@ function initChart() {
 </script>
 
 <template>
-  <div ref="chartRef" style="width: 100%; height: 200px"></div>
+  <div ref="chartRef" style="width: 100%; height: 300px"></div>
 </template>
 
 <style scoped></style>

+ 30 - 22
src/views/keyword/topSearchTermRank/index.vue → src/views/searchTerm/topSearchTermRank/index.vue

@@ -6,14 +6,14 @@
  */
 
 import { Key, PictureRounded, Refresh, Search, TopRight } from '@element-plus/icons-vue';
-import { onMounted, ref, watch } from 'vue';
+import { nextTick, onMounted, ref, watch } from 'vue';
 import dayjs from 'dayjs';
 import { useRouter } from 'vue-router';
 import { marketplaceIdEnum } from '/@/utils/marketplaceIdEnum';
 import { usePagination } from '/@/utils/usePagination';
 import { getTopSearchTermTable } from './api';
 import { ElMessage } from 'element-plus';
-import ColumnChart from '/src/views/keyword/topSearchTermRank/column-chart.vue';
+import ColumnChart from '/src/views/searchTerm/topSearchTermRank/column-chart.vue';
 
 const router = useRouter();
 
@@ -47,19 +47,27 @@ async function refreshTable() {
 async function fetchTableData() {
   tableLoading.value = true;
   const query = {
-    date_start: date.value[0],
-    date_end: date.value[1],
     page: currentPage.value,
     limit: pageSize.value,
     asin: asinInp.value,
     search_term: searchTermInp.value,
     report_type: reportTypeSelect.value,
     marketplace_Ids: marketplaceSelect.value,
+    date_start: date.value[0],
+    date_end: date.value[1],
   };
-  const response = await getTopSearchTermTable(query);
-  total.value = response.total;
-  tableData.value = response.data;
-  tableLoading.value = false;
+  try {
+    const response = await getTopSearchTermTable(query);
+    total.value = response.total;
+    tableData.value = response.data;
+  } catch (error) {
+    console.error('==Error==:', error);
+  } finally {
+    tableLoading.value = false;
+    await nextTick();
+    // 触发窗口 resize 事件
+    window.dispatchEvent(new Event('resize'));
+  }
 }
 
 async function handleSelectChange() {
@@ -71,7 +79,7 @@ async function handleQueryChange() {
     if (searchTermInp.value.length == 0) {
       return;
     } else {
-      ElMessage.warning({ message: '关键词只能输入数字和英文字母', plain: true });
+      ElMessage.warning({ message: '搜索词只能输入数字和英文字母', plain: true });
       return;
     }
   }
@@ -101,14 +109,14 @@ function validateAsinInput(input: string) {
 }
 
 function handleJump() {
-  router.push({ path: '/keyword/rootWordManage' });
+  router.push({ path: '/searchTerm/rootWordManage' });
 }
 </script>
 
 <template>
   <div class="mx-3">
     <el-divider>
-      <div class="font-bold text-lg">
+      <div class="font-bold text-xl">
         <el-icon style="top: 3px">
           <DataLine />
         </el-icon>
@@ -122,7 +130,7 @@ function handleJump() {
       <div class="flex gap-5 flex-wrap">
         <div>
           <span class="font-medium mr-0.5">市场 </span>
-          <el-select v-model="marketplaceSelect" @change="handleSelectChange" style="width: 130px">
+          <el-select v-model="marketplaceSelect" @change="handleSelectChange" style="width: 90px">
             <el-option
               v-for="item in marketplaceOptions"
               :disabled="item.disabled"
@@ -133,13 +141,13 @@ function handleJump() {
         </div>
         <div>
           <span class="font-medium mr-0.5">报告类型 </span>
-          <el-select v-model="reportTypeSelect" @change="handleSelectChange" style="width: 100px">
+          <el-select v-model="reportTypeSelect" @change="handleSelectChange" style="width: 90px">
             <el-option label="周度" value="weekly" />
             <el-option label="月度" value="monthly" />
           </el-select>
         </div>
         <div>
-          <span class="font-medium mr-0.5">关键词 </span>
+          <span class="font-medium mr-0.5">搜索词 </span>
           <el-input
             v-model="searchTermInp"
             @keyup.enter="handleQueryChange"
@@ -147,7 +155,7 @@ function handleJump() {
             placeholder="输入后回车查询"
             clearable
             @clear="handleSelectChange"
-            style="width: 300px" />
+            style="width: 240px" />
         </div>
         <div>
           <span class="font-medium mr-0.5">ASIN </span>
@@ -173,28 +181,28 @@ function handleJump() {
         </div>
       </div>
       <div class="flex">
-        <el-button type="primary" plain @click="handleJump" :icon="TopRight">关键词管理</el-button>
+        <el-button type="primary" plain @click="handleJump" :icon="TopRight">搜索词管理</el-button>
         <el-button @click="refreshTable" :icon="Refresh" circle></el-button>
       </div>
     </div>
     <!-- table -->
     <el-card shadow="never" class="mt-5">
       <div style="height: 795px; overflow: auto">
-        <el-table :data="tableData" stripe style="width: 100%">
-          <el-table-column fixed prop="searchTerm" label="关键词" width="260">
+        <el-table :data="tableData" height="795" stripe style="width: 100%">
+          <el-table-column fixed prop="searchTerm" label="搜索词" width="260">
             <template #header>
               <el-icon style="top: 2px; margin-right: 3px">
                 <Key />
               </el-icon>
-              <span>关键词</span>
+              <span>搜索词</span>
             </template>
             <template #default="{ row }">
-              <el-link :underline="false" href="https://www.bilibili.com/" target="_blank" style="color: #0b3289"
-                >{{ row.searchTerm }}
+              <el-link :underline="false" href="https://www.bilibili.com/" target="_blank" style="color: #0b3289">
+                {{ row.searchTerm }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="rank" label="关键词搜索排名" align="center">
+          <el-table-column prop="rank" label="搜索词搜索排名" align="center">
             <template #header>
               <el-icon style="top: 2px; margin-right: 4px">
                 <PictureRounded />

+ 23 - 0
src/views/searchTerm/topSearchTermTable/api.ts

@@ -0,0 +1,23 @@
+import { request } from '/@/utils/service';
+
+const apiPrefix = '/api/searchterm/';
+
+export function getTopSearchTermTable(query: any) {
+  return request({
+    url: apiPrefix + 'topsearchtermTable/',
+    method: 'GET',
+    params: query,
+  });
+}
+
+export function postDownload(body: any) {
+  return request({
+    url: apiPrefix + 'topsearchtermTable/',
+    method: 'POST',
+    params: body,
+    responseType: 'blob',
+    headers: {
+      'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' // 指定接受 Excel 文件
+    }
+  });
+}

+ 175 - 58
src/views/keyword/topSearchTermTable/index.vue → src/views/searchTerm/topSearchTermTable/index.vue

@@ -1,51 +1,70 @@
 <script setup lang="ts">
 /**
  * @Name: index.vue
- * @Description: 关键词-TopSearchTerm Table
+ * @Description: 搜索词-TopSearchTerm Table
  * @Author: Cheney
  */
 
-import { onMounted, ref, watch } from 'vue';
+import { nextTick, onBeforeMount, onMounted, ref, watch } from 'vue';
 import { usePagination } from '/@/utils/usePagination';
-import { getTopSearchTermTable } from './api';
+import { getTopSearchTermTable, postDownload } from './api';
 import { marketplaceIdEnum } from '/@/utils/marketplaceIdEnum';
-import {
-  Download,
-  Goods,
-  Key,
-  Medal,
-  Memo,
-  Pointer,
-  Rank,
-  Reading,
-  Refresh,
-  Search,
-  Switch,
-  TopRight,
-} from '@element-plus/icons-vue';
+import { Download, Goods, Key, Medal, Pointer, Rank, Refresh, Search, Switch, TopRight } from '@element-plus/icons-vue';
 import { useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import dayjs from 'dayjs';
+import enLocale from 'element-plus/es/locale/lang/en';
 
 const router = useRouter();
 
 const { tableData, total, currentPage, pageSize, handlePageChange } = usePagination(fetchTableData);
-const date = ref([dayjs().subtract(7, 'day').format('YYYY-MM-DD'), dayjs().subtract(1, 'day').format('YYYY-MM-DD')]);
+
 const marketplaceSelect = ref(marketplaceIdEnum[0].value); // 当前只有美国区 默认第一个为美国
 const marketplaceOptions = marketplaceIdEnum;
 const reportTypeSelect = ref('weekly');
 const searchTermInp = ref('');
 const asinInp = ref('');
 const tableLoading = ref(false);
+const downloadLoading = ref(false);
+const date = ref(calculateLastWeek()); // 设置默认日期为上周的周日到周六
+const dateDimension = ref(date.value[0]);
+
 
-onMounted(() => {
+onBeforeMount(() => {
   fetchTableData();
 });
 
-watch(date, () => {
+watch(dateDimension, () => {
+  calculateDate();
+  // console.log('==Date==', date.value[0], date.value[1]);
   fetchTableData();
 });
 
+function calculateDate() {
+  if (reportTypeSelect.value === 'weekly') {
+    date.value[0] = dateDimension.value;
+    date.value[1] = calculateEndDate(dateDimension.value);
+  } else if (reportTypeSelect.value === 'monthly') {
+    const selectedMonth = dayjs(dateDimension.value);
+    date.value[0] = selectedMonth.startOf('month').format('YYYY-MM-DD');
+    date.value[1] = selectedMonth.endOf('month').format('YYYY-MM-DD');
+  }
+}
+
+/**
+ * 计算上周的周日到周六的日期范围
+ */
+function calculateLastWeek() {
+  const today = dayjs();
+  const lastSaturday = today.subtract(today.day() + 1, 'day'); // 上周六
+  const lastSunday = lastSaturday.subtract(6, 'day'); // 上周日
+  return [lastSunday.format('YYYY-MM-DD'), lastSaturday.format('YYYY-MM-DD')];
+}
+
+function calculateEndDate(startDate: string) {
+  return dayjs(startDate).add(6, 'day').format('YYYY-MM-DD');
+}
+
 async function refreshTable() {
   currentPage.value = 1;
   pageSize.value = 10;
@@ -68,13 +87,22 @@ async function fetchTableData() {
     date_start: date.value[0],
     date_end: date.value[1],
   };
-  const response = await getTopSearchTermTable(query);
-  total.value = response.total;
-  tableData.value = response.data;
-  tableLoading.value = false;
+  try {
+    const response = await getTopSearchTermTable(query);
+    total.value = response.total;
+    tableData.value = response.data;
+  } catch (error) {
+    console.error('==Error==:', error);
+  } finally {
+    tableLoading.value = false;
+    await nextTick();
+    // 触发窗口 resize 事件
+    window.dispatchEvent(new Event('resize'));
+  }
 }
 
 async function handleSelectChange() {
+  calculateDate();
   await fetchTableData();
 }
 
@@ -83,7 +111,7 @@ async function handleQueryChange() {
     if (searchTermInp.value.length == 0) {
       return;
     } else {
-      ElMessage.warning({ message: '关键词只能输入数字和英文字母', plain: true });
+      ElMessage.warning({ message: '搜索词只能输入数字和英文字母', plain: true });
       return;
     }
   }
@@ -114,7 +142,53 @@ function validateAsinInput(input: string) {
 
 function handleJump() {
   // console.log('All defined routes:', router.getRoutes());
-  router.push({ path: '/keyword/rootWordManage' });
+  router.push({ path: '/searchTerm/rootWordManage' });
+}
+
+async function handleDownload() {
+  downloadLoading.value = true;
+  try {
+    const body = {
+      asin: asinInp.value,
+      date_start: date.value[0],
+      date_end: date.value[1],
+      search_term: searchTermInp.value,
+      marketplace_Ids: marketplaceSelect.value,
+      report_type: reportTypeSelect.value,
+    };
+
+    const response = await postDownload(body);
+
+    const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+
+    // 创建一个临时 URL
+    const url = window.URL.createObjectURL(blob);
+
+    // 创建一个临时的 <a> 元素并触发下载
+    const link = document.createElement('a');
+    link.href = url;
+
+    // 设置文件名
+    const currentTime = dayjs().format('YYYY-MM-DD_HH_mm_ss');
+    const filename = `TopSearchTerm_${currentTime}.xlsx`;
+
+    link.setAttribute('download', filename);
+
+    // 添加到 body, 触发点击, 然后移除
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+
+    // 释放 URL 对象
+    window.URL.revokeObjectURL(url);
+
+    ElMessage.success('文件下载成功');
+  } catch (error) {
+    console.error('==Error==:', error);
+    ElMessage.error('文件下载失败,请重试');
+  } finally {
+    downloadLoading.value = false;
+  }
 }
 
 function getTagStyle(clickShareRank: number): Record<string, string> {
@@ -134,7 +208,7 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
 <template>
   <div class="mx-3">
     <el-divider>
-      <div class="font-bold text-lg">
+      <div class="font-bold text-xl">
         <el-icon style="top: 3px">
           <DataAnalysis />
         </el-icon>
@@ -148,7 +222,7 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
       <div class="flex gap-5 flex-wrap">
         <div>
           <span class="font-medium mr-0.5">市场 </span>
-          <el-select v-model="marketplaceSelect" @change="handleSelectChange" style="width: 130px">
+          <el-select v-model="marketplaceSelect" @change="handleSelectChange" style="width: 90px">
             <el-option
               v-for="item in marketplaceOptions"
               :disabled="item.disabled"
@@ -159,13 +233,13 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
         </div>
         <div>
           <span class="font-medium mr-0.5">报告类型 </span>
-          <el-select v-model="reportTypeSelect" @change="handleSelectChange" style="width: 100px">
+          <el-select v-model="reportTypeSelect" @change="handleSelectChange" style="width: 90px">
             <el-option label="周度" value="weekly" />
             <el-option label="月度" value="monthly" />
           </el-select>
         </div>
         <div>
-          <span class="font-medium mr-0.5">关键词 </span>
+          <span class="font-medium mr-0.5">搜索词 </span>
           <el-input
             v-model="searchTermInp"
             @keyup.enter="handleQueryChange"
@@ -173,7 +247,7 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
             placeholder="输入后回车查询"
             clearable
             @clear="handleSelectChange"
-            style="width: 300px" />
+            style="width: 240px" />
         </div>
         <div>
           <span class="font-medium mr-0.5">ASIN </span>
@@ -188,34 +262,58 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
         </div>
         <div>
           <span class="font-medium mr-0.5">报告日期 </span>
-          <el-date-picker
-            v-model="date"
-            type="daterange"
-            value-format="YYYY-MM-DD"
-            :popper-options="{ placement: 'bottom-end' }"
-            :clearable="false"
-            :disabled-date="(time: Date) => time > new Date()"
-            range-separator="至"
-            start-placeholder="开始日期"
-            end-placeholder="结束日期" />
+
+          <el-config-provider :locale="enLocale">
+            <el-date-picker
+              v-if="reportTypeSelect === 'weekly'"
+              v-model="dateDimension"
+              type="week"
+              value-format="YYYY-MM-DD"
+              :format="`${date[0]} To ${date[1]}`"
+              :popper-options="{ placement: 'bottom-end' }"
+              :disabled-date="(time: Date) => time > new Date()"
+              :clearable="false" />
+            <el-date-picker
+              v-else
+              v-model="dateDimension"
+              type="month"
+              value-format="YYYY-MM"
+              :format="`${date[0]} To ${date[1]}`"
+              :popper-options="{ placement: 'bottom-end' }"
+              :disabled-date="(time: Date) => time > new Date()"
+              :clearable="false">
+              <template #default>
+                123
+              </template>
+            </el-date-picker>
+          </el-config-provider>
         </div>
       </div>
       <div class="flex">
-        <el-button type="primary" plain @click="handleJump" :icon="TopRight">关键词管理</el-button>
-        <el-button type="success" plain round :icon="Download">下载表格</el-button>
+        <el-button type="primary" plain @click="handleJump" :icon="TopRight">搜索词管理</el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleDownload"
+          :icon="Download"
+          round
+          :loading="downloadLoading"
+          :disabled="!tableData.length"
+          >下载表格
+        </el-button>
         <el-button @click="refreshTable" :icon="Refresh" circle></el-button>
       </div>
     </div>
     <!-- table -->
     <el-card shadow="never" class="mt-5">
       <div style="height: 795px; overflow: auto">
-        <el-table :data="tableData" stripe style="width: 100%">
-          <el-table-column fixed prop="searchTerm" label="关键词" width="260">
+        <el-table :data="tableData" height="795" stripe style="width: 100%">
+          <el-table-column fixed prop="searchTerm" label="搜索词" width="260">
             <template #header>
               <el-icon style="top: 2px; margin-right: 3px">
                 <Key />
               </el-icon>
-              <span>关键词</span>
+              <span>搜索词</span>
             </template>
             <template #default="{ row }">
               <el-link :underline="false" href="https://www.bilibili.com/" target="_blank" style="color: #0b3289"
@@ -223,18 +321,18 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="searchFrequencyRank" label="关键词搜索排名" align="center" width="150">
+          <el-table-column prop="searchFrequencyRank" label="搜索词搜索排名" align="center" width="150">
             <template #header>
               <el-icon style="top: 2px; margin-right: 4px">
                 <Rank />
               </el-icon>
-              <span>关键词搜索排名</span>
+              <span>搜索词搜索排名</span>
             </template>
             <template #default="{ row }">
               <span class="font-medium">{{ row.searchFrequencyRank }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="clickedAsin" align="center" label="Asin">
+          <el-table-column prop="clickedAsin" label="Asin" align="center">
             <template #header>
               <el-icon style="top: 2px; margin-right: 5px">
                 <Goods />
@@ -242,17 +340,25 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
               <span>Asin</span>
             </template>
             <template #default="{ row }">
-              <span class="font-medium">{{ row.clickedAsin }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column prop="clickedItemName" label="标题">
-            <template #header>
-              <el-icon style="top: 2px; margin-right: 5px">
-                <Reading />
-              </el-icon>
-              <span>标题</span>
+              <div class="font-medium" style="color: black">{{ row.clickedAsin }}</div>
+              <div class="text-sm text-left">
+                <el-tooltip class="box-item" effect="dark" :content="row.clickedItemName" placement="top-start">
+                  <div class="tooltip-text">
+                    <span class="font-medium mr-1">Title:</span>
+                    {{ row.clickedItemName }}
+                  </div>
+                </el-tooltip>
+              </div>
             </template>
           </el-table-column>
+          <!--<el-table-column prop="clickedItemName" label="标题">-->
+          <!--  <template #header>-->
+          <!--    <el-icon style="top: 2px; margin-right: 5px">-->
+          <!--      <Reading />-->
+          <!--    </el-icon>-->
+          <!--    <span>标题</span>-->
+          <!--  </template>-->
+          <!--</el-table-column>-->
           <el-table-column prop="clickShareRank" label="点击分享率排名" align="center" width="150">
             <template #header>
               <el-icon style="top: 2px; margin-right: 4px">
@@ -308,4 +414,15 @@ function getTagStyle(clickShareRank: number): Record<string, string> {
 :deep(.el-divider__text.is-center.el-divider__text) {
   background-color: #f8f8f8;
 }
+
+.tooltip-text {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: normal;
+  line-height: 1.2em;
+  max-height: 2.4em;
+}
 </style>