From 25b7f38df80138bc63c466e0d27853fb0483b531 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Wed, 11 Nov 2026 11:12:24 +0800 Subject: [PATCH] 111 --- src/api/mall/trade/brokerage/record/index.ts | 39 ++- src/api/member/opentypehistory/index.ts | 66 +++- src/api/member/user/index.ts | 9 + src/utils/formatter.ts | 9 + .../brokerage/record/BrokerageRecordList.vue | 266 +++++++++++++++ .../mall/trade/brokerage/record/index.vue | 190 +---------- .../user/BrokerageOrderListDialog.vue | 2 +- src/views/member/opentype-history/index.vue | 314 ++++++++++++++++-- .../user/components/UserExportDialog.vue | 41 ++- .../user/detail/UserBrokerageRecordDrawer.vue | 32 ++ .../member/user/detail/UserBrokerageTree.vue | 110 +++++- src/views/member/user/detail/index.vue | 14 +- .../member/user/detail/useBrokerageTree.ts | 1 + src/views/member/user/index.vue | 127 ++++++- tests/brokerage-record-list.test.cjs | 271 +++++++++++++++ tests/member-user-brokerage.test.cjs | 252 +++++++++++++- tests/member-user-export.test.cjs | 57 +++- 17 files changed, 1539 insertions(+), 261 deletions(-) create mode 100644 src/views/mall/trade/brokerage/record/BrokerageRecordList.vue create mode 100644 src/views/member/user/detail/UserBrokerageRecordDrawer.vue create mode 100644 tests/brokerage-record-list.test.cjs diff --git a/src/api/mall/trade/brokerage/record/index.ts b/src/api/mall/trade/brokerage/record/index.ts index 7df9a225..f908bb9a 100644 --- a/src/api/mall/trade/brokerage/record/index.ts +++ b/src/api/mall/trade/brokerage/record/index.ts @@ -1,8 +1,43 @@ import request from '@/config/axios' +export interface BrokerageRecordVO { + id: number + userId: number + userAvatar?: string + userNickname?: string + sourceUserId?: number | null + sourceUserAvatar?: string + sourceUserNickname?: string + bizId: string + bizType: number + sourceUserLevel?: number | null + title: string + description: string + price: number | string + status: number + unfreezeTime?: string | number + createTime: string | number +} + +export interface BrokerageRecordPageParams { + pageNo: number + pageSize: number + userId?: number | string | null + sourceUserId?: number | string | null + sourceUserLevel?: number + bizType?: number + bizId?: string + status?: number | null + createTime?: string[] | null + incomeOnly?: boolean +} + // 查询佣金记录列表 -export const getBrokerageRecordPage = async (params: any) => { - return await request.get({ url: `/trade/brokerage-record/page`, params }) +export const getBrokerageRecordPage = async (params: BrokerageRecordPageParams) => { + return await request.get<{ list: BrokerageRecordVO[]; total: number }>({ + url: '/trade/brokerage-record/page', + params + }) } // 查询佣金记录详情 diff --git a/src/api/member/opentypehistory/index.ts b/src/api/member/opentypehistory/index.ts index fda42896..6ecaaa05 100644 --- a/src/api/member/opentypehistory/index.ts +++ b/src/api/member/opentypehistory/index.ts @@ -1,4 +1,42 @@ import request from '@/config/axios' +import type { UserExportProgress } from '@/api/member/user' + +export interface OpenTypeBrokerageRecipient { + userId: number + userCode?: string + nickname?: string + avatar?: string + memberExists: boolean + sourceUserLevel?: number | null + settledPrice: number | string + pendingPrice: number | string + cancelledPrice: number | string +} + +export interface OpenTypeHistoryVO { + id: number + memberId: number + memberNickname?: string + memberUserCode?: string + memberAvatar?: string + memberMobile?: string + vipTitle?: string + vipDuration?: string + payPrice: number + payStatus: boolean | number | string + payTime?: string | number + payRefundId?: number + refundStatus?: number | null + refundPayPrice?: number + refundTime?: string | number + createTime?: string | number + brokerageSummary?: { + settledPrice: number | string + pendingPrice: number | string + cancelledPrice: number | string + recipients: OpenTypeBrokerageRecipient[] + } +} // 会员开通记录 API export const OpenTypeHistoryApi = { @@ -9,9 +47,31 @@ export const OpenTypeHistoryApi = { return await request.post({ url: `/member/open-type/history/admin-open`, data }) }, page: async (params: any) => { - return await request.get({ url: `/member/open-type/history/page`, params }) + return await request.get<{ list: OpenTypeHistoryVO[]; total: number }>({ + url: `/member/open-type/history/page`, + params + }) + }, + exportHistory: (params: Record) => { + return request.post({ + url: '/member/open-type/history/export-async', + params + }) + }, + getExportProgress: (taskId: string) => { + return request.get({ + url: '/member/open-type/history/export-progress', + params: { taskId } + }) + }, + downloadExport: (taskId: string) => { + return request.download({ + url: '/member/open-type/history/export-download', + params: { taskId }, + timeout: 300000 + }) }, refund: async (params: any) => { return await request.get({ url: `/member/open-type/history/refund`, params }) - }, -} \ No newline at end of file + } +} diff --git a/src/api/member/user/index.ts b/src/api/member/user/index.ts index 9848df8f..44c4d61c 100644 --- a/src/api/member/user/index.ts +++ b/src/api/member/user/index.ts @@ -1,11 +1,18 @@ import request from '@/config/axios' +export interface BrokerageIncomeSummary { + settledPrice: number | string + pendingPrice: number | string +} + export interface MemberSummary { id: number userCode?: string | null nickname?: string | null avatar?: string | null memberExists: boolean + brokerageSummary?: BrokerageIncomeSummary + contributionSummary?: BrokerageIncomeSummary } export interface UserInviterVO extends MemberSummary { @@ -23,6 +30,7 @@ export interface UserVO { id: number userCode?: string | null inviter?: UserInviterVO | null + brokerageSummary?: BrokerageIncomeSummary avatar: string | undefined birthday: number | undefined createTime: number | undefined @@ -90,6 +98,7 @@ export const getUser = async (id: number) => { export const getBrokerageChildrenPage = (params: { bindUserId: number + contributionUserId?: number pageNo: number pageSize: number }) => { diff --git a/src/utils/formatter.ts b/src/utils/formatter.ts index 8777f322..795575e7 100644 --- a/src/utils/formatter.ts +++ b/src/utils/formatter.ts @@ -1,5 +1,14 @@ import { floatToFixed2 } from '@/utils' +/** 累计佣金可能以 Long 字符串返回,保留整数分的完整精度。 */ +export const brokeragePriceToYuan = (price?: number | string | null): string => { + if (price == null) return '—' + const amount = BigInt(price) + const negative = amount < 0n + const digits = (negative ? -amount : amount).toString().padStart(3, '0') + return `${negative ? '-' : ''}${digits.slice(0, -2)}.${digits.slice(-2)}` +} + // 格式化金额【分转元】 // @ts-ignore export const fenToYuanFormat = (_, __, cellValue: any, ___) => { diff --git a/src/views/mall/trade/brokerage/record/BrokerageRecordList.vue b/src/views/mall/trade/brokerage/record/BrokerageRecordList.vue new file mode 100644 index 00000000..ce67a665 --- /dev/null +++ b/src/views/mall/trade/brokerage/record/BrokerageRecordList.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/src/views/mall/trade/brokerage/record/index.vue b/src/views/mall/trade/brokerage/record/index.vue index aa10c242..aae4daf8 100644 --- a/src/views/mall/trade/brokerage/record/index.vue +++ b/src/views/mall/trade/brokerage/record/index.vue @@ -1,196 +1,10 @@ diff --git a/src/views/mall/trade/brokerage/user/BrokerageOrderListDialog.vue b/src/views/mall/trade/brokerage/user/BrokerageOrderListDialog.vue index 04ad6ccf..5323604b 100644 --- a/src/views/mall/trade/brokerage/user/BrokerageOrderListDialog.vue +++ b/src/views/mall/trade/brokerage/user/BrokerageOrderListDialog.vue @@ -109,7 +109,7 @@ defineOptions({ name: 'BrokerageOrderListDialog' }) const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 -const list = ref([]) // 列表的数据 +const list = ref([]) // 列表的数据 const queryParams = reactive({ pageNo: 1, pageSize: 10, diff --git a/src/views/member/opentype-history/index.vue b/src/views/member/opentype-history/index.vue index d6092e1c..55617c76 100644 --- a/src/views/member/opentype-history/index.vue +++ b/src/views/member/opentype-history/index.vue @@ -1,18 +1,101 @@ + + + + + + \ No newline at end of file + diff --git a/src/views/member/user/components/UserExportDialog.vue b/src/views/member/user/components/UserExportDialog.vue index e09c9d69..0b2ff2f0 100644 --- a/src/views/member/user/components/UserExportDialog.vue +++ b/src/views/member/user/components/UserExportDialog.vue @@ -1,7 +1,7 @@ @@ -54,7 +54,7 @@ :status="progressStatus" :stroke-width="8" :show-text="false" - :aria-label="`会员用户导出进度 ${task.progress}%`" + :aria-label="`${recordName}导出进度 ${task.progress}%`" />
() + +const recordName = computed(() => props.recordName ?? '会员用户') const visible = ref(false) const submitting = ref(false) const downloading = ref(false) @@ -120,7 +129,7 @@ const errorMessage = ref('') const pollFailed = ref(false) const existingTask = ref(false) const userStore = useUserStore() -const storageKey = `member-user-export:${getTenantId() ?? ''}:${userStore.getUser.id}` +const storageKey = `${props.storagePrefix ?? 'member-user-export'}:${getTenantId() ?? ''}:${userStore.getUser.id}` const isActive = computed( () => task.value?.status === 'WAITING' || task.value?.status === 'RUNNING' ) @@ -135,7 +144,7 @@ const statusTitle = computed(() => { if (task.value?.status === 'SUCCESS') return '导出已完成' if (task.value?.status === 'FAILURE') return '导出未完成' if (task.value?.status === 'WAITING') return '等待开始导出' - return '正在导出用户数据' + return `正在导出${recordName.value}数据` }) const statusIcon = computed(() => { if (submitting.value) return 'ep:loading' @@ -171,8 +180,10 @@ const downloadFile = async () => { downloading.value = true errorMessage.value = '' try { - const data = await UserApi.downloadUserExport(downloadTask.taskId) - download.excel(data, downloadTask.fileName || '会员用户.xlsx') + const data = await (props.exportApi?.download ?? UserApi.downloadUserExport)( + downloadTask.taskId + ) + download.excel(data, downloadTask.fileName || `${recordName.value}.xlsx`) } catch { errorMessage.value = '文件已生成,下载失败,请点击“下载 Excel”重试。' visible.value = true @@ -184,7 +195,9 @@ const downloadFile = async () => { const poll = async (version: number) => { if (!task.value || !activePage || version !== pollingVersion) return try { - const data = await UserApi.getUserExportProgress(task.value.taskId) + const data = await (props.exportApi?.progress ?? UserApi.getUserExportProgress)( + task.value.taskId + ) if (!activePage || version !== pollingVersion) return task.value = data pollFailed.value = false @@ -221,7 +234,7 @@ const submitExport = async () => { pollFailed.value = false errorMessage.value = '' try { - const data = await UserApi.exportUsers(querySnapshot) + const data = await (props.exportApi?.create ?? UserApi.exportUsers)(querySnapshot) if (!data?.taskId) throw new Error('导出任务编号为空') task.value = data existingTask.value = data.existingTask @@ -238,7 +251,7 @@ const submitExport = async () => { const open = async (params: Record) => { visible.value = true if (submitting.value) return - // 从同一个 search-form 对象拍下快照,只去掉分页;保留男/女页面固定的性别条件。 + // 拷贝当前搜索条件,去掉分页参数并保留数组和零值条件。 querySnapshot = Object.fromEntries( Object.entries(params) .filter(([key]) => key !== 'pageNo' && key !== 'pageSize') diff --git a/src/views/member/user/detail/UserBrokerageRecordDrawer.vue b/src/views/member/user/detail/UserBrokerageRecordDrawer.vue new file mode 100644 index 00000000..1cedbb16 --- /dev/null +++ b/src/views/member/user/detail/UserBrokerageRecordDrawer.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/views/member/user/detail/UserBrokerageTree.vue b/src/views/member/user/detail/UserBrokerageTree.vue index 201b67f9..ddf6e6bd 100644 --- a/src/views/member/user/detail/UserBrokerageTree.vue +++ b/src/views/member/user/detail/UserBrokerageTree.vue @@ -3,7 +3,7 @@

分销关系

-

查看直接邀请人,逐层展开下级会员

+

收益按已结算累计,贡献以当前会员为收益人

当前会员 @@ -60,6 +60,38 @@ ID {{ data.member.userCode || data.member.id }}
+
+
+
+ 分销收益 + 收益记录 +
+ {{ priceText(data.member.brokerageSummary?.settledPrice) }} + 待结算 {{ priceText(data.member.brokerageSummary?.pendingPrice) }} +
+
+
+ 为当前会员贡献 + 贡献记录 +
+ {{ priceText(data.member.contributionSummary?.settledPrice) }} + 待结算 {{ priceText(data.member.contributionSummary?.pendingPrice) }} +
+
{{ data.branch.error @@ -181,11 +213,12 @@

滚轮缩放,拖动画布移动。每个分支每次加载 20 人,悬停时钟查看绑定时间。

+