zhou zhou
2026-04-10 0d93ec4c10d146ffe287e7f4430ee66ad5832a17
rsf-design/src/views/orders/delivery/index.vue
@@ -11,21 +11,50 @@
    <ElCard class="art-table-card">
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <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 wrap>
            <ElUpload
              v-auth="'update'"
              :auto-upload="false"
              :show-file-list="false"
              accept=".xlsx,.xls"
              @change="handleImportFileChange"
            >
              <ElButton :loading="importing">{{
                t('pages.orders.delivery.buttons.import')
              }}</ElButton>
            </ElUpload>
            <ElButton
              v-auth="'update'"
              :loading="templateDownloading"
              @click="handleDownloadTemplate"
            >
              {{ t('pages.orders.delivery.buttons.downloadTemplate') }}
            </ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
              plain
              :disabled="selectedRows.length === 0"
              @click="handleBatchDelete"
            >
              {{ 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>
@@ -50,15 +79,24 @@
      @size-change="handleDetailSizeChange"
      @current-change="handleDetailCurrentChange"
    />
    <DeliveryManageDialog
      v-model:visible="manageDialogVisible"
      :delivery="manageDeliveryData"
      :can-add="hasAuth('update')"
      :can-edit="hasAuth('update')"
      :can-delete="hasAuth('delete')"
      @changed="handleManageChanged"
    />
  </div>
</template>
<script setup>
  import { computed, reactive, ref } from 'vue'
  import { useRouter } from 'vue-router'
  import { useI18n } from 'vue-i18n'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { useUserStore } from '@/store/modules/user'
  import { useAuth } from '@/hooks/core/useAuth'
  import { useTable } from '@/hooks/core/useTable'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
@@ -73,24 +111,29 @@
    createDeliverySearchState,
    getDeliveryReportTitle,
    getDeliveryPaginationKey,
    getDeliveryStatusOptions,
    getDeliveryExceStatusOptions,
    normalizeDeliveryItemRow,
    normalizeDeliveryRow
  } from './deliveryPage.helpers.js'
  import {
    fetchDeleteDelivery,
    fetchDeleteDeliveryMany,
    fetchDeliveryItemPage,
    fetchDeliveryPage,
    fetchDownloadDeliveryTemplate,
    fetchExportDeliveryReport,
    fetchGetDeliveryDetail,
    fetchGetDeliveryMany
  } from '@/api/delivery'
  import { fetchImportDelivery } from '@/api/delivery'
  import DeliveryDetailDrawer from './modules/delivery-detail-drawer.vue'
  import DeliveryManageDialog from './modules/delivery-manage-dialog.vue'
  import { createDeliveryTableColumns } from './deliveryTable.columns.js'
  defineOptions({ name: 'Delivery' })
  const userStore = useUserStore()
  const router = useRouter()
  const { hasAuth } = useAuth()
  const { t } = useI18n()
  const reportTitle = computed(() => getDeliveryReportTitle(t))
  const searchForm = ref(createDeliverySearchState())
@@ -101,6 +144,10 @@
  const detailData = ref({})
  const detailItemRows = ref([])
  const activeDeliveryId = ref(null)
  const importing = ref(false)
  const templateDownloading = ref(false)
  const manageDialogVisible = ref(false)
  const manageDeliveryData = ref({})
  const detailItemPagination = reactive({
    current: 1,
    size: 20,
@@ -116,6 +163,26 @@
      props: {
        clearable: true,
        placeholder: t('pages.orders.delivery.placeholder.condition')
      }
    },
    {
      label: t('pages.orders.delivery.search.timeStart'),
      key: 'timeStart',
      type: 'date',
      props: {
        clearable: true,
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.orders.delivery.placeholder.timeStart')
      }
    },
    {
      label: t('pages.orders.delivery.search.timeEnd'),
      key: 'timeEnd',
      type: 'date',
      props: {
        clearable: true,
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.orders.delivery.placeholder.timeEnd')
      }
    },
    {
@@ -164,11 +231,84 @@
      }
    },
    {
      label: t('pages.orders.delivery.search.exceStatus'),
      key: 'exceStatus',
      label: t('pages.orders.delivery.search.anfme'),
      key: 'anfme',
      type: 'number',
      props: {
        min: 0,
        precision: 2,
        controlsPosition: 'right',
        placeholder: t('pages.orders.delivery.placeholder.anfme')
      }
    },
    {
      label: t('pages.orders.delivery.search.qty'),
      key: 'qty',
      type: 'number',
      props: {
        min: 0,
        precision: 2,
        controlsPosition: 'right',
        placeholder: t('pages.orders.delivery.placeholder.qty')
      }
    },
    {
      label: t('pages.orders.delivery.search.workQty'),
      key: 'workQty',
      type: 'number',
      props: {
        min: 0,
        precision: 2,
        controlsPosition: 'right',
        placeholder: t('pages.orders.delivery.placeholder.workQty')
      }
    },
    {
      label: t('pages.orders.delivery.search.platCode'),
      key: 'platCode',
      type: 'input',
      props: {
        clearable: true,
        placeholder: t('pages.orders.delivery.placeholder.platCode')
      }
    },
    {
      label: t('pages.orders.delivery.search.startTime'),
      key: 'startTime',
      type: 'date',
      props: {
        clearable: true,
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.orders.delivery.placeholder.startTime')
      }
    },
    {
      label: t('pages.orders.delivery.search.endTime'),
      key: 'endTime',
      type: 'date',
      props: {
        clearable: true,
        valueFormat: 'YYYY-MM-DD',
        placeholder: t('pages.orders.delivery.placeholder.endTime')
      }
    },
    {
      label: t('table.status'),
      key: 'status',
      type: 'select',
      props: {
        clearable: true,
        options: getDeliveryStatusOptions(t),
        placeholder: t('pages.orders.delivery.placeholder.status')
      }
    },
    {
      label: t('pages.orders.delivery.search.exceStatus'),
      key: 'exceStatus',
      type: 'select',
      props: {
        clearable: true,
        options: getDeliveryExceStatusOptions(t),
        placeholder: t('pages.orders.delivery.placeholder.exceStatus')
      }
    },
@@ -208,9 +348,13 @@
        { timeoutMessage: t('pages.orders.delivery.messages.itemsTimeout') }
      )
      const normalizedResponse = defaultResponseAdapter(response)
      detailItemRows.value = normalizedResponse.records.map((item) => normalizeDeliveryItemRow(item, t))
      detailItemRows.value = normalizedResponse.records.map((item) =>
        normalizeDeliveryItemRow(item, t)
      )
      detailItemPagination.total = Number(normalizedResponse.total || 0)
      detailItemPagination.current = Number(normalizedResponse.current || detailItemPagination.current || 1)
      detailItemPagination.current = Number(
        normalizedResponse.current || detailItemPagination.current || 1
      )
      detailItemPagination.size = Number(normalizedResponse.size || detailItemPagination.size || 20)
    } finally {
      detailItemsLoading.value = false
@@ -248,51 +392,75 @@
  }
  async function handleDelete(row) {
    try {
      await ElMessageBox.confirm(
        t('crud.confirm.deleteMessage', {
          entity: t('pages.orders.delivery.entity'),
          label: row.code || row.id
        }),
        t('crud.confirm.deleteTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchDeleteDelivery(row.id)
      ElMessage.success(t('crud.messages.deleteSuccess'))
      await refreshRemove()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
    await ElMessageBox.confirm(
      t('crud.confirm.deleteMessage', {
        entity: t('pages.orders.delivery.entity'),
        label: row.code || row.id
      }),
      t('crud.confirm.deleteTitle'),
      {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      }
    }
    )
    await fetchDeleteDeliveryMany([row.id])
    ElMessage.success(t('crud.messages.deleteSuccess'))
    await refreshRemove()
  }
  function handleTableActionClick(action, row) {
  function openManageDialog(row) {
    manageDeliveryData.value = normalizeDeliveryRow(row, t)
    manageDialogVisible.value = true
  }
  async function handleBatchDelete() {
    if (!selectedRows.value.length) {
      return
    }
    await ElMessageBox.confirm(
      t('crud.confirm.batchDeleteMessage', {
        count: selectedRows.value.length,
        entity: t('pages.orders.delivery.entity')
      }),
      t('crud.confirm.batchDeleteTitle'),
      {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      }
    )
    await fetchDeleteDeliveryMany(selectedRows.value.map((item) => item.id))
    ElMessage.success(t('crud.messages.batchDeleteSuccess'))
    selectedRows.value = []
    await refreshData()
  }
  async function handleTableActionClick(action, row) {
    if (!action) {
      return
    }
    if (action.key === 'view') {
      openDetail(row)
      await openDetail(row)
      return
    }
    if (action.key === 'edit') {
      openManageDialog(row)
      return
    }
    if (action.key === 'items') {
      if (!row?.id) {
        return
      }
      router.push({
        path: '/orders/delivery-item',
        query: {
          deliveryId: String(row.id)
        }
      })
      openManageDialog(row)
      return
    }
    if (action.key === 'delete') {
      handleDelete(row)
      try {
        await handleDelete(row)
      } catch (error) {
        if (error === 'cancel' || error?.message === 'cancel') {
          return
        }
        ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
      }
    }
  }
@@ -317,10 +485,16 @@
        pageSize: 20
      }),
      paginationKey: getDeliveryPaginationKey(),
      columnsFactory: () => createDeliveryTableColumns({ handleActionClick: handleTableActionClick })
      columnsFactory: () =>
        createDeliveryTableColumns({
          handleActionClick: handleTableActionClick,
          canEdit: hasAuth('update'),
          canDelete: hasAuth('delete')
        })
    },
    transform: {
      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeDeliveryRow(item, t)) : [])
      dataTransformer: (records) =>
        Array.isArray(records) ? records.map((item) => normalizeDeliveryRow(item, t)) : []
    }
  })
