Jelajahi Sumber

Merge branch 'dev' into xinyan

xinyan 7 bulan lalu
induk
melakukan
d8213c682a

+ 1 - 0
components.d.ts

@@ -23,6 +23,7 @@ declare module 'vue' {
     ManyToMany: typeof import('./src/components/manyToMany/index.vue')['default']
     NoticeBar: typeof import('./src/components/noticeBar/index.vue')['default']
     PermissionButton: typeof import('./src/components/PermissionButton/index.vue')['default']
+    ProgressBar: typeof import('./src/components/ProgressBar/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SvgIcon: typeof import('./src/components/svgIcon/index.vue')['default']

+ 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": {

+ 3 - 3
src/auto-imports.d.ts

@@ -73,12 +73,12 @@ declare global {
   const useId: typeof import('vue')['useId']
   const useLink: typeof import('vue-router')['useLink']
   const useModel: typeof import('vue')['useModel']
-  const usePagination: typeof import('../../../../../../@/utils/usePagination')['usePagination']
-  const useResponse: typeof import('../../../../../../@/utils/useResponse')['useResponse']
+  const usePagination: typeof import('../../../../../../../@/utils/usePagination')['usePagination']
+  const useResponse: typeof import('../../../../../../../@/utils/useResponse')['useResponse']
   const useRoute: typeof import('vue-router')['useRoute']
   const useRouter: typeof import('vue-router')['useRouter']
   const useSlots: typeof import('vue')['useSlots']
-  const useTableData: typeof import('../../../../../../@/utils/useTableData')['useTableData']
+  const useTableData: typeof import('../../../../../../../@/utils/useTableData')['useTableData']
   const useTemplateRef: typeof import('vue')['useTemplateRef']
   const watch: typeof import('vue')['watch']
   const watchEffect: typeof import('vue')['watchEffect']

+ 1 - 1
src/utils/useResponse.ts

@@ -1,7 +1,7 @@
 /**
  * 请求响应处理
- * @param parameter 请求参数
  * @param requestApi 请求接口
+ * @param parameter 请求参数
  * @param loadingOrOptions ref-loading | vxe-loading (ref | obj)
  */
 export async function useResponse(

+ 25 - 128
src/views/product-manage/product-list/ColumnsTsx.tsx

@@ -1,20 +1,9 @@
-import { useCountryInfoStore } from '/@/stores/countryInfo';
-import { getTagType } from '/@/utils/useTagColor';
-
-
-const countryInfoStore = useCountryInfoStore();
-
-export const productColumns = (handleMonitor: Function) => [
+export const productColumns = [
   { type: 'checkbox', width: 50, align: 'center', fixed: 'left' },
   { type: 'seq', title: 'No.', width: 60, align: 'center' },
   {
     field: 'is_monitor', title: '监控管理', width: 90, align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <el-switch v-model={ row.is_monitor }
-                          onChange={ (newVal: any) => handleMonitor({ id: row.id, isMonitor: newVal }) } />;
-      }
-    }
+    slots: { default: 'is_monitor' }
   },
   {
     field: 'product_info', title: '商品信息', minWidth: 'auto', align: 'center',
@@ -22,155 +11,63 @@ export const productColumns = (handleMonitor: Function) => [
   },
   {
     field: 'sku', title: 'SKU', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <span class={ 'font-medium' }>{ row.sku ? row.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: 'brand', title: '品 牌', minWidth: 'auto', align: 'center',
+    slots: { default: 'brand' }
   },
   {
     field: 'platform_number', title: '平台编号', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return <span class={ 'font-medium' }>{ row.platform_number ? row.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>
-        );
-      }
-    }
-  },
-  {
-    field: 'tag', title: '分 组',  minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <el-tag type={ getTagType.value(row.tag) }>
-              { row.tag ? row.tag : '--' }
-            </el-tag>
-        );
-      }
-    }
+    slots: { default: 'shop_name' }
   },
   {
-    field: 'brand', title: '品 牌', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <el-tag type={ getTagType.value(row.brand) } effect="plain" round>
-              { row.brand ? row.brand : '--' }
-            </el-tag>
-        );
-      }
-    }
-  },
-  {
-    field: 'price_info', title: '价 格', minWidth: 'auto', align: 'center',
-    slots: {
-      default({ row }: any) {
-        return (
-            <div v-if={ row.price > 0 } class={ `font-medium text-left` }>
-              <p>现 价:{ row.currency_code + '‎' + row.price }</p>
-              <p>折 扣:{ row.discount > 0 ? row.discount + '%' : '-' }</p>
-              <p>优惠劵:{ !row || row.coupon <= 0 ? '-' : row.currency_code + '‎' + row.coupon }</p>
-            </div>
-        );
-      }
-    }
+    field: 'tag', title: '分 组', minWidth: 'auto', align: 'center',
+    slots: { default: 'tag' }
+  },
+  {
+    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.show_price ? row.currency_code + row.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.activity_price ? row.currency_code + row.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.minimum_price ? row.currency_code + row.minimum_price : '--' }</div>;
-      }
-    }
+    field: 'minimum_price', title: '最低活动售价', minWidth: 'auto', align: 'center',
+    slots: { default: 'minimum_price' }
   },
   {
     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',

+ 32 - 44
src/views/product-manage/product-list/api.ts

@@ -3,92 +3,80 @@ import { request } from '/@/utils/service';
 
 const apiPrefix = '/api/choice/goods/';
 
-export function getCardData(query: any) {
-  return request({
-    url: apiPrefix + 'card/',
-    method: 'GET',
-    params: query,
-  });
-}
-
 export function getTableData(query: any) {
   return request({
     url: apiPrefix,
     method: 'GET',
-    params: query,
+    params: query
   });
 }
 
-export function getPlatformDetailOverview(query: any) {
+export function updateRow(body: any) {
   return request({
-    url: apiPrefix + 'platform/',
-    method: 'GET',
-    params: query,
+    url: apiPrefix + body.id + '/',
+    method: 'PUT',
+    data: body
   });
 }
 
 export function getGroupOptions() {
   return request({
     url: apiPrefix + 'tags/',
-    method: 'GET',
-  });
-}
-export function getImportTemplateProduct() {
-  return request({
-    url: apiPrefix + 'import_data/',
-    method: 'GET',
+    method: 'GET'
   });
 }
-export function getImportTemplatePrice() {
+
+export function getBrandsOptions() {
   return request({
-    url: apiPrefix + 'GuidancePrice/import_data/',
-    method: 'GET',
+    url: apiPrefix + 'brands/',
   });
 }
 
-export function getCurrentInfo(query: any) {
+export function getShopOptions() {
   return request({
-    url: apiPrefix + 'current/',
+    url: '/api/choice/marketplace_shops/select/',
     method: 'GET',
-    params: query,
   });
 }
 
-export function getHistoryInfo(query: any) {
+export function getStaffsOptions(query: any) {
   return request({
-    url: apiPrefix + 'past/',
-    method: 'GET',
+    url: '/api/system/user/select/',
     params: query,
-  });
+    method: 'GET'
+  })
 }
 
-export function getComputerInfo(query: any) {
+export function getExistingStaffs(query: any) {
   return request({
-    url: apiPrefix + 'computer/',
-    method: 'GET',
+    url: apiPrefix + 'alarm-manage/',
     params: query,
-  });
+    method: 'GET'
+  })
 }
 
-export function getShopSelect() {
+export function postMonitor(body: any) {
   return request({
-    url: apiPrefix + 'box/',
-    method: 'GET',
-  });
+    url: apiPrefix + 'monitor/',
+    data: body,
+    method: 'POST'
+  })
 }
-export function getCompanySelect() {
+
+export function postStaffs(body: any) {
   return request({
-    url: '/api/assets/company/box/',
-    method: 'GET',
-  });
+    url: apiPrefix + 'alarm-manage/',
+    data: body,
+    method: 'post'
+  })
 }
 
 export function updateShopDetail(body: any) {
   return request({
-    url: apiPrefix + `${body.id}/`,
+    url: apiPrefix + `${ body.id }/`,
     method: 'POST',
     params: { partial: body.partial },
-    data: body.formData,
+    data: body.formData
   });
 }
 

