Răsfoiți Sursa

✨ feat:
新增产品选项卡和创建产品线功能, 移除加载时浏览器控制台默认log; 新增商品分析TopAin组件

WanGxC 1 an în urmă
părinte
comite
4f725365ef

+ 1 - 1
src/router/backEnd.ts

@@ -38,7 +38,7 @@ const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...lay
  * @method setFilterMenuAndCacheTagsViewRoutes 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
  */
 export async function initBackEndControlRoutes() {
-	console.log("--------initBackEndControlRoutes------------")
+	// console.log("--------initBackEndControlRoutes------------")
 
 	// 界面 loading 动画开始执行
 	if (window.nextLoading === undefined) NextLoading.start();

+ 1 - 1
src/views/plugins/index.ts

@@ -12,5 +12,5 @@ export const scanAndInstallPlugins = (app: any) => {
 		pluginNames.add(pluginsName);
 	}
 	pluginsAll = Array.from(pluginNames);
-	console.log('已发现插件:', pluginsAll);
+	// console.log('已发现插件:', pluginsAll);
 };

+ 23 - 0
src/views/productCenter/productAnalysis/components/TopAsin.vue

@@ -0,0 +1,23 @@
+<template>
+  <div>
+    <el-card>
+      <div>
+        <div class="product-image">
+          <!-- <img src="" alt=""> -->
+          <div style="width: 100%; height: 100%; background: #f5f5f5"></div>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped>
+.product-image {
+  width: 126px;
+  height: 126px;
+  border: 1px solid e5e6eb;
+  border-radius: 4px;
+}
+</style>

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

@@ -1,13 +1,15 @@
 <template>
-  <div>
-
+  <div class="outer-container">
+    <TopAsin></TopAsin>
   </div>
 </template>
 
 <script setup lang="ts">
-
+import TopAsin from './components/TopAsin.vue'
 </script>
 
 <style scoped>
-
+.outer-container {
+  padding: 5px 10px 0 10px;
+}
 </style>

+ 8 - 1
src/views/productCenter/productList/api.ts

@@ -54,7 +54,14 @@ export function postCreateProductLine(body) {
   return request({
     url: '/api/sellers/productline/create/',
     method: 'POST',
-    params: body,
+    data: body
+  })
+}
+export function getProductCardData(query) {
+  return request({
+    url: '/api/sellers/productline/sales/',
+    method: 'GET',
+    params: query
   })
 }
 

+ 36 - 12
src/views/productCenter/productList/components/ProductLineDialog.vue

@@ -1,6 +1,7 @@
 <template>
   <el-dialog v-model="createProductDialog" title="新建产品线" width="1300">
     <el-form
+      v-loading="load"
       ref="ruleFormRef"
       class="custom-ruleForm"
       :model="ruleForm"
@@ -9,11 +10,11 @@
       size="default"
       label-position="top"
       status-icon>
-      <el-form-item prop="productLine">
+      <el-form-item prop="productLineName">
         <template #label>
           <span class="form-label"> 产品线名称: </span>
         </template>
-        <el-input v-model="ruleForm.productLine" placeholder="请输入产品线名称" maxlength="150" show-word-limit style="width: 500px" />
+        <el-input v-model="ruleForm.productLineName" placeholder="请输入产品线名称" maxlength="150" show-word-limit style="width: 500px" />
       </el-form-item>
       <el-form-item required>
         <template #label>
@@ -22,22 +23,24 @@
         <ProductList></ProductList>
       </el-form-item>
       <el-form-item>
-        <div style="display: flex">
-          <el-button style="margin: 0 auto" type="primary" @click="submitForm(ruleFormRef)"> 创建 </el-button>
+        <div style="display: flex; flex-direction: row-reverse">
+          <el-button style="margin-left: 10px" color="#3c59b0" @click="submitForm(ruleFormRef)"> 创建 </el-button>
+          <el-button plain @click="createProductDialog = false">取消</el-button>
         </div>
