Files
TakeoutSaaS.TenantUI/apps/web-antd/src/views/finance/overview/composables/useFinanceOverviewPage.ts

250 lines
5.5 KiB
TypeScript

/**
* 文件职责:财务概览页面状态编排。
*/
import type {
FinanceOverviewCompositionViewItem,
FinanceOverviewDashboardState,
FinanceOverviewFilterState,
FinanceOverviewTrendState,
OptionItem,
} from '../types';
import type {
FinanceOverviewDimension,
FinanceOverviewTrendRange,
} from '#/api/finance/overview';
import type { StoreListItemDto } from '#/api/store';
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
import { useAccessStore } from '@vben/stores';
import {
createDefaultFinanceOverviewDashboard,
DEFAULT_OVERVIEW_FILTER,
DEFAULT_OVERVIEW_TREND_STATE,
FINANCE_OVERVIEW_VIEW_PERMISSION,
OVERVIEW_DIMENSION_OPTIONS,
} from './overview-page/constants';
import { createDataActions } from './overview-page/data-actions';
import {
toCostCompositionViewItems,
toIncomeCompositionViewItems,
} from './overview-page/helpers';
/** 创建财务概览页面组合状态。 */
export function useFinanceOverviewPage() {
const accessStore = useAccessStore();
const stores = ref<StoreListItemDto[]>([]);
const filters = reactive<FinanceOverviewFilterState>({
...DEFAULT_OVERVIEW_FILTER,
});
const trendState = reactive<FinanceOverviewTrendState>({
...DEFAULT_OVERVIEW_TREND_STATE,
});
const dashboard = reactive<FinanceOverviewDashboardState>(
createDefaultFinanceOverviewDashboard(),
);
const isStoreLoading = ref(false);
const isDashboardLoading = ref(false);
const accessCodeSet = computed(
() => new Set((accessStore.accessCodes ?? []).map(String)),
);
const canView = computed(() =>
accessCodeSet.value.has(FINANCE_OVERVIEW_VIEW_PERMISSION),
);
const storeOptions = computed<OptionItem[]>(() =>
stores.value.map((item) => ({
label: item.name,
value: item.id,
})),
);
const showStoreSelect = computed(() => filters.dimension === 'store');
const hasStore = computed(() => stores.value.length > 0);
const canQueryCurrentScope = computed(
() => filters.dimension === 'tenant' || Boolean(filters.storeId),
);
const incomeTrendPoints = computed(() =>
trendState.incomeRange === '7'
? dashboard.incomeTrend.last7Days
: dashboard.incomeTrend.last30Days,
);
const profitTrendPoints = computed(() =>
trendState.profitRange === '7'
? dashboard.profitTrend.last7Days
: dashboard.profitTrend.last30Days,
);
const incomeCompositionItems = computed<FinanceOverviewCompositionViewItem[]>(
() => toIncomeCompositionViewItems(dashboard.incomeComposition.items),
);
const costCompositionItems = computed<FinanceOverviewCompositionViewItem[]>(
() => toCostCompositionViewItems(dashboard.costComposition.items),
);
const topProductMaxPercentage = computed(() => {
if (dashboard.topProducts.items.length === 0) {
return 0;
}
return Math.max(
...dashboard.topProducts.items.map((item) => item.percentage),
);
});
const dataActions = createDataActions({
stores,
filters,
dashboard,
isStoreLoading,
isDashboardLoading,
});
function ensureStoreSelection() {
if (filters.dimension !== 'store') {
return false;
}
if (filters.storeId) {
return false;
}
const firstStore = stores.value[0];
if (!firstStore) {
return false;
}
filters.storeId = firstStore.id;
return true;
}
async function loadByCurrentScope() {
if (!canView.value) {
return;
}
if (filters.dimension === 'store' && !filters.storeId) {
dataActions.clearDashboard();
return;
}
await dataActions.loadDashboard();
}
async function initPageData() {
if (!canView.value) {
stores.value = [];
filters.storeId = '';
dataActions.clearDashboard();
return;
}
await dataActions.loadStores();
if (ensureStoreSelection()) {
return;
}
await loadByCurrentScope();
}
function setDimension(value: FinanceOverviewDimension) {
filters.dimension = value;
}
function setStoreId(value: string) {
filters.storeId = value;
}
function setIncomeRange(value: FinanceOverviewTrendRange) {
trendState.incomeRange = value;
}
function setProfitRange(value: FinanceOverviewTrendRange) {
trendState.profitRange = value;
}
watch(
() => [filters.dimension, filters.storeId],
async () => {
if (!canView.value) {
return;
}
if (filters.dimension === 'store' && ensureStoreSelection()) {
return;
}
await loadByCurrentScope();
},
);
watch(
() => canView.value,
async (value, oldValue) => {
if (value === oldValue) {
return;
}
if (!value) {
stores.value = [];
filters.storeId = '';
dataActions.clearDashboard();
return;
}
await initPageData();
},
);
onMounted(async () => {
await initPageData();
});
onActivated(() => {
if (!canView.value) {
return;
}
if (stores.value.length === 0) {
void initPageData();
return;
}
if (!showStoreSelect.value || filters.storeId) {
void loadByCurrentScope();
}
});
return {
canQueryCurrentScope,
canView,
costCompositionItems,
dashboard,
hasStore,
incomeCompositionItems,
incomeTrendPoints,
isDashboardLoading,
isStoreLoading,
OVERVIEW_DIMENSION_OPTIONS,
profitTrendPoints,
showStoreSelect,
storeOptions,
topProductMaxPercentage,
trendState,
filters,
setDimension,
setStoreId,
setIncomeRange,
setProfitRange,
};
}