From c64bb94c7e1758f47e8c946e4656a71bb5fd53e8 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 12 八月 2026 17:10:57 +0800
Subject: [PATCH] feat(system): 新增部门管理和岗位管理的 Excel 导入功能

---
 src/views/system/dept/modules/import-form.vue          |  104 +++++++++++
 src/views/system/dept/index.vue                        |   19 ++
 src/views/system/post/index.vue                        |   19 ++
 src/views/mes/md/workstation/modules/form.vue          |   34 +-
 src/api/system/dept/index.ts                           |   13 +
 src/views/system/post/modules/import-form.vue          |   82 +++++++++
 src/views/mes/md/item/modules/form.vue                 |    4 
 src/views/mes/md/item/modules/product-media-list.vue   |    4 
 src/views/system/post/data.ts                          |   24 ++
 src/views/mes/md/item/index.vue                        |    6 
 src/views/system/dept/data.ts                          |   24 ++
 src/views/mes/pro/workorder/modules/form.vue           |   33 +-
 src/views/mes/pro/workorder/index.vue                  |   35 +--
 src/api/system/post/index.ts                           |   13 +
 src/views/mes/md/workstation/index.vue                 |   42 ++--
 src/views/mes/md/workstation/workshop/modules/form.vue |   32 +-
 16 files changed, 393 insertions(+), 95 deletions(-)

diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts
index d6403d7..7d20bf2 100644
--- a/src/api/system/dept/index.ts
+++ b/src/api/system/dept/index.ts
@@ -50,3 +50,16 @@
 export async function deleteDeptList(ids: number[]) {
   return requestClient.delete(`/system/dept/delete-list?ids=${ids.join(',')}`);
 }
+
+/** 涓嬭浇閮ㄩ棬瀵煎叆妯℃澘 */
+export function importDeptTemplate() {
+  return requestClient.download('/system/dept/get-import-template');
+}
+
+/** 瀵煎叆閮ㄩ棬 */
+export function importDept(file: File, updateSupport: boolean) {
+  return requestClient.upload('/system/dept/import', {
+    file,
+    updateSupport,
+  });
+}
diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts
index 8b32ad2..3032a56 100644
--- a/src/api/system/post/index.ts
+++ b/src/api/system/post/index.ts
@@ -61,3 +61,16 @@
     params,
   });
 }
+
+/** 涓嬭浇宀椾綅瀵煎叆妯℃澘 */
+export function importPostTemplate() {
+  return requestClient.download('/system/post/get-import-template');
+}
+
+/** 瀵煎叆宀椾綅 */
+export function importPost(file: File, updateSupport: boolean) {
+  return requestClient.upload('/system/post/import', {
+    file,
+    updateSupport,
+  });
+}
diff --git a/src/views/mes/md/item/index.vue b/src/views/mes/md/item/index.vue
index 66c6421..f8855f6 100644
--- a/src/views/mes/md/item/index.vue
+++ b/src/views/mes/md/item/index.vue
@@ -25,7 +25,7 @@
   } from "#/api/mes/md/item";
   import { $t } from "#/locales";
   import { MdItemTypeTree } from "#/views/mes/md/item/type/components";
-  import { PrinterLabel } from "#/views/wls/barcode/components";
+  // import { PrinterLabel } from "#/views/wls/barcode/components";
 
   import { useGridColumns, useGridFormSchema } from "./data";
   import Form from "./modules/form.vue";
@@ -203,9 +203,9 @@
                     },
                   },
                 ]" />
-              <PrinterLabel :biz-id="row.id"
+              <!-- <PrinterLabel :biz-id="row.id"
                             :biz-code="row.code"
-                            biz-type="ITEM" />
+                            biz-type="ITEM" /> -->
             </div>
           </template>
         </Grid>
