1. 业务员查看维度增加
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
This commit is contained in:
@@ -1,14 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { openRouteInNewWindow } from '@vben/utils';
|
||||
|
||||
import { Avatar, Descriptions, Spin } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { Avatar, Descriptions, Empty, message, Spin, Table } from 'ant-design-vue';
|
||||
|
||||
import { getPromoterDetailApi } from '#/views/system/store-input/api';
|
||||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||
|
||||
type PromoterStoreItem = {
|
||||
store_id: number;
|
||||
name: string;
|
||||
type: number;
|
||||
type_label: string;
|
||||
input_at: number;
|
||||
total_amount: string;
|
||||
month_amount: string;
|
||||
};
|
||||
|
||||
type PromoterDetail = {
|
||||
id: number;
|
||||
nick_name: string;
|
||||
@@ -16,18 +29,74 @@ type PromoterDetail = {
|
||||
code: string;
|
||||
phone: string;
|
||||
role_name: string;
|
||||
clinic_input_count: number;
|
||||
pharmacy_input_count: number;
|
||||
clinic_store_count: number;
|
||||
pharmacy_store_count: number;
|
||||
store_total_count: number;
|
||||
store_list: PromoterStoreItem[];
|
||||
total_valid_amount: string;
|
||||
month_valid_amount: string;
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const detail = ref<PromoterDetail | null>(null);
|
||||
|
||||
const storeColumns = [
|
||||
{ title: '门店名称', dataIndex: 'name', key: 'name', ellipsis: true },
|
||||
{ title: '类型', dataIndex: 'type_label', key: 'type_label', width: 72 },
|
||||
{ title: '录入时间', dataIndex: 'input_at', key: 'input_at', width: 160 },
|
||||
{ title: '总营业额', dataIndex: 'total_amount', key: 'total_amount', width: 120, align: 'right' as const },
|
||||
{ title: '本月营业额', dataIndex: 'month_amount', key: 'month_amount', width: 120, align: 'right' as const },
|
||||
];
|
||||
|
||||
/** 解析商品订单列表路由(菜单动态注册,path 含 product-order) */
|
||||
function findProductOrderListPath(): string | null {
|
||||
const routes = router.getRoutes();
|
||||
const hit = routes.find(
|
||||
(r) =>
|
||||
typeof r.path === 'string' &&
|
||||
r.path.length > 0 &&
|
||||
!r.redirect &&
|
||||
/product-order/i.test(r.path),
|
||||
);
|
||||
return hit?.path ?? null;
|
||||
}
|
||||
|
||||
/** 金额格式化为 ¥ + 2 位小数 */
|
||||
function formatMoney(value: number | string | undefined) {
|
||||
const num = Number(value || 0);
|
||||
return `¥${num.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}`;
|
||||
}
|
||||
|
||||
/** Unix 时间戳格式化为可读录入时间 */
|
||||
function formatInputAt(timestamp?: number) {
|
||||
if (!timestamp) return '-';
|
||||
return dayjs.unix(timestamp).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
|
||||
/** 新标签页打开商品订单,按门店及时间范围筛选 */
|
||||
function openStoreOrders(storeId: number, timeScope: 'all' | 'month') {
|
||||
const path = findProductOrderListPath();
|
||||
if (!path) {
|
||||
message.warning('未找到商品订单菜单路由,请从左侧菜单进入商品订单');
|
||||
return;
|
||||
}
|
||||
const resolved = router.resolve({
|
||||
path,
|
||||
query: {
|
||||
store_id: String(storeId),
|
||||
time_scope: timeScope,
|
||||
},
|
||||
});
|
||||
openRouteInNewWindow(resolved.fullPath);
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '业务员详情',
|
||||
class: 'w-[480px]',
|
||||
class: 'w-[860px]',
|
||||
footer: false,
|
||||
onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
@@ -63,24 +132,59 @@ async function loadDetail(id?: number, code?: string) {
|
||||
<div class="promoter-detail__title">{{ detail.nick_name || '-' }}</div>
|
||||
<div class="promoter-detail__role">{{ detail.role_name || '业务员' }}</div>
|
||||
</div>
|
||||
<Descriptions bordered :column="1" size="small" class="promoter-detail__desc">
|
||||
<Descriptions bordered :column="2" size="small" class="promoter-detail__desc">
|
||||
<Descriptions.Item label="推广码">{{ detail.code || '-' }}</Descriptions.Item>
|
||||
<Descriptions.Item label="手机号">
|
||||
<SensitiveText :record="detail" field="phone" />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="录入诊所">
|
||||
{{ detail.clinic_input_count ?? 0 }} 家
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="录入药店">
|
||||
{{ detail.pharmacy_input_count ?? 0 }} 家
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="正式诊所">
|
||||
<Descriptions.Item label="绑定诊所">
|
||||
{{ detail.clinic_store_count ?? 0 }} 家
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="正式药店">
|
||||
<Descriptions.Item label="绑定药店">
|
||||
{{ detail.pharmacy_store_count ?? 0 }} 家
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="绑定门店总营业额">
|
||||
{{ formatMoney(detail.total_valid_amount) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="本月营业额">
|
||||
{{ formatMoney(detail.month_valid_amount) }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<div class="promoter-detail__section-title">正式门店列表</div>
|
||||
<Table
|
||||
v-if="detail.store_list?.length"
|
||||
:columns="storeColumns"
|
||||
:data-source="detail.store_list"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 320 }"
|
||||
row-key="store_id"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'input_at'">
|
||||
{{ formatInputAt(record.input_at) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'total_amount'">
|
||||
<button
|
||||
type="button"
|
||||
class="promoter-detail__amount-link"
|
||||
@click="openStoreOrders(record.store_id, 'all')"
|
||||
>
|
||||
{{ formatMoney(record.total_amount) }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'month_amount'">
|
||||
<button
|
||||
type="button"
|
||||
class="promoter-detail__amount-link"
|
||||
@click="openStoreOrders(record.store_id, 'month')"
|
||||
>
|
||||
{{ formatMoney(record.month_amount) }}
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<Empty v-else class="promoter-detail__empty" description="暂无绑定正式门店" />
|
||||
</div>
|
||||
</Spin>
|
||||
</Modal>
|
||||
@@ -110,11 +214,41 @@ async function loadDetail(id?: number, code?: string) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dark .promoter-detail__title {
|
||||
.promoter-detail__section-title {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.promoter-detail__empty {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.promoter-detail__amount-link {
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
color: #1677ff;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.promoter-detail__amount-link:hover {
|
||||
color: #4096ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dark .promoter-detail__title,
|
||||
.dark .promoter-detail__section-title {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.dark .promoter-detail__role {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.dark .promoter-detail__amount-link {
|
||||
color: #60a5fa;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user