| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- <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"
- />
- <el-button
- class="btn-main"
- type="primary"
- :disabled="
- selectedRows.length !== 1 ||
- !selectedRows[0]?.canApprove ||
- !selectedRows[0]?.currentApprovalId
- "
- @click="handleToolbarApprove"
- >
- <el-icon><CircleCheck /></el-icon>通过
- </el-button>
- <el-button
- class="btn-ghost"
- type="danger"
- :disabled="
- selectedRows.length !== 1 ||
- !selectedRows[0]?.canApprove ||
- !selectedRows[0]?.currentApprovalId
- "
- @click="handleToolbarReject"
- >
- <el-icon><CircleClose /></el-icon>驳回
- </el-button>
- <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="pendingTableWrapRef" class="table-scroll bx-list-table-wrap">
- <ListTable table-key="pl-freight-components-approval-pending-panel"
- class="list-table"
- :data="settlementList"
- v-loading="loading"
- stripe
- row-key="id"
- @selection-change="handleSelectionChange"
- @row-dblclick="handleView"
- @header-dragend="() => pendingStatsRowRef?.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('planCount')"
- prop="planCount"
- label="关联计划数"
- min-width="110"
- align="center"
- />
- <el-table-column
- v-if="isColumnVisible('licensePlateSummary')"
- prop="licensePlateSummary"
- label="车牌摘要"
- min-width="140"
- show-overflow-tooltip
- />
- <el-table-column
- v-if="isColumnVisible('currentApproverName')"
- prop="currentApproverName"
- label="当前审批人"
- min-width="110"
- align="center"
- >
- <template #default="{ row }">{{ row.currentApproverName || '—' }}</template>
- </el-table-column>
- <el-table-column
- v-if="isColumnVisible('submitterName')"
- prop="submitterName"
- label="提交人"
- min-width="100"
- align="center"
- >
- <template #default="{ row }">{{ row.submitterName || '—' }}</template>
- </el-table-column>
- <el-table-column
- v-if="isColumnVisible('createTime')"
- prop="createTime"
- label="提交时间"
- min-width="160"
- align="center"
- >
- <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
- </el-table-column>
- </ListTable>
- <ListColumnStatsRow
- ref="pendingStatsRowRef"
- storage-key="pl-freight-approval-pending-column-stats"
- :columns="pendingStatsRowColumns"
- :data="settlementList"
- :table-wrap-el="pendingTableWrapRef"
- :get-cell-value="getPendingStatCellValue"
- />
- </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">
- <template v-if="viewingRow" #hero-actions>
- <el-button
- v-if="viewingRow.canApprove && viewingRow.currentApprovalId"
- class="btn-main"
- type="primary"
- @click="handleApprove(viewingRow)"
- >
- <el-icon><CircleCheck /></el-icon>通过
- </el-button>
- <el-button
- v-if="viewingRow.canApprove && viewingRow.currentApprovalId"
- class="btn-ghost"
- type="danger"
- @click="handleReject(viewingRow)"
- >
- <el-icon><CircleClose /></el-icon>驳回
- </el-button>
- </template>
- </PlFreightSettlementViewDialog>
- <PlFreightApprovalDialog ref="approvalDialogRef" @success="handleMutationSuccess" />
- </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 { CircleCheck, CircleClose, RefreshLeft, Search } from '@element-plus/icons-vue'
- import { freightApi, type FreightSettlement } from '@/api/pl'
- import PlFreightApprovalDialog from '@/components/business/pl-freight-approval-dialog/index.vue'
- import PlFreightSettlementViewDialog from '@/components/business/pl-freight-settlement-view-dialog/index.vue'
- import { formatMoneyDisplay } from '@/utils/money-format'
- import { formatDateTime } from '@/utils/format'
- const listColumnDefs = [
- { prop: 'totalFreight', label: '运费合计(元)' },
- { prop: 'planCount', label: '关联计划数' },
- { prop: 'licensePlateSummary', label: '车牌摘要' },
- { prop: 'currentApproverName', label: '当前审批人' },
- { prop: 'submitterName', label: '提交人' },
- { prop: 'createTime', label: '提交时间' }
- ]
- const {
- visibleColumns,
- checkAll,
- isIndeterminate,
- isColumnVisible,
- resetToDefault
- } = usePersistedListColumns('pl-freight-approval-pending-columns', listColumnDefs)
- const pendingTableWrapRef = ref<HTMLElement | null>(null)
- const pendingStatsRowRef = ref<{ resync?: () => void } | null>(null)
- const pendingStatsRowColumns = 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'
- })
- }
- return cols
- })
- function getPendingStatCellValue(row: FreightSettlement, prop: string): unknown {
- if (prop === 'currentApproverName') return row.currentApproverName || ''
- if (prop === 'submitterName') return row.submitterName || ''
- if (prop === 'createTime') {
- return row.createTime ? formatDateTime(row.createTime) : ''
- }
- return (row as any)?.[prop]
- }
- const loading = ref(false)
- const settlementList = ref<FreightSettlement[]>([])
- const selectedRows = ref<FreightSettlement[]>([])
- function getSettlementExportFilterParams() {
- return {
- tab: 'approval_pending',
- q: quickSearchKeyword.value.trim() || searchForm.q || undefined,
- }
- }
- const settlementExportColumns = computed(() => {
- const cols = [...visibleColumns.value]
- if (!cols.includes('settlementNo')) cols.unshift('settlementNo')
- return cols
- })
- const viewingRow = ref<FreightSettlement | null>(null)
- const viewDialogRef = ref<InstanceType<typeof PlFreightSettlementViewDialog>>()
- const approvalDialogRef = ref<InstanceType<typeof PlFreightApprovalDialog>>()
- const quickSearchKeyword = ref('')
- const searchForm = reactive({ q: '' })
- const pagination = reactive({
- page: 1,
- pageSize: 20,
- total: 0
- })
- function formatAmount(amount: number) {
- if (!amount) return '¥0.00'
- return formatMoneyDisplay(amount)
- }
- function handleSelectionChange(selection: FreightSettlement[]) {
- selectedRows.value = selection
- }
- async function fetchData() {
- loading.value = true
- try {
- const res = await freightApi.list({
- tab: 'approval_pending',
- 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) {
- viewingRow.value = row
- viewDialogRef.value?.open(row.id)
- }
- function handleApprove(row: FreightSettlement) {
- if (!row.currentApprovalId) return
- approvalDialogRef.value?.openApprove(row.id, row.currentApprovalId)
- }
- function handleReject(row: FreightSettlement) {
- if (!row.currentApprovalId) return
- approvalDialogRef.value?.openReject(row.id, row.currentApprovalId)
- }
- function syncViewingRowFromList() {
- if (!viewingRow.value?.id) return
- const updated = settlementList.value.find(row => row.id === viewingRow.value!.id)
- if (updated) viewingRow.value = updated
- }
- async function handleMutationSuccess() {
- await fetchData()
- syncViewingRowFromList()
- await viewDialogRef.value?.reload()
- }
- function handleToolbarApprove() {
- if (selectedRows.value.length !== 1) return
- handleApprove(selectedRows.value[0])
- }
- function handleToolbarReject() {
- if (selectedRows.value.length !== 1) return
- handleReject(selectedRows.value[0])
- }
- 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>
|