瀏覽代碼

✨ feat: 新增TopSearchTerm页面

WanGxC 11 月之前
父節點
當前提交
f185058824

+ 118 - 0
src/utils/marketplaceIdEnum.ts

@@ -0,0 +1,118 @@
+export const marketplaceIdEnum = [
+  {
+    value: 'ATVPDKIKX0DER',
+    label: '美国',
+    disabled: false,
+  },
+  {
+    value: 'A2VIGQ35RCS4UG',
+    label: '阿联酋',
+    disabled: true,
+  },
+  {
+    value: 'AMEN7PMS3EDWL',
+    label: '比利时',
+    disabled: true,
+  },
+  {
+    value: 'A1PA6795UKMFR9',
+    label: '德国',
+    disabled: true,
+  },
+  {
+    value: 'A1C3SOZRARQ6R3',
+    label: '波兰',
+    disabled: true,
+  },
+  {
+    value: 'ARBP9OOSHTCHU',
+    label: '埃及',
+    disabled: true,
+  },
+  {
+    value: 'A1RKKUPIHCS9HS',
+    label: '西班牙',
+    disabled: true,
+  },
+  {
+    value: 'A13V1IB3VIYZZH',
+    label: '法国',
+    disabled: true,
+  },
+  {
+    value: 'A1F83G8C2ARO7P',
+    label: '英国',
+    disabled: true,
+  },
+  {
+    value: 'A21TJRUUN4KGV',
+    label: '印度',
+    disabled: true,
+  },
+  {
+    value: 'APJ6JRA9NG5V4',
+    label: '意大利',
+    disabled: true,
+  },
+  {
+    value: 'A1805IZSGTT6HS',
+    label: '荷兰',
+    disabled: true,
+  },
+  {
+    value: 'A17E79C6D8DWNP',
+    label: '沙特阿拉伯',
+    disabled: true,
+  },
+  {
+    value: 'A2NODRKZP88ZB9',
+    label: '瑞典',
+    disabled: true,
+  },
+  {
+    value: 'A33AVAJ2PDY3EV',
+    label: '土耳其',
+    disabled: true,
+  },
+  {
+    value: 'A1F83G8C2ARO7P',
+    label: '英国',
+    disabled: true,
+  },
+  {
+    value: 'AE08WJ6YKNBMC',
+    label: '南非',
+    disabled: true,
+  },
+  {
+    value: 'A39IBJ37TRP1C6',
+    label: '澳大利亚',
+    disabled: true,
+  },
+  {
+    value: 'A1VC38T7YXB528',
+    label: '日本',
+    disabled: true,
+  },
+  {
+    value: 'A19VAU5U5O7RUS',
+    label: '新加坡',
+    disabled: true,
+  },
+
+  {
+    value: 'A2Q3Y263D00KWC',
+    label: '巴西',
+    disabled: true,
+  },
+  {
+    value: 'A2EUQ1WTGCTBG2',
+    label: '加拿大',
+    disabled: true,
+  },
+  {
+    value: 'A1AM78C64UM0Y8',
+    label: '墨西哥',
+    disabled: true,
+  },
+];

+ 32 - 0
src/utils/usePagination.ts

@@ -0,0 +1,32 @@
+import { ref } from 'vue';
+
+
+/**
+ * 分页改变获取数据
+ * @param getData 获取数据的方法, 在外面完成getData方法的实现
+ */
+export function usePagination(getData: Function) {
+  const tableData = ref([]);
+  const total = ref(0);
+  const currentPage = ref(1);
+  const pageSize = ref(10);
+
+  /**
+   * 分页改变
+   * @param newPage 当前页码
+   * @param newSize 每页条数
+   */
+  async function handlePageChange(newPage: number, newSize: number) {
+    currentPage.value = newPage;
+    pageSize.value = newSize;
+    await getData();
+  }
+
+  return {
+    tableData,
+    total,
+    currentPage,
+    pageSize,
+    handlePageChange
+  };
+}

+ 4 - 1
src/views/keyword/rootWordManage/components/root-word-manage-table.vue

