|  | @@ -0,0 +1,289 @@
 | 
	
		
			
				|  |  | +<script setup lang="ts">
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * @Name: index.vue
 | 
	
		
			
				|  |  | + * @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>
 |