Browse Source

✨ feat: 新增搜索词-分析页 指标总览卡片

WanGxC 1 year ago
parent
commit
99eb8e5fec

+ 139 - 0
src/views/searchTerm/analysisPage/IndicatorOverview.vue

@@ -0,0 +1,139 @@
+<script setup lang="ts">
+/**
+ * @Name: IndicatorOverview.vue
+ * @Description: 分析页-指标总览
+ * @Author: Cheney
+ */
+import { reactive, inject, watch, Ref } from 'vue';
+import { Bottom, Top } from '@element-plus/icons-vue';
+
+const queryConditionData = inject<Ref>('queryConditionData');
+const indicator = reactive([
+  {
+    title: '',
+    value: '',
+    compare: '',
+    color: '#edf6fd',
+  },
+  {
+    title: '',
+    value: '--',
+    compare: '',
+    color: '#eefdf0',
+  },
+  {
+    title: '',
+    value: '',
+    compare: '',
+    color: '#fff7ed',
+  },
+  {
+    title: '',
+    value: '',
+    compare: '',
+    color: '#f0f0fe',
+  },
+]);
+
+// 指标与返回数据字段的映射关系
+const mapping = {
+  imp: {
+    title: '曝光量',
+    key: 0, // indicator 数组中对应的索引
+    kw: 'Impressions',
+  },
+  click: {
+    title: '点击率',
+    key: 1,
+    kw: 'Clicks',
+  },
+  cart_add: {
+    title: '加购份额',
+    key: 2,
+    kw: 'Cart_Adds',
+  },
+  purchases: {
+    title: '购买份额',
+    key: 3,
+    kw: 'Purchases',
+  },
+};
+
+watch(queryConditionData, (newData) => {
+  console.log('newData', newData);
+  if (newData) {
+    for (const key in mapping) {
+      if (newData[key]) {
+        const index = mapping[key].key;
+        indicator[index].title = mapping[key].title;
+        indicator[index].value = newData[key][`${mapping[key].kw + '_A_B_Share'}`];
+        indicator[index].compare = newData[key][`diff_${mapping[key].kw + '_A_B_Share'}`];
+      }
+    }
+  } else {
+    indicator.forEach((item) => {
+      item.title = '暂无数据';
+      item.value = '--';
+      item.compare = '--';
+    });
+  }
+});
+</script>
+
+<template>
+  <el-card shadow="hover" body-class="flex justify-between w-full" style="border: none; margin-bottom: 10px">
+    <el-card
+      v-for="(item, index) in indicator"
+      :key="index"
+      body-class="flex flex-col items-center"
+      class="flex-1 mx-1"
+      :style="{
+        background: item.color
+          ? `linear-gradient(to top, ${item.color}, transparent)`
+          : 'linear-gradient(to top, #fff, transparent)',
+      }">
+      <div class="text-2xl font-bold mb-1.5">{{ item.title || '暂无数据' }}</div>
+      <div class="font-medium text-2xl mb-1.5">
+        {{ item.value !== null && item.value !== undefined && item.value !== '' ? item.value : '--' }}
+        <span v-if="item.value !== null && item.value !== undefined && item.value !== '' && item.value !== '--'">%</span>
+      </div>
+      <div>
+        <span class="mr-2 font-medium" style="color: #9ca3af">较上期</span>
+        <template v-if="item.compare !== '-'">
+          <el-icon
+            v-if="item.compare && item.compare !== '--'"
+            :color="Number(item.compare) > 0 ? '#59b939' : '#e36f53'"
+            style="display: inline-block; padding-top: 2px">
+            <component :is="Number(item.compare) > 0 ? Top : Bottom" />
+          </el-icon>
+          <span
+            class="font-medium"
+            :style="{
+              color:
+                item.compare && item.compare !== '--'
+                  ? Number(item.compare) > 0
+                    ? '#59b939'
+                    : Number(item.compare) < 0
+                    ? '#e36f53'
+                    : ''
+                  : '',
+            }">
+            {{ item.compare !== null && item.compare !== undefined && item.compare !== '' ? item.compare : '--' }}
+          </span>
+          <span
+            v-if="item.compare && item.compare !== '--'"
+            :style="{
+              color: Number(item.compare) > 0 ? '#59b939' : Number(item.compare) < 0 ? '#e36f53' : '',
+            }"
+            >%</span
+          >
+        </template>
+        <template v-else>
+          <span class="font-medium">无数据</span>
+        </template>
+      </div>
+    </el-card>
+  </el-card>
+</template>
+
+<style scoped></style>

+ 108 - 0
src/views/searchTerm/analysisPage/QueryCondition.vue

