feat(pl): add Excel export for plan and transport lists

Support filtered/selected export on plan, archive, cert and transport pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-21 17:59:04 +08:00
1 parent b06fb6ac56
commit 6a89452f1a
7 files changed
+339 -5

No files matched your search

+6 -2
View File
@@ -138,9 +138,9 @@ export interface VehicleQuery {
export const vehicleApi = {
list: (params?: VehicleQuery) =>
request.get<PlListResponse<Vehicle>>('/transport/vehicle', { params }),
export: (data: Record<string, any>) =>
request.post('/transport/vehicle/export', data, { responseType: 'blob' }),
request.get<PlListResponse<Vehicle>>('/transport/vehicle', { params }),
get: (id: string) => request.get<VehicleDetail>(`/transport/vehicle/${id}`),
create: (data: VehiclePayload) => request.post<VehicleDetail>('/transport/vehicle', data),
update: (id: string, data: Partial<VehiclePayload>) =>
@@ -194,9 +194,9 @@ export interface PersonnelQuery {
export const personnelApi = {
list: (params?: PersonnelQuery) =>
request.get<PlListResponse<Personnel>>('/transport/personnel', { params }),
export: (data: Record<string, any>) =>
request.post('/transport/personnel/export', data, { responseType: 'blob' }),
request.get<PlListResponse<Personnel>>('/transport/personnel', { params }),
get: (id: string) => request.get<PersonnelDetail>(`/transport/personnel/${id}`),
create: (data: PersonnelPayload) => request.post<PersonnelDetail>('/transport/personnel', data),
update: (id: string, data: Partial<PersonnelPayload>) =>
@@ -696,6 +696,8 @@ export interface PlanContractGapQuery {
export const planApi = {
list: (params?: PlanQuery) => request.get<PlanListResponse>('/transport/plan/', { params }),
export: (data: Record<string, any>) =>
request.post('/transport/plan/export', data, { responseType: 'blob' }),
get: (id: string) => request.get<Plan>(`/transport/plan/${id}`),
countByContract: (contractIds: string[]) =>
request.get<{ counts: Record<string, number> }>('/transport/plan/count-by-contract', {
@@ -1002,6 +1004,8 @@ export interface TransportListResponse {
export const transportApi = {
list: (params?: TransportQuery) => request.get<TransportListResponse>('/transport/order/', { params }),
export: (data: Record<string, any>) =>
request.post('/transport/order/export', data, { responseType: 'blob' }),
get: (id: string) => request.get<Transport>(`/transport/order/${id}`),
create: (data: Partial<Transport> & { planIds?: string[] }) =>
request.post<Transport>('/transport/order/', data),
@@ -95,6 +95,15 @@
<el-icon><FolderChecked /></el-icon>
{{ selectedPlan ? archiveButtonLabel(selectedPlan) : '归档' }}
</el-button>
<ListExportDropdown
:export-fn="planApi.export"
:get-filter-params="getPlanArchiveExportFilterParams"
:has-selection="true"
:selected-ids="selectedRows.map((r) => r.id)"
:visible-columns="planArchiveExportColumns"
filename="计划归档.xlsx"
/>
<ListToolbarEndActions
:loading="loading"
:column-defs="listColumnDefs"
@@ -351,6 +360,7 @@
<script setup lang="ts">
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
import type { StatsRowColumn } from '@/utils/list-column-stats'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
@@ -478,6 +488,28 @@ function getArchiveStatCellValue(row: Plan, prop: string): unknown {
}
const selectedRows = ref<Plan[]>([])
function getPlanArchiveExportFilterParams() {
const keyword = quickSearch.value.trim()
return {
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: searchForm.planType || undefined,
followStatus: searchForm.followStatus || undefined,
transportStatus: 'arranged',
customerName: keyword ? undefined : searchForm.customerName || undefined,
productName: keyword ? undefined : searchForm.productName || undefined,
archiveStatus: searchForm.archiveStatus || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined
}
}
const planArchiveExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('planNo')) cols.unshift('planNo')
return cols
})
const viewingPlan = ref<Plan | null>(null)
function syncViewingPlanFromList() {
@@ -135,6 +135,15 @@
<el-icon><CircleClose /></el-icon>取消齐备
</el-button>
<ListExportDropdown
:export-fn="planApi.export"
:get-filter-params="getPlanCertExportFilterParams"
:has-selection="true"
:selected-ids="selectedRows.map((r) => r.id)"
:visible-columns="planCertExportColumns"
filename="计划证件.xlsx"
/>
<ListToolbarEndActions
:loading="loading"
:column-defs="listColumnDefs"
@@ -413,6 +422,7 @@ import PlPlanListCertCell from '@/components/business/pl-plan-list-cert-cell/ind
import PlPlanListProductCell from '@/components/business/pl-plan-list-product-cell/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import {
buildHazardCertReadyConfirmMessage,
isHazardCertReady,
@@ -477,6 +487,27 @@ const {
} = usePersistedListColumns('pl-plan-cert-list-columns', listColumnDefs)
const selectedRows = ref<Plan[]>([])
function getPlanCertExportFilterParams() {
const keyword = quickSearch.value.trim()
return {
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: searchForm.planType || undefined,
followStatus: searchForm.followStatus || undefined,
customerName: keyword ? undefined : searchForm.customerName || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined,
hasHazardProduct: 1,
certUploadStatus: searchForm.certUploadStatus || undefined
}
}
const planCertExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('planNo')) cols.unshift('planNo')
return cols
})
const viewingPlan = ref<Plan | null>(null)
const selectedPlan = computed(() => (selectedRows.value.length === 1 ? selectedRows.value[0] : null))
const dateRange = ref<string[]>([])
+31
View File
@@ -183,6 +183,15 @@
<el-icon><EditPen /></el-icon>强制改状态
</el-button>
<ListExportDropdown
:export-fn="planApi.export"
:get-filter-params="getPlanExportFilterParams"
:has-selection="true"
:selected-ids="selectedRows.map((r) => r.id)"
:visible-columns="planExportColumns"
filename="计划列表.xlsx"
/>
<ListToolbarEndActions
:loading="loading"
:column-defs="listColumnDefs"
@@ -626,6 +635,7 @@ import PlPasteRecognizeDialog from '@/components/business/pl-paste-recognize-dia
import PlPlanFormDialog from '@/components/business/pl-plan-form-dialog/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
import type { StatsRowColumn } from '@/utils/list-column-stats'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
@@ -761,6 +771,27 @@ function getPlanStatCellValue(row: Plan, prop: string): unknown {
}
const selectedRows = ref<Plan[]>([])
function getPlanExportFilterParams() {
const keyword = quickSearch.value.trim()
return {
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: tabFilters.planType || undefined,
followStatus: tabFilters.followStatus || searchForm.followStatus || undefined,
customerName: keyword ? undefined : searchForm.customerName || undefined,
productName: keyword ? undefined : searchForm.productName || undefined,
licensePlate: keyword ? undefined : searchForm.licensePlate || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined
}
}
const planExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('planNo')) cols.unshift('planNo')
return cols
})
const viewingPlan = ref<Plan | null>(null)
function syncViewingPlanFromList() {
@@ -62,6 +62,15 @@
<el-icon><Share /></el-icon>二维码
</el-button>
<ListExportDropdown
:export-fn="transportApi.export"
:get-filter-params="getTransportExportFilterParams"
:has-selection="true"
:selected-ids="selectedRows.map((r) => r.id)"
:visible-columns="transportExportColumns"
filename="运输列表.xlsx"
/>
<ListToolbarEndActions
:loading="loading"
:column-defs="listColumnDefs"
@@ -250,6 +259,7 @@ import SystemViewDialog, {
import { transportApi, type Transport } from '@/api/pl'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { formatPlVehiclePlateDisplay } from '@/utils/pl-display'
@@ -283,6 +293,22 @@ const detailLoading = ref(false)
const qrcodeLoading = ref(false)
const transportList = ref<Transport[]>([])
const selectedRows = ref<Transport[]>([])
function getTransportExportFilterParams() {
const keyword = quickSearchKeyword.value.trim()
return {
q: keyword || undefined,
transportNo: keyword ? undefined : searchForm.transportNo || undefined,
transportStatus: searchForm.transportStatus || undefined,
approvalStatus: searchForm.approvalStatus || undefined
}
}
const transportExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('transportNo')) cols.unshift('transportNo')
return cols
})
const detailVisible = ref(false)
const currentTransport = ref<Transport | null>(null)