Преглед на файлове

✨广告管理<SP>: 顶部sku模块弹窗勾选父ASIN获取对应数据;父ASIN的Table无限滚动加载;

WanGxC преди 1 година
родител
ревизия
d2cfbc21b2
променени са 4 файла, в които са добавени 352 реда и са изтрити 84 реда
  1. 13 2
      src/views/adManage/sp/api.ts
  2. 218 37
      src/views/adManage/sp/components/PopoverFilterTable.vue
  3. 8 6
      src/views/adManage/sp/components/TopFilter.vue
  4. 113 39
      src/views/demo/index.vue

+ 13 - 2
src/views/adManage/sp/api.ts

@@ -1,5 +1,6 @@
 import { request } from '/@/utils/service'
 
+
 export const apiPrefix = '/api/ad_manage/sp/search'
 
 export function getProductSelect() {
@@ -8,17 +9,27 @@ export function getProductSelect() {
     method: 'get',
   })
 }
-export function getProductline(query) {
+
+export function getProductline(query: any) {
   return request({
     url: '/api/sellers/productline/',
     method: 'get',
     params: query,
   })
 }
-export function getParentAsin(body) {
+
+export function getParentAsin(body: any) {
   return request({
     url: apiPrefix + '/pasin',
     method: 'post',
     data: body,
   })
 }
+
+export function getSearchedItem(body: any) {
+  return request({
+    url: apiPrefix,
+    method: 'post',
+    data: body,
+  })
+}

+ 218 - 37
src/views/adManage/sp/components/PopoverFilterTable.vue

@@ -1,20 +1,29 @@
 <script setup lang="ts">
-import { inject, reactive, Ref, ref } from 'vue'
-import { getParentAsin } from '../api'
+import { inject, onBeforeUnmount, reactive, Ref, ref } from 'vue'
+import { getParentAsin, getSearchedItem } from '../api'
 import { Connection, Picture as IconPicture } from '@element-plus/icons-vue'
 import emitter from '/@/utils/emitter'
 
 
 emitter.on('TopFilter', (value: any) => {
-  if (value) {
+  if (value.isVisible?.value) {
     fetchParentAsin()
+    searchType = value.productFilterSelect.value
+    updateGridColumnField()
   } else {
     gridOptions.data = []
+    gridOptionsRight.data = []
   }
 })
+
 const profile = <Ref>inject('profile')
 const parentLoading = ref(false)
+const rightLoading = ref(false)
+let page = 1
+let limit = 15
+let searchType = ''
 
+// 父ASIN表格配置
 const gridOptions = <any>reactive({
   height: 500,
   align: null,
@@ -25,30 +34,50 @@ const gridOptions = <any>reactive({
   data: [],
 })
 
+// 右侧表格配置
 const gridOptionsRight = <any>reactive({
   height: 500,
   align: null,
   columns: [
     { type: 'checkbox', width: 30 },
-    { field: 'name', title: 'name', width: '300' },
-    { field: 'role', title: 'role', width: '300' },
-    { field: 'sex', title: 'sex', width: '300' },
-    { field: 'age', title: 'age', width: '300' },
-    { field: 'address', title: 'address', width: '300' },
-  ],
-  data: [
-    { id: 10001, name: 'Test1', nickname: 'T1', role: 'Develop', sex: 'Man', age: 28, address: 'Shenzhen' },
-    { id: 10002, name: 'Test2', nickname: 'T2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
-    { id: 10003, name: 'Test3', nickname: 'T3', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
+    { field: 'sku', title: 'SKU', width: '350' },
+    { field: 'productLineName', title: '产品线', width: '150' },
+    { field: 'ordersIn_14', title: '近14天SP广告订单数', width: '180' },
+    { field: 'acosIn_14', title: '近14天SP广告ACOS', width: '180' },
   ],
+  data: [],
 })
 
+// 全选事件
+function selectAllChangeEvent(table: any) {
+  console.log(table.records)
+}
+
+// 单选事件
+function selectChangeEvent(table: any) {
+  // console.log(table.records)
+  // 被选中的所有parentAsin组成的数组
+  const selectedParentAsins = table.records.map((record: any) => record.parentAsin)
+
+  // 保留被选中的parentAsin的对应的右侧表格数据
+  gridOptionsRight.data = gridOptionsRight.data.filter((item: any) => selectedParentAsins.includes(item.parentAsin))
+  // console.log(gridOptionsRight.data)
+  // 如果勾选项的parentAsin在gridOptionsRight.data中,则发送请求获取右侧表格数据
+  if (table.row && selectedParentAsins.includes(table.row.parentAsin)) {
+    fetchSearchedItem([table.row.parentAsin])
+  }
+}
+
 async function fetchParentAsin() {
   parentLoading.value = true
-  const body = { profileId: profile.value.profile_id }
+  const body = {
+    profileId: profile.value.profile_id,
+    page,
+    limit,
+  }
   try {
     const response = await getParentAsin(body)
-    gridOptions.data = response.data
+    gridOptions.data.push(...response.data)
   } catch (error) {
     console.log('error:', error)
   } finally {
@@ -56,6 +85,50 @@ async function fetchParentAsin() {
   }
 }
 
+// 获取右侧表格数据
+async function fetchSearchedItem(parentAsin: Array<string>) {
+  rightLoading.value = true
+  const body = {
+    profileId: profile.value.profile_id,
+    searchType: searchType,
+    parentAsin,
+  }
+  try {
+    const response = await getSearchedItem(body)
+    gridOptionsRight.data.push(...response.data)
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    rightLoading.value = false
+  }
+}
+
+function handleScroll(event: any) {
+  const target = event.$event.target
+  // 计算是否达到底部:容器高度 + 滚动顶部距离 >= 滚动内容的总高度
+  if (target.scrollHeight - (target.scrollTop + target.clientHeight) < 1) {
+    page += 1
+    fetchParentAsin()
+  }
+}
+
+// 切换搜索类型时,更新右侧表格字段
+function updateGridColumnField() {
+  // 直接映射现有的columns,只修改需要改变的部分
+  gridOptionsRight.columns = gridOptionsRight.columns.map((column: any) => {
+    if (column.field === 'sku' || column.field === 'asin') {
+      const newField = searchType === 'sku' ? 'sku' : 'asin'
+      const newTitle = searchType === 'sku' ? 'SKU' : 'ASIN'
+
+      // 返回更新后的列配置
+      return { ...column, field: newField, title: newTitle, slots: { default: `${ newField }_default` } }
+    }
+    // 对于不需要更改的列,直接返回原配置
+    return column
+  })
+}
+
+// 更改表头样式
 function changeHeaderCellStyle() {
   return {
     // backgroundColor: '#eef0f7',
@@ -63,27 +136,32 @@ function changeHeaderCellStyle() {
   }
 }
 
-// onMounted(() => {
-//   fetchParentAsin()
-// })
+onBeforeUnmount(() => {
+  emitter.all.clear()
+})
 </script>
 
 <template>
   <div class="flex">
     <vxe-grid
-      v-loading="parentLoading"
-      v-bind="gridOptions"
-      :header-row-style="changeHeaderCellStyle"
-      :header-cell-style="changeHeaderCellStyle"
-      class="ml-2 mb-2 mt-0 border border-r-0 rounded-bl-md rounded-tl-md overflow-hidden"
-      style="flex: 0 1 260px">
+        v-loading="parentLoading"
+        v-bind="gridOptions"
+        :header-row-style="changeHeaderCellStyle"
+        :header-cell-style="changeHeaderCellStyle"
+        @checkbox-all="selectAllChangeEvent"
+        @checkbox-change="selectChangeEvent"
+        class="ml-2 mb-2 mt-0 border border-r-0 rounded-bl-md rounded-tl-md overflow-hidden"
+        style="flex: 0 1 260px; overflow: auto"
+        @scroll.native="handleScroll">
       <template #parentAsin_default="{ row }">
         <div style="display: flex">
-          <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain" />
+          <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain"/>
           <el-image v-else class="w-12 h-12 mr-1 border rounded">
             <template #error>
               <div class="image-slot">
-                <el-icon><icon-picture /></el-icon>
+                <el-icon>
+                  <icon-picture/>
+                </el-icon>
               </div>
             </template>
           </el-image>
@@ -93,12 +171,12 @@ function changeHeaderCellStyle() {
             <div>
               ASIN:<span class="text-black">{{ row.asinNum }}</span>
               <el-link
-                :href="row.amazonUrl"
-                target="_blank"
-                :disabled="!row.amazonUrl"
-                :underline="false"
-                :icon="Connection"
-                :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
+                  :href="row.amazonUrl"
+                  target="_blank"
+                  :disabled="!row.amazonUrl"
+                  :underline="false"
+                  :icon="Connection"
+                  :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
               </el-link>
               <span class="text-gray-300 mx-1">|</span>
               SKU:<span class="text-black">{{ row.skuNum }}</span>
@@ -107,11 +185,102 @@ function changeHeaderCellStyle() {
         </div>
       </template>
     </vxe-grid>
+
     <vxe-grid
-      v-bind="gridOptionsRight"
-      :header-cell-style="changeHeaderCellStyle"
-      class="mr-2 mb-2 mt-0 border rounded-br-md rounded-tr-md overflow-hidden w-0"
-      style="flex: 2 1 0">
+        v-loading="rightLoading"
+        v-bind="gridOptionsRight"
+        :header-cell-style="changeHeaderCellStyle"
+        class="mr-2 mb-2 mt-0 border rounded-br-md rounded-tr-md overflow-hidden w-0"
+        style="flex: 2 1 0">
+      <template v-if="searchType === 'sku'" #sku_default="{ row }">
+        <div style="display: flex; align-items: center">
+          <el-image class="w-14 h-14 mr-1 border rounded" style="min-width: 56px; min-height: 56px" v-if="row.Image" :src="row.Image" alt=""
+                    fit="contain"/>
+          <el-image v-else class="w-12 h-12 mr-1 border rounded">
+            <template #error>
+              <div class="image-slot">
+                <el-icon>
+                  <icon-picture/>
+                </el-icon>
+              </div>
+            </template>
+          </el-image>
+          <!--<div class="flex flex-col justify-center">-->
+          <div>
+            <el-tooltip
+                class="box-item"
+                effect="dark"
+                :content="row.Title"
+                placement="top-start">
+              <div class="font-medium text-black display-line">{{ row.Title }}</div>
+            </el-tooltip>
+
+            <div>
+              <span class="text-black font-semibold">{{ row.price }}</span>
+              <span class="text-gray-300 mx-1">|</span>
+              <span>{{ row.quantity }}</span>
+            </div>
+            <el-tooltip
+                offset="0"
+                :show-arrow="false"
+                effect="dark"
+                :content="row.sku"
+                placement="top-end">
+              <div class="display-line">
+
+                ASIN:<span class="text-black">{{ row.asin }}</span>
+                <el-link
+                    :href="row.amazonUrl"
+                    target="_blank"
+                    :disabled="!row.amazonUrl"
+                    :underline="false"
+                    :icon="Connection"
+                    :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
+                </el-link>
+                <span class="text-gray-300 mx-1">|</span>
+
+                SKU:<span class="text-black">{{ row.sku }}</span>
+              </div>
+            </el-tooltip>
+          </div>
+        </div>
+      </template>
+
+      <template v-if="searchType === 'asin'" #asin_default="{ row }">
+        <div style="display: flex">
+          <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain"/>
+          <el-image v-else class="w-12 h-12 mr-1 border rounded">
+            <template #error>
+              <div class="image-slot">
+                <el-icon>
+                  <icon-picture/>
+                </el-icon>
+              </div>
+            </template>
+          </el-image>
+          <div class="flex flex-col justify-center">
+            <div class="font-medium text-black">{{ row.Title }}</div>
+            <div>
+              <span>{{ row.price }}</span>
+              <span>|</span>
+              <span>{{ row.quantity }}</span>
+            </div>
+            <div>
+              ASIN:<span class="text-black">{{ row.asin }}</span>
+              <el-link
+                  :href="row.amazonUrl"
+                  target="_blank"
+                  :disabled="!row.amazonUrl"
+                  :underline="false"
+                  :icon="Connection"
+                  :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
+              </el-link>
+              <span class="text-gray-300 mx-1">|</span>
+              SKU:<span class="text-black">{{ row.sku }}</span>
+            </div>
+          </div>
+        </div>
+      </template>
     </vxe-grid>
   </div>
 </template>
@@ -121,6 +290,7 @@ function changeHeaderCellStyle() {
 :deep(.vxe-table--render-default .vxe-table--border-line) {
   border: none;
 }
+
 .image-slot {
   display: flex;
   justify-content: center;
@@ -131,11 +301,22 @@ function changeHeaderCellStyle() {
   color: var(--el-text-color-secondary);
   font-size: 30px;
 }
-.image-slot .el-icon {
+
+.image-slot el-icon {
   font-size: 30px;
 }
+
 /* 修改表头填充颜色 默认会有空隙 */
 :deep(.vxe-table--header-wrapper.body--wrapper) {
   background-color: #eef0f7;
 }
+
+.display-line {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: var(--line-clamp);
+  /* white-space: pre-wrap; */
+  --line-clamp: 1;
+}
 </style>

+ 8 - 6
src/views/adManage/sp/components/TopFilter.vue

@@ -6,6 +6,7 @@ import PopoverFilterParent from './PopoverFilterParent.vue'
 import { getProductSelect } from '../api'
 import emitter from '/@/utils/emitter'
 
+
 const isVisible = ref(false)
 
 const productFilterInput = ref('')
@@ -24,8 +25,9 @@ async function fetchProductSelect() {
 
 function handleOpen() {
   isVisible.value = true
-  emitter.emit('TopFilter', isVisible)
+  emitter.emit('TopFilter', { isVisible, productFilterSelect })
 }
+
 function handleClose() {
   isVisible.value = false
   emitter.emit('TopFilter', isVisible)
@@ -33,10 +35,10 @@ function handleClose() {
 
 const currentComponent = computed(() => {
   switch (productFilterSelect.value) {
-    case 'PASIN':
+    case 'pAsin':
       return PopoverFilterParent
-    case 'SKU':
-    case 'ASIN':
+    case 'sku':
+    case 'asin':
       return PopoverFilter
     default:
       return null // 或者是一个默认的组件
@@ -55,7 +57,7 @@ onMounted(() => {
         <el-input v-model="productFilterInput" @click="handleOpen" style="width: 350px" placeholder="请选择推广商品">
           <template #prepend>
             <el-select v-model="productFilterSelect" placeholder="Select" style="width: 100px">
-              <el-option v-for="item in productFilterOptions" :key="item.value" :label="item.label" :value="item.value" />
+              <el-option v-for="item in productFilterOptions" :key="item.value" :label="item.label" :value="item.value"/>
             </el-select>
           </template>
         </el-input>
@@ -63,7 +65,7 @@ onMounted(() => {
       <div class="flex justify-between">
         <div class="pb-3 font-bold text-slate-950 text-base">搜索</div>
         <el-icon class="cursor-pointer" @click="handleClose">
-          <CloseBold />
+          <CloseBold/>
         </el-icon>
       </div>
       <component :is="currentComponent"></component>

+ 113 - 39
src/views/demo/index.vue

@@ -1,52 +1,126 @@
 <script setup lang="ts">
-import { nextTick, ref } from 'vue'
+import { onMounted, reactive, ref } from 'vue'
+import { Connection, Picture as IconPicture } from '@element-plus/icons-vue'
+import { getParentAsin } from '../adManage/sp/api'
+import { useShopInfo } from '../../stores/shopInfo'
+import { storeToRefs } from 'pinia'
 
-const isVisible = ref(false)
 
-function handleFocus() {
-  isVisible.value = true
+const count = ref(0)
+const load = () => {
+  count.value += 2
 }
+const parentLoading = ref(false)
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
 
-function handleBlur() {
-  // 延时是为了解决点击Popover内容时,input失去焦点导致Popover立即关闭的问题。
-  setTimeout(() => {
-    isVisible.value = false
-  }, 200)
+const gridOptions = <any>reactive({
+  height: 500,
+  align: null,
+  columns: [
+    { type: 'checkbox', width: 30 },
+    { field: 'parentAsin', title: '父ASIN', slots: { default: 'parentAsin_default' } },
+  ],
+  data: [],
+})
+
+async function fetchParentAsin() {
+  parentLoading.value = true
+  const body = { profileId: profile.value.profile_id }
+  try {
+    const response = await getParentAsin(body)
+    gridOptions.data = response.data
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    parentLoading.value = false
+  }
+}
+
+function handleScroll(event: any) {
+  // 获取滚动容器
+  const target = event.$event.target
+  // console.log('target:', target)
+  // // 计算是否达到底部:容器高度 + 滚动顶部距离 >= 滚动内容的总高度
+  if (target.scrollHeight - (target.scrollTop + target.clientHeight) < 1) {
+    parentLoading.value = true
+    console.log('执行了')
+    load()
+    parentLoading.value = false
+  }
 }
+
+onMounted(() => {
+  fetchParentAsin()
+})
 </script>
 
 <template>
-  <!-- <div style="max-width: 400px">
-    <el-popover :visible="isVisible" placement="bottom" title="Title" :width="200" content="this is content, this is content, this is content">
-      <template #reference>
-        <div>
-          <el-input v-model="input1" @click="handlePopover" style="width: 350px" placeholder="Please input">
-            <template #prepend>
-              <el-select v-model="select" placeholder="Select" style="width: 115px">
-                <el-option label="Restaurant" value="1" />
-                <el-option label="Order No." value="2" />
-                <el-option label="Tel" value="3" />
-              </el-select>
-            </template>
-          </el-input>
-        </div>
-      </template>
-    </el-popover>
-  </div> -->
+  <ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
+    <li v-for="i in count" :key="i" class="infinite-list-item">{{ i }}</li>
+  </ul>
+  <vxe-grid
+      v-loading="parentLoading"
+      v-bind="gridOptions"
+      :header-row-style="changeHeaderCellStyle"
+      :header-cell-style="changeHeaderCellStyle"
+      class="ml-2 mb-2 mt-0 border border-r-0 rounded-bl-md rounded-tl-md overflow-hidden"
+      style="flex: 0 1 260px; overflow: auto"
+      @scroll.native="handleScroll">
+    <template #parentAsin_default="{ row }">
+      <div style="display: flex">
+        <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain"/>
+        <el-image v-else class="w-12 h-12 mr-1 border rounded">
+          <template #error>
+            <div class="image-slot">
+              <el-icon>
+                <icon-picture/>
+              </el-icon>
+            </div>
+          </template>
+        </el-image>
 
-  <!-- <el-popover ref="popover" placement="right" title="Title" :width="200" trigger="focus" content="this is content, this is content, this is content">
-    <template #reference>
-      <el-button class="m-2">Focus to activate</el-button>
+        <div class="flex flex-col justify-center">
+          <div class="font-medium text-black">{{ row.parentAsin }}</div>
+          <div>
+            ASIN:<span class="text-black">{{ row.asinNum }}</span>
+            <el-link
+                :href="row.amazonUrl"
+                target="_blank"
+                :disabled="!row.amazonUrl"
+                :underline="false"
+                :icon="Connection"
+                :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
+            </el-link>
+            <span class="text-gray-300 mx-1">|</span>
+            SKU:<span class="text-black">{{ row.skuNum }}</span>
+          </div>
+        </div>
+      </div>
     </template>
-  </el-popover> -->
-
-  <div>
-    <el-popover v-model:visible="isVisible" placement="bottom" title="Title" content="This is content">
-      <template #reference>
-        <el-input placeholder="Please input" @focus="handleFocus" @blur="handleBlur"></el-input>
-      </template>
-    </el-popover>
-  </div>
+  </vxe-grid>
 </template>
 
-<style scoped></style>
+<style scoped>
+
+.infinite-list {
+  height: 300px;
+  padding: 0;
+  margin: 0;
+  list-style: none;
+}
+
+.infinite-list .infinite-list-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 50px;
+  background: var(--el-color-primary-light-9);
+  margin: 10px;
+  color: var(--el-color-primary);
+}
+
+.infinite-list .infinite-list-item + .list-item {
+  margin-top: 10px;
+}
+</style>