Ver código fonte

refactor(product-monitor): 重构商品监控页面

- 移除冗余代码,优化列定义
- 新增 DataTableSlot 组件用于渲染自定义列内容
- 更新 ProductInfo 组件样式
- 升级 vxe-table 依赖至最新版本
WanGxC 7 meses atrás
pai
commit
8af1cee9b8

+ 15 - 12
package-lock.json

@@ -56,7 +56,7 @@
 				"vue-grid-layout": "^3.0.0-beta1",
 				"vue-i18n": "^9.2.2",
 				"vue-router": "^4.1.6",
-				"vxe-table": "^4.7.81",
+				"vxe-table": "^4.7.94",
 				"xe-utils": "^3.5.7"
 			},
 			"devDependencies": {
@@ -4374,13 +4374,16 @@
 			}
 		},
 		"node_modules/@vxe-ui/core": {
-			"version": "4.0.13",
-			"resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.0.13.tgz",
-			"integrity": "sha512-DKXHFDOIPzIxF3Pl4VUXDDiMYxqQjrOk2iABZSt8WXDhz3VpZTLegd8EHH353WFPaSIOofBrNbwJi2nffzVihw==",
+			"version": "4.0.16",
+			"resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.0.16.tgz",
+			"integrity": "sha512-7d8485RYAv3tgr5+ckz5iK+Tt3yPjcXBaWcRdxRrxnDJNYVq6A4jAFT+Z9eHoFxqx60ASTOVCqnS8Tav/oPutA==",
 			"license": "MIT",
 			"dependencies": {
 				"dom-zindex": "^1.0.6",
 				"xe-utils": "^3.5.31"
+			},
+			"peerDependencies": {
+				"vue": "^3.2.0"
 			}
 		},
 		"node_modules/@wangeditor/basic-modules": {
@@ -10348,21 +10351,21 @@
 			"license": "MIT"
 		},
 		"node_modules/vxe-pc-ui": {
-			"version": "4.2.21",
-			"resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.2.21.tgz",
-			"integrity": "sha512-BZmSgGLoYW8bBNVkTLI/uuoUKFf5WdIZGhXqq6CXsJmc5ExzJQd10qFHNJeM9HIRH9hGKW4jIzGheCk3BJCTlA==",
+			"version": "4.2.26",
+			"resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.2.26.tgz",
+			"integrity": "sha512-dwPN0r0X+C6fr8ve0Tug19RZjxcdWQRfYy3V2fzqOoz+t2eB2F085/Pn7+Zpjps7mxajJCsgfQKSakN9tOKTmg==",
 			"license": "MIT",
 			"dependencies": {
-				"@vxe-ui/core": "^4.0.13"
+				"@vxe-ui/core": "^4.0.16"
 			}
 		},
 		"node_modules/vxe-table": {
-			"version": "4.7.90",
-			"resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.7.90.tgz",
-			"integrity": "sha512-zWa4b4lgpxYxIceQ3dzpNemzfkj+ZyVmofruKMSSrxkRHVdJTRhRdw4IK13LvvYd7Y0rXZFECKRJi+QE/dXeRg==",
+			"version": "4.7.94",
+			"resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.7.94.tgz",
+			"integrity": "sha512-jlWSIU2txvowBX8DTa5dXzys973DC2qgYSCsSH8t3aR0AceMKJ7TcZWo3U0GOz94j8kJGI1ftw6k3xXLwf0qww==",
 			"license": "MIT",
 			"dependencies": {
-				"vxe-pc-ui": "^4.2.20"
+				"vxe-pc-ui": "^4.2.26"
 			}
 		},
 		"node_modules/webpack-virtual-modules": {

+ 1 - 1
package.json

@@ -57,7 +57,7 @@
 		"vue-grid-layout": "^3.0.0-beta1",
 		"vue-i18n": "^9.2.2",
 		"vue-router": "^4.1.6",
-		"vxe-table": "^4.7.81",
+		"vxe-table": "^4.7.94",
 		"xe-utils": "^3.5.7"
 	},
 	"devDependencies": {

+ 8 - 27
src/views/product-manage/product-list/component/ProductInfo.vue

@@ -49,16 +49,17 @@ const props = defineProps({
       </div>
     </el-image>
     <div class="text-left">
-      <el-link type="primary" :href=item.url :underline="false" target="_blank">
-        <el-tooltip :content="item.title" :disabled="showTitleTooltip" :offset="-50" effect="dark" placement="top-start">
-          <span class="en-text">{{ item.title ?? '暂无标题' }}</span>
-        </el-tooltip>
-      </el-link>
-      <div class="asin-sku-text">
+      <el-tooltip :content="item.title" :disabled="showTitleTooltip" :show-after="300" effect="dark" 
+                  placement="top-start">
+        <el-link :href=item.url :underline="false" target="_blank" type="primary">
+          <span class="line-clamp-2 text-ellipsis whitespace-normal">{{ item.title ?? '--' }}</span>
+        </el-link>
+      </el-tooltip>
+      <div class="flex justify-between">
         <span>
           {{ item.asin }}
         </span>
-        <p v-show="displaySKU" class="clean-text">
+        <p v-show="displaySKU" class="m-0 p-0">
           {{ item.sku }}
         </p>
       </div>
@@ -67,24 +68,4 @@ const props = defineProps({
 </template>
 
 <style scoped>
-span.en-text {
-  word-break: break-word;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: normal;
-  font-size: 12px;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-}
-
-.clean-text {
-  margin: 0;
-  padding: 0;
-}
-
-.asin-sku-text {
-  display: flex;
-  justify-content: space-between;
-}
 </style>

+ 28 - 150
src/views/product-manage/product-monitor/ColumnsTsx.tsx

@@ -1,218 +1,96 @@
-import { useCountryInfoStore } from '/@/stores/countryInfo';
-import { getTagType } from '/@/utils/useTagColor';
-
-
-const countryInfoStore = useCountryInfoStore();
-
 export const productColumns = [
-  { type: 'checkbox', width: 50, align: 'center', fixed: 'left' },
-  { type: 'seq', title: 'No.', width: 60, align: 'center' },
+  { type: 'checkbox', minWidth: 50, align: 'center', fixed: 'left' },
+  { type: 'seq', title: 'No.', minWidth: 60, align: 'center', fixed: 'left' },
   {
-    field: 'product_info', title: '商品信息', minWidth: 'auto', align: 'center',
+    field: 'product_info', title: '商品信息', minWidth: 'auto', align: 'center', fixed: 'left',
     slots: { default: 'product_info' }
   },
   {
     field: 'sku', title: 'SKU', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <span class={ 'font-medium' }>{ row.goods.sku ? row.goods.sku : '--' }</span>;
-      }
-    }
+    slots: {default: 'sku' }
   },
   {
     field: 'country_code', title: '国 家', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        const country = countryInfoStore.countries.find(c => c.code === row.country_code);
-        const color = country ? country.color : '#3875F6';
-        return (
-            <el-tag effect="plain" round
-                    style={ { color: color, borderColor: color } }>{ country ? country.name : '--' }
-            </el-tag>
-        );
-      }
-    }
+    slots: { default: 'country_code' }
   },
   {
     field: 'platform_number', title: '平台编号', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <span class={ 'font-medium' }>{ row.goods.platform_number ? row.goods.platform_number : '--' }</span>;
-      }
-    }
+    slots: { default: 'platform_number' }
   },
   {
     field: 'shop_name', title: '店 铺', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <el-tag type={ getTagType.value(row.shop_name) }>
-              { row.shop_name ? row.shop_name : '--' }
-            </el-tag>
-        );
-      }
-    }
+    slots: { default: 'shop_name' }
   },
   {
     field: 'tag', title: '分 组',  minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <el-tag type={ getTagType.value(row.goods.tag) }>
-              { row.goods.tag ? row.goods.tag : '--' }
-            </el-tag>
-        );
-      }
-    }
+    slots: { default: 'tag' }
   },
   {
     field: 'brand', title: '品 牌', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <el-tag type={ getTagType.value(row.goods.brand) } effect="plain" round>
-              { row.goods.brand ? row.goods.brand : '--' }
-            </el-tag>
-        );
-      }
-    }
-  },
-  {
-    field: 'price_info', title: '价 格', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <div v-if={ row.goods.price > 0 } class={ `font-medium text-left` }>
-              <p>现 价:{ row.goods.currency_code + '‎' + row.goods.price }</p>
-              <p>折 扣:{ row.goods.discount > 0 ? row.goods.discount + '%' : '-' }</p>
-              <p>优惠劵:{ !row || row.goods.coupon <= 0 ? '-' : row.goods.currency_code + '‎' + row.goods.coupon }</p>
-            </div>
-        );
-      }
-    }
+    slots: { default: 'brand' }
+  },
+  {
+    field: 'price_info', title: '价 格', minWidth: 'auto', headerAlign: 'center', align: 'left',
+    slots: { default: 'price_info' }
   },
   {
     field: 'show_price', title: '展示价格', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.show_price ? row.goods.currency_code + row.goods.show_price : '--' }</div>;
