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

feat(table): 增强ZTable组件插槽功能并优化导出逻辑

- 为 ZTable 组件新增 `op` 和 `prefix` 插槽类型定义,并支持传入参数
- 修复 axios 后端错误提示文本为中文
- 调整 `.env.test` 中测试环境接口地址配置
- 新增售后订单导出接口函数
- 重构 commonExport 工具函数以支持文件名动态生成与下载状态提示
- 在多个页面中集成新的导出方法,添加 loading 状态与禁用逻辑
- 优化政府模块用户列表和积分管理中的搜索联动及权限控制
zhangtao преди 2 седмици
родител
ревизия
b563264735

+ 2 - 2
.env.test

@@ -2,11 +2,11 @@
 # VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun
 # VITE_SERVICE_BASE_URL=http://192.168.1.253:8114 #付
 # VITE_SERVICE_BASE_URL=http://192.168.0.157:8114 #王
-VITE_SERVICE_BASE_URL=http://192.168.1.166:8114 #张
+# VITE_SERVICE_BASE_URL=http://192.168.1.166:8114 #张
 # VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
 # VITE_SERVICE_BASE_URL=https://shop.platform.zswlgz.com #服务器
 # VITE_SERVICE_BASE_URL=/plt #测试打包服务器
-# VITE_SERVICE_BASE_URL=http://47.109.84.152:8114 #测试本地服务器
+VITE_SERVICE_BASE_URL=http://47.109.84.152:8114 #测试本地服务器
 
 
 # other backend service base url, test environment

+ 1 - 1
packages/axios/src/index.ts

@@ -63,7 +63,7 @@ function createCommonRequest<
       }
 
       const backendError = new AxiosError<ResponseData>(
-        'the backend request error',
+        '后端返回格式不符合标准',
         BACKEND_ERROR_CODE,
         response.config,
         response.request,

+ 13 - 0
src/components/zt/Table/props.ts

@@ -48,3 +48,16 @@ export const basicProps = {
     }
   }
 };
+export interface ZTableSlots<T = any> {
+  /**
+   * 操作列插槽
+   * @param row 当前行数据
+   */
+  op?: (props: { row: T }) => any;
+
+  /**
+   * 表格头部前缀区域插槽
+   * @param data 当前加载状态
+   */
+  prefix?: (props: { loading: boolean }) => any;
+}

+ 2 - 1
src/components/zt/Table/z-table.vue

@@ -7,6 +7,7 @@ import type { FormProps } from '../Form/types/form';
 import type { TableMethods } from './hooks/useTable';
 import type { tableProp } from './types';
 import { basicProps } from './props';
