feat(finance): 实现发票管理页面与子页面

This commit is contained in:
2026-03-04 15:58:08 +08:00
parent e0bef7259a
commit 0c7adc149b
28 changed files with 3323 additions and 158 deletions

View File

@@ -0,0 +1,397 @@
/**
* 文件职责:发票管理页面状态与动作编排。
*/
import type {
FinanceInvoiceIssueResultDto,
FinanceInvoiceRecordDetailDto,
FinanceInvoiceRecordListItemDto,
FinanceInvoiceStatsDto,
} from '#/api/finance';
import type {
FinanceInvoiceFilterState,
FinanceInvoiceIssueFormState,
FinanceInvoicePaginationState,
FinanceInvoiceSettingFormState,
FinanceInvoiceTabKey,
FinanceInvoiceVoidFormState,
} from '../types';
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import {
createDefaultFilters,
createDefaultIssueForm,
createDefaultPagination,
createDefaultSettingsForm,
createDefaultVoidForm,
DEFAULT_INVOICE_STATS,
FINANCE_INVOICE_ISSUE_PERMISSION,
FINANCE_INVOICE_SETTINGS_PERMISSION,
FINANCE_INVOICE_VIEW_PERMISSION,
FINANCE_INVOICE_VOID_PERMISSION,
INVOICE_TAB_OPTIONS,
} from './invoice-page/constants';
import { createDataActions } from './invoice-page/data-actions';
import { createDrawerActions } from './invoice-page/drawer-actions';
import { isDateRangeInvalid } from './invoice-page/helpers';
import { createSettingsActions } from './invoice-page/settings-actions';
/** 创建发票管理页面组合状态。 */
export function useFinanceInvoicePage() {
const accessStore = useAccessStore();
const activeTab = ref<FinanceInvoiceTabKey>('records');
const filters = reactive<FinanceInvoiceFilterState>(
createDefaultFilters() as FinanceInvoiceFilterState,
);
const pagination = reactive<FinanceInvoicePaginationState>(
createDefaultPagination(),
);
const rows = ref<FinanceInvoiceRecordListItemDto[]>([]);
const stats = reactive<FinanceInvoiceStatsDto>({ ...DEFAULT_INVOICE_STATS });
const settingForm = reactive<FinanceInvoiceSettingFormState>(
createDefaultSettingsForm(),
);
const settingSnapshot = ref<FinanceInvoiceSettingFormState>(
createDefaultSettingsForm(),
);
const issueForm = reactive<FinanceInvoiceIssueFormState>(
createDefaultIssueForm(),
);
const voidForm = reactive<FinanceInvoiceVoidFormState>(createDefaultVoidForm());
const detail = ref<FinanceInvoiceRecordDetailDto | null>(null);
const issueDetail = ref<FinanceInvoiceRecordDetailDto | null>(null);
const issueResult = ref<FinanceInvoiceIssueResultDto | null>(null);
const issueTargetRecord = ref<FinanceInvoiceRecordListItemDto | null>(null);
const voidTargetRecord = ref<FinanceInvoiceRecordListItemDto | null>(null);
const isListLoading = ref(false);
const isSettingsLoading = ref(false);
const isDetailLoading = ref(false);
const isIssueDetailLoading = ref(false);
const isIssueSubmitting = ref(false);
const isVoidSubmitting = ref(false);
const isSettingsSaving = ref(false);
const isDetailDrawerOpen = ref(false);
const isIssueDrawerOpen = ref(false);
const isVoidModalOpen = ref(false);
const isIssueResultModalOpen = ref(false);
const isSettingsConfirmModalOpen = ref(false);
const accessCodeSet = computed(
() => new Set((accessStore.accessCodes ?? []).map(String)),
);
const canViewRecords = computed(() => {
const accessCodes = accessCodeSet.value;
return (
accessCodes.has(FINANCE_INVOICE_VIEW_PERMISSION) ||
accessCodes.has(FINANCE_INVOICE_ISSUE_PERMISSION) ||
accessCodes.has(FINANCE_INVOICE_VOID_PERMISSION)
);
});
const canIssue = computed(() =>
accessCodeSet.value.has(FINANCE_INVOICE_ISSUE_PERMISSION),
);
const canVoid = computed(() =>
accessCodeSet.value.has(FINANCE_INVOICE_VOID_PERMISSION),
);
const canViewSettings = computed(() => {
const accessCodes = accessCodeSet.value;
return (
accessCodes.has(FINANCE_INVOICE_VIEW_PERMISSION) ||
accessCodes.has(FINANCE_INVOICE_SETTINGS_PERMISSION)
);
});
const canManageSettings = computed(() =>
accessCodeSet.value.has(FINANCE_INVOICE_SETTINGS_PERMISSION),
);
const canViewPage = computed(
() => canViewRecords.value || canViewSettings.value,
);
const tabOptions = computed(() =>
INVOICE_TAB_OPTIONS.filter((item) => {
if (item.value === 'records') {
return canViewRecords.value;
}
if (item.value === 'settings') {
return canViewSettings.value;
}
return false;
}),
);
const {
applySettingsResult,
clearByPermission,
clearRecordData,
loadRecordList,
loadSettings,
} = createDataActions({
canViewRecords,
canViewSettings,
createDefaultSettingsForm,
filters,
isListLoading,
isSettingsLoading,
pagination,
rows,
settingForm,
settingSnapshot,
stats,
});
const {
openDetail,
openIssue,
openVoid,
setDetailDrawerOpen,
setIssueDrawerOpen,
setIssueResultModalOpen,
setVoidModalOpen,
submitIssue,
submitVoid,
} = createDrawerActions({
canIssue,
canVoid,
detail,
isDetailDrawerOpen,
isDetailLoading,
isIssueDetailLoading,
isIssueDrawerOpen,
isIssueResultModalOpen,
isIssueSubmitting,
isVoidModalOpen,
isVoidSubmitting,
issueDetail,
issueForm,
issueResult,
issueTargetRecord,
reloadRecordList: loadRecordList,
voidForm,
voidTargetRecord,
});
const {
hasSettingsChanged,
openSettingsConfirmModal,
resetSettings,
setSettingsConfirmModalOpen,
submitSettings,
} = createSettingsActions({
applySettingsResult,
canManageSettings,
isSettingsConfirmModalOpen,
isSettingsSaving,
settingForm,
settingSnapshot,
});
function setActiveTab(value: FinanceInvoiceTabKey) {
activeTab.value = value;
}
function setStartDate(value: string) {
filters.startDate = value;
}
function setEndDate(value: string) {
filters.endDate = value;
}
function setStatus(value: string) {
filters.status = (value || 'all') as FinanceInvoiceFilterState['status'];
}
function setInvoiceType(value: string) {
filters.invoiceType = (value || 'all') as FinanceInvoiceFilterState['invoiceType'];
}
function setKeyword(value: string) {
filters.keyword = value;
}
function setIssueFormContactEmail(value: string) {
issueForm.contactEmail = value;
}
function setIssueFormRemark(value: string) {
issueForm.issueRemark = value;
}
function setVoidReason(value: string) {
voidForm.voidReason = value;
}
function setSettingsField<K extends keyof FinanceInvoiceSettingFormState>(
field: K,
value: FinanceInvoiceSettingFormState[K],
) {
settingForm[field] = value;
}
async function handleSearch() {
if (isDateRangeInvalid(filters)) {
message.warning('开始日期不能晚于结束日期');
return;
}
pagination.page = 1;
await loadRecordList();
}
async function handlePageChange(page: number, pageSize: number) {
pagination.page = page;
pagination.pageSize = pageSize;
await loadRecordList();
}
function closeTransientPanels() {
setDetailDrawerOpen(false);
setIssueDrawerOpen(false);
setVoidModalOpen(false);
setIssueResultModalOpen(false);
setSettingsConfirmModalOpen(false);
}
async function loadByPermission() {
const tasks: Array<Promise<void>> = [];
if (canViewRecords.value) {
tasks.push(loadRecordList());
} else {
clearRecordData();
}
if (canViewSettings.value) {
tasks.push(loadSettings());
}
await Promise.all(tasks);
}
watch(tabOptions, (next) => {
const values = next.map((item) => item.value);
if (values.length === 0) {
return;
}
if (!values.includes(activeTab.value)) {
activeTab.value = values[0] as FinanceInvoiceTabKey;
}
});
watch(
canViewPage,
async (value, oldValue) => {
if (value === oldValue) {
return;
}
if (!value) {
clearByPermission();
closeTransientPanels();
return;
}
await loadByPermission();
},
{ immediate: false },
);
onMounted(async () => {
if (!canViewPage.value) {
clearByPermission();
closeTransientPanels();
return;
}
await loadByPermission();
});
onActivated(() => {
if (!canViewPage.value) {
return;
}
void loadByPermission();
});
return {
activeTab,
canIssue,
canManageSettings,
canViewPage,
canViewRecords,
canViewSettings,
canVoid,
detail,
filters,
handlePageChange,
handleSearch,
hasSettingsChanged,
isDetailDrawerOpen,
isDetailLoading,
isIssueDetailLoading,
isIssueDrawerOpen,
isIssueResultModalOpen,
isIssueSubmitting,
isListLoading,
isSettingsConfirmModalOpen,
isSettingsLoading,
isSettingsSaving,
isVoidModalOpen,
isVoidSubmitting,
issueDetail,
issueForm,
issueResult,
issueTargetRecord,
openDetail,
openIssue,
openSettingsConfirmModal,
openVoid,
pagination,
resetSettings,
rows,
setActiveTab,
setDetailDrawerOpen,
setEndDate,
setInvoiceType,
setIssueDrawerOpen,
setIssueFormContactEmail,
setIssueFormRemark,
setIssueResultModalOpen,
setKeyword,
setSettingsConfirmModalOpen,
setSettingsField,
setStartDate,
setStatus,
setVoidModalOpen,
setVoidReason,
settingForm,
stats,
submitIssue,
submitSettings,
submitVoid,
tabOptions,
voidForm,
voidTargetRecord,
};
}