-      }
-    }
+    slots: { default: 'show_price' }
   },
   {
     field: 'activity_price', title: '平时活动售价', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div
-            class={ 'font-medium' }>{ row.goods.activity_price ? row.goods.currency_code + row.goods.activity_price : '--' }</div>;
-      }
-    }
+    slots: { default: 'activity_price' }
   },
   {
     field: 'minimum_price', title: '最低活动售价', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.minimum_price ? row.goods.currency_code + row.goods.minimum_price : '--' }</div>;
-      }
-    }
+    slots: { default: 'minimum_price' }
   },
   {
     field: 'ratings', title: '子ASIN评分人数', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.ratings ? row.goods.ratings : '--' }</div>;
-      }
-    }
+    slots: { default: 'ratings' }
   },
   {
     field: 'all_ratings', title: '亚马逊显示评分人数', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.all_ratings ? row.goods.all_ratings : '--' }</div>;
-      }
-    }
+    slots: { default: 'all_ratings' }
   },
   {
     field: 'reviews', title: '子ASIN评论人数', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.reviews ? row.goods.reviews : '--' }</div>;
-      }
-    }
+    slots: { default: 'reviews' }
   },
   {
     field: 'all_reviews', title: '亚马逊显示评论人数', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.all_reviews ? row.goods.all_reviews : '--' }</div>;