@@ -338,6 +512,69 @@
    resetSearchParams()
  }
  async function handleManageChanged() {
    await refreshData()
    if (detailDrawerVisible.value && activeDeliveryId.value) {
      await loadDetailItems(activeDeliveryId.value)
    }
  }
  async function handleImportFileChange(uploadFile) {
    if (!uploadFile?.raw) {
      return
    }
    importing.value = true
    try {
      await fetchImportDelivery(uploadFile.raw)
      ElMessage.success(t('pages.orders.delivery.messages.importSuccess'))
      await refreshData()
    } catch (error) {
      ElMessage.error(error?.message || t('pages.orders.delivery.messages.importFailed'))
    } finally {
      importing.value = false
    }
  }
  async function downloadFile(response, fallbackName) {
    const blob = await response.blob()
    if (!blob || !blob.size) {
      throw new Error(t('pages.orders.delivery.messages.templateDownloadFailed'))
    }
    const disposition = response.headers.get('Content-Disposition') || ''
    const matchedName =
      disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1] ||
      disposition.match(/filename="?([^";]+)"?/i)?.[1]
    const fileName = matchedName ? decodeURIComponent(matchedName) : fallbackName
    const url = URL.createObjectURL(blob)
    const anchor = document.createElement('a')
    anchor.href = url
    anchor.download = fileName
    document.body.appendChild(anchor)
    anchor.click()
    anchor.remove()
    URL.revokeObjectURL(url)
  }
  async function handleDownloadTemplate() {
    templateDownloading.value = true
    try {
      const response = await fetchDownloadDeliveryTemplate(
        {},
        {
          headers: {
            Authorization: userStore.accessToken || ''
          }
        }
      )
      await downloadFile(response, 'delivery-template.xlsx')
      ElMessage.success(t('pages.orders.delivery.messages.templateDownloadSuccess'))
    } catch (error) {
      ElMessage.error(error?.message || t('pages.orders.delivery.messages.templateDownloadFailed'))
    } finally {
      templateDownloading.value = false
    }
  }
  const resolvePrintRecords = async (payload) => {
    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
      return defaultResponseAdapter(await fetchGetDeliveryMany(payload.ids)).records
@@ -346,7 +583,8 @@
      await fetchDeliveryPage({
        ...reportQueryParams.value,
        current: 1,
        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
        pageSize:
          Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
      })
    ).records
  }