Browse Source

✨ feat: 商品分析: 销售概览数据展示

WanGxC 1 year ago
parent
commit
8bed764bbe

+ 7 - 0
src/views/productCenter/productAnalysis/api.ts

@@ -79,3 +79,10 @@ export function getListData(query) {
     params: query,
   })
 }
+export function getSalesListData(query) {
+  return request({
+    url: '/api/sellers/product/sales/',
+    method: 'GET',
+    params: query,
+  })
+}

+ 2 - 2
src/views/productCenter/productAnalysis/components/DataTable/index.vue

@@ -3,7 +3,7 @@
     <vxe-grid v-bind="gridOptions">
       <template v-for="col in universalColumns" #[`${col.field}_default`]="{ row }">
         <div>
-          {{ row[col.field] }}
+          {{ row[col.field] ? row[col.field] : '-'}}
         </div>
       </template>
       <!-- <template v-for="col in dynamicCols" #[`${col.field}_footer`]="{ items, _columnIndex }">
@@ -24,7 +24,7 @@
 
 <script setup lang="ts">
 import { inject, onMounted, reactive, ref, watch } from 'vue'
-import { VXETable } from 'vxe-table'
+// import { VXETable } from 'vxe-table'
 import { getListData } from '../../api'
 import { universalColumns } from '../../utils/columns'
 

