2026-08-12 16:31:46 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
|
|
|
|
import { Check, MoreHorizontal, Pencil, Plus, RefreshCw, Trash2, Undo2, X } from '@lucide/vue'
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
import {
|
|
|
|
|
adjustInboundFeeDetail, createInboundFeeDetail, deleteInboundFeeDetail,
|
|
|
|
|
listInboundFeeDetails, listInboundFeeItemOptions, listInboundFeePartnerOptions,
|
|
|
|
|
reviewInboundFeeDetail, updateInboundFeeDetail, voidInboundFeeDetail,
|
|
|
|
|
} from '@/modules/unified-orders/api/inboundFeeDetails'
|
|
|
|
|
import type {
|
|
|
|
|
InboundFeeDetail, InboundFeeItemOption,
|
|
|
|
|
InboundFeePartnerOption, SaveInboundFeeDetailRequest,
|
|
|
|
|
} from '@/modules/unified-orders/api/inboundFeeDetails'
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ accountId: string; inboundId: string }>()
|
2026-08-17 19:13:25 +08:00
|
|
|
interface OtherFeeSummary { currency: string; approvedReceivable: number; approvedPayable: number }
|
|
|
|
|
const emit = defineEmits<{ changed: []; summary: [value: OtherFeeSummary] }>()
|
2026-08-12 16:31:46 +08:00
|
|
|
const access = useAccessPreviewStore()
|
|
|
|
|
const rows = ref<InboundFeeDetail[]>([])
|
|
|
|
|
const feeItems = ref<InboundFeeItemOption[]>([])
|
|
|
|
|
const partners = ref<InboundFeePartnerOption[]>([])
|
2026-08-12 16:40:09 +08:00
|
|
|
const feeItemsLoading = ref(false)
|
|
|
|
|
const partnersLoading = ref(false)
|
|
|
|
|
const feeItemsError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const partnersError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const partnersLoadAttempted = ref(false)
|
2026-08-12 16:31:46 +08:00
|
|
|
const loading = ref(false)
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
const loadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const actionError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const editorOpen = ref(false)
|
|
|
|
|
const editorMode = ref<'create' | 'edit' | 'adjust'>('create')
|
|
|
|
|
const target = ref<InboundFeeDetail | null>(null)
|
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
|
const rejectOpen = ref(false)
|
|
|
|
|
const rejectReason = ref('')
|
|
|
|
|
|
|
|
|
|
const emptyForm = (): SaveInboundFeeDetailRequest => ({
|
|
|
|
|
feeItemId: '', direction: 'RECEIVABLE', businessDate: new Date().toISOString().slice(0, 10),
|
|
|
|
|
unitPrice: 0, currency: 'CNY', amount: 0, settlementIncluded: true,
|
|
|
|
|
partnerId: null, partnerName: null, remark: null,
|
|
|
|
|
})
|
|
|
|
|
const form = reactive<SaveInboundFeeDetailRequest>(emptyForm())
|
|
|
|
|
const rules: FormRules = {
|
|
|
|
|
feeItemId: [{ required: true, message: '请选择费用项目', trigger: 'change' }],
|
|
|
|
|
businessDate: [{ required: true, message: '请选择业务发生日', trigger: 'change' }],
|
|
|
|
|
unitPrice: [{ required: true, type: 'number', min: 0, message: '单价不能小于 0', trigger: 'change' }],
|
|
|
|
|
amount: [{ required: true, type: 'number', min: 0, message: '金额不能小于 0', trigger: 'change' }],
|
|
|
|
|
currency: [{ required: true, pattern: /^[A-Z]{3}$/, message: '货币须为三个大写字母', trigger: 'blur' }],
|
|
|
|
|
}
|
|
|
|
|
const canView = computed(() => access.can('firstleg:inbound:fee-detail:view'))
|
2026-08-12 16:40:09 +08:00
|
|
|
const canViewFeeItems = computed(() => access.can('firstleg:fee-item:view'))
|
|
|
|
|
const canViewPartners = computed(() => access.can('platform:counterparty:list'))
|
|
|
|
|
const directionOptions = computed(() => [
|
|
|
|
|
{ label: '应收', value: 'RECEIVABLE' },
|
|
|
|
|
{ label: '应付', value: 'PAYABLE', disabled: !canViewPartners.value },
|
|
|
|
|
])
|
2026-08-12 16:31:46 +08:00
|
|
|
const applicableItems = computed(() => feeItems.value.filter((item) => form.direction === 'RECEIVABLE' ? item.warehouseReceivable : item.warehousePayable))
|
|
|
|
|
|
2026-08-17 19:13:25 +08:00
|
|
|
function isManual(row: InboundFeeDetail) { return row.sourceType === 'MANUAL' }
|
|
|
|
|
function canEdit(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:edit') && row.reviewStatus === 'PENDING' }
|
|
|
|
|
function canDelete(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:delete') && row.reviewStatus === 'PENDING' }
|
|
|
|
|
function canReview(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:review') && row.reviewStatus === 'PENDING' }
|
|
|
|
|
function canAdjust(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:edit') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
|
|
|
|
function canVoid(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:review') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
2026-08-12 16:31:46 +08:00
|
|
|
function reviewLabel(value: string) { return ({ PENDING: '待审核', APPROVED: '已审核', REJECTED: '已驳回' } as Record<string, string>)[value] || value }
|
|
|
|
|
function directionLabel(value: string) { return value === 'RECEIVABLE' ? '应收' : '应付' }
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
if (!canView.value) return
|
|
|
|
|
loading.value = true; loadError.value = null
|
2026-08-17 19:13:25 +08:00
|
|
|
try {
|
|
|
|
|
rows.value = await listInboundFeeDetails(props.accountId, props.inboundId)
|
|
|
|
|
const approved = rows.value.filter((row) => isManual(row) && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' && row.settlementIncluded)
|
|
|
|
|
emit('summary', {
|
|
|
|
|
currency: approved.find((row) => row.currency)?.currency || 'CNY',
|
|
|
|
|
approvedReceivable: approved.filter((row) => row.direction === 'RECEIVABLE').reduce((sum, row) => sum + Number(row.amount || 0), 0),
|
|
|
|
|
approvedPayable: approved.filter((row) => row.direction === 'PAYABLE').reduce((sum, row) => sum + Number(row.amount || 0), 0),
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-08-12 16:31:46 +08:00
|
|
|
catch (error) { loadError.value = getApiErrorDetails(error, '费用明细加载失败') }
|
|
|
|
|
finally { loading.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-12 16:40:09 +08:00
|
|
|
async function loadFeeItems() {
|
|
|
|
|
if (!canViewFeeItems.value || feeItemsLoading.value) return
|
|
|
|
|
feeItemsLoading.value = true; feeItemsError.value = null
|
|
|
|
|
try { feeItems.value = await listInboundFeeItemOptions() }
|
|
|
|
|
catch (error) { feeItemsError.value = getApiErrorDetails(error, '费用项目加载失败') }
|
|
|
|
|
finally { feeItemsLoading.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadPartners() {
|
|
|
|
|
if (!canViewPartners.value || partnersLoading.value || partners.value.length || partnersLoadAttempted.value) return
|
|
|
|
|
partnersLoadAttempted.value = true
|
|
|
|
|
partnersLoading.value = true; partnersError.value = null
|
|
|
|
|
try { partners.value = await listInboundFeePartnerOptions() }
|
|
|
|
|
catch (error) { partnersError.value = getApiErrorDetails(error, '往来单位加载失败') }
|
|
|
|
|
finally { partnersLoading.value = false }
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assignForm(row?: InboundFeeDetail) {
|
|
|
|
|
Object.assign(form, row ? {
|
|
|
|
|
feeItemId: row.feeItemId, direction: row.direction, businessDate: row.businessDate,
|
|
|
|
|
unitPrice: Number(row.unitPrice), currency: row.currency, amount: Number(row.amount),
|
|
|
|
|
settlementIncluded: Boolean(row.settlementIncluded), partnerId: row.partnerId,
|
|
|
|
|
partnerName: row.partnerName, remark: row.remark,
|
|
|
|
|
} : emptyForm())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openEditor(mode: 'create' | 'edit' | 'adjust', row?: InboundFeeDetail) {
|
2026-08-12 16:40:09 +08:00
|
|
|
partnersLoadAttempted.value = false
|
2026-08-12 16:31:46 +08:00
|
|
|
editorMode.value = mode; target.value = row || null; assignForm(row); actionError.value = null
|
2026-08-12 16:40:09 +08:00
|
|
|
feeItemsError.value = null; partnersError.value = null
|
2026-08-12 16:31:46 +08:00
|
|
|
editorOpen.value = true
|
2026-08-12 16:40:09 +08:00
|
|
|
if (!feeItems.value.length) await loadFeeItems()
|
|
|
|
|
if (form.direction === 'PAYABLE') await loadPartners()
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizePayload(): SaveInboundFeeDetailRequest {
|
|
|
|
|
const partner = partners.value.find((item) => String(item.id) === String(form.partnerId))
|
|
|
|
|
return { ...form, currency: 'CNY', partnerId: form.direction === 'PAYABLE' ? form.partnerId : null,
|
|
|
|
|
partnerName: form.direction === 'PAYABLE' ? partner?.partnerName || form.partnerName : null,
|
|
|
|
|
remark: form.remark?.trim() || null }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function finishAction(message: string) {
|
|
|
|
|
await load(); emit('changed'); ElMessage.success(message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function save() {
|
|
|
|
|
if (!await formRef.value?.validate()) return
|
2026-08-12 16:40:09 +08:00
|
|
|
if (!canViewFeeItems.value) { actionError.value = { message: '当前账号缺少费用项目查看权限,无法保存费用' }; return }
|
|
|
|
|
if (form.direction === 'PAYABLE' && !canViewPartners.value) { actionError.value = { message: '当前账号缺少往来单位查看权限,无法维护应付费用' }; return }
|
2026-08-12 16:31:46 +08:00
|
|
|
if (form.direction === 'PAYABLE' && !form.partnerId) { actionError.value = { message: '应付费用必须选择往来单位' }; return }
|
|
|
|
|
saving.value = true; actionError.value = null
|
|
|
|
|
try {
|
|
|
|
|
const payload = normalizePayload()
|
|
|
|
|
if (editorMode.value === 'create') await createInboundFeeDetail(props.accountId, props.inboundId, payload)
|
|
|
|
|
else if (editorMode.value === 'edit' && target.value) await updateInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), payload)
|
|
|
|
|
else if (target.value) await adjustInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), target.value.sourceFeeVersion, payload)
|
|
|
|
|
editorOpen.value = false
|
|
|
|
|
await finishAction(editorMode.value === 'adjust' ? '费用调整已提交审核' : '费用明细已保存')
|
|
|
|
|
} catch (error) { actionError.value = getApiErrorDetails(error, '费用明细保存失败') }
|
|
|
|
|
finally { saving.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function remove(row: InboundFeeDetail) {
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm('删除后无法恢复,是否继续?', '删除费用明细', { type: 'warning', confirmButtonText: '删除' })
|
|
|
|
|
await deleteInboundFeeDetail(props.accountId, props.inboundId, String(row.id)); await finishAction('费用明细已删除')
|
|
|
|
|
} catch (error) { if (error !== 'cancel' && error !== 'close') actionError.value = getApiErrorDetails(error, '费用明细删除失败') }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function approve(row: InboundFeeDetail) {
|
|
|
|
|
try { await reviewInboundFeeDetail(props.accountId, props.inboundId, String(row.id), 'approve'); await finishAction('费用审核通过') }
|
|
|
|
|
catch (error) { actionError.value = getApiErrorDetails(error, '费用审核失败') }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openReject(row: InboundFeeDetail) { target.value = row; rejectReason.value = ''; actionError.value = null; rejectOpen.value = true }
|
|
|
|
|
async function reject() {
|
|
|
|
|
if (!target.value || !rejectReason.value.trim()) { actionError.value = { message: '请填写驳回原因' }; return }
|
|
|
|
|
saving.value = true
|
|
|
|
|
try { await reviewInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), 'reject', rejectReason.value.trim()); rejectOpen.value = false; await finishAction('费用已驳回') }
|
|
|
|
|
catch (error) { actionError.value = getApiErrorDetails(error, '费用驳回失败') }
|
|
|
|
|
finally { saving.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function voidRow(row: InboundFeeDetail) {
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm('作废会同步撤销尚未结算的财务费用来源,是否继续?', '作废费用明细', { type: 'warning', confirmButtonText: '作废' })
|
|
|
|
|
await voidInboundFeeDetail(props.accountId, props.inboundId, String(row.id), row.sourceFeeVersion); await finishAction('费用明细已作废')
|
|
|
|
|
} catch (error) { if (error !== 'cancel' && error !== 'close') actionError.value = getApiErrorDetails(error, '费用作废失败') }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(() => [props.accountId, props.inboundId], () => void load())
|
2026-08-12 16:40:09 +08:00
|
|
|
watch(() => form.direction, (direction) => {
|
|
|
|
|
if (direction === 'RECEIVABLE') { form.partnerId = null; form.partnerName = null; partnersError.value = null }
|
|
|
|
|
else void loadPartners()
|
|
|
|
|
})
|
2026-08-12 16:31:46 +08:00
|
|
|
onMounted(() => void load())
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<section class="fee-detail-manager" aria-label="运单费用维护">
|
|
|
|
|
<div class="fee-toolbar">
|
|
|
|
|
<span>共 {{ rows.length }} 条应收应付费用明细</span>
|
|
|
|
|
<div>
|
2026-08-17 18:32:41 +08:00
|
|
|
<el-tooltip content="刷新费用明细" placement="top"><el-button circle aria-label="刷新费用明细" :aria-busy="loading" :disabled="loading" @click="load"><RefreshCw :size="16" :class="{ 'is-spinning': loading }" /></el-button></el-tooltip>
|
2026-08-12 16:40:09 +08:00
|
|
|
<el-button v-if="access.can('firstleg:inbound:fee-detail:edit') && canViewFeeItems" type="primary" @click="openEditor('create')"><Plus :size="16" />新增费用</el-button>
|
2026-08-12 16:31:46 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-alert v-if="!canView" title="当前账号没有查看费用明细的权限" type="warning" :closable="false" show-icon />
|
|
|
|
|
<LoadErrorState v-else-if="loadError" title="费用明细加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<el-alert v-if="actionError && !editorOpen && !rejectOpen" type="error" :closable="true" @close="actionError = null"><template #title>{{ actionError.message }}</template><ApiErrorMeta :details="actionError" /></el-alert>
|
|
|
|
|
<div v-loading="loading" class="fee-table-shell">
|
2026-08-12 18:52:22 +08:00
|
|
|
<el-table :data="rows" border stripe empty-text="暂无费用数据" row-key="id">
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-table-column label="操作" fixed="left" width="250">
|
|
|
|
|
<template #default="{ row }">
|
2026-08-12 18:24:05 +08:00
|
|
|
<el-button v-if="canEdit(row as InboundFeeDetail)" size="small" plain type="primary" @click="openEditor('edit', row as InboundFeeDetail)"><Pencil :size="15" />编辑</el-button>
|
|
|
|
|
<el-button v-if="canReview(row as InboundFeeDetail)" size="small" plain type="success" @click="approve(row as InboundFeeDetail)"><Check :size="15" />审核</el-button>
|
|
|
|
|
<el-button v-if="canReview(row as InboundFeeDetail)" size="small" plain type="warning" @click="openReject(row as InboundFeeDetail)"><Undo2 :size="15" />驳回</el-button>
|
|
|
|
|
<el-button v-if="canAdjust(row as InboundFeeDetail)" size="small" plain type="primary" @click="openEditor('adjust', row as InboundFeeDetail)"><Pencil :size="15" />调整</el-button>
|
|
|
|
|
<el-button v-if="canVoid(row as InboundFeeDetail)" size="small" plain type="danger" @click="voidRow(row as InboundFeeDetail)"><X :size="15" />作废</el-button>
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-dropdown v-if="canDelete(row as InboundFeeDetail)">
|
|
|
|
|
<el-button link aria-label="更多费用操作"><MoreHorizontal :size="17" /></el-button>
|
|
|
|
|
<template #dropdown><el-dropdown-menu>
|
|
|
|
|
<el-dropdown-item @click="remove(row as InboundFeeDetail)"><Trash2 :size="15" />删除</el-dropdown-item>
|
|
|
|
|
</el-dropdown-menu></template>
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="审核状态" width="120"><template #default="{ row }"><StatusBadge :status="reviewLabel(row.reviewStatus)" /></template></el-table-column>
|
2026-08-17 19:13:25 +08:00
|
|
|
<el-table-column label="来源" width="120"><template #default="{ row }">{{ row.sourceType === 'FBA_PRICING' ? '头程派生' : '人工录入' }}</template></el-table-column>
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-table-column label="应收/应付" width="110"><template #default="{ row }">{{ directionLabel(row.direction) }}</template></el-table-column>
|
|
|
|
|
<el-table-column prop="feeTypeNameSnapshot" label="费用类型" min-width="150" />
|
|
|
|
|
<el-table-column label="往来单位/客户" min-width="170"><template #default="{ row }">{{ row.direction === 'PAYABLE' ? row.partnerName : row.customerName || '—' }}</template></el-table-column>
|
|
|
|
|
<el-table-column prop="businessDate" label="业务发生日" width="130" />
|
|
|
|
|
<el-table-column prop="currency" label="货币" width="90" />
|
|
|
|
|
<el-table-column label="金额" min-width="120"><template #default="{ row }">{{ Number(row.amount).toFixed(2) }}</template></el-table-column>
|
|
|
|
|
<el-table-column label="单价" min-width="110"><template #default="{ row }">{{ Number(row.unitPrice).toFixed(4) }}</template></el-table-column>
|
|
|
|
|
<el-table-column label="参与结算" width="110"><template #default="{ row }">{{ row.settlementIncluded ? '是' : '否' }}</template></el-table-column>
|
|
|
|
|
<el-table-column prop="sourceFeeVersion" label="版本" width="85" />
|
|
|
|
|
<el-table-column prop="publicationStatus" label="发布状态" min-width="110" />
|
|
|
|
|
<el-table-column prop="reviewReason" label="驳回原因" min-width="180" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<AppDialog v-model="editorOpen" :title="editorMode === 'create' ? '新增费用' : editorMode === 'adjust' ? '调整已审核费用' : '编辑费用'" width="min(680px, calc(100vw - 28px))" :confirm-loading="saving" confirm-text="保存" @confirm="save">
|
|
|
|
|
<el-alert v-if="editorMode === 'adjust'" title="调整将生成新版本并重新进入待审核状态" type="warning" :closable="false" show-icon />
|
2026-08-12 16:40:09 +08:00
|
|
|
<el-alert v-if="!canViewFeeItems" title="当前账号缺少费用项目查看权限,无法新增或修改费用" type="warning" :closable="false" show-icon />
|
|
|
|
|
<el-alert v-if="!canViewPartners" title="当前账号缺少往来单位查看权限,应付费用不可维护;应收费用不受影响" type="warning" :closable="false" show-icon />
|
|
|
|
|
<el-alert v-if="feeItemsError" type="error" :closable="true" @close="feeItemsError = null"><template #title>{{ feeItemsError.message }}</template><ApiErrorMeta :details="feeItemsError" /></el-alert>
|
|
|
|
|
<el-alert v-if="partnersError" type="error" :closable="true" @close="partnersError = null"><template #title>{{ partnersError.message }}</template><ApiErrorMeta :details="partnersError" /></el-alert>
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-alert v-if="actionError" type="error" :closable="true" @close="actionError = null"><template #title>{{ actionError.message }}</template><ApiErrorMeta :details="actionError" /></el-alert>
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="fee-form">
|
2026-08-12 16:40:09 +08:00
|
|
|
<el-form-item label="应收/应付" prop="direction"><el-segmented v-model="form.direction" :options="directionOptions" /></el-form-item>
|
|
|
|
|
<el-form-item label="费用项目" prop="feeItemId"><el-select v-model="form.feeItemId" filterable :loading="feeItemsLoading" :disabled="!canViewFeeItems"><el-option v-for="item in applicableItems" :key="item.id" :label="item.feeTypeName" :value="item.id" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item v-if="form.direction === 'PAYABLE'" label="往来单位" required><el-select v-model="form.partnerId" filterable :loading="partnersLoading" :disabled="!canViewPartners"><el-option v-for="partner in partners" :key="partner.id" :label="partner.partnerName" :value="partner.id" /></el-select></el-form-item>
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-form-item label="业务发生日" prop="businessDate"><el-date-picker v-model="form.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item>
|
|
|
|
|
<el-form-item label="货币" prop="currency"><el-input model-value="CNY" readonly aria-label="货币 CNY" /></el-form-item>
|
|
|
|
|
<el-form-item label="单价" prop="unitPrice"><el-input-number v-model="form.unitPrice" :min="0" :precision="4" /></el-form-item>
|
|
|
|
|
<el-form-item label="金额" prop="amount"><el-input-number v-model="form.amount" :min="0" :precision="2" /></el-form-item>
|
|
|
|
|
<el-form-item label="参与结算"><el-switch v-model="form.settlementIncluded" /></el-form-item>
|
|
|
|
|
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
<AppDialog v-model="rejectOpen" title="驳回费用" :confirm-loading="saving" confirm-text="确认驳回" confirm-type="warning" @confirm="reject">
|
|
|
|
|
<el-alert v-if="actionError" type="error" :closable="true" @close="actionError = null"><template #title>{{ actionError.message }}</template><ApiErrorMeta :details="actionError" /></el-alert>
|
|
|
|
|
<el-form label-position="top"><el-form-item label="驳回原因" required><el-input v-model="rejectReason" type="textarea" :rows="4" maxlength="500" show-word-limit /></el-form-item></el-form>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.fee-detail-manager { display: grid; min-width: 0; max-width: 100%; gap: 12px; }
|
|
|
|
|
.fee-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--el-text-color-secondary); font-size: 13px; }
|
|
|
|
|
.fee-toolbar > div { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
|
|
|
.fee-toolbar :deep(.el-button + .el-button) { margin-left: 0; }
|
2026-08-17 18:32:41 +08:00
|
|
|
.fee-toolbar .is-spinning { animation: fee-refresh-spin 0.8s linear infinite; }
|
2026-08-12 16:31:46 +08:00
|
|
|
.fee-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; margin-top: 14px; }
|
|
|
|
|
.fee-form :deep(.el-select), .fee-form :deep(.el-date-editor), .fee-form :deep(.el-input-number) { width: 100%; }
|
|
|
|
|
.fee-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
|
|
|
|
.fee-table-shell { min-width: 0; max-width: 100%; overflow: hidden; }
|
|
|
|
|
.fee-table-shell :deep(.el-table) { width: 100%; max-width: 100%; }
|
2026-08-17 18:32:41 +08:00
|
|
|
@keyframes fee-refresh-spin { to { transform: rotate(360deg); } }
|
2026-08-12 16:31:46 +08:00
|
|
|
@media (max-width: 720px) { .fee-toolbar { align-items: flex-start; flex-direction: column; } .fee-form { grid-template-columns: 1fr; } .fee-form :deep(.el-form-item:last-child) { grid-column: auto; } }
|
|
|
|
|
</style>
|