250 lines
5.5 KiB
TypeScript
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,
|
|
};
|
|
}
|