1
1 天以前 3a4a8c78098f41d3a7ce41f272cdefc35b572681
rsf-design/src/views/basic-info/bas-station-area/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="handleShowDialog('add')" v-ripple>
              {{ t('pages.basicInfo.basStationArea.actions.add') }}
            </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>
@@ -60,18 +77,24 @@
<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 { useCrudPage } from '@/views/system/common/useCrudPage'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import { useUserStore } from '@/store/modules/user'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { fetchDictDataPage } from '@/api/system-manage'
  import { fetchBasStationPage } from '@/api/bas-station'
  import { fetchBasStationOptionPage } from '@/api/bas-station'
  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
  import {
    fetchBasStationAreaDetail,
    fetchBasStationAreaMany,
    fetchBasStationAreaPage,
    fetchDeleteBasStationArea,
    fetchExportBasStationAreaReport,
    fetchSaveBasStationArea,
    fetchUpdateBasStationArea
  } from '@/api/bas-station-area'
@@ -79,13 +102,17 @@
  import BasStationAreaDetailDrawer from './modules/bas-station-area-detail-drawer.vue'
  import { createBasStationAreaTableColumns } from './basStationAreaTable.columns'
  import {
    BAS_STATION_AREA_REPORT_STYLE,
    buildBasStationAreaDialogModel,
    buildBasStationAreaPageQueryParams,
    buildBasStationAreaPrintRows,
    buildBasStationAreaReportMeta,
    buildBasStationAreaSavePayload,
    buildBasStationAreaSearchParams,
    createBasStationAreaSearchState,
    getBasStationAreaBinaryOptions,
    getBasStationAreaPaginationKey,
    getBasStationAreaReportTitle,
    getBasStationAreaStatusOptions,
    getBasStationAreaTypeOptions,
    normalizeBasStationAreaDetailRecord,
@@ -99,6 +126,8 @@
  defineOptions({ name: 'BasStationArea' })
  const { hasAuth } = useAuth()
  const userStore = useUserStore()
  const { t } = useI18n()
  const searchForm = ref(createBasStationAreaSearchState())
  const detailDrawerVisible = ref(false)
@@ -109,6 +138,8 @@
  const containerTypeOptions = ref([])
  const stationOptions = ref([])
  const useStatusOptions = ref([])
  const stationOptionsLoaded = ref(false)
  const stationOptionsLoading = ref(null)
  let handleDeleteAction = null
  const areaLabelMap = computed(
@@ -130,7 +161,7 @@
  const typeLabelMap = computed(
    () =>
      new Map(
        getBasStationAreaTypeOptions()
        getBasStationAreaTypeOptions(t)
          .map((item) => [String(item.value), item.label])
          .filter(([value, label]) => value && label)
      )
@@ -158,46 +189,70 @@
  const resolveTypeLabel = (value) => typeLabelMap.value.get(String(value)) || ''
  const resolveStationAliasLabel = (id) => stationLabelMap.value.get(String(id)) || ''
  const resolveUseStatusLabel = (value) => useStatusLabelMap.value.get(String(value)) || ''
  const reportTitle = computed(() => getBasStationAreaReportTitle(t))
  const reportQueryParams = computed(() => buildBasStationAreaSearchParams(searchForm.value))
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.basicInfo.basStationArea.search.condition'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域名称/编号/备注'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.condition')
      }
    },
    {
      label: '站点区域名称',
      label: t('pages.basicInfo.basStationArea.search.timeStart'),
      key: 'timeStart',
      type: 'date',
      props: {
        clearable: true,
        type: 'date',
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.timeStart')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.timeEnd'),
      key: 'timeEnd',
      type: 'date',
      props: {
        clearable: true,
        type: 'date',
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.timeEnd')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.stationAreaName'),
      key: 'stationAreaName',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域名称'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaName')
      }
    },
    {
      label: '站点区域编号',
      label: t('pages.basicInfo.basStationArea.search.stationAreaId'),
      key: 'stationAreaId',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域编号'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaId')
      }
    },
    {
      label: '站点类型',
      label: t('pages.basicInfo.basStationArea.search.type'),
      key: 'type',
      type: 'select',
      props: {
        clearable: true,
        options: getBasStationAreaTypeOptions()
        options: getBasStationAreaTypeOptions(t)
      }
    },
    {
      label: '所属库区',
      label: t('pages.basicInfo.basStationArea.search.area'),
      key: 'area',
      type: 'select',
      props: {
@@ -207,7 +262,7 @@
      }
    },
    {
      label: '使用状态',
      label: t('pages.basicInfo.basStationArea.search.useStatus'),
      key: 'useStatus',
      type: 'select',
      props: {
@@ -217,7 +272,7 @@
      }
    },
    {
      label: '可入',
      label: t('pages.basicInfo.basStationArea.search.inAble'),
      key: 'inAble',
      type: 'select',
      props: {
@@ -226,7 +281,7 @@
      }
    },
    {
      label: '可出',
      label: t('pages.basicInfo.basStationArea.search.outAble'),
      key: 'outAble',
      type: 'select',
      props: {
@@ -235,7 +290,7 @@
      }
    },
    {
      label: '是否跨区',
      label: t('pages.basicInfo.basStationArea.search.isCrossZone'),
      key: 'isCrossZone',
      type: 'select',
      props: {
@@ -244,7 +299,16 @@
      }
    },
    {
      label: '是否WCS',
      label: t('pages.basicInfo.basStationArea.search.crossZoneArea'),
      key: 'crossZoneArea',
      type: 'input',
      props: {
        clearable: true,
        placeholder: t('pages.basicInfo.basStationArea.placeholder.crossZoneArea')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.isWcs'),
      key: 'isWcs',
      type: 'select',
      props: {
@@ -253,7 +317,25 @@
      }
    },
    {
      label: '自动调拨',
      label: t('pages.basicInfo.basStationArea.search.wcsData'),
      key: 'wcsData',
      type: 'input',
      props: {
        clearable: true,
        placeholder: t('pages.basicInfo.basStationArea.placeholder.wcsData')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.containerType'),
      key: 'containerType',
      type: 'input',
      props: {
        clearable: true,
        placeholder: t('pages.basicInfo.basStationArea.placeholder.containerType')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.autoTransfer'),
      key: 'autoTransfer',
      type: 'select',
      props: {
@@ -262,30 +344,39 @@
      }
    },
    {
      label: '条码',
      label: t('pages.basicInfo.basStationArea.search.barcode'),
      key: 'barcode',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入条码'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.barcode')
      }
    },
    {
      label: '状态',
      label: t('pages.basicInfo.basStationArea.search.stationAlias'),
      key: 'stationAlias',
      type: 'input',
      props: {
        clearable: true,
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAlias')
      }
    },
    {
      label: t('pages.basicInfo.basStationArea.search.status'),
      key: 'status',
      type: 'select',
      props: {
        clearable: true,
        options: getBasStationAreaStatusOptions()
        options: getBasStationAreaStatusOptions(t)
      }
    },
    {
      label: '备注',
      label: t('pages.basicInfo.basStationArea.search.memo'),
      key: 'memo',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入备注'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.memo')
      }
    }
  ])