diff --git a/src/views/mes/md/item/modules/form.vue b/src/views/mes/md/item/modules/form.vue
index db322fd..7f1f865 100644
--- a/src/views/mes/md/item/modules/form.vue
+++ b/src/views/mes/md/item/modules/form.vue
@@ -141,10 +141,10 @@
       <Tabs.TabPane key="substitute" tab="鏇夸唬鍝�">
         <Empty description="鏇夸唬鍝侊紙寰呭疄鐜帮級" />
       </Tabs.TabPane>
-      <Tabs.TabPane key="sip" tab="SIP">
+      <Tabs.TabPane key="sip" tab="妫�楠屾寚瀵间功(SIP)">
         <ProductSipForm :form-type="formType" :item-id="formData.id" />
       </Tabs.TabPane>
-      <Tabs.TabPane key="sop" tab="SOP">
+      <Tabs.TabPane key="sop" tab="浣滀笟鎸囧涔�(SOP)">
         <ProductSopForm :form-type="formType" :item-id="formData.id" />
       </Tabs.TabPane>
     </Tabs>
diff --git a/src/views/mes/md/item/modules/product-media-list.vue b/src/views/mes/md/item/modules/product-media-list.vue
index 777e9fd..06563c7 100644
--- a/src/views/mes/md/item/modules/product-media-list.vue
+++ b/src/views/mes/md/item/modules/product-media-list.vue
@@ -48,7 +48,9 @@
 );
 
 const isReadOnly = computed(() => props.formType === 'detail'); // 鏄惁鍙
-const title = computed(() => props.kind); // 褰撳墠濯掍綋绫诲瀷鏍囬
+const title = computed(() =>
+  props.kind === 'SIP' ? '妫�楠屾寚瀵间功(SIP)' : '浣滀笟鎸囧涔�(SOP)',
+);
 
 /** 灏嗗畬鏁� URL 杞负鐩稿璺緞锛岀‘淇濊蛋鍓嶇浠g悊 */
 function resolveImageUrl(url?: string): string {
diff --git a/src/views/mes/md/workstation/index.vue b/src/views/mes/md/workstation/index.vue
index 6f505e4..8baf7e2 100644
--- a/src/views/mes/md/workstation/index.vue
+++ b/src/views/mes/md/workstation/index.vue
@@ -5,7 +5,7 @@
 import { ref } from 'vue';
 
 import { Page, useVbenModal } from '@vben/common-ui';
-import { BarcodeBizTypeEnum } from '@vben/constants';
+// import { BarcodeBizTypeEnum } from '@vben/constants';
 import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
@@ -17,12 +17,12 @@
   getWorkstationPage,
 } from '#/api/mes/md/workstation';
 import { $t } from '#/locales';
-import { BarcodeDetail } from '#/views/wls/barcode/components';
+// import { BarcodeDetail } from '#/views/wls/barcode/components';
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
 
-const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
+// const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
@@ -65,17 +65,17 @@
 }
 
 /** 鏌ョ湅宸ヤ綔绔欐潯鐮� */
-function handleBarcode(row: MesMdWorkstationApi.Workstation) {
-  if (!row.id) {
-    return;
-  }
-  barcodeDetailRef.value?.openByBusiness(
-    row.id,
-    BarcodeBizTypeEnum.WORKSTATION,
-    row.code,
-    row.name,
-  );
-}
+// function handleBarcode(row: MesMdWorkstationApi.Workstation) {
+//   if (!row.id) {
+//     return;
+//   }
+//   barcodeDetailRef.value?.openByBusiness(
+//     row.id,
+//     BarcodeBizTypeEnum.WORKSTATION,
+//     row.code,
+//     row.name,
+//   );
+// }
 
 /** 瀵煎嚭宸ヤ綔绔� */
 async function handleExport() {
@@ -116,7 +116,7 @@
 
 <template>
   <Page auto-content-height><FormModal @success="handleRefresh" />
-    <BarcodeDetail ref="barcodeDetailRef" />
+    <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
 
     <Grid table-title="宸ヤ綔绔欏垪琛�">
       <template #toolbar-tools>
@@ -165,12 +165,12 @@
                 confirm: handleDelete.bind(null, row),
               },
             },
