Jelajahi Sumber

✨ feat: 新增商品分析

WanGxC 1 tahun lalu
induk
melakukan
2d6c95059a

+ 1 - 1
.prettierrc.js

@@ -2,7 +2,7 @@ module.exports = {
 	singleAttributePerLine: false,
 	htmlWhitespaceSensitivity: 'ignore',
 	// 一行最多多少个字符
-	printWidth: 260,
+	printWidth: 200,
 	// 指定每个缩进级别的空格数
 	tabWidth: 2,
 	// 使用制表符而不是空格缩进行

+ 16 - 58
src/views/productCenter/productAnalysis/api.ts

@@ -39,6 +39,20 @@ export function getLineMonthData(query: UserPageQuery) {
   })
 }
 
+// export function getProductLineSelect(query) {
+//   return request({
+//     url: '/api/sellers/productline/',
+//     method: 'GET',
+//     params: query,
+//   })
+// }
+// export function postCreateProductLine(body) {
+//   return request({
+//     url: '/api/sellers/productline/create/',
+//     method: 'POST',
+//     data: body,
+//   })
+// }
 export function getProductLineSelect(query) {
   return request({
     url: '/api/sellers/productline/',
@@ -46,65 +60,9 @@ export function getProductLineSelect(query) {
     params: query,
   })
 }
-export function getAllProduct(query) {
+export function getProduct(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/',
+    url: '/api/sellers/product/',
     method: 'GET',
     params: query,
   })

+ 222 - 0
src/views/productCenter/productAnalysis/components/TopParentAsin/ExchangeProduct.vue

@@ -0,0 +1,222 @@
+<template>
+  <el-popover placement="bottom-start" :width="1000" :visible="visible">
+    <template #reference>
+      <span class="exchange-btn" @click="visible = true">
+        <el-icon><Switch /></el-icon>
+        切换商品
+      </span>
+    </template>
+    <div class="exchange-popover__title">切换商品</div>
+    <div class="filter-bar">
+      <el-input v-model="searchInp" style="max-width: 600px" placeholder="请输入标题/父ASIN/ASIN/SKU查询商品">
+        <template #prepend>
+          <el-select v-model="select" style="width: 80px">
+            <el-option label="模糊" value="vague" />
+            <el-option label="精确" value="accurate" />
+          </el-select>
+        </template>
+        <template #append>
+          <el-button :icon="Search" />
+        </template>
+      </el-input>
+      <el-select v-if="select == 'vague'" v-model="selectValue" class="filter-select">
+        <el-option v-for="item in options" :key="item.productlineId" :label="item.productlineName" :value="item.productlineId" />
+      </el-select>
+      <el-button @click="visible = false">关闭</el-button>
+    </div>
+    <div class="asin-selector">
+      <div class="asin-selector__part" v-loading="parentloading">
+        <div class="part-title">父ASIN</div>
+        <ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
+          <li v-for="item in allData" class="infinite-list-item" @click="selectPaAsin">
+            <div class="list-content">
+              <img :src="item.Image" class="image-item" />
+              <div>
+                <el-tooltip effect="dark" :content="item.Title" placement="top-start">
+                  <span class="list-item-title">{{ item.Title }}</span>
+                </el-tooltip>
+                <div>
+                  <span class="item-font">${{ item.priceMin ? item.priceMin : '--' }}</span>
+                  ~
+                  <span class="item-font">${{ item.priceMax ? item.priceMax : '--' }}</span>
+                </div>
+                <div>
+                  <span class="li-label">父ASIN:</span>
+                  {{ item.parentAsin ? item.parentAsin : '--' }}
+                </div>
+                <div>
+                  <span class="li-label">ASIN:</span>
+                  {{ item.asinNumbers ? item.asinNumbers : '--' }}
+                  <span class="li-label">SKU:</span>
+                  {{ item.skuNumbers ? item.skuNumbers : '--' }}
+                </div>
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+      <div class="asin-selector__part">
+        <div class="part-title">ASIN</div>
+      </div>
+      <div class="asin-selector__part sku"></div>
+    </div>
+  </el-popover>
+</template>
+
+<script setup lang="ts">
+import { Search } from '@element-plus/icons-vue'
+import { inject, onMounted, ref } from 'vue'
+import { getProductLineSelect, getProduct } from '../../api'
+
+const profile = <any>inject('profile')
+
+const visible = ref(false)
+const parentloading = ref(false)
+
+const searchInp = ref('')
+const select = ref('vague')
+
+const selectValue = ref('')
+const options = ref([])
+
+const allData = ref([])
+let currentPage = 1
+let total = 0
+let limit = 40  // 不要修改
+
+function load() {
+  if (currentPage * limit < total) {
+    currentPage++
+    fetchProduct()
+  }
+  console.log(12)
+}
+
+async function fetchProductLine() {
+  try {
+    const resp = await getProductLineSelect({ profileId: profile.value.profile_id })
+    options.value = resp.data
+    selectValue.value = options.value[0].productlineId
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
+
+async function fetchProduct() {
+  parentloading.value = true
+  const query = {
+    profileId: profile.value.profile_id,
+    productlineId: 'ALL',
+    page: 1,
+    limit: limit,
+  }
+  try {
+    const res = await getProduct(query)
+    if (res && res.data) {
+      const newData = res.data
+      if (currentPage > 1) {
+        allData.value.push(...newData)
+      } else {
+        allData.value = newData
+      }
+      total = res.total
+    }
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    parentloading.value = false
+  }
+}
+function selectPaAsin() {
+console.log(1233)
+}
+
+onMounted(() => {
+  fetchProductLine()
+  fetchProduct()
+})
+</script>
+
+<style scoped>
+.exchange-btn {
+  color: #3359b5;
+  font-weight: 700;
+  cursor: pointer;
+  width: fit-content;
+}
+.exchange-popover__title {
+  padding-bottom: 12px;
+  color: #1d2129;
+  font-size: 16px;
+  font-weight: 700;
+}
+.filter-bar {
+  display: flex;
+  gap: 8px;
+}
+.asin-selector {
+  display: flex;
+  align-items: stretch;
+  padding-top: 12px;
+  color: #1d2129;
+}
+.asin-selector__part {
+  position: relative;
+  flex-grow: 1;
+  width: 40%;
+  border: 1px solid #e5e6eb;
+}
+.asin-selector__part .sku {
+  width: 20%;
+}
+.part-title {
+  padding: 8px 4px;
+  color: #6b7785;
+}
+.infinite-list-item {
+  display: flex;
+}
+.infinite-list {
+  height: 420px;
+  padding: 0;
+  margin: 0;
+  list-style: none;
+}
+.infinite-list .infinite-list-item + .list-item {
+  margin-top: 10px;
+}
+.image-item {
+  min-width: 70px;
+  height: 70px;
+  margin: 0 5px;
+  border: 1px solid #e5e6eb;
+  border-radius: 4px;
+}
+.list-item-title {
+  color: #6b7785;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: var(--line-clamp);
+  white-space: pre-wrap;
+  --line-clamp: 1;
+}
+.item-font {
+  font-weight: 700;
+  color: #1d2129;
+}
+.item-asin {
+  color: #6b7785;
+}
+.filter-select {
+  width: 300px;
+}
+.list-content {
+  display: flex;
+  align-items: center;
+  padding-bottom: 5px;
+}
+.li-label {
+  color: #6b7785;
+}
+</style>

+ 92 - 4
src/views/productCenter/productAnalysis/components/TopParentAsin/index.vue

@@ -1,20 +1,59 @@
 <template>
   <div class="top-parent-asin">
-    <el-card>
-      <div>
+    <el-card body-style="padding: 10px;">
+      <div class="product-card">
         <div class="product-image">
-          <!-- <img src="" alt=""> -->
           <div style="width: 100%; height: 100%; background: #f5f5f5"></div>
         </div>
+        <div class="product-info">
+          <div>
+            <ExchangeProduct></ExchangeProduct>
+          </div>
+          <div class="product-title">
+            ZOSI 16CH 4K PoE Security Cameras System,4K 8MP 16 Channel H.265 NVR with 4TB Hard Drive and 8pcs 5MP Indoor Outdoor PoE IP Cameras,120ft Night Vision,Remote Access for Home Business 24/7
+            Recording131232321321313123123213213
+          </div>
+          <div class="product-detail">
+            <span class="product-detail__label">父ASIN: </span>
+            <span>B09VHLFYTP</span>
+            <span class="product-detail__label">SKU: </span>
+            <span>18</span>
+            <span class="product-detail__label">FBA库存: </span>
+            <span>171</span>
+          </div>
+          <div class="product-brand">
+            <span>ZOSI</span>
+          </div>
+        </div>
+        <div class="product-price">
+          <div class="product-price__label">价格:</div>
+          <div style="position: relative; top: 1px">
+            <div>$1234 ~ $2312</div>
+          </div>
+        </div>
+        <div class="product-stars">
+          <div>星级评分:</div>
+          <div>1231341231223213(2131)</div>
+        </div>
       </div>
     </el-card>
   </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import ExchangeProduct from './ExchangeProduct.vue'
+
+</script>
 
 <style scoped>
+.product-card {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  gap: 16px;
+}
 .product-image {
+  flex-shrink: 0;
   width: 126px;
   height: 126px;
   border: 1px solid e5e6eb;
@@ -25,5 +64,54 @@
   top: 0;
   z-index: 1000;
 }
+.product-info {
+  display: flex;
+  justify-content: space-around;
+  flex-direction: column;
+  gap: 8px;
+}
+.product-title {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  color: #1d2129;
+  font-size: 18px;
+  font-weight: 700;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.product-price {
+  flex-shrink: 0;
+  min-width: 120px;
+  color: #1d2129;
+  font-size: 18px;
+  font-weight: 700;
+}
+.product-price__label {
+  padding-bottom: 4px;
+  color: #6b7785;
+  font-size: 12px;
+}
+.product-stars {
+  flex-shrink: 0;
+  min-width: 180px;
+  color: #1d2129;
+  font-size: 18px;
+  font-weight: 700;
+}
+.product-detail {
+  color: #1d2129;
+}
+.product-detail__label {
+  padding-left: 0;
+  color: #6b7785;
+}
+.product-brand {
+  display: flex;
+  gap: 12px;
+  color: #1d2129;
+  height: 16px;
+  line-height: 16px;
+}
 
 </style>

+ 2 - 4
src/views/productCenter/productList/components/ProductDialog/ProductList.vue

@@ -20,8 +20,8 @@
                     <el-checkbox v-model="item.checked" @click.stop="" @change="checkAll(item, item.checked)" :disabled="item.allChildrenExist">
                       {{ item.parentAsin }}
                     </el-checkbox>
-                    <p style="margin-left: 10px">111---{{ item.allChildrenExist }}</p>
-                    <el-tag style="margin-left: 8px" effect="plain" size="small" round
+                    <!-- <p style="margin-left: 10px">111---{{ item.allChildrenExist }}</p> -->
+                    <el-tag style="margin-left: 8px;" effect="plain" size="small" round
                       >{{ item.num }}
                       <span v-if="item.num == '1'">ASIN</span>
                       <span v-else>ASINs</span>
@@ -145,7 +145,6 @@ async function handleSearch() {
   }
   try { 
     const response = await getAllProduct(query)
-    console.log('🚀 ~ response', response.data)
     data.value = response.data
   } catch (error) {
     console.log('error:', error)
@@ -158,7 +157,6 @@ async function handleSearch() {
 function handleKeydown(event) {
   if (event.$event.key === 'Enter') {
     handleSearch()
-    console.log(111)
   }
 }