浏览代码

✨ feat<Asin View & Brand View>: 新增Asin View 和 Brand View 页面;
全局layout布局中parent.vue中添加class="flex-1",避免高度错误;

WanGxC 10 月之前
父节点
当前提交
15fe195cf5

+ 1 - 1
src/layout/routerView/parent.vue

@@ -3,7 +3,7 @@
 		<router-view v-slot="{ Component }">
 			<transition :name="setTransitionName" mode="out-in">
 				<keep-alive :include="getKeepAliveNames" v-if="showView">
-					<div>
+					<div class="flex-1"> <!-- flex-1 避免 layout main 的内容不能全屏 -->
 						<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
 					</div>
 				</keep-alive>

+ 24 - 0
src/views/searchTerm/asinView/api.ts

@@ -0,0 +1,24 @@
+import { request } from '/@/utils/service';
+
+const apiPrefix = '/api/searchterm/';
+
+
+export function getTableData(query: any) {
+  return request({
+    url: apiPrefix + 'asinview/',
+    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 文件
+    }
+  });
+}

+ 166 - 137
src/views/searchTerm/asinView/index.vue

@@ -6,98 +6,100 @@
  */
 
 import { Download, Refresh, Search } from '@element-plus/icons-vue';
-import enLocale from 'element-plus/es/locale/lang/en';
-import { usePagination } from '/@/utils/usePagination';
-import { nextTick, ref } from 'vue';
-import { marketplaceIdEnum } from '/@/utils/marketplaceIdEnum';
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
 import dayjs from 'dayjs';
-import { getTopSearchTermTable, postDownload } from '/@/views/searchTerm/topSearchTermTable/api';
+import { getTableData, postDownload } from './api';
 import { ElMessage } from 'element-plus';
+import { asinColumns } from './useColumns';
+import { brandColumns } from '/@/views/searchTerm/brandView/useColumns';
 
-const { tableData, total, currentPage, pageSize, handlePageChange } = usePagination(fetchTableData);
-const marketplaceSelect = ref(marketplaceIdEnum[0].value); // 当前只有美国区 默认第一个为美国
-const marketplaceOptions = marketplaceIdEnum;
-const reportTypeSelect = ref('weekly');
+const reportTypeSelect = ref('monthly');
 const searchTermInp = ref('');
-const asinInp = ref('');
+const asinInp = ref('B0');
 const tableLoading = ref(false);
 const downloadLoading = ref(false);
-const date = ref(calculateLastWeek()); // 设置默认日期为上周的周日到周六
-const dateDimension = ref(date.value[0]);
+const date = ref(calculateLastWeek());
 
-/**
- * 判断当前时间维度 并 计算起始和结束日期
- */
-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');
-  }
-}
+const gridOptions: any = reactive({
+  height: 'auto',
+  border: false,
+  round: true,
+  columnConfig: {
+    resizable: true,
+  },
+  toolbarConfig: {
+    custom: true,
+    slots: {
+      buttons: 'toolbar_buttons',
+    },
+  },
+  columns: asinColumns,
+  data: [],
+});
 
-/**
- * 计算上周的周日到周六的日期范围
- */
-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')];
+const tablePage = reactive({
+  total: 0,
+  currentPage: 1,
+  pageSize: 20,
+});
+
+onBeforeMount(() => {
+  fetchTableData();
+});
+
+watch(date, () => {
+  fetchTableData();
+});
+
+async function handlePageChange({ currentPage, pageSize }) {
+  tablePage.currentPage = currentPage;
+  tablePage.pageSize = pageSize;
+  await fetchTableData();
 }
 