-
-        <!-- <el-button @click="createProductDialog = false">取消</el-button> -->
       </el-form-item>
     </el-form>
   </el-dialog>
 </template>
 
 <script setup lang="ts">
-import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
+import { onBeforeUnmount, reactive, ref, inject } from 'vue'
 import emitter from '/@/utils/emitter'
 import type { FormInstance, FormRules } from 'element-plus'
 import ProductList from '../components/ProductList.vue'
 import { postCreateProductLine } from '../api'
+import { ElMessage } from 'element-plus'
+
+const profile = <any>inject('profile')
 
 const createProductDialog = ref(false)
 
@@ -47,15 +50,15 @@ emitter.on('open-productLine-dialog', (value: any) => {
 
 // 表单相关
 interface RuleForm {
-  productLine: string
+  productLineName: string
 }
 const ruleFormRef = ref<FormInstance>()
 const ruleForm = reactive<RuleForm>({
-  productLine: '',
+  productLineName: '',
 })
 
 const rules = reactive<FormRules<RuleForm>>({
-  productLine: [{ required: true, message: '请输入产品线名称', trigger: 'blur' }],
+  productLineName: [{ required: true, message: '请输入产品线名称', trigger: 'blur' }],
 })
 
 const submitForm = async (formEl: FormInstance | undefined) => {
@@ -63,18 +66,39 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   await formEl.validate((valid, fields) => {
     if (valid) {
       console.log('submit!')
+      createProductLine()
     } else {
       console.log('error submit!', fields)
     }
   })
 }
 
+const load = ref(false)
+let asinList = []
+emitter.on('asinList', (value: any) => {
+  asinList = value
+})
+
 async function createProductLine() {
-  const body = {}
+  load.value = true
+  const body = {
+    profileId: profile.value.profile_id,
+    productLineName: ruleForm.productLineName,
+    asinList: asinList
+  }
   try {
-    const resp = await postCreateProductLine(body)
+    const response = await postCreateProductLine(body)
+    if (response.data.code == 'success') {
+      ElMessage.success('创建成功')
+    } else if (response.data.code == 'error') {
+      ElMessage.warning(`${response.data.description}`)
+    } else {
+      ElMessage.error('创建失败')
+    }
   } catch (error) {
     console.log('error:', error)
+  } finally {
+    load.value = false
   }
 }
 

+ 18 - 8
src/views/productCenter/productList/components/ProductList.vue

@@ -10,9 +10,8 @@
             <div class="list-bar">
               <span class="padding-0-10">商品列表</span>
               <vxe-button
-                style="position: absolute; margin: 3px; right: 0"
+                style="position: absolute; margin: 3px; right: 0; color: #3c59b0;"
                 type="text"
-                status="primary"
                 content="添加"
                 icon="vxe-icon-add"
                 @click="handleSelectedItems" />
@@ -152,15 +151,14 @@ async function fetchAllProduct() {
   try {
     const res = await getAllProduct(query)
     if (res && res.data) {
-      // 转换数据并更新data
+      // 转换数据并更新data, 如果不是第一页的数据就追加, 否则替换
       const newData = res.data
       if (currentPage > 1) {
-        data.value.push(...newData) // 追加数据而不是替换
+        data.value.push(...newData)
       } else {
-        data.value = newData // 第一页数据,直接替换
+        data.value = newData
       }
       total = res.total
-      console.log('请求的数据', data.value)
     }
   } catch (error) {
     console.log('error:', error)
@@ -239,7 +237,20 @@ function handleSelectedItems() {
       selectedData.value.push(newItem)
     }
   })
-
+  // console.log('selectedData', selectedData.value)
+  const asinList = selectedData.value.map(item => {
+    return {
+      parentAsin: item.parentAsin,
+      childAsin: item.childAsin.map(childItem => {
+        return {
+          Asin: childItem.Asin,
+          Image: childItem.Image,
+          Title: childItem.Title
+        }
+      }),
+    }
+  })
+  emitter.emit('asinList', asinList)
   clearSelections()
 }
 
@@ -277,7 +288,6 @@ const hasSelectedItems = computed(() => {
     return parentItem.childAsin.some((childItem) => childItem.checked)
   })
 })
