From 34d36a15f339d331d668d4063cfdff50cffa5800 Mon Sep 17 00:00:00 2001
From: zhou zhou <zozhouo3o@gmail.com>
Date: 星期五, 17 四月 2026 15:11:32 +0800
Subject: [PATCH] #导出服务

---
 rsf-design/src/views/basic-info/loc/index.vue |  192 ++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 176 insertions(+), 16 deletions(-)

diff --git a/rsf-design/src/views/basic-info/loc/index.vue b/rsf-design/src/views/basic-info/loc/index.vue
index 8e69d59..d106336 100644
--- a/rsf-design/src/views/basic-info/loc/index.vue
+++ b/rsf-design/src/views/basic-info/loc/index.vue
@@ -33,8 +33,10 @@
               :preview-rows="previewRows"
               :preview-meta="resolvedPreviewMeta"
               :total="pagination.total"
-              :disabled="loading"
-              @export="handleExport"
+              :disabled="loading || exportTaskLoading"
+              export-auth="manager:loc:export"
+              print-auth="list"
+              @export="handleExportRequest"
               @print="handlePrint"
             />
           </ElSpace>
@@ -71,7 +73,7 @@
 </template>
 
 <script setup>
-  import { computed, onMounted, ref } from 'vue'
+  import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
   import { ElMessage } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { useAuth } from '@/hooks/core/useAuth'
@@ -84,9 +86,12 @@
   import { fetchWarehouseAreasList, fetchWarehouseList } from '@/api/warehouse-areas'
   import {
     fetchDeleteLoc,
+    fetchCreateLocExportTask,
+    fetchDownloadLocExportTask,
     fetchExportLocReport,
     fetchGetLocDetail,
     fetchGetLocMany,
+    fetchLocExportTask,
     fetchLocPage,
     fetchLocTypeList,
     fetchSaveLoc,
@@ -117,6 +122,9 @@
 
   defineOptions({ name: 'Loc' })
 
+  const EXPORT_SYNC_MAX_ROWS = 5000
+  const EXPORT_TASK_POLL_INTERVAL = 3000
+
   const { hasAuth } = useAuth()
   const userStore = useUserStore()
 
@@ -127,7 +135,9 @@
   const warehouseOptions = ref([])
   const areaOptions = ref([])
   const locTypeOptions = ref([])
+  const exportTaskLoading = ref(false)
   let handleDeleteAction = null
+  let exportTaskTimer = null
 
   const reportTitle = LOC_REPORT_TITLE
   const reportQueryParams = computed(() => buildLocSearchParams(searchForm.value))
@@ -189,9 +199,13 @@
   ])
 
   async function fetchLocDetailById(id) {
-    return guardRequestWithMessage(fetchGetLocDetail(id), {}, {
-      timeoutMessage: '搴撲綅璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
-    })
+    return guardRequestWithMessage(
+      fetchGetLocDetail(id),
+      {},
+      {
+        timeoutMessage: '搴撲綅璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
   }
 
   async function openDetail(row) {
@@ -292,13 +306,17 @@
   }
 
   const resolvePrintRecords = async (payload) => {
-    const response = Array.isArray(payload?.ids) && payload.ids.length > 0
-      ? await fetchGetLocMany(payload.ids)
-      : await fetchLocPage({
-          ...reportQueryParams.value,
-          current: 1,
-          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
-        })
+    const response =
+      Array.isArray(payload?.ids) && payload.ids.length > 0
+        ? await fetchGetLocMany(payload.ids)
+        : await fetchLocPage({
+            ...reportQueryParams.value,
+            current: 1,
+            pageSize:
+              Number(pagination.total) > 0
+                ? Number(pagination.total)
+                : Number(payload?.pageSize) || 20
+          })
     return defaultResponseAdapter(response).records
   }
 
@@ -321,6 +339,129 @@
     buildPreviewRows: (records) => buildLocPrintRows(records),
     buildPreviewMeta: (rows) => buildPreviewDialogMeta(rows)
   })
