Browse Source

🦄 refactor: 文件重构

WanGxC 1 year ago
parent
commit
edd95b006b

+ 71 - 5
src/views/productCenter/productAnalysis/api.ts

@@ -1,10 +1,13 @@
 import { request } from '/@/utils/service'
 import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud'
+import emitter from '/@/utils/emitter'
 
-export const apiPrefix = '/api/ad_manage/sbcampaigns/'
+
+export const apiPrefix = '/api/sellers/home/daily/'
 export function getCardData(query: UserPageQuery) {
+
   return request({
-    url: apiPrefix + 'total/',
+    url: '/api/sellers/home/total/',
     method: 'GET',
     params: query,
   })
@@ -12,7 +15,7 @@ export function getCardData(query: UserPageQuery) {
 export function getLineData(query: UserPageQuery) {
   query['dateRangeType'] = 'D'
   return request({
-    url: apiPrefix + 'daily/',
+    url: apiPrefix,
     method: 'GET',
     params: query,
   })
@@ -21,7 +24,7 @@ export function getLineData(query: UserPageQuery) {
 export function getLineWeekData(query: UserPageQuery) {
   query['dateRangeType'] = 'W'
   return request({
-    url: apiPrefix + 'daily/',
+    url: apiPrefix,
     method: 'GET',
     params: query,
   })
@@ -30,7 +33,7 @@ export function getLineWeekData(query: UserPageQuery) {
 export function getLineMonthData(query: UserPageQuery) {
   query['dateRangeType'] = 'M'
   return request({
-    url: apiPrefix + 'daily/',
+    url: apiPrefix,
     method: 'GET',
     params: query,
   })
@@ -43,3 +46,66 @@ export function getProductLineSelect(query) {
     params: query,
   })
 }
+export function getAllProduct(query) {
+  return request({
+    url: '/api/sellers/goods/all/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function postCreateProductLine(body) {
+  return request({
+    url: '/api/sellers/productline/create/',
+    method: 'POST',
+    data: body,
+  })
+}
+export function getProductCardData(query) {
+  return request({
+    url: '/api/sellers/productline/sales/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function getProductDetail(query) {
+  return request({
+    url: '/api/sellers/productlinedetail/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function postUpdateProductLine(body) {
+  return request({
+    url: '/api/sellers/productlinedetail/update/',
+    method: 'POST',
+    data: body,
+  })
+}
+export function getTableDataForSKU(query) {
+  return request({
+    url: '/api/sellers/home/sku/list/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function getTableDataForProductLine(query) {
+  return request({
+    url: '/api/sellers/home/productline/list/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function getTableDataForParentASIN(query) {
+  return request({
+    url: '/api/sellers/home/pasin/list/',
+    method: 'GET',
+    params: query,
+  })
+}
+export function getTableDataForASIN(query) {
+  return request({
+    url: '/api/sellers/home/asin/list/',
+    method: 'GET',
+    params: query,
+  })
+}

+ 325 - 0
src/views/productCenter/productAnalysis/components/DateTendency/index.vue

@@ -0,0 +1,325 @@
+<template>
+  <div v-loading="loading">
+    <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
+    <el-radio-group v-model="statDim" class="chart-button-group" @change="changeStatDim">
+      <el-radio-button label="day">日</el-radio-button>
+      <el-radio-button label="week" :disabled="!props.fetchLineWeek">周</el-radio-button>
+      <el-radio-button label="month" :disabled="!props.fetchLineWeek">月</el-radio-button>
+    </el-radio-group>
+    <div style="height: 350px" ref="chartRef"></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, onBeforeUnmount, Ref, unref, watch, computed } from 'vue'
+import * as echarts from 'echarts'
+import { productListMetricsEnum } from '/@/views/productCenter/productList/utils/enum'
+// import MetricsCards from '/@/components/MetricsCards/index.vue'
+import MetricsCards from '../MetricsCards/index.vue'
+import XEUtils from 'xe-utils'
+import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
+
+
+defineOptions({
+  name: 'DataTendencyChart',
+})
+
+interface Props {
+  fetchCard: Function
+  fetchLine: Function
+  fetchLineMonth?: Function
+  fetchLineWeek?: Function
+  query: { [key: string]: any }
+  initMetric?: ShowMetric[]
+  metricEnum?: { [key: string]: string }[]
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  initMetric: () => [
+    { metric: 'Impression', color: '#0085ff', label: '曝光量' },
+    { metric: 'Click', color: '#3fd4cf', label: '点击量' },
+    { metric: 'Spend', color: '#ff9500', label: '花费' },
+  ],
+  metricEnum: () => productListMetricsEnum,
+})
+
+const metrics = ref(props.initMetric)
+
+const metricsItems: Ref<MetricData[]> = ref([])
+let chartObj: any
+const chartRef = ref()
+const statDim = ref('day')
+const option: any = {
+  dataset: {
+    source: [],
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985',
+      },
+    },
+  },
+  legend: {
+    selected: {}, // 控制显隐
+    show: false,
+  },
+  grid: {
+    top: 50,
+    right: 150,
+    bottom: 30,
+    left: 65,
+  },
+  xAxis: {
+    type: 'category',
+  },
+  yAxis: [
+    {
+      id: 0,
+      type: 'value',
+      name: '',
+      splitLine: {
+        show: true, // 设置显示分割线
+      },
+      axisLine: {
+        show: true,
+        lineStyle: { color: '' },
+      },
+      show: true,
+    },
+    {
+      id: 1,
+      type: 'value',
+      name: '',
+      position: 'right',
+      splitLine: {
+        show: false,
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '',
+        },
+      },
+      show: true,
+    },
+    {
+      id: 2,
+      type: 'value',
+      position: 'right',
+      offset: 90,
+      name: '',
+      splitLine: {
+        show: false,
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '',
+        },
+      },
+      show: true,
+    },
+  ],
+  series: [
+    {
+      id: 0,
+      name: '',
+      type: 'bar',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      barWidth: '18px',
+      yAxisIndex: 0,
+      itemStyle: {
+        color: '',
+        borderRadius: 4,
+      },
+    },
+    {
+      id: 1,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 1,
+      itemStyle: {
+        // color: '#ff9500',
+        // borderColor: '#ff9500'
+      },
+      areaStyle: {
+        // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+        //   { offset: 0, color: '#3fd4cf53' },
+        //   { offset: 1, color: '#3fd4cf03' },
+        // ]),
+      },
+      emphasis: {
+        focus: 'series',
+      },
+    },
+    {
+      id: 2,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 2,
+      itemStyle: {},
+      areaStyle: {},
+      emphasis: {
+        focus: 'series',
+      },
+    },
+  ],
+}
+const loading = ref(true)
+const queryParams = computed(() => parseQueryParams(props.query))
+
+onMounted(() => {
+  getMetricsItems()
+  addResize()
+  // initLine()
+  setTimeout(() => {
+    initLine()
+  }, 0)
+})
+onBeforeUnmount(() => {
+  if (chartObj) {
+    chartObj.dispose()
+    chartObj = null
+  }
+  removeResize()
+})
+
+const initLine = async () => {
+  chartObj = echarts.init(chartRef.value)
+  const items = await getDataset()
+  option.dataset.source = items
+
+  XEUtils.arrayEach(option.series, (info: any, index) => {
+    const color = metrics.value[index].color
+    info.name = metrics.value[index].label
+    info.encode.y = metrics.value[index].metric
+    if (info.type === 'bar') {
+      info.itemStyle.color = color
+    } else {
+      info.itemStyle = { color: color, borderColor: color }
+      info.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: color + '53' },
+          { offset: 1, color: color + '03' },
+        ]),
+      }
+    }
+  })
+  XEUtils.arrayEach(option.yAxis, (info: any, index) => {
+    info.name = metrics.value[index].label
+    info.axisLine.lineStyle.color = metrics.value[index].color
+  })
+
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    option.legend.selected[info.label] = false
+  })
+  for (const info of metrics.value) {
+    option.legend.selected[info.label] = true
+  }
+  // console.log(option)
+  chartObj.setOption(option)
+  loading.value = false
+}
+
+const getDataset = async () => {
+  if (statDim.value === 'week') {
+    if (props.fetchLineWeek) {
+      const resp = await props.fetchLineWeek(queryParams.value)
+      return resp.data
+    }
+  } else if (statDim.value === 'month') {
+    if (props.fetchLineMonth) {
+      const resp = await props.fetchLineMonth(queryParams.value)
+      return resp.data
+    }
+  } else {
+    const resp = await props.fetchLine(queryParams.value)
+    return resp.data
+  }
+}
+
+const getMetricsItems = async () => {
+  const resp = await props.fetchCard(queryParams.value)
+  const data = resp.data
+  metricsItems.value.length = 0
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    const tmp: MetricData = {
+      label: info.label,
+      value: info.value,
+      metricVal: data[info.value],
+      gapVal: data[`gap${info.value}`],
+      preVal: data[`prev${info.value}`],
+    }
+    metricsItems.value.push(tmp)
+  })
+}
+
+const changeMetric = () => {
+  const opt = buildChartOpt(option, metrics.value)
+  chartObj.setOption(opt)
+}
+
+const changeStatDim = async () => {
+  loading.value = true
+  let source = await getDataset()
+  if (source.length > 0) {
+    chartObj.setOption({ dataset: { source: source } })
+  }
+  loading.value = false
+}
+
+watch(props.query, async () => {
+  // console.log("------watch-----queryParams", props.query)
+  loading.value = true
+  await getMetricsItems()
+  const items = await getDataset()
+  const opt = { dataset: { source: items } }
+  chartObj.setOption(opt)
+  loading.value = false
+})
+
+const resizeChart = () => {
+  chartObj.resize()
+}
+const addResize = () => {
+  window.addEventListener('resize', resizeChart)
+}
+const removeResize = () => {
+  window.removeEventListener('resize', resizeChart)
+}
+</script>
+
+<style scoped>
+.metrics-cards {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 12px;
+  width: 100%;
+}
+
+.chart-button-group {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 5px;
+}
+</style>

+ 149 - 0
src/views/productCenter/productAnalysis/components/MetricsCards/index.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="metrics-cards">
+    <MCard
+     v-model="info.metric"
+     :metric-items="props.metricItems"
+     :color="info.color"
+     v-for="info in displayMetrics"
+     @change-metric="changedMetric"
+     @click="clickCard(info.metric)"/>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, Ref, onBeforeMount, watch, onMounted, computed } from 'vue'
+import MCard from './mCard.vue'
+import XEUtils from 'xe-utils';
+
+interface Props {
+  modelValue: ShowMetric[],
+  metricItems: MetricData[],
+}
+const colorsMap: { [key: string]: boolean } = {}
+const props = defineProps<Props>()
+const emits = defineEmits(['change', 'update:modelValue'])
+// const allMetricItems = ref(props.metricItems)
+const selectedMetric = ref(props.modelValue)
+const displayMetrics: Ref<{metric:string, color?: string}[]> = ref([])
+
+const metricMap = computed(():{[key: string]: string} => {
+  const tmp:{[key: string]: string} = {}
+  for (const info of props.metricItems) {
+    tmp[info.value] = info.label
+  }
+  return tmp
+})
+onBeforeMount(()=> {
+  const dup:{[key: string]: boolean} = {}
+  // 初始显示图线的三个维度
+  for (const info of selectedMetric.value) {
+    displayMetrics.value.push({ metric: info.metric, color: info.color })
+    dup[info.metric] = true
+    if (info.color) { colorsMap[info.color] = true }
+  }
+})
+
+const getColor = () => {
+  for (const [k,v] of Object.entries(colorsMap)) {
+    if (!v) {
+      colorsMap[k] = true
+      return k
+    }
+  }
+  return ""
+}
+const unsetColor = (color: string ) => {
+  if (XEUtils.has(colorsMap, color)) {
+    colorsMap[color] = false
+  }
+}
+const changedMetric = (newVal: string, oldVal: string) => {
+  for (const info of props.metricItems) {
+    if (info.value === newVal) {
+      info.disabled = true 
+    } else if (info.value === oldVal) {
+      info.disabled = false
+    }
+  }
+  const index = selectedMetric.value.findIndex( info => info.metric === oldVal)
+  if (index > -1) {
+    selectedMetric.value[index].metric = newVal
+    selectedMetric.value[index].label = metricMap.value[newVal]
+    emits('update:modelValue', selectedMetric.value)
+    emits('change', selectedMetric.value)
+  }
+}
+const clickCard = (metric: string) => {
+  const index = selectedMetric.value.findIndex( info => info.metric === metric)
+  if (index > -1) {  // 已存在则删除
+    if (selectedMetric.value.length <= 1 ) return
+    const tmp = selectedMetric.value[index]
+    selectedMetric.value.splice(index, 1)
+    unsetColor(tmp.color)
+    emits('update:modelValue', selectedMetric.value)
+    emits('change', selectedMetric.value)
+  } else {  // 不存在则添加
+    if (selectedMetric.value.length === 3) { 
+      selectedMetric.value[2].metric = metric
+      selectedMetric.value[2].label = metricMap.value[metric]
+    } else {
+      const color = getColor()
+      selectedMetric.value.push({ metric: metric, color: color, label: metricMap.value[metric]})
+    }
+    emits('update:modelValue', selectedMetric.value)
+    emits('change', selectedMetric.value)
+  }
+}
+watch(selectedMetric.value, () => {
+  const cache:{ [key: string]: string } = {}
+  for (const info of selectedMetric.value) {
+    cache[info.metric] = info.color
+  }
+  for (const info of displayMetrics.value) {
+    const color = cache[info.metric]
+    if (color) {
+      info.color = color
+    } else {
+      info.color = undefined
+    }
+  }
+})
+
+watch(
+  props.metricItems,
+  () => {
+    const dup:{[key: string]: boolean} = {}
+    for (const info of displayMetrics.value) { dup[info.metric] = true }
+    let needNum = 6 - displayMetrics.value.length
+    if (needNum > 0) {  
+      // 从所有维度中选择剩余
+      for (const info of props.metricItems) {
+        if (!dup[info.value]) {
+          displayMetrics.value.push({ metric: info.value })
+          dup[info.value] = true
+          needNum --
+          if (needNum === 0) break
+        }
+      }
+    }
+    for (const info of props.metricItems) {
+      if (dup[info.value]) {
+        info.disabled = true
+      } else {
+        info.disabled = false
+      }  
+    }
+  }
+)
+
+</script>
+
+<style scoped>
+.metrics-cards {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 12px;
+  width: 100%;
+}
+</style>