-            {
-              label: '鏉$爜',
-              type: 'link',
-              auth: ['mes:md-workstation:query'],
-              onClick: handleBarcode.bind(null, row),
-            },
+            // {
+            //   label: '鏉$爜',
+            //   type: 'link',
+            //   auth: ['mes:md-workstation:query'],
+            //   onClick: handleBarcode.bind(null, row),
+            // },
           ]"
         />
       </template>
diff --git a/src/views/mes/md/workstation/modules/form.vue b/src/views/mes/md/workstation/modules/form.vue
index 0c47ba3..0258a32 100644
--- a/src/views/mes/md/workstation/modules/form.vue
+++ b/src/views/mes/md/workstation/modules/form.vue
@@ -6,7 +6,7 @@
   import { computed, ref } from "vue";
 
   import { useVbenModal } from "@vben/common-ui";
-  import { BarcodeBizTypeEnum } from "@vben/constants";
+  // import { BarcodeBizTypeEnum } from "@vben/constants";
 
   import { Button, message, Tabs } from "ant-design-vue";
 
@@ -17,7 +17,7 @@
     updateWorkstation,
   } from "#/api/mes/md/workstation";
   import { $t } from "#/locales";
-  import { BarcodeDetail } from "#/views/wls/barcode/components";
+  // import { BarcodeDetail } from "#/views/wls/barcode/components";
 
   import { useFormSchema } from "../data";
   import MachineList from "./machine-list.vue";
@@ -28,7 +28,7 @@
   const formType = ref<FormType>("create"); // 琛ㄥ崟妯″紡
   const subTabsName = ref("machine"); // 褰撳墠璧勬簮椤电
   const formData = ref<MesMdWorkstationApi.Workstation>();
-  const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
+  // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
 
   const isDetail = computed(() => formType.value === "detail"); // 鏄惁鏌ョ湅妯″紡
   const getTitle = computed(() => {
@@ -53,17 +53,17 @@
   });
 
   /** 鏌ョ湅宸ヤ綔绔欐潯鐮� */