-/**
- * 计算结束日期
- * @param startDate el-date-picker组件的value
- */
-function calculateEndDate(startDate: string) {
-  return dayjs(startDate).add(6, 'day').format('YYYY-MM-DD');
+async function handleSelectChange() {
+  if (!asinInp.value) {
+    ElMessage.warning({ message: '请输入ASIN', plain: true });
+    return;
+  } else {
+    await fetchTableData();
+  }
+
 }
 
 async function refreshTable() {
-  currentPage.value = 1;
-  pageSize.value = 20;
+  tablePage.currentPage = 1;
+  tablePage.pageSize = 20;
   asinInp.value = '';
   searchTermInp.value = '';
   reportTypeSelect.value = 'weekly';
-  marketplaceSelect.value = marketplaceIdEnum[0].value;
+  // marketplaceSelect.value = marketplaceIdEnum[0].value;
   await fetchTableData();
 }
 
 async function fetchTableData() {
   tableLoading.value = true;
   const query = {
-    page: currentPage.value,
-    limit: pageSize.value,
+    page: tablePage.currentPage,
+    limit: tablePage.pageSize,
     asin: asinInp.value,
     search_term: searchTermInp.value,
     report_type: reportTypeSelect.value,
-    marketplace_Ids: marketplaceSelect.value,
+    // marketplace_Ids: marketplaceSelect.value,
     date_start: date.value[0],
     date_end: date.value[1],
   };
   try {
-    const response = await getTopSearchTermTable(query);
-    total.value = response.total;
-    tableData.value = response.data;
+    const response = await getTableData(query);
+    tablePage.total = response.total;
+    gridOptions.data = 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();
-}
-
 /**
  * 输入框按下回车后触发
  */
@@ -135,56 +137,66 @@ function validateAsinInput(input: string) {
   return regex.test(input);
 }
 
-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 calculateLastWeek() {
+  const today = dayjs();
+  const lastDay = today.subtract(1, 'day'); // 昨天
+  const firstDay = lastDay.subtract(6, 'day'); // 一周前
+  return [firstDay.format('YYYY-MM-DD'), lastDay.format('YYYY-MM-DD')];
 }
+
+// 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;
+//   }
+// }
 </script>
 
 <template>
-  <div class="py-2 px-2.5">
-    <el-card class="mb-2.5">
+  <div class="py-2 px-2.5" style="background-color: #f7f7f7">
+    <el-card shadow="hover" class="mb-2.5" style="border: none; margin-bottom: 10px">
       <div class="flex justify-between">
         <div class="flex gap-5 flex-wrap">
           <div>
@@ -218,49 +230,66 @@ async function handleDownload() {
           </div>
           <div>
             <span class="font-medium mr-0.5">报告日期 </span>
-            <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>
+            <el-date-picker
+              v-model="date"
+              type="daterange"
+              value-format="YYYY-MM-DD"
+              range-separator="To"
+              :disabled-date="(time: Date) => time > new Date()"
+              :popper-options="{ placement: 'bottom-end' }"
+              :clearable="false" />
           </div>
         </div>
         <div class="flex">
-          <el-button
-            type="success"
-            plain
-            @click="handleDownload"
-            :icon="Download"
-            round
-            :loading="downloadLoading"
-            :disabled="!tableData.length"
-            >下载表格
-          </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>
     </el-card>
-    <el-card>
-      <el-table></el-table>
+    <el-card shadow="hover" style="border: none">
+      <div style="overflow: hidden; width: 100%; height: 950px" v-loading="tableLoading">
+        <vxe-grid v-bind="gridOptions">
+          <template #toolbar_buttons></template>
+          <template v-for="col in asinColumns" #[`${col.field}_default`]="{ row }">
+            <div v-if="col.field === 'clickedItemName'">
+              <el-tooltip effect="dark" :content="row.clickedItemName" placement="top" :show-after="300" >
+                <div class="line-text font-medium">
+                  {{ row.clickedItemName }}
+                </div>
+              </el-tooltip>
+            </div>
+            <div v-else class="font-medium">
+              {{ row[col.field] ? row[col.field] : '-' }}
+            </div>
+          </template>
+          <template #pager>
+            <vxe-pager
+              :layouts="['Sizes', 'PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'FullJump', 'Total']"
+              v-model:current-page="tablePage.currentPage"
+              v-model:page-size="tablePage.pageSize"
+              :total="tablePage.total"
+              @page-change="handlePageChange">
+            </vxe-pager>
+          </template>
+        </vxe-grid>
+      </div>
     </el-card>
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.line-text {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>

+ 307 - 0
src/views/searchTerm/asinView/useColumns.ts

@@ -0,0 +1,307 @@
+export const asinColumns = [
+  {
+    field: 'Search_Query',
+    title: '搜索词',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Search_Query_default' }
+  },
+  {
+    field: 'Search_Query_Score',
+    title: '综合得分',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'Search_Query_Score_default' }
+  },
+  {
+    field: 'Search_Query_Volume',
+    title: '查询量',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'Search_Query_Volume_default' }
+  },
+  {
+    field: 'Impressions_Total_Count',
+    title: '关键词曝光量',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Impressions_Total_Count_default' }
+  },
+  {
+    field: 'Impressions_ASIN_Count',
+    title: '该asin的关键词曝光量',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Impressions_ASIN_Count_default' }
+  },
+  {
+    field: 'Impressions_ASIN_Share',
+    title: '该asin曝光占比',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Impressions_ASIN_Share_default' }
+  },
+  {
+    field: 'Clicks_Total_Count',
+    title: '点击总数',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'Clicks_Total_Count_default' }
+  },
+  {
+    field: 'Clicks_Click_Rate',
+    title: '点击率',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'Clicks_Click_Rate_default' }
+  },
+  {
+    field: 'Clicks_ASIN_Count',
+    title: '该asin此关键词的点击量',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Clicks_ASIN_Count_default' }
+  },
+  {
+    field: 'Clicks_ASIN_Share',
+    title: '该asin点击占比',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Clicks_ASIN_Share_default' }
+  },
+  {
+    field: 'Clicks_Price_Median',
+    title: '点击价格中位数',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Clicks_Price_Median_default' }
+  },
+  {
+    field: 'Clicks_ASIN_Price_Median',
+    title: '该asin点击价格中位数',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Clicks_ASIN_Price_Median_default' }
+  },
+  {
+    field: 'Clicks_Same_Day_Shipping_Speed',
+    title: '点击同日发货速度',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Clicks_Same_Day_Shipping_Speed_default' }
+  },
+  {
+    field: 'Clicks_1D_Shipping_Speed',
+    title: '点击隔日发货速度',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Clicks_1D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Clicks_2D_Shipping_Speed',
+    title: '点击两日内发货速度',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Clicks_2D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Cart_Adds_Total_Count',
+    title: '加入购物车总数',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Cart_Adds_Total_Count_default' }
+  },
+  {
+    field: 'Cart_Adds_Cart_Add_Rate',
+    title: '加购率',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'Cart_Adds_Cart_Add_Rate_default' }
+  },
+  {
+    field: 'Cart_Adds_ASIN_Count',
+    title: '此asin该关键词的加购数量',
+    minWidth: 230,
+    sortable: true,
+    slots: { default: 'Cart_Adds_ASIN_Count_default' }
+  },
+  {
+    field: 'Cart_Adds_ASIN_Share',
+    title: '此asin的关键词加购数站总加购的百分比',
+    minWidth: 300,
+    sortable: true,
+    slots: { default: 'Cart_Adds_ASIN_Share_default' }
+  },
+  {
+    field: 'Cart_Adds_Price_Median',
+    title: '加入购物车价格中位数',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Cart_Adds_Price_Median_default' }
+  },
+  {
+    field: 'Cart_Adds_ASIN_Price_Median',
+    title: '此asin下关键词的产品平均价格',
+    minWidth: 240,
+    sortable: true,
+    slots: { default: 'Cart_Adds_ASIN_Price_Median_default' }
+  },
+  {
+    field: 'Cart_Adds_Same_Day_Shipping_Speed',
+    title: '同日发货速度',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Cart_Adds_Same_Day_Shipping_Speed_default' }
+  },
+  {
+    field: 'Cart_Adds_1D_Shipping_Speed',
+    title: '隔日发货速度',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Cart_Adds_1D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Cart_Adds_2D_Shipping_Speed',
+    title: '两天内发货速度',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Cart_Adds_2D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Purchases_Total_Count',
+    title: '购买总数',
+    minWidth: 100,
+    sortable: true,
+    slots: { default: 'Purchases_Total_Count_default' }
+  },
+  {
+    field: 'Purchases_Purchase_Rate',
+    title: '购买率',
+    minWidth: 100,
+    sortable: true,
+    slots: { default: 'Purchases_Purchase_Rate_default' }
+  },
+  {
+    field: 'Purchases_ASIN_Count',
+    title: '此asin下关键词的购买数',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Purchases_ASIN_Count_default' }
+  },
+  {
+    field: 'Purchases_ASIN_Share',
+    title: '此asin该关键词的购买占比',
+    minWidth: 230,
+    sortable: true,
+    slots: { default: 'Purchases_ASIN_Share_default' }
+  },
+  {
+    field: 'Purchases_Price_Median',
+    title: '购买价格中位数',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Purchases_Price_Median_default' }
+  },
+  {
+    field: 'Purchases_ASIN_Price_Median',
+    title: '此asin购买价格中位数',
+    minWidth: 200,
+    sortable: true,
+    slots: { default: 'Purchases_ASIN_Price_Median_default' }
+  },
+  {
+    field: 'Purchases_Same_Day_Shipping_Speed',
+    title: '同日配送速度购买数',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Purchases_Same_Day_Shipping_Speed_default' }
+  },
+  {
+    field: 'Purchases_1D_Shipping_Speed',
+    title: '隔日发货速度',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Purchases_1D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Purchases_2D_Shipping_Speed',
+    title: '2天内发货速度',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'Purchases_2D_Shipping_Speed_default' }
+  },
+  {
+    field: 'Reporting_Date',
+    title: '报告日期',
+    minWidth: 180,
+    sortable: true,
+    slots: { default: 'Reporting_Date_default' }
+  },
+  {
+    field: 'ASIN',
+    title: 'ASIN',
+    minWidth: 120,
+    slots: { default: 'ASIN_default' }
+  },
+  {
+    field: 'departmentName',
+    title: '站点名',
+    minWidth: 150,
+    slots: { default: 'departmentName_default' }
+  },
+  {
+    field: 'searchTerm',
+    title: '搜索词',
+    minWidth: 120,
+    slots: { default: 'searchTerm_default' }
+  },
+  {
+    field: 'searchFrequencyRank',
+    title: '搜索频率排名',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'searchFrequencyRank_default' }
+  },
+  {
+    field: 'clickedAsin',
+    title: '点击ASIN',
+    minWidth: 120,
+    sortable: true,
+    slots: { default: 'clickedAsin_default' }
+  },
+  {
+    field: 'clickedItemName',
+    title: '点击商品名称',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'clickedItemName_default' }
+  },
+  {
+    field: 'clickShareRank',
+    title: '点击分享排名',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'clickShareRank_default' }
+  },
+  {
+    field: 'clickShare',
+    title: '点击分享率',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'clickShare_default' }
+  },
+  {
+    field: 'conversionShare',
+    title: '转化分享率',
+    minWidth: 150,
+    sortable: true,
+    slots: { default: 'conversionShare_default' }
+  },
+
+  {
+    field: 'marketplaceIds',
+    title: '市场ID',
+    minWidth: 180,
+    slots: { default: 'marketplaceIds_default' }
+  }
+];

