67 lines
7.8 KiB
Vue
67 lines
7.8 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { Eye, Plus } from '@lucide/vue'
|
||
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'
|
||
import { financePhase1Capabilities, listFinancePayableSettlements, listFinanceReceivableSettlements } from '../api/finance'
|
||
import { financeStatusLabel, financeStatusTone } from '../status'
|
||
import type { FinancePayableSettlement, FinanceReceivableSettlement } from '../types'
|
||
|
||
const props = defineProps<{ kind: 'receivable' | 'payable' }>()
|
||
const access = useAccessPreviewStore()
|
||
const customers = ref<PlatformTenantResource[]>([])
|
||
const filters = reactive<QueryModel>({ customerId: '' })
|
||
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' ? '结算单应收' : '结算单应付')
|
||
const blocked = computed(() => props.kind === 'receivable' ? !financePhase1Capabilities.receivableSettlementCreate : !financePhase1Capabilities.payableSettlementCreate)
|
||
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) })) }])
|
||
const columns = computed<DataTableColumn[]>(() => props.kind === 'receivable'
|
||
? [{ 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' }])
|
||
async function loadCustomers() { 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) { Object.assign(filters, model); rows.value = []; error.value = null; if (!filters.customerId) return; loading.value = true; try { rows.value = props.kind === 'receivable' ? await listFinanceReceivableSettlements(String(filters.customerId)) : await listFinancePayableSettlements(String(filters.customerId)) } catch (e) { error.value = getApiErrorDetails(e, `${title.value}加载失败,请重试`) } finally { loading.value = false } }
|
||
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 }
|
||
function customerName(customerId: string) { return customers.value.find((item) => String(item.id) === String(customerId))?.tenantName || '客户信息暂不可用' }
|
||
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 #toolbar-actions><el-button type="primary" disabled :title="blocked ? '接口尚未提供,暂不可操作' : ''"><Plus :size="16" />生成{{ title }}</el-button></template>
|
||
<template #actions="{ row }"><el-button link type="primary" @click="openDetail(row as unknown as FinanceReceivableSettlement | FinancePayableSettlement)"><Eye :size="15" />详情</el-button></template>
|
||
<template #customer="{ row }">{{ customerName(String(row.customerId)) }}</template>
|
||
<template #status="{ row }"><el-tag :type="financeStatusTone(row.status)">{{ financeStatusLabel(row.status) }}</el-tag></template>
|
||
<template #pagination-left>币种:CNY · 生成接口尚未提供时仅展示真实查询结果</template>
|
||
</DataTable>
|
||
</section>
|
||
</ListPageLayout>
|
||
<AppDrawer v-model="drawerOpen" :title="`${title}详情`" size="min(680px, 94vw)">
|
||
<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>
|
||
<el-alert class="blocked-alert" type="info" :closable="false" title="结算单详情/收付款接口尚未提供,当前不显示不可执行的提交按钮。" />
|
||
</AppDrawer>
|
||
</AdminPage>
|
||
</template>
|
||
<style scoped>.finance-settlement-page{min-width:0}.blocked-alert{margin-top:16px}@media(max-width:720px){:deep(.el-drawer__footer .el-button){min-height:44px}}</style>
|