-  function handleBarcode() {
-    if (!formData.value?.id) {
-      return;
-    }
-    barcodeDetailRef.value?.openByBusiness(
-      formData.value.id,
-      BarcodeBizTypeEnum.WORKSTATION,
-      formData.value.code,
-      formData.value.name
-    );
-  }
+  // function handleBarcode() {
+  //   if (!formData.value?.id) {
+  //     return;
+  //   }
+  //   barcodeDetailRef.value?.openByBusiness(
+  //     formData.value.id,
+  //     BarcodeBizTypeEnum.WORKSTATION,
+  //     formData.value.code,
+  //     formData.value.name
+  //   );
+  // }
 
   const [Modal, modalApi] = useVbenModal({
     async onConfirm() {
@@ -146,13 +146,13 @@
     </Tabs>
     <template #prepend-footer>
       <div class="flex flex-auto items-center">
-        <Button v-if="isDetail && formData?.id"
+        <!-- <Button v-if="isDetail && formData?.id"
                 type="primary"
                 @click="handleBarcode">
           鏌ョ湅鏉$爜
-        </Button>
+        </Button> -->
       </div>
     </template>
-    <BarcodeDetail ref="barcodeDetailRef" />
+    <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
   </Modal>
 </template>
diff --git a/src/views/mes/md/workstation/workshop/modules/form.vue b/src/views/mes/md/workstation/workshop/modules/form.vue
index 2dd8b84..ec8f682 100644
--- a/src/views/mes/md/workstation/workshop/modules/form.vue
+++ b/src/views/mes/md/workstation/workshop/modules/form.vue
@@ -6,7 +6,7 @@
   import { computed, ref } from "vue";
 
   import { useVbenModal } from "@vben/common-ui";
-  import { BarcodeBizTypeEnum } from "@vben/constants";
+  // import { BarcodeBizTypeEnum } from "@vben/constants";
 
   import { Button, message } from "ant-design-vue";
 
@@ -17,14 +17,14 @@
     updateWorkshop,
   } from "#/api/mes/md/workstation/workshop";
   import { $t } from "#/locales";
-  import { BarcodeDetail } from "#/views/wls/barcode/components";
+  // import { BarcodeDetail } from "#/views/wls/barcode/components";
 
   import { useFormSchema } from "../data";
 
   const emit = defineEmits(["success"]);
   const formType = ref<FormType>("create"); // 琛ㄥ崟妯″紡
   const formData = ref<MesMdWorkshopApi.Workshop>();
-  const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
+  // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
 
   const isDetail = computed(() => formType.value === "detail"); // 鏄惁鏌ョ湅妯″紡
   const getTitle = computed(() => {
@@ -49,17 +49,17 @@
   });
 
   /** 鏌ョ湅杞﹂棿鏉$爜 */
-  function handleBarcode() {
-    if (!formData.value?.id) {
-      return;
-    }
-    barcodeDetailRef.value?.openByBusiness(
-      formData.value.id,
-      BarcodeBizTypeEnum.WORKSHOP,
-      formData.value.code,
-      formData.value.name
-    );
-  }
+  // function handleBarcode() {
+  //   if (!formData.value?.id) {
+  //     return;
+  //   }
+  //   barcodeDetailRef.value?.openByBusiness(
+  //     formData.value.id,
+  //     BarcodeBizTypeEnum.WORKSHOP,
+  //     formData.value.code,
+  //     formData.value.name
+  //   );
+  // }
 
   const [Modal, modalApi] = useVbenModal({
     async onConfirm() {
@@ -117,9 +117,9 @@
     <template v-if="isDetail && formData?.id"
               #prepend-footer>
       <div class="flex flex-auto items-center">
-        <Button @click="handleBarcode">鏌ョ湅鏉$爜</Button>
+        <!-- <Button @click="handleBarcode">鏌ョ湅鏉$爜</Button> -->
       </div>
     </template>
   </Modal>
-  <BarcodeDetail ref="barcodeDetailRef" />
+  <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
 </template>
diff --git a/src/views/mes/pro/workorder/index.vue b/src/views/mes/pro/workorder/index.vue
index e3c8248..9a388d0 100644
--- a/src/views/mes/pro/workorder/index.vue
+++ b/src/views/mes/pro/workorder/index.vue
@@ -6,7 +6,6 @@
 
   import { Page, useVbenModal } from "@vben/common-ui";
   import {
-    BarcodeBizTypeEnum,
     MesProWorkOrderStatusEnum,
     MesProWorkOrderTypeEnum,
   } from "@vben/constants";
@@ -23,7 +22,7 @@
   } from "#/api/mes/pro/workorder";
   import { $t } from "#/locales";
   import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
-import { BarcodeDetail } from '#/views/wls/barcode/components';
+// import { BarcodeDetail } from '#/views/wls/barcode/components';
 
   import { useGridColumns, useGridFormSchema } from "./data";
   import Form from "./modules/form.vue";
@@ -34,7 +33,7 @@
   });
 
   const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>();
-const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
+// const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
 
   /** 鍒锋柊琛ㄦ牸 */
   function handleRefresh() {
@@ -89,14 +88,14 @@
   }
 
   /** 鏌ョ湅璁㈠崟鏉$爜 */
-  function handleBarcode(row: MesProWorkOrderApi.WorkOrder) {
-    barcodeDetailRef.value?.openByBusiness(
-      row.id!,
-      BarcodeBizTypeEnum.WORKORDER,
-      row.code,
-      row.name
-    );
-  }
+  // function handleBarcode(row: MesProWorkOrderApi.WorkOrder) {
+  //   barcodeDetailRef.value?.openByBusiness(
+  //     row.id!,
+  //     BarcodeBizTypeEnum.WORKORDER,
+  //     row.code,
+  //     row.name
+  //   );
+  // }
 
   /** 瀵煎嚭琛ㄦ牸 */
   async function handleExport() {
@@ -221,12 +220,12 @@
                 confirm: handleCancel.bind(null, row),
               },
             },
-            {
-              label: '鏉$爜',
-              type: 'link',
-              auth: ['mes:pro-work-order:query'],
-              onClick: handleBarcode.bind(null, row),
-            },
+            // {
+            //   label: '鏉$爜',
+            //   type: 'link',
+            //   auth: ['mes:pro-work-order:query'],
+            //   onClick: handleBarcode.bind(null, row),
+            // },
             {
               label: '褰掓。',
               type: 'link',
@@ -235,7 +234,7 @@
           ]" />
       </template>
     </Grid>
-    <BarcodeDetail ref="barcodeDetailRef" />
+    <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
     <ArchiveDetail ref="archiveDetailRef" />
   </Page>
 </template>
diff --git a/src/views/mes/pro/workorder/modules/form.vue b/src/views/mes/pro/workorder/modules/form.vue
index 5da9d79..2a51ec9 100644
--- a/src/views/mes/pro/workorder/modules/form.vue
+++ b/src/views/mes/pro/workorder/modules/form.vue
@@ -8,7 +8,6 @@
 
   import { confirm, useVbenModal } from "@vben/common-ui";
   import {
-    BarcodeBizTypeEnum,
     MesProWorkOrderStatusEnum,
   } from "@vben/constants";
 
@@ -24,7 +23,7 @@
   } from "#/api/mes/pro/workorder";
   import { batchSaveWorkOrderProcess, getWorkOrderProcessListByWorkOrderId } from "#/api/mes/pro/workorder/process";
   import { $t } from "#/locales";
