2026-08-11 18:43:56 +08:00
< script setup lang = "ts" >
2026-08-11 19:59:15 +08:00
import { computed , onMounted , ref } from 'vue'
import { Eye } from '@lucide/vue'
2026-08-11 18:43:56 +08:00
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants , type PlatformTenantResource } from '@/modules/iam'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable , { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
2026-08-11 19:59:15 +08:00
import { listFinancePayableSettlements , listFinanceReceivableSettlements } from '../api/finance'
2026-08-11 19:35:28 +08:00
import { financeStatusLabel , financeStatusTone } from '../status'
2026-08-11 18:43:56 +08:00
import type { FinancePayableSettlement , FinanceReceivableSettlement } from '../types'
const props = defineProps < { kind : 'receivable' | 'payable' } > ()
const access = useAccessPreviewStore ()
const customers = ref < PlatformTenantResource [] >([])
2026-08-11 19:59:15 +08:00
const filters = ref < QueryModel >({ customerId : '' })
2026-08-11 18:43:56 +08:00
const rows = ref < Array < FinanceReceivableSettlement | FinancePayableSettlement > > ([])
const loading = ref ( false )
const error = ref < ApiErrorDetails | null >( null )
const customerError = ref < ApiErrorDetails | null >( null )
const drawerOpen = ref ( false )
const selected = ref < FinanceReceivableSettlement | FinancePayableSettlement | null >( null )
const viewPermission = computed (() => props . kind === 'receivable' ? 'platform:finance:settlement-ar:view' : 'platform:finance:settlement-ap:view' )
const canView = computed (() => access . can ( viewPermission . value ))
const title = computed (() => props . kind === 'receivable' ? '结算单应收' : '结算单应付' )
2026-08-11 19:35:28 +08:00
const activeCustomers = computed (() => customers . value . filter (( item ) => item . status === 'ACTIVE' ))
const fields = computed < QueryField [] >(() => [{ key : 'customerId' , type : 'select' , label : '客户' , width : 280 , placeholder : '请选择客户' , props : { filterable : true , loading : ! customers . value . length && ! customerError . value }, options : activeCustomers . value . map (( item ) => ({ label : item . tenantName , value : String ( item . id ) })) }])
2026-08-11 18:43:56 +08:00
const columns = computed < DataTableColumn [] >(() => props . kind === 'receivable'
2026-08-11 19:35:28 +08:00
? [{ key : 'actions' , label : '操作' , width : 100 , fixed : 'left' , slotName : 'actions' }, { key : 'customerId' , label : '客户' , minWidth : 160 , slotName : 'customer' }, { key : 'settlementNo' , label : '应收结算单号' , minWidth : 180 }, { key : 'receivableAmount' , label : '应收(CNY)' , width : 140 }, { key : 'receivedAmount' , label : '已收(CNY)' , width : 140 }, { key : 'outstandingAmount' , label : '未收(CNY)' , width : 140 }, { key : 'status' , label : '状态' , width : 120 , slotName : 'status' }]
: [{ key : 'actions' , label : '操作' , width : 100 , fixed : 'left' , slotName : 'actions' }, { key : 'customerId' , label : '客户' , minWidth : 160 , slotName : 'customer' }, { key : 'settlementNo' , label : '应付结算单号' , minWidth : 180 }, { key : 'feeItemName' , label : '费用项目' , minWidth : 160 }, { key : 'payableAmount' , label : '应付(CNY)' , width : 140 }, { key : 'paidAmount' , label : '已付(CNY)' , width : 140 }, { key : 'outstandingAmount' , label : '未付(CNY)' , width : 140 }, { key : 'status' , label : '状态' , width : 120 , slotName : 'status' }])
2026-08-11 19:59:15 +08:00
async function loadCustomers () { customerError . value = null ; try { const result = await listPlatformTenants ({ page : 1 , size : 200 }); customers . value = result . records } catch ( e ) { customerError . value = getApiErrorDetails ( e , '客户列表加载失败' ) } }
async function load ( model : QueryModel = filters . value ) { filters . value = { ... model }; rows . value = []; error . value = null ; if ( ! filters . value . customerId ) return ; loading . value = true ; try { rows . value = props . kind === 'receivable' ? await listFinanceReceivableSettlements ( String ( filters . value . customerId )) : await listFinancePayableSettlements ( String ( filters . value . customerId )) } catch ( e ) { error . value = getApiErrorDetails ( e , ` ${ title . value } 加载失败,请重试` ) } finally { loading . value = false } }
2026-08-11 18:43:56 +08:00
function openDetail ( row : FinanceReceivableSettlement | FinancePayableSettlement ) { selected . value = row ; drawerOpen . value = true }
function totalAmount ( row : FinanceReceivableSettlement | FinancePayableSettlement ) { return props . kind === 'receivable' ? ( row as FinanceReceivableSettlement ). receivableAmount : ( row as FinancePayableSettlement ). payableAmount }
2026-08-11 19:35:28 +08:00
function customerName ( customerId : string ) { return customers . value . find (( item ) => String ( item . id ) === String ( customerId )) ? . tenantName || '客户信息暂不可用' }
2026-08-11 18:43:56 +08:00
onMounted (() => void loadCustomers ())
</ script >
< template >
< AdminPage as = "div" class = "finance-settlement-page" spacing = "normal" >
< el-result v-if = "!canView" icon="warning" :title="`无权访问${title}`" sub-title="请联系管理员开通结算单查看权限" />
< ListPageLayout v-else :aria-label = "title" >
< template # toolbar >< QueryToolbar v-model = "filters" class="surface-panel" :ariaLabel="`${title}筛选`" :fields="fields" preference-key="finance-settlement-query" @search="load" /></ template >
< template v-if = "customerError || error" #state><LoadErrorState :title="`${title}加载失败`" :message="(customerError || error)?.message || ''" :loading="loading" @retry="customerError ? loadCustomers() : load()"><template #details><ApiErrorMeta :details="(customerError || error)!" /></ template ></ LoadErrorState ></ template >
< section v-if = "!customerError && !error" class="surface-panel table-panel" >
< DataTable :data = "rows" :columns = "columns" :loading = "loading" :aria-label = "`${title}列表`" row -key =" id " empty -text =" 请选择客户查询结算单 " >
< template # actions = "{ row }" >< el-button link type = "primary" @click ="openDetail(row as unknown as FinanceReceivableSettlement | FinancePayableSettlement)" >< Eye :size = "15" /> 详情 </ el-button ></ template >
2026-08-11 19:35:28 +08:00
< template # customer = "{ row }" >{{ customerName ( String ( row . customerId )) }}</ template >
< template # status = "{ row }" >< el-tag :type = "financeStatusTone(row.status)" >{{ financeStatusLabel ( row . status ) }}</ el-tag ></ template >
2026-08-11 19:59:15 +08:00
< template # pagination -left > 币种 : CNY </ template >
2026-08-11 18:43:56 +08:00
</ DataTable >
</ section >
</ ListPageLayout >
< AppDrawer v-model = "drawerOpen" :title="`${title}详情`" size="min(680px, 94vw)" >
2026-08-11 19:35:28 +08:00
< el-descriptions v-if = "selected" :column="2" border><el-descriptions-item label="结算单号">{{ selected.settlementNo }}</el-descriptions-item><el-descriptions-item label="客户">{{ customerName(selected.customerId) }}</el-descriptions-item><el-descriptions-item :label="props.kind === 'receivable' ? '应收金额' : '应付金额'">{{ totalAmount(selected) }} CNY</el-descriptions-item><el-descriptions-item :label="props.kind === 'receivable' ? '未收金额' : '未付金额'">{{ selected.outstandingAmount }} CNY</el-descriptions-item><el-descriptions-item label="状态" >{{ financeStatusLabel ( selected.status ) }} </ el -descriptions-item ></ el-descriptions >
2026-08-11 18:43:56 +08:00
</ AppDrawer >
</ AdminPage >
</ template >
2026-08-11 19:59:15 +08:00
< style scoped >. finance - settlement - page { min - width : 0 } @ media ( max - width : 720 px ){ : deep (. el - drawer__footer . el - button ){ min - height : 44 px }}</ style >