1
2 天以前 3a4a8c78098f41d3a7ce41f272cdefc35b572681
rsf-design/src/views/basic-info/warehouse-areas/index.vue
@@ -12,7 +12,9 @@
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElSpace wrap>
            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增库区</ElButton>
            <ElButton v-auth="'add'" @click="openWarehouseAreasDialog('add')" v-ripple>
              {{ t('common.actions.add') + t('pages.basicInfo.warehouseAreas.entity') }}
            </ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
@@ -20,8 +22,23 @@
              @click="handleBatchDelete"
              v-ripple
            >
              批量删除
              {{ t('common.actions.batchDelete') }}
            </ElButton>
            <ListExportPrint
              class="inline-flex"
              :preview-visible="previewVisible"
              @update:previewVisible="handlePreviewVisibleChange"
              :report-title="reportTitle"
              :selected-rows="selectedRows"
              :query-params="reportQueryParams"
              :columns="columns"
              :preview-rows="previewRows"
              :preview-meta="resolvedPreviewMeta"
              :total="pagination.total"
              :disabled="loading"
              @export="handleExport"
              @print="handlePrint"
            />
          </ElSpace>
        </template>
      </ArtTableHeader>
@@ -59,10 +76,14 @@
<script setup>
  import { computed, onMounted, ref } from 'vue'
  import { ElMessage } from 'element-plus'
  import { useI18n } from 'vue-i18n'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
  import { useAuth } from '@/hooks/core/useAuth'
  import { useTable } from '@/hooks/core/useTable'
  import { useTableColumns } from '@/hooks/core/useTableColumns'
  import { useUserStore } from '@/store/modules/user'
  import { useCrudPage } from '@/views/system/common/useCrudPage'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { fetchDictDataPage } from '@/api/system-manage'
@@ -70,7 +91,9 @@
    fetchCompanysList,
    fetchDeleteWarehouseAreas,
    fetchWarehouseAreasDetail,
    fetchWarehouseAreasMany,
    fetchWarehouseAreasPage,
    fetchExportWarehouseAreasReport,
    fetchSaveWarehouseAreas,
    fetchUpdateWarehouseAreas,
    fetchWarehouseList