+ 219 - 0
src/views/productCenter/productAnalysis/components/SalesOverview/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <div style="overflow: hidden; width: 100%; height: 800px" v-loading="tableLoading">
+    <vxe-grid v-bind="gridOptions">
+      <template #toolbar_buttons>
+        <vxe-input v-model="searchInp" @search-click="handleSearch" @keydown="handleKeydown" placeholder="快速查询ASIN、SKU或商品标题" type="search" style="width: 300px;"></vxe-input>
+        <vxe-select v-model="selectVal" @change="handleSelect" style="margin-left: 10px">
+          <vxe-option v-for="num in 15" :key="num" :value="num" :label="`选项${num}`"></vxe-option>
+        </vxe-select>
+      </template>
+
+      <template v-for="col in salesColumns" #[`${col.field}_default`]="{ row }">
+        <div>
+          <template v-if="col.field == 'sku'">
+            <div class="list-content">
+              <div class="image-item">
+                <img v-if="row.Image" :src="row.Image" />
+                <div v-else>
+                  <el-image>
+                    <template #error>
+                      <div class="image-slot">
+                        <el-icon><icon-picture /></el-icon>
+                      </div>
+                    </template>
+                  </el-image>
+                </div>
+              </div>
+              <div>
+                <el-tooltip effect="dark" :content="row.Title" placement="top-start">
+                  <span class="item-title">{{ row.Title ? row.Title : '--' }}</span>
+                </el-tooltip>
+                <div>
+                  <span class="item-font">{{ row.quantity ? '有库存' : '缺货' }}</span>
+                </div>
+                <span class="item-font">
+                  ASIN: <span class="black-color">{{ row.Asin }}</span>
+                  </span>
+                <el-tooltip effect="dark" :content="row.sku" placement="top-start">
+                  <span class="item-font display-line">
+                    SKU: <span class="black-color">{{ row.sku }}</span>
+                  </span>
+                </el-tooltip>
+              </div>
+            </div>
+          </template>
+          <template v-else>
+            {{ row[col.field] ? row[col.field] : '-' }}
+          </template>
+        </div>
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { inject, onMounted, reactive, ref, watch } from 'vue'
+// import { VXETable } from 'vxe-table'
+import { getSalesListData } from '../../api'
+import { salesColumns } from '../../utils/columns'
+import emitter from '/@/utils/emitter'
+import { Picture as IconPicture } from '@element-plus/icons-vue'
+
+const profile = <any>inject('profile')
+const dateRange = <any>inject('dateRange')
+const tableData = <any>inject('tableData')
+const parentAsin = ref('')
+const childAsin = ref('')
+const sku = ref('')
+
+// 根据不同的点击事件发送请求获取数据
+emitter.on('useSelectItem-clickParentAsinBtn', (value: any) => {
+  parentAsin.value = value.selectedParentAsin
+  fetchListData()
+})
+
+emitter.on('useSelectItem-clickAsinBtn', (value: any) => {
+  parentAsin.value = value.selectedParentAsin
+  childAsin.value = value.selectedAsin
+  fetchListData()
+})
+
+emitter.on('useSelectItem-clickSkuItem', (value: any) => {
+  parentAsin.value = value.selectedParentAsin
+  childAsin.value = value.selectedAsin
+  sku.value = value.selectedSku
+  fetchListData()
+})
+
+const tableLoading = ref(false)
+const searchInp = ref('')
+const selectVal = ref('')
+
+const gridOptions = reactive({
+  height: 'auto',
+  border: false,
+  round: true,
+  showFooter: true,
+
+  columnConfig: {
+    resizable: true,
+  },
+  toolbarConfig: {
+    custom: true,
+    slots: {
+      buttons: 'toolbar_buttons',
+    },
+  },
+  columns: [],
+  data: [],
+})
+
+async function fetchListData() {
+  tableLoading.value = true
+  const query = {
+    profileId: profile.value.profile_id,
+    startDate: dateRange.value[0],
+    endDate: dateRange.value[1],
+    parentAsin: parentAsin.value,
+    childAsin: childAsin.value,
+    sku: sku.value,
+  }
+  try {
+    const res = await getSalesListData(query)
+    gridOptions.columns = salesColumns
+    gridOptions.data = res.data
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    tableLoading.value = false
+  }
+}
+
+// watch(tableData, () => {
+//   // console.log('tableData.value', tableData.value)
+//   gridOptions.data = tableData.value
+// })
+
+function handleSearch() {
+  tableLoading.value = true
+  // const query = {}
+  console.log(1213)
+  try {
+    // const res = await xxx(query)
+    // gridOptions.data = res.data
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+  tableLoading.value = true
+  }
+}
+
+function handleKeydown(event) {
+  if (event.$event.key === 'Enter') {
+    handleSearch()
+  }
+}
+
+function handleSelect() {
+  handleSearch()
+}
+
+onMounted(() => {
+  fetchListData()
+})
+</script>
+
+<style scoped>
+.list-content {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.image-item {
+  min-width: 72px;
+  height: 72px;
+}
+.image-item img {
+  min-width: 72px;
+  height: 72px;
+  border: 1px solid #dde0eb;
+  border-radius: 4px;
+}
+.item-title {
+  font-weight: 500;
+  color: #1d2129;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: var(--line-clamp);
+  white-space: pre-wrap;
+  --line-clamp: 1;
+}
+.item-font {
+  color: #6b7785;
+  font-size: 13px;
+  line-height: 13px;
+}
+.display-line {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: var(--line-clamp);
+  white-space: pre-wrap;
+  --line-clamp: 1;
+}
+
+.image-slot {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 72px;
+  height: 72px;
+  border: 1px solid #dde0eb;
+  border-radius: 4px;
+  background: var(--el-fill-color-light);
+  color: var(--el-text-color-secondary);
+  font-size: 30px;
+}
+
+</style>

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

@@ -19,7 +19,9 @@
           <DataTable></DataTable>
         </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="salesOverview" lazy>
+          <SalesOverview></SalesOverview>
+        </el-tab-pane>
         <el-tab-pane label="竞品推荐" name="cpRecommendations">竞品推荐</el-tab-pane>
       </el-tabs>
     </div>
@@ -28,13 +30,14 @@
 
 <script setup lang="ts">
 import { storeToRefs } from 'pinia'
-import { provide, ref, watch } from 'vue'
+import { provide, ref } from 'vue'
+import DataTable from './components/DataTable/index.vue'
 import TopParentAsin from './components/TopParentAsin/index.vue'
 import TrendOverview from './components/TrendOverview/index.vue'
+import SalesOverview from './components/SalesOverview/index.vue'
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 import { usePublicData } from '/@/stores/publicData'
 import { useShopInfo } from '/@/stores/shopInfo'
-import DataTable from './components/DataTable/index.vue'
 
 // 店铺信息
 const shopInfo = useShopInfo()

+ 32 - 34
src/views/productCenter/productAnalysis/utils/columns.ts

@@ -1,5 +1,5 @@
 export const universalColumns = [
-  { field: 'Name', title: '日期', align: 'right', width: 130 },
+  { field: 'Name', title: '日期', align: 'left', width: 130 },
   { field: 'TotalSales', title: '总销售额', align: 'right', width: 130, sortable: true, slots: { default: 'TotalSales_default' } },
   { field: 'TotalOrderItems', title: '总订单数', align: 'right', width: 130, sortable: true, slots: { default: 'TotalOrderItems_default' } },
   { field: 'TotalUnitOrdered', title: '总销量', align: 'right', width: 130, sortable: true, slots: { default: 'TotalUnitOrdered_default' } },
@@ -25,38 +25,36 @@ export const universalColumns = [
   { field: 'ProductCr', title: '商品会话百分比', align: 'right', width: 150, sortable: true, slots: { default: 'ProductCr_default' } },
   { field: 'PageViews', title: '页面浏览量', align: 'right', width: 130, sortable: true, slots: { default: 'PageViews_default' } },
   { field: 'BuyBoxPercentage', title: '推荐报价(购买按钮)百分比', align: 'right', width: 180, sortable: true, showHeaderOverflow: true, slots: { default: 'BuyBoxPercentage_default' } },
-  { field: 'FBAQuantity', title: 'FBA库存', align: 'right', width: 130, sortable: true },
-  { field: 'FBMQuantity', title: 'FBM库存', align: 'right', width: 130, sortable: true, showHeaderOverflow: true },
 ]
 
-// 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' } },
-// ]
+export const salesColumns = [
+  { field: 'sku', title: 'SKU', align: 'left', width: 260, slots: { default: 'sku_default' } },
+  { field: 'productlineName', title: '产品线', align: 'left', width: 180, sortable: true, slots: { default: 'productlineName_default' } },
+  { field: 'status', title: '商品状态', align: 'center', width: 80, slots: { default: 'status_default' } },
+  { field: 'TotalSales', title: '总销售额', align: 'right', width: 130, sortable: true, slots: { default: 'TotalSales_default' } },
+  { field: 'TotalUnitOrdered', title: '总销量', align: 'right', width: 130, sortable: true, slots: { default: 'TotalUnitOrdered_default' } },
+  { field: 'TotalOrderItems', title: '总订单数', align: 'right', width: 130, sortable: true, slots: { default: 'TotalOrderItems_default' } },
+  { field: 'SAP', title: '单均价', align: 'right', width: 130, sortable: true, slots: { default: 'SAP_default' } },
+  { field: 'TotalAdSales', title: '广告销售额', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdSales_default' } },
+  { field: 'TotalAdSalesSameSKU', title: '本商品广告销售额', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdSalesSameSKU_default' } },
+  { field: 'TotalAdPurchases', title: '广告订单数', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdPurchases_default' } },
+  { field: 'TotalAdPurchasesSameSKU', title: '本商品广告订单数', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdPurchasesSameSKU_default' } },
+  { field: 'TotalAdUnitOrdered', title: '广告销量', align: 'right', width: 130, sortable: true, slots: { default: 'TotalAdUnitOrdered_default' } },
+  { field: 'TotalAdUnitOrderedSameSKU', title: '本商品广告销量', align: 'right', width: 180, sortable: true, slots: { default: 'TotalAdUnitOrderedSameSKU_default' } },
+  { field: 'Spend', title: '花费', align: 'right', width: 130, sortable: true, showOverflow: true, slots: { default: 'Spend_default' } },
+  { field: 'ACOS', title: 'ACOS', align: 'right', width: 130, sortable: true, slots: { default: 'ACOS_default' } },
+  { field: 'ROAS', title: 'ROAS', align: 'right', width: 130, sortable: true, slots: { default: 'ROAS_default' } },
+  { field: 'TACOS', title: 'TACOS', align: 'right', width: 130, sortable: true, slots: { default: 'TACOS_default' } },
+  { field: 'CR', title: '转化率', align: 'right', width: 130, sortable: true, slots: { default: 'CR_default' } },
+  { field: 'CTR', title: '点击率', align: 'right', width: 130, sortable: true, slots: { default: 'CTR_default' } },
+  { field: 'CPC', title: '点击成本', align: 'right', width: 130, sortable: true, slots: { default: 'CPC_default' } },
+  { field: 'CPO', title: '总订单成本', align: 'right', width: 130, sortable: true, slots: { default: 'CPO_default' } },
+  { field: 'CPA', title: '广告订单成本', align: 'right', width: 180, sortable: true, slots: { default: 'CPA_default' } },
+  { field: 'Impression', title: '曝光量', align: 'right', width: 130, sortable: true, slots: { default: 'Impression_default' } },
+  { field: 'Click', title: '点击量', align: 'right', width: 130, sortable: true, slots: { default: 'Click_default' } },
+  { field: 'Sessions', title: '会话次数', align: 'right', width: 150, sortable: true, slots: { default: 'Sessions_default' } },
+  { field: 'ProductCr', title: '商品会话百分比', align: 'right', width: 150, sortable: true, slots: { default: 'ProductCr_default' } },
+  { field: 'PageViews', title: '页面浏览量', align: 'right', width: 130, sortable: true, slots: { default: 'PageViews_default' } },
+  { field: 'BuyBoxPercentage', title: '推荐报价(购买按钮)百分比', align: 'right', width: 180, sortable: true, showHeaderOverflow: true, slots: { default: 'BuyBoxPercentage_default' } },
+  { field: 'quantity', title: '库存', align: 'right', width: 180, sortable: true, showHeaderOverflow: true, slots: { default: 'quantity_default' } },
+]