-  import { BarcodeDetail } from "#/views/wls/barcode/components";
+  // import { BarcodeDetail } from "#/views/wls/barcode/components";
 
   import { useFormSchema } from "../data";
   import PredictDelivery from "./ai/predict-delivery.vue";
@@ -39,7 +38,7 @@
   const formData = ref<MesProWorkOrderApi.WorkOrder>();
   const originalSnapshot = ref("");
   const subTabsName = ref("process");
-  const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+  // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
   const processListRef = ref<InstanceType<typeof ProcessList>>();
   const currentProductId = ref<number>(); // 褰撳墠閫変腑鐨勪骇鍝� ID锛堢敤浜庡伐搴忓姞杞斤級
   const tempProcessList = ref<MesProWorkOrderProcessApi.WorkOrderProcess[]>([]); // 涓存椂宸ュ簭鍒楄〃
@@ -147,17 +146,17 @@
   }
 
   /** 鏌ョ湅璁㈠崟鏉$爜 */
-  function handleBarcode() {
-    if (!formData.value?.id) {
-      return;
-    }
-    barcodeDetailRef.value?.openByBusiness(
-      formData.value.id,
-      BarcodeBizTypeEnum.WORKORDER,
-      formData.value.code,
-      formData.value.name
-    );
-  }
+  // function handleBarcode() {
+  //   if (!formData.value?.id) {
+  //     return;
+  //   }
+  //   barcodeDetailRef.value?.openByBusiness(
+  //     formData.value.id,
+  //     BarcodeBizTypeEnum.WORKORDER,
+  //     formData.value.code,
+  //     formData.value.name
+  //   );
+  // }
 
   /** 宸ュ簭鍒楄〃鍙樺寲鍥炶皟 */
   function handleProcessListChange(list: MesProWorkOrderProcessApi.WorkOrderProcess[]) {
@@ -384,13 +383,13 @@
                 @click="handleSave">
           淇濆瓨淇敼
         </Button>
-        <Button v-if="formType === 'detail' && formData?.id"
+        <!-- <Button v-if="formType === 'detail' && formData?.id"
                 @click="handleBarcode">
           鏌ョ湅鏉$爜
-        </Button>
+        </Button> -->
         <Button @click="modalApi.close()">鍙栨秷</Button>
       </div>
     </template>
   </Modal>
-  <BarcodeDetail ref="barcodeDetailRef" />
+  <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
 </template>
diff --git a/src/views/system/dept/data.ts b/src/views/system/dept/data.ts
index d111330..9fe39e2 100644
--- a/src/views/system/dept/data.ts
+++ b/src/views/system/dept/data.ts
@@ -114,6 +114,30 @@
   ];
 }
 