+ 100 - 0
src/views/productCenter/productAnalysis/components/MetricsCards/mCard.vue

@@ -0,0 +1,100 @@
+<template>
+  <el-card class="metric-card">
+    <div class="metric-card__color" :style="boardTopStyle"></div>
+    <TextSelector v-model="metric" :options="props.metricItems" @change="changeMetric"></TextSelector>
+    <div class="metric-value">{{ selectedData?.metricVal }}</div>
+    <div class="metric-pre">
+      <span>{{ selectedData?.preVal }}&nbsp;&nbsp;</span>
+      <el-icon v-show="selectedData?.gapVal" style="display: inline-block; padding-top: 2px">
+        <Top :class="colorClass" v-if="isBoost"/>
+        <Bottom :class="colorClass" v-else/>
+      </el-icon>
+      <span :class="colorClass">{{ selectedData?.gapVal ? selectedData?.gapVal + '%' : '' }}</span>
+    </div>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed } from 'vue'
+import TextSelector from '/@/components/TextSelector/index.vue'
+
+defineOptions({
+  name: 'MCard'
+})
+
+interface Props {
+  modelValue: string,
+  metricItems: MetricData[],
+  color?: string,
+}
+const props = defineProps<Props>()
+const emits = defineEmits(["update:modelValue", "change-metric"])
+const metric = ref(props.modelValue)
+const changeMetric = ( newVal: string, oldVal: string) => {
+  emits('update:modelValue', newVal)
+  emits('change-metric', newVal, oldVal)
+}
+const selectedData = computed(():MetricData|null => {
+  const info = props.metricItems.find(item => item.value === metric.value)
+  if(!info) return null
+  return info
+})
+const boardTopStyle = computed(() => {
+  const style_ = { "border-top-color": "rgb(232, 244, 255)" }
+  if (props.color) { style_["border-top-color"] = props.color }
+  return style_
+})
+const isBoost = computed(():boolean => {
+  return (selectedData.value?.gapVal ?? -1) > 0
+})
+const colorClass = computed((): "green"|"red" => isBoost.value ? "green": "red")
+
+</script>
+
+<style scoped>
+:deep(.el-card__body) {
+  padding: 0;
+}
+.metric-card {
+  padding: 12px 8px;
+  height: 100px;
+
+  position: relative;
+  min-width: 150px;
+  overflow-y: hidden;
+  line-height: 1.4;
+  background-color: #fff;
+  border-radius: 10px;
+  box-shadow: 0 0 12px rgba(51,89,181,.1607843137254902);
+  cursor: pointer;
+  flex-grow: 1;
+}
+.metric-card__color {
+  position: absolute;
+  top: 0;
+  left: 8px;
+  width: calc(100% - 16px);
+  height: 0;
+  border-top: 4px solid #86909c;
+  border-left: 2px solid transparent;
+  border-right: 2px solid transparent;
+}
+.metric-value {
+  padding: 8px 0;
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 25px;
+}
+
+.metric-pre {
+  color: #6b7785;
+  font-size: 12px;
+  white-space: nowrap;
+}
+.red {
+  color: red;
+}
+.green {
+  color: #1cbc0e;
+}
+</style>