@@ -0,0 +1,108 @@
+<script setup lang="ts">
+/**
+ * @Name: QueryCondition.vue
+ * @Description:
+ * @Author: Cheney
+ */
+import { RefreshLeft, Search, Upload, View } from '@element-plus/icons-vue';
+import { reactive, ref, watch } from 'vue';
+import * as api from './api';
+
+const emit = defineEmits(['dataFetched']);
+
+const defaultLabel = ref('ASIN');
+const filter = reactive({
+  layerType: '',
+  searchTerm: '',
+  reportType: '',
+  reportDate: '',
+  variable: '',
+});
+
+watch(
+  () => filter.layerType,
+  (newValue) => {
+    if (newValue === 'asin_view') {
+      defaultLabel.value = 'ASIN';
+      // filter.variable = 'asin';
+    } else if (newValue === 'brand_view') {
+      defaultLabel.value = 'Brand';
+      // filter.variable = 'brand';
+    } else {
+      defaultLabel.value = 'ASIN';
+      filter.variable = '';
+    }
+  }
+);
+
+async function handleQuery() {
+  const query = {
+    date_start: filter.reportDate[0],
+    date_end: filter.reportDate[1],
+    layer_type: filter.layerType,
+    search_term: filter.searchTerm,
+    report_range: filter.reportType,
+    [filter.variable]: filter.variable,
+  };
+  try {
+    const response = await api.getAsinMetrics(query);
+    emit('dataFetched', response.data);
+  } catch (error) {
+    console.error('==Error==', error);
+  }
+}
+
+function resetCondition() {
+  filter.layerType = '';
+  filter.searchTerm = '';
+  filter.reportType = '';
+  filter.reportDate = '';
+  filter.variable = '';
+}
+</script>
+
+<template>
+  <el-card body-class="flex justify-between gap-3.5" shadow="hover" style="border: none; margin-bottom: 10px">
+    <div class="flex flex-wrap gap-7">
+      <div>
+        <span class="font-bold mr-2" style="color: #303133">报告类型:</span>
+        <el-select v-model="filter.layerType" style="width: 130px">
+          <el-option label="Asin View" value="asin_view"></el-option>
+          <el-option label="Brand View" value="brand_view"></el-option>
+        </el-select>
+      </div>
+      <div>
+        <span class="font-bold mr-2" style="color: #303133">报告类型:</span>
+        <el-select v-model="filter.reportType" style="width: 100px">
+          <el-option label="月度" value="MONTHLY"></el-option>
+          <el-option label="周度" value="WEEKLY"></el-option>
+        </el-select>
+      </div>
+      <div>
+        <span class="font-bold mr-2" style="color: #303133">搜索词:</span>
+        <el-input v-model="filter.searchTerm" style="width: 200px"></el-input>
+      </div>
+      <div>
+        <span class="font-bold mr-2" style="color: #303133">{{ defaultLabel }}:</span>
+        <el-input v-model="filter.variable" style="width: 200px"></el-input>
+      </div>
+      <div>
+        <span class="font-bold mr-2" style="color: #303133">报告日期:</span>
+        <el-date-picker
+          v-model="filter.reportDate"
+          type="daterange"
+          range-separator="To"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="YYYY-MM-DD"
+          :disabled-date="(time: Date) => time > new Date()"/>
+      </div>
+    </div>
+    <div class="flex gap-3.5">
+      <el-button type="primary" plain :icon="Search" @click="handleQuery">查询</el-button>
+      <el-button type="warning" plain round :icon="RefreshLeft" color="#0891b2" @click="resetCondition">重置</el-button>
+    </div>
+  </el-card>
+</template>
+
+<style scoped></style>

+ 11 - 0
src/views/searchTerm/analysisPage/api.ts

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

+ 27 - 0
src/views/searchTerm/analysisPage/index.vue

@@ -0,0 +1,27 @@
+<script setup lang="ts">
+/**
+ * @Name: index.vue
+ * @Description: 分析页
+ * @Author: Cheney
+ */
+import { onMounted, provide, ref } from 'vue';
+import QueryCondition from './QueryCondition.vue';
+import IndicatorOverview from './IndicatorOverview.vue';
+
+const queryConditionData = ref();
+provide('queryConditionData', queryConditionData);
+
+function handleDataUpdate(data: any) {
+  queryConditionData.value = data;
+}
+
+</script>
+
+<template>
+  <div class="py-2 px-2.5">
+    <QueryCondition @dataFetched="handleDataUpdate" />
+    <IndicatorOverview />
+  </div>
+</template>
+
+<style scoped></style>

+ 1 - 1
src/views/searchTerm/importPage/api.ts

@@ -1,6 +1,6 @@
 import { request } from '/@/utils/service';
 
-const apiPrefix = '/api/searchterm/'
+const apiPrefix = '/api/searchterm/';
 
 export function uploadFile(file: any) {
   const formData = new FormData();

+ 4 - 6
src/views/searchTerm/importPage/index.vue

@@ -4,7 +4,7 @@
  * @Description: 导入页
  * @Author: Cheney
  */
-import { Plus, Upload, View } from '@element-plus/icons-vue';
+import { Upload, View } from '@element-plus/icons-vue';
 import { reactive, ref } from 'vue';
 import { ElMessage, genFileId, UploadInstance, UploadRawFile } from 'element-plus';
 import { SUCCESS_CODE, WARNING_CODE } from '/@/utils/requestCode';
@@ -13,13 +13,13 @@ import { VxeGridProps } from 'vxe-table';
 
 const upload = ref<UploadInstance>();
 const upBtnLoading = ref(false);
+const defaultLabel = ref('ASIN');
 const filter = reactive({
   reportFilter: '',
   reportDateFilter: '',
   typeFilter: '',
   variableFilter: '',
 });
-const defaultLabel = ref('ASIN');
 
 const gridOptions = reactive<VxeGridProps>({
   loading: upBtnLoading,
@@ -151,7 +151,7 @@ async function confirmUpload() {
 <template>
   <div class="py-2 px-2.5" 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 class="flex flex-wrap gap-7">
         <div>
           <span class="font-bold mr-2" style="color: #303133">报告类型:</span>
           <el-input v-model="filter.reportFilter" :disabled="true" style="width: 200px"></el-input>
@@ -185,9 +185,7 @@ async function confirmUpload() {
             </template>
           </el-upload>
         </div>
-        <el-button plain round type="warning" :icon="Upload" @click="confirmUpload">
-          确认导入
-        </el-button>
+        <el-button plain round type="warning" :icon="Upload" @click="confirmUpload"> 确认导入 </el-button>
       </div>
     </el-card>
     <el-card shadow="hover" style="border: none">