+/** 閮ㄩ棬瀵煎叆鐨勮〃鍗� */
+export function useImportFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'file',
+      label: '閮ㄩ棬鏁版嵁',
+      component: 'Upload',
+      rules: 'required',
+      help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
+    },
+    {
+      fieldName: 'updateSupport',
+      label: '鏄惁瑕嗙洊',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+      help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勯儴闂ㄦ暟鎹�',
+    },
+  ];
+}
+
 /** 鍒楄〃鐨勫瓧娈� */
 export function useGridColumns(): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] {
   return [
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
index 64f998e..1646f7b 100644
--- a/src/views/system/dept/index.vue
+++ b/src/views/system/dept/index.vue
@@ -15,9 +15,15 @@
 
 import { useGridColumns } from './data';
 import Form from './modules/form.vue';
+import ImportForm from './modules/import-form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+  connectedComponent: ImportForm,
   destroyOnClose: true,
 });
 
@@ -36,6 +42,11 @@
 /** 鍒涘缓閮ㄩ棬 */
 function handleCreate() {
   formModalApi.setData(null).open();
+}
+
+/** 瀵煎叆閮ㄩ棬 */
+function handleImport() {
+  importModalApi.open();
 }
 
 /** 娣诲姞涓嬬骇閮ㄩ棬 */
@@ -129,6 +140,7 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <ImportModal @success="handleRefresh" />
     <Grid table-title="閮ㄩ棬鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -141,6 +153,13 @@
               onClick: handleCreate,
             },
             {
+              label: $t('ui.actionTitle.import', ['閮ㄩ棬']),
+              type: 'primary',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['system:dept:import'],
+              onClick: handleImport,
+            },
+            {
               label: isExpanded ? '鏀剁缉' : '灞曞紑',
               type: 'primary',
               onClick: handleExpand,
diff --git a/src/views/system/dept/modules/import-form.vue b/src/views/system/dept/modules/import-form.vue
new file mode 100644
index 0000000..08eee2b
--- /dev/null
+++ b/src/views/system/dept/modules/import-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+
+import { h } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, notification, Upload } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { importDept, importDeptTemplate } from '#/api/system/dept';
+import { $t } from '#/locales';
+
+import { useImportFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useImportFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = await formApi.getValues();
+    try {
+      const response = (await importDept(data.file, data.updateSupport)) as any;
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+
+      const resultData = response?.data || response || {};
+      const failureNames = resultData.failureDeptNames || {};
+
+      if (Object.keys(failureNames).length > 0) {
+        const errorNodes = Object.entries(failureNames).map(([name, reason]) => {
+          return h('div', `${name}: ${reason}`);
+        });
+
+        notification.warning({
+          message: '瀵煎叆瀹屾垚锛岄儴鍒嗛儴闂ㄥ鍏ュけ璐�',
+          description: () => h('div', errorNodes),
+          duration: null,
+        });
+      } else {
+        message.success($t('ui.actionMessage.operationSuccess'));
+      }
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+/** 涓婁紶鍓� */
+function beforeUpload(file: FileType) {
+  formApi.setFieldValue('file', file);
+  return false;
+}
+
+/** 涓嬭浇妯$増 */
+async function handleDownload() {
+  const data = await importDeptTemplate();
+  downloadFileFromBlobPart({ fileName: '閮ㄩ棬瀵煎叆妯℃澘.xls', source: data });
+}
+</script>
+
+<template>
+  <Modal title="瀵煎叆閮ㄩ棬" class="w-1/3">
+    <Form class="mx-4">
+      <template #file>
+        <div class="w-full">
+          <Upload
+            :max-count="1"
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+          >
+            <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
+          </Upload>
+        </div>
+      </template>
+    </Form>
+    <div class="mx-4 mt-2 mb-4 text-gray-500 text-xs">
+      鎻愮ず锛氭ā鏉块噷鐨勨�滀笂绾ч儴闂ㄢ�濊濉啓鍏ㄨ矾寰勶紙濡�:鎬诲叕鍙�/鍖椾含鍒嗗叕鍙革級
+      <div>涓婄骇閮ㄩ棬绌虹潃鍗充负椤剁骇閮ㄩ棬銆�</div>
+    </div>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center">
+        <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/system/post/data.ts b/src/views/system/post/data.ts
index 3c66ebc..ca71395 100644
--- a/src/views/system/post/data.ts
+++ b/src/views/system/post/data.ts
@@ -68,6 +68,30 @@
   ];
 }
 