+ 7 - 1
src/views/productCenter/productAnalysis/components/TopAsin.vue → src/views/productCenter/productAnalysis/components/TopParentAsin/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="top-parent-asin">
     <el-card>
       <div>
         <div class="product-image">
@@ -20,4 +20,10 @@
   border: 1px solid e5e6eb;
   border-radius: 4px;
 }
+.top-parent-asin {
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+}
+
 </style>

+ 33 - 0
src/views/productCenter/productAnalysis/components/TrendOverview/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <el-card>
+    <DataTendencyChart :metricEnum="productListMetricsEnum" 
+      :query="queryParams" 
+      :fetchCard="getCardData"
+      :fetchLine="getLineData"
+      :fetch-line-month="getLineMonthData"
+      :fetch-line-week="getLineWeekData">
+    </DataTendencyChart>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { storeToRefs } from 'pinia'
+import { onBeforeUnmount, provide, ref, inject } from 'vue'
+import { getCardData, getLineData, getLineMonthData, getLineWeekData } from '../../api'
+import DataTendencyChart from '/@/views/productCenter/productAnalysis/components/DateTendency/index.vue'
+import { productListMetricsEnum } from '../../utils/enum'
+import { usePublicData } from '/@/stores/publicData'
+
+const profile = <any>inject('profile')
+
+// 公共数据
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const queryParams = ref({
+  profileId: profile.value.profile_id,
+  dateRange,
+  // productlineId,
+})
+</script>
+
+<style scoped></style>

