Browse Source

```
feat(table): 添加分页显示控制功能

- 在Table组件中新增showPagination属性,默认为true
- 根据showPagination属性控制表格分页的显示与隐藏
- 更新类型定义文件,添加showPagination属性注释

feat(modal-form): 支持插槽内容透传

- 在BasicModalForm组件中添加对插槽的支持
- 使用v-for循环将父级插槽透传到内部BasicForm组件

feat(route): 新增免运费管理页面路由

- 添加xsb-manage_free-ship路由配置
- 在路由映射表中注册新的免运费管理页面
- 更新类型定义文件,添加新路由的类型声明

fix(hook): 修复表格数据转换时的解构问题

- 修改defaultTransform函数中的数据解构逻辑
- 添加默认值防止数据结构不完整时出现错误

chore(locale): 添加免运费管理页面国际化配置

- 在中英文语言包中添加xsb-manage_free-ship相关配置项

chore(env): 调整测试环境服务地址配置

- 启用张同学的服务地址配置
- 注释掉本地测试服务器地址配置
```

wenjie 15 hours ago
parent
commit
531c0997da

+ 2 - 2
.env.test

@@ -1,7 +1,7 @@
 # backend service base url, test environment
 
 # VITE_SERVICE_BASE_URL=https://522d2ea1.r39.cpolar.top #王
-# VITE_SERVICE_BASE_URL=http://89561bkaq794.vicp.fun:53846 #张
+VITE_SERVICE_BASE_URL=http://89561bkaq794.vicp.fun #张
  #VITE_SERVICE_BASE_URL=https://25740642.r3.cpolar.top #田
 # VITE_SERVICE_BASE_URL=https://749885d7.r16.cpolar.top #邓
 # VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun #付
@@ -10,7 +10,7 @@
 # VITE_SERVICE_BASE_URL=http://192.168.0.11:8081 #wzq
 # VITE_SERVICE_BASE_URL=http://89561bkaq794.vicp.fun:53846
 
-VITE_SERVICE_BASE_URL=http://47.109.84.152:8081#打包测试本地服务器
+# VITE_SERVICE_BASE_URL=http://47.109.84.152:8081#打包测试本地服务器
 # VITE_SERVICE_BASE_URL=https://smqjh.api.zswlgz.com #服务器
 
 # other backend service base url, test environment

+ 5 - 1
src/components/zt/ModalForm/basic-model-form.vue

@@ -89,7 +89,11 @@ export default defineComponent({
 
 <template>
   <BasicModal @register="registerModal" @ok="handleSubmitForm">
-    <BasicForm @register-form="registerForm"></BasicForm>
+    <BasicForm @register-form="registerForm">
+      <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
+        <slot :name="slotName" v-bind="slotProps" />
+      </template>
+    </BasicForm>
   </BasicModal>
 </template>
 

+ 2 - 1
src/components/zt/Table/props.ts

@@ -44,7 +44,8 @@ export const basicProps = {
       keyField: 'id',
       scrollX: 1000,
       showTableHeaderAction: true,
-      showSearch: true
+      showSearch: true,
+      showPagination: true
     }
   }
 };

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