+ 24 - 0
src/views/searchTerm/brandView/api.ts

@@ -0,0 +1,24 @@
+import { request } from '/@/utils/service';
+
+const apiPrefix = '/api/searchterm/';
+
+
+export function getTableData(query: any) {
+  return request({
+    url: apiPrefix + 'brandview/',
+    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 文件
+    }
+  });
+}

+ 276 - 2
src/views/searchTerm/brandView/index.vue

@@ -4,12 +4,286 @@
  * @Description: brandView
  * @Author: Cheney
  */
+
+import { Download, Refresh, Search } from '@element-plus/icons-vue';
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
+import dayjs from 'dayjs';
+import { getTableData, postDownload } from './api';
+import { ElMessage } from 'element-plus';
+import { brandColumns } from './useColumns';
+import { universalColumns } from '/@/views/productCenter/productAnalysis/utils/columns';
+
+const reportTypeSelect = ref('weekly');
+const searchTermInp = ref('zosi');
+// const asinInp = ref('B0');
+const tableLoading = ref(false);
+const downloadLoading = ref(false);
+const date = ref(calculateLastWeek());
+
+const gridOptions: any = reactive({
+  height: 'auto',
+  border: false,
+  round: true,
+  columnConfig: {
+    resizable: true,
+  },
+  toolbarConfig: {
+    custom: true,
+    slots: {
+      buttons: 'toolbar_buttons',
+    },
+  },
+  columns: brandColumns,
+  data: [],
+});
+
+const tablePage = reactive({
+  total: 0,
+  currentPage: 1,
+  pageSize: 20,
+});
+
+onBeforeMount(() => {
+  fetchTableData();
+});
+
+watch(date, () => {
+  fetchTableData();
+});
+
+async function handlePageChange({ currentPage, pageSize }) {
+  tablePage.currentPage = currentPage;
+  tablePage.pageSize = pageSize;
+  await fetchTableData();
+}
+
+async function handleSelectChange() {
+  await fetchTableData();
+}
+
+async function refreshTable() {
+  tablePage.currentPage = 1;
+  tablePage.pageSize = 20;
+  // asinInp.value = '';
+  searchTermInp.value = '';
+  reportTypeSelect.value = 'weekly';
+  // marketplaceSelect.value = marketplaceIdEnum[0].value;
+  await fetchTableData();
+}
+
+async function fetchTableData() {
+  tableLoading.value = true;
+  const query = {
+    page: tablePage.currentPage,
+    limit: tablePage.pageSize,
+    // 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],
+  };
+  try {
+    const response = await getTableData(query);
+    tablePage.total = response.total;
+    gridOptions.data = response.data;
+  } catch (error) {
+    console.error('==Error==:', error);
+  } finally {
+    tableLoading.value = false;
+  }
+}
+
+/**
+ * 输入框按下回车后触发
+ */
+async function handleQueryChange() {
+  if (!validateSearchTermInput(searchTermInp.value)) {
+    if (searchTermInp.value.length == 0) {
+      return;
+    } else {
+      ElMessage.warning({ message: '搜索词只能输入数字和英文字母', plain: true });
+      return;
+    }
+  }
+  // if (asinInp.value.length > 0 && !validateAsinInput(asinInp.value)) {
+  //   ElMessage.warning({ message: '不符合匹配规范', plain: true });
+  //   return;
+  // }
+  await fetchTableData();
+}
+
+/**
+ * 校验SearchTerm输入是否合法
+ * @param input 输入的字符串
+ */
+function validateSearchTermInput(input: string) {
+  const regex = /^[a-zA-Z0-9\s]*$/;
+  return regex.test(input);
+}
+
+/**
+ * 校验Asin输入是否合法
+ * @param input 输入的字符串
+ */
+function validateAsinInput(input: string) {
+  const regex = /^[Bb]0[A-Za-z0-9\s]*$/i;
+  return regex.test(input);
+}
+
+/**
+ * 计算上周的周日到周六的日期范围
+ */
+function calculateLastWeek() {
+  const today = dayjs();
+  const lastDay = today.subtract(1, 'day'); // 昨天
+  const firstDay = lastDay.subtract(6, 'day'); // 一周前
+  return [firstDay.format('YYYY-MM-DD'), lastDay.format('YYYY-MM-DD')];
+}
+
+// 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;
+//   }
+// }
 </script>
 
 <template>
