feat(@vben/web-antd): 客户画像页面与二级抽屉

This commit is contained in:
2026-03-03 14:41:04 +08:00
parent 543b82ab5e
commit 4fe8bbdba7
38 changed files with 3591 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
import { computed, onActivated, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import type { CustomerProfileDto } from '#/api/customer';
import type { StoreListItemDto } from '#/api/store';
import { createDefaultListFilters } from './customer-profile-page/constants';
import { createDataActions } from './customer-profile-page/data-actions';
import {
buildRouteQuery,
parseRouteQueryValue,
} from './customer-profile-page/helpers';
export function useCustomerProfilePage() {
const route = useRoute();
const router = useRouter();
const stores = ref<StoreListItemDto[]>([]);
const selectedStoreId = ref('');
const activeCustomerKey = ref('');
const profile = ref<CustomerProfileDto | null>(null);
const isStoreLoading = ref(false);
const isProfileLoading = ref(false);
const filters = createDefaultListFilters();
const { loadStores, pickDefaultCustomerKey, loadProfile } = createDataActions({
stores,
profile,
filters,
isStoreLoading,
isProfileLoading,
});
const emptyDescription = computed(() => {
if (isStoreLoading.value || isProfileLoading.value) {
return '';
}
if (stores.value.length === 0) {
return '暂无门店,请先创建门店';
}
if (!activeCustomerKey.value) {
return '当前门店暂无客户';
}
return '暂无画像';
});
async function syncRouteQuery(storeId: string, customerKey: string) {
const currentStoreId = parseRouteQueryValue(route.query.storeId);
const currentCustomerKey = parseRouteQueryValue(route.query.customerKey);
if (currentStoreId === storeId && currentCustomerKey === customerKey) {
return;
}
await router.replace({
path: '/customer/profile',
query: buildRouteQuery(
route.query as Record<string, unknown>,
storeId,
customerKey,
),
});
}
function resolveStoreId(routeStoreId: string) {
if (!routeStoreId) {
return stores.value[0]?.id || '';
}
const matched = stores.value.find((item) => item.id === routeStoreId);
return matched?.id || stores.value[0]?.id || '';
}
async function loadProfileByRoute() {
if (stores.value.length === 0) {
await loadStores();
}
if (stores.value.length === 0) {
selectedStoreId.value = '';
activeCustomerKey.value = '';
profile.value = null;
return;
}
const routeStoreId = parseRouteQueryValue(route.query.storeId);
const routeCustomerKey = parseRouteQueryValue(route.query.customerKey);
const nextStoreId = resolveStoreId(routeStoreId);
selectedStoreId.value = nextStoreId;
let nextCustomerKey = routeCustomerKey;
if (!nextCustomerKey) {
nextCustomerKey = await pickDefaultCustomerKey(nextStoreId);
}
activeCustomerKey.value = nextCustomerKey;
if (!nextCustomerKey) {
profile.value = null;
await syncRouteQuery(nextStoreId, '');
return;
}
await loadProfile(nextStoreId, nextCustomerKey);
await syncRouteQuery(nextStoreId, nextCustomerKey);
}
watch(
() => route.fullPath,
() => {
void loadProfileByRoute();
},
);
onMounted(() => {
void loadProfileByRoute();
});
onActivated(() => {
if (stores.value.length === 0) {
void loadProfileByRoute();
}
});
return {
activeCustomerKey,
emptyDescription,
isProfileLoading,
isStoreLoading,
profile,
};
}