@@ -81,10 +104,13 @@
  import {
    buildWarehouseAreasDialogModel,
    buildWarehouseAreasPageQueryParams,
    buildWarehouseAreasPrintRows,
    buildWarehouseAreasSavePayload,
    buildWarehouseAreasSearchParams,
    createWarehouseAreasSearchState,
    getWarehouseAreasReportTitle,
    getWarehouseAreasPaginationKey,
    WAREHOUSE_AREAS_REPORT_STYLE,
    getWarehouseAreasStatusOptions,
    normalizeWarehouseAreasDetailRecord,
    normalizeWarehouseAreasListRow
@@ -92,7 +118,9 @@
  defineOptions({ name: 'WarehouseAreas' })
  const { t } = useI18n()
  const { hasAuth } = useAuth()
  const userStore = useUserStore()
  const searchForm = ref(createWarehouseAreasSearchState())
  const detailDrawerVisible = ref(false)
@@ -102,20 +130,22 @@
  const shipperOptions = ref([])
  const supplierOptions = ref([])
  const typeOptions = ref([])
  const companyOptionsLoaded = ref(false)
  const companyOptionsLoading = ref(null)
  let handleDeleteAction = null
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('table.keyword'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入库区名称/编码/备注'
        placeholder: t('pages.basicInfo.warehouseAreas.search.conditionPlaceholder')
      }
    },
    {
      label: '仓库',
      label: t('pages.basicInfo.warehouseAreas.table.warehouseName'),
      key: 'warehouseId',
      type: 'select',
      props: {
@@ -125,25 +155,25 @@
      }
    },
    {
      label: '库区编码',
      label: t('pages.basicInfo.warehouseAreas.table.code'),
      key: 'code',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入库区编码'
        placeholder: t('pages.basicInfo.warehouseAreas.search.codePlaceholder')
      }
    },
    {
      label: '库区名称',
      label: t('pages.basicInfo.warehouseAreas.table.name'),
      key: 'name',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入库区名称'
        placeholder: t('pages.basicInfo.warehouseAreas.search.namePlaceholder')
      }
    },
    {
      label: '业务类型',
      label: t('pages.basicInfo.warehouseAreas.table.type'),
      key: 'type',
      type: 'select',
      props: {
@@ -153,28 +183,31 @@
      }
    },
    {
      label: '状态',
      label: t('table.status'),
      key: 'status',
      type: 'select',
      props: {
        clearable: true,
        options: getWarehouseAreasStatusOptions()
        options: getWarehouseAreasStatusOptions(t)
      }
    }
  ])
  const reportTitle = computed(() => getWarehouseAreasReportTitle(t))
  const reportQueryParams = computed(() => buildWarehouseAreasSearchParams(searchForm.value))
  async function openDetail(row) {
    detailDrawerVisible.value = true
    detailLoading.value = true
    try {
      const detail = await guardRequestWithMessage(fetchWarehouseAreasDetail(row.id), {}, {
        timeoutMessage: '库区详情加载超时,已停止等待'
        timeoutMessage: t('pages.basicInfo.warehouseAreas.messages.detailTimeout')
      })
      detailData.value = normalizeWarehouseAreasDetailRecord(detail)
    } catch (error) {
      detailDrawerVisible.value = false
      detailData.value = {}
      ElMessage.error(error?.message || '获取库区详情失败')
      ElMessage.error(error?.message || t('pages.basicInfo.warehouseAreas.messages.detailFailed'))
    } finally {
      detailLoading.value = false
    }
@@ -182,13 +215,19 @@
  async function openEditDialog(row) {
    try {
      await ensureCompanyOptions()
      const detail = await guardRequestWithMessage(fetchWarehouseAreasDetail(row.id), {}, {
        timeoutMessage: '库区详情加载超时,已停止等待'
        timeoutMessage: t('pages.basicInfo.warehouseAreas.messages.detailTimeout')
      })
      showDialog('edit', detail)
    } catch (error) {
      ElMessage.error(error?.message || '获取库区详情失败')
      ElMessage.error(error?.message || t('pages.basicInfo.warehouseAreas.messages.detailFailed'))
    }
  }
  async function openWarehouseAreasDialog(type, record) {
    await ensureCompanyOptions()
    showDialog(type, record)
  }
  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
@@ -199,6 +238,7 @@
        paginationKey: getWarehouseAreasPaginationKey(),
        columnsFactory: () =>
          createWarehouseAreasTableColumns({
            t,
            handleView: openDetail,
            handleEdit: hasAuth('update') ? openEditDialog : null,
            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
@@ -233,13 +273,68 @@
    saveRequest: fetchSaveWarehouseAreas,
    updateRequest: fetchUpdateWarehouseAreas,
    deleteRequest: fetchDeleteWarehouseAreas,
    entityName: '库区',
    entityName: t('pages.basicInfo.warehouseAreas.entity'),
    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
    refreshCreate,
    refreshUpdate,
    refreshRemove
  })
  handleDeleteAction = handleDelete
  const buildPreviewMeta = (rows) => {
    const now = new Date()
    return {
      reportDate: now.toLocaleDateString('zh-CN'),
      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
      count: rows.length,
      reportTitle: reportTitle.value,
      reportStyle: { ...WAREHOUSE_AREAS_REPORT_STYLE }
    }
  }
  const resolvePrintRecords = async (payload) => {
    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
      return defaultResponseAdapter(await fetchWarehouseAreasMany(payload.ids)).records
    }
    return defaultResponseAdapter(
      await fetchWarehouseAreasPage({
        ...reportQueryParams.value,
        current: 1,
        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
      })
    ).records
  }
  const {
    previewVisible,
    previewRows,
    previewMeta,
    handlePreviewVisibleChange,
    handleExport,
    handlePrint
  } = usePrintExportPage({
    downloadFileName: 'warehouse-areas.xlsx',
    requestExport: (payload) =>
      fetchExportWarehouseAreasReport(payload, {
        headers: {
          Authorization: userStore.accessToken || ''
        }
      }),
    resolvePrintRecords,
    buildPreviewRows: (records) => buildWarehouseAreasPrintRows(records),
    buildPreviewMeta
  })
  const resolvedPreviewMeta = computed(() => ({
    ...previewMeta.value,
    reportTitle: reportTitle.value,
    count: previewRows.value.length || previewMeta.value?.count || 0,
    reportStyle: {
      ...WAREHOUSE_AREAS_REPORT_STYLE,
      ...(previewMeta.value?.reportStyle || {})
    }
  }))
  function handleSearch(params) {
    replaceSearchParams(buildWarehouseAreasSearchParams(params))
@@ -251,33 +346,51 @@
    resetSearchParams()
  }
  async function loadWarehouseOptions() {
    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
      timeoutMessage: '仓库选项加载超时,已停止等待'
    })
    warehouseOptions.value = defaultResponseAdapter(records).records.map((item) => ({
      label: item.name || item.code || `仓库 ${item.id}`,
      value: item.id
    }))
  async function ensureCompanyOptions() {
    if (companyOptionsLoaded.value) {
      return
    }
    if (companyOptionsLoading.value) {
      await companyOptionsLoading.value
      return
    }
    companyOptionsLoading.value = (async () => {
      const records = await guardRequestWithMessage(fetchCompanysList(), [], {
        timeoutMessage: t('pages.basicInfo.warehouseAreas.messages.companyOptionsTimeout')
      })
      const normalizedRecords = defaultResponseAdapter(records).records
      shipperOptions.value = normalizedRecords
        .filter((item) => item.type === 'shipper')
        .map((item) => ({
          label: item.name || item.code || `${t('pages.basicInfo.warehouseAreas.table.shipperName')} ${item.id}`,
          value: item.id
        }))
      supplierOptions.value = normalizedRecords
        .filter((item) => item.type === 'supplier')
        .map((item) => ({
          label: item.name || item.code || `${t('pages.basicInfo.warehouseAreas.table.supplierName')} ${item.id}`,
          value: item.id
        }))
      companyOptionsLoaded.value = true
    })()
    try {
      await companyOptionsLoading.value
    } finally {
      companyOptionsLoading.value = null
    }
  }
  async function loadCompanyOptions() {
    const records = await guardRequestWithMessage(fetchCompanysList(), [], {
      timeoutMessage: '往来企业选项加载超时,已停止等待'
  async function loadWarehouseOptions() {
    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
      timeoutMessage: t('pages.basicInfo.warehouseAreas.messages.warehouseOptionsTimeout')
    })
    const normalizedRecords = defaultResponseAdapter(records).records
    shipperOptions.value = normalizedRecords
      .filter((item) => item.type === 'shipper')
      .map((item) => ({
        label: item.name || item.code || `货主 ${item.id}`,
        value: item.id
      }))
    supplierOptions.value = normalizedRecords
      .filter((item) => item.type === 'supplier')
      .map((item) => ({
        label: item.name || item.code || `供应商 ${item.id}`,
        value: item.id
      }))
    warehouseOptions.value = defaultResponseAdapter(records).records.map((item) => ({
      label: item.name || item.code || `${t('pages.basicInfo.warehouseAreas.table.warehouseName')} ${item.id}`,
      value: item.id
    }))
  }
  async function loadTypeOptions() {
@@ -289,7 +402,7 @@
        status: 1
      }),
      { records: [] },
      { timeoutMessage: '业务类型加载超时,已停止等待' }
      { timeoutMessage: t('pages.basicInfo.warehouseAreas.messages.typeOptionsTimeout') }
    )
    typeOptions.value = defaultResponseAdapter(response).records.map((item) => ({
      label: item.label || item.name || item.value,
@@ -298,6 +411,6 @@
  }
  onMounted(async () => {
    await Promise.all([loadWarehouseOptions(), loadCompanyOptions(), loadTypeOptions(), getData()])
    await Promise.all([loadWarehouseOptions(), loadTypeOptions(), getData()])
  })
</script>