-
 onMounted(() => {
   fetchAllProduct()
 })

+ 168 - 17
src/views/productCenter/productList/components/ProductTab.vue

@@ -1,9 +1,32 @@
 <template>
-  <div style="width: 100%; padding: 12px">
+  <div style="width: 100%; padding: 5px 12px 0 12px">
     <el-scrollbar>
       <div class="scrollbar-flex-content">
-        <el-card v-for="item in 50" :key="item" class="scrollbar-demo-item">
-          {{ item }}
+        <el-card
+          v-for="(item, index) in cardData"
+          :key="item.productlineId"
+          body-style="padding: 0;box-sizing: border-box; position: relative; width: 100%;"
+          class="scrollbar-demo-item"
+          :class="{ selected: selectedCardIndex === index }"
+          @click="selectCard(index, item)">
+          <div class="pct-chart" :id="`chart${index}-${item.productlineId}`"></div>
+          <el-popover placement="bottom" :width="150" trigger="click" >
+            <template #reference>
+              <el-icon class="custom-icon" @click.stop=""><MoreFilled /></el-icon>
+            </template>
+            <div class="custom-popoer">
+              <el-button :icon="Edit" text size="small">编辑</el-button>
+              <el-button :icon="Delete" text size="small" style="margin-left: 0;">删除</el-button>
+            </div>
+          </el-popover>
+          <div style="padding: 10px; position: relative">
+            <div class="product-line-name">{{ item.productlineName }}</div>
+
+            <div class="custom-part">
+              <div class="total-sales">{{ item.totalSales }}</div>
+              <span class="label">总销售额</span>
+            </div>
+          </div>
         </el-card>
       </div>
     </el-scrollbar>
