2026-08-12 16:31:46 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
|
|
|
import { Check, CircleX, Pencil, Plus, Trash2 } from '@lucide/vue'
|
|
|
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
|
|
|
|
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
2026-08-12 16:58:52 +08:00
|
|
|
import { adjustContainerFee, approveContainerFee, createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFeePartnerOptions, listContainerFees, rejectContainerFee, voidContainerFee, type ApplicableFeeItem, type ContainerCostDetail, type ContainerFeePartnerOption, type ContainerFeeResource, type ContainerFeeSaveRequest } from '../api/containerFees'
|
2026-08-12 16:31:46 +08:00
|
|
|
|
|
|
|
|
const props = defineProps<{ planId: string; customers: Array<{ accountId: string; customerName: string | null }> }>()
|
|
|
|
|
const access = useAccessPreviewStore()
|
|
|
|
|
const accountId = ref('')
|
|
|
|
|
const fees = ref<ContainerFeeResource[]>([])
|
|
|
|
|
const feeItems = ref<ApplicableFeeItem[]>([])
|
2026-08-12 16:58:52 +08:00
|
|
|
const partners = ref<ContainerFeePartnerOption[]>([])
|
2026-08-12 16:31:46 +08:00
|
|
|
const costs = ref<ContainerCostDetail[]>([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const error = ref<ApiErrorDetails | null>(null)
|
2026-08-12 16:58:52 +08:00
|
|
|
const feeItemsError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const partnersError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const feeItemsLoading = ref(false)
|
|
|
|
|
const partnersLoading = ref(false)
|
2026-08-12 16:31:46 +08:00
|
|
|
const editorOpen = ref(false)
|
|
|
|
|
const rejectOpen = ref(false)
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
const target = ref<ContainerFeeResource | null>(null)
|
|
|
|
|
const formRef = ref<FormInstance>()
|
2026-08-12 16:58:52 +08:00
|
|
|
const form = reactive<ContainerFeeSaveRequest>({ feeItemId: '', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
|
2026-08-12 16:31:46 +08:00
|
|
|
const rejection = ref('')
|
2026-08-12 16:40:09 +08:00
|
|
|
let loadSequence = 0
|
2026-08-12 16:31:46 +08:00
|
|
|
|
|
|
|
|
const canView = computed(() => access.can('firstleg:inbound:fee-detail:view'))
|
|
|
|
|
const canEdit = computed(() => access.can('firstleg:inbound:fee-detail:edit'))
|
|
|
|
|
const canReview = computed(() => access.can('firstleg:inbound:fee-detail:review'))
|
2026-08-12 16:58:52 +08:00
|
|
|
const canViewFeeItems = computed(() => access.can('firstleg:fee-item:view'))
|
|
|
|
|
const canViewPartners = computed(() => access.can('platform:counterparty:list'))
|
2026-08-12 16:31:46 +08:00
|
|
|
const customerOptions = computed(() => {
|
|
|
|
|
const unique = new Map<string, string>()
|
|
|
|
|
for (const item of props.customers) if (item.accountId) unique.set(item.accountId, item.customerName || '客户信息缺失')
|
|
|
|
|
return [...unique].map(([value, label]) => ({ value, label }))
|
|
|
|
|
})
|
|
|
|
|
const selectedCustomer = computed(() => customerOptions.value.find((item) => item.value === accountId.value))
|
|
|
|
|
const applicableItems = computed(() => feeItems.value.filter((item) => form.direction === 'RECEIVABLE'
|
|
|
|
|
? item.warehouseReceivable || item.pickupReceivable : item.warehousePayable || item.pickupPayable))
|
|
|
|
|
const containerId = computed(() => fees.value.find((item) => item.containerBookingId)?.containerBookingId || null)
|
|
|
|
|
const columns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'actions', label: '操作', fixed: 'left', width: 300, slotName: 'actions', hideable: false, movable: false },
|
|
|
|
|
{ key: 'feeItemName', prop: 'feeItemName', label: '费用项目', minWidth: 150 },
|
|
|
|
|
{ key: 'direction', label: '方向', width: 90, slotName: 'direction' },
|
2026-08-12 16:58:52 +08:00
|
|
|
{ key: 'partner', label: '往来单位/客户', minWidth: 170, slotName: 'partner' },
|
2026-08-12 16:31:46 +08:00
|
|
|
{ key: 'amount', label: '金额(CNY)', width: 120, align: 'right', slotName: 'amount' },
|
|
|
|
|
{ key: 'businessDate', prop: 'businessDate', label: '业务日期', width: 120 },
|
|
|
|
|
{ key: 'reviewStatus', label: '审核状态', width: 110, slotName: 'reviewStatus' },
|
|
|
|
|
{ key: 'sourceStatus', label: '来源状态', width: 100, slotName: 'sourceStatus' },
|
|
|
|
|
{ key: 'settlementIncluded', label: '参与结算', width: 100, slotName: 'settlementIncluded' },
|
|
|
|
|
{ key: 'remark', prop: 'remark', label: '备注', minWidth: 180, showOverflowTooltip: true },
|
|
|
|
|
]
|
|
|
|
|
const allocationColumns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'orderId', prop: 'orderId', label: '订单', fixed: 'left', minWidth: 150 },
|
|
|
|
|
{ key: 'inboundId', prop: 'inboundId', label: '入库单', minWidth: 150 },
|
|
|
|
|
{ key: 'volumeCbm', label: '订单体积(m³)', width: 125, align: 'right', slotName: 'volume' },
|
|
|
|
|
{ key: 'allocationRate', label: '分摊比例', width: 110, align: 'right', slotName: 'rate' },
|
|
|
|
|
{ key: 'allocatedAmount', label: '分摊金额(CNY)', width: 135, align: 'right', slotName: 'allocated' },
|
|
|
|
|
]
|
|
|
|
|
const rules: FormRules = { feeItemId: [{ required: true, message: '请选择费用项目', trigger: 'change' }], businessDate: [{ required: true, message: '请选择业务日期', trigger: 'change' }], amount: [{ type: 'number', min: 0, message: '金额不能小于 0', trigger: 'change' }] }
|
|
|
|
|
|
|
|
|
|
function resetForm(item?: ContainerFeeResource) {
|
|
|
|
|
target.value = item || null
|
2026-08-12 16:58:52 +08:00
|
|
|
Object.assign(form, item ? { feeItemId: item.feeItemId, direction: item.direction, partnerId: item.partnerId, partnerName: item.partnerName, businessDate: item.businessDate, amount: item.amount, currency: 'CNY', settlementIncluded: item.settlementIncluded, remark: item.remark }
|
|
|
|
|
: { feeItemId: '', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadFeeItems() {
|
|
|
|
|
feeItems.value = []; feeItemsError.value = null
|
|
|
|
|
if (!canViewFeeItems.value) return
|
|
|
|
|
feeItemsLoading.value = true
|
|
|
|
|
try { feeItems.value = await listApplicableFeeItems() }
|
|
|
|
|
catch (cause) { feeItemsError.value = getApiErrorDetails(cause, '费用项目加载失败') }
|
|
|
|
|
finally { feeItemsLoading.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadPartners() {
|
|
|
|
|
partners.value = []; partnersError.value = null
|
|
|
|
|
if (!canViewPartners.value) return
|
|
|
|
|
partnersLoading.value = true
|
|
|
|
|
try { partners.value = await listContainerFeePartnerOptions() }
|
|
|
|
|
catch (cause) { partnersError.value = getApiErrorDetails(cause, '往来单位加载失败') }
|
|
|
|
|
finally { partnersLoading.value = false }
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
if (!accountId.value || !canView.value) return
|
2026-08-12 16:40:09 +08:00
|
|
|
const sequence = ++loadSequence
|
|
|
|
|
const requestedAccountId = accountId.value
|
2026-08-12 16:31:46 +08:00
|
|
|
loading.value = true; error.value = null
|
|
|
|
|
try {
|
2026-08-12 16:58:52 +08:00
|
|
|
const feeRows = await listContainerFees(props.planId, requestedAccountId)
|
2026-08-12 16:40:09 +08:00
|
|
|
if (sequence !== loadSequence || requestedAccountId !== accountId.value) return
|
2026-08-12 16:58:52 +08:00
|
|
|
fees.value = feeRows
|
2026-08-12 16:31:46 +08:00
|
|
|
costs.value = []
|
|
|
|
|
const currentContainerId = feeRows.find((item) => item.containerBookingId)?.containerBookingId
|
2026-08-12 16:40:09 +08:00
|
|
|
if (currentContainerId) {
|
|
|
|
|
const result = await listContainerCostDetails(requestedAccountId, currentContainerId)
|
|
|
|
|
if (sequence !== loadSequence || requestedAccountId !== accountId.value) return
|
|
|
|
|
costs.value = result
|
|
|
|
|
}
|
|
|
|
|
} catch (cause) { if (sequence === loadSequence && requestedAccountId === accountId.value) error.value = getApiErrorDetails(cause, '柜次费用加载失败') }
|
|
|
|
|
finally { if (sequence === loadSequence) loading.value = false }
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-12 16:58:52 +08:00
|
|
|
function openCreate() { if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return; resetForm(); editorOpen.value = true; void Promise.all([loadFeeItems(), loadPartners()]) }
|
|
|
|
|
function openAdjust(item: ContainerFeeResource) { if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return; resetForm(item); editorOpen.value = true; void Promise.all([loadFeeItems(), loadPartners()]) }
|
2026-08-12 16:31:46 +08:00
|
|
|
async function save() {
|
2026-08-12 16:58:52 +08:00
|
|
|
if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return
|
|
|
|
|
if (form.direction === 'PAYABLE' && (!canViewPartners.value || !form.partnerId)) { ElMessage.warning(canViewPartners.value ? '应付费用必须选择往来单位' : '当前账号缺少往来单位查看权限,无法保存应付费用'); return }
|
|
|
|
|
if (!await formRef.value?.validate().catch(() => false)) return
|
|
|
|
|
const partner = partners.value.find((item) => item.id === String(form.partnerId))
|
|
|
|
|
const payload: ContainerFeeSaveRequest = { ...form, partnerId: form.direction === 'PAYABLE' ? form.partnerId : null, partnerName: form.direction === 'PAYABLE' ? partner?.partnerName || null : null }
|
|
|
|
|
if (form.direction === 'PAYABLE' && !payload.partnerName) { ElMessage.warning('请选择有效的往来单位'); return }
|
2026-08-12 16:31:46 +08:00
|
|
|
saving.value = true
|
|
|
|
|
try {
|
2026-08-12 16:58:52 +08:00
|
|
|
if (target.value) await adjustContainerFee(props.planId, accountId.value, target.value.id, target.value.sourceVersion, payload)
|
|
|
|
|
else await createContainerFee(props.planId, accountId.value, payload)
|
2026-08-12 16:31:46 +08:00
|
|
|
editorOpen.value = false; ElMessage.success(target.value ? '费用调整已提交审核' : '费用已新增'); await load()
|
|
|
|
|
} catch (cause) { ElMessage.error(getApiErrorDetails(cause, '费用保存失败').message) }
|
|
|
|
|
finally { saving.value = false }
|
|
|
|
|
}
|
2026-08-12 16:40:09 +08:00
|
|
|
async function approve(item: ContainerFeeResource) { if (!canReview.value || !accountId.value) return; await approveContainerFee(props.planId, accountId.value, item.id).then(() => ElMessage.success('费用已审核通过')).then(load).catch((cause) => ElMessage.error(getApiErrorDetails(cause, '审核失败').message)) }
|
|
|
|
|
function openReject(item: ContainerFeeResource) { if (!canReview.value || !accountId.value) return; target.value = item; rejection.value = ''; rejectOpen.value = true }
|
2026-08-12 16:31:46 +08:00
|
|
|
async function reject() {
|
2026-08-12 16:40:09 +08:00
|
|
|
if (!canReview.value || !accountId.value || !target.value) return
|
|
|
|
|
if (!rejection.value.trim()) return ElMessage.warning('请输入驳回原因')
|
2026-08-12 16:31:46 +08:00
|
|
|
saving.value = true
|
|
|
|
|
try { await rejectContainerFee(props.planId, accountId.value, target.value.id, rejection.value.trim()); rejectOpen.value = false; ElMessage.success('费用已驳回'); await load() }
|
|
|
|
|
catch (cause) { ElMessage.error(getApiErrorDetails(cause, '驳回失败').message) } finally { saving.value = false }
|
|
|
|
|
}
|
|
|
|
|
async function voidFee(item: ContainerFeeResource) {
|
2026-08-12 16:40:09 +08:00
|
|
|
if (!canReview.value || !accountId.value) return
|
2026-08-12 16:31:46 +08:00
|
|
|
await ElMessageBox.confirm(`确认作废“${item.feeItemName}”费用?作废后将同步财务来源状态。`, '作废费用', { type: 'warning', confirmButtonText: '确认作废' })
|
|
|
|
|
await voidContainerFee(props.planId, accountId.value, item.id, item.sourceVersion).then(() => ElMessage.success('费用已作废')).then(load).catch((cause) => ElMessage.error(getApiErrorDetails(cause, '作废失败').message))
|
|
|
|
|
}
|
|
|
|
|
function money(value: number) { return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }
|
|
|
|
|
function initializeCustomer() { accountId.value = customerOptions.value.length === 1 ? customerOptions.value[0].value : '' }
|
2026-08-12 16:40:09 +08:00
|
|
|
function clearCustomerContext() {
|
|
|
|
|
loadSequence += 1
|
2026-08-12 16:58:52 +08:00
|
|
|
fees.value = []; costs.value = []; feeItems.value = []; partners.value = []; error.value = null; feeItemsError.value = null; partnersError.value = null; loading.value = false; feeItemsLoading.value = false; partnersLoading.value = false
|
2026-08-12 16:40:09 +08:00
|
|
|
editorOpen.value = false; rejectOpen.value = false; target.value = null; rejection.value = ''
|
|
|
|
|
resetForm()
|
|
|
|
|
}
|
2026-08-12 16:31:46 +08:00
|
|
|
watch(customerOptions, initializeCustomer, { immediate: true })
|
2026-08-12 16:40:09 +08:00
|
|
|
watch(accountId, () => { clearCustomerContext(); void load() })
|
2026-08-12 16:58:52 +08:00
|
|
|
watch(() => form.direction, (direction) => {
|
|
|
|
|
if (!applicableItems.value.some((item) => item.id === form.feeItemId)) form.feeItemId = ''
|
|
|
|
|
if (direction === 'RECEIVABLE') { form.partnerId = null; form.partnerName = null; partnersError.value = null }
|
|
|
|
|
})
|
2026-08-12 16:31:46 +08:00
|
|
|
onMounted(() => void load())
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<section class="fees-panel surface-panel" aria-label="柜次费用维护">
|
2026-08-12 16:58:52 +08:00
|
|
|
<header class="fees-header"><div><h2>柜次费用</h2><p>费用与财务分摊均来自服务端真实记录</p></div><el-button v-if="canEdit && canViewFeeItems && accountId" type="primary" @click="openCreate"><Plus :size="16" />新增费用</el-button></header>
|
2026-08-12 16:31:46 +08:00
|
|
|
<el-alert v-if="!canView" type="warning" :closable="false" show-icon title="当前账号没有查看柜次费用的权限" />
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div v-if="customerOptions.length > 1" class="customer-context"><span>客户</span><el-select v-model="accountId" placeholder="请选择费用归属客户"><el-option v-for="item in customerOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></div>
|
|
|
|
|
<el-alert v-if="customerOptions.length === 0" type="warning" :closable="false" show-icon title="装柜明细缺少客户归属,无法安全维护费用" />
|
|
|
|
|
<el-alert v-else-if="!accountId" type="info" :closable="false" show-icon title="该柜次包含多个客户,请先选择费用归属客户" />
|
2026-08-12 16:58:52 +08:00
|
|
|
<el-alert v-else-if="!canViewFeeItems" type="warning" :closable="false" show-icon title="当前账号缺少费用项目查看权限,无法新增或调整费用" />
|
2026-08-12 16:31:46 +08:00
|
|
|
<LoadErrorState v-else-if="error" compact title="柜次费用加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
|
|
|
|
<DataTable v-else :data="fees" :columns="columns" :loading="loading" preference-key="container-fees" row-key="id" empty-text="当前客户暂无柜次费用">
|
|
|
|
|
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canReview && row.reviewStatus === 'PENDING'" link type="success" @click="approve(row as ContainerFeeResource)"><Check :size="15" />通过</el-button><el-button v-if="canReview && row.reviewStatus === 'PENDING'" link type="danger" @click="openReject(row as ContainerFeeResource)"><CircleX :size="15" />驳回</el-button><el-button v-if="canEdit && row.reviewStatus === 'APPROVED' && row.sourceStatus === 'ACTIVE'" link type="primary" @click="openAdjust(row as ContainerFeeResource)"><Pencil :size="15" />调整</el-button><el-button v-if="canReview && row.reviewStatus === 'APPROVED' && row.sourceStatus === 'ACTIVE'" link type="danger" @click="voidFee(row as ContainerFeeResource)"><Trash2 :size="15" />作废</el-button></div></template>
|
|
|
|
|
<template #direction="{ row }"><el-tag :type="row.direction === 'RECEIVABLE' ? 'success' : 'warning'" effect="plain">{{ row.direction === 'RECEIVABLE' ? '应收' : '应付' }}</el-tag></template>
|
2026-08-12 16:58:52 +08:00
|
|
|
<template #partner="{ row }">{{ row.direction === 'PAYABLE' ? row.partnerName || '服务端未返回' : selectedCustomer?.label || '客户信息缺失' }}</template>
|
2026-08-12 16:31:46 +08:00
|
|
|
<template #amount="{ row }">{{ money(row.amount) }}</template><template #reviewStatus="{ row }"><StatusBadge :status="row.reviewStatus === 'PENDING' ? '待审核' : row.reviewStatus === 'APPROVED' ? '已通过' : '已驳回'" /></template><template #sourceStatus="{ row }"><StatusBadge :status="row.sourceStatus === 'ACTIVE' ? '有效' : '已作废'" /></template><template #settlementIncluded="{ row }">{{ row.settlementIncluded ? '是' : '否' }}</template>
|
|
|
|
|
</DataTable>
|
|
|
|
|
|
|
|
|
|
<div v-if="accountId" class="allocation-section"><header><div><h3>财务体积分摊</h3><p>{{ selectedCustomer?.label }} · 仅展示 Finance 已持久化结果</p></div></header><el-empty v-if="!loading && costs.length === 0" description="尚无与该柜次关联的财务分摊结果" :image-size="64" /><article v-for="cost in costs" :key="cost.id" class="cost-block"><div class="cost-summary"><strong>{{ cost.feeItemName || '柜次成本' }}</strong><span>往来单位:{{ cost.supplierName || '服务端未返回' }}</span><span>金额:¥{{ money(cost.amount) }}</span></div><DataTable :data="cost.allocations" :columns="allocationColumns" :preference-key="`container-cost-${cost.id}`" row-key="id" empty-text="服务端未返回分摊明细"><template #volume="{ row }">{{ row.volumeCbm.toFixed(3) }}</template><template #rate="{ row }">{{ (row.allocationRate * 100).toFixed(2) }}%</template><template #allocated="{ row }">{{ money(row.allocatedAmount) }}</template></DataTable></article></div>
|
|
|
|
|
</template>
|
|
|
|
|
|
2026-08-12 16:58:52 +08:00
|
|
|
<AppDialog v-model="editorOpen" :title="target ? '调整已审核费用' : '新增柜次费用'" confirm-text="保存" :confirm-loading="saving" @confirm="save"><el-alert v-if="target" type="info" :closable="false" show-icon title="调整后版本递增并重新进入待审核状态" /><el-alert v-if="!canViewPartners" type="warning" :closable="false" show-icon title="当前账号缺少往来单位查看权限,仅可维护应收费用" /><el-alert v-else-if="partnersError" type="warning" :closable="false" show-icon :title="partnersError.message" /><el-alert v-if="feeItemsError" type="warning" :closable="false" show-icon :title="feeItemsError.message" /><el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="fee-form"><el-form-item label="方向" prop="direction"><el-segmented v-model="form.direction" :options="[{ label: '应收', value: 'RECEIVABLE' }, { label: '应付', value: 'PAYABLE', disabled: !canViewPartners || !!partnersError }]" /></el-form-item><el-form-item label="费用项目" prop="feeItemId"><el-select v-model="form.feeItemId" filterable :loading="feeItemsLoading" :disabled="!canViewFeeItems" placeholder="请选择启用且适用的费用项目"><el-option v-for="item in applicableItems" :key="item.id" :label="item.name" :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 || !!partnersError" placeholder="请选择启用的往来单位"><el-option v-for="partner in partners" :key="partner.id" :label="partner.partnerName" :value="partner.id" /></el-select></el-form-item><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="金额(CNY)" prop="amount"><el-input-number v-model="form.amount" :min="0" :precision="2" controls-position="right" /></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>
|
2026-08-12 16:31:46 +08:00
|
|
|
<AppDialog v-model="rejectOpen" title="驳回柜次费用" confirm-text="确认驳回" :confirm-loading="saving" @confirm="reject"><el-input v-model="rejection" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入驳回原因" /></AppDialog>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.fees-panel { min-width: 0; overflow: hidden; border: 1px solid var(--cc-border); background: #fff; }
|
|
|
|
|
.fees-header, .allocation-section > header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 18px; }
|
|
|
|
|
.fees-header h2, .allocation-section h3 { margin: 0; font-size: 16px; }.fees-header p, .allocation-section p { margin: 4px 0 0; color: var(--cc-muted); font-size: 12px; }
|
|
|
|
|
.customer-context { display: flex; align-items: center; gap: 12px; padding: 0 18px 16px; }.customer-context > span { color: var(--cc-muted); font-size: 13px; }.customer-context :deep(.el-select) { width: min(320px, 100%); }
|
|
|
|
|
.row-actions { display: flex; flex-wrap: wrap; }.allocation-section { border-top: 1px solid var(--cc-border); }.cost-block { border-top: 1px solid var(--cc-border); }.cost-summary { display: flex; flex-wrap: wrap; gap: 12px 20px; padding: 14px 18px; background: #f8fafc; font-size: 13px; }.cost-summary span { color: var(--cc-muted); }.fee-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; margin-top: 14px; }.fee-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
|
|
|
|
@media (max-width: 640px) { .fees-header { align-items: stretch; flex-direction: column; }.fees-header .el-button { width: 100%; }.fee-form { grid-template-columns: 1fr; }.fee-form :deep(.el-form-item:last-child) { grid-column: auto; }.customer-context { align-items: stretch; flex-direction: column; } }
|
|
|
|
|
</style>
|