+ 70 - 3
src/views/productCenter/productAnalysis/index.vue

@@ -1,15 +1,82 @@
 <template>
   <div class="outer-container">
-    <TopAsin></TopAsin>
+    <TopParentAsin></TopParentAsin>
+    <div class="filters">
+      <DateRangePicker v-model="dateRange"></DateRangePicker>
+      <div v-show="activeName == 'trendOverview'">
+        <el-select v-model="filter1" placeholder="Select" style="width: 240px; margin-right: 8px;;">
+          <el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+        <el-select v-model="filter2" placeholder="Select" style="width: 240px">
+          <el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </div>
+    </div>
+    <div>
+      <el-tabs v-model="activeName" type="border-card" class="chart-tabs">
+        <el-tab-pane label="趋势总览" name="trendOverview">
+          <TrendOverview></TrendOverview>
+        </el-tab-pane>
+        <el-tab-pane label="广告优化" name="adOptimization">广告优化</el-tab-pane>
+        <el-tab-pane label="销售概览" name="salesOverview">销售概览</el-tab-pane>
+        <el-tab-pane label="竞品推荐" name="cpRecommendations">竞品推荐</el-tab-pane>
+      </el-tabs>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import TopAsin from './components/TopAsin.vue'
+import { storeToRefs } from 'pinia'
+import { provide, ref } from 'vue'
+import TopParentAsin from './components/TopParentAsin/index.vue'
+import TrendOverview from './components/TrendOverview/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import { usePublicData } from '/@/stores/publicData'
+import { useShopInfo } from '/@/stores/shopInfo'
+
+// 店铺信息
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+// 公共数据
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+
+provide('dateRange', dateRange)
+provide('profile', profile)
+
+const activeName = ref('trendOverview')
+const filter1 = ref('')
+const options1 = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+]
+
+const filter2 = ref('')
+const options2 = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+]
 </script>
 
 <style scoped>
 .outer-container {
   padding: 5px 10px 0 10px;
 }