@@ -11,15 +34,96 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, inject } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useShopInfo } from '/@/stores/shopInfo'
+import { onMounted, inject, ref, nextTick, watchEffect } from 'vue'
+import { getProductCardData } from '../api'
+import * as echarts from 'echarts'
+import { Edit, Delete } from '@element-plus/icons-vue'
+
+const profile = <any>inject('profile')
+
+const cardData = ref([])
+const pieChartRefs = ref<HTMLDivElement[]>([])
+
+const selectedCardIndex = ref(0)
+
+async function fetchProductCardData() {
+  try {
+    const { data } = await getProductCardData({ profileId: profile.value.profile_id })
+    cardData.value = data
+    // console.log('🚀 ~ cardData.value', cardData.value)
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
 
-const shopInfo = useShopInfo()
-const { profile } = storeToRefs(shopInfo)
-const dateRange = <any>inject('dateRange')
+function initChart() {
+  cardData.value.forEach((item, index) => {
+    const chartId = `chart${index}-${item.productlineId}`
+    const el = document.getElementById(chartId)
+    // const el:any = document.querySelector('#' + chartId)
+    if (el) {
+      const pieChart = echarts.init(el)
+      const option = {
+        animation: false,
+        series: [
+          {
+            silent: true,
+            avoidLabelOverlap: true,
+            label: {
+              show: true,
+              position: 'center',
+              formatter: '{d}%',
+            },
+            emphasis: {
+              disable: true,
+              scale: false,
+              shadowBlur: 0,
+              hoverAnimation: false,
+              label: {
+                show: true,
+                fontSize: 12,
+              },
+            },
+            labelLine: {
+              show: false,
+            },
+            radius: ['75%', '100%'],
+            type: 'pie',
+            data: [
+              {
+                value: item.salesPercentage,
+                name: item.productlineName,
+              },
+              {
+                value: 100 - item.salesPercentage,
+                name: '',
+                itemStyle: {
+                  color: '#f1eff5',
+                },
+                label: {
+                  show: false,
+                },
+              },
+            ],
+          },
+        ],
+      }
+      pieChart.setOption(option)
+      pieChartRefs.value[chartId] = pieChart
+    }
+  })
+}
 
+function selectCard(index: number, item: any) {
+  selectedCardIndex.value = index
+  console.log('🚀 ~ selectedCardIndex.value', selectedCardIndex.value)
+  console.log('Selected card data:', item)
+}
 
+onMounted(async () => {
+  await fetchProductCardData()
+  initChart()
+})
 </script>
 
 <style scoped>
@@ -29,14 +133,61 @@ const dateRange = <any>inject('dateRange')
 .scrollbar-demo-item {
   flex-shrink: 0;
   display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 100px;
-  height: 50px;
-  margin: 10px;
-  text-align: center;
+  width: 202px;
+  height: 92px;
+  margin: 10px 5px;
   border-radius: 4px;
-  background: var(--el-color-danger-light-9);
-  color: var(--el-color-danger);
+  box-sizing: border-box;
+  border: 1px solid transparent; /* 添加透明边框 */
+  transition: border-color 0.3s; /* 可选,使边框颜色改变更平滑 */
+  cursor: pointer;
+}
+.product-line-name {
+  position: relative;
+  padding: 0 12px 8px 0;
+  color: #4e5969;
+  font-weight: 700;
+  font-size: 13px;
+  line-height: 17px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.custom-part {
+  padding-top: 8px;
+}
+.total-sales {
+  color: #4e5969;
+  font-size: 13px;
+  font-weight: 700;
+}
+.label {
+  padding-right: 4px;
+  color: #c9cdd4;
+  font-size: 12px;
+}
+.pct-chart {
+  box-sizing: border-box;
+  position: absolute;
+  top: 34px;
+  right: 5%;
+  width: 50px;
+  height: 50px;
+}
+.selected {
+  border: 1px solid #3359b5;
+}
+.custom-icon {
+  position: absolute;
+  top: 8px;
+  right: 5%;
+  z-index: 2;
+  color: #c9cdd4;
+  /* padding: 4px; */
+  transform: rotate(90deg);
+}
+.custom-popoer {
+  display: flex;
+  flex-direction: column;
 }
 </style>

+ 14 - 6
src/views/productCenter/productList/components/TopFilters.vue

@@ -2,7 +2,7 @@
   <div class="filters">
     <el-input placeholder="ASIN/父ASIN/标题/SKU"></el-input>
     <el-select v-model="value" style="width: 300px">
-      <el-option v-for="item in options" :key="item.id" :label="item.productlinename" :value="item.id" />
+      <el-option v-for="item in options" :key="item.productlineId" :label="item.productlinename" :value="item.productlineId" />
     </el-select>
     <DateRangePicker v-model="dateRange"></DateRangePicker>
   </div>
@@ -13,19 +13,27 @@ import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 import { usePublicData } from '/@/stores/publicData'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
-
-import { onMounted, ref } from 'vue'
+import {getProductLineSelect} from '../api'
+import { inject, onMounted, ref } from 'vue'
 
 const publicData = usePublicData()
 const { dateRange } = storeToRefs(publicData)
-const shopInfo = useShopInfo()
-const { profile } = storeToRefs(shopInfo)
+const profile = <any>inject('profile')
 
 const value = ref('')
 const options = ref([])
 
-onMounted(()=>{
+async function fetchProductLine() {
+  try { 
+    const resp = await getProductLineSelect({profileId: profile.value.profile_id})
+    options.value = resp.data
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
 
+onMounted(()=>{
+  fetchProductLine()
 })
 </script>
 

+ 1 - 0
src/views/productCenter/productList/index.vue

@@ -33,6 +33,7 @@
           </el-button>
         </el-button-group>
       </div>
+      
       <AsinTable></AsinTable>
     </div>
   </div>