Преглед изворни кода

```
feat(table): 为表格组件新增 setFieldsValue 方法并支持默认参数配置

- 在 `useTable` hook 中添加 `setFieldsValue` 方法,用于设置搜索表单字段值
- 为 `z-table` 组件增加 `defaultParams` 属性,支持传入默认请求参数
- 更新类型定义文件,补充相关接口和方法声明

feat(api): 新增售后订单与门店分类商品管理接口

- 添加查看退款订单详情接口 `fetchGetAfterSalesOrderDetail`
- 新增门店前台类目导入记录查询接口 `fetchGetFailLogList`
- 实现分类商品绑定/取消绑定相关接口:
- `fetchGetCategoryProdList`:分页查询已关联商品
- `fetchGetCategoryProdHb`:分页查询未关联商品
- `fetchEditCategoryProd`:绑定分类商品
- `fetchCancelCategoryProd`:取消分类绑定商品

feat(after-sales): 完善售后订单列表及详情功能

- 增加售后订单搜索表单配置
- 实现售后订单列表展示及状态渲染逻辑
- 开发售后详情弹窗组件,包含订单信息、商品明细和状态提示
- 补充复制退款编号和订单编号的功能

feat(delivery): 优化普通订单发货流程及相关展示

- 调整订单详情中发货按钮的显示条件和文案逻辑
- 修复订单信息展示问题,如买家留言等字段获取错误
- 优化发货弹窗中的商品规格和售后标签显示逻辑
- 改进支付方式列的展示内容,区分微信支付和其他情况

feat(desk-category): 实现门店分类关联商品功能

- 开发关联商品弹窗组件,支持从接口加载商品数据
- 提供商品选择与保存功能,并触发父级刷新回调
```

zhangtao пре 3 дана
родитељ
комит
744e9e6304

+ 1 - 0
.env.test

@@ -2,6 +2,7 @@
 # VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun
 # VITE_SERVICE_BASE_URL=http://192.168.1.206: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=https://mock.apifox.cn/m1/3109515-0-default
 VITE_SERVICE_BASE_URL=https://shop.platform.zswlgz.com #服务器
 

+ 4 - 0
src/components/zt/Table/hooks/useTable.ts

@@ -47,6 +47,9 @@ export function useTable(tableProps: ztTableProps): UseTableReturnType {
     },
     getSeachForm() {
       return tableRef.value?.getSeachForm() as Recordable;
+    },
+    setFieldsValue(values: Recordable) {
+      return tableRef.value?.setFieldsValue(values);
     }
   };
   return [register, methods];
@@ -60,6 +63,7 @@ export interface TableMethods {
   getTableCheckedRowKeys: () => string[];
   getTableData: () => any[];
   getSeachForm: () => Recordable;
+  setFieldsValue: (values: Recordable) => void;
 }
 
 export type RegisterFn = (TableInstance: TableMethods) => void;

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

@@ -13,6 +13,10 @@ export const basicProps = {
     type: Array as PropType<tableProp['columns']>,
     default: () => []
   },