-</style>
+.filters {
+  display: flex;
+  gap: 8px;
+  margin-top: 5px;
+}
+</style>

+ 58 - 0
src/views/productCenter/productAnalysis/utils/columns.ts

@@ -0,0 +1,58 @@
+const universal = [
+  { field: 'TotalSales', title: '总销售额', align: 'right', width: 130, sortable: true, slots: { default: 'TotalSales_default', footer: 'TotalSales_footer' } },
+  { field: 'TotalOrderItems', title: '总订单数', align: 'right', width: 130, sortable: true, slots: { default: 'TotalOrderItems_default', footer: 'TotalOrderItems_footer' } },
+  { field: 'TotalUnitOrdered', title: '总销量', align: 'right', width: 130, sortable: true, slots: { default: 'TotalUnitOrdered_default', footer: 'TotalUnitOrdered_footer' } },
+  { field: 'SAP', title: '单均价', align: 'right', width: 130, sortable: true, slots: { default: 'SAP_default', footer: 'SAP_footer' } },
+  { field: 'TotalAdSales', title: '广告销售额', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdSales_default', footer: 'TotalAdSales_footer' } },
+  { field: 'TotalAdSalesSameSKU', title: '本商品广告销售额', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdSalesSameSKU_default', footer: 'TotalAdSalesSameSKU_footer' } },
+  { field: 'TotalAdPurchases', title: '广告订单数', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdPurchases_default', footer: 'TotalAdPurchases_footer' } },
+  { field: 'TotalAdPurchasesSameSKU', title: '本商品广告订单数', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdPurchasesSameSKU_default', footer: 'TotalAdPurchasesSameSKU_footer' } },
+  { field: 'TotalAdUnitOrdered', title: '广告销量', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdUnitOrdered_default', footer: 'TotalAdUnitOrdered_footer' } },
+  { field: 'TotalAdUnitOrderedSameSKU', title: '本商品广告销量', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdUnitOrderedSameSKU_default', footer: 'TotalAdUnitOrderedSameSKU_footer' } },
+  { field: 'Spend', title: '花费', align: 'right', width: 130, sortable: true, showOverflow: true, slots: { default: 'Spend_default', footer: 'Spend_footer' } },
+  { field: 'ACOS', title: 'ACOS', align: 'right', width: 130, sortable: true, slots: { default: 'ACOS_default', footer: 'ACOS_footer' } },
+  { field: 'ROAS', title: 'ROAS', align: 'right', width: 130, sortable: true, slots: { default: 'ROAS_default', footer: 'ROAS_footer' } },
+  { field: 'TACOS', title: 'TACOS', align: 'right', width: 130, sortable: true, slots: { default: 'TACOS_default', footer: 'TACOS_footer' } },
+  { field: 'CR', title: '转化率', align: 'right', width: 130, sortable: true, slots: { default: 'CR_default', footer: 'CR_footer' } },
+  { field: 'CTR', title: '点击率', align: 'right', width: 130, sortable: true, slots: { default: 'CTR_default', footer: 'CTR_footer' } },
+  { field: 'CPC', title: '点击成本', align: 'right', width: 130, sortable: true, slots: { default: 'CPC_default', footer: 'CPC_footer' } },
+  { field: 'CPO', title: '总订单成本', align: 'right', width: 130, sortable: true, slots: { default: 'CPO_default', footer: 'CPO_footer' } },
+  { field: 'CPA', title: '广告订单成本', align: 'right', width: 180, sortable: true, slots: { default: 'CPA_default', footer: 'CPA_footer' } },
+  { field: 'Impression', title: '曝光量', align: 'right', width: 130, sortable: true, slots: { default: 'Impression_default', footer: 'Impression_footer' } },
+  { field: 'Click', title: '点击量', align: 'right', width: 130, sortable: true, slots: { default: 'Click_default', footer: 'Click_footer' } },
+  { field: 'Sessions', title: '会话次数', align: 'right', width: 150, sortable: true, slots: { default: 'Sessions_default', footer: 'Sessions_footer' } },
+  { field: 'ProductCr', title: '商品会话百分比', align: 'right', width: 150, sortable: true, slots: { default: 'ProductCr_default', footer: 'ProductCr_footer' } },
+  { field: 'PageViews', title: '页面浏览量', align: 'right', width: 130, sortable: true, slots: { default: 'PageViews_default', footer: 'PageViews_footer' } },
+  { field: 'BuyBoxPercentage', title: '推荐报价(购买按钮)百分比', align: 'right', width: 180, sortable: true, showHeaderOverflow: true, slots: { default: 'BuyBoxPercentage_default', footer: 'BuyBoxPercentage_footer' } },
+  { field: 'FBAQuantity', title: 'FBA库存', align: 'right', width: 130, sortable: true, slots: { footer: 'FBAQuantity_footer' } },
+  { field: 'FBMQuantity', title: 'FBM库存', align: 'right', width: 130, sortable: true, showHeaderOverflow: true, slots: { footer: 'FBMQuantity_footer' } },
+]
+
+export const productLineColumns = [{ field: 'productlineName', title: '产品线', align: 'left', fixed: 'left', width: 180, sortable: true, slots: { default: 'productlineName_default', footer: 'productlineName_footer' } }, ...universal]
+
+export const parentAsinColumns = [
+  { field: 'parentAsin', title: '父ASIN', align: 'left', fixed: 'left', width: 180, sortable: true, slots: { default: 'parentAsin_default', footer: 'parentAsin_footer' } },
+  { field: 'bestSku', title: '最佳SKU', align: 'right', width: 260, sortable: true, slots: { default: 'bestSku_default', footer: 'bestSku_footer' } },
+  // { field: 'TACOS', title: '预警TACOS', align: 'right', width: 130, slots: { default: 'TACOS_default', footer: 'TACOS_footer' } },
+  ...universal,
+]
+
+export const asinColumns = [
+  { field: 'Asin', title: 'ASIN', align: 'left', fixed: 'left', width: 180, sortable: true, slots: { default: 'Asin_default', footer: 'Asin_footer' } },
+  { field: 'productlineName', title: '产品线', align: 'left', fixed: 'left', width: 180, sortable: true, slots: { default: 'productlineName_default', footer: 'productlineName_footer' } },
+  { field: 'rank', title: '排名', align: 'left', fixed: 'left', width: 120, sortable: true, slots: { default: 'rank_default', footer: 'rank_footer' } },
+  // { field: 'TACOS', title: '预警TACOS', align: 'right', width: 130, slots: { default: 'TACOS_default', footer: 'TACOS_footer' } },
+  ...universal,
+]
+
+export const skuColumns = [
+  { field: 'sku', title: 'SKU', align: 'left', fixed: 'left', width: 260, slots: { default: 'sku_default', footer: 'sku_footer' } },
+  { field: 'productlineName', title: '产品线', align: 'left', width: 180, slots: { default: 'productlineName_default', footer: 'productlineName_footer' } },
+  { field: 'status', title: '商品状态', align: 'center', width: 80, slots: { default: 'status_default', footer: 'status_footer' } },
+  { field: 'rank', title: '排名', align: 'left', width: 130, slots: { default: 'rank_default', footer: 'rank_footer' } },
+  { field: 'parentAsin', title: '父ASIN', align: 'left', width: 130, slots: { default: 'parentAsin_default', footer: 'parentAsin_footer' } },
+  { field: 'launchDatetime', title: '上架时间', align: 'center', width: 120, sortable: true, slots: { default: 'launchDatetime_default', footer: 'launchDatetime_footer' } },
+  ...universal,
+  // { field: 'ABP', title: '异常推广', align: 'right', width: 130, slots: { default: 'ABP_default', footer: 'ABP_footer' } },
+  // { field: 'TACOS', title: '预警TACOS', align: 'right', width: 130, slots: { default: 'TACOS_default', footer: 'TACOS_footer' } },
+]

+ 23 - 0
src/views/productCenter/productAnalysis/utils/enum.ts

@@ -0,0 +1,23 @@
+export const productListMetricsEnum = [
+  {label: '曝光量', value: 'Impression'},
+  {label: '点击量', value: 'Click'},
+  {label: '花费', value: 'Spend'},
+  {label: '总订单数', value: 'TotalOrderItems'},
+  {label: '总销售额', value: 'TotalSales'},
+  {label: '单均价', value: 'SAP'},
+  {label: '广告订单数', value: 'TotalAdPurchases'},
+  {label: '广告销售额', value: 'TotalAdSales'},
+  {label: '广告销量', value: 'TotalAdUnitOrdered'},
+  {label: '本商品广告销售额', value: 'TotalAdSalesSameSKU'},
+  {label: '本商品广告订单数', value: 'TotalAdPurchasesSameSKU'},
+  {label: '本商品广告销量', value: 'TotalAdUnitOrderedSameSKU'},
+  // {label: 'SAP', value: 'SAP'},
+  {label: 'ACOS', value: 'ACOS'},
+  {label: 'ROAS', value: 'ROAS'},
+  {label: 'TACOS', value: 'TACOS'},
+  {label: '转化率', value: 'CR'},
+  {label: '点击率', value: 'CTR'},
+  {label: '点击成本', value: 'CPC'},
+  {label: '总订单成本', value: 'CPO'},
+  {label: '广告订单成本', value: 'CPA'},
+]