-      }
-    }
+    slots: { default: 'all_reviews' }
   },
   {
     field: 'score', title: '子ASIN计算评分', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.score ? row.goods.score : '--' }</div>;
-      }
-    }
+    slots: { default: 'score' }
   },
   {
     field: 'all_score', title: '亚马逊显示评分', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.goods.all_score ? row.goods.all_score : '--' }</div>;
-      }
-    }
+    slots: { default: 'all_score' }
   },
   {
     field: 'launch_date', title: '上架日期', minWidth: 'auto', align: 'center', sortable: true,
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.launch_date ? row.launch_date : '--' }</div>;
-      }
-    }
+    slots: { default: 'launch_date' }
   },
   {
     field: 'category', title: '类 目', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.category ? row.category : '--' }</div>;
-      }
-    }
+    slots: { default: 'category' }
   },
   {
     field: 'status', title: '状 态', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        const statusText = row.status === 1 ? '在售' : '停售';
-        const statusType = row.status === 1 ? 'success' : 'info';
-
-        return (
-            <el-tag type={ statusType }>
-              { statusText }
-            </el-tag>
-        );
-      }
-    }
+    slots: { default: 'status' }
   },
   {
     field: 'update_datetime', title: '更新时间', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.update_datetime ? row.update_datetime : '--' }</div>;