@@ -294,10 +385,15 @@
    detailDrawerVisible.value = true
    detailLoading.value = true
    try {
      const detail = await guardRequestWithMessage(fetchBasStationAreaDetail(row.id), {}, {
        timeoutMessage: '站点区域详情加载超时,已停止等待'
      })
      const detail = await guardRequestWithMessage(
        fetchBasStationAreaDetail(row.id),
        {},
        {
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.detailTimeout')
        }
      )
      detailData.value = normalizeBasStationAreaDetailRecord(detail, {
        t,
        resolveAreaLabel,
        resolveCrossZoneAreaLabel,
        resolveContainerTypeLabel,
@@ -308,56 +404,124 @@
    } catch (error) {
      detailDrawerVisible.value = false
      detailData.value = {}
      ElMessage.error(error?.message || '获取站点区域详情失败')
      ElMessage.error(
        error?.message || t('pages.basicInfo.basStationArea.messages.detailLoadFailed')
      )
    } finally {
      detailLoading.value = false
    }
  }
  async function openEditDialog(row) {
  async function ensureStationOptions() {
    if (stationOptionsLoaded.value) {
      return
    }
    if (stationOptionsLoading.value) {
      await stationOptionsLoading.value
      return
    }
    stationOptionsLoading.value = (async () => {
      const response = await guardRequestWithMessage(
        fetchBasStationOptionPage(
          {
            current: 1,
            pageSize: 100
          },
          {
            showErrorMessage: false
          }
        ),
        { records: [] },
        {
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.stationAliasTimeout')
        }
      )
      stationOptions.value = resolveBasStationAreaStationOptions(
        defaultResponseAdapter(response).records
      )
      stationOptionsLoaded.value = true
    })()
    try {
      const detail = await guardRequestWithMessage(fetchBasStationAreaDetail(row.id), {}, {
        timeoutMessage: '站点区域详情加载超时,已停止等待'
      })
      showDialog('edit', detail)
    } catch (error) {
      ElMessage.error(error?.message || '获取站点区域详情失败')
      await stationOptionsLoading.value
    } finally {
      stationOptionsLoading.value = null
    }
  }
  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
    useTable({
      core: {
        apiFn: fetchBasStationAreaPage,
        apiParams: buildBasStationAreaPageQueryParams(searchForm.value),
        paginationKey: getBasStationAreaPaginationKey(),
        columnsFactory: () =>
          createBasStationAreaTableColumns({
            handleView: openDetail,
            handleEdit: hasAuth('update') ? openEditDialog : null,
            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
            canEdit: hasAuth('update'),
            canDelete: hasAuth('delete')
          })
      },
      transform: {
        dataTransformer: (records) => {
          if (!Array.isArray(records)) {
            return []
          }
          return records.map((item) =>
            normalizeBasStationAreaListRow(item, {
              resolveAreaLabel,
              resolveCrossZoneAreaLabel,
              resolveContainerTypeLabel,
              resolveTypeLabel,
              resolveStationAliasLabel,
              resolveUseStatusLabel
            })
          )
  async function handleShowDialog(type, record) {
    await ensureStationOptions()
    showDialog(type, record)
  }
  async function openEditDialog(row) {
    try {
      const detail = await guardRequestWithMessage(
        fetchBasStationAreaDetail(row.id),
        {},
        {
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.detailTimeout')
        }
      )
      await handleShowDialog('edit', detail)
    } catch (error) {
      ElMessage.error(
        error?.message || t('pages.basicInfo.basStationArea.messages.detailLoadFailed')
      )
    }
  }
  const {
    columns,
    columnChecks,
    data,
    loading,
    pagination,
    getData,
    replaceSearchParams,
    resetSearchParams,
    handleSizeChange,
    handleCurrentChange,
    refreshData,
    refreshCreate,
    refreshUpdate,
    refreshRemove
  } = useTable({
    core: {
      apiFn: fetchBasStationAreaPage,
      apiParams: buildBasStationAreaPageQueryParams(searchForm.value),
      paginationKey: getBasStationAreaPaginationKey(),
      columnsFactory: () =>
        createBasStationAreaTableColumns({
          t,
          handleView: openDetail,
          handleEdit: hasAuth('update') ? openEditDialog : null,
          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
          canEdit: hasAuth('update'),
          canDelete: hasAuth('delete')
        })
    },
    transform: {
      dataTransformer: (records) => {
        if (!Array.isArray(records)) {
          return []
        }
        return records.map((item) =>
          normalizeBasStationAreaListRow(item, {
            t,
            resolveAreaLabel,
            resolveCrossZoneAreaLabel,
            resolveContainerTypeLabel,
            resolveTypeLabel,
            resolveStationAliasLabel,
            resolveUseStatusLabel
          })
        )
      }
    })
    }
  })
  const {
    dialogVisible,
@@ -376,13 +540,77 @@
    saveRequest: fetchSaveBasStationArea,
    updateRequest: fetchUpdateBasStationArea,
    deleteRequest: fetchDeleteBasStationArea,
    entityName: '站点区域',
    entityName: t('pages.basicInfo.basStationArea.entity'),
    resolveRecordLabel: (record) => record?.stationAreaName || record?.stationAreaId || 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,
      reportStyle: { ...BAS_STATION_AREA_REPORT_STYLE }
    }
  }
  const resolvePrintRecords = async (payload) => {
    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
      return defaultResponseAdapter(await fetchBasStationAreaMany(payload.ids)).records
    }
    return defaultResponseAdapter(
      await fetchBasStationAreaPage({
        ...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: 'bas-station-area.xlsx',
    requestExport: (payload) =>
      fetchExportBasStationAreaReport(payload, {
        headers: {
          Authorization: userStore.accessToken || ''
        }
      }),
    resolvePrintRecords,
    buildPreviewRows: (records) =>
      buildBasStationAreaPrintRows(records, {
        t,
        resolveAreaLabel,
        resolveCrossZoneAreaLabel,
        resolveContainerTypeLabel,
        resolveTypeLabel,
        resolveStationAliasLabel,
        resolveUseStatusLabel
      }),
    buildPreviewMeta
  })
  const resolvedPreviewMeta = computed(() =>
    buildBasStationAreaReportMeta({
      previewMeta: previewMeta.value,
      count: previewRows.value.length,
      orientation:
        previewMeta.value?.reportStyle?.orientation || BAS_STATION_AREA_REPORT_STYLE.orientation,
      t
    })
  )
  function handleSearch(params) {
    replaceSearchParams(buildBasStationAreaSearchParams(params))
@@ -396,27 +624,13 @@
  async function loadAreaOptions() {
    const response = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
      timeoutMessage: '库区选项加载超时,已停止等待'
      timeoutMessage: t('pages.basicInfo.basStationArea.messages.areaOptionsTimeout')
    })
    const options = resolveBasStationAreaWarehouseAreaOptions(defaultResponseAdapter(response).records)
    const options = resolveBasStationAreaWarehouseAreaOptions(
      defaultResponseAdapter(response).records
    )
    areaOptions.value = options
    crossZoneAreaOptions.value = options
  }
  async function loadStationOptions() {
    const response = await guardRequestWithMessage(
      fetchBasStationPage({
        current: 1,
        pageSize: 500
      }, {
        showErrorMessage: false
      }),
      { records: [] },
      {
        timeoutMessage: '站点别名选项加载超时,已停止等待'
      }
    )
    stationOptions.value = resolveBasStationAreaStationOptions(defaultResponseAdapter(response).records)
  }
  async function loadContainerTypeOptions() {
@@ -428,9 +642,11 @@
        status: 1
      }),
      { records: [] },
      { timeoutMessage: '容器类型选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.basicInfo.basStationArea.messages.containerTypeTimeout') }
    )
    containerTypeOptions.value = resolveBasStationAreaContainerTypeOptions(defaultResponseAdapter(response).records)
    containerTypeOptions.value = resolveBasStationAreaContainerTypeOptions(
      defaultResponseAdapter(response).records
    )
  }
  async function loadUseStatusOptions() {
@@ -442,15 +658,16 @@
        status: 1
      }),
      { records: [] },
      { timeoutMessage: '使用状态选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.basicInfo.basStationArea.messages.useStatusTimeout') }
    )
    useStatusOptions.value = resolveBasStationAreaUseStatusOptions(defaultResponseAdapter(response).records)
    useStatusOptions.value = resolveBasStationAreaUseStatusOptions(
      defaultResponseAdapter(response).records
    )
  }
  onMounted(async () => {
    await Promise.allSettled([
      loadAreaOptions(),
      loadStationOptions(),
      loadContainerTypeOptions(),
      loadUseStatusOptions()
    ])