+
 export default defineComponent({
   name: 'ZTable',
   props: {
@@ -183,7 +184,7 @@ export default defineComponent({
         @refresh="getData"
       >
         <template #prefix>
-          <slot name="prefix"></slot>
+          <slot name="prefix" :loading="loading"></slot>
         </template>
       </TableHeaderOperation>
     </template>

+ 2 - 2
src/layouts/modules/global-header/index.vue

@@ -6,7 +6,7 @@ import { useThemeStore } from '@/store/modules/theme';
 import GlobalLogo from '../global-logo/index.vue';
 import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
 import GlobalSearch from '../global-search/index.vue';
-// import ThemeButton from './components/theme-button.vue';
+import ThemeButton from './components/theme-button.vue';
 import UserAvatar from './components/user-avatar.vue';
 
 defineOptions({
@@ -45,7 +45,7 @@ const { isFullscreen, toggle } = useFullscreen();
         :is-dark="themeStore.darkMode"
         @switch="themeStore.toggleThemeScheme"
       />
-      <!-- <ThemeButton /> -->
+      <ThemeButton />
       <UserAvatar />
     </div>
   </DarkModeContainer>

+ 12 - 0
src/service/api/delivery/after-sales-order/index.ts

@@ -13,6 +13,18 @@ export function fetchGetAfterSalesOrderList(data: any) {
   });
 }
 
+/**
+ * 导出售后快递订单
+ * @param data
+ */
+export function fetchExportAfterSalesOrderList(data: any) {
+  return request<any>({
+    url: '/platform/orderRefund/export',
+    method: 'get',
+    params: data
+  });
+}
+
 /**
  *  获取售后快递订单状态数量
  * @returns

+ 37 - 6
src/utils/common.ts

@@ -1,5 +1,6 @@
+import dayjs from 'dayjs';
+import { request } from '@/service/request';
 import { $t } from '@/locales';
-
 /**
  * Transform record to option
  *
@@ -62,9 +63,39 @@ export function toggleHtmlClass(className: string) {
  * @param url
  * @param params
  */
-export function commonExport(url: string, params: any) {
-  const queryParams = new URLSearchParams(params).toString();
-  const baseUrl = `${import.meta.env.VITE_SERVICE_BASE_URL}${url}`;
-  const fullUrl = queryParams ? `${baseUrl}?${queryParams}` : baseUrl;
-  window.open(fullUrl, '_blank');
+export function commonExport(url: string, params: any, filename: string) {
+  if (!filename) {
+    window.$message?.error('文件未命名');
+    return false;
+  }
+  return new Promise((resolve, reject) => {
+    request({
+      url,
+      method: 'get',
+      params,
+      responseType: 'blob'
+    })
+      .then(res => {
+        const blob = new Blob([res.data as unknown as BlobPart], {
+          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+        });
+        const exportUrl = URL.createObjectURL(blob);
+        const link = document.createElement('a');
+        link.href = exportUrl;
+        const nameWithoutExt = filename.substring(0, filename.lastIndexOf('.'));
+        const ext = filename.substring(filename.lastIndexOf('.'));
+        link.download = `${nameWithoutExt}_${dayjs().valueOf()}${ext}`;
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+        URL.revokeObjectURL(url);
+        window.$message?.success('导出成功');
+        console.log(res, '导出');
+
+        resolve(res);
+      })
+      .catch(err => {
+        reject(err);
+      });
+  });
 }

+ 0 - 2
src/views/_builtin/login/index.vue

@@ -37,8 +37,6 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
 
 <template>
   <div class="relative size-full flex-center overflow-hidden">
-    <!-- <WaveBg :theme-color="bgThemeColor" /> -->
-
     <NCard :bordered="false" class="relative z-4 w-auto rd-12px">
       <div class="flex items-center">
         <div class="w-400px lt-sm:w-300px">

+ 12 - 3
src/views/delivery/after-sales-order/index.vue

@@ -257,7 +257,12 @@ function handleOpenMoadl(row: Api.delivery.OrderRefund) {
   orderMoadl.value?.open(String(row.orderNumber));
 }
 async function getNums() {
-  const { data: keyData } = await fetchGetAfterSalesStatusNum({ ...getFieldsValue() });
+  const form = getFieldsValue();
+  const params = {
+    ...form,
+    channelIdList: channelIdList.value
+  };
+  const { data: keyData } = await fetchGetAfterSalesStatusNum(params);
   if (!keyData) return;
   const orderStatusList = [
     {
@@ -333,9 +338,11 @@ function handleReset() {
   getData();
   getNums();
 }
-function handleExport() {
+async function handleExport() {
   const form = getFieldsValue();
-  commonExport('/platform/orderRefund/export', form);
+  loading.value = true;
+  await commonExport('/platform/orderRefund/export', form, '售后订单列表.xlsx');
+  loading.value = false;
 }
 </script>
 
@@ -373,6 +380,8 @@ function handleExport() {
           type="primary"
           class="ml20px mt30px"
           ghost
+          :disabled="data.length == 0"
+          :loading="loading"
           @click="handleExport"
         >
           <template #icon>

+ 12 - 3
src/views/delivery/normal-order/index.vue

@@ -242,7 +242,12 @@ function handleOpenMoadl(row: Api.delivery.deliveryOrder) {
   orderMoadl.value?.open(String(row.orderNumber));
 }
 async function getNums() {
-  const { data: keyData } = await fetchGetDeliveryStatusNum({ ...getFieldsValue() });
+  const form = getFieldsValue();
+  const params = {
+    ...form,
+    channelIdList: channelIdList.value
+  };
+  const { data: keyData } = await fetchGetDeliveryStatusNum(params);
   if (!keyData) return;
   const orderStatusList = [
     {
@@ -321,9 +326,11 @@ function handleRefsh() {
   getData();
   getNums();
 }
-function handleExport() {
+async function handleExport() {
   const form = getFieldsValue();
-  commonExport('/platform/order/export', form);
+  loading.value = true;
+  await commonExport('/platform/order/export', form, '正常订单列表.xlsx');
+  loading.value = false;
 }
 </script>
 
@@ -361,6 +368,8 @@ function handleExport() {
           type="primary"
           class="ml20px mt30px"
           ghost
+          :loading="loading"
+          :disabled="data.length == 0"
           @click="handleExport"
         >
           <template #icon>

+ 14 - 9
src/views/finance/commodity-freight/index.vue

@@ -62,7 +62,7 @@ const columns: NaiveUI.TableColumn<Api.finance.FreightStatisticsVo>[] = [
   }
 ];
 
-const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
+const [registerTable, { refresh, setTableLoading, setFieldsValue, getSeachForm, getTableData }] = useTable({
   searchFormConfig: {
     schemas: [
       {
@@ -80,7 +80,7 @@ const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
             });
           },
           getOptions: async (options: any) => {
-            await setSearchForm({ channelIdList: [options[0].id] });
+            await setFieldsValue({ channelIds: [options[0].id] });
             handleSearch();
           }
         }
@@ -107,11 +107,11 @@ const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
             },
             {
               label: '邮政',
-              value: '邮政'
+              value: '1'
             },
             {
               label: '麦芽田-闪送',
-              value: '麦芽田-闪送'
+              value: '3'
             }
           ]
         }
@@ -153,15 +153,18 @@ const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
     keyField: 'id',
     title: '商品运费明细表',
     showAddButton: false,
-    defaultParamsNotReset: 'channelIdList',
+    defaultParamsNotReset: 'channelIds',
     fieldMapToTime: [['Time', ['startTime', 'endTime']]]
   }
 });
 function handleSearch() {
   refresh();
 }
-async function setSearchForm(data: Recordable) {
-  setFieldsValue(data);
+
+async function handleExport() {
+  setTableLoading(true);
+  await commonExport('/platform/sku/freightStatisticsExcel', getSeachForm(), '运费明细列表.xlsx');
+  setTableLoading(false);
 }
 </script>
 
@@ -174,11 +177,13 @@ async function setSearchForm(data: Recordable) {
       :show-table-action="false"
       @register="registerTable"
     >
-      <template #prefix>
+      <template #prefix="{ loading }">
         <NButton
           v-if="useAuth().hasAuth('finance:commodity-freight:export')"
           size="small"
-          @click="commonExport('/platform/sku/freightStatisticsExcel', getSeachForm())"
+          :loading="loading"
+          :disabled="getTableData().length == 0"
+          @click="handleExport"
         >
           导出
         </NButton>

+ 12 - 7
src/views/finance/summary/index.vue

@@ -44,7 +44,7 @@ const columns: NaiveUI.TableColumn<Api.finance.SkuStatisticsVo>[] = [
   }
 ];
 
-const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
+const [registerTable, { refresh, setTableLoading, setFieldsValue, getSeachForm, getTableData }] = useTable({
   searchFormConfig: {
     schemas: [
       {
@@ -62,7 +62,7 @@ const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
             });
           },
           getOptions: async (options: any) => {
-            await setSearchForm({ channelIdList: [options[0].id] });
+            await setFieldsValue({ channelIds: [options[0].id] });
             handleSearch();
           }
         }
@@ -104,15 +104,18 @@ const [registerTable, { refresh, setFieldsValue, getSeachForm }] = useTable({
     keyField: 'id',
     title: '对账单汇总表(商品)',
     showAddButton: false,
-    defaultParamsNotReset: 'channelIdList',
+    defaultParamsNotReset: 'channelIds',
     fieldMapToTime: [['Time', ['startTime', 'endTime']]]
   }
 });
 function handleSearch() {
   refresh();
 }
-async function setSearchForm(data: Recordable) {
-  setFieldsValue(data);
+
+async function handleExport() {
+  setTableLoading(true);
+  await commonExport('/platform/sku/skuStatisticsExcel', getSeachForm(), '对账单列表.xlsx');
+  setTableLoading(false);
 }
 </script>
 
@@ -125,11 +128,13 @@ async function setSearchForm(data: Recordable) {
       :show-table-action="false"
       @register="registerTable"
     >
-      <template #prefix>
+      <template #prefix="{ loading }">
         <NButton
           v-if="useAuth().hasAuth('finance:summary:export')"
           size="small"
-          @click="commonExport('/platform/sku/skuStatisticsExcel', getSeachForm())"
+          :loading="loading"
+          :disabled="getTableData().length == 0"
+          @click="handleExport"
         >
           导出
         </NButton>

+ 8 - 6
src/views/goods/store-goods/index.vue

@@ -199,7 +199,7 @@ const PriceColumns: NaiveUI.TableColumn<Price>[] = [
 ];
 const PriceData = ref<Price[]>([]);
 const selectData = ref<Api.goods.ShopSku>();
-const [registerTable, { getTableCheckedRowKeys, refresh, getTableData, getSeachForm }] = useTable({
+const [registerTable, { getTableCheckedRowKeys, refresh, getTableData, getSeachForm, setTableLoading }] = useTable({
   searchFormConfig: {
     schemas: [
       {
@@ -334,9 +334,9 @@ async function getData() {
 }
 getData();
 async function handleExport() {
-  const form = getSeachForm();
-  // 将form对象转换为查询参数字符串
-  commonExport('/shop/shopProd/export', form);
+  setTableLoading(true);
+  await commonExport('/shop/shopProd/export', getSeachForm(), '商品列表.xlsx');
+  setTableLoading(false);
 }
 </script>
 
@@ -346,10 +346,12 @@ async function handleExport() {
       <template #op="{ row }">
         <NButton size="small" ghost type="primary" @click="handleModalPrice(row)">设置渠道及价格</NButton>
       </template>
-      <template #prefix>
+      <template #prefix="{ loading }">
         <NSpace>
           <NButton size="small" @click="openImportModal">导入商品销售渠道及价格</NButton>
-          <NButton size="small" :disabled="tableData.length == 0" @click="handleExport">导出全部</NButton>
+          <NButton size="small" :disabled="tableData.length == 0" :loading="loading" @click="handleExport">
+            导出全部
+          </NButton>
           <NButton size="small" :disabled="isDisabledExport">导出选中数据</NButton>
           <NButton size="small">修改记录</NButton>
         </NSpace>

+ 23 - 2
src/views/government/government-list/index.vue

@@ -1,6 +1,8 @@
 <script setup lang="tsx">
+import { nextTick } from 'vue';
 import { NSwitch } from 'naive-ui';
 import { fetchAddChannel, fetchGetChannelList, fetchUpdateChannel } from '@/service/api/government/government-list';
+import { fetchGetLoginUserList } from '@/service/api/common';
 import { useTable } from '@/components/zt/Table/hooks/useTable';
 import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
 
@@ -50,13 +52,28 @@ const columns: NaiveUI.TableColumn<Api.government.ChannelVO>[] = [
   }
 ];
 
-const [registerTable, { refresh }] = useTable({
+const [registerTable, { refresh, setFieldsValue: setSearchValues }] = useTable({
   searchFormConfig: {
     schemas: [
       {
         field: 'channelName',
         label: '企业名称',
-        component: 'NInput'
+        component: 'ApiSelect',
+        componentProps: {
+          api: fetchGetLoginUserList,
+          labelFeild: 'channelName',
+          valueFeild: 'id',
+          multiple: true,
+          onUpdateValue: () => {
+            nextTick(() => {
+              handleSearch();
+            });
+          },
+          getOptions: async (options: any) => {
+            await setSearchValues({ channelIdList: [options[0].id] });
+            handleSearch();
+          }
+        }
       },
       {
         field: 'status',
@@ -138,6 +155,10 @@ async function handleSubmit() {
   refresh();
 }
 
+function handleSearch() {
+  refresh();
+}
+
 async function edit(row: Recordable) {
   openModal(row);
   setFieldsValue(row);

+ 55 - 22
src/views/government/points/index.vue

@@ -14,6 +14,8 @@ import { useModal } from '@/components/zt/Modal/hooks/useModal';
 import type { FormSchema } from '@/components/zt/Form/types/form';
 import SVGIcon from '@/components/custom/svg-icon.vue';
 const importTemplateRef = useTemplateRef('importTemplateRef');
+const loading = ref(false);
+
 const ModalColumns: NaiveUI.TableColumn<Api.government.PointsRecharge>[] = [
   {
     key: 'channelName',
@@ -163,7 +165,7 @@ const failColumns: NaiveUI.TableColumn<Api.government.PointsFailureRecordVO>[] =
   }
 ];
 const failData = ref<Api.government.PointsFailureRecordVO[]>([]);
-const [registerTable, { refresh }] = useTable({
+const [registerTable, { refresh, setFieldsValue: setSearchValues }] = useTable({
   searchFormConfig: {
     schemas: [
       {
@@ -174,7 +176,16 @@ const [registerTable, { refresh }] = useTable({
           api: fetchGetLoginUserList,
           labelFeild: 'channelName',
           valueFeild: 'id',
-          multiple: true
+          multiple: true,
+          onUpdateValue: () => {
+            nextTick(() => {
+              listHandleSearch();
+            });
+          },
+          getOptions: async (options: any) => {
+            await setSearchValues({ channelIdList: [options[0].id] });
+            listHandleSearch();
+          }
         }
       }
     ],
@@ -186,25 +197,27 @@ const [registerTable, { refresh }] = useTable({
   tableConfig: {
     keyField: 'id',
     title: '充值积分',
-    showAddButton: false
-  }
-});
-const [registerModalTable, { refresh: refreshModal, setFieldsValue, getSeachForm }] = useTable({
-  searchFormConfig: {
-    schemas: searchSchemas,
-    inline: false,
-    size: 'small',
-    labelPlacement: 'left',
-    isFull: false
-  },
-  tableConfig: {
-    keyField: 'id',
-    title: '积分列表',
     showAddButton: false,
-    minHeight: 400,
     defaultParamsNotReset: 'channelIdList'
   }
 });
+const [registerModalTable, { refresh: refreshModal, setFieldsValue, getSeachForm, setTableLoading, getTableData }] =
+  useTable({
+    searchFormConfig: {
+      schemas: searchSchemas,
+      inline: false,
+      size: 'small',
+      labelPlacement: 'left',
+      isFull: false
+    },
+    tableConfig: {
+      keyField: 'id',
+      title: '积分列表',
+      showAddButton: false,
+      minHeight: 400,
+      defaultParamsNotReset: 'channelIdList'
+    }
+  });
 
 const [registerModal, { openModal }] = useModal({
   title: '充值积分',
@@ -234,12 +247,30 @@ async function openImportModal() {
   const { data } = await fetchGetFailPointsList();
   failData.value = data as Api.government.PointsFailureRecordVO[];
 }
-function hanleExportFailure(code: string) {
-  commonExport('/platform/pointsFailureRecord/export', { code });
+async function hanleExportFailure(code: string) {
+  if (loading.value) {
+    window.$message?.error('正在导出,请勿重复点击');
+    return;
+  }
+  loading.value = true;
+  await commonExport('/platform/pointsFailureRecord/export', { code }, '失败的记录.xlsx');
+  loading.value = false;
+}
+
+async function exportIntegral() {
+  setTableLoading(true);
+  await commonExport('/platform/pointsRecharge/export', getSeachForm(), '积分列表.xlsx');
+  setTableLoading(false);
 }
+
 function handleSearch() {
   refreshModal();
 }
+
+function listHandleSearch() {
+  refresh();
+}
+
 async function setSearchForm(value: Recordable) {
   await setFieldsValue(value);
 }
@@ -247,7 +278,7 @@ async function setSearchForm(value: Recordable) {
 
 <template>
   <LayoutTable>
-    <ZTable :show-table-action="false" :columns="outColumns" :api="fetchGetPointsOutList" @register="registerTable">
+    <ZTable :columns="outColumns" :immediate="false" :api="fetchGetPointsOutList" @register="registerTable">
       <template #prefix>
         <NButton size="small" @click="openModal">充值积分</NButton>
       </template>
@@ -261,11 +292,13 @@ async function setSearchForm(value: Recordable) {
           :api="fetchGetPointsList"
           @register="registerModalTable"
         >
-          <template #prefix>
+          <template #prefix="{ loading: tabloading }">
             <NButton
               v-if="useAuth().hasAuth('points:user:export')"
               size="small"
-              @click="commonExport('/platform/pointsRecharge/export', getSeachForm())"
+              :loading="tabloading"
+              :disabled="getTableData().length == 0"
+              @click="exportIntegral"
             >
               导出
             </NButton>

+ 15 - 9
src/views/government/user-list/index.vue

@@ -181,11 +181,8 @@ const failColumns: NaiveUI.TableColumn<Api.government.importRecordList>[] = [
     }
   }
 ];
-function hanleExportFailure(batchNo: string) {
-  window.open(
-    `${import.meta.env.VITE_SERVICE_BASE_URL}/admin/enterprise/downloadErrorExcel?batchNo=${batchNo}`,
-    '_blank'
-  );
+async function hanleExportFailure(batchNo: string) {
+  await commonExport(`/admin/enterprise/downloadErrorExcel`, { batchNo }, '员工失败列表.xlsx');
 }
 async function handleDelete(row: Api.government.userList) {
   setTableLoading(true);
@@ -295,9 +292,11 @@ async function handleSubmitImport(file: File) {
 function handleSearch() {
   refresh();
 }
-function handleExport() {
+async function handleExport() {
   const form = getSeachForm();
-  commonExport('/admin/enterprise/export', form);
+  setTableLoading(true);
+  await commonExport('/admin/enterprise/export', form, '员工列表.xlsx');
+  setTableLoading(false);
 }
 </script>
 
@@ -322,9 +321,16 @@ function handleExport() {
           确定删除吗?
         </NPopconfirm>
       </template>
-      <template #prefix>
+      <template #prefix="{ loading }">
         <NSpace>
-          <NButton v-if="useAuth().hasAuth('user:list:export-btn')" size="small" @click="handleExport">导出</NButton>
+          <NButton
+            v-if="useAuth().hasAuth('user:list:export-btn')"
+            size="small"
+            :loading="loading"
+            @click="handleExport"
+          >
+            导出
+          </NButton>
           <NButton v-if="useAuth().hasAuth('user:list:export')" size="small" @click="openImportModal">导入员工</NButton>
           <NButton v-if="useAuth().hasAuth('user:list:exportRecord')" size="small" @click="openModalFail">
             导入记录