-      }
-    }
+    slots: { default: 'update_datetime' }
   },
   {
     field: 'create_datetime', title: '创建时间', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <div class={ 'font-medium' }>{ row.create_datetime ? row.create_datetime : '--' }</div>;
-      }
-    }
+    slots: { default: 'create_datetime' }
   },
   {
-    field: 'operate', title: '操 作', width: 100, align: 'center', fixed: 'right',
+    field: 'operate', title: '操 作', minWidth: 100, align: 'center', fixed: 'right',
     slots: { default: 'operate' }
   }
 ];

+ 13 - 20
src/views/product-manage/product-monitor/component/DataTable.vue

@@ -5,14 +5,14 @@
  * @Author: Cheney
  */
 
-import { Delete, Download, Operation, Plus, Refresh, Upload } from '@element-plus/icons-vue';
+import { Delete, Download, Plus, Refresh, Upload } from '@element-plus/icons-vue';
 import * as api from '../api';
 import PermissionButton from '/src/components/PermissionButton/index.vue';
 import EditDrawer from './EditDrawer.vue';
 import ImportButton from '/src/components/ImportButton/index.vue';
 import VerticalDivider from '/src/components/VerticalDivider/index.vue';
 import { productColumns } from '../ColumnsTsx';
-import ProductInfo from '/@/views/product-manage/product-list/component/ProductInfo.vue';
+import DataTableSlot from '/@/views/product-manage/product-monitor/component/DataTableSlot.vue';
 
 
 interface Parameter {
@@ -37,6 +37,7 @@ const gridOptions: any = reactive({
   border: false,
   round: true,
   stripe: true,
+  showHeader: true,
   currentRowHighLight: true,
   height: '100%',
   toolbarConfig: {
@@ -62,7 +63,7 @@ const gridOptions: any = reactive({
     icon: 'vxe-icon-indicator roll',
     text: '正在拼命加载中...'
   },
-  columns: productColumns,
+  columns: '',
   data: ''
 });
 
@@ -79,7 +80,9 @@ onMounted(() => {
   fetchList();
 });
 
+// TODO: 删除goods
 async function fetchList() {
+  gridOptions.data = [];
   const query = {
     country_code: queryParameter?.country,
     goods__brand: queryParameter?.brand,
@@ -91,9 +94,13 @@ async function fetchList() {
     platform_number: queryParameter?.platformId,
     goods__all_ratings: queryParameter?.scoreNumber,
     goods__all_reviews: queryParameter?.commentNumber,
-    goods__all_score: queryParameter?.displayScore,
+    goods__all_score: queryParameter?.displayScore
   };
+  
   await useTableData(api.getTableData, query, gridOptions);
+  await gridRef.value.loadColumn(productColumns);
+  gridOptions.showHeader = Boolean(gridOptions.data?.length);
+
 }
 
 function handleRefresh() {
@@ -204,22 +211,8 @@ defineExpose({ fetchList });
       />
     </template>
     <!-- 自定义列插槽 -->
-    <template #product_info="{ row }">
-      <ProductInfo :item="row.goods" :img-width="50"></ProductInfo>
-    </template>
-    <template #operate="{ row }">
-      <div class="flex justify-center gap-2">
-        <PermissionButton circle plain type="warning" @click="handleEdit(row)">
-          <el-icon>
-            <Operation />
-          </el-icon>
-        </PermissionButton>
-        <PermissionButton circle plain type="danger" @click="singleDelete(row)">
-          <el-icon>
-            <Delete />
-          </el-icon>
-        </PermissionButton>
-      </div>
+    <template v-for="col in productColumns" #[`${col.field}`]="{ row }">
+      <DataTableSlot :key="row.id" :field="col.field" :row="row" @edit-row="handleEdit" @handle-notice="singleDelete" />
     </template>
   </vxe-grid>
   <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" />

+ 112 - 0
src/views/product-manage/product-monitor/component/DataTableSlot.vue

@@ -0,0 +1,112 @@
+<script lang="ts" setup>
+/**
+ * @Name: DataTableSlot.vue
+ * @Description:
+ * @Author: Cheney
+ */
+
+
+import { useCountryInfoStore } from '/@/stores/countryInfo';
+import { Operation, Delete, Message } from '@element-plus/icons-vue';
+import PermissionButton from '/@/components/PermissionButton/index.vue'
+import ProductInfo from '/@/views/product-manage/product-list/component/ProductInfo.vue';
+
+
+const props = defineProps<{
+  row: any,
+  field: any
+}>();
+const { row, field } = props;
+
+const emit = defineEmits([ 'edit-row', 'handle-notice' ]);
+
+const countryInfoStore = useCountryInfoStore();
+const country = countryInfoStore.countries.find(c => c.code == row.country_code);
+const color = country ? country.color : '#3875F6';
+
+const statusText = row.status === 1 ? '在售' : '停售';
+const statusType = row.status === 1 ? 'success' : 'info';
+
+function handleEdit(row: any) {
+  emit('edit-row', row);
+}
+
+function handleNotice(row: any) {
+  emit('handle-notice', row);
+}
+</script>
+
+<template>
+  <div class="font-medium">
+    <div v-if="field === 'country_code'">
+      <el-tag :disable-transitions="true" :style="{ color: color, borderColor: color }" effect="plain" round>
+        {{ country ? country.name : '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'product_info'">
+      <ProductInfo :item="row.goods" :img-width="50" />
+    </div>
+    <div v-else-if="field === 'sku'">
+      {{ row.goods.sku ?? '--' }}
+    </div>
+    <div v-else-if="field === 'platform_number'">
+      {{ row.goods.platform_number ?? '--' }}
+    </div>
+    <div v-else-if="field === 'shop_name'">
+      <el-tag :disable-transitions="true" type="primary" effect="plain">{{ row.shop_name ?? '--' }}</el-tag>
+    </div>
+    <div v-else-if="field === 'brand'">
+      <el-tag :disable-transitions="true" :style="{ color: '#F88B8A', borderColor: '#FBC4C4'}"  effect="plain">
+        {{ row.goods.brand ?? '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'price_info'">
+      <div v-if="row.goods.price > 0" class="font-medium">
+        <p>现 价:{{ row.goods.currency_code + '‎' + row.goods.price }}</p>
+        <p>折 扣:{{ row.goods.discount > 0 ? row.goods.discount + '%' : '-' }}</p>
+        <p>优惠劵:{{ !row || row.goods.coupon <= 0 ? '-' : row.goods.currency_code + '‎' + row.goods.coupon }}</p>
+      </div>
+    </div>
+    <div v-else-if="field === 'show_price'">
+      <div class="font-medium">
+        {{ row.goods.show_price ? row.goods.currency_code + row.goods.show_price : '--' }}
+    </div>
+    </div>
+    <div v-else-if="field === 'activity_price'">
+      <div class="font-medium">
+        {{ row.goods.activity_price ? row.goods.currency_code + row.goods.activity_price : '--' }}
+      </div>
+    </div>
+    <div v-else-if="field === 'minimum_price'">
+      <div class="font-medium">
+        {{ row.goods.minimum_price ? row.goods.currency_code + row.goods.minimum_price : '--' }}
+      </div>
+    </div>
+    <div v-else-if="field === 'status'">
+      <el-tag :disable-transitions="true" :type=statusType>
+      {{ statusText }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'operate'">
+      <div class="flex justify-center gap-2">
+        <PermissionButton circle plain type="warning" @click="handleEdit(row)">
+          <el-icon>
+            <Operation />
+          </el-icon>
+        </PermissionButton>
+        <PermissionButton circle plain type="info" @click="handleNotice(row)">
+          <el-icon>
+            <Message />
+          </el-icon>
+        </PermissionButton>
+      </div>
+    </div>
+    <div v-else>
+      {{ row.goods[field] }}
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>