+
+  function clearExportTaskTimer() {
+    if (exportTaskTimer) {
+      clearTimeout(exportTaskTimer)
+      exportTaskTimer = null
+    }
+  }
+
+  function getExportRowCount(payload) {
+    const selectedIds = Array.isArray(payload?.ids) ? payload.ids.filter(Boolean) : []
+    if (selectedIds.length > 0) {
+      return selectedIds.length
+    }
+    return Number(pagination.total) || 0
+  }
+
+  function needsAsyncExport(payload) {
+    return getExportRowCount(payload) > EXPORT_SYNC_MAX_ROWS
+  }
+
+  function scheduleExportTaskPoll(taskId) {
+    clearExportTaskTimer()
+    exportTaskTimer = setTimeout(() => {
+      pollExportTask(taskId)
+    }, EXPORT_TASK_POLL_INTERVAL)
+  }
+
+  function resolveDownloadFileName(task = {}, response) {
+    const contentDisposition = response?.headers?.get('Content-Disposition') || ''
+    const matchedPart = contentDisposition
+      .split(';')
+      .map((part) => part.trim())
+      .find((part) => /^filename\*?=/i.test(part))
+
+    if (matchedPart) {
+      let fileName = matchedPart.replace(/^filename\*?=/i, '').trim()
+      if (fileName.toLowerCase().startsWith("utf-8''")) {
+        fileName = fileName.slice(7)
+      }
+      if (fileName.startsWith('"') && fileName.endsWith('"')) {
+        fileName = fileName.slice(1, -1)
+      }
+      try {
+        return decodeURIComponent(fileName)
+      } catch {
+        return fileName
+      }
+    }
+    return task.fileName || 'loc.xlsx'
+  }
+
+  async function downloadExportTaskFile(task) {
+    const response = await fetchDownloadLocExportTask(task.id, {
+      headers: {
+        Authorization: userStore.accessToken || ''
+      }
+    })
+    if (!response.ok) {
+      throw new Error(`瀵煎嚭鏂囦欢涓嬭浇澶辫触锛�${response.status}锛塦)
+    }
+
+    const blob = await response.blob()
+    const downloadUrl = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = downloadUrl
+    link.download = resolveDownloadFileName(task, response)
+    document.body.appendChild(link)
+    link.click()
+    link.remove()
+    window.URL.revokeObjectURL(downloadUrl)
+  }
+
+  async function pollExportTask(taskId) {
+    try {
+      const task = await fetchLocExportTask(taskId)
+      const status = Number(task?.status)
+      if (status === 2) {
+        clearExportTaskTimer()
+        await downloadExportTaskFile(task)
+        exportTaskLoading.value = false
+        ElMessage.success(
+          `瀵煎嚭浠诲姟宸插畬鎴愶紝宸插紑濮嬩笅杞�${task?.rowCount ? `锛�${task.rowCount}琛岋級` : ''}`
+        )
+        return
+      }
+      if (status === 3) {
+        clearExportTaskTimer()
+        exportTaskLoading.value = false
+        ElMessage.error(task?.errorMsg || '瀵煎嚭浠诲姟鎵ц澶辫触')
+        return
+      }
+      scheduleExportTaskPoll(taskId)
+    } catch (error) {
+      clearExportTaskTimer()
+      exportTaskLoading.value = false
+      ElMessage.error(error?.message || '鏌ヨ瀵煎嚭浠诲姟鐘舵�佸け璐�')
+    }
+  }
+
+  async function handleExportRequest(payload) {
+    if (!needsAsyncExport(payload)) {
+      await handleExport(payload)
+      return
+    }
+
+    const exportRowCount = getExportRowCount(payload)
+    exportTaskLoading.value = true
+    clearExportTaskTimer()
+    try {
+      const task = await fetchCreateLocExportTask(payload)
+      ElMessage.success(
+        `鏈瀵煎嚭鍏� ${exportRowCount} 琛岋紝宸茶秴杩� ${EXPORT_SYNC_MAX_ROWS} 琛岋紝绯荤粺宸茶嚜鍔ㄥ垏鎹负鍚庡彴瀵煎嚭浠诲姟${task?.taskCode ? `锛�${task.taskCode}锛塦 : ''}`
+      )
+      if (!task?.id) {
+        throw new Error('瀵煎嚭浠诲姟鍒涘缓鎴愬姛锛屼絾鏈繑鍥炰换鍔D')
+      }
+      scheduleExportTaskPoll(task.id)
+    } catch (error) {
+      exportTaskLoading.value = false
+      clearExportTaskTimer()
+      ElMessage.error(error?.message || '鍒涘缓瀵煎嚭浠诲姟澶辫触')
+    }
+  }
 
   const resolvedPreviewMeta = computed(() =>
     buildLocReportMeta({
@@ -348,9 +489,28 @@
 
   onMounted(async () => {
     await Promise.all([
-      loadOptions(fetchWarehouseList, resolveLocWarehouseOptions, warehouseOptions, '浠撳簱閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'),
-      loadOptions(fetchWarehouseAreasList, resolveLocAreaOptions, areaOptions, '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'),
-      loadOptions(fetchLocTypeList, resolveLocTypeOptions, locTypeOptions, '搴撲綅绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟')
+      loadOptions(
+        fetchWarehouseList,
+        resolveLocWarehouseOptions,
+        warehouseOptions,
+        '浠撳簱閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      ),
+      loadOptions(
+        fetchWarehouseAreasList,
+        resolveLocAreaOptions,
+        areaOptions,
+        '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      ),
+      loadOptions(
+        fetchLocTypeList,
+        resolveLocTypeOptions,
+        locTypeOptions,
+        '搴撲綅绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      )
     ])
   })
+
+  onBeforeUnmount(() => {
+    clearExportTaskTimer()
+  })
 </script>

--
Gitblit v1.9.1