feat(energy): 实现能源账单详情页
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
39
apps/web-antd/src/views/energy/bill/detail-data.ts
Normal file
39
apps/web-antd/src/views/energy/bill/detail-data.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Detail list columns (for reference / VXE usage)
|
||||||
|
export function useDetailColumns() {
|
||||||
|
return [
|
||||||
|
{ field: 'plateNumber', title: '车牌号', minWidth: 120 },
|
||||||
|
{ field: 'hydrogenDate', title: '加氢日期', minWidth: 120, formatter: 'formatDate' },
|
||||||
|
{ field: 'hydrogenQuantity', title: '加氢量(KG)', minWidth: 100, align: 'right' },
|
||||||
|
{ field: 'costPrice', title: '成本单价', minWidth: 80, align: 'right' },
|
||||||
|
{ field: 'costAmount', title: '成本金额', minWidth: 100, align: 'right' },
|
||||||
|
{ field: 'customerPrice', title: '对客单价', minWidth: 80, align: 'right' },
|
||||||
|
{ field: 'customerAmount', title: '对客金额', minWidth: 100, align: 'right' },
|
||||||
|
{
|
||||||
|
field: 'deductionStatus',
|
||||||
|
title: '扣款状态',
|
||||||
|
minWidth: 100,
|
||||||
|
align: 'center',
|
||||||
|
slots: { default: 'deductionStatus' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjustment list columns (for reference / VXE usage)
|
||||||
|
export function useAdjustmentColumns() {
|
||||||
|
return [
|
||||||
|
{ field: 'adjustmentType', title: '调整类型', minWidth: 100, slots: { default: 'adjustmentType' } },
|
||||||
|
{ field: 'amount', title: '调整金额', minWidth: 100, align: 'right' },
|
||||||
|
{ field: 'reason', title: '调整原因', minWidth: 200 },
|
||||||
|
{ field: 'detailId', title: '关联明细', minWidth: 100 },
|
||||||
|
{ field: 'operatorName', title: '操作人', minWidth: 100 },
|
||||||
|
{ field: 'operateTime', title: '操作时间', minWidth: 180, formatter: 'formatDateTime' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export status constants from data.ts
|
||||||
|
export {
|
||||||
|
BILL_AUDIT_STATUS_OPTIONS,
|
||||||
|
BILL_STATUS_OPTIONS,
|
||||||
|
COOPERATION_TYPE_OPTIONS,
|
||||||
|
PAYMENT_STATUS_OPTIONS,
|
||||||
|
} from './data';
|
||||||
271
apps/web-antd/src/views/energy/bill/detail.vue
Normal file
271
apps/web-antd/src/views/energy/bill/detail.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { EnergyBillApi } from '#/api/energy/bill';
|
||||||
|
import type { EnergyHydrogenDetailApi } from '#/api/energy/hydrogen-detail';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Descriptions,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Radio,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
Timeline,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
auditBill,
|
||||||
|
getAdjustmentList,
|
||||||
|
getBill,
|
||||||
|
getBillDetailList,
|
||||||
|
} from '#/api/energy/bill';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BILL_AUDIT_STATUS_OPTIONS,
|
||||||
|
BILL_STATUS_OPTIONS,
|
||||||
|
COOPERATION_TYPE_OPTIONS,
|
||||||
|
PAYMENT_STATUS_OPTIONS,
|
||||||
|
} from './detail-data';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const billId = Number(route.params.id);
|
||||||
|
|
||||||
|
const bill = ref<EnergyBillApi.Bill>({});
|
||||||
|
const details = ref<EnergyHydrogenDetailApi.Detail[]>([]);
|
||||||
|
const adjustments = ref<EnergyBillApi.Adjustment[]>([]);
|
||||||
|
const activeTab = ref('detail');
|
||||||
|
|
||||||
|
// Audit modal state
|
||||||
|
const auditVisible = ref(false);
|
||||||
|
const auditApproved = ref(true);
|
||||||
|
const auditRemark = ref('');
|
||||||
|
const auditLoading = ref(false);
|
||||||
|
|
||||||
|
// Ant Design Table column definitions
|
||||||
|
const detailTableColumns = [
|
||||||
|
{ title: '车牌号', dataIndex: 'plateNumber', width: 120 },
|
||||||
|
{ title: '加氢日期', dataIndex: 'hydrogenDate', width: 120 },
|
||||||
|
{ title: '加氢量(KG)', dataIndex: 'hydrogenQuantity', width: 100, align: 'right' as const },
|
||||||
|
{ title: '成本单价', dataIndex: 'costPrice', width: 80, align: 'right' as const },
|
||||||
|
{ title: '成本金额', dataIndex: 'costAmount', width: 100, align: 'right' as const },
|
||||||
|
{ title: '对客单价', dataIndex: 'customerPrice', width: 80, align: 'right' as const },
|
||||||
|
{ title: '对客金额', dataIndex: 'customerAmount', width: 100, align: 'right' as const },
|
||||||
|
];
|
||||||
|
|
||||||
|
const adjustmentTableColumns = [
|
||||||
|
{ title: '调整类型', dataIndex: 'adjustmentType', width: 100 },
|
||||||
|
{ title: '调整金额', dataIndex: 'amount', width: 100, align: 'right' as const },
|
||||||
|
{ title: '调整原因', dataIndex: 'reason', width: 200 },
|
||||||
|
{ title: '关联明细', dataIndex: 'detailId', width: 100 },
|
||||||
|
{ title: '操作人', dataIndex: 'operatorName', width: 100 },
|
||||||
|
{ title: '操作时间', dataIndex: 'operateTime', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
bill.value = await getBill(billId);
|
||||||
|
details.value = await getBillDetailList(billId);
|
||||||
|
adjustments.value = await getAdjustmentList(billId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAudit() {
|
||||||
|
auditLoading.value = true;
|
||||||
|
try {
|
||||||
|
await auditBill(billId, auditApproved.value, auditRemark.value || undefined);
|
||||||
|
message.success('审核完成');
|
||||||
|
auditVisible.value = false;
|
||||||
|
loadData();
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e.message || '审核失败');
|
||||||
|
} finally {
|
||||||
|
auditLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBack() {
|
||||||
|
router.push({ name: 'EnergyBill' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: find option by value
|
||||||
|
function findOption(options: { value: any; label: string; color: string }[], value: any) {
|
||||||
|
return options.find((o) => o.value === value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const billPeriod = computed(() => {
|
||||||
|
if (bill.value.billPeriodStart) {
|
||||||
|
return bill.value.billPeriodStart.substring(0, 7);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(loadData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mb-4 flex items-center justify-between px-4 pt-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h2 class="text-lg font-bold">{{ bill.billCode || '—' }}</h2>
|
||||||
|
<Tag
|
||||||
|
v-if="bill.status != null"
|
||||||
|
:color="findOption(BILL_STATUS_OPTIONS, bill.status)?.color"
|
||||||
|
>
|
||||||
|
{{ findOption(BILL_STATUS_OPTIONS, bill.status)?.label }}
|
||||||
|
</Tag>
|
||||||
|
<Tag
|
||||||
|
v-if="bill.auditStatus != null"
|
||||||
|
:color="findOption(BILL_AUDIT_STATUS_OPTIONS, bill.auditStatus)?.color"
|
||||||
|
>
|
||||||
|
{{ findOption(BILL_AUDIT_STATUS_OPTIONS, bill.auditStatus)?.label }}
|
||||||
|
</Tag>
|
||||||
|
<Tag
|
||||||
|
v-if="bill.paymentStatus != null"
|
||||||
|
:color="findOption(PAYMENT_STATUS_OPTIONS, bill.paymentStatus)?.color"
|
||||||
|
>
|
||||||
|
{{ findOption(PAYMENT_STATUS_OPTIONS, bill.paymentStatus)?.label }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
v-if="bill.status === 1 && bill.auditStatus === 0"
|
||||||
|
type="primary"
|
||||||
|
@click="auditVisible = true"
|
||||||
|
>
|
||||||
|
审核
|
||||||
|
</Button>
|
||||||
|
<Button @click="handleBack">返回</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info Cards -->
|
||||||
|
<div class="mb-4 grid grid-cols-2 gap-4 px-4">
|
||||||
|
<Card title="基本信息" size="small">
|
||||||
|
<Descriptions :column="2" bordered size="small">
|
||||||
|
<Descriptions.Item label="客户名称">{{ bill.customerName || '—' }}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="合同编号">{{ bill.contractId || '—' }}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="加氢站">{{ bill.stationName || '—' }}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="账单周期">{{ billPeriod || '—' }}</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
<Card title="金额汇总" size="small">
|
||||||
|
<!-- 预充值模式 -->
|
||||||
|
<template v-if="bill.cooperationType === 1">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>应收金额</span>
|
||||||
|
<span>¥{{ bill.receivableAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>调整金额</span>
|
||||||
|
<span class="text-red-500">¥{{ bill.adjustmentAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between font-bold">
|
||||||
|
<span>实收金额</span>
|
||||||
|
<span>¥{{ bill.actualAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>已扣款金额</span>
|
||||||
|
<span class="font-bold text-blue-500">¥{{ bill.paidAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-xs text-gray-400">
|
||||||
|
预充值模式:加氢时已从账户实时扣款,账单仅作对账凭据
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<!-- 月结算模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>应收金额</span>
|
||||||
|
<span>¥{{ bill.receivableAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>调整金额</span>
|
||||||
|
<span class="text-red-500">¥{{ bill.adjustmentAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between font-bold">
|
||||||
|
<span>实收金额</span>
|
||||||
|
<span>¥{{ bill.actualAmount ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>已付款 / 待付款</span>
|
||||||
|
<span class="font-bold text-pink-500">
|
||||||
|
¥{{ bill.paidAmount ?? 0 }} /
|
||||||
|
¥{{ ((bill.actualAmount ?? 0) - (bill.paidAmount ?? 0)).toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-xs text-gray-400">
|
||||||
|
月结算模式:账单审核后发送客户,客户按账单付款
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="px-4">
|
||||||
|
<Tabs v-model:activeKey="activeTab">
|
||||||
|
<Tabs.TabPane key="detail" tab="加氢明细">
|
||||||
|
<a-table
|
||||||
|
:data-source="details"
|
||||||
|
:columns="detailTableColumns"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
row-key="id"
|
||||||
|
/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane key="adjustment" tab="调整记录">
|
||||||
|
<a-table
|
||||||
|
:data-source="adjustments"
|
||||||
|
:columns="adjustmentTableColumns"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
row-key="id"
|
||||||
|
/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane key="log" tab="操作日志">
|
||||||
|
<Timeline>
|
||||||
|
<Timeline.Item>账单创建于 {{ bill.createTime || '—' }}</Timeline.Item>
|
||||||
|
<Timeline.Item v-if="bill.auditTime">
|
||||||
|
账单审核于 {{ bill.auditTime }}
|
||||||
|
<span v-if="bill.auditRemark">({{ bill.auditRemark }})</span>
|
||||||
|
</Timeline.Item>
|
||||||
|
</Timeline>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Audit Modal -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="auditVisible"
|
||||||
|
title="账单审核"
|
||||||
|
:confirm-loading="auditLoading"
|
||||||
|
@ok="handleAudit"
|
||||||
|
@cancel="auditVisible = false"
|
||||||
|
>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">审核结果</label>
|
||||||
|
<Radio.Group v-model:value="auditApproved">
|
||||||
|
<Radio :value="true">通过</Radio>
|
||||||
|
<Radio :value="false">驳回</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block font-medium">审核备注</label>
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="auditRemark"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入审核备注(选填)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user