123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- <script lang="ts" setup>
- /**
- * @Name: index.vue
- * @Description: 搜索词-TopSearchTerm Table
- * @Author: Cheney
- */
- import { nextTick, onBeforeMount, ref, watch } from 'vue';
- import { usePagination } from '/@/utils/usePagination';
- import { getTopSearchTermTable, postDownload } from './api';
- import { marketplaceIdEnum } from '/@/utils/marketplaceIdEnum';
- 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';
- import { useTableHeight } from '/@/utils/useTableHeight';
- import { useCustomHeight } from '/@/utils/useCustomHeight';
- const router = useRouter();
- const { tableData, total, currentPage, pageSize } = usePagination(fetchTableData);
- 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]);
- const titleContainer = ref();
- const queryContainer = ref();
- const heightObj = {
- a: 32 + 13 + 40 + 70 + 40 + 48 + 95
- }
- const { tableHeight } = useCustomHeight(heightObj);
- onBeforeMount(() => {
- pageSize.value = 7; // 将usePagination中的pageSize默认修改每页显示21条
- fetchTableData();
- });
- 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') ];
- }
- /**
- * 计算结束日期
- * @param startDate el-date-picker组件的value
- */
- function calculateEndDate(startDate: string) {
- return dayjs(startDate).add(6, 'day').format('YYYY-MM-DD');
- }
- async function refreshTable() {
- currentPage.value = 1;
- pageSize.value = 7;
- asinInp.value = '';
- searchTermInp.value = '';
- reportTypeSelect.value = 'weekly';
- marketplaceSelect.value = marketplaceIdEnum[0].value;
- await fetchTableData();
- }
- async function fetchTableData(isQuery = false) {
- tableLoading.value = true;
- if (isQuery) {
- currentPage.value = 1;
- }
- const query = {
- page: currentPage.value,
- limit: pageSize.value * 3,
- 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 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'));
- }
- }
- /**
- * 下拉框值改变和input清空事件触发
- */
- function handleSelectChange() {
- calculateDate();
- // await fetchTableData();
- }
- /**
- * 输入框按下回车后触发
- */
- 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 handleJump() {
- // console.log('All defined routes:', router.getRoutes());
- 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> {
- // switch (clickShareRank) {
- // case 1:
- // return { backgroundColor: '#fbbf24', color: '#fff', border: '1px solid #fbbf24' }; // 金色
- // case 2:
- // return { backgroundColor: '#C0C0C0', color: '#fff', border: '1px solid #C0C0C0' }; // 银色
- // case 3:
- // return { backgroundColor: '#CD7F32', color: '#fff', border: '1px solid #CD7F32' }; // 铜色
- // default:
- // return { backgroundColor: '#f0f0f0', color: '#000', border: '1px solid #e0e0e0' };
- // }
- // }
- function arraySpanMethod({ row, column, rowIndex, columnIndex }) {
- // 每三个合并为一个单元格
- if (columnIndex >= 0 && columnIndex <= 4) {
- if (rowIndex % 3 === 0) {
- return [ 3, 1 ]; // 跨越三行
- } else {
- return [ 0, 0 ]; // 被合并的单元格
- }
- }
- return [ 1, 1 ];
- }
- async function handlePageChange(newPage: number, newSize: number) {
- currentPage.value = newPage;
- pageSize.value = newSize;
- await fetchTableData();
- }
- </script>
- <template>
- <div ref="titleContainer" class="mx-3">
- <el-divider>
- <div class="font-bold text-xl">
- <el-icon style="top: 3px">
- <DataAnalysis/>
- </el-icon>
- Top Search Term - Table
- </div>
- </el-divider>
- </div>
- <el-card v-loading="tableLoading" class="mx-3 mb-2.5" style="border: none;">
- <!-- table筛选栏 -->
- <div ref="queryContainer" class="flex justify-between">
- <div class="flex gap-5 flex-wrap">
- <div>
- <span class="font-medium mr-0.5">市 场 </span>
- <el-select v-model="marketplaceSelect" style="width: 90px" @change="handleSelectChange">
- <el-option
- v-for="item in marketplaceOptions"
- :key="item.value"
- :disabled="item.disabled"
- :label="item.label"
- :value="item.value"/>
- </el-select>
- </div>
- <div>
- <span class="font-medium mr-0.5">报告类型 </span>
- <el-select v-model="reportTypeSelect" style="width: 90px" @change="handleSelectChange">
- <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"
- :prefix-icon="Search"
- clearable
- placeholder="请输入"
- style="width: 240px" />
- </div>
- <div>
- <span class="font-medium mr-0.5">ASIN </span>
- <el-input
- v-model="asinInp"
- :prefix-icon="Search"
- clearable
- placeholder="请输入"
- style="width: 180px" />
- </div>
- <div>
- <span class="font-medium mr-0.5">报告日期 </span>
- <el-config-provider v-if="reportTypeSelect === 'weekly'" :locale="enLocale">
- <el-date-picker
- v-model="dateDimension"
- :clearable="false"
- :disabled-date="(time: Date) => time > new Date()"
- :format="`${date[0]} To ${date[1]}`"
- :popper-options="{ placement: 'bottom-end' }"
- type="week"
- value-format="YYYY-MM-DD"/>
- </el-config-provider>
- <el-date-picker
- v-else
- v-model="dateDimension"
- :clearable="false"
- :disabled-date="(time: Date) => time > new Date()"
- :format="`${date[0]} To ${date[1]}`"
- :popper-options="{ placement: 'bottom-end' }"
- type="month"
- value-format="YYYY-MM">
- <!--<template #default> 123</template>-->
- </el-date-picker>
- </div>
- </div>
- <div class="flex">
- <el-button :icon="Search" type="primary" @click="fetchTableData(true)">查 询</el-button>
- <el-button :icon="Refresh" @click="refreshTable">刷 新</el-button>
- </div>
- </div>
- <div class="mt-6">
- <el-button :icon="TopRight" plain type="primary" @click="handleJump">搜索词管理</el-button>
- <el-button
- :disabled="!tableData.length"
- :icon="Download"
- :loading="downloadLoading"
- plain
- round
- type="success"
- @click="handleDownload"
- >下载表格
- </el-button>
- </div>
- <!-- table -->
- <el-card class="mt-5" shadow="never">
- <div>
- <el-table :data="tableData" :span-method="arraySpanMethod" :height="tableHeight" stripe style="width: 100%">
- <!-- 保持索引是1, 2, 3的顺序 不会收到合并单元格的影响 -->
- <el-table-column :index="(index) => Math.floor(index / 3) + (currentPage - 1) * pageSize + 1" fixed
- label="No." type="index" width="80"/>
- <el-table-column label="搜索词" prop="searchTerm">
- <template #header>
- <el-icon style="top: 2px; margin-right: 3px">
- <Key/>
- </el-icon>
- <span>搜索词</span>
- </template>
- <template #default="{ row }">
- <el-link :underline="false" style="font-size: 18px;" target="_blank" type="primary">
- {{ row.searchTerm }}
- </el-link>
- </template>
- </el-table-column>
- <el-table-column align="center" label="搜索词搜索排名" prop="searchFrequencyRank" width="90">
- <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 align="center" label="点击分享率(SUM)" prop="clickShareSummary" width="90">
- <template #header>
- <el-icon style="top: 2px; margin-right: 4px">
- <Star/>
- </el-icon>
- <span>点击分享率汇总</span>
- </template>
- <template #default="{ row }">
- <span class="font-medium">{{ row.clickShareSummary }}</span>
- </template>
- </el-table-column>
- <el-table-column align="center" label="转化分享率(SUM)" prop="conversionShareSummary" width="90">
- <template #header>
- <el-icon style="top: 2px; margin-right: 4px">
- <Star/>
- </el-icon>
- <span>转化分享率汇总</span>
- </template>
- <template #default="{ row }">
- <span class="font-medium">{{ row.conversionShareSummary }}</span>
- </template>
- </el-table-column>
- <el-table-column align="center" label="Asin" prop="clickedAsin" width="160">
- <template #header>
- <el-icon style="top: 2px; margin-right: 5px">
- <Goods/>
- </el-icon>
- <span>Asin</span>
- </template>
- <template #default="{ row }">
- <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" :show-after="500">-->
- <!-- <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 label="标 题" prop="clickedItemName">
- <template #header>
- <el-icon style="top: 2px; margin-right: 5px">
- <Reading/>
- </el-icon>
- <span>标 题</span>
- </template>
- <template #default="{ row }">
- <div class="text-sm text-left">
- <el-tooltip :content="row.clickedItemName" :show-after="500" class="box-item" effect="dark"
- placement="top">
- <div class="tooltip-text">
- {{ row.clickedItemName }}
- </div>
- </el-tooltip>
- </div>
- </template>
- </el-table-column>
- <el-table-column align="center" label="点击分享率排名" prop="clickShareRank" width="90">
- <template #header>
- <el-icon style="top: 2px; margin-right: 4px">
- <Medal/>
- </el-icon>
- <span>点击分享率排名</span>
- </template>
- <template #default="{ row }">
- <!--<el-tag :style="getTagStyle(row.clickShareRank)">-->
- {{ row.clickShareRank }}
- <!--</el-tag>-->
- </template>
- </el-table-column>
- <el-table-column align="center" label="点击分享率" prop="clickShare" width="90">
- <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 align="center" label="转化分享率" prop="conversionShare" width="90">
- <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="[7, 14, 21, 28, 35]"
- :total="Math.floor(total / 3)"
- layout="prev, pager, next, sizes, total"
- @change="handlePageChange"/>
- </div>
- </el-card>
- </el-card>
- </template>
- <style scoped>
- /* 修改 el-divider 的背景颜色 */
- :deep(.el-divider__text.is-center.el-divider__text) {
- background-color: #f8f8f8;
- }
- .tooltip-text {
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: normal;
- line-height: 1.2em;
- max-height: 2.4em;
- }
- </style>
|