@@ -368,6 +368,9 @@ function handleResponse(response: any) {
                 </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>
@@ -378,7 +381,7 @@ function handleResponse(response: any) {
               </template>
               <template #default="{ row }">
                 <el-input ref="searchTermInpRef" v-if="row.isEditing" v-model="row.searchTerm" @change="updateSearchTerm(row)" />
-                <span class="font-semibold" style="color: #5c96f7" v-else>{{ row.searchTerm }}</span>
+                <span class="font-semibold" v-else>{{ row.searchTerm }}</span>
               </template>
             </el-table-column>
             <el-table-column prop="searchTerm_type" label="词根类型" sortable>

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

@@ -0,0 +1,19 @@
+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 postCreateSearchTerm(body: any) {
+//   return request({
+//     url: apiPrefix + 'topsearchtermroot/',
+//     method: 'POST',
+//     data: body,
+//   });
+// }

+ 271 - 8
src/views/keyword/topSearchTermTable/index.vue

@@ -4,19 +4,282 @@
  * @Description: 关键词-TopSearchTerm Table
  * @Author: Cheney
  */
+
+import { onMounted, ref, watch } from 'vue';
+import { usePagination } from '/@/utils/usePagination';
+import { getTopSearchTermTable } from './api';
+import { marketplaceIdEnum } from '/@/utils/marketplaceIdEnum';
+import { RefreshRight, Search } from '@element-plus/icons-vue';
+import { useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import dayjs from 'dayjs';
+
+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 searchTermInp = ref('');
+const asinInp = ref('');
+const marketplaceSelect = ref(marketplaceIdEnum[0].value); // 当前只有美国区 默认第一个为美国
+const marketplaceOptions = marketplaceIdEnum;
+const reportTypeSelect = ref('weekly');
+
+const tableLoading = ref(false);
+
+onMounted(() => {
+  fetchTableData();
+});
+
+watch(date, () => {
+  fetchTableData();
+});
+
+async function refreshTable() {
+  currentPage.value = 1;
+  pageSize.value = 10;
+  asinInp.value = '';
+  searchTermInp.value = '';
+  reportTypeSelect.value = 'weekly';
+  marketplaceSelect.value = marketplaceIdEnum[0].value;
+  await fetchTableData();
+}
+
+async function fetchTableData() {
+  tableLoading.value = true;
+  const query = {
+    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;
+}
+
+async function handleQueryChange() {
+  if (!validateSearchTermInput(searchTermInp.value)) {
+    ElMessage.warning({ message: '关键词只能输入数字和英文字母', plain: true });
+    return;
+  }
+
+  if (!validateAsinInput(asinInp.value)) {
+    if (asinInp.value.length == 0) return;
+    else {
+      ElMessage.warning({ message: '不符合匹配规范', plain: true });
+    }
+    return;
+  }
+
+  await fetchTableData();
+}
+
+function handleJump() {
+  // console.log('All defined routes:', router.getRoutes());
+  router.push({ path: '/keyword/rootWordManage' });
+}
+
+/**
+ * 校验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);
+}
 </script>
 
 <template>
-<div style="background-color: #f7f7f7;">
-  <div class="flex justify-between mt-1.5 mx-2">
-    <span class="font-bold text-lg">
-      Top Search Term - Table
-    </span>
-    <el-button type="info">关键词管理</el-button>
+  <div class="mt-3 mx-1.5" style="background-color: #f7f7f7">
+    <div class="flex justify-between mt-1.5 mx-2">
+      <div class="font-bold text-lg">
+        <el-icon style="top: 3px">
+          <Memo />
+        </el-icon>
+        Top Search Term - Table
+      </div>
+      <div>
+        <el-button type="primary" plain @click="handleJump">关键词管理</el-button>
+        <el-button type="success" plain round>下载表格</el-button>
+      </div>
+    </div>
+  </div>
+  <div class="mx-3" style="margin-top: -8px">
+    <el-divider>
+      <el-icon>
+        <star-filled />
+      </el-icon>
+    </el-divider>
   </div>
-</div>
+
+  <el-card class="mx-3" v-loading="tableLoading" style="border: none">
+    <!-- table筛选栏 -->
+    <div class="flex justify-between">
+      <div class="flex gap-6 flex-wrap">
+        <div>
+          <span class="font-medium mr-0.5">市场 </span>
+          <el-select v-model="marketplaceSelect" @change="handleQueryChange" style="width: 130px">
+            <el-option
+              v-for="item in marketplaceOptions"
+              :disabled="item.disabled"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label" />
+          </el-select>
+        </div>
+        <div>
+          <span class="font-medium mr-0.5">报告类型 </span>
+          <el-select v-model="reportTypeSelect" @change="handleQueryChange" style="width: 100px">
+            <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
+            style="width: 300px"></el-input>
+        </div>
+        <div>
+          <span class="font-medium mr-0.5">ASIN </span>
+          <el-input
+            v-model="asinInp"
+            @keyup.enter="handleQueryChange"
+            :prefix-icon="Search"
+            placeholder="输入后回车查询"
+            clearable
+            style="width: 180px"></el-input>
+        </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"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期" />
+        </div>
+      </div>
+      <el-button @click="refreshTable" :icon="RefreshRight" circle></el-button>
+    </div>
+
+    <!-- table -->
+    <el-card shadow="never" class="mt-5">
+      <div style="height: 765px">
+        <el-table :data="tableData" stripe height="765px" 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>
+            </template>
+            <template #default="{ row }">
+              <span class="font-medium text-lg" style="color: #0b3289">{{ row.searchTerm }}</span>
+            </template>
+          </el-table-column>
+          <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>
+            </template>
+            <template #default="{ row }">
+              <span class="font-medium">{{ row.searchFrequencyRank }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="clickedAsin" align="center" label="Asin">
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 5px">
+                <Goods />
+              </el-icon>
+              <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>
+            </template>
+          </el-table-column>
+          <el-table-column prop="clickShareRank" label="点击分享率排名" align="center" width="150">
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 4px">
+                <Medal />
+              </el-icon>
+              <span>点击分享率排名</span>
+            </template>
+            <template #default="{ row }">
+              <span class="font-semibold">{{ row.clickShareRank }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="clickShare" align="center" label="点击分享率">
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 4px">
+                <Pointer />
+              </el-icon>
+              <span>点击分享率</span>
+            </template>
+            <template #default="{ row }">
+              <span class="font-semibold">{{ row.clickShare }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="conversionShare" align="center" label="转化分享率">
+            <template #header>
+              <el-icon style="top: 2px; margin-right: 5px">
+                <Switch />
+              </el-icon>
+              <span>转化分享率</span>
+            </template>
+            <template #default="{ row }">
+              <span class="font-semibold">{{ row.conversionShare }}</span>
+            </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="[10, 20, 30, 50]"
+          layout="sizes, prev, pager, next"
+          :total="total"
+          @change="handlePageChange" />
+      </div>
+    </el-card>
+  </el-card>
 </template>
 
 <style scoped>
-
+:deep(.el-divider__text.is-center.el-divider__text) {
+  background-color: #f8f8f8;
+}
 </style>