-
+  <div class="py-2 px-2.5" style="background-color: #f7f7f7">
+    <el-card shadow="hover" class="mb-2.5" style="border: none; margin-bottom: 10px">
+      <div class="flex justify-between">
+        <div class="flex gap-5 flex-wrap">
+          <div>
+            <span class="font-medium mr-0.5">报告类型 </span>
+            <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>
+            <el-input
+              v-model="searchTermInp"
+              @keyup.enter="handleQueryChange"
+              :prefix-icon="Search"
+              placeholder="输入后回车查询"
+              clearable
+              @clear="handleSelectChange"
+              style="width: 240px" />
+          </div>
+          <!--<div>-->
+          <!--  <span class="font-medium mr-0.5">ASIN </span>-->
+          <!--  <el-input-->
+          <!--      v-model="asinInp"-->
+          <!--      @keyup.enter="handleQueryChange"-->
+          <!--      :prefix-icon="Search"-->
+          <!--      placeholder="输入后回车查询"-->
+          <!--      clearable-->
+          <!--      @clear="handleSelectChange"-->
+          <!--      style="width: 180px" />-->
+          <!--</div>-->
+          <div>
+            <span class="font-medium mr-0.5">报告日期 </span>
+            <el-date-picker
+              v-model="date"
+              type="daterange"
+              value-format="YYYY-MM-DD"
+              range-separator="To"
+              :disabled-date="(time: Date) => time > new Date()"
+              :popper-options="{ placement: 'bottom-end' }"
+              :clearable="false" />
+          </div>
+        </div>
+        <div class="flex">
+          <!--<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>
+    </el-card>
+    <el-card shadow="hover" style="border: none">
+      <div style="overflow: hidden; width: 100%; height: 950px" v-loading="tableLoading">
+        <vxe-grid v-bind="gridOptions">
+          <template #toolbar_buttons></template>
+          <template v-for="col in brandColumns" #[`${col.field}_default`]="{ row }">
+            <div v-if="col.field === 'clickedItemName'">
+              <el-tooltip effect="dark" :content="row.clickedItemName" placement="top" :show-after="300" >
+                <div class="line-text font-medium">
+                  {{ row.clickedItemName }}
+                </div>
+              </el-tooltip>
+            </div>
+            <div v-else class="font-medium">
+              {{ row[col.field] ? row[col.field] : '-' }}
+            </div>
+          </template>
+          <template #pager>
+            <vxe-pager
+              :layouts="['Sizes', 'PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'FullJump', 'Total']"
+              v-model:current-page="tablePage.currentPage"
+              v-model:page-size="tablePage.pageSize"
+              :total="tablePage.total"
+              @page-change="handlePageChange">
+            </vxe-pager>
+          </template>
+        </vxe-grid>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <style scoped>
-
+.line-text {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
 </style>

+ 260 - 0
src/views/searchTerm/brandView/useColumns.ts

@@ -0,0 +1,260 @@
+export const brandColumns = [
+  {
+    field: 'Search_Query',
+    title: '搜索词',
+    minWidth: 130,
+    slots: { default: 'Search_Query_default' },
+  },
+  {
+    field: 'Search_Query_Score',
+    title: '综合得分',
+    minWidth: 130,
+    slots: { default: 'Search_Query_Score_default' },
+  },
+  {
+    field: 'Search_Query_Volume',
+    title: '查询量',
+    minWidth: 130,
+    slots: { default: 'Search_Query_Volume_default' },
+  },
+  {
+    field: 'Impressions_Total_Count',
+    title: '关键词曝光量',
+    minWidth: 130,
+    slots: { default: 'Impressions_Total_Count_default' },
+  },
+  {
+    field: 'Impressions_ASIN_Count',
+    title: '该asin的关键词曝光量',
+    minWidth: 180,
+    slots: { default: 'Impressions_ASIN_Count_default' },
+  },
+  {
+    field: 'Impressions_ASIN_Share',
+    title: '该asin曝光占比',
+    minWidth: 130,
+    slots: { default: 'Impressions_ASIN_Share_default' },
+  },
+  {
+    field: 'Clicks_Total_Count',
+    title: '此关键词的点击量',
+    minWidth: 150,
+    slots: { default: 'Clicks_Total_Count_default' },
+  },
+  {
+    field: 'Clicks_Click_Rate',
+    title: '点击率',
+    minWidth: 100,
+    slots: { default: 'Clicks_Click_Rate_default' },
+  },
+  {
+    field: 'Clicks_ASIN_Count',
+    title: '该asin此关键词的点击量',
+    minWidth: 180,
+    slots: { default: 'Clicks_ASIN_Count_default' },
+  },
+  {
+    field: 'Clicks_ASIN_Share',
+    title: '该asin点击占比',
+    minWidth: 130,
+    slots: { default: 'Clicks_ASIN_Share_default' },
+  },
+  {
+    field: 'Clicks_Price_Median',
+    title: '该关键词的平均价格',
+    minWidth: 150,
+    slots: { default: 'Clicks_Price_Median_default' },
+  },
+  {
+    field: 'Clicks_ASIN_Price_Median',
+    title: '该asin此关键词的价格',
+    minWidth: 170,
+    slots: { default: 'Clicks_ASIN_Price_Median_default' },
+  },
+  {
+    field: 'Clicks_Same_Day_Shipping_Speed',
+    title: '点击同一天发货速度',
+    minWidth: 150,
+    slots: { default: 'Clicks_Same_Day_Shipping_Speed_default' },
+  },
+  {
+    field: 'Clicks_1D_Shipping_Speed',
+    title: '点击隔日发货速度',
+    minWidth: 150,
+    slots: { default: 'Clicks_1D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Clicks_2D_Shipping_Speed',
+    title: '点击2天内发货速度',
+    minWidth: 150,
+    slots: { default: 'Clicks_2D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Cart_Adds_Total_Count',
+    title: '该关键词加购数量',
+    minWidth: 150,
+    slots: { default: 'Cart_Adds_Total_Count_default' },
+  },
+  {
+    field: 'Cart_Adds_Cart_Add_Rate',
+    title: '加购率',
+    minWidth: 100,
+    slots: { default: 'Cart_Adds_Cart_Add_Rate_default' },
+  },
+  {
+    field: 'Cart_Adds_ASIN_Count',
+    title: '此asin该关键词的加购数量',
+    minWidth: 200,
+    slots: { default: 'Cart_Adds_ASIN_Count_default' },
+  },
+  {
+    field: 'Cart_Adds_ASIN_Share',
+    title: '此asin的关键词加购数站总加购的百分比',
+    minWidth: 280,
+    slots: { default: 'Cart_Adds_ASIN_Share_default' },
+  },
+  {
+    field: 'Cart_Adds_Price_Median',
+    title: '此关键词的产品价格',
+    minWidth: 160,
+    slots: { default: 'Cart_Adds_Price_Median_default' },
+  },
+  {
+    field: 'Cart_Adds_ASIN_Price_Median',
+    title: '此asin下关键词的产品平均价格',
+    minWidth: 230,
+    slots: { default: 'Cart_Adds_ASIN_Price_Median_default' },
+  },
+  {
+    field: 'Cart_Adds_Same_Day_Shipping_Speed',
+    title: '同一天发货速度',
+    minWidth: 130,
+    slots: { default: 'Cart_Adds_Same_Day_Shipping_Speed_default' },
+  },
+  {
+    field: 'Cart_Adds_1D_Shipping_Speed',
+    title: '隔日发货速度',
+    minWidth: 130,
+    slots: { default: 'Cart_Adds_1D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Cart_Adds_2D_Shipping_Speed',
+    title: '两天内发货速度',
+    minWidth: 130,
+    slots: { default: 'Cart_Adds_2D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Purchases_Total_Count',
+    title: '该关键词下的购买数',
+    minWidth: 150,
+    slots: { default: 'Purchases_Total_Count_default' },
+  },
+  {
+    field: 'Purchases_Purchase_Rate',
+    title: '该关键词购买率',
+    minWidth: 130,
+    slots: { default: 'Purchases_Purchase_Rate_default' },
+  },
+  {
+    field: 'Purchases_ASIN_Count',
+    title: '此asin下关键词的购买数',
+    minWidth: 180,
+    slots: { default: 'Purchases_ASIN_Count_default' },
+  },
+  {
+    field: 'Purchases_ASIN_Share',
+    title: '此asin该关键词的购买占比',
+    minWidth: 200,
+    slots: { default: 'Purchases_ASIN_Share_default' },
+  },
+  {
+    field: 'Purchases_Price_Median',
+    title: '关键词的购买平均价格',
+    minWidth: 180,
+    slots: { default: 'Purchases_Price_Median_default' },
+  },
+  {
+    field: 'Purchases_ASIN_Price_Median',
+    title: '此asin下关键词的购买价格',
+    minWidth: 200,
+    slots: { default: 'Purchases_ASIN_Price_Median_default' },
+  },
+  {
+    field: 'Purchases_Same_Day_Shipping_Speed',
+    title: '同一天发货速度',
+    minWidth: 130,
+    slots: { default: 'Purchases_Same_Day_Shipping_Speed_default' },
+  },
+  {
+    field: 'Purchases_1D_Shipping_Speed',
+    title: '隔日发货速度',
+    minWidth: 130,
+    slots: { default: 'Purchases_1D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Purchases_2D_Shipping_Speed',
+    title: '2天内发货速度',
+    minWidth: 130,
+    slots: { default: 'Purchases_2D_Shipping_Speed_default' },
+  },
+  {
+    field: 'Reporting_Date',
+    title: '报告日期',
+    minWidth: 100,
+    slots: { default: 'Reporting_Date_default' },
+  },
+  {
+    field: 'departmentName',
+    title: '站点名',
+    minWidth: 150,
+    slots: { default: 'departmentName_default' },
+  },
+  {
+    field: 'searchTerm',
+    title: '关键词',
+    minWidth: 100,
+    slots: { default: 'searchTerm_default' },
+  },
+  {
+    field: 'searchFrequencyRank',
+    title: '关键词搜索排名',
+    minWidth: 130,
+    slots: { default: 'searchFrequencyRank_default' },
+  },
+  {
+    field: 'clickedAsin',
+    title: 'ASIN',
+    minWidth: 150,
+    slots: { default: 'clickedAsin_default' },
+  },
+  {
+    field: 'clickedItemName',
+    title: '标题',
+    minWidth: 150,
+    slots: { default: 'clickedItemName_default' },
+  },
+  {
+    field: 'clickShareRank',
+    title: '点击分享排名',
+    minWidth: 130,
+    slots: { default: 'clickShareRank_default' },
+  },
+  {
+    field: 'clickShare',
+    title: '点击分享率',
+    minWidth: 130,
+    slots: { default: 'clickShare_default' },
+  },
+  {
+    field: 'conversionShare',
+    title: '转化分享率',
+    minWidth: 130,
+    slots: { default: 'conversionShare_default' },
+  },
+  {
+    field: 'marketplaceIds',
+    title: '站点id',
+    minWidth: 180,
+    slots: { default: 'marketplaceIds_default' },
+  },
+];

+ 123 - 124
src/views/searchTerm/rootWordManage/components/root-word-manage-table.vue

@@ -311,135 +311,134 @@ function handleResponse(response: any) {
 </script>
 
 <template>
-  <div>
-    <el-card v-loading="tableLoading" body-style="background-color: #f7f7f7;">
-      <!-- 筛选 -->
-      <el-card body-class="flex justify-between gap-3.5" shadow="hover" style="border: none; margin-bottom: 10px">
-        <div class="flex gap-7">
-          <div>
-            <span class="font-bold mr-2" style="color: #303133">词根:</span>
-            <el-input
-              v-model="searchTermFilter"
-              placeholder="请输入词根"
-              clearable
-              @change="fetchSearchTermList"
-              :prefix-icon="Search"
-              style="width: 240px"></el-input>
-          </div>
-          <div>
-            <span class="font-bold mr-2" style="color: #303133">词根类型:</span>
-            <el-select v-model="searchTermTypeFilter" style="width: 200px" @change="fetchSearchTermList">
-              <el-option label="全部" value="all" />
-              <el-option label="positive" value="positive" />
-              <el-option label="negative" value="negative" />
-              <el-option label="无词根类型" value="typeless" />
-            </el-select>
-          </div>
+  <div class="p-2.5" v-loading="tableLoading" style="background-color: #f7f7f7">
+    <!-- 筛选 -->
+    <el-card body-class="flex justify-between gap-3.5" shadow="hover" style="border: none; margin-bottom: 10px">
+      <div class="flex gap-7">
+        <div>
+          <span class="font-bold mr-2" style="color: #303133">词根:</span>
+          <el-input
+            v-model="searchTermFilter"
+            placeholder="请输入词根"
+            clearable
+            @change="fetchSearchTermList"
+            :prefix-icon="Search"
+            style="width: 240px"></el-input>
         </div>
-        <div class="flex gap-3.5">
-          <el-button plain type="primary" @click="handleDialogVisible">
-            <el-icon>
-              <Plus />
-            </el-icon>
-            添加词根
-          </el-button>
-          <!-- 想要不页面不跳动可以加72的高度 -->
-          <div>
-            <el-upload
-              ref="upload"
-              action="#"
-              :limit="1"
-              :auto-upload="true"
-              :on-exceed="handleExceed"
-              :http-request="handleCustomUpload">
-              <template #trigger>
-                <el-button plain round type="warning" :icon="Upload">批量词根上传</el-button>
-              </template>
-            </el-upload>
-          </div>
+        <div>
+          <span class="font-bold mr-2" style="color: #303133">词根类型:</span>
+          <el-select v-model="searchTermTypeFilter" style="width: 200px" @change="fetchSearchTermList">
+            <el-option label="全部" value="all" />
+            <el-option label="positive" value="positive" />
+            <el-option label="negative" value="negative" />
+            <el-option label="无词根类型" value="typeless" />
+          </el-select>
         </div>
-      </el-card>
-      <!-- 表格 -->
-      <el-card shadow="hover" style="border: none">
-        <div style="height: 800px">
-          <el-table :data="tableData" height="800" stripe style="width: 100%">
-            <el-table-column fixed prop="add_date" label="添加日期" width="180" sortable>
-              <template #header>
-                <el-icon style="top: 2px; margin-right: 3px">
-                  <Calendar />
-                </el-icon>
-                <span>添加日期</span>
-              </template>
-              <template #default="{ row }">
-                <span class="font-medium">{{ row.add_date }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="searchTerm" label="词根" sortable>
-              <template #header>
-                <el-icon style="top: 2px; right: 2px">
-                  <Key />
-                </el-icon>
-                <span>词根</span>
-              </template>
-              <template #default="{ row }">
-                <el-input ref="searchTermInpRef" v-if="row.isEditing" v-model="row.searchTerm" @change="updateSearchTerm(row)" />
-                <span class="font-semibold" v-else>{{ row.searchTerm }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="searchTerm_type" label="词根类型" sortable>
-              <template #header>
-                <el-icon style="top: 2px; right: 2px">
-                  <Coin />
-                </el-icon>
-                <span>词根类型</span>
-              </template>
-              <template #default="{ row }">
-                <el-popconfirm
-                  title="确定修改吗?"
-                  @confirm="updateSearchTermType(row)"
-                  @cancel="cancelUpdate(row)"
-                  :visible="row.popConfirmVisible">
-                  <template #reference>
-                    <el-select v-model="row.searchTerm_type" @change="showPopConfirm(row)" style="width: 150px">
-                      <el-option label="positive" value="positive" />
-                      <el-option label="negative" value="negative" />
-                    </el-select>
-                  </template>
-                </el-popconfirm>
-              </template>
-            </el-table-column>
-            <el-table-column fixed="right" label="操作" width="120">
-              <template #header>
-                <el-icon style="top: 2px; margin-right: 5px">
-                  <EditPen />
-                </el-icon>
-                <span>操作</span>
-              </template>
-              <template #default="{ row }">
-                <el-button link type="primary" size="small" @click="handleClick(row)" v-if="!row.isEditing"> 编辑</el-button>
-                <el-button link type="primary" size="small" @click="handleClick(row)" v-else> 取消</el-button>
-                <el-popconfirm title="确定删除吗?" @confirm="handleDelete(row)">
-                  <template #reference>
-                    <el-button link type="danger" size="small">删除</el-button>
-                  </template>
-                </el-popconfirm>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-        <div class="mt-3.5 flex justify-end">
-          <el-pagination
-            v-model:current-page="currentPage"
-            v-model:page-size="pageSize"
-            :page-sizes="[20, 40, 50, 100]"
-            layout="sizes, prev, pager, next"
-            :total="total"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange" />
+      </div>
+      <div class="flex gap-3.5">
+        <el-button plain type="primary" @click="handleDialogVisible">
+          <el-icon>
+            <Plus />
+          </el-icon>
+          添加词根
+        </el-button>
+        <!-- 想要不页面不跳动可以加72的高度 -->
+        <div>
+          <el-upload
+            ref="upload"
+            action="#"
+            :limit="1"
+            :auto-upload="true"
+            :on-exceed="handleExceed"
+            :http-request="handleCustomUpload">
+            <template #trigger>
+              <el-button plain round type="warning" :icon="Upload">批量词根上传</el-button>
+            </template>
+          </el-upload>
         </div>
-      </el-card>
+      </div>
+    </el-card>
+    <!-- 表格 -->
+    <el-card shadow="hover" style="border: none">
+      <div style="height: 800px">
+        <el-table :data="tableData" height="800" stripe style="width: 100%">
+          <el-table-column fixed prop="add_date" label="添加日期" width="180" sortable>
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 3px">
+                <Calendar />
+              </el-icon>
+              <span>添加日期</span>
+            </template>
+            <template #default="{ row }">
+              <span class="font-medium">{{ row.add_date }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="searchTerm" label="词根" sortable>
+            <template #header>
+              <el-icon style="top: 2px; right: 2px">
+                <Key />
+              </el-icon>
+              <span>词根</span>
+            </template>
+            <template #default="{ row }">
+              <el-input ref="searchTermInpRef" v-if="row.isEditing" v-model="row.searchTerm" @change="updateSearchTerm(row)" />
+              <span class="font-semibold" v-else>{{ row.searchTerm }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="searchTerm_type" label="词根类型" sortable>
+            <template #header>
+              <el-icon style="top: 2px; right: 2px">
+                <Coin />
+              </el-icon>
+              <span>词根类型</span>
+            </template>
+            <template #default="{ row }">
+              <el-popconfirm
+                title="确定修改吗?"
+                @confirm="updateSearchTermType(row)"
+                @cancel="cancelUpdate(row)"
+                :visible="row.popConfirmVisible">
+                <template #reference>
+                  <el-select v-model="row.searchTerm_type" @change="showPopConfirm(row)" style="width: 150px">
+                    <el-option label="positive" value="positive" />
+                    <el-option label="negative" value="negative" />
+                  </el-select>
+                </template>
+              </el-popconfirm>
+            </template>
+          </el-table-column>
+          <el-table-column fixed="right" label="操作" width="120">
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 5px">
+                <EditPen />
+              </el-icon>
+              <span>操作</span>
+            </template>
+            <template #default="{ row }">
+              <el-button link type="primary" size="small" @click="handleClick(row)" v-if="!row.isEditing"> 编辑</el-button>
+              <el-button link type="primary" size="small" @click="handleClick(row)" v-else> 取消</el-button>
+              <el-popconfirm title="确定删除吗?" @confirm="handleDelete(row)">
+                <template #reference>
+                  <el-button link type="danger" size="small">删除</el-button>
+                </template>
+              </el-popconfirm>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="mt-3.5 flex justify-end">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[20, 40, 50, 100]"
+          layout="sizes, prev, pager, next"
+          :total="total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange" />
+      </div>
     </el-card>
   </div>
+
   <!-- 添加词根弹窗 -->
   <el-dialog v-model="dialogVisible" title="添加关键词" width="500" :before-close="handleClose">
     <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" status-icon :rules="rules" label-width="auto">

+ 1 - 1
src/views/searchTerm/topSearchTermRank/index.vue

@@ -124,7 +124,7 @@ function handleJump() {
       </div>
     </el-divider>
   </div>
-  <el-card class="mx-3 mb-2.5 h-full" v-loading="tableLoading" style="border: none;">
+  <el-card class="mx-3 mb-2.5" v-loading="tableLoading" style="border: none;">
     <!-- table筛选栏 -->
     <div class="flex justify-between">
       <div class="flex gap-5 flex-wrap">

+ 1 - 1
src/views/searchTerm/topSearchTermTable/index.vue

@@ -239,7 +239,7 @@ function arraySpanMethod({ row, column, rowIndex, columnIndex }) {
       </div>
     </el-divider>
   </div>
-  <el-card class="mx-3 mb-2.5 h-full" v-loading="tableLoading" style="border: none;">
+  <el-card class="mx-3 mb-2.5" v-loading="tableLoading" style="border: none;">
     <!-- table筛选栏 -->
     <div class="flex justify-between">
       <div class="flex gap-5 flex-wrap">