+  defaultParams: {
+    type: Object as PropType<tableProp['defaultParams']>,
+    default: () => ({})
+  },
   searchFormConfig: {
     type: Object as PropType<FormProps>,
     default: {

+ 4 - 0
src/components/zt/Table/types/index.ts

@@ -45,6 +45,10 @@ export interface tableProp extends DataTableProps {
    * 是否显示搜索组件
    */
   showSearch?: boolean;
+  /**
+   * 默认参数
+   */
+  defaultParams?: Recordable;
 }
 
 export interface ztTableProps {

+ 5 - 5
src/components/zt/Table/z-table.vue

@@ -41,16 +41,15 @@ export default defineComponent({
         ...unref(tableProps)
       };
     });
-    const [registerSearchForm, { getFieldsValue: getSeachForm, setFormProps: setSearchProps }] = useForm(
-      getFormSearch.value
-    );
+    const [registerSearchForm, { getFieldsValue: getSeachForm, setFormProps: setSearchProps, setFieldsValue }] =
+      useForm(getFormSearch.value);
     const searchPage = reactive({
       current: 1,
       size: 10
     });
     const getForm = ref();
     const { columns, columnChecks, data, getData, loading, mobilePagination, getDataByPage } = useNaivePaginatedTable({
-      api: () => propsData.api({ ...searchPage, ...getForm.value }),
+      api: () => propsData.api({ ...searchPage, ...getForm.value, ...propsData.defaultParams }),
       transform: response => defaultTransform(response),
       onPaginationParamsChange: params => {
         searchPage.current = Number(params.page);
@@ -92,7 +91,8 @@ export default defineComponent({
       setTableConfig,
       getTableCheckedRowKeys,
       getTableData,
-      getSeachForm
+      getSeachForm,
+      setFieldsValue
     };
     function setTableLoading(flage: boolean) {
       loading.value = flage;

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

@@ -59,3 +59,15 @@ export function fetchDeliveryOrder(data: any) {
     data
   });
 }
+
+/**
+ * 查看退款订单详情
+ * @param refundId
+ * @returns
+ */
+export function fetchGetAfterSalesOrderDetail(refundId: number) {
+  return request<Api.delivery.OrderRefund>({
+    url: `/platform/orderRefund/orderRefundInfo/${refundId}`,
+    method: 'get'
+  });
+}

+ 62 - 0
src/service/api/goods/desk-category/index.ts

@@ -44,3 +44,65 @@ export function fetchUpdateCategory(data: any) {
     data
   });
 }
+
+/**
+ * 查询门店前台类目导入记录
+ * @returns
+ */
+export function fetchGetFailLogList() {
+  return request<Api.goods.ShopCategoryLogVO[]>({
+    url: '/platform/shopCategoryLog/list',
+    method: 'get'
+  });
+}
+
+/**
+ *
+ * 查询已关联商品传三级分类code
+ *
+ * 分页通过code查询分类绑定商品
+ * @param data
+ * @returns
+ */
+export function fetchGetCategoryProdList(data: any) {
+  return request<Api.goods.CategoryProd[]>({
+    url: '/platform/categoryProd/pageByCode',
+    method: 'get',
+    params: data
+  });
+}
+
+/**
+ *
+ * @param data 查询未关联商品传二级分类code
+ * @returns
+ */
+export function fetchGetCategoryProdHb(data: any) {
+  return request<Api.goods.CategoryProd[]>({
+    url: '/platform/categoryProdHb/pageByCode',
+    method: 'get',
+    params: data
+  });
+}
+
+/***
+ * 绑定门店分类商品
+ */
+export function fetchEditCategoryProd(data: any) {
+  return request({
+    url: '/platform/categoryProd/categoryProduct',
+    method: 'POST',
+    data
+  });
+}
+
+/**
+ * 取消分类绑定商品
+ */
+export function fetchCancelCategoryProd(data: any) {
+  return request({
+    url: '/platform/categoryProd/cancelCategoryProduct',
+    method: 'POST',
+    data
+  });
+}

+ 77 - 4
src/typings/api.d.ts

@@ -100,6 +100,75 @@ declare namespace Api {
    */
 
   namespace goods {
+    interface CategoryProd {
+      /**
+       * 分类id
+       */
+      categoryId?: number;
+      code?: string;
+      /**
+       * 创建时间
+       */
+      createTime?: string;
+      id?: number;
+      /**
+       * 0 正常,1 删除
+       */
+      isDelete?: number;
+      /**
+       * 商品属性id即表tz_prod_prop中的prop_id
+       */
+      prodId?: number;
+      /**
+       * 商品名称
+       */
+      prodName?: string;
+      /**
+       * 门店
+       */
+      shopId?: number;
+      /**
+       * 商品上架状态(0:商家下架、1:上架、2:违规下架、3:平台审核)
+       */
+      status: number;
+      [property: string]: any;
+    }
+    interface ShopCategoryLogVO {
+      /**
+       * 操作人
+       */
+      createBy?: string;
+      /**
+       * 操作人名称
+       */
+      createByName?: string;
+      /**
+       * 操作人角色
+       */
+      createByRole?: string;
+      /**
+       * 创建时间
+       */
+      createTime?: string;
+      /**
+       * 失败状态数量
+       */
+      failureStatus?: number;
+      /**
+       * 成功状态数量
+       */
+      successStatus?: number;
+      /**
+       * 任务编号
+       */
+      taskCode: string;
+      /**
+       * 任务名称
+       */
+      taskName?: string;
+      [property: string]: any;
+    }
+
     interface ShopCategory {
       children?: ShopCategory[];
       /**
@@ -1017,7 +1086,7 @@ declare namespace Api {
        */
       payTime?: string;
       /**
-       * 支付方式 1 微信支付 2 支付宝
+       * 支付方式 1 微信支付 2 支付宝,0积分支付
        */
       payType?: number;
       /**
@@ -1226,6 +1295,10 @@ declare namespace Api {
        * 售后处理中
        */
       refundIngCount?: number;
+      /**
+       * 退款成功
+       */
+      refundSuccessCount?: number;
       [property: string]: any;
     }
     interface UserAddrOrder {
@@ -1327,7 +1400,7 @@ declare namespace Api {
       /**
        * 申请类型:1,仅退款,2退款退货,5差价退款
        */
-      applyType?: number;
+      applyType: number;
       /**
        * 申请说明
        */
@@ -1410,7 +1483,7 @@ declare namespace Api {
       /**
        * 记录ID
        */
-      refundId?: number;
+      refundId: number;
       /**
        * 退还积分
        */
@@ -1438,7 +1511,7 @@ declare namespace Api {
       /**
        * 退款单状态 10:待审核 20:处理中 30:驳回退款 40:撤销退款 60:待退货(一审同意) 65:待确认收货(二审待审核) 70:退款完成
        */
-      returnMoneySts?: number;
+      returnMoneySts: number;
       /**
        * 卖家备注
        */

+ 0 - 1
src/typings/components.d.ts

@@ -83,7 +83,6 @@ declare module 'vue' {
     NFormItem: typeof import('naive-ui')['NFormItem']
     NGi: typeof import('naive-ui')['NGi']
     NGrid: typeof import('naive-ui')['NGrid']
-    NGridItem: typeof import('naive-ui')['NGridItem']
     NIcon: typeof import('naive-ui')['NIcon']
     NImage: typeof import('naive-ui')['NImage']
     NInput: typeof import('naive-ui')['NInput']

+ 141 - 0
src/views/delivery/after-sales-order/after-sales-order.ts

@@ -1,3 +1,93 @@
+import { h } from 'vue';
+import { NImage } from 'naive-ui';
+import { fetchGetAllStoreList } from '@/service/api/goods/desk-category';
+import type { FormSchema } from '@/components/zt/Form/types/form';
+export const SearchForm: FormSchema[] = [
+  {
+    label: '门店名称',
+    component: 'ApiSelect',
+    field: 'shopId',
+    componentProps: {
+      api: fetchGetAllStoreList,
+      labelFeild: 'shopName',
+      valueFeild: 'shopId'
+    }
+  },
+  {
+    label: '退款编号',
+    component: 'NInput',
+    field: 'refundSn'
+  },
+  {
+    label: '订单编号',
+    component: 'NInput',
+    field: 'orderNumber'
+  },
+  {
+    label: '收货人姓名',
+    component: 'NInput',
+    field: 'receiver'
+  },
+  {
+    label: '收货人手机号',
+    component: 'NInput',
+    field: 'userMobile'
+  },
+  {
+    label: '申请方式',
+    component: 'NSelect',
+    field: 'applyType',
+    componentProps: {
+      options: [
+        {
+          label: '仅退款',
+          value: 1
+        },
+        {
+          label: '退货退款',
+          value: 2
+        },
+        {
+          label: '差价退款',
+          value: 5
+        }
+      ]
+    }
+  },
+  {
+    label: '售后状态',
+    component: 'NSelect',
+    field: 'refundStatus',
+    componentProps: {
+      options: [
+        {
+          label: '申请退款',
+          value: 1
+        },
+        {
+          label: '退款成功',
+          value: 2
+        },
+        {
+          label: '部分退款成功',
+          value: 3
+        },
+        {
+          label: '退款失败',
+          value: 4
+        }
+      ]
+    }
+  },
+  {
+    label: '下单时间',
+    component: 'NDatePicker',
+    field: 'createTime',
+    componentProps: {
+      type: 'datetimerange'
+    }
+  }
+];
 export enum refundEnum {
   /**
    * 买家申请
@@ -36,3 +126,54 @@ export const refundStatus = {
   [refundEnum.BUYER_DELIVERY]: '买家发货',
   [refundEnum.REVOKE_APPLY]: '撤回申请'
 };
+
+export const TypeText = ['仅退款', '退款退货', '差价退款'];
+
+export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderRefundSku>[] = [
+  {
+    title: '退款商品',
+    key: 'goods',
+    width: 300,
+    render: row => {
+      const goodsNodes = [
+        h(NImage, { src: row.pic, width: 80, height: 80 }),
+        h('div', { class: 'ml-2 ' }, [
+          h('div', { class: 'text-15px font-semibold' }, row.skuName),
+          h('div', { class: 'text-gray' }, `规格:${row.spec || '--'}`)
+        ])
+      ];
+      return h('div', { class: 'flex items-center' }, goodsNodes);
+    }
+  },
+  {
+    title: '购买单价(元)',
+    key: 'skuPrice',
+    width: 150
+  },
+  {
+    title: '购买数量',
+    key: 'productCount',
+    width: 150,
+    render: row => {
+      return row.orderItem.prodCount;
+    }
+  },
+  {
+    title: '小计/元',
+    key: 'productTotalAmount',
+    width: 100,
+    render: row => {
+      return row.orderItem.productTotalAmount;
+    }
+  },
+  {
+    title: '退款数量',
+    key: 'productCount',
+    width: 100
+  },
+  {
+    title: '退款金额',
+    key: 'skuPrice',
+    width: 100
+  }
+];

+ 39 - 13
src/views/delivery/after-sales-order/index.vue

@@ -8,13 +8,15 @@ import { copyTextToClipboard } from '@/utils/zt';
 import { $t } from '@/locales';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
 import NormalMoadl from '../normal-order/component/normal-modal.vue';
-import { SearchForm, orderStatus } from '../normal-order/normal-order';
-import { refundEnum, refundStatus } from './after-sales-order';
+import { orderStatus } from '../normal-order/normal-order';
+import { SearchForm, TypeText, refundEnum, refundStatus } from './after-sales-order';
+import OrderModal from './order-modal.vue';
 const appStore = useAppStore();
 const checkedRowKeys = ref([]);
 const activeTab = ref(0);
 const statusList = ref<{ label: string; value: number; num?: number; key: string }[]>([]);
 const orderMoadl = useTemplateRef('orderMoadl');
+const AfterSalesModal = useTemplateRef('AfterSalesModal');
 
 const [registerSearchForm, { getFieldsValue }] = useForm({
   schemas: SearchForm,
@@ -50,17 +52,20 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
       width: 200,
       colSpan: (_rowData, _rowIndex) => 2,
       render: row => {
+        const statusKey = row.returnMoneySts as keyof typeof refundStatus;
+        const statusText = refundStatus[statusKey] || '暂无售后';
+
         return (
           <div>
             <div class={'mb3 flex items-center'}>
               <n-tag>
                 <div class={'flex items-center'}>
                   退款编号:{row.refundSn}
-                  <div onClick={() => handleCopy(row.refundSn)}>
+                  <div onClick={() => copyTextToClipboard(row.refundSn)}>
                     <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
                   </div>
                   订单编号:{row.orderNumber}
-                  <div onClick={() => handleCopy(row.orderNumber)}>
+                  <div onClick={() => copyTextToClipboard(row.orderNumber)}>
                     <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
                   </div>
                   申请时间:
@@ -83,10 +88,11 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
                         <div class={'text-16px font-semibold'}>¥{item.skuPrice} </div>
                       </div>
                     </div>
-                    <div class={'w-full flex items-center justify-between'}>
+                    <div class={'mb2 w-full flex items-center justify-between'}>
                       <div class={'w200px text-gray'}>规格:{item.spec || '--'} </div>
                       <div class={'w150px pl30px text-left text-gray'}>x{item.productCount} </div>
                     </div>
+                    <NTag type={statusKey == refundEnum.REFUND_SUCCESS ? 'success' : 'error'}>{statusText}</NTag>
                   </div>
                 </div>
               );
@@ -107,10 +113,17 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
       align: 'center',
       width: 120,
       render: row => {
+        // 后端不算,让前端算
+        const actualTotal = row.orderRefundSkuList?.reduce((acc, item) => {
+          return acc + Number(item.skuPrice) * Number(item.productCount);
+        }, 0);
+        const goodsTotalCount = row.orderRefundSkuList?.reduce((acc, item) => {
+          return acc + Number(item.productCount);
+        }, 0);
         return (
           <div class={'mt7'}>
-            <div class={'text-16px text-#ff0000 font-semibold'}>{row.actualTotal} 元</div>
-            <div>共 {row.goodsTotalCount} 件</div>
+            <div class={'text-16px text-#ff0000 font-semibold'}>{actualTotal?.toFixed(2)} 元</div>
+            <div>共 {goodsTotalCount} 件</div>
           </div>
         );
       }
@@ -121,10 +134,17 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
       align: 'center',
       width: 120,
       render: row => {
+        // 后端不算,让前端算
+        const actualTotal = row.orderRefundSkuList?.reduce((acc, item) => {
+          return acc + Number(item.skuPrice) * Number(item.productCount);
+        }, 0);
+        const goodsTotalCount = row.orderRefundSkuList?.reduce((acc, item) => {
+          return acc + Number(item.productCount);
+        }, 0);
         return (
           <div class={'mt7'}>
-            <div class={'text-16px text-#ff0000 font-semibold'}>{row.actualTotal} 元</div>
-            <div>共 {row.goodsTotalCount} 件</div>
+            <div class={'text-16px text-#ff0000 font-semibold'}>{actualTotal?.toFixed(2)} 元</div>
+            <div>共 {goodsTotalCount} 件</div>
           </div>
         );
       }
@@ -135,7 +155,6 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
       width: 220,
       align: 'center',
       render: row => {
-        const TypeText = ['仅退款', '退款退货', '差价退款'];
         return (
           <div class={'mt7'}>
             <div class={'text-16px font-semibold'}>{TypeText[Number(row.applyType) - 1] || '未知状况'} </div>
@@ -178,6 +197,14 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
             <n-button size="small" type="primary" quaternary onClick={() => handleOpenMoadl(row)}>
               查看订单
             </n-button>
+            <n-button
+              size="small"
+              type="primary"
+              quaternary
+              onClick={() => AfterSalesModal.value?.handleOpenOrder(row.refundId)}
+            >
+              售后详情
+            </n-button>
           </div>
         );
       }
@@ -264,9 +291,7 @@ function handleSearch() {
 function handleReset() {
   searchForm.value = getFieldsValue();
   getData();
-}
-async function handleCopy(No: string) {
-  await copyTextToClipboard(No);
+  getNums();
 }
 </script>
 
@@ -305,6 +330,7 @@ async function handleCopy(No: string) {
       </NTabs>
 
       <NormalMoadl ref="orderMoadl" @finish="(getData, getNums)"></NormalMoadl>
+      <OrderModal ref="AfterSalesModal"></OrderModal>
     </NCard>
   </div>
 </template>

+ 113 - 2
src/views/delivery/after-sales-order/order-modal.vue

@@ -1,7 +1,118 @@
-<script setup lang="ts"></script>
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import { fetchGetAfterSalesOrderDetail } from '@/service/api/delivery/after-sales-order';
+import { useAppStore } from '@/store/modules/app';
+import { copyTextToClipboard } from '@/utils/zt';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import { TypeText, orderColumns, refundEnum, refundStatus } from './after-sales-order';
+const [registerModal, { openModal, setModalLoading }] = useModal({
+  title: '处理退款',
+  width: 1200,
+  height: 800,
+  isShowHeaderText: false,
+  showFooter: false
+});
+const appStore = useAppStore();
+
+const orderInfo = ref<Api.delivery.OrderRefund>();
+const isRefundIng = ref(false);
+const goodsMoney = computed(() => {
+  // 后端不算,让前端算
+  return orderInfo.value?.orderRefundSkuList?.reduce((acc, item) => {
+    return acc + Number(item.orderItem.productTotalAmount) * Number(item.orderItem.prodCount);
+  }, 0);
+});
+const refundTMoney = computed(() => {
+  // 后端不算,让前端算
+  return orderInfo.value?.orderRefundSkuList?.reduce((acc, item) => {
+    return acc + Number(item.skuPrice) * Number(item.productCount);
+  }, 0);
+});
+defineExpose({
+  handleOpenOrder
+});
+
+async function handleOpenOrder(refundId: number) {
+  openModal();
+  setModalLoading(true);
+  const { data, error } = await fetchGetAfterSalesOrderDetail(refundId);
+  if (!error) {
+    orderInfo.value = data;
+  }
+  setModalLoading(false);
+}
+</script>
 
 <template>
-  <div></div>
+  <BasicModal @register="registerModal" @after-leave="isRefundIng = false">
+    <div v-if="orderInfo">
+      <NFlex justify="space-between">
+        <NFlex vertical>
+          <NTag>
+            <div class="flex items-center">
+              退款编号: {{ orderInfo?.refundSn }}
+              <div @click="copyTextToClipboard(orderInfo.refundSn)">
+                <SvgIcon icon="bxs:copy" class="mx-3 cursor-pointer text-[#f97316]"></SvgIcon>
+              </div>
+            </div>
+          </NTag>
+          <NTag>申请人: | 申请时间:{{ orderInfo.applyTime }}</NTag>
+        </NFlex>
+        <NFlex vertical>
+          <div class="text-16px font-semibold">
+            {{ refundStatus[orderInfo.returnMoneySts as keyof typeof refundStatus] }}
+          </div>
+          <div class="text-gray">
+            <template v-if="orderInfo.returnMoneySts == refundEnum.BUYER_APPLY">
+              <div class="text-12px">逾期未处理,将自动退款给买家。</div>
+              <div class="mt3 text-15px">
+                <div>温馨提示</div>
+                <div>如果你同意,将直接退款给买家。</div>
+                <div>如果你拒绝,最好和买家先协商一致。</div>
+                <div>如果你逾期未处理,视作同意买家申请,系统将自动退款给买家。</div>
+              </div>
+            </template>
+            <template v-if="orderInfo.returnMoneySts == refundEnum.REVOKE_APPLY">
+              <div>用户主动撤回申请,退款关闭。</div>
+            </template>
+            <template v-if="orderInfo.returnMoneySts == refundEnum.SELLER_REFUSE">
+              <div>卖家拒绝了用户的申请</div>
+            </template>
+            <template v-if="orderInfo.returnMoneySts == refundEnum.REFUND_SUCCESS">
+              <div>已完成退款,具体到账时间请用户查询支付账户</div>
+            </template>
+          </div>
+        </NFlex>
+      </NFlex>
+      <NDivider />
+      <NDescriptions bordered :column="appStore.isMobile ? 1 : 4">
+        <NDescriptionsItem label="售后信息">
+          <div>售后方式:{{ TypeText[orderInfo.applyType - 1] || '未知状况' }}</div>
+          <div>退款原因:{{ orderInfo.buyerReason || '---' }}</div>
+          <div>退款件数: {{ orderInfo.goodsNum + '件' || '---' }}</div>
+        </NDescriptionsItem>
+        <NDescriptionsItem label="购买信息">
+          <div>商品总额: {{ goodsMoney + '元' || '---' }}</div>
+          <div>配送费(快递): {{ orderInfo.freightAmount + '元' || '---' }}</div>
+          <div>积分抵扣: {{ orderInfo.offsetPoints + '元' || '---' }}</div>
+          <div>实际付款: {{ goodsMoney + '元' || '---' }}</div>
+        </NDescriptionsItem>
+      </NDescriptions>
+      <NDivider />
+      <NCard title="退款商品" :bordered="false">
+        <NDataTable :columns="orderColumns" :data="orderInfo.orderRefundSkuList" :bordered="false" />
+      </NCard>
+      <div class="flex items-center justify-end">
+        <div class="flex items-center">
+          <div class="text-16px font-semibold">退款总金额: {{ refundTMoney?.toFixed(2) }}元</div>
+          <div v-if="orderInfo.returnMoneySts == refundEnum.REFUND_SUCCESS" class="ml2 flex items-center text-gray">
+            <div>退还金额:---</div>
+            <div class="ml2">退还积分:{{ orderInfo.offsetPoints }} (已过期{{ orderInfo.refundExpiredScore }})</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </BasicModal>
 </template>
 
 <style scoped></style>

+ 26 - 15
src/views/delivery/normal-order/component/delivery-modal.vue

@@ -35,10 +35,10 @@ const ShipmentColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
     render: row => {
       return (
         <div class={'flex items-center'}>
-          <n-image src={row.pic} width={80} height={80}></n-image>
+          <n-image src={row.pic} width={80} height={80} class={'min-w-80px'}></n-image>
           <div class={'ml2'}>
             <div class={'text-15px font-semibold'}> {row.skuName} </div>
-            <div class={'text-gray'}>规格: {'---'}</div>
+            <div class={'text-gray'}>规格: {row.spec || '---'}</div>
           </div>
         </div>
       );
@@ -47,22 +47,15 @@ const ShipmentColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
   {
     title: '可发货数量',
     key: 'num',
-    width: 100,
+    width: 250,
     render: row => {
       const count = Number(row.prodCount) - Number(row.refundSuccessCount);
-      if (row.refundIngCount) {
+      if (Number(row.refundIngCount)) {
         isRefundIng.value = true;
       }
       return (
         <div class={'flex items-center justify-center'}>
-          {count} {row.refundSuccessCount ? <n-tag type="success">已扣除退款成功:{row.refundSuccessCount}</n-tag> : ''}
-          {row.refundIngCount ? (
-            <n-tag type="error" class={'ml2'}>
-              售后处理:{row.refundIngCount}
-            </n-tag>
-          ) : (
-            ''
-          )}
+          {count} {handleCreateTag(row)}
         </div>
       );
     }
@@ -80,7 +73,7 @@ const ShipmentColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
     key: 'count',
     align: 'center',
     render: row => {
-      const count = Number(row.prodCount) - row.refundSuccessCount;
+      const count = Number(row.prodCount) - Number(row.refundSuccessCount);
       return count;
     }
   }
@@ -90,6 +83,23 @@ defineExpose({
   setModalProps,
   handleOpenOrder
 });
+function handleCreateTag(row: Api.delivery.OrderItemElement) {
+  if (row.refundSuccessCount) {
+    return (
+      <n-tag type="success" class={'ml2'}>
+        已扣除退款成功:{row.refundSuccessCount}
+      </n-tag>
+    );
+  }
+  if (row.refundIngCount) {
+    return (
+      <n-tag type="error" class={'ml2'}>
+        售后处理:{row.refundIngCount}
+      </n-tag>
+    );
+  }
+  return '';
+}
 async function handleOpenOrder(orderNumber: string) {
   openModal();
   setModalLoading(true);
@@ -98,7 +108,8 @@ async function handleOpenOrder(orderNumber: string) {
   if (!error) {
     orderInfo.value = data;
     const Info = {
-      ...data.userAddrOrder
+      ...data.userAddrOrder,
+      ...data
     };
     nextTick(() => {
       setFieldsValue(Info);
@@ -133,7 +144,7 @@ async function handleSubmit() {
   <BasicModal @register="registerModal" @ok="handleSubmit" @after-leave="isRefundIng = false">
     <div v-if="orderInfo">
       <NCard :bordered="false" title="商品信息">
-        <NDataTable :scroll-x="800" :columns="ShipmentColumns" :data="orderInfo.orderItems" :bordered="false" />
+        <NDataTable :scroll-x="1000" :columns="ShipmentColumns" :data="orderInfo.orderItems" :bordered="false" />
       </NCard>
       <NCard :bordered="false" title="订单信息">
         <BasicForm @register-form="registerForm"></BasicForm>

+ 19 - 12
src/views/delivery/normal-order/component/normal-modal.vue

@@ -42,16 +42,15 @@ function handleFinish() {
   open(String(orderInfo.value?.orderNumber));
   emit('finish');
 }
+const isRefund = computed(() => {
+  const goodsData = orderInfo.value?.orderItems?.find(it => it.refundIngCount == 1);
+  return Boolean(goodsData);
+});
 function handleShipment() {
-  if (orderInfo.value?.refundStatus == 1) {
+  if (isRefund.value) {
     window.$message?.error('当前订单正在申请退款中');
     return;
   }
-  if (orderInfo.value?.refundStatus == 2) {
-    window.$message?.error('当前订单已经退款成功');
-    return;
-  }
-
   ShipmentRef.value?.handleOpenOrder(String(orderInfo.value?.orderNumber));
 }
 
@@ -107,9 +106,17 @@ function handleCopy() {
                 内付款,订单将 自动关闭。
               </div>
             </template>
-            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_DELIVERY">
-              <div class="text-16px font-semibold">等待商家发货</div>
-              <NButton size="small" type="primary" @click="handleShipment">发货</NButton>
+            <template v-if="!orderInfo.dvyFlowId">
+              <template
+                v-if="
+                  orderInfo.hbOrderStatus == orderStatusEnum.WAIT_DELIVERY ||
+                  orderInfo.hbOrderStatus == orderStatusEnum.ORDER_ACCEPT ||
+                  orderInfo.hbOrderStatus == orderStatusEnum.ORDER_DELIVERY
+                "
+              >
+                <div class="text-16px font-semibold">等待商家发货</div>
+                <NButton size="small" type="primary" @click="handleShipment">发货</NButton>
+              </template>
             </template>
             <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_ARRIVE">
               <div class="text-16px font-semibold">等待买家收货</div>
@@ -159,9 +166,9 @@ function handleCopy() {
             <div>付款时间:{{ orderInfo.payTime || '暂无' }}</div>
           </NDescriptionsItem>
           <NDescriptionsItem label="买家信息">
-            <div>买家昵称:{{ orderInfo?.userInfo?.nickName || '---' }}</div>
-            <div>买家电话:{{ orderInfo?.userInfo?.mobile || '---' }}</div>
-            <div>买家留言:{{ orderInfo?.userInfo?.message || '暂无' }}</div>
+            <div>买家昵称:{{ orderInfo?.nickName || '---' }}</div>
+            <div>买家电话:{{ orderInfo?.userMobile || '---' }}</div>
+            <div>买家留言:{{ orderInfo?.remarks || '暂无' }}</div>
           </NDescriptionsItem>
         </NDescriptions>
         <NDivider />

+ 20 - 7
src/views/delivery/normal-order/index.vue

@@ -7,7 +7,7 @@ import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
 import { copyTextToClipboard } from '@/utils/zt';
 import { $t } from '@/locales';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
-import { SearchForm, orderStatus, refundStatus } from './normal-order';
+import { SearchForm, orderStatus, orderStatusEnum, refundStatus } from './normal-order';
 import DeliveryModal from './component/delivery-modal.vue';
 import NormalMoadl from './component/normal-modal.vue';
 const appStore = useAppStore();
@@ -81,7 +81,7 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
                       </div>
                     </div>
                     <div class={'w-full flex items-center justify-between'}>
-                      <div class={'w200px text-gray'}>规格: </div>
+                      <div class={'w200px text-gray'}>规格: {item.spec}</div>
                       <div class={'w150px pl30px text-left text-gray'}>x{item.prodCount} </div>
                     </div>
                   </div>
@@ -116,7 +116,14 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
       key: 'payType',
       title: '支付方式',
       align: 'center',
-      width: 120
+      width: 120,
+      render: row => {
+        return (
+          <NTag class={'mt7'} type="success">
+            {row.payType == 0 || row.payType == 1 ? '微信' : '未支付'}
+          </NTag>
+        );
+      }
     },
     {
       key: 'seq',
@@ -166,9 +173,11 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
             <n-button size="small" type="primary" quaternary onClick={() => handleOpenMoadl(row)}>
               查看订单
             </n-button>
-            {row.hbOrderStatus == 1 && (
+            {(row.hbOrderStatus == orderStatusEnum.WAIT_DELIVERY ||
+              row.hbOrderStatus == orderStatusEnum.ORDER_DELIVERY ||
+              row.hbOrderStatus == orderStatusEnum.ORDER_DELIVERY) && (
               <n-button size="small" type="primary" quaternary onClick={() => handleDeivery(row)}>
-                发货
+                {row.dvyFlowId ? '修改物流' : '发货'}
               </n-button>
             )}
           </div>
@@ -262,6 +271,10 @@ async function handleCopy(row: Api.delivery.deliveryOrder) {
   }
   await copyTextToClipboard(row.orderNumber);
 }
+function handleRefsh() {
+  getData();
+  getNums();
+}
 </script>
 
 <template>
@@ -297,8 +310,8 @@ async function handleCopy(row: Api.delivery.deliveryOrder) {
           />
         </NTabPane>
       </NTabs>
-      <NormalMoadl ref="orderMoadl" @finish="(getData, getNums)"></NormalMoadl>
-      <DeliveryModal ref="Shipment" @finish="(getData, getNums)"></DeliveryModal>
+      <NormalMoadl ref="orderMoadl" @finish="handleRefsh"></NormalMoadl>
+      <DeliveryModal ref="Shipment" @finish="handleRefsh"></DeliveryModal>
     </NCard>
   </div>
 </template>

+ 15 - 9
src/views/delivery/normal-order/normal-order.ts

@@ -160,7 +160,7 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
         h(NImage, { src: row.pic, width: 80, height: 80 }),
         h('div', { class: 'ml-2 ' }, [
           h('div', { class: 'text-15px font-semibold' }, row.skuName),
-          h('div', { class: 'text-gray' }, `规格:${'--'}`)
+          h('div', { class: 'text-gray' }, `规格:${row.spec || '--'}`)
         ])
       ];
       return h('div', { class: 'flex items-center' }, goodsNodes);
@@ -174,15 +174,15 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
   {
     title: '数量',
     key: 'prodCount',
-    width: 150,
+    width: 250,
     render: row => {
       const nodes = [h('div', { class: 'mr-2' }, row.prodCount)];
-      if (row.refundIngCount) {
-        nodes.push(h(NTag, { class: 'ml2', type: 'error' }, () => `售后处理:${row.refundIngCount}`));
-      }
       if (row.refundSuccessCount) {
         nodes.push(h(NTag, { class: 'ml2', type: 'success' }, () => `退款成功:${row.refundSuccessCount}`));
       }
+      if (row.refundIngCount && !row.refundSuccessCount) {
+        nodes.push(h(NTag, { class: 'ml2', type: 'error' }, () => `售后处理:${row.refundIngCount}`));
+      }
       return h(NFlex, { align: 'center' }, () => nodes);
     }
   },
@@ -234,11 +234,11 @@ export const deliveryInfo: FormSchema[] = [
     componentProps: {
       options: [
         {
-          label: '快递',
+          label: '自己联系快递',
           value: 1
         },
         {
-          label: '自提',
+          label: '无需发货',
           value: 2
         }
       ]
@@ -259,12 +259,18 @@ export const deliveryInfo: FormSchema[] = [
       labelFeild: 'dvyName',
       valueFeild: 'dvyId'
     },
-    required: true
+    required: true,
+    ifShow({ model }) {
+      return model.dvyType === 1;
+    }
   },
   {
     label: '快递单号',
     component: 'NInput',
     field: 'dvyFlowId',
-    required: true
+    required: true,
+    ifShow({ model }) {
+      return model.dvyType === 1;
+    }
   }
 ];

+ 43 - 12
src/views/goods/desk-category/components/related-goods-modal.vue

@@ -1,27 +1,58 @@
 <script setup lang="tsx">
-import { ref } from 'vue';
+import { ref, unref } from 'vue';
+import { fetchEditCategoryProd, fetchGetCategoryProdHb } from '@/service/api/goods/desk-category';
 import { useModal } from '@/components/zt/Modal/hooks/useModal';
-const options = createOptions();
+const options = ref();
 const value = ref([]);
-const [registerModal, { openModal }] = useModal({
+const rowData = ref<Api.goods.ShopCategory>();
+const [registerModal, { openModal, setModalLoading, setSubLoading, closeModal }] = useModal({
   title: '关联商品',
-  width: 900,
+  width: 1000,
   isShowHeaderText: false,
   height: 800
 });
-function createOptions() {
-  return Array.from({ length: 100 }).map((_v, i) => ({
-    label: `商品 ${i}`,
-    value: i
-  }));
-}
+const emit = defineEmits<{ (e: 'submit'): void }>();
 defineExpose({
-  openModal
+  handleOpenModal
 });
+async function handleOpenModal(row: Api.goods.ShopCategory) {
+  rowData.value = row;
+  openModal();
+  setModalLoading(true);
+  const { data } = await fetchGetCategoryProdHb({ code: row.parentCode });
+  if (data) {
+    options.value = data.map(it => {
+      return {
+        ...it,
+        label: it.prodName,
+        value: it.prodId
+      };
+    });
+  }
+  setModalLoading(false);
+}
+async function handleSave() {
+  if (!value.value.length) {
+    window.$message?.error('请选择需要关联的商品');
+    return;
+  }
+  setSubLoading(true);
+  await fetchEditCategoryProd({
+    parentCode: unref(rowData)?.parentCode,
+    code: unref(rowData)?.code,
+    shopId: unref(rowData)?.shopId,
+    categoryId: unref(rowData)?.id,
+    prodIdList: unref(value)
+  });
+  emit('submit');
+  closeModal();
+
+  console.log(111);
+}
 </script>
 
 <template>
-  <BasicModal @register="registerModal">
+  <BasicModal @register="registerModal" @ok="handleSave">
     <NTransfer
       v-model:value="value"
       :options="options"

+ 117 - 0
src/views/goods/desk-category/components/related-goods.vue

@@ -0,0 +1,117 @@
+<script setup lang="ts">
+import { ref, unref, useTemplateRef } from 'vue';
+import { fetchCancelCategoryProd, fetchGetCategoryProdList } from '@/service/api/goods/desk-category';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+import RelatedGoodsModal from './related-goods-modal.vue';
+const RelatedGoodsModalRef = useTemplateRef('GoodsModal');
+const [register, { openModal }] = useModal({
+  title: '已关联商品',
+  isShowHeaderText: false,
+  showFooter: false,
+  width: 1000,
+  height: 900
+});
+const searchForm = ref<Api.goods.ShopCategory>();
+const [registerModalTable, { refresh }] = useTable({
+  searchFormConfig: {
+    schemas: [
+      {
+        field: 'code',
+        label: '商品code',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'prodName',
+        label: '产品名称',
+        component: 'NInput'
+      }
+    ],
+    gridProps: {
+      cols: '1'
+    }
+  },
+  tableConfig: {
+    keyField: 'id',
+    title: '商品列表',
+    showAddButton: false,
+    minHeight: 500,
+    scrollX: 800
+  }
+});
+const emit = defineEmits<{ (e: 'refresh'): void }>();
+
+const columns: NaiveUI.TableColumn<Api.goods.CategoryProd>[] = [
+  {
+    title: '产品名称',
+    key: 'prodName',
+    width: 500,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    title: '上下架状态',
+    key: 'status',
+    render: row => {
+      const statusText = ['下架', '上架', '违规下架', '平台审核'];
+      return statusText[row.status] || '未知状态';
+    }
+  }
+];
+defineExpose({
+  handleOpenModal
+});
+function handleOpenModal(row: Api.goods.ShopCategory) {
+  searchForm.value = row;
+  openModal();
+}
+function handleClick() {
+  RelatedGoodsModalRef.value?.handleOpenModal(searchForm.value as Api.goods.ShopCategory);
+}
+async function handleCancle(row: Api.goods.CategoryProd) {
+  await fetchCancelCategoryProd({
+    parentCode: unref(searchForm)?.parentCode,
+    code: row.code,
+    shopId: row.shopId,
+    categoryId: row.id,
+    prodIdList: [row.prodId]
+  });
+  refresh();
+  emit('refresh');
+}
+function handleRefsh() {
+  refresh();
+  emit('refresh');
+}
+</script>
+
+<template>
+  <BasicModal @register="register">
+    <LayoutTable>
+      <ZTable
+        :columns="columns"
+        show-table-action
+        :api="fetchGetCategoryProdList"
+        :default-params="{ code: searchForm?.code, shopId: searchForm?.shopId }"
+        @register="registerModalTable"
+      >
+        <template #op="{ row }">
+          <NPopconfirm @positive-click="handleCancle(row)">
+            <template #trigger>
+              <NButton type="primary" size="small" quaternary>取消关联</NButton>
+            </template>
+            确定取消关联此商品吗?
+          </NPopconfirm>
+        </template>
+        <template #prefix>
+          <NButton size="small" @click="handleClick">关联商品</NButton>
+        </template>
+      </ZTable>
+    </LayoutTable>
+    <RelatedGoodsModal ref="GoodsModal" @submit="handleRefsh"></RelatedGoodsModal>
+  </BasicModal>
+</template>
+
+<style scoped></style>

+ 147 - 33
src/views/goods/desk-category/index.vue

@@ -1,16 +1,24 @@
 <script setup lang="tsx">
 import { nextTick, ref, useTemplateRef } from 'vue';
-import { NButton, NImage } from 'naive-ui';
-import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
-import { fetchGetAllStoreList, fetchGetDeskCategoryList, fetchUpdateCategory } from '@/service/api/goods/desk-category';
+import { NButton, NImage, NTag } from 'naive-ui';
+import {
+  fetchGategoryImport,
+  fetchGetAllStoreList,
+  fetchGetDeskCategoryList,
+  fetchGetFailLogList,
+  fetchUpdateCategory
+} from '@/service/api/goods/desk-category';
 import { fetchGetAllTagList } from '@/service/api/goods/tag';
 import { useAppStore } from '@/store/modules/app';
 import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
-import RelatedGoodsModal from './components/related-goods-modal.vue';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import SVGIcon from '@/components/custom/svg-icon.vue';
+import RelatedGoods from './components/related-goods.vue';
 const appStore = useAppStore();
 const deskData = ref<Api.goods.ShopCategory[]>([]);
 const loading = ref(false);
+const failData = ref<Api.goods.ShopCategoryLogVO[]>();
 const relatedGoodsModalRef = useTemplateRef('relatedGoodsModalRef');
 const [registerSearchForm, { getFieldsValue: getSearchForm, setFieldsValue }] = useForm({
   schemas: [
@@ -47,8 +55,8 @@ const [registerSearchForm, { getFieldsValue: getSearchForm, setFieldsValue }] =
   },
   collapsedRows: 1
 });
-// const importTemplateRef = useTemplateRef('importTemplateRef');
-const tableColumns: NaiveUI.TableColumn<InternalRowData>[] = [
+const importTemplateRef = useTemplateRef('importTemplateRef');
+const tableColumns: NaiveUI.TableColumn<Api.goods.ShopCategory>[] = [
   {
     title: '分类名称',
     key: 'name'
@@ -56,18 +64,23 @@ const tableColumns: NaiveUI.TableColumn<InternalRowData>[] = [
   {
     title: '分类图标',
     key: 'icon',
-    render(rowData) {
-      const row = rowData as Api.goods.ShopCategory;
+    render(row) {
       return <NImage src={row.icon} class="h-[40px] w-[40px]" />;
     }
   },
   {
     title: '关联商品',
-    key: 'icon'
+    key: 'prodCount',
+    render: row => {
+      return row.level == 3 ? `${row.prodCount}个商品` : '';
+    }
   },
   {
     title: '标签',
-    key: 'labelName'
+    key: 'labelName',
+    render: row => {
+      return row.labelName ? <NTag> {row.labelName} </NTag> : '';
+    }
   },
   {
     title: '排序',
@@ -80,13 +93,18 @@ const tableColumns: NaiveUI.TableColumn<InternalRowData>[] = [
     align: 'center',
     render: row => (
       <div class="flex-center gap-8px">
-        {row.level == 1 && (
+        {
           <NButton type="primary" size="small" quaternary onClick={() => edit(row)}>
             编辑
           </NButton>
-        )}
-        {row.level == 2 && (
-          <NButton type="primary" size="small" quaternary onClick={() => handleClick()}>
+        }
+        {row.level == 3 && (
+          <NButton
+            type="primary"
+            size="small"
+            quaternary
+            onClick={() => relatedGoodsModalRef.value?.handleOpenModal(row)}
+          >
             关联商品
           </NButton>
         )}
@@ -110,6 +128,7 @@ const [
   formConfig: {
     schemas: [
       { label: '', field: 'id', show: false, component: 'NInput' },
+      { label: '', field: 'level', show: false, component: 'NInput' },
       {
         label: '分类名称',
         field: 'name',
@@ -140,6 +159,9 @@ const [
           // api: fetchGetTagList,
           labelFeild: 'name',
           valueFeild: 'id'
+        },
+        ifShow({ model }) {
+          return model.level == 1;
         }
       },
       {
@@ -162,13 +184,85 @@ const [
     }
   }
 });
-// async function handleSubmit(file: File) {
-//   const { error } = await fetchGategoryImport(file);
-//   if (!error) {
-//     importTemplateRef.value?.closeModal();
-//   }
-//   importTemplateRef.value?.setSubLoading(false);
-// }
+const failColumns: NaiveUI.TableColumn<Api.goods.ShopCategoryLogVO>[] = [
+  {
+    key: 'index',
+    title: '序号',
+    align: 'center',
+    render(_, rowIndex) {
+      return rowIndex + 1;
+    }
+  },
+  {
+    key: 'taskName',
+    title: '任务名称',
+    align: 'center'
+  },
+  {
+    key: 'createTime',
+    title: '时间',
+    align: 'center',
+    render(row) {
+      return (
+        <div>
+          <div>创建时间:{row.createTime}</div>
+          <div>完成时间:{row.createTime}</div>
+        </div>
+      );
+    }
+  },
+  {
+    key: 'totalUserCount',
+    title: '操作人',
+    align: 'center',
+    render(row) {
+      return (
+        <div>
+          <div>{row.createByRole}</div>
+          <div>({row.createByName})</div>
+        </div>
+      );
+    }
+  },
+  {
+    key: 'successStatus',
+    title: '状态',
+    align: 'center',
+    width: 240,
+    render(row) {
+      return (
+        <div class={'flex items-center'}>
+          共{Number(row.successStatus) + Number(row.failureStatus)}条,成功:{row.successStatus},
+          <span class={'flex items-center text-red-500'}>
+            失败:
+            {row.failureStatus}
+            {row.failureStatus != 0 && (
+              <div onClick={() => hanleExportFailure(row.taskCode)}>
+                <SVGIcon
+                  icon={'tdesign:download'}
+                  class={'ml-1 cursor-pointer text-20px'}
+                  style={'color:var(--n-color)'}
+                ></SVGIcon>
+              </div>
+            )}
+          </span>
+        </div>
+      );
+    }
+  }
+];
+const [registerModalFail, { openModal: openModalFail, setModalLoading }] = useModal({
+  title: '导入分类',
+  isShowHeaderText: false,
+  showFooter: false
+});
+async function handleSubmit(file: File) {
+  const { error } = await fetchGategoryImport(file);
+  if (!error) {
+    importTemplateRef.value?.closeModal();
+  }
+  importTemplateRef.value?.setSubLoading(false);
+}
 function edit(row: Recordable) {
   openModalForm(row);
   setModalFormValue({ ...row, label: row.label });
@@ -178,6 +272,7 @@ async function getData() {
   const { data, error } = await fetchGetDeskCategoryList(getSearchForm());
   if (!error) {
     deskData.value = buildTree(data).sort((a, b) => b.num - a.num);
+    console.log(deskData.value);
   }
 }
 async function handleSubmitForm() {
@@ -188,12 +283,25 @@ async function handleSubmitForm() {
     getData();
   }
 }
-function handleClick() {
-  relatedGoodsModalRef.value?.openModal();
+
+function handleOpen() {
+  importTemplateRef.value?.openModal();
+}
+async function handleOpenFaileModal() {
+  openModalFail();
+  setModalLoading(true);
+  const { data: failList, error } = await fetchGetFailLogList();
+  if (!error) {
+    failData.value = failList;
+  }
+  setModalLoading(false);
+}
+function hanleExportFailure(taskCode: string) {
+  window.open(
+    `${import.meta.env.VITE_SERVICE_BASE_URL}/platform/shopCategoryLog/export?taskCode=${taskCode}`,
+    '_blank'
+  );
 }
-// function handleOpen() {
-//   importTemplateRef.value?.openModal();
-// }
 interface BuildTreeOptions {
   idKey?: string;
   pidKey?: string;
@@ -251,16 +359,20 @@ function buildTree<T>(items: T[], options: BuildTreeOptions = {}): T[] {
     <NCard title="前台类目" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
       <template #header-extra>
         <TableHeaderOperation :loading="loading" @refresh="getData">
-          <!--
- <template #prefix>
+          <template #prefix>
             <NButton size="small" @click="handleOpen">
               <template #icon>
                 <icon-file-icons:microsoft-excel class="text-icon"></icon-file-icons:microsoft-excel>
               </template>
               Excel导入分类
             </NButton>
+            <NButton size="small" @click="handleOpenFaileModal">
+              <template #icon>
+                <icon-file-icons:microsoft-excel class="text-icon"></icon-file-icons:microsoft-excel>
+              </template>
+              导入记录
+            </NButton>
           </template>
--->
         </TableHeaderOperation>
       </template>
       <NDataTable
@@ -275,17 +387,19 @@ function buildTree<T>(items: T[], options: BuildTreeOptions = {}): T[] {
         class="sm:h-full"
       />
     </NCard>
-    <!--
- <ZImportTemplate
+    <BasicModal @register="registerModalFail">
+      <NDataTable :columns="failColumns" :data="failData" size="small" remote class="sm:h-full" />
+    </BasicModal>
+    <ZImportTemplate
       ref="importTemplateRef"
       url="/platform/shopCategory/exportTemplate"
       template-text="店内分类导入模版.xlsx"
       modal-text="Excel导入分类"
       @submit="handleSubmit"
     ></ZImportTemplate>
--->
+
     <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmitForm"></BasicModelForm>
-    <RelatedGoodsModal ref="relatedGoodsModalRef"></RelatedGoodsModal>
+    <RelatedGoods ref="relatedGoodsModalRef" @refresh="getData"></RelatedGoods>
   </LayoutTable>
 </template>