+/** 宀椾綅瀵煎叆鐨勮〃鍗� */
+export function useImportFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'file',
+      label: '宀椾綅鏁版嵁',
+      component: 'Upload',
+      rules: 'required',
+      help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
+    },
+    {
+      fieldName: 'updateSupport',
+      label: '鏄惁瑕嗙洊',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+      help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勫矖浣嶆暟鎹�',
+    },
+  ];
+}
+
 /** 鍒楄〃鐨勬悳绱㈣〃鍗� */
 export function useGridFormSchema(): VbenFormSchema[] {
   return [
diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue
index c59418e..a8039dc 100644
--- a/src/views/system/post/index.vue
+++ b/src/views/system/post/index.vue
@@ -20,9 +20,15 @@
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+import ImportForm from './modules/import-form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+  connectedComponent: ImportForm,
   destroyOnClose: true,
 });
 
@@ -40,6 +46,11 @@
 /** 鍒涘缓宀椾綅 */
 function handleCreate() {
   formModalApi.setData(null).open();
+}
+
+/** 瀵煎叆宀椾綅 */
+function handleImport() {
+  importModalApi.open();
 }
 
 /** 缂栬緫宀椾綅 */
@@ -126,6 +137,7 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <ImportModal @success="handleRefresh" />
     <Grid table-title="宀椾綅鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -145,6 +157,13 @@
               onClick: handleExport,
             },
             {
+              label: $t('ui.actionTitle.import'),
+              type: 'primary',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['system:post:import'],
+              onClick: handleImport,
+            },
+            {
               label: $t('ui.actionTitle.deleteBatch'),
               type: 'primary',
               danger: true,
diff --git a/src/views/system/post/modules/import-form.vue b/src/views/system/post/modules/import-form.vue
new file mode 100644
index 0000000..ba6b9ca
--- /dev/null
+++ b/src/views/system/post/modules/import-form.vue
@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, Upload } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { importPost, importPostTemplate } from '#/api/system/post';
+import { $t } from '#/locales';
+
+import { useImportFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useImportFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = await formApi.getValues();
+    try {
+      await importPost(data.file, data.updateSupport);
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+/** 涓婁紶鍓� */
+function beforeUpload(file: FileType) {
+  formApi.setFieldValue('file', file);
+  return false;
+}
+
+/** 涓嬭浇妯$増 */
+async function handleDownload() {
+  const data = await importPostTemplate();
+  downloadFileFromBlobPart({ fileName: '宀椾綅瀵煎叆妯℃澘.xls', source: data });
+}
+</script>
+
+<template>
+  <Modal title="瀵煎叆宀椾綅" class="w-1/3">
+    <Form class="mx-4">
+      <template #file>
+        <div class="w-full">
+          <Upload
+            :max-count="1"
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+          >
+            <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
+          </Upload>
+        </div>
+      </template>
+    </Form>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center">
+        <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
+      </div>
+    </template>
+  </Modal>
+</template>

--
Gitblit v1.9.3