@@ -57,6 +57,12 @@ export interface tableProp extends DataTableProps {
   // defaultParamsNotReset?: string;
   // 默认参数,重置的时候不重置这个参数 新增可以传多个
   defaultParamsNotReset?: string | string[];
+  /**
+   * 是否显示分页
+   *
+   * @default true
+   */
+  showPagination?: boolean;
 }
 
 export interface ztTableProps {

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

@@ -273,7 +273,7 @@ export default defineComponent({
       :loading="loading"
       remote
       :row-key="row => row[getTableProps.keyField]"
-      :pagination="mobilePagination"
+      :pagination="getTableProps.showPagination !== false ? mobilePagination : undefined"
       class="sm:h-full"
     />
   </NCard>

+ 2 - 1
src/hooks/common/table.ts

@@ -235,7 +235,8 @@ export function defaultTransform<ApiData>(
   const { data, error } = response;
 
   if (!error) {
-    const { list, current, size, total } = data;
+    //  const { list, current, size, total } = data;
+    const { list = data as unknown as ApiData[], current = 1, size = 10, total = 0 } = data;
 
     return {
       list,

+ 2 - 1
src/locales/langs/en-us.ts

@@ -309,7 +309,8 @@ const local: App.I18n.Schema = {
     'h5-manage_user-list': '',
     'reconciliation-center': '',
     'reconciliation-center_points-checking': '',
-    'reconciliation-center_points-details': ''
+    'reconciliation-center_points-details': '',
+    'xsb-manage_free-ship': ''
   },
   page: {
     login: {

+ 2 - 1
src/locales/langs/zh-cn.ts

@@ -306,7 +306,8 @@ const local: App.I18n.Schema = {
     'h5-manage_user-list': '',
     'reconciliation-center': '',
     'reconciliation-center_points-checking': '',
-    'reconciliation-center_points-details': ''
+    'reconciliation-center_points-details': '',
+    'xsb-manage_free-ship': ''
   },
   page: {
     login: {

+ 1 - 0
src/router/elegant/imports.ts

@@ -73,6 +73,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   "user-center": () => import("@/views/user-center/index.vue"),
   "user-management_user-list": () => import("@/views/user-management/user-list/index.vue"),
   "xsb-manage_advertisement": () => import("@/views/xsb-manage/advertisement/index.vue"),
+  "xsb-manage_free-ship": () => import("@/views/xsb-manage/free-ship/index.vue"),
   "xsb-manage_fright-config": () => import("@/views/xsb-manage/fright-config/index.vue"),
   "xsb-manage_search": () => import("@/views/xsb-manage/search/index.vue"),
   "xsb-manage_store-info": () => import("@/views/xsb-manage/store-info/index.vue"),

+ 9 - 0
src/router/elegant/routes.ts

@@ -714,6 +714,15 @@ export const generatedRoutes: GeneratedRoute[] = [
           i18nKey: 'route.xsb-manage_advertisement'
         }
       },
+      {
+        name: 'xsb-manage_free-ship',
+        path: '/xsb-manage/free-ship',
+        component: 'view.xsb-manage_free-ship',
+        meta: {
+          title: 'xsb-manage_free-ship',
+          i18nKey: 'route.xsb-manage_free-ship'
+        }
+      },
       {
         name: 'xsb-manage_fright-config',
         path: '/xsb-manage/fright-config',

+ 1 - 0
src/router/elegant/transform.ts

@@ -251,6 +251,7 @@ const routeMap: RouteMap = {
   "user-management_user-list": "/user-management/user-list",
   "xsb-manage": "/xsb-manage",
   "xsb-manage_advertisement": "/xsb-manage/advertisement",
+  "xsb-manage_free-ship": "/xsb-manage/free-ship",
   "xsb-manage_fright-config": "/xsb-manage/fright-config",
   "xsb-manage_search": "/xsb-manage/search",
   "xsb-manage_store-info": "/xsb-manage/store-info"

+ 250 - 0
src/service/api/xsb-manage/free-ship/index.ts

@@ -0,0 +1,250 @@
+import { request } from '@/service/request';
+
+/** 获取免邮配置 */
+export function fetchConfig(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/globalConfig/find`,
+    method: 'get',
+    params
+  });
+}
+
+/** 保存免邮配置 */
+export function fetchSaveConfig(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/globalConfig/save`,
+    method: 'post',
+    data
+  });
+}
+
+/** 获取区域规则配置 */
+export function fetchAreaRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/findList`,
+    method: 'get',
+    params
+  });
+}
+
+/** 保存区域规则配置 */
+export function fetchSaveAreaRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/save`,
+    method: 'post',
+    data
+  });
+}
+
+/** 编辑区域规则配置 */
+export function fetchUpdateAreaRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/update`,
+    method: 'put',
+    data
+  });
+}
+
+/** 删除区域规则配置 */
+export function fetchDelAreaRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/delete`,
+    method: 'delete',
+    params
+  });
+}
+
+/** 变更状态 */
+export function fetchUpdateAreaStatus(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/updateByStatus`,
+    method: 'get',
+    params
+  });
+}
+
+/** 检查是否存在 */
+export function fetchCheckAreaRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/check`,
+    method: 'post',
+    data
+  });
+}
+
+/** 省列表 */
+export function fetchProvince(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/globalConfig/findAddressAll`,
+    method: 'get',
+    params
+  });
+}
+
+/** 详情 */
+export function fetchAreaDetail(params: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/regionRule/findById`,
+    method: 'get',
+    params
+  });
+}
+
+//   -------------身份------------
+
+/** 获取身份列表 */
+export function fetchUserList(params?: any) {
+  return request({
+    url: `/smqjh-system/api/v1/channel/listAll`,
+    method: 'get',
+    params
+  });
+}
+
+/** 获取区域规则配置 */
+export function fetchUserRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/findList`,
+    method: 'get',
+    params
+  });
+}
+
+/** 保存区域规则配置 */
+export function fetchSaveUserRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/save`,
+    method: 'post',
+    data
+  });
+}
+
+/** 编辑区域规则配置 */
+export function fetchUpdateUserRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/update`,
+    method: 'put',
+    data
+  });
+}
+
+/** 删除区域规则配置 */
+export function fetchDelUserRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/delete`,
+    method: 'delete',
+    params
+  });
+}
+
+/** 变更状态 */
+export function fetchUpdateUserStatus(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/updateByStatus`,
+    method: 'get',
+    params
+  });
+}
+
+/** 详情 */
+export function fetchUserDetail(params: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userIdentityRule/findById`,
+    method: 'get',
+    params
+  });
+}
+
+//   -------------会员------------
+
+/** 获取会员列表 */
+export function fetchMemberList(params?: any) {
+  return request({
+    url: `/smqjh-pms/api/v1/member/type/findAll`,
+    method: 'get',
+    params
+  });
+}
+
+/** 获取区域规则配置 */
+export function fetchMemberRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/findList`,
+    method: 'get',
+    params
+  });
+}
+
+/** 保存区域规则配置 */
+export function fetchSaveMemberRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/save`,
+    method: 'post',
+    data
+  });
+}
+
+/** 编辑区域规则配置 */
+export function fetchUpdateMemberRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/update`,
+    method: 'put',
+    data
+  });
+}
+
+/** 删除区域规则配置 */
+export function fetchDelMemberRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/delete`,
+    method: 'delete',
+    params
+  });
+}
+
+/** 变更状态 */
+export function fetchUpdateMemberStatus(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/updateByStatus`,
+    method: 'get',
+    params
+  });
+}
+
+/** 详情 */
+export function fetchMemberDetail(params: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/userMemberRule/findById`,
+    method: 'get',
+    params
+  });
+}
+
+//   -------------分类--------------
+
+/** 获取分类列表 */
+export function fetchTypeList(params?: any) {
+  return request({
+    url: `/smqjh-pms/api/v1/shop-categories/findAll`,
+    method: 'get',
+    params
+  });
+}
+
+/** 获取分类规则配置 */
+export function fetchTypeRule(params?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/categoryMapping/find`,
+    method: 'get',
+    params
+  });
+}
+
+/** 保存分类规则配置 */
+export function fetchSaveTypeRule(data?: any) {
+  return request({
+    url: `/smqjh-oms/v1/freight/categoryMapping/save`,
+    method: 'post',
+    data
+  });
+}

+ 2 - 0
src/typings/elegant-router.d.ts

@@ -105,6 +105,7 @@ declare module "@elegant-router/types" {
     "user-management_user-list": "/user-management/user-list";
     "xsb-manage": "/xsb-manage";
     "xsb-manage_advertisement": "/xsb-manage/advertisement";
+    "xsb-manage_free-ship": "/xsb-manage/free-ship";
     "xsb-manage_fright-config": "/xsb-manage/fright-config";
     "xsb-manage_search": "/xsb-manage/search";
     "xsb-manage_store-info": "/xsb-manage/store-info";
@@ -252,6 +253,7 @@ declare module "@elegant-router/types" {
     | "user-center"
     | "user-management_user-list"
     | "xsb-manage_advertisement"
+    | "xsb-manage_free-ship"
     | "xsb-manage_fright-config"
     | "xsb-manage_search"
     | "xsb-manage_store-info"

+ 926 - 0
src/views/xsb-manage/free-ship/index.vue

@@ -0,0 +1,926 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NButton, NSpin, NTag, useMessage } from 'naive-ui';
+import type { TreeOption } from 'naive-ui';
+import {
+  fetchAreaDetail,
+  fetchAreaRule,
+  fetchCheckAreaRule,
+  fetchConfig,
+  fetchDelAreaRule,
+  fetchDelMemberRule,
+  fetchDelUserRule,
+  fetchMemberDetail,
+  fetchMemberList,
+  fetchMemberRule,
+  fetchProvince,
+  fetchSaveAreaRule,
+  fetchSaveConfig,
+  fetchSaveMemberRule,
+  fetchSaveTypeRule,
+  fetchSaveUserRule,
+  fetchTypeList,
+  fetchTypeRule,
+  fetchUpdateAreaRule,
+  fetchUpdateAreaStatus,
+  fetchUpdateMemberRule,
+  fetchUpdateMemberStatus,
+  fetchUpdateUserRule,
+  fetchUpdateUserStatus,
+  fetchUserDetail,
+  fetchUserList,
+  fetchUserRule
+} from '@/service/api/xsb-manage/free-ship';
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+const message = useMessage();
+const config = ref({
+  isEnabled: 0, // 总开关: 0-关闭, 1-开启
+  calcBasis: 1, // 1-实付金额, 2-商品价格
+  refundPolicy: 0 // : 0-关闭(不支持部分退款免邮), 1-开启
+});
+const userOptions = ref<any[]>([]);
+const memberOptions = ref<any[]>([]);
+const defaultCheckedKeys = ref<any[]>([]);
+const treeData = ref<TreeOption[]>([]);
+
+const treeLoading = ref(true);
+const typeRule = ref<any[]>([]);
+const [
+  registerModalForm,
+  {
+    openModal: openModalForm,
+    setFieldsValue: setModalFormValue,
+    getFieldsValue: getModalFormValue,
+    closeModal: closeModalForm,
+    setSubLoading
+  }
+] = useModalFrom({
+  modalConfig: {
+    title: '区域规则',
+    isShowHeaderText: false
+  },
+  formConfig: {
+    schemas: [
+      {
+        label: '',
+        field: 'id',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'ruleName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'updateTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'createTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'tenantId',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'ruleName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'regionType',
+        label: '区域类型',
+        component: 'NSelect',
+        required: true,
+        componentProps: {
+          options: [
+            {
+              label: '省内',
+              value: 1
+            },
+            {
+              label: '省外',
+              value: 2
+            },
+            {
+              label: '指定省份',
+              value: 3
+            }
+          ],
+          placeholder: '请选择'
+        }
+      },
+      {
+        field: 'ruleName',
+        label: '省份类型',
+        component: 'ApiSelect',
+        required: true,
+        componentProps: {
+          api: fetchProvince,
+          labelField: 'provinceName',
+          valueField: 'provinceName',
+          placeholder: '请选择'
+        },
+        ifShow({ model }) {
+          return model.regionType == 3;
+        }
+      },
+      {
+        field: 'thresholdAmount',
+        label: '门槛金额',
+        component: 'NInputNumber',
+        componentProps: {
+          placeholder: '请输入',
+          precision: 2
+        },
+        suffix: 'desc-suffix',
+        required: true
+      },
+      {
+        field: 'regionStatus',
+        label: '状态',
+        component: 'NSwitch',
+        defaultValue: 1,
+        required: true,
+        componentProps: {
+          checkedValue: 1,
+          uncheckedValue: 0
+        }
+      }
+    ],
+    labelWidth: 120,
+    layout: 'horizontal',
+    gridProps: {
+      cols: '1',
+      itemResponsive: true
+    }
+  }
+});
+
+const [
+  registerUserModalForm,
+  {
+    openModal: openUserModalForm,
+    setFieldsValue: setUserModalFormValue,
+    getFieldsValue: getUserModalFormValue,
+    closeModal: closeUserModal,
+    setSubLoading: setUserSubLoading
+  }
+] = useModalFrom({
+  modalConfig: {
+    title: '身份规则',
+    isShowHeaderText: false
+  },
+  formConfig: {
+    schemas: [
+      {
+        label: '',
+        field: 'id',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'ruleName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'updateTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'createTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'tenantId',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'identityName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'identityId',
+        label: '身份类型',
+        component: 'ApiSelect',
+        required: true,
+        componentProps: {
+          api: fetchUserList,
+          labelField: 'channelName',
+          valueField: 'id',
+          placeholder: '请选择',
+          getOptions: async (options: any) => {
+            userOptions.value = options;
+          }
+        }
+      },
+      {
+        field: 'thresholdAmount',
+        label: '门槛金额',
+        component: 'NInputNumber',
+        componentProps: {
+          placeholder: '请输入',
+          precision: 2
+        },
+        suffix: 'desc-suffix',
+        required: true
+      },
+      {
+        field: 'identityStatus',
+        label: '状态',
+        component: 'NSwitch',
+        defaultValue: 1,
+        required: true,
+        componentProps: {
+          checkedValue: 1,
+          uncheckedValue: 0
+        }
+      }
+    ],
+    labelWidth: 120,
+    layout: 'horizontal',
+    gridProps: {
+      cols: '1',
+      itemResponsive: true
+    }
+  }
+});
+
+const [
+  registerMemberModalForm,
+  {
+    openModal: openMemberModalForm,
+    setFieldsValue: setMemberModalFormValue,
+    getFieldsValue: getMemberModalFormValue,
+    closeModal: closeMemberModal,
+    setSubLoading: setMemberSubLoading
+  }
+] = useModalFrom({
+  modalConfig: {
+    title: '会员身份规则',
+    isShowHeaderText: false
+  },
+  formConfig: {
+    schemas: [
+      {
+        label: '',
+        field: 'id',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'ruleName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'updateTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'createTime',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'tenantId',
+        component: 'NInput',
+        show: false
+      },
+      {
+        label: '',
+        field: 'identityName',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'identityId',
+        label: '身份类型',
+        component: 'ApiSelect',
+        required: true,
+        componentProps: {
+          api: fetchMemberList,
+          labelField: 'memberName',
+          valueField: 'id',
+          placeholder: '请选择',
+          getOptions: async (options: any) => {
+            memberOptions.value = options;
+          }
+        }
+      },
+      {
+        field: 'thresholdAmount',
+        label: '门槛金额',
+        component: 'NInputNumber',
+        componentProps: {
+          placeholder: '请输入',
+          precision: 2
+        },
+        suffix: 'desc-suffix',
+        required: true
+      },
+      {
+        field: 'memberStatus',
+        label: '状态',
+        component: 'NSwitch',
+        defaultValue: 1,
+        required: true,
+        componentProps: {
+          checkedValue: 1,
+          uncheckedValue: 0
+        }
+      }
+    ],
+    labelWidth: 120,
+    layout: 'horizontal',
+    gridProps: {
+      cols: '1',
+      itemResponsive: true
+    }
+  }
+});
+
+const [registerTable, { refresh }] = useTable({
+  tableConfig: {
+    showTableHeaderAction: false,
+    showSearch: false,
+    showAddButton: false,
+    keyField: 'id',
+    title: '',
+    showDeleteButton: false,
+    showPagination: false,
+    opWdith: 240
+  }
+});
+const [registerUserTable, { refresh: refreshUser }] = useTable({
+  tableConfig: {
+    showTableHeaderAction: false,
+    showSearch: false,
+    showAddButton: false,
+    keyField: 'id',
+    title: '',
+    showDeleteButton: false,
+    showPagination: false,
+    opWdith: 240
+  }
+});
+
+const [registerMemberTable, { refresh: refreshMember }] = useTable({
+  tableConfig: {
+    showTableHeaderAction: false,
+    showSearch: false,
+    showAddButton: false,
+    keyField: 'id',
+    title: '',
+    showDeleteButton: false,
+    showPagination: false,
+    opWdith: 240
+  }
+});
+
+const areaColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '规则类型',
+    key: 'ruleName',
+    align: 'center'
+  },
+  {
+    title: '省份',
+    key: 'provinceName',
+    align: 'center',
+    render: row => {
+      return row.provinceName || '--';
+    }
+  },
+  {
+    title: '门槛金额',
+    align: 'center',
+    key: 'thresholdAmount',
+    render: row => {
+      return `¥${row.thresholdAmount}`;
+    }
+  },
+  {
+    title: '状态',
+    align: 'center',
+    key: 'regionStatus',
+    render: row => {
+      return row.regionStatus === 1 ? '启用' : '停用';
+    }
+  }
+];
+
+const userColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '用户身份',
+    key: 'identityName',
+    align: 'center'
+  },
+
+  {
+    title: '门槛金额',
+    align: 'center',
+    key: 'thresholdAmount',
+    render: row => {
+      return `¥${row.thresholdAmount}`;
+    }
+  },
+  {
+    title: '状态',
+    align: 'center',
+    key: 'identityStatus',
+    render: row => {
+      return row.identityStatus === 1 ? '启用' : '停用';
+    }
+  }
+];
+
+const memberColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '用户身份',
+    key: 'identityName',
+    align: 'center'
+  },
+
+  {
+    title: '门槛金额',
+    align: 'center',
+    key: 'thresholdAmount',
+    render: row => {
+      return `¥${row.thresholdAmount}`;
+    }
+  },
+  {
+    title: '状态',
+    align: 'center',
+    key: 'memberStatus',
+    render: row => {
+      return row.memberStatus === 1 ? '启用' : '停用';
+    }
+  }
+];
+
+async function handleEdit(row: any) {
+  // 编辑逻辑待实现
+  openModalForm();
+  const { data } = await fetchAreaDetail({ id: row.id });
+  setModalFormValue(data);
+}
+
+function handleDelete(row: any) {
+  // 删除逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: '你确定要删除吗?',
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelAreaRule({ id: row.id });
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+
+function handleDisable(row: any) {
+  // 停用逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要${row.regionStatus === 1 ? '停用' : '启用'}吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchUpdateAreaStatus({ id: row.id });
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+
+async function handleValidateClick(e: MouseEvent) {
+  e.preventDefault();
+  const res = await fetchSaveConfig(config.value);
+  if (!res.error) {
+    message.success('保存成功');
+  }
+}
+
+async function getConfig() {
+  const res = await fetchConfig();
+  console.log(res, 'res');
+  if (!res.error) {
+    config.value = res.data;
+  }
+}
+getConfig();
+async function handleSubmit() {
+  const form = await getModalFormValue();
+  console.log('submit', form);
+
+  setSubLoading(true);
+  if (form.id) {
+    // 编辑
+    fetchUpdateAreaRule(form).then(res => {
+      if (!res.error) {
+        message.success('编辑成功');
+        closeModalForm();
+        refresh();
+      }
+    });
+  } else {
+    const { error } = await fetchCheckAreaRule(form);
+    if (error) {
+      return;
+    }
+    // 新增
+    fetchSaveAreaRule(form).then(res => {
+      if (!res.error) {
+        message.success('新增成功');
+        closeModalForm();
+        refresh();
+      }
+    });
+  }
+}
+
+// -------------身份----------------
+
+// 身份
+async function handleUserSubmit() {
+  const form = await getUserModalFormValue();
+  console.log('submit', form);
+  form.identityName = userOptions.value.find((item: any) => String(item.id) === String(form.identityId))?.channelName;
+
+  setUserSubLoading(true);
+  if (form.id) {
+    // 编辑
+    fetchUpdateUserRule(form).then(res => {
+      if (!res.error) {
+        message.success('编辑成功');
+        closeUserModal();
+        refreshUser();
+      }
+    });
+  } else {
+    // 新增
+    fetchSaveUserRule(form).then(res => {
+      if (!res.error) {
+        message.success('新增成功');
+        closeUserModal();
+        refreshUser();
+      }
+    });
+  }
+}
+
+async function handleEditUser(row: any) {
+  // 编辑逻辑待实现
+  openUserModalForm();
+  const { data } = await fetchUserDetail({ id: row.id });
+  setUserModalFormValue(data);
+}
+
+function handleDeleteUser(row: any) {
+  // 删除逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: '你确定要删除吗?',
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelUserRule({ id: row.id });
+      if (!error) {
+        refreshUser();
+      }
+    }
+  });
+}
+
+function handleDisableUser(row: any) {
+  // 停用逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要${row.identityStatus === 1 ? '停用' : '启用'}吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchUpdateUserStatus({ id: row.id });
+      if (!error) {
+        refreshUser();
+      }
+    }
+  });
+}
+
+// -------------会员----------------
+
+// 身份
+async function handleMemberSubmit() {
+  const form = await getMemberModalFormValue();
+  console.log('submit', form);
+  form.identityName = memberOptions.value.find((item: any) => String(item.id) === String(form.identityId))?.memberName;
+
+  setMemberSubLoading(true);
+  if (form.id) {
+    // 编辑
+    fetchUpdateMemberRule(form).then(res => {
+      if (!res.error) {
+        message.success('编辑成功');
+        closeMemberModal();
+        refreshMember();
+      }
+    });
+  } else {
+    // 新增
+    fetchSaveMemberRule(form).then(res => {
+      if (!res.error) {
+        message.success('新增成功');
+        closeMemberModal();
+        refreshMember();
+      }
+    });
+  }
+}
+
+async function handleEditMember(row: any) {
+  // 编辑逻辑待实现
+  openMemberModalForm();
+  const { data } = await fetchMemberDetail({ id: row.id });
+  setMemberModalFormValue(data);
+}
+
+function handleDeleteMember(row: any) {
+  // 删除逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: '你确定要删除吗?',
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelMemberRule({ id: row.id });
+      if (!error) {
+        refreshMember();
+      }
+    }
+  });
+}
+
+function handleDisableMember(row: any) {
+  // 停用逻辑待实现
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要${row.memberStatus === 1 ? '停用' : '启用'}吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchUpdateMemberStatus({ id: row.id });
+      if (!error) {
+        refreshMember();
+      }
+    }
+  });
+}
+
+// --------------------分类-----------------------
+function updateCheckedKeys(_keys: Array<string | number>, options: Array<TreeOption | null>) {
+  typeRule.value = options.map(item => {
+    return {
+      categoryId: item?.id,
+      categoryName: item?.name,
+      parentCategoryId: item?.pid,
+      shopId: item?.shopId
+    };
+  });
+}
+
+async function getTreeData() {
+  const { data } = await fetchTypeList();
+  treeData.value = data;
+}
+
+async function getTypeRule() {
+  const { data } = await fetchTypeRule();
+  typeRule.value = data;
+  data.forEach((item: any) => {
+    defaultCheckedKeys.value.push(item.categoryId);
+  });
+}
+
+async function initTree() {
+  treeLoading.value = true;
+  await Promise.all([getTreeData(), getTypeRule()]);
+  treeLoading.value = false;
+}
+initTree();
+
+async function submitType() {
+  treeLoading.value = true;
+  const { error } = await fetchSaveTypeRule(typeRule.value);
+  if (!error) {
+    message.success('保存成功');
+
+    await getTypeRule();
+    treeLoading.value = false;
+  }
+}
+
+function removeCategory(id: string | number) {
+  defaultCheckedKeys.value = defaultCheckedKeys.value.filter(key => key !== id);
+  typeRule.value = typeRule.value.filter(item => item.categoryId !== id);
+}
+</script>
+
+<template>
+  <div class="flex-col-stretch">
+    <NCard :bordered="false" class="mb-20px flex-shrink-0">
+      <div class="mb-20px text-16px font-bold">基础设置</div>
+      <NForm
+        :model="config"
+        label-placement="left"
+        label-width="auto"
+        require-mark-placement="right-hanging"
+        size="medium"
+        class="max-w-640px"
+      >
+        <NFormItem label="启用免邮功能">
+          <NSwitch v-model:value="config.isEnabled" :checked-value="1" :unchecked-value="0" />
+          <div class="text-12px text-#ccc">(关闭后所有免邮规则不生效,订单运费恢复正常计费)</div>
+        </NFormItem>
+        <NFormItem label="计算基准">
+          <NRadioGroup v-model:value="config.calcBasis">
+            <NRadio :value="1">实付金额</NRadio>
+            <NRadio :value="2">商品价格</NRadio>
+          </NRadioGroup>
+        </NFormItem>
+        <NFormItem label="免邮退款限制">
+          <NSwitch v-model:value="config.refundPolicy" :checked-value="1" :unchecked-value="0" />
+          <div class="text-12px text-#ccc">(开启后免邮订单仅支持整单退款,不允许部分退款)</div>
+        </NFormItem>
+        <NFormItem>
+          <NButton attr-type="button" type="primary" size="small" @click="handleValidateClick">保存设置</NButton>
+        </NFormItem>
+      </NForm>
+    </NCard>
+    <div class="rounded-8px bg-#fff p-10px">
+      <div class="flex items-center">
+        <div class="text-16px font-bold">区域门槛规则</div>
+        <div class="text-12px text-#ccc">
+          (根据收货地址区域设置不同的免邮门槛,若 规则命中"指定省份"规则后不再向上匹配)
+        </div>
+      </div>
+      <div>
+        <NButton type="primary" size="small" @click="openModalForm">+添加区域</NButton>
+      </div>
+    </div>
+    <LayoutTable class="mh-200 mb-20px">
+      <ZTable :columns="areaColumns" :api="fetchAreaRule" @register="registerTable">
+        <template #op="{ row }">
+          <NSpace>
+            <NButton size="small" type="primary" @click="handleEdit(row)">编辑</NButton>
+            <NButton size="small" @click="handleDisable(row)">{{ row.regionStatus == 1 ? '停用' : '启用' }}</NButton>
+            <NButton size="small" type="error" @click="handleDelete(row)">删除</NButton>
+          </NSpace>
+        </template>
+      </ZTable>
+    </LayoutTable>
+    <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit">
+      <template #desc-suffix="{}">
+        <span>设为0元表示包邮</span>
+      </template>
+    </BasicModelForm>
+
+    <div class="rounded-8px bg-#fff p-10px">
+      <div class="flex items-center">
+        <div class="text-16px font-bold">用户身份规则</div>
+        <div class="text-12px text-#ccc">
+          (根据不同身份等级的用户配置专属免邮策略,若同一用户命中多条身份规则时,取最低门槛金额)
+        </div>
+      </div>
+      <div>
+        <NButton type="primary" size="small" @click="openUserModalForm">+添加身份</NButton>
+      </div>
+    </div>
+    <LayoutTable class="mh-200 mb-20px">
+      <ZTable :columns="userColumns" :api="fetchUserRule" @register="registerUserTable">
+        <template #op="{ row }">
+          <NSpace>
+            <NButton size="small" type="primary" @click="handleEditUser(row)">编辑</NButton>
+            <NButton size="small" @click="handleDisableUser(row)">
+              {{ row.identityStatus == 1 ? '停用' : '启用' }}
+            </NButton>
+            <NButton size="small" type="error" @click="handleDeleteUser(row)">删除</NButton>
+          </NSpace>
+        </template>
+      </ZTable>
+    </LayoutTable>
+    <BasicModelForm @register-modal-form="registerUserModalForm" @submit-form="handleUserSubmit">
+      <template #desc-suffix="{}">
+        <span>设为0元表示包邮</span>
+      </template>
+    </BasicModelForm>
+
+    <!------------ 会员------------ -->
+
+    <div class="rounded-8px bg-#fff p-10px">
+      <div class="flex items-center">
+        <div class="text-16px font-bold">会员身份规则</div>
+        <div class="text-12px text-#ccc">
+          (根据不同身份等级的用户配置专属免邮策略,若同一用户命中多条身份规则时,取最低门槛金额)
+        </div>
+      </div>
+      <div>
+        <NButton type="primary" size="small" @click="openMemberModalForm">+添加身份</NButton>
+      </div>
+    </div>
+    <LayoutTable class="mh-200 mb-20px">
+      <ZTable :columns="memberColumns" :api="fetchMemberRule" @register="registerMemberTable">
+        <template #op="{ row }">
+          <NSpace>
+            <NButton size="small" type="primary" @click="handleEditMember(row)">编辑</NButton>
+            <NButton size="small" @click="handleDisableMember(row)">
+              {{ row.memberStatus == 1 ? '停用' : '启用' }}
+            </NButton>
+            <NButton size="small" type="error" @click="handleDeleteMember(row)">删除</NButton>
+          </NSpace>
+        </template>
+      </ZTable>
+    </LayoutTable>
+    <BasicModelForm @register-modal-form="registerMemberModalForm" @submit-form="handleMemberSubmit">
+      <template #desc-suffix="{}">
+        <span>设为0元表示包邮</span>
+      </template>
+    </BasicModelForm>
+
+    <!--------------- 分类 ---------------------->
+    <NCard :bordered="false" class="mb-20px flex-shrink-0">
+      <div class="flex items-center rounded-8px bg-#fff">
+        <div class="text-16px font-bold">商品类目规则</div>
+        <div class="text-12px text-#ccc">(选择参与免邮的商品类目)</div>
+      </div>
+      <div class="flex">
+        <div class="w-400px">
+          <NSpin :show="treeLoading">
+            <NTree
+              v-model:checked-keys="defaultCheckedKeys"
+              block-line
+              cascade
+              checkable
+              :selectable="false"
+              check-strategy="child"
+              :data="treeData"
+              key-field="id"
+              label-field="name"
+              default-expand-all
+              @update:checked-keys="updateCheckedKeys"
+            />
+          </NSpin>
+        </div>
+        <div class="ml-12px min-h-400px flex-1 border-1px border-#e8e8e8 rounded-8px bg-#fafafa p-12px">
+          <div class="mb-8px text-14px text-#333 font-bold">已选包邮类目</div>
+          <div class="flex flex-wrap content-start items-start gap-6px">
+            <NTag v-for="item in typeRule" :key="item.categoryId" closable @close="removeCategory(item.categoryId)">
+              {{ item.categoryName }}
+            </NTag>
+            <span v-if="!typeRule.length" class="text-12px text-#999">暂未选择</span>
+          </div>
+        </div>
+      </div>
+
+      <NButton :loading="treeLoading" type="primary" size="small" @click="submitType">保存设置</NButton>
+    </NCard>
+  </div>
+</template>
+
+<style scoped>
+.mh-200 {
+  min-height: 200px !important;
+}
+</style>