+ 39 - 36
src/views/product-manage/product-list/component/DataTable.vue

@@ -14,8 +14,8 @@ import ImportButton from '/src/components/ImportButton/index.vue';
 import VerticalDivider from '/src/components/VerticalDivider/index.vue';
 import { productColumns } from '../ColumnsTsx';
 import { downloadFile } from '/@/utils/service';
-import { getTagType } from '/@/utils/useTagColor';
-import ProductInfo from '/@/views/product-manage/product-list/component/ProductInfo.vue';
+import DataTableSlot from './DataTableSlot.vue';
+import { ElMessage } from 'element-plus';
 
 
 interface Parameter {
@@ -33,13 +33,11 @@ const { tableOptions, handlePageChange } = usePagination(fetchList);
 
 const gridRef = ref();
 const gridOptions: any = reactive({
-  id: 'tableID',
   border: false,
   round: true,
   stripe: true,
   currentRowHighLight: true,
   height: '100%',
-  width: '100%',
   toolbarConfig: {
     custom: true,
     slots: {
@@ -63,11 +61,12 @@ const gridOptions: any = reactive({
     icon: 'vxe-icon-indicator roll',
     text: '正在拼命加载中...'
   },
-  columns: productColumns(handleMonitor),
+  columns: '',
   data: ''
 });
 
 const checkedList = ref<Set<number>>(new Set());
+const btnLoading = ref(false);
 
 const editOpen = ref(false);
 const rowData = ref({});
@@ -76,11 +75,13 @@ const dialogVisible = ref(false);
 
 const templateType = ref('notice');
 
-onBeforeMount(() => {
+onMounted(() => {
   fetchList();
 });
 
 async function fetchList() {
+  gridOptions.data = [];
+  
   const query = {
     country_code: queryParameter?.country,
     brand: queryParameter?.brand,
@@ -90,20 +91,16 @@ async function fetchList() {
     sku: queryParameter?.sku,
     shop_id: queryParameter?.shop
   };
+
   await useTableData(api.getTableData, query, gridOptions);
+  await gridRef.value.loadColumn(productColumns);
+  gridOptions.showHeader = Boolean(gridOptions.data?.length);
 }
 
 function handleRefresh() {
   fetchList();
 }
 
-async function batchOpen() {
-  const ids = Array.from(checkedList.value);
-  await useResponse(api.updateShopDetail, { ids, status: 1 });
-  checkedList.value.clear();
-  await fetchList();
-}
-
 function selectChangeEvent({ checked, row }: any) {
   if (checked) {
     checkedList.value.add(row.id); // 获取单个数据
@@ -136,6 +133,28 @@ function handleNotice(row: any) {
   rowData.value = row;
 }
 
+async function switchMonitor(row: any) {
+  const res = await useResponse(api.postMonitor, { ids: [row.id], status: row.is_monitor ? 1 : 0 })
+  if (res && res.code === 2000) {
+    ElMessage.success('操作成功!');
+  } else {
+    row.is_monitor = !row.is_monitor;
+    ElMessage.error('操作失败!');
+  }
+}
+
+
+async function batchOpen() {
+  const ids = Array.from(checkedList.value);
+  const res = await useResponse(api.postMonitor, { ids, status: 1 }, btnLoading);
+  if (res && res.code === 2000) {
+    ElMessage.success('操作成功!');
+  }
+  checkedList.value.clear();
+  await fetchList();
+}
+
+
 function downloadTemplate() {
   const urlMap: { [key: string]: string } = {
     notice: '/api/choice/goods/alarm/import_data/',
@@ -154,22 +173,19 @@ function downloadTemplate() {
   }
 }
 
-function handleMonitor(val: any) {
-  console.log('(DataTable.vue: 157)=> val', val);
-}
-
 defineExpose({ fetchList });
 </script>
 
 <template>
-  <vxe-grid :key="gridOptions.id" ref="gridRef"
+  <vxe-grid ref="gridRef"
             v-bind="gridOptions"
             @checkbox-change="selectChangeEvent"
             @checkbox-all="selectAllChangeEvent">
     <!-- 工具栏左侧 -->
     <template #toolbar_buttons>
       <div class="flex gap-2">
-        <PermissionButton :disabled="!checkedList.size" :icon="Open" plain round type="primary" @click="batchOpen">
+        <PermissionButton :disabled="!checkedList.size" :icon="Open" :loading="btnLoading" plain round type="primary" 
+                          @click="batchOpen">
           批量开启
         </PermissionButton>
         <div class="custom-el-input">
@@ -226,25 +242,12 @@ defineExpose({ fetchList });
       />
     </template>
     <!-- 自定义列插槽 -->
-    <template #product_info="{ row }">
-      <ProductInfo :item="row" :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="info" @click="handleNotice(row)">
-          <el-icon>
-            <Message />
-          </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="handleNotice"
+     @handle-monitor="switchMonitor" />
     </template>
   </vxe-grid>
-  <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" />
+  <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" @refresh="handleRefresh" />
   <NoticeDialog v-if="dialogVisible" v-model="dialogVisible" :row-data="rowData" />
 </template>
 

+ 56 - 19
src/views/product-manage/product-list/component/DataTableSlot.vue

@@ -1,14 +1,15 @@
 <script lang="ts" setup>
 /**
  * @Name: DataTableSlot.vue
- * @Description:
+ * @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 { Message, Operation } from '@element-plus/icons-vue';
+import PermissionButton from '/@/components/PermissionButton/index.vue';
+import ProductInfo from '/@/views/product-manage/product-list/component/ProductInfo.vue';
+import { getTagType } from '/@/utils/useTagColor';
 
 
 const props = defineProps<{
@@ -17,12 +18,15 @@ const props = defineProps<{
 }>();
 const { row, field } = props;
 
-const emit = defineEmits([ 'edit-row', 'handle-notice' ]);
+const emit = defineEmits([ 'edit-row', 'handle-notice', 'handle-monitor' ]);
 
 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);
 }
@@ -30,33 +34,66 @@ function handleEdit(row: any) {
 function handleNotice(row: any) {
   emit('handle-notice', row);
 }
+
+function handleMonitor(row: any) {
+  emit('handle-monitor', row);
+}
 </script>
 
 <template>
   <div class="font-medium">
     <div v-if="field === 'is_monitor'">
-      <el-switch v-model="row.is_monitor"></el-switch>
+      <el-switch v-model=row.is_monitor @change="handleMonitor(row)" />
+    </div>
+    <div v-else-if="field === 'product_info'">
+      <ProductInfo :img-width="50" :item="row" style="min-width: 230px" />
     </div>
     <div v-else-if="field === 'country_code'">
-      <el-tag :style="{ color: color, borderColor: color }" effect="plain" round>
+      <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'">
-        {{ row.goods_info ? row.goods_info : '--' }}
-    </div>
-    <div v-else-if="field === 'platform_number'">
-        {{ row.platform_number ? row.platform_number : '--' }}
-    </div>
     <div v-else-if="field === 'shop_name'">
-      <el-tag type="primary" effect="plain">{{ row.shop_name ? 
-          row.shop_name : 
-          '--' }}</el-tag>
+      <el-tag :disable-transitions="true" :type=getTagType(row.shop_name)>
+        {{ row.shop_name ?? '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'tag'">
+      <el-tag :disable-transitions="true" :type=getTagType(row.tag)>
+        {{ row.tag ?? '--' }}
+      </el-tag>
     </div>
     <div v-else-if="field === 'brand'">
-      <el-tag :style="{ color: '#F88B8A', borderColor: '#FBC4C4'}"  effect="plain">{{ row.brand ? 
-          row.brand : 
-          '--' }}</el-tag>
+      <el-tag :disable-transitions="true" :type=getTagType(row.brand) effect="plain" round>
+        {{ row.brand ?? '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'price_info'">
+      <div v-if="row.price > 0" class="font-medium">
+        <p>现 价:{{ row.currency_code + '‎' + row.price }}</p>
+        <p>折 扣:{{ row.discount > 0 ? row.discount + '%' : '-' }}</p>
+        <p>优惠劵:{{ !row || row.coupon <= 0 ? '-' : row.currency_code + '‎' + row.coupon }}</p>
+      </div>
+    </div>
+    <div v-else-if="field === 'show_price'">
+      <div class="font-medium">
+        {{ row.show_price ? row.currency_code + row.show_price : '--' }}
+      </div>
+    </div>
+    <div v-else-if="field === 'activity_price'">
+      <div class="font-medium">
+        {{ row.activity_price ? row.currency_code + row.activity_price : '--' }}
+      </div>
+    </div>
+    <div v-else-if="field === 'minimum_price'">
+      <div class="font-medium">
+        {{ row.minimum_price ? row.currency_code + row.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">

+ 104 - 69
src/views/product-manage/product-list/component/EditDrawer.vue

@@ -1,112 +1,147 @@
 <script lang="ts" setup>
 /**
  * @Name: EditDrawer.vue
- * @Description: 店铺编辑
+ * @Description: 商品列表-行编辑
  * @Author: Cheney
  */
 
 import { ElMessage, FormInstance, FormRules } from 'element-plus';
+import { Close, Finished } from '@element-plus/icons-vue';
+import * as api from '../api';
 
 
-const loading = ref(false);
+const shopOptions = <Ref>inject('shopOptions');
+const groupOptions = <Ref>inject('groupOptions');
+
+const btnLoading = ref(false);
+
 const editOpen = defineModel({ default: false });
-const { rowData } = defineProps<{
-  rowData: object;
-}>();
+
+const editDrawer = <Ref>useTemplateRef('editDrawer');
+
+const props = defineProps({
+  rowData: Object
+});
+const { rowData } = props;
 
 const emit = defineEmits([ 'refresh' ]);
 
 onBeforeMount(() => {
-  // replaceCol();
   console.log('rowData=> ', rowData);
 });
 
 interface RuleForm {
-  operatorName: any,
-  country: any
+  sku: any,
+  shop_name: any,
+  shop_id: any,
+  tag: any,
+  show_price: any,
+  activity_price: any,
+  minimum_price: any,
 }
 
 const ruleFormRef = ref<FormInstance>();
 const ruleForm = reactive<RuleForm>({
-  operatorName: '',
-  country: ''
+  sku: rowData?.sku,
+  shop_name: rowData?.shop_name,
+  shop_id: rowData?.shop_id,
+  tag: rowData?.tag,
+  show_price: rowData?.show_price,
+  activity_price: rowData?.activity_price,
+  minimum_price: rowData?.minimum_price
 });
 
-const rules = reactive<FormRules<RuleForm>>({
-  operatorName: [
-    { message: 'Please input operator name', trigger: 'blur' }
-  ]
-
-});
+const rules = reactive<FormRules<RuleForm>>({});
 
 const submitForm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return;
   await formEl.validate(async (valid, fields) => {
     if (valid) {
-      // await useResponse({ id: gridOptions.data[0].id, partial: 1, formData: ruleForm }, api.updateShopDetail, loading);
-      editOpen.value = false;
-      ElMessage.success('编辑成功');
-      emit('refresh');
+      try {
+        const res = await useResponse(api.updateRow, { id: rowData?.id, ...ruleForm }, btnLoading);
+        if (res && res.code == 2000) {
+          editOpen.value = false;
+          ElMessage.success('编辑成功');
+          emit('refresh');
+        }
+      } catch (error) {
+        console.error('Error==>', error);
+      }
     } else {
       console.log('error submit!', fields);
     }
   });
 };
 
-const resetForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-};
-
-// function replaceCol() {
-//   const result = Object.keys(ruleForm).reduce((acc, key) => {
-//     if (key in gridOptions.data[0]) {
-//       acc[key] = gridOptions.data[0][key];
-//     }
-//     return acc;
-//   }, {} as { [key: string]: any });
-//   Object.assign(ruleForm, result);
-// }
+function closeDrawer() {
+  editDrawer.value.handleClose();
+}
 </script>
 
 <template>
-  <el-drawer v-model="editOpen"
-             :close-on-click-modal="false"
-             :close-on-press-escape="false"
-             :title="`店铺编辑 - `"
-             size="30%">
-    <el-form
-        ref="ruleFormRef"
-        :model="ruleForm"
-        :rules="rules"
-        class="mx-2.5 mt-2.5"
-        label-width="auto"
-        status-icon>
-      <el-form-item label="运营" prop="operatorName">
-        <el-input v-model="ruleForm.operatorName"/>
-      </el-form-item>
-
-      <el-form-item label="国家" prop="country">
-        <!--<el-select v-model="ruleForm.country" placeholder="请选择线路">-->
-        <!--  <el-option-->
-        <!--      v-for="item in rowData.country"-->
-        <!--      :key="item"-->
-        <!--      :label="item"-->
-        <!--      :value="item">-->
-        <!--  </el-option>-->
-        <!--</el-select>-->
-      </el-form-item>
-      <el-form-item>
-        <el-divider />
-        <div class="flex flex-1 justify-center">
-          <el-button :loading="loading" type="primary" @click="submitForm(ruleFormRef)">确 定</el-button>
-          <el-button @click="resetForm(ruleFormRef)">重 置</el-button>
-        </div>
-      </el-form-item>
-    </el-form>
-  </el-drawer>
+  <div class="drawer-container">
+    <el-drawer
+        ref="editDrawer"
+        v-model="editOpen"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        size="30%"
+        title="商品列表- 编辑">
+      <!--<div class="mx-2.5">-->
+      <!--  -->
+      <!--<el-divider style="margin: 0"  />-->
+      <!--</div>-->
+      <el-form
+          ref="ruleFormRef"
+          :model="ruleForm"
+          :rules="rules"
+          class="mx-2.5 mt-7"
+          label-width="auto"
+          status-icon>
+        <el-form-item class="font-medium" label="SKU" prop="sku">
+          <el-input v-model="ruleForm.sku" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="店 铺" prop="shop_name">
+          <el-select v-model="ruleForm.shop_name">
+            <el-option v-for="item in shopOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item class="font-medium" label="分 组" prop="tag">
+          <el-select v-model="ruleForm.tag">
+            <el-option v-for="item in groupOptions" :label="item.tag" :value="item.tag" />
+          </el-select>
+        </el-form-item>
+        <el-form-item class="font-medium" label="展示价格" prop="show_price">
+          <el-input v-model="ruleForm.show_price" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="平时活动售价" prop="activity_price">
+          <el-input v-model="ruleForm.activity_price" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="最低活动售价" prop="minimum_price">
+          <el-input v-model="ruleForm.minimum_price" />
+        </el-form-item>
+        <el-form-item>
+          <el-divider />
+          <div class="flex flex-1 justify-end">
+            <el-button :icon="Close" @click="closeDrawer">取 消</el-button>
+            <el-button :icon="Finished" :loading="btnLoading" type="primary" @click="submitForm(ruleFormRef)">
+              确 定
+            </el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+    </el-drawer>
+  </div>
+
 </template>
 
 <style scoped>
+.drawer-container :deep(.el-drawer__header) {
+  border-bottom: none;
+  font-weight: 500;
+}
 
+.drawer-container :deep(.el-drawer__title) {
+  font-size: 18px;
+}
 </style>

+ 52 - 31
src/views/product-manage/product-list/component/NoticeDialog.vue

@@ -1,23 +1,34 @@
 <script lang="ts" setup>/**
  * @Name: NoticeDialog.vue
- * @Description:
+ * @Description: 变更通知弹窗
  * @Author: Cheney
  */
 import { ElMessage } from 'element-plus';
+import { useResponse } from '/@/utils/useResponse';
+import * as api from '../api';
+import { Close, Finished } from '@element-plus/icons-vue';
 
 
-const { rowData } = defineProps<{
-  rowData: object;
-}>();
-// console.log('rowData=> ', rowData);
+const props = defineProps({
+  rowData: Object
+});
+const { rowData } = props;
+
 const dialogVisible = defineModel({ default: false });
 
+const noticeDialog = <Ref>useTemplateRef('noticeDialog');
+
 const staffSelect = ref('');
 const staffOptions: any = ref([]);
 const staffTags: any = ref([]);
 const staffLoading = ref(false);
 const currentRow: any = ref(null);
 
+onBeforeMount(() => {
+  fetchStaffsOptions();
+  fetchExistingStaff();
+});
+
 function handleClose(done: any) {
   staffSelect.value = '';
   staffTags.value = [];
@@ -40,47 +51,52 @@ function isOptionDisabled(id: any) {
 }
 
 function removeTag(tag: any) {
+  console.log('tag=> ', tag);
   staffTags.value = staffTags.value.filter((t: any) => t.id !== tag.id);
+  staffSelect.value = '';
 }
 
-async function fetchExistingStaff(row: any) {
-  const query = {
-    id: row.value.id ? row.value.id : row.value.rowid
-  };
-  // const resp = await api.getExistingStaffs(query);
-  // staffTags.value = resp.data;
+async function fetchStaffsOptions() {
+  const res = await useResponse(api.getStaffsOptions);
+  staffOptions.value = res.data;
+}
+
+async function fetchExistingStaff() {
+  const resp = await useResponse(api.getExistingStaffs, {
+    id: rowData?.id ? rowData?.id : rowData?.rowid
+  });
+  staffTags.value = resp.data;
 }
 
 async function addStaffs() {
-  staffLoading.value = true;
   const body = {
-    id: currentRow.id,
-    user_ids: staffTags.map((tag: any) => tag.id)
+    id: rowData?.id,
+    user_ids: staffTags.value.map((tag: any) => tag.id)
   };
   try {
-    // const resp = await api.postStaffs(body);
-    // if (resp.code === 2000) {
-    //   ElMessage.error('编辑成功!');
-    //   await fetchExistingStaff(currentRow);
-    // }
+    const resp = await useResponse(api.postStaffs, body, staffLoading)
+    if (resp.code === 2000) {
+      ElMessage.success('编辑成功!');
+      fetchExistingStaff();
+    }
   } catch (error) {
     ElMessage.error('编辑失败!');
   } finally {
     staffSelect.value = '';
-    staffLoading.value = false;
   }
 }
 
 function cancelDialog() {
-  handleClose(() => {
-    dialogVisible.value = false;
-  });
+  staffSelect.value = '';
+  staffTags.value = [];
+  noticeDialog.value.visible = false;
 }
 </script>
 
 <template>
   <div>
     <el-dialog
+        ref="noticeDialog"
         v-model="dialogVisible"
         :before-close="handleClose"
         :close-on-click-modal="false"
@@ -103,29 +119,34 @@ function cancelDialog() {
           </el-select>
         </el-col>
       </el-row>
-      <el-row :gutter="20">
+      <el-row :gutter="20" class="mb-4">
         <el-col :span="2">
-
         </el-col>
         <el-col :span="20" class="ml-2.5">
           <i class="bi bi-info-circle"></i>
           <span class="ml-1" style="color: #909399">仅可添加已绑定邮箱的用户</span>
         </el-col>
       </el-row>
-      <el-divider style="margin: 12px 0 20px 0"></el-divider>
-      <div class="flex flex-wrap gap-1.5">
+      <div class="flex flex-wrap gap-1.5 min-h-6">
         <el-tag
             v-for="tag in staffTags"
             :key="tag.id"
             closable
+            round
+            effect="plain"
             @close="removeTag(tag)">
           {{ tag.username }}
         </el-tag>
       </div>
-      <span slot="footer" class="dialog-footer">
-        <el-button @click="cancelDialog">取 消</el-button>
-        <el-button :loading="staffLoading" type="primary" @click="addStaffs">确 定</el-button>
-      </span>
+
+      <el-divider style="margin: 12px 0 20px 0"></el-divider>
+      <template #footer>
+        <el-button :icon="Close" @click="cancelDialog">取 消</el-button>
+        <el-button :icon="Finished" :loading="staffLoading" type="primary" @click="addStaffs">确 定</el-button>
+      </template>
+      <!--<span slot="footer" class="dialog-footer">-->
+      <!--  -->
+      <!--</span>-->
     </el-dialog>
   </div>
 </template>

+ 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>

+ 35 - 18
src/views/product-manage/product-list/index.vue

@@ -15,40 +15,56 @@ import * as api from './api';
 import { useTemplateRef } from 'vue';
 
 
-const { data: staticData } = DictionaryStore()
+const { data: staticData } = DictionaryStore();
 
 const titleContainer: Ref<HTMLElement | null> = useTemplateRef('titleContainer');
 const queryContainer: Ref<HTMLElement | null> = useTemplateRef('queryContainer');
 const { tableHeight } = useTableHeight(titleContainer, queryContainer);
 
-const tableRef: Ref<any> = useTemplateRef('table')
+const tableRef: Ref<any> = useTemplateRef('table');
 
 const btnLoading = ref(false);
 
 const formInline = reactive({
-  country: '',
-  brand: '',
+  country: 'US',
+  brand: 'ZOSI',
   group: '',
   status: '',
   asin: '',
   sku: '',
-  shop: '',
+  shop: ''
 });
-provide('query-parameter', formInline)
+provide('query-parameter', formInline);
 
-const groupOptions:any = ref([])
+const groupOptions: any = ref([]);
+const brandsOptions: any = ref([]);
+const shopOptions: any = ref([]);
+
+provide('groupOptions', groupOptions);
+provide('brandsOptions', brandsOptions);
+provide('shopOptions', shopOptions);
 
 onBeforeMount(() => {
-  fetchGroupOptions()
-})
+  fetchAllOptions();
+});
 
-async function fetchGroupOptions() {
-  const res = await useResponse(api.getGroupOptions)
-  groupOptions.value = res.data
+async function fetchAllOptions() {
+  try {
+    const [ groupRes, brandsRes, shopRes ] = await Promise.all([
+      useResponse(api.getGroupOptions),
+      useResponse(api.getBrandsOptions),
+      useResponse(api.getShopOptions)
+    ]);
+
+    groupOptions.value = groupRes.data;
+    brandsOptions.value = brandsRes.data;
+    shopOptions.value = shopRes.data;
+  } catch (error) {
+    console.error('Error fetching options: ', error);
+  }
 }
 
 async function handleQuery() {
-  console.log('tableRef=> ', tableRef.value);
   btnLoading.value = true;
   await tableRef.value?.fetchList();
   btnLoading.value = false;
@@ -68,7 +84,7 @@ async function handleQuery() {
                 <div class="flex items-center">
                   <span class="mr-2">国 家</span>
                   <el-select v-model="formInline.country" clearable placeholder="请选择国家">
-                    <el-option v-for="item in staticData.country_code" :key="item.value" :label="item.label" 
+                    <el-option v-for="item in staticData.country_code" :key="item.value" :label="item.label"
                                :value="item.value" />
                   </el-select>
                 </div>
@@ -76,15 +92,16 @@ async function handleQuery() {
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">品 牌</span>
-                  <el-select v-model="formInline.brand" clearable placeholder="请选择品牌" />
+                  <el-select v-model="formInline.brand" clearable placeholder="请选择品牌">
+                    <el-option v-for="item in brandsOptions" :label="item.brand" :value="item.brand" />
+                  </el-select>
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">分 组</span>
                   <el-select v-model="formInline.group" clearable placeholder="请选择分组">
-                    <el-option v-for="item in groupOptions" :label="item.tag" 
-                               :value="item.tag" />
+                    <el-option v-for="item in groupOptions" :label="item.tag" :value="item.tag" />
                   </el-select>
                 </div>
               </el-col>
@@ -92,7 +109,7 @@ async function handleQuery() {
                 <div class="flex items-center">
                   <span class="mr-2">状 态</span>
                   <el-select v-model="formInline.status" clearable placeholder="请选择状态">
-                    <el-option v-for="item in staticData.goods_status" :key="item.value" :label="item.label" 
+                    <el-option v-for="item in staticData.goods_status" :key="item.value" :label="item.label"
                                :value="item.value" />
                   </el-select>
                 </div>

+ 94 - 6
src/views/product-manage/product-monitor/ColumnsTsx.tsx

@@ -1,16 +1,104 @@
-export const productColumns: any = [
+export const productColumns = [
   { type: 'checkbox', width: 50, align: 'center', fixed: 'left' },
-  { type: 'seq', width: 60, align: 'center' },
+  { type: 'seq', title: 'No.', width: 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: 'country', title: '国 家', minWidth: 'auto', align: 'center',
-    slots: { default: 'country' }
+    field: 'sku', title: 'SKU', minWidth: 'auto', align: 'center',
+    slots: { default: 'sku' }
   },
   {
-    field: 'operate', title: '操 作', width: 100, align: 'center', fixed: 'right',
+    field: 'country_code', title: '国 家', minWidth: 'auto', align: 'center',
+    slots: { default: 'country_code' }
+  },
+  {
+    field: 'brand', title: '品 牌', minWidth: 'auto', align: 'center',
+    slots: { default: 'brand' }
+  },
+  {
+    field: 'platform_number', title: '平台编号', minWidth: 'auto', align: 'center',
+    slots: { default: 'platform_number' }
+  },
+  {
+    field: 'shop_name', title: '店 铺', minWidth: 'auto', align: 'center',
+    slots: { default: 'shop_name' }
+  },
+  {
+    field: 'tag', title: '分 组', minWidth: 'auto', align: 'center',
+    slots: { default: 'tag' }
+  },
+  {
+    field: 'price_info', title: '价 格', minWidth: 'auto', headerAlign: 'center', align: 'left',
+    slots: { default: 'price_info' }
+  },
+  {
+    field: 'show_price', title: '展示价格', minWidth: 'auto', align: 'center',
+    slots: { default: 'show_price' }
+  },
+  {
+    field: 'activity_price', title: '平时活动售价', minWidth: 'auto', align: 'center',
+    slots: { default: 'activity_price' }
+  },
+  {
+    field: 'minimum_price', title: '最低活动售价', minWidth: 'auto', align: 'center',
+    slots: { default: 'minimum_price' }
+  },
+  {
+    field: 'ratings', title: '子ASIN评分人数', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'ratings' }
+  },
+  {
+    field: 'all_ratings', title: '亚马逊显示评分人数', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'all_ratings' }
+  },
+  {
+    field: 'reviews', title: '子ASIN评论人数', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'reviews' }
+  },
+  {
+    field: 'all_reviews', title: '亚马逊显示评论人数', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'all_reviews' }
+  },
+  {
+    field: 'score', title: '子ASIN计算评分', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'score' }
+  },
+  {
+    field: 'all_score', title: '亚马逊显示评分', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'all_score' }
+  },
+  {
+    field: 'stars', title: '子ASIN星级分布', minWidth: 'auto', headerAlign: 'center', align: 'center',
+    slots: { default: 'stars' }
+  },
+  {
+    field: 'all_stars', title: '亚马逊星级分布', minWidth: 'auto', headerAlign: 'center', align: 'center',
+    slots: { default: 'all_stars' }
+  },
+  {
+    field: 'launch_date', title: '上架日期', minWidth: 'auto', align: 'center', sortable: true,
+    slots: { default: 'launch_date' }
+  },
+  {
+    field: 'category', title: '类 目', minWidth: 'auto', align: 'center',
+    slots: { default: 'category' }
+  },
+  {
+    field: 'status', title: '状 态', minWidth: 'auto', align: 'center',
+    slots: { default: 'status' }
+  },
+  {
+    field: 'update_datetime', title: '更新时间', minWidth: 'auto', align: 'center',
+    slots: { default: 'update_datetime' }
+  },
+  {
+    field: 'create_datetime', title: '创建时间', minWidth: 'auto', align: 'center',
+    slots: { default: 'create_datetime' }
+  },
+  {
+    field: 'operate', title: '操 作', minWidth: 100, align: 'center', fixed: 'right',
     slots: { default: 'operate' }
   }
 ];

+ 15 - 52
src/views/product-manage/product-monitor/api.ts

@@ -1,83 +1,46 @@
-import { request } from '/src/utils/service';
+import { request } from '/@/utils/service';
 
 
-const apiPrefix = '/api/assets/shop/';
-
-export function getCardData(query: any) {
-  return request({
-    url: apiPrefix + 'card/',
-    method: 'GET',
-    params: query,
-  });
-}
+const apiPrefix = '/api/choice/';
 
 export function getTableData(query: any) {
   return request({
-    url: 'api/choice/reviews_monitor',
-    method: 'GET',
-    params: query,
-  });
-}
-
-export function getPlatformDetailOverview(query: any) {
-  return request({
-    url: apiPrefix + 'platform/',
-    method: 'GET',
-    params: query,
-  });
-}
-
-export function getShopDetailOverview(query: any) {
-  return request({
-    url: apiPrefix + 'detail/',
-    method: 'GET',
-    params: query,
-  });
-}
-
-export function getCurrentInfo(query: any) {
-  return request({
-    url: apiPrefix + 'current/',
+    url: apiPrefix + 'reviews_monitor/',
     method: 'GET',
-    params: query,
+    params: query
   });
 }
 
-export function getHistoryInfo(query: any) {
+export function getGroupOptions(query: any) {
   return request({
-    url: apiPrefix + 'past/',
+    url: apiPrefix + 'goods/tags/',
     method: 'GET',
-    params: query,
+    params: query
   });
 }
 
-export function getComputerInfo(query: any) {
+export function getBrandsOptions(query: any) {
   return request({
-    url: apiPrefix + 'computer/',
+    url: apiPrefix + 'goods/brands/',
     method: 'GET',
-    params: query,
+    params: query
   });
 }
 
-export function getShopSelect() {
-  return request({
-    url: apiPrefix + 'box/',
-    method: 'GET',
-  });
-}
-export function getCompanySelect() {
+export function getShopsOptions(query: any) {
   return request({
-    url: '/api/assets/company/box/',
+    url: apiPrefix + 'marketplace_shops/select/',
     method: 'GET',
+    params: query
   });
 }
 
 export function updateShopDetail(body: any) {
   return request({
-    url: apiPrefix + `${body.id}/`,
+    url: apiPrefix + `${ body.id }/`,
     method: 'POST',
     params: { partial: body.partial },
-    data: body.formData,
+    data: body.formData
   });
 }
 

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

@@ -1,20 +1,35 @@
 <script lang="ts" setup>
 /**
  * @Name: Table.vue
- * @Description: 商品列表表格
+ * @Description: 商品监控表格
  * @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 DataTableSlot from './DataTableSlot.vue';
 import { productColumns } from '../ColumnsTsx';
+import DataTableSlot from '/@/views/product-manage/product-monitor/component/DataTableSlot.vue';
+
+
+interface Parameter {
+  country: string,
+  brand: string,
+  group: string,
+  status: string,
+  shop: string
+  asin: string,
+  sku: string,
+  platformId: string,
+  scoreNumber: string,
+  commentNumber: string,
+  displayScore: string,
+}
 
-
+const queryParameter: Parameter | undefined = inject('query-parameter');
 const { tableOptions, handlePageChange } = usePagination(fetchList);
 
 const gridRef = ref();
@@ -22,6 +37,7 @@ const gridOptions: any = reactive({
   border: false,
   round: true,
   stripe: true,
+  showHeader: true,
   currentRowHighLight: true,
   height: '100%',
   toolbarConfig: {
@@ -47,7 +63,7 @@ const gridOptions: any = reactive({
     icon: 'vxe-icon-indicator roll',
     text: '正在拼命加载中...'
   },
-  columns: productColumns,
+  columns: '',
   data: ''
 });
 
@@ -60,16 +76,31 @@ const dialogVisible = ref(false);
 
 const templateType = ref();
 
-onBeforeMount(() => {
+onMounted(() => {
   fetchList();
 });
 
+// TODO: 删除goods
 async function fetchList() {
+  gridOptions.data = [];
   const query = {
-    page: gridOptions.pagerConfig.page,
-    limit: gridOptions.pagerConfig.limit
+    country_code: queryParameter?.country,
+    goods__brand: queryParameter?.brand,
+    goods__tag: queryParameter?.group,
+    status: queryParameter?.status,
+    shop_id: queryParameter?.shop,
+    asin: queryParameter?.asin,
+    goods__sku: queryParameter?.sku,
+    platform_number: queryParameter?.platformId,
+    goods__all_ratings: queryParameter?.scoreNumber,
+    goods__all_reviews: queryParameter?.commentNumber,
+    goods__all_score: queryParameter?.displayScore
   };
+
   await useTableData(api.getTableData, query, gridOptions);
+  await gridRef.value.loadColumn(productColumns);
+  gridOptions.showHeader = Boolean(gridOptions.data?.length);
+
 }
 
 function handleRefresh() {
@@ -108,18 +139,19 @@ function selectAllChangeEvent({ checked }: any) {
 function handleEdit(row: any) {
   editOpen.value = true;
   rowData.value = row;
-  console.log('(DataTable.vue: 111)=> rowData.value', rowData.value);
 }
 
 function singleDelete(row: any) {
   // dialogVisible.value = true;
   rowData.value = row;
-  console.log('(DataTable.vue: 116)=> rowData.value', rowData.value);
 }
 
 function downloadTemplate() {
   // console.log('111=> ');
 }
+
+defineExpose({ fetchList });
+
 </script>
 
 <template>
@@ -179,10 +211,9 @@ function downloadTemplate() {
       />
     </template>
     <!-- 自定义列插槽 -->
-    <template v-for="col in productColumns" :key="col.field" #[`${col.field}`]="{ row }">
-      <DataTableSlot :field="col.field" :row="row" @edit-row="handleEdit" @single-delete="singleDelete" />
+    <template v-for="col in productColumns" #[`${col.field}`]="{ row }">
+      <DataTableSlot :key="row.id" :field="col.field" :row="row" @edit-row="handleEdit" @handle-delete="singleDelete" />
     </template>
-
   </vxe-grid>
   <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" />
 </template>

+ 126 - 23
src/views/product-manage/product-monitor/component/DataTableSlot.vue

@@ -1,46 +1,150 @@
 <script lang="ts" setup>
 /**
  * @Name: DataTableSlot.vue
- * @Description:
+ * @Description: 商品监控-表格插槽
  * @Author: Cheney
  */
 
-import PermissionButton from '/src/components/PermissionButton/index.vue';
-import { useCountryInfoStore } from '/src/stores/countryInfo';
-import { Operation, Delete } from '@element-plus/icons-vue';
+import { useCountryInfoStore } from '/@/stores/countryInfo';
+import { Delete, Operation } from '@element-plus/icons-vue';
+import PermissionButton from '/@/components/PermissionButton/index.vue';
+import ProductInfo from '/@/views/product-manage/product-list/component/ProductInfo.vue';
+import { getTagType } from '/@/utils/useTagColor';
 
 
 const props = defineProps<{
   row: any,
-  field: String
+  field: any
 }>();
 const { row, field } = props;
 
-const emit = defineEmits(['edit-row', 'single-delete']);
+const emit = defineEmits([ 'edit-row', 'handle-delete' ]);
 
 const countryInfoStore = useCountryInfoStore();
-const country = countryInfoStore.countries.find(c => c.name === row.country);
+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)
+  emit('edit-row', row);
 }
 
-function singleDelete(row: any) {
-  emit('single-delete', row)
+function handleDelete(row: any) {
+  emit('handle-delete', row);
 }
 </script>
 
 <template>
   <div class="font-medium">
     <div v-if="field === 'product_info'">
-      <div>
-        1232432
+      <ProductInfo :img-width="50" :item="row.goods" style="min-width: 230px" />
+    </div>
+    <div v-else-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 === 'shop_name'">
+      <el-tag :disable-transitions="true" :type=getTagType(row.shop_name)>
+        {{ row.shop_name ?? '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'tag'">
+      <el-tag :disable-transitions="true" :type=getTagType(row.goods.tag)>
+        {{ row.goods.tag ?? '--' }}
+      </el-tag>
+    </div>
+    <div v-else-if="field === 'brand'">
+      <el-tag :disable-transitions="true" :type=getTagType(row.goods.brand) effect="plain" round>
+        {{ 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 === 'country'">
-      <el-tag :style="{ color: color, borderColor: color }" effect="plain" round>
-        {{ row.country ? row.country : '--' }}
+    <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 === 'score'">
+      <template v-if="row.goods.score !== null && row.goods.score !== undefined && row.goods.score !== ''">
+        <el-rate
+            v-if="row.goods.score > 0"
+            v-model="row.goods.score"
+            :colors="['#FF0000', '#FF9900', '#67C23A']"
+            disabled
+            show-score
+            text-color="#1e293b"
+        />
+        <span v-else>{{ row.goods.score }}</span> <!-- 值为0时显示0 -->
+      </template>
+      <template v-else>
+        <span>--</span> <!-- 无值时显示'--' -->
+      </template>
+    </div>
+
+    <div v-else-if="field === 'all_score'">
+      <template v-if="row.goods.all_score !== null && row.goods.all_score !== undefined && row.goods.all_score !== ''">
+        <el-rate
+            v-if="row.goods.all_score > 0"
+            v-model="row.goods.all_score"
+            :colors="['#FF0000', '#FF9900', '#67C23A']"
+            disabled
+            show-score
+            text-color="#1e293b"
+        />
+        <span v-else>{{ row.goods.all_score }}</span>
+      </template>
+      <template v-else>
+        <span>--</span>
+      </template>
+    </div>
+    <div v-else-if="field === 'stars'" class="flex flex-col font-normal" style="min-width: 170px">
+      <div v-for="i in [5,4,3,2,1]" :key="i" class="w-full flex items-center">
+        <span class="w-10 text-right mr-2">{{ i }}星</span>
+        <el-progress
+            striped
+            striped-flow
+            :color="'#3A8EE6'"
+            :percentage="row.goods[`ratings${i}`]"
+            :stroke-width="10"
+            class="flex-1"
+        />
+      </div>
+    </div>
+    <div v-else-if="field === 'all_stars'" class="flex flex-col font-normal" style="min-width: 170px">
+      <div v-for="i in [5,4,3,2,1]" :key="i" class="w-full flex items-center">
+        <span class="w-10 text-right mr-2">{{ i }}星</span>
+        <el-progress
+            striped
+            striped-flow
+            :color="'#3A8EE6'"
+            :percentage="row.goods[`all_rate${i}`]"
+            :stroke-width="10"
+            class="flex-1"
+        />
+      </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'">
@@ -50,22 +154,21 @@ function singleDelete(row: any) {
             <Operation />
           </el-icon>
         </PermissionButton>
-        <PermissionButton circle plain type="danger" @click="singleDelete(row)">
+        <PermissionButton circle plain type="danger" @click="handleDelete(row)">
           <el-icon>
             <Delete />
           </el-icon>
         </PermissionButton>
       </div>
-  </div>
-    <div v-else-if="field === 'country'">
-      <el-tag :style="{ color: color, borderColor: color }" effect="plain" round>
-        {{ row.country ? row.country : '--' }}
-      </el-tag>
+    </div>
+    <div v-else>
+      {{ row.goods[field] }}
     </div>
   </div>
-
 </template>
 
 <style scoped>
-
+:deep(.flex-1 .el-progress__text) {
+  font-size: 14px !important;
+}
 </style>

+ 9 - 11
src/views/product-manage/product-monitor/component/EditDrawer.vue

@@ -1,13 +1,11 @@
 <script lang="ts" setup>
 /**
  * @Name: EditDrawer.vue
- * @Description: 店铺编辑
+ * @Description: 商品监控-行编辑
  * @Author: Cheney
  */
 
 import { ElMessage, FormInstance, FormRules } from 'element-plus';
-import { useResponse } from '/src/utils/useResponse';
-import * as api from '/src/views/shop-information/api';
 
 
 const loading = ref(false);
@@ -40,7 +38,7 @@ const ruleForm = reactive<RuleForm>({
   shop: '',
   group: '',
   status: '',
-  frequency: '',
+  frequency: ''
 });
 
 const rules = reactive<FormRules<RuleForm>>({
@@ -49,7 +47,7 @@ const rules = reactive<FormRules<RuleForm>>({
   country: [ { required: true, message: '请选择国家', trigger: 'change' } ],
   shop: [ { required: true, message: '请输入店铺', trigger: 'blur' } ],
   group: [ { required: true, message: '请输入分组', trigger: 'blur' } ],
-  status: [ {  message: '请选择状态', trigger: 'blur' } ],
+  status: [ { message: '请选择状态', trigger: 'blur' } ],
   frequency: [ { message: '请选择更新频率', trigger: 'blur' } ]
 
 });
@@ -98,19 +96,19 @@ const resetForm = (formEl: FormInstance | undefined) => {
         label-width="auto"
         status-icon>
       <el-form-item label="ASIN" prop="asin">
-        <el-input v-model="ruleForm.asin"/>
+        <el-input v-model="ruleForm.asin" />
       </el-form-item>
       <el-form-item label="SKU" prop="sku">
-        <el-input v-model="ruleForm.sku"/>
+        <el-input v-model="ruleForm.sku" />
       </el-form-item>
       <el-form-item label="店 铺" prop="shop">
-        <el-input v-model="ruleForm.shop"/>
+        <el-input v-model="ruleForm.shop" />
       </el-form-item>
       <el-form-item label="分 组" prop="group">
-        <el-input v-model="ruleForm.group"/>
+        <el-input v-model="ruleForm.group" />
       </el-form-item>
       <el-form-item label="状 态" prop="status">
-        <el-select v-model="ruleForm.status"/>
+        <el-select v-model="ruleForm.status" />
       </el-form-item>
       <el-form-item label="国 家" prop="country">
         <el-select v-model="ruleForm.country" placeholder="请选择国家">
@@ -123,7 +121,7 @@ const resetForm = (formEl: FormInstance | undefined) => {
         </el-select>
       </el-form-item>
       <el-form-item label="更新频率" prop="frequency">
-        <el-input-number v-model="ruleForm.frequency"/>
+        <el-input-number v-model="ruleForm.frequency" />
       </el-form-item>
       <el-form-item>
         <el-divider />

+ 0 - 135
src/views/product-manage/product-monitor/component/NoticeDialog.vue

@@ -1,135 +0,0 @@
-<script lang="ts" setup>/**
- * @Name: NoticeDialog.vue
- * @Description:
- * @Author: Cheney
- */
-import { ElMessage } from 'element-plus';
-
-
-const { rowData } = defineProps<{
-  rowData: object;
-}>();
-// console.log('rowData=> ', rowData);
-const dialogVisible = defineModel({ default: false });
-
-const staffSelect = ref('');
-const staffOptions: any = ref([]);
-const staffTags: any = ref([]);
-const staffLoading = ref(false);
-const currentRow: any = ref(null);
-
-function handleClose(done: any) {
-  staffSelect.value = '';
-  staffTags.value = [];
-  done();
-}
-
-function addStaffChange() {
-  const selectedOption: any = staffOptions.value.find((option: any) => option.id === staffSelect.value);
-  if (selectedOption && !staffTags.value.some((tag: any) => tag.id === selectedOption.id)) {
-    // 如果选中的项不在 staffTags 中,则添加到 staffTags
-    staffTags.value.push({
-      id: selectedOption.id,
-      username: selectedOption.username
-    });
-  }
-}
-
-function isOptionDisabled(id: any) {
-  return staffTags.value.some((tag: any) => tag.id === id);
-}
-
-function removeTag(tag: any) {
-  staffTags.value = staffTags.value.filter((t: any) => t.id !== tag.id);
-}
-
-async function fetchExistingStaff(row: any) {
-  const query = {
-    id: row.value.id ? row.value.id : row.value.rowid
-  };
-  // const resp = await api.getExistingStaffs(query);
-  // staffTags.value = resp.data;
-}
-
-async function addStaffs() {
-  staffLoading.value = true;
-  const body = {
-    id: currentRow.id,
-    user_ids: staffTags.map((tag: any) => tag.id)
-  };
-  try {
-    // const resp = await api.postStaffs(body);
-    // if (resp.code === 2000) {
-    //   ElMessage.error('编辑成功!');
-    //   await fetchExistingStaff(currentRow);
-    // }
-  } catch (error) {
-    ElMessage.error('编辑失败!');
-  } finally {
-    staffSelect.value = '';
-    staffLoading.value = false;
-  }
-}
-
-function cancelDialog() {
-  handleClose(() => {
-    dialogVisible.value = false;
-  });
-}
-</script>
-
-<template>
-  <div>
-    <el-dialog
-        v-model="dialogVisible"
-        :before-close="handleClose"
-        :close-on-click-modal="false"
-        :close-on-press-escape="false"
-        title="变更通知"
-        width="35%"
-    >
-      <el-row class="mb-2">
-        <el-col>
-          <span class="mr-2">人员选择</span>
-          <el-select v-model="staffSelect" filterable placeholder="输入搜索" style="width: 200px;"
-                     @change="addStaffChange">
-            <el-option
-                v-for="item in staffOptions"
-                :key="item.id"
-                :disabled="isOptionDisabled(item.id)"
-                :label="item.username"
-                :value="item.id">
-            </el-option>
-          </el-select>
-        </el-col>
-      </el-row>
-      <el-row :gutter="20">
-        <el-col :span="2">
-
-        </el-col>
-        <el-col :span="20" class="ml-2.5">
-          <i class="bi bi-info-circle"></i>
-          <span class="ml-1" style="color: #909399">仅可添加已绑定邮箱的用户</span>
-        </el-col>
-      </el-row>
-      <el-divider style="margin: 12px 0 20px 0"></el-divider>
-      <div class="flex flex-wrap gap-1.5">
-        <el-tag
-            v-for="tag in staffTags"
-            :key="tag.id"
-            closable
-            @close="removeTag(tag)">
-          {{ tag.username }}
-        </el-tag>
-      </div>
-      <span slot="footer" class="dialog-footer">
-        <el-button @click="cancelDialog">取 消</el-button>
-        <el-button :loading="staffLoading" type="primary" @click="addStaffs">确 定</el-button>
-      </span>
-    </el-dialog>
-  </div>
-</template>
-
-<style scoped>
-
-</style>

+ 61 - 14
src/views/product-manage/product-monitor/index.vue

@@ -1,23 +1,31 @@
 <script lang="ts" setup>
 /**
  * @Name: index.vue
- * @Description:
+ * @Description: 商品监控
  * @Author: Cheney
  */
 
 import { RefreshRight, Search } from '@element-plus/icons-vue';
 import VerticalDivider from '/src/components/VerticalDivider/index.vue';
 import DataTable from './component/DataTable.vue';
-import { useTableHeight } from '/src/utils/useTableHeight';
+import { useTableHeight } from '/@/utils/useTableHeight';
+import { useResponse } from '/@/utils/useResponse';
+import * as api from './api';
+import { useTemplateRef } from 'vue';
+import { DictionaryStore } from '/@/stores/dictionary';
 
 
+const { data: staticData } = DictionaryStore();
+
 const titleContainer: Ref<HTMLElement | null> = useTemplateRef('titleContainer');
 const queryContainer: Ref<HTMLElement | null> = useTemplateRef('queryContainer');
 const { tableHeight } = useTableHeight(titleContainer, queryContainer);
 
+const tableRef: Ref<any> = useTemplateRef('table');
+
 const formInline = reactive({
-  country: '',
-  brand: '',
+  country: 'US',
+  brand: 'ZOSI',
   group: '',
   status: '',
   shop: '',
@@ -28,12 +36,39 @@ const formInline = reactive({
   commentNumber: '',
   displayScore: ''
 });
+provide('query-parameter', formInline);
+
+const groupOptions: any = ref([]);
+const brandsOptions: any = ref([]);
+const shopsOptions: any = ref([]);
+
+onBeforeMount(() => {
+  fetchGroupOptions();
+  fetchBrandsOptions();
+  fetchShopsOptions();
+});
 
-const loading = ref(false);
+async function fetchGroupOptions() {
+  const res = await useResponse(api.getGroupOptions);
+  groupOptions.value = res.data;
+}
+
+async function fetchBrandsOptions() {
+  const res = await useResponse(api.getBrandsOptions);
+  brandsOptions.value = res.data;
+}
+
+async function fetchShopsOptions() {
+  const res = await useResponse(api.getShopsOptions);
+  shopsOptions.value = res.data;
+}
 
-function onClick() {
-  loading.value = true;
+const btnLoading = ref(false);
 
+async function handleQuery() {
+  btnLoading.value = true;
+  await tableRef.value?.fetchList();
+  btnLoading.value = false;
 }
 </script>
 
@@ -49,25 +84,35 @@ function onClick() {
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">国 家</span>
-                  <el-select v-model="formInline.country" clearable placeholder="请选择国家" />
+                  <el-select v-model="formInline.country" clearable placeholder="请选择国家">
+                    <el-option v-for="item in staticData.country_code" :key="item.value" :label="item.label"
+                               :value="item.value" />
+                  </el-select>
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">品 牌</span>
-                  <el-select v-model="formInline.brand" clearable placeholder="请选择品牌" />
+                  <el-select v-model="formInline.brand" clearable placeholder="请选择品牌">
+                    <el-option v-for="item in brandsOptions" :label="item.brand" :value="item.brand" />
+                  </el-select>
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">分 组</span>
-                  <el-select v-model="formInline.group" clearable placeholder="请选择分组" />
+                  <el-select v-model="formInline.group" clearable placeholder="请选择分组">
+                    <el-option v-for="item in groupOptions" :label="item.tag" :value="item.tag" />
+                  </el-select>
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">状 态</span>
-                  <el-select v-model="formInline.status" clearable placeholder="请选择状态" />
+                  <el-select v-model="formInline.status" clearable placeholder="请选择状态">
+                    <el-option v-for="item in staticData.goods_status" :key="item.value" :label="item.label"
+                               :value="item.value" />
+                  </el-select>
                 </div>
               </el-col>
             </el-row>
@@ -75,7 +120,9 @@ function onClick() {
               <el-col :span="6" class="flex">
                 <div class="flex items-center">
                   <span class="mr-2">店 铺</span>
-                  <el-select v-model="formInline.shop" clearable placeholder="请输入店铺"></el-select>
+                  <el-select v-model="formInline.shop" clearable placeholder="请输入店铺">
+                    <el-option v-for="item in shopsOptions" :key="item.id" :label="item.name" :value="item.id" />
+                  </el-select>
                 </div>
               </el-col>
               <el-col :span="6">
@@ -124,7 +171,7 @@ function onClick() {
         </div>
         <VerticalDivider />
         <div class="flex flex-col  items-end">
-          <el-button :icon="Search" :loading="loading" class="mb-4" type="primary" @click="onClick">
+          <el-button :icon="Search" :loading="btnLoading" class="mb-4" type="primary" @click="handleQuery">
             查 询
           </el-button>
           <el-button :icon="RefreshRight" color="#ECECF1C9" style="width: 88px; color: #3c3c3c;">
@@ -134,7 +181,7 @@ function onClick() {
       </div>
       <el-divider ref="dividerContainer" style="margin: 20px 0 12px 0;" />
       <div :style="{ height: tableHeight + 'px' }">
-        <DataTable />
+        <DataTable ref="table" />
       </div>
     </el-card>
   </div>