diff --git a/apps/web-antd/src/api/energy/bill.ts b/apps/web-antd/src/api/energy/bill.ts index 790d33d..4bdb873 100644 --- a/apps/web-antd/src/api/energy/bill.ts +++ b/apps/web-antd/src/api/energy/bill.ts @@ -34,14 +34,21 @@ export namespace EnergyBillApi { } export interface GenerateReq { - customerId: number; - contractId: number; - stationId: number; - billPeriodStart: string; - billPeriodEnd: string; + startDate?: string; + endDate?: string; + customerId?: number; + contractId?: number; + stationId?: number; + billPeriodStart?: string; + billPeriodEnd?: string; energyType?: number; } + export interface GenerateResult { + total?: number; + billIds?: number[]; + } + export interface Adjustment { id?: number; billId?: number; @@ -72,7 +79,7 @@ export function getBill(id: number) { } export function generateBill(data: EnergyBillApi.GenerateReq) { - return requestClient.post('/energy/bill/generate', data); + return requestClient.post('/energy/bill/generate', data); } export function batchGenerateByPeriod(billPeriod: string) { diff --git a/apps/web-antd/src/api/energy/hydrogen-detail.ts b/apps/web-antd/src/api/energy/hydrogen-detail.ts index 1a8d477..dfaacd4 100644 --- a/apps/web-antd/src/api/energy/hydrogen-detail.ts +++ b/apps/web-antd/src/api/energy/hydrogen-detail.ts @@ -51,10 +51,22 @@ export function auditHydrogenDetail(id: number, approved: boolean, remark?: stri }); } -export function batchAuditHydrogenDetail(ids: number[], approved: boolean, remark?: string) { - return requestClient.post('/energy/hydrogen-detail/batch-audit', null, { - params: { ids: ids.join(','), approved, remark }, - }); +export interface BatchAuditReqVO { + ids: number[]; + passed: boolean; + remark?: string; +} + +export interface BatchAuditResultDTO { + total: number; + successCount: number; + failCount: number; + successIds: number[]; + failIds: number[]; +} + +export function batchAuditHydrogenDetail(data: BatchAuditReqVO) { + return requestClient.post('/energy/hydrogen-detail/batch-audit', data); } export function exportHydrogenDetail(params: any) { diff --git a/apps/web-antd/src/api/energy/hydrogen-record.ts b/apps/web-antd/src/api/energy/hydrogen-record.ts index f975953..41d8a68 100644 --- a/apps/web-antd/src/api/energy/hydrogen-record.ts +++ b/apps/web-antd/src/api/energy/hydrogen-record.ts @@ -21,38 +21,7 @@ export namespace EnergyHydrogenRecordApi { createTime?: string; } - export interface ImportPreview { - batchNo: string; - totalCount: number; - validCount: number; - duplicateCount: number; - errorCount: number; - records: RecordPreviewItem[]; - duplicates: RecordPreviewItem[]; - errors: ImportErrorItem[]; - } - export interface RecordPreviewItem { - rowNum: number; - plateNumber: string; - hydrogenDate: string; - hydrogenQuantity: number; - unitPrice: number; - amount: number; - mileage: number; - isDuplicate: boolean; - } - - export interface ImportErrorItem { - rowNum: number; - reason: string; - } - - export interface ImportProgress { - current: number; - total: number; - status: string; - } } export function getHydrogenRecordPage(params: PageParam) { @@ -85,31 +54,17 @@ export function exportHydrogenRecord(params: any) { return requestClient.download('/energy/hydrogen-record/export-excel', { params }); } -export function importPreview(stationId: number, file: File) { - const formData = new FormData(); - formData.append('file', file); - formData.append('stationId', String(stationId)); - return requestClient.post( - '/energy/hydrogen-record/import-preview', - formData, - ); +export interface ImportResultDTO { + total: number; + successCount: number; + failCount: number; + successIds: number[]; + failIds: number[]; } -export function importConfirm(batchNo: string, duplicateStrategy: string) { - return requestClient.post>( - '/energy/hydrogen-record/import-confirm', - null, - { params: { batchNo, duplicateStrategy } }, +export function importHydrogenRecords(data: FormData) { + return requestClient.post( + '/energy/hydrogen-record/import', + data, ); } - -export function getImportProgress(batchNo: string) { - return requestClient.get( - '/energy/hydrogen-record/import-progress', - { params: { batchNo } }, - ); -} - -export function batchMatch() { - return requestClient.post>('/energy/hydrogen-record/batch-match'); -} diff --git a/apps/web-antd/src/api/energy/station-price.ts b/apps/web-antd/src/api/energy/station-price.ts index 880a1e1..131d0b5 100644 --- a/apps/web-antd/src/api/energy/station-price.ts +++ b/apps/web-antd/src/api/energy/station-price.ts @@ -36,3 +36,15 @@ export function updateStationPrice(data: EnergyStationPriceApi.Price) { export function deleteStationPrice(id: number) { return requestClient.delete('/energy/station-price/delete', { params: { id } }); } + +export interface ImportResult { + total: number; + successCount: number; + failCount: number; +} + +export function batchImportPrice(data: FormData) { + return requestClient.post('/energy/station-price/batch-import', data, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +} diff --git a/apps/web-antd/src/views/energy/bill/data.ts b/apps/web-antd/src/views/energy/bill/data.ts index 3299207..13cfff0 100644 --- a/apps/web-antd/src/views/energy/bill/data.ts +++ b/apps/web-antd/src/views/energy/bill/data.ts @@ -48,14 +48,14 @@ export function useGridFormSchema(): VbenFormSchema[] { }, }, { - fieldName: 'billPeriod', - label: '账单周期', - component: 'DatePicker', + fieldName: 'dateRange', + label: '账单时间', + component: 'RangePicker', componentProps: { picker: 'month', format: 'YYYY-MM', valueFormat: 'YYYY-MM', - placeholder: '选择月份', + placeholder: ['开始月份', '结束月份'], allowClear: true, class: 'w-full', }, @@ -137,7 +137,7 @@ export function useGridColumns(): VxeTableGridOptions['colum align: 'center', formatter: ({ row }: { row: EnergyBillApi.Bill }) => { if (row.billPeriodStart) { - return row.billPeriodStart.substring(0, 7); + return String(row.billPeriodStart).substring(0, 7); } return '—'; }, diff --git a/apps/web-antd/src/views/energy/bill/index.vue b/apps/web-antd/src/views/energy/bill/index.vue index afd0b9a..f45f5a8 100644 --- a/apps/web-antd/src/views/energy/bill/index.vue +++ b/apps/web-antd/src/views/energy/bill/index.vue @@ -8,12 +8,14 @@ import { useRouter } from 'vue-router'; import { Page } from '@vben/common-ui'; import { downloadFileFromBlobPart } from '@vben/utils'; -import { Tag, message } from 'ant-design-vue'; +import { Modal, Tag, message } from 'ant-design-vue'; +import dayjs from 'dayjs'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { deleteBill, exportBill, + generateBill, getBillPage, } from '#/api/energy/bill'; import { $t } from '#/locales'; @@ -43,6 +45,41 @@ async function handleExport() { downloadFileFromBlobPart({ fileName: '能源账单.xlsx', source: blob }); } +/** 快速生成账单 */ +function handleQuickGenerate(type: 'current' | 'last') { + const now = dayjs(); + let startDate: string; + let endDate: string; + let title: string; + + if (type === 'current') { + startDate = now.startOf('month').format('YYYY-MM-DD'); + endDate = now.format('YYYY-MM-DD'); + title = '本月'; + } else { + startDate = now.subtract(1, 'month').startOf('month').format('YYYY-MM-DD'); + endDate = now.subtract(1, 'month').endOf('month').format('YYYY-MM-DD'); + title = '上月'; + } + + Modal.confirm({ + title: `生成${title}账单`, + content: `确定要生成 ${startDate} 至 ${endDate} 的账单吗?`, + onOk: async () => { + try { + const res = await generateBill({ + startDate, + endDate, + }); + message.success(`账单生成成功!共生成 ${res.total || 0} 张账单`); + handleRefresh(); + } catch (error: any) { + message.error('生成账单失败:' + (error.message || '未知错误')); + } + }, + }); +} + /** 生成账单 */ function handleGenerate() { generateModalRef.value?.open('single'); @@ -119,18 +156,24 @@ const [Grid, gridApi] = useVbenVxeGrid({ -import { ref } from 'vue'; +import { ref, watch } from 'vue'; -import { Alert, DatePicker, InputNumber, message, Modal } from 'ant-design-vue'; +import { Alert, Button, DatePicker, message, Modal, RangePicker, Select, Space } from 'ant-design-vue'; import dayjs from 'dayjs'; import { @@ -9,6 +9,9 @@ import { generateBill, } from '#/api/energy/bill'; import type { EnergyBillApi } from '#/api/energy/bill'; +import { getStationConfigSimpleList } from '#/api/energy/station-config'; +import { getSimpleCustomerList } from '#/api/asset/customer'; +import { getSimpleContractList } from '#/api/asset/contract'; const emit = defineEmits(['success']); const visible = ref(false); @@ -19,41 +22,119 @@ const mode = ref<'batch' | 'single'>('single'); const customerId = ref(); const contractId = ref(); const stationId = ref(); +const dateRange = ref(); const billPeriod = ref(); +// Dropdown options +const customerOptions = ref<{ label: string; value: number }[]>([]); +const contractOptions = ref<{ label: string; value: number }[]>([]); +const filteredContractOptions = ref<{ label: string; value: number; customerId?: number }[]>([]); +const stationOptions = ref<{ label: string; value: number }[]>([]); + +async function loadOptions() { + const [customers, contracts, stations] = await Promise.all([ + getSimpleCustomerList(), + getSimpleContractList(), + getStationConfigSimpleList(), + ]); + customerOptions.value = customers.map((c) => ({ + label: c.customerName, + value: c.id!, + })); + contractOptions.value = contracts.map((c) => ({ + label: `${c.contractCode} - ${c.projectName}`, + value: c.id!, + customerId: c.customerId, + })); + filteredContractOptions.value = contractOptions.value; + stationOptions.value = stations.map((s) => ({ + label: s.stationName, + value: s.stationId, + })); +} + +// 选择客户后,筛选该客户下的合同 +watch(customerId, (val) => { + contractId.value = undefined; + if (val) { + filteredContractOptions.value = contractOptions.value.filter( + (c) => (c as any).customerId === val, + ); + } else { + filteredContractOptions.value = contractOptions.value; + } +}); + +// 快捷时间选择 +function setDateRange(type: string) { + const now = dayjs(); + + switch (type) { + case 'today': + dateRange.value = [now, now]; + break; + case 'yesterday': + dateRange.value = [now.subtract(1, 'day'), now.subtract(1, 'day')]; + break; + case 'thisWeek': + dateRange.value = [now.startOf('week'), now]; + break; + case 'lastWeek': + dateRange.value = [ + now.subtract(1, 'week').startOf('week'), + now.subtract(1, 'week').endOf('week'), + ]; + break; + case 'thisMonth': + dateRange.value = [now.startOf('month'), now]; + break; + case 'lastMonth': + dateRange.value = [ + now.subtract(1, 'month').startOf('month'), + now.subtract(1, 'month').endOf('month'), + ]; + break; + } +} + function open(m: 'batch' | 'single' = 'single') { mode.value = m; customerId.value = undefined; contractId.value = undefined; stationId.value = undefined; + dateRange.value = undefined; billPeriod.value = undefined; visible.value = true; + if (m === 'single') { + loadOptions(); + } } async function handleConfirm() { - if (!billPeriod.value) { - message.warning('请选择账单周期'); - return; - } loading.value = true; try { - const periodStr = dayjs(billPeriod.value).format('YYYY-MM'); if (mode.value === 'single') { - if (!customerId.value || !stationId.value) { - message.warning('请填写完整信息'); + if (!dateRange.value || dateRange.value.length !== 2) { + message.warning('请选择时间范围'); loading.value = false; return; } const data: EnergyBillApi.GenerateReq = { - customerId: customerId.value, + customerId: customerId.value!, contractId: contractId.value!, - stationId: stationId.value, - billPeriodStart: dayjs(billPeriod.value).startOf('month').format('YYYY-MM-DD'), - billPeriodEnd: dayjs(billPeriod.value).endOf('month').format('YYYY-MM-DD'), + stationId: stationId.value!, + billPeriodStart: dayjs(dateRange.value[0]).format('YYYY-MM-DD'), + billPeriodEnd: dayjs(dateRange.value[1]).format('YYYY-MM-DD'), }; await generateBill(data); message.success('账单生成成功'); } else { + if (!billPeriod.value) { + message.warning('请选择账单周期'); + loading.value = false; + return; + } + const periodStr = dayjs(billPeriod.value).format('YYYY-MM'); const result = await batchGenerateByPeriod(periodStr); const generated = result.generatedCount ?? 0; const skipped = result.skippedCount ?? 0; @@ -74,7 +155,7 @@ defineExpose({ open }); -
- - +
+ + +
+ -
- - + diff --git a/apps/web-antd/src/views/energy/hydrogen-detail/index.vue b/apps/web-antd/src/views/energy/hydrogen-detail/index.vue index e3301c6..319cace 100644 --- a/apps/web-antd/src/views/energy/hydrogen-detail/index.vue +++ b/apps/web-antd/src/views/energy/hydrogen-detail/index.vue @@ -42,12 +42,26 @@ async function handleExport() { /** 批量审核 */ function handleBatchAudit() { - const selected = checkedRecords.value.filter((r) => r.auditStatus === 0); - if (selected.length === 0) { - message.warning('请选择待审核记录'); + if (checkedRecords.value.length === 0) { + message.warning('请选择要审核的明细'); return; } - auditModalRef.value?.open(selected); + + // 过滤出待审核的记录 + const pendingRecords = checkedRecords.value.filter((r) => r.auditStatus === 0); + + if (pendingRecords.length === 0) { + message.warning('所选记录中没有待审核的明细'); + return; + } + + if (pendingRecords.length < checkedRecords.value.length) { + message.warning( + `已过滤 ${checkedRecords.value.length - pendingRecords.length} 条非待审核记录` + ); + } + + auditModalRef.value?.open(pendingRecords); } /** 单条审核 */ diff --git a/apps/web-antd/src/views/energy/hydrogen-detail/modules/audit-modal.vue b/apps/web-antd/src/views/energy/hydrogen-detail/modules/audit-modal.vue index 8c2e393..da8ee3e 100644 --- a/apps/web-antd/src/views/energy/hydrogen-detail/modules/audit-modal.vue +++ b/apps/web-antd/src/views/energy/hydrogen-detail/modules/audit-modal.vue @@ -5,7 +5,7 @@ import { computed, ref } from 'vue'; import { Descriptions, Input, message, Modal, Radio } from 'ant-design-vue'; -import { auditHydrogenDetail } from '#/api/energy/hydrogen-detail'; +import { auditHydrogenDetail, batchAuditHydrogenDetail } from '#/api/energy/hydrogen-detail'; const emit = defineEmits(['success']); @@ -28,18 +28,32 @@ function open(data: EnergyHydrogenDetailApi.Detail[]) { } async function handleConfirm() { + if (!approved.value && !remark.value) { + message.warning('驳回时必须填写审核备注'); + return; + } + loading.value = true; try { - let successCount = 0; - for (const record of records.value) { + if (isBatch.value) { + // 批量审核:调用批量接口 + const result = await batchAuditHydrogenDetail({ + ids: records.value.map(r => r.id!), + passed: approved.value, + remark: remark.value || undefined, + }); + message.success( + `审核完成!成功 ${result.successCount} 条,失败 ${result.failCount} 条` + ); + } else { + // 单条审核:调用单条接口 await auditHydrogenDetail( - record.id!, + records.value[0].id!, approved.value, remark.value || undefined, ); - successCount++; + message.success('审核完成'); } - message.success(`审核完成,共 ${successCount} 条`); visible.value = false; emit('success'); } catch (e: any) { @@ -92,16 +106,19 @@ defineExpose({ open }); 通过 - 驳回 + 驳回
- +
diff --git a/apps/web-antd/src/views/energy/hydrogen-record/data.ts b/apps/web-antd/src/views/energy/hydrogen-record/data.ts index 5589495..759e046 100644 --- a/apps/web-antd/src/views/energy/hydrogen-record/data.ts +++ b/apps/web-antd/src/views/energy/hydrogen-record/data.ts @@ -5,6 +5,106 @@ import type { EnergyHydrogenRecordApi } from '#/api/energy/hydrogen-record'; import { useActionColumn } from '#/utils/table'; import { getRangePickerDefaultProps } from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'stationId', + label: '加氢站', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择加氢站', + api: () => + import('#/api/energy/station-config').then( + (m) => m.getStationConfigSimpleList(), + ), + labelField: 'stationName', + valueField: 'stationId', + showSearch: true, + filterOption: (input: string, option: any) => + option.label?.toLowerCase().includes(input.toLowerCase()), + }, + rules: 'required', + }, + { + fieldName: 'plateNumber', + label: '车牌号', + component: 'Input', + componentProps: { + placeholder: '请输入车牌号', + }, + rules: 'required', + }, + { + fieldName: 'hydrogenDate', + label: '加氢日期', + component: 'DatePicker', + componentProps: { + placeholder: '请选择加氢日期', + format: 'YYYY-MM-DD', + valueFormat: 'YYYY-MM-DD', + style: { width: '100%' }, + }, + rules: 'required', + }, + { + fieldName: 'hydrogenQuantity', + label: '加氢量(KG)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入加氢量', + min: 0, + precision: 2, + style: { width: '100%' }, + }, + rules: 'required', + }, + { + fieldName: 'unitPrice', + label: '单价(元/KG)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入单价', + min: 0, + precision: 2, + style: { width: '100%' }, + }, + rules: 'required', + }, + { + fieldName: 'amount', + label: '金额', + component: 'InputNumber', + componentProps: { + placeholder: '请输入金额', + min: 0, + precision: 2, + style: { width: '100%' }, + }, + rules: 'required', + }, + { + fieldName: 'mileage', + label: '里程(KM)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入里程数', + min: 0, + precision: 2, + style: { width: '100%' }, + }, + }, + ]; +} + export const SOURCE_TYPE_OPTIONS = [ { label: 'Excel导入', value: 1, color: 'blue' }, { label: 'API同步', value: 2, color: 'green' }, @@ -12,9 +112,9 @@ export const SOURCE_TYPE_OPTIONS = [ ]; export const MATCH_STATUS_OPTIONS = [ - { label: '待匹配', value: 0, color: 'orange' }, - { label: '已匹配', value: 1, color: 'green' }, - { label: '匹配失败', value: 2, color: 'red' }, + { label: '完全匹配', value: 0, color: 'green' }, + { label: '部分匹配', value: 1, color: 'orange' }, + { label: '未匹配', value: 2, color: 'red' }, ]; /** 搜索表单 */ @@ -23,9 +123,18 @@ export function useGridFormSchema(): VbenFormSchema[] { { fieldName: 'stationId', label: '加氢站', - component: 'Input', + component: 'ApiSelect', componentProps: { - placeholder: '请输入加氢站', + placeholder: '请选择加氢站', + api: () => + import('#/api/energy/station-config').then( + (m) => m.getStationConfigSimpleList(), + ), + labelField: 'stationName', + valueField: 'stationId', + showSearch: true, + filterOption: (input: string, option: any) => + option.label?.toLowerCase().includes(input.toLowerCase()), allowClear: true, }, }, diff --git a/apps/web-antd/src/views/energy/hydrogen-record/index.vue b/apps/web-antd/src/views/energy/hydrogen-record/index.vue index 953636f..554c0e7 100644 --- a/apps/web-antd/src/views/energy/hydrogen-record/index.vue +++ b/apps/web-antd/src/views/energy/hydrogen-record/index.vue @@ -11,7 +11,6 @@ import { Tag, message } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { - batchMatch, deleteHydrogenRecord, exportHydrogenRecord, getHydrogenRecordPage, @@ -25,8 +24,10 @@ import { useGridFormSchema, } from './data'; import ImportModal from './modules/import-modal.vue'; +import FormModal from './modules/form-modal.vue'; const importModalRef = ref>(); +const formModalRef = ref>(); /** 刷新表格 */ function handleRefresh() { @@ -45,18 +46,9 @@ async function handleExport() { downloadFileFromBlobPart({ fileName: '加氢记录.xlsx', source: blob }); } -/** 批量匹配 */ -async function handleBatchMatch() { - const hideLoading = message.loading({ content: '正在匹配...', duration: 0 }); - try { - const result = await batchMatch(); - message.success( - `匹配完成:成功 ${result.successCount} 条,失败 ${result.failCount} 条`, - ); - handleRefresh(); - } finally { - hideLoading(); - } +/** 编辑记录 */ +function handleEdit(row: EnergyHydrogenRecordApi.Record) { + formModalRef.value?.open(row); } /** 删除记录 */ @@ -100,6 +92,7 @@ const [Grid, gridApi] = useVbenVxeGrid({