feat(@vben/web-antd): add finance overview cockpit and fee rate
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 文件职责:财务概览页面状态编排。
|
||||
*/
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user