add CustomerInviteRank.vue
This commit is contained in:
@@ -18,7 +18,8 @@ async function list(page: CustomerPage): Promise<PageResult<Customer>> {
|
||||
size: req.size,
|
||||
keyword: page.keyword || page.likesExample?.code || page.likesExample?.name || page.likesExample?.telephone,
|
||||
storeId: page.storeId || req.equalsExample?.storeId,
|
||||
tagIdList: page.tagIdList || []
|
||||
tagIdList: page.tagIdList || [],
|
||||
orderMap: page.orderMap || {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<t-pull-down-refresh
|
||||
v-can="permissionCodes.customer.read"
|
||||
class="customer-invite-rank"
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@refresh="refresh"
|
||||
@scrolltolower="loadRanks"
|
||||
>
|
||||
<t-cell-group v-if="rankedCustomerList.length" class="list" theme="card">
|
||||
<customer-cell
|
||||
v-for="(item, index) in rankedCustomerList"
|
||||
:key="item.id || item.code || index"
|
||||
v-entrance-transition="index * 30"
|
||||
class="item"
|
||||
:class="{ 'in-top3': index < 3 }"
|
||||
:customer="item"
|
||||
arrow
|
||||
hover
|
||||
@click="toCustomerDetail(item)"
|
||||
>
|
||||
<template #note>
|
||||
<div
|
||||
class="rank"
|
||||
:class="{
|
||||
top1: index === 0,
|
||||
top2: index === 1,
|
||||
top3: index === 2
|
||||
}"
|
||||
v-text="String(index + 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<t-tag
|
||||
theme="primary"
|
||||
variant="light-outline"
|
||||
:content="getInvitedCountText(item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</customer-cell>
|
||||
</t-cell-group>
|
||||
<div v-else class="center-tips" v-text="emptyText" />
|
||||
<div v-if="rankedCustomerList.length" class="center-tips" v-text="loadStateText" />
|
||||
</t-pull-down-refresh>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type PageResult, useUserStore } from "timi-web-api";
|
||||
import CustomerAPI, { type CustomerPage } from "@/api/CustomerAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerInviteRank"
|
||||
});
|
||||
|
||||
const pageSize = 20;
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||
|
||||
// ---------- 列表数据 ----------
|
||||
|
||||
const page = reactive<CustomerPage>({
|
||||
index: 0,
|
||||
size: pageSize,
|
||||
orderMap: {
|
||||
invitedCount: "DESC"
|
||||
}
|
||||
});
|
||||
const pageResult = reactive<PageResult<Customer>>({
|
||||
total: 0,
|
||||
list: []
|
||||
});
|
||||
const isLoading = ref(false);
|
||||
const isRefreshing = ref(false);
|
||||
const isNoMore = ref(false);
|
||||
let loadedCustomerCount = 0;
|
||||
|
||||
const rankedCustomerList = computed(() => pageResult.list);
|
||||
const summaryText = computed(() => isNoMore.value
|
||||
? `${rankedCustomerList.value.length} 位客户有邀请记录`
|
||||
: `已加载 ${rankedCustomerList.value.length} 位客户`);
|
||||
const emptyText = computed(() => isLoading.value ? "正在加载客户邀请排行" : "暂无客户邀请记录");
|
||||
const loadStateText = computed(() => {
|
||||
if (isLoading.value) {
|
||||
return "正在加载更多";
|
||||
}
|
||||
return isNoMore.value ? "没有更多排行" : "上拉加载更多";
|
||||
});
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
if (isLoading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
page.index = 0;
|
||||
pageResult.total = 0;
|
||||
pageResult.list = [];
|
||||
loadedCustomerCount = 0;
|
||||
isNoMore.value = false;
|
||||
isRefreshing.value = true;
|
||||
await loadRanks();
|
||||
}
|
||||
|
||||
async function loadRanks(): Promise<void> {
|
||||
if (isLoading.value) {
|
||||
return;
|
||||
}
|
||||
if (isNoMore.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result: PageResult<Customer> = await CustomerAPI.list({
|
||||
...page,
|
||||
storeId: currentStoreStore.currentStoreId
|
||||
});
|
||||
const list = result.list || [];
|
||||
const rankedList = list.filter(item => 0 < getInvitedCount(item));
|
||||
pageResult.total = result.total || 0;
|
||||
pageResult.list = pageResult.list.concat(rankedList);
|
||||
loadedCustomerCount += list.length;
|
||||
page.index += 1;
|
||||
isNoMore.value = !list.length
|
||||
|| list.length < page.size
|
||||
|| pageResult.total <= loadedCustomerCount
|
||||
|| list.some(item => 0 >= getInvitedCount(item));
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "客户邀请排行加载失败"));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 导航和格式化 ----------
|
||||
|
||||
async function toCustomerDetail(customer: Customer): Promise<void> {
|
||||
if (!customer.id) {
|
||||
return;
|
||||
}
|
||||
await router.push(`/management/customers/${customer.id}`);
|
||||
}
|
||||
|
||||
function getInvitedCount(customer: Customer): number {
|
||||
return customer.invitedCount ?? 0;
|
||||
}
|
||||
|
||||
function getInvitedCountText(customer: Customer): string {
|
||||
return `邀请 ${getInvitedCount(customer)} 人`;
|
||||
}
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
|
||||
onMounted(async () => {
|
||||
if (isGlobalManager.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
} else {
|
||||
await currentStoreStore.loadLoginStore();
|
||||
}
|
||||
await refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.customer-invite-rank {
|
||||
gap: 1rem;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.summary {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
color: #0f172a;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #0f766e;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
|
||||
.list {
|
||||
|
||||
.item {
|
||||
.rank {
|
||||
flex: 0 0 1.5rem;
|
||||
width: 1.5rem;
|
||||
color: #52607a;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
font-size: .76rem;
|
||||
background: #f0f0f0;
|
||||
font-weight: 700;
|
||||
align-items: center;
|
||||
border-radius: .5rem;
|
||||
justify-content: center;
|
||||
|
||||
&.top1 {
|
||||
color: #5c3a00;
|
||||
background: #ffd700;
|
||||
}
|
||||
|
||||
&.top2 {
|
||||
color: #374151;
|
||||
background: #c0c0c0;
|
||||
}
|
||||
|
||||
&.top3 {
|
||||
color: #fff8f0;
|
||||
background: #cd7f32;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.customer-invite-rank {
|
||||
.summary {
|
||||
background: #182231;
|
||||
|
||||
.title {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #2dd4bf;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
.item {
|
||||
background: #182231;
|
||||
|
||||
.rank {
|
||||
color: #cbd5e1;
|
||||
background: #243244;
|
||||
|
||||
&.top1 {
|
||||
color: #facc15;
|
||||
background: #3f3415;
|
||||
}
|
||||
|
||||
&.top2 {
|
||||
color: #e2e8f0;
|
||||
background: #334155;
|
||||
}
|
||||
|
||||
&.top3 {
|
||||
color: #fdba74;
|
||||
background: #3f2a1b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -130,6 +130,12 @@ const menuGroupList = computed<MenuGroup[]>(() => [
|
||||
title: "客户管理",
|
||||
visible: userStore.hasPermissions(permissionCodes.customer.read)
|
||||
},
|
||||
{
|
||||
icon: "order-list",
|
||||
path: "/management/customer-invite-rank",
|
||||
title: "客户邀请排行",
|
||||
visible: userStore.hasPermissions(permissionCodes.customer.read)
|
||||
},
|
||||
{
|
||||
icon: "chart-combo",
|
||||
path: "/management/customer-data-charts",
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { RouteRecordRaw } from "vue-router";
|
||||
import QuickSignInPage from "@/pages/home/QuickSignInPage.vue";
|
||||
import EventRecordEdit from "@/pages/event/EventRecordEdit.vue";
|
||||
import CustomerList from "@/pages/customer/CustomerList.vue";
|
||||
import CustomerInviteRank from "@/pages/customer/CustomerInviteRank.vue";
|
||||
import CustomerDetail from "@/pages/customer/CustomerDetail.vue";
|
||||
import CustomerInviteeList from "@/pages/customer/CustomerInviteeList.vue";
|
||||
import CustomerEdit from "@/pages/customer/CustomerEdit.vue";
|
||||
@@ -49,6 +50,20 @@ const customerRoutes: RouteRecordRaw[] = [
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management/customer-invite-rank",
|
||||
name: "CustomerInviteRank",
|
||||
component: CustomerInviteRank,
|
||||
meta: {
|
||||
depth: 3,
|
||||
navBarVisible: true,
|
||||
navBarCanBack: true,
|
||||
navBarTitle: "客户邀请排行",
|
||||
tabBarVisible: false,
|
||||
bodyBackground: "#F5F5F8",
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management/customers/create",
|
||||
name: "CustomerCreate",
|
||||
|
||||
Reference in New Issue
Block a user