Files
shanxia2.0/web-admin/src/views/pl/freight/components/SettledPanel.vue
T
zlwuandCursor b1f734dbb0 feat(pl): add Excel export for freight unsettled plans and settlement tabs
Support filtered and selected export across freight settlement board panels.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 18:17:10 +08:00

332 lines
9.9 KiB
Vue

<template>
<div class="freight-tab-panel">
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="结算单号">
<el-input
v-model="searchForm.q"
placeholder="搜索结算单号"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
</div>
<div class="filter-actions">
<div class="filter-actions-end">
<el-button class="btn-ghost" :loading="loading" @click="handleReset">
<el-icon><RefreshLeft /></el-icon>重置
</el-button>
<el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch">
<el-icon><Search /></el-icon>搜索
</el-button>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<ListQuickSearchInput
v-model="quickSearchKeyword"
placeholder="结算单号"
@search="handleQuickSearch"
@clear="handleQuickSearchClear"
/>
<ListExportDropdown
:export-fn="freightApi.export"
:get-filter-params="getSettlementExportFilterParams"
:has-selection="true"
:selected-ids="selectedRows.map((r) => r.id)"
:visible-columns="settlementExportColumns"
filename="已结算.xlsx"
/>
<ListToolbarEndActions
:loading="loading"
:column-defs="listColumnDefs"
v-model:visible-columns="visibleColumns"
v-model:check-all="checkAll"
:is-indeterminate="isIndeterminate"
@refresh="fetchData"
@reset-columns="resetToDefault"
/>
</div>
</div>
<div class="list-body list-wrap">
<div ref="settledTableWrapRef" class="table-scroll bx-list-table-wrap">
<ListTable table-key="pl-freight-components-settled-panel"
class="list-table"
:data="settlementList"
v-loading="loading"
stripe
row-key="id"
@row-dblclick="handleView"
@selection-change="handleSelectionChange"
@header-dragend="() => settledStatsRowRef?.resync?.()"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
/>
<el-table-column prop="settlementNo" label="结算单号" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<el-link type="primary" @click="handleView(row)">{{ row.settlementNo }}</el-link>
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('totalFreight')"
prop="totalFreight"
label="运费合计(元)"
min-width="130"
align="right"
>
<template #default="{ row }">{{ formatAmount(row.totalFreight) }}</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('disburseAmount')"
prop="disburseAmount"
label="发放金额(元)"
min-width="130"
align="right"
>
<template #default="{ row }">{{
row.disburseAmount ? formatAmount(row.disburseAmount) : '—'
}}</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('disburseTime')"
prop="disburseTime"
label="发放时间"
min-width="160"
align="center"
>
<template #default="{ row }">{{
row.disburseTime ? formatDateTime(row.disburseTime) : '—'
}}</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('settlementStatus')"
prop="settlementStatus"
label="结算状态"
min-width="100"
align="center"
>
<template #default="{ row }">
<el-tag :type="getSettlementStatusTagType(row.settlementStatus)" size="small">
{{ getSettlementStatusLabel(row.settlementStatus) }}
</el-tag>
</template>
</el-table-column>
</ListTable>
<ListColumnStatsRow
ref="settledStatsRowRef"
storage-key="pl-freight-settled-column-stats"
:columns="settledStatsRowColumns"
:data="settlementList"
:table-wrap-el="settledTableWrapRef"
:get-cell-value="getSettledStatCellValue"
/>
</div>
<div class="pagination list-pager-bar">
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 100, 500]"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
<PlFreightSettlementViewDialog ref="viewDialogRef" />
</div>
</template>
<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 { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { RefreshLeft, Search } from '@element-plus/icons-vue'
import { freightApi, type FreightSettlement } from '@/api/pl'
import PlFreightSettlementViewDialog from '@/components/business/pl-freight-settlement-view-dialog/index.vue'
import {
getSettlementStatusLabel,
getSettlementStatusTagType
} from '@/views/pl/freight/freight-status'
import { formatMoneyDisplay } from '@/utils/money-format'
import { formatDateTime } from '@/utils/format'
const listColumnDefs = [
{ prop: 'totalFreight', label: '运费合计(元)' },
{ prop: 'disburseAmount', label: '发放金额(元)' },
{ prop: 'disburseTime', label: '发放时间' },
{ prop: 'settlementStatus', label: '结算状态' }
]
const {
visibleColumns,
checkAll,
isIndeterminate,
isColumnVisible,
resetToDefault
} = usePersistedListColumns('pl-freight-settled-columns', listColumnDefs)
const settledTableWrapRef = ref<HTMLElement | null>(null)
const settledStatsRowRef = ref<{ resync?: () => void } | null>(null)
const settledStatsRowColumns = computed((): StatsRowColumn[] => {
const cols: StatsRowColumn[] = [
{ key: '__selection', interactive: false },
{ key: '__index', interactive: false },
{ key: 'settlementNo', interactive: true, prop: 'settlementNo' }
]
for (const def of listColumnDefs) {
if (!isColumnVisible(def.prop)) continue
cols.push({
key: def.prop,
interactive: true,
prop: def.prop,
money: def.prop === 'totalFreight' || def.prop === 'disburseAmount'
})
}
return cols
})
function getSettledStatCellValue(row: FreightSettlement, prop: string): unknown {
if (prop === 'disburseAmount') return row.disburseAmount || 0
if (prop === 'disburseTime') {
return row.disburseTime ? formatDateTime(row.disburseTime) : ''
}
if (prop === 'settlementStatus') return getSettlementStatusLabel(row.settlementStatus)
return (row as any)?.[prop]
}
const loading = ref(false)
const selectedRows = ref<FreightSettlement[]>([])
function getSettlementExportFilterParams() {
return {
tab: 'settled',
q: quickSearchKeyword.value.trim() || searchForm.q || undefined,
}
}
const settlementExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('settlementNo')) cols.unshift('settlementNo')
return cols
})
const settlementList = ref<FreightSettlement[]>([])
const viewDialogRef = ref<InstanceType<typeof PlFreightSettlementViewDialog>>()
const quickSearchKeyword = ref('')
const searchForm = reactive({ q: '' })
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
function handleSelectionChange(rows: FreightSettlement[]) {
selectedRows.value = rows
}
function formatAmount(amount: number) {
if (!amount) return '¥0.00'
return formatMoneyDisplay(amount)
}
async function fetchData() {
loading.value = true
try {
const res = await freightApi.list({
tab: 'settled',
q: quickSearchKeyword.value.trim() || searchForm.q || undefined,
page: pagination.page,
pageSize: pagination.pageSize
})
const data = res.data ?? res
settlementList.value = data.list || []
pagination.total = data.total || 0
} finally {
loading.value = false
}
}
function handleSearch() {
quickSearchKeyword.value = ''
pagination.page = 1
fetchData()
}
function handleReset() {
searchForm.q = ''
quickSearchKeyword.value = ''
pagination.page = 1
fetchData()
}
function handleQuickSearch() {
pagination.page = 1
fetchData()
}
function handleQuickSearchClear() {
quickSearchKeyword.value = ''
pagination.page = 1
fetchData()
}
function handlePageChange() {
fetchData()
}
function handleSizeChange() {
pagination.page = 1
fetchData()
}
function handleView(row: FreightSettlement) {
viewDialogRef.value?.open(row.id)
}
function refresh() {
fetchData()
}
useDbInstantSearch(() => ({ q: searchForm.q }), handleSearch)
onMounted(() => {
fetchData()
})
defineExpose({ refresh })
</script>
<style scoped lang="scss">
.bx-list-table-wrap {
min-width: 0;
}
</style>