f
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
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
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

This commit is contained in:
2025-11-27 19:08:41 +08:00
commit 0668eea99b
1450 changed files with 125078 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
export function useCommissionDeductionColumns(): VxeTableGridOptions['columns'] {
return [
{
title: 'ID',
field: 'id',
width: 80,
},
{
title: '代理ID',
field: 'agent_id',
width: 100,
},
{
title: '被扣代理ID',
field: 'deducted_agent_id',
width: 100,
},
{
title: '抽佣金额',
field: 'amount',
width: 120,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
title: '产品名称',
field: 'product_name',
width: 150,
},
{
title: '抽佣类型',
field: 'type',
width: 120,
formatter: ({ cellValue }: { cellValue: 'cost' | 'pricing' }) => {
const typeMap = {
cost: '成本抽佣',
pricing: '定价抽佣',
};
return typeMap[cellValue] || cellValue;
},
},
{
title: '状态',
field: 'status',
width: 100,
cellRender: {
name: 'CellTag',
options: [
{ value: 0, color: 'warning', label: '待结算' },
{ value: 1, color: 'success', label: '已结算' },
{ value: 2, color: 'error', label: '已取消' },
],
},
},
{
title: '创建时间',
field: 'create_time',
width: 180,
},
];
}
export function useCommissionDeductionFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'product_name',
label: '产品名称',
component: 'Input',
componentProps: {
allowClear: true,
},
},
{
fieldName: 'type',
label: '抽佣类型',
component: 'Select',
componentProps: {
options: [
{ label: '成本抽佣', value: 'cost' },
{ label: '定价抽佣', value: 'pricing' },
],
allowClear: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: [
{ label: '待结算', value: 0 },
{ label: '已结算', value: 1 },
{ label: '已取消', value: 2 },
],
allowClear: true,
},
},
];
}

View File

@@ -0,0 +1,67 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentCommissionDeductionList } from '#/api/agent';
import {
useCommissionDeductionColumns,
useCommissionDeductionFormSchema,
} from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useCommissionDeductionFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useCommissionDeductionColumns(),
proxyConfig: {
ajax: {
query: async ({
form,
page,
}: {
form: Record<string, any>;
page: QueryParams;
}) => {
return await getAgentCommissionDeductionList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '上级抽佣列表' : '所有上级抽佣记录'" />
</Page>
</template>

View File

@@ -0,0 +1,74 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 佣金记录列表列配置
export function useCommissionColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'agent_id',
title: '代理ID',
width: 100,
},
{
field: 'order_id',
title: '订单ID',
width: 100,
},
{
field: 'amount',
title: '佣金金额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'product_name',
title: '产品名称',
width: 150,
},
{
field: 'status',
title: '状态',
width: 100,
formatter: ({ cellValue }: { cellValue: number }) => {
const statusMap: Record<number, string> = {
0: '待结算',
1: '已结算',
2: '已取消',
};
return statusMap[cellValue] || '未知';
},
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
sortType: 'string' as const,
},
];
}
// 佣金记录搜索表单配置
export function useCommissionFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
allowClear: true,
options: [
{ label: '待结算', value: 0 },
{ label: '已结算', value: 1 },
{ label: '已取消', value: 2 },
],
},
},
];
}

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentCommissionList } from '#/api/agent';
import { useCommissionColumns, useCommissionFormSchema } from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useCommissionFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useCommissionColumns(),
proxyConfig: {
ajax: {
query: async ({
page,
form,
}: {
form: Record<string, any>;
page: QueryParams;
}) => {
return await getAgentCommissionList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '佣金记录列表' : '所有佣金记录'" />
</Page>
</template>

View File

@@ -0,0 +1,51 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 推广链接列表列配置
export function useLinkColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'agent_id',
title: '代理ID',
width: 100,
},
{
field: 'product_name',
title: '产品名称',
width: 150,
},
{
field: 'price',
title: '价格',
width: 120,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
field: 'link_identifier',
title: '推广码',
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
sortType: 'string' as const,
},
];
}
// 推广链接搜索表单配置
export function useLinkFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
},
{
component: 'Input',
fieldName: 'link_identifier',
label: '推广码',
},
];
}

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentLinkList } from '#/api/agent';
import { useLinkColumns, useLinkFormSchema } from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useLinkFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useLinkColumns(),
proxyConfig: {
ajax: {
query: async ({
page,
form,
}: {
form: Record<string, any>;
page: QueryParams;
}) => {
return await getAgentLinkList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '推广链接列表' : '所有推广链接'" />
</Page>
</template>

View File

@@ -0,0 +1,436 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 表单配置
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'mobile',
label: '手机号',
rules: 'required',
},
{
component: 'Input',
fieldName: 'level_name',
label: '等级名称',
rules: 'required',
},
{
component: 'Input',
fieldName: 'region',
label: '区域',
rules: 'required',
},
{
component: 'DatePicker',
fieldName: 'membership_expiry_time',
label: '会员到期时间',
rules: 'required',
componentProps: {
showTime: true,
},
},
];
}
// 搜索表单配置
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'mobile',
label: '手机号',
},
{
component: 'Input',
fieldName: 'region',
label: '区域',
},
{
component: 'RangePicker',
fieldName: 'create_time',
label: '创建时间',
componentProps: {
showTime: true,
},
},
];
}
// 表格列配置
export function useColumns(): VxeTableGridOptions['columns'] {
const columns = [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'user_id',
title: '用户ID',
width: 100,
},
{
field: 'level_name',
title: '等级名称',
width: 120,
formatter: ({ cellValue }: { cellValue: string }) => {
if (cellValue === '' || cellValue === 'normal') {
return '普通代理';
}
return cellValue;
},
},
{
field: 'region',
title: '区域',
width: 120,
},
{
field: 'mobile',
title: '手机号',
width: 120,
},
{
cellRender: {
name: 'CellTag',
options: [
{ value: 'approved', color: 'success', label: '已认证' },
{ value: 'pending', color: 'warning', label: '审核中' },
{ value: 'rejected', color: 'error', label: '已拒绝' },
{ value: '', color: 'default', label: '未认证' },
],
},
field: 'real_name_status',
title: '实名认证状态',
width: 120,
},
{
field: 'real_name',
title: '实名姓名',
width: 120,
formatter: ({ cellValue }: { cellValue: string }) => {
if (!cellValue) return '-';
return cellValue;
},
},
{
field: 'id_card',
title: '身份证号',
width: 180,
formatter: ({ cellValue }: { cellValue: string }) => {
if (!cellValue) return '-';
// 只显示前6位和后4位中间用*代替
return `${cellValue.slice(0, 6)}${'*'.repeat(8)}${cellValue.slice(-4)}`;
},
},
{
field: 'membership_expiry_time',
title: '会员到期时间',
width: 160,
sortable: true,
sortType: 'string' as const,
},
{
field: 'balance',
title: '钱包余额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'total_earnings',
title: '累计收益',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'frozen_balance',
title: '冻结余额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'withdrawn_amount',
title: '提现总额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'create_time',
title: '成为代理时间',
width: 160,
sortable: true,
sortType: 'string' as const,
},
{
align: 'center' as const,
slots: { default: 'operation' },
field: 'operation',
fixed: 'right' as const,
title: '操作',
width: 280,
},
];
return columns;
}
// 推广链接列表列配置
export function useLinkColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'product_name',
title: '产品名称',
width: 150,
},
{
field: 'price',
title: '价格',
width: 120,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
field: 'link_identifier',
title: '推广码',
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
sortType: 'string' as const,
},
];
}
// 推广链接搜索表单配置
export function useLinkFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
},
{
component: 'Input',
fieldName: 'link_identifier',
label: '推广码',
},
];
}
// 佣金记录列表列配置
export function useCommissionColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'agent_id',
title: '代理ID',
width: 100,
},
{
field: 'order_id',
title: '订单ID',
width: 100,
},
{
field: 'amount',
title: '佣金金额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'product_name',
title: '产品名称',
width: 150,
},
{
field: 'status',
title: '状态',
width: 100,
formatter: ({ cellValue }: { cellValue: number }) => {
const statusMap: Record<number, string> = {
0: '待结算',
1: '已结算',
2: '已取消',
};
return statusMap[cellValue] || '未知';
},
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
},
] as const;
}
// 佣金记录搜索表单配置
export function useCommissionFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
allowClear: true,
options: [
{ label: '待结算', value: 0 },
{ label: '已结算', value: 1 },
{ label: '已取消', value: 2 },
],
},
},
];
}
// 奖励记录列表列配置
export function useRewardColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'agent_id',
title: '代理ID',
width: 100,
},
{
field: 'relation_agent_id',
title: '关联代理ID',
width: 100,
},
{
field: 'amount',
title: '奖励金额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'type',
title: '奖励类型',
width: 120,
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
},
] as const;
}
// 奖励记录搜索表单配置
export function useRewardFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'type',
label: '奖励类型',
},
{
component: 'Input',
fieldName: 'relation_agent_id',
label: '关联代理ID',
},
];
}
// 提现记录列表列配置
export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'agent_id',
title: '代理ID',
width: 100,
},
{
field: 'withdraw_no',
title: '提现单号',
width: 180,
},
{
field: 'amount',
title: '提现金额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'status',
title: '状态',
width: 100,
formatter: ({ cellValue }: { cellValue: number }) => {
const statusMap: Record<number, string> = {
0: '待审核',
1: '已通过',
2: '已拒绝',
3: '已打款',
};
return statusMap[cellValue] || '未知';
},
},
{
field: 'payee_account',
title: '收款账户',
width: 180,
},
{
field: 'remark',
title: '备注',
width: 200,
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
},
] as const;
}
// 提现记录搜索表单配置
export function useWithdrawalFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'withdraw_no',
label: '提现单号',
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
allowClear: true,
options: [
{ label: '待审核', value: 0 },
{ label: '已通过', value: 1 },
{ label: '已拒绝', value: 2 },
{ label: '已打款', value: 3 },
],
},
},
];
}

View File

@@ -0,0 +1,341 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeGridListeners,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { AgentApi } from '#/api/agent';
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { Button, Card, Dropdown, Menu } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentList } from '#/api/agent';
import { useColumns, useGridFormSchema } from './data';
import CommissionDeductionModal from './modules/commission-deduction-modal.vue';
import CommissionModal from './modules/commission-modal.vue';
import Form from './modules/form.vue';
import LinkModal from './modules/link-modal.vue';
import PlatformDeductionModal from './modules/platform-deduction-modal.vue';
import RewardModal from './modules/reward-modal.vue';
import WithdrawalModal from './modules/withdrawal-modal.vue';
const route = useRoute();
const router = useRouter();
// 表单抽屉
const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form,
destroyOnClose: true,
});
// 推广链接弹窗
const [LinkModalComponent, linkModalApi] = useVbenModal({
connectedComponent: LinkModal,
destroyOnClose: true,
});
// 佣金记录弹窗
const [CommissionModalComponent, commissionModalApi] = useVbenModal({
connectedComponent: CommissionModal,
destroyOnClose: true,
});
// 奖励记录弹窗
const [RewardModalComponent, rewardModalApi] = useVbenModal({
connectedComponent: RewardModal,
destroyOnClose: true,
});
// 提现记录弹窗
const [WithdrawalModalComponent, withdrawalModalApi] = useVbenModal({
connectedComponent: WithdrawalModal,
destroyOnClose: true,
});
// 上级抽佣弹窗
const [CommissionDeductionModalComponent, commissionDeductionModalApi] =
useVbenModal({
connectedComponent: CommissionDeductionModal,
destroyOnClose: true,
});
// 平台抽佣弹窗
const [PlatformDeductionModalComponent, platformDeductionModalApi] =
useVbenModal({
connectedComponent: PlatformDeductionModal,
destroyOnClose: true,
});
// 表格配置
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
fieldMappingTime: [
['create_time', ['create_time_start', 'create_time_end']],
],
schema: useGridFormSchema(),
submitOnChange: true,
},
gridEvents: {
sortChange: () => {
gridApi.query();
},
} as VxeGridListeners<AgentApi.AgentListItem>,
gridOptions: {
columns: useColumns(),
height: 'auto',
keepSource: true,
sortConfig: {
remote: true,
multiple: false,
trigger: 'default',
orders: ['asc', 'desc', null],
resetPage: true,
},
proxyConfig: {
ajax: {
query: async ({ page, sort }, formValues) => {
const sortParams = sort
? {
order_by: sort.field,
order_type: sort.order,
}
: {};
const res = await getAgentList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
...sortParams,
parent_agent_id: route.query.parent_agent_id
? Number(route.query.parent_agent_id)
: undefined,
});
return {
...res,
sort: sort || null,
};
},
},
props: {
result: 'items',
total: 'total',
},
autoLoad: true,
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeTableGridOptions<AgentApi.AgentListItem>,
});
// 更多操作菜单项
const moreMenuItems = [
{
key: 'links',
label: '推广链接',
},
// {
// key: 'commission',
// label: '佣金记录',
// },
// {
// key: 'commission-deduction',
// label: '上级抽佣',
// },
// {
// key: 'platform-deduction',
// label: '平台抽佣',
// },
{
key: 'reward',
label: '奖励记录',
},
{
key: 'withdrawal',
label: '提现记录',
},
];
// 上级代理信息
const parentAgentId = computed(() => route.query.parent_agent_id);
// 返回上级列表
function onBackToParent() {
router.replace({
query: {
...route.query,
parent_agent_id: undefined,
},
});
}
// 操作处理函数
function onActionClick(
e:
| OnActionClickParams<AgentApi.AgentListItem>
| { code: string; row: AgentApi.AgentListItem },
) {
switch (e.code) {
case 'commission': {
onViewCommission(e.row);
break;
}
case 'commission-deduction': {
onViewCommissionDeduction(e.row);
break;
}
case 'edit': {
onEdit(e.row);
break;
}
case 'links': {
onViewLinks(e.row);
break;
}
case 'platform-deduction': {
onViewPlatformDeduction(e.row);
break;
}
case 'reward': {
onViewReward(e.row);
break;
}
case 'view-sub-agent': {
router.replace({
query: {
...route.query,
parent_agent_id: e.row.id,
},
});
break;
}
case 'withdrawal': {
onViewWithdrawal(e.row);
break;
}
}
}
// 编辑处理
function onEdit(row: AgentApi.AgentListItem) {
formDrawerApi.setData(row).open();
}
// 查看推广链接
function onViewLinks(row: AgentApi.AgentListItem) {
linkModalApi.setData({ agentId: row.id }).open();
}
// 查看佣金记录
function onViewCommission(row: AgentApi.AgentListItem) {
commissionModalApi.setData({ agentId: row.id }).open();
}
// 查看奖励记录
function onViewReward(row: AgentApi.AgentListItem) {
rewardModalApi.setData({ agentId: row.id }).open();
}
// 查看提现记录
function onViewWithdrawal(row: AgentApi.AgentListItem) {
withdrawalModalApi.setData({ agentId: row.id }).open();
}
// 查看上级抽佣记录
function onViewCommissionDeduction(row: AgentApi.AgentListItem) {
commissionDeductionModalApi.setData({ agentId: row.id }).open();
}
// 查看平台抽佣记录
function onViewPlatformDeduction(row: AgentApi.AgentListItem) {
platformDeductionModalApi.setData({ agentId: row.id }).open();
}
// 刷新处理
function onRefresh() {
gridApi.query();
}
</script>
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<LinkModalComponent />
<CommissionModalComponent />
<CommissionDeductionModalComponent />
<PlatformDeductionModalComponent />
<RewardModalComponent />
<WithdrawalModalComponent />
<!-- 上级代理信息卡片 -->
<Card v-if="parentAgentId" class="mb-4">
<div class="flex items-center gap-4">
<Button @click="onBackToParent">返回上级列表</Button>
<div>上级代理ID{{ parentAgentId }}</div>
</div>
</Card>
<Grid table-title="代理列表">
<template #operation="{ row }">
<div class="operation-buttons">
<Button type="link" @click="onActionClick({ code: 'edit', row })">
编辑
</Button>
<Button
type="link"
@click="onActionClick({ code: 'view-sub-agent', row })"
>
查看下级
</Button>
<!-- <Button
type="link"
@click="onActionClick({ code: 'order-record', row })"
>
订单记录
</Button> -->
<Dropdown>
<Button type="link">更多操作</Button>
<template #overlay>
<Menu
:items="moreMenuItems"
@click="(e) => onActionClick({ code: String(e.key), row })"
/>
</template>
</Dropdown>
</div>
</template>
</Grid>
</Page>
</template>
<style lang="less" scoped>
.parent-agent-card {
margin-bottom: 16px;
background-color: #fafafa;
}
.operation-buttons {
display: flex;
align-items: center;
justify-content: space-around;
gap: 4px;
:deep(.ant-btn-link) {
padding: 0 4px;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import CommissionDeductionList from '../../agent-commission-deduction/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '上级抽佣记录',
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal class="w-[calc(100vw-200px)]" :footer="false">
<div class="agent-commission-deduction-modal">
<CommissionDeductionList :agent-id="modalData?.agentId" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.agent-commission-deduction-modal {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import CommissionList from '../../agent-commission/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '佣金记录列表',
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal class="w-[calc(100vw-200px)]" :footer="false">
<div class="agent-commission-modal">
<CommissionList :agent-id="modalData?.agentId" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.agent-commission-modal {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,62 @@
<script lang="ts" setup>
import type { AgentApi } from '#/api/agent';
import { computed, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AgentApi.AgentListItem>();
const [Form, formApi] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
});
const id = ref();
const [Drawer, drawerApi] = useVbenDrawer({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) return;
const values = await formApi.getValues();
drawerApi.lock();
// TODO: 实现更新代理信息的接口
// updateAgent(id.value, values as AgentApi.UpdateAgentRequest)
// .then(() => {
// emit('success');
// drawerApi.close();
// })
// .catch(() => {
// drawerApi.unlock();
// });
},
onOpenChange(isOpen) {
if (isOpen) {
const data = drawerApi.getData<AgentApi.AgentListItem>();
formApi.resetForm();
if (data) {
formData.value = data;
id.value = data.id;
formApi.setValues(data);
} else {
id.value = undefined;
}
}
},
});
const getDrawerTitle = computed(() => {
return formData.value?.id ? '编辑代理' : '创建代理';
});
</script>
<template>
<Drawer :title="getDrawerTitle">
<Form />
</Drawer>
</template>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import LinkList from '../../agent-links/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '推广链接列表',
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal class="w-[calc(100vw-200px)]" :footer="false">
<div class="agent-link-modal">
<LinkList :agent-id="modalData?.agentId" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.agent-link-modal {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import PlatformDeductionList from '../../agent-platform-deduction/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '平台抽佣记录',
width: 1000,
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal>
<PlatformDeductionList v-if="modalData" :agent-id="modalData.agentId" />
</Modal>
</template>
<style scoped>
:deep(.ant-modal-body) {
padding: 24px;
}
</style>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import RewardList from '../../agent-reward/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '奖励记录',
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal class="w-[calc(100vw-200px)]" :footer="false">
<div class="agent-reward-modal">
<RewardList :agent-id="modalData?.agentId" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.agent-reward-modal {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import WithdrawalList from '../../agent-withdrawal/list.vue';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '提现记录',
destroyOnClose: true,
});
const modalData = computed(() => modalApi.getData<ModalData>());
</script>
<template>
<Modal class="w-[calc(100vw-200px)]" :footer="false">
<div class="agent-withdrawal-modal">
<WithdrawalList :agent-id="modalData?.agentId" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.agent-withdrawal-modal {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,254 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 会员等级选项
export const levelNameOptions = [
{ label: '普通会员', value: 'normal' },
{ label: 'VIP会员', value: 'VIP' },
{ label: 'SVIP会员', value: 'SVIP' },
];
// 代理会员配置列表列配置
export function useColumns(): VxeTableGridOptions['columns'] {
return [
{ field: 'id', title: 'ID', width: 80 },
{
field: 'level_name',
title: '会员等级',
formatter: ({ cellValue }) => {
const option = levelNameOptions.find(
(item) => item.value === cellValue,
);
return option?.label || cellValue;
},
},
{
field: 'price',
title: '会员年费',
formatter: ({ cellValue }) =>
cellValue !== null && cellValue !== undefined
? `¥${cellValue.toFixed(2)}`
: '-',
},
{
field: 'report_commission',
title: '直推报告收益',
formatter: ({ cellValue }) =>
cellValue !== null && cellValue !== undefined
? `¥${cellValue.toFixed(2)}`
: '-',
},
{
field: 'lower_activity_reward',
title: '下级活跃奖励',
formatter: ({ cellValue }) =>
cellValue !== null && cellValue !== undefined
? `¥${cellValue.toFixed(2)}`
: '-',
},
{
field: 'new_activity_reward',
title: '新增活跃奖励',
formatter: ({ cellValue }) =>
cellValue !== null && cellValue !== undefined
? `¥${cellValue.toFixed(2)}`
: '-',
},
{
field: 'lower_standard_count',
title: '活跃下级达标数',
formatter: ({ cellValue }) => cellValue ?? '-',
},
{
field: 'new_lower_standard_count',
title: '新增活跃下级达标数',
formatter: ({ cellValue }) => cellValue ?? '-',
},
{
field: 'lower_withdraw_reward_ratio',
title: '下级提现奖励比例',
formatter: ({ cellValue }) =>
cellValue !== null && cellValue !== undefined
? `${(cellValue * 100).toFixed(2)}%`
: '-',
},
{
field: 'create_time',
title: '创建时间',
width: 180,
},
{
align: 'center',
slots: { default: 'operation' },
field: 'operation',
fixed: 'right',
title: '操作',
width: 120,
},
] as const;
}
// 代理会员配置搜索表单配置
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
component: 'Select',
fieldName: 'level_name',
label: '会员等级',
componentProps: {
placeholder: '请选择会员等级',
options: levelNameOptions,
},
},
];
}
// 代理会员配置编辑表单配置
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Select',
fieldName: 'level_name',
label: '会员等级',
rules: 'required',
componentProps: {
options: levelNameOptions,
},
},
{
component: 'InputNumber',
fieldName: 'price',
label: '会员年费',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'report_commission',
label: '直推报告收益',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'lower_activity_reward',
label: '下级活跃奖励',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'new_activity_reward',
label: '新增活跃奖励',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'lower_standard_count',
label: '活跃下级达标数',
componentProps: {
min: 0,
precision: 0,
},
},
{
component: 'InputNumber',
fieldName: 'new_lower_standard_count',
label: '新增活跃下级达标数',
componentProps: {
min: 0,
precision: 0,
},
},
{
component: 'InputNumber',
fieldName: 'lower_withdraw_reward_ratio',
label: '下级提现奖励比例',
componentProps: {
min: 0,
max: 100,
precision: 2,
step: 0.01,
addonAfter: '%',
},
},
{
component: 'InputNumber',
fieldName: 'lower_convert_vip_reward',
label: '下级转化VIP奖励',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'lower_convert_svip_reward',
label: '下级转化SVIP奖励',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'exemption_amount',
label: '免责金额',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'price_increase_max',
label: '提价最高金额',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'price_ratio',
label: '提价区间收取比例',
componentProps: {
min: 0,
max: 100,
precision: 2,
step: 0.01,
addonAfter: '%',
},
},
{
component: 'InputNumber',
fieldName: 'price_increase_amount',
label: '加价金额',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
];
}

View File

@@ -0,0 +1,105 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeGridListeners,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { AgentApi } from '#/api/agent';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { Button, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentMembershipConfigList } from '#/api/agent';
import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
// 表单抽屉
const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form,
destroyOnClose: true,
});
// 表格配置
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
submitOnChange: true,
},
gridEvents: {
sortChange: () => {
gridApi.query();
},
} as VxeGridListeners<AgentApi.AgentMembershipConfigListItem>,
gridOptions: {
columns: useColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
const res = await getAgentMembershipConfigList({
page: page.currentPage,
pageSize: page.pageSize,
level_name: formValues.level_name,
});
return res;
},
},
props: {
result: 'items',
total: 'total',
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeTableGridOptions<AgentApi.AgentMembershipConfigListItem>,
});
// 操作处理函数
function onActionClick(
e: OnActionClickParams<AgentApi.AgentMembershipConfigListItem>,
) {
switch (e.code) {
case 'edit': {
onEdit(e.row);
break;
}
}
}
// 编辑处理
function onEdit(row: AgentApi.AgentMembershipConfigListItem) {
formDrawerApi.setData(row).open();
}
// 刷新处理
function onRefresh() {
gridApi.query();
}
</script>
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<Grid table-title="代理会员配置列表">
<template #operation="{ row }">
<Space>
<Button type="link" @click="onActionClick({ code: 'edit', row })">
配置
</Button>
</Space>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,88 @@
<script lang="ts" setup>
import type { AgentApi } from '#/api/agent';
import { ref } from 'vue';
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
import { updateAgentMembershipConfig } from '#/api/agent';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AgentApi.AgentMembershipConfigListItem>();
const id = ref<number>();
const [Form, formApi] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
});
const drawerTitle = ref('会员配置');
const [Drawer, drawerApi] = useVbenDrawer({
title: drawerTitle.value,
destroyOnClose: true,
async onConfirm() {
const valid = await formApi.validate();
if (!valid || !id.value) return;
const values = await formApi.getValues();
const params: AgentApi.UpdateAgentMembershipConfigParams = {
id: id.value,
level_name: values.level_name,
price: values.price,
report_commission: values.report_commission,
lower_activity_reward: values.lower_activity_reward ?? null,
new_activity_reward: values.new_activity_reward ?? null,
lower_standard_count: values.lower_standard_count ?? null,
new_lower_standard_count: values.new_lower_standard_count ?? null,
lower_withdraw_reward_ratio:
values.lower_withdraw_reward_ratio !== null &&
values.lower_withdraw_reward_ratio !== undefined
? values.lower_withdraw_reward_ratio / 100
: null,
lower_convert_vip_reward: values.lower_convert_vip_reward ?? null,
lower_convert_svip_reward: values.lower_convert_svip_reward ?? null,
exemption_amount: values.exemption_amount ?? null,
price_increase_max: values.price_increase_max ?? null,
price_ratio:
values.price_ratio !== null && values.price_ratio !== undefined
? values.price_ratio / 100
: null,
price_increase_amount: values.price_increase_amount ?? null,
};
await updateAgentMembershipConfig(params);
emit('success');
drawerApi.close();
},
onOpenChange(isOpen) {
if (isOpen) {
const data = drawerApi.getData<AgentApi.AgentMembershipConfigListItem>();
formApi.resetForm();
if (data) {
formData.value = data;
id.value = data.id;
formApi.setValues({
...data,
lower_withdraw_reward_ratio: data.lower_withdraw_reward_ratio
? data.lower_withdraw_reward_ratio * 100
: null,
price_ratio: data.price_ratio ? data.price_ratio * 100 : null,
});
} else {
id.value = undefined;
}
}
},
});
</script>
<template>
<Drawer :title="drawerTitle">
<Form />
</Drawer>
</template>

View File

@@ -0,0 +1,134 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 支付方式选项
export const paymentMethodOptions = [
{ label: '支付宝', value: 'alipay' },
{ label: '微信', value: 'wechat' },
{ label: '苹果支付', value: 'appleiap' },
{ label: '其他', value: 'other' },
];
// 会员等级选项
export const levelNameOptions = [
{ label: '普通会员', value: '' },
{ label: 'VIP会员', value: 'VIP' },
{ label: 'SVIP会员', value: 'SVIP' },
];
// 状态选项
export const statusOptions = [
{ label: '待支付', value: 'pending' },
{ label: '支付成功', value: 'success' },
{ label: '支付失败', value: 'failed' },
{ label: '已取消', value: 'cancelled' },
];
// 列表列配置
export function useMembershipRechargeOrderColumns(): VxeTableGridOptions['columns'] {
return [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'user_id', title: '用户ID', width: 100 },
{ field: 'agent_id', title: '代理ID', width: 100 },
{
field: 'level_name',
title: '会员等级',
width: 100,
formatter: ({ cellValue }) => {
const option = levelNameOptions.find(
(item) => item.value === cellValue,
);
return option?.label || '普通会员';
},
},
{
field: 'amount',
title: '金额',
width: 100,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
field: 'payment_method',
title: '支付方式',
width: 100,
formatter: ({ cellValue }) => {
const option = paymentMethodOptions.find(
(item) => item.value === cellValue,
);
return option?.label || cellValue;
},
},
{ field: 'order_no', title: '订单号', width: 180 },
{ field: 'platform_order_id', title: '平台订单号', width: 180 },
{
field: 'status',
title: '状态',
width: 100,
formatter: ({ cellValue }) => {
const option = statusOptions.find((item) => item.value === cellValue);
return option?.label || cellValue;
},
},
{
field: 'create_time',
title: '创建时间',
width: 180,
},
];
}
// 搜索表单配置
export function useMembershipRechargeOrderFormSchema(): VbenFormSchema[] {
return [
{
component: 'InputNumber',
fieldName: 'user_id',
label: '用户ID',
componentProps: {
placeholder: '请输入用户ID',
},
},
{
component: 'InputNumber',
fieldName: 'agent_id',
label: '代理ID',
componentProps: {
placeholder: '请输入代理ID',
},
},
{
component: 'Input',
fieldName: 'order_no',
label: '订单号',
componentProps: {
placeholder: '请输入订单号',
},
},
{
component: 'Input',
fieldName: 'platform_order_id',
label: '平台订单号',
componentProps: {
placeholder: '请输入平台订单号',
},
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
placeholder: '请选择状态',
options: statusOptions,
},
},
{
component: 'Select',
fieldName: 'payment_method',
label: '支付方式',
componentProps: {
placeholder: '请选择支付方式',
options: paymentMethodOptions,
},
},
];
}

View File

@@ -0,0 +1,57 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { AgentApi } from '#/api/agent/agent';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getMembershipRechargeOrderList } from '#/api/agent';
import {
useMembershipRechargeOrderColumns,
useMembershipRechargeOrderFormSchema,
} from './data';
const [Grid, _gridApi] = useVbenVxeGrid({
formOptions: {
schema: useMembershipRechargeOrderFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useMembershipRechargeOrderColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async (
{ page }: { page: { currentPage: number; pageSize: number } },
formValues: Record<string, any>,
) => {
const res = await getMembershipRechargeOrderList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
return { items: res.items, total: res.total };
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeGridProps<AgentApi.MembershipRechargeOrderListItem>,
});
</script>
<template>
<Page auto-content-height>
<Grid table-title="会员充值订单列表" />
</Page>
</template>

View File

@@ -0,0 +1,96 @@
import type { VbenFormSchema } from '@vben/common-ui';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AgentApi } from '#/api/agent';
// 平台抽佣列表列配置
export function usePlatformDeductionColumns(): VxeTableGridOptions['columns'] {
return [
{
title: 'ID',
field: 'id',
width: 80,
},
{
title: '代理ID',
field: 'agent_id',
width: 100,
},
{
title: '抽佣金额',
field: 'amount',
width: 120,
formatter: ({ cellValue }) => {
return `¥${cellValue.toFixed(2)}`;
},
},
{
title: '抽佣类型',
field: 'type',
width: 120,
formatter: ({
cellValue,
}: {
cellValue: AgentApi.AgentPlatformDeductionListItem['type'];
}) => {
const typeMap = {
cost: '成本抽佣',
pricing: '定价抽佣',
};
return typeMap[cellValue] || cellValue;
},
},
{
title: '状态',
field: 'status',
width: 100,
formatter: ({ cellValue }) => {
const statusMap = {
0: { text: '待处理', type: 'warning' },
1: { text: '已处理', type: 'success' },
2: { text: '已取消', type: 'error' },
};
const status = statusMap[cellValue as keyof typeof statusMap];
return status
? `<a-tag color="${status.type}">${status.text}</a-tag>`
: cellValue;
},
},
{
title: '创建时间',
field: 'create_time',
width: 180,
},
];
}
// 平台抽佣列表搜索表单配置
export function usePlatformDeductionFormSchema(): VbenFormSchema[] {
return [
{
component: 'Select',
fieldName: 'type',
label: '抽佣类型',
componentProps: {
options: [
{ label: '成本抽佣', value: 'cost' },
{ label: '定价抽佣', value: 'pricing' },
],
allowClear: true,
},
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
options: [
{ label: '待处理', value: 0 },
{ label: '已处理', value: 1 },
{ label: '已取消', value: 2 },
],
allowClear: true,
},
},
];
}

View File

@@ -0,0 +1,67 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentPlatformDeductionList } from '#/api/agent';
import {
usePlatformDeductionColumns,
usePlatformDeductionFormSchema,
} from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: usePlatformDeductionFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: usePlatformDeductionColumns(),
proxyConfig: {
ajax: {
query: async ({
form,
page,
}: {
form: Record<string, any>;
page: QueryParams;
}) => {
return await getAgentPlatformDeductionList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '平台抽佣列表' : '所有平台抽佣记录'" />
</Page>
</template>

View File

@@ -0,0 +1,131 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// 代理产品配置列表列配置
export function useColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'product_name',
title: '产品名称',
},
{
field: 'cost_price',
title: '成本',
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'price_range_min',
title: '最低定价',
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'price_range_max',
title: '最高定价',
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'pricing_standard',
title: '定价标准',
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'overpricing_ratio',
title: '超标抽佣比例',
formatter: ({ cellValue }: { cellValue: number }) =>
`${(cellValue * 100).toFixed(2)}%`,
},
{
align: 'center',
slots: { default: 'operation' },
field: 'operation',
fixed: 'right',
title: '操作',
width: 120,
},
] as const;
}
// 代理产品配置搜索表单配置
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
},
];
}
// 代理产品配置编辑表单配置
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'InputNumber',
fieldName: 'cost_price',
label: '成本',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'price_range_min',
label: '最低定价',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'price_range_max',
label: '最高定价',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'pricing_standard',
label: '定价标准',
rules: 'required',
componentProps: {
min: 0,
precision: 2,
step: 0.01,
},
},
{
component: 'InputNumber',
fieldName: 'overpricing_ratio',
label: '超标抽佣比例',
rules: 'required',
componentProps: {
min: 0,
max: 100,
precision: 2,
step: 0.01,
addonAfter: '%',
controls: true,
validateTrigger: ['blur', 'change'],
},
},
];
}

View File

@@ -0,0 +1,127 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeGridListeners,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { AgentApi } from '#/api/agent';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { Button, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentProductionConfigList } from '#/api/agent';
import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
// 表单抽屉
const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form,
destroyOnClose: true,
});
// 表格配置
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
submitOnChange: true,
},
gridEvents: {
sortChange: () => {
gridApi.query();
},
} as VxeGridListeners<AgentApi.AgentProductionConfigItem>,
gridOptions: {
columns: useColumns(),
height: 'auto',
keepSource: true,
sortConfig: {
remote: true,
multiple: false,
trigger: 'default',
orders: ['asc', 'desc', null],
resetPage: true,
},
proxyConfig: {
ajax: {
query: async ({ page, sort }, formValues) => {
const sortParams = sort
? {
order_by: sort.field,
order_type: sort.order,
}
: {};
const params: AgentApi.GetAgentProductionConfigListParams = {
page: page.currentPage,
pageSize: page.pageSize,
product_name: formValues.product_name,
...sortParams,
};
const res = await getAgentProductionConfigList(params);
return {
...res,
sort: sort || null,
};
},
},
props: {
result: 'items',
total: 'total',
},
autoLoad: true,
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeTableGridOptions<AgentApi.AgentProductionConfigItem>,
});
// 操作处理函数
function onActionClick(
e: OnActionClickParams<AgentApi.AgentProductionConfigItem>,
) {
switch (e.code) {
case 'edit': {
onEdit(e.row);
break;
}
}
}
// 编辑处理
function onEdit(row: AgentApi.AgentProductionConfigItem) {
formDrawerApi.setData(row).open();
}
// 刷新处理
function onRefresh() {
gridApi.query();
}
</script>
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<Grid table-title="代理产品配置列表">
<template #operation="{ row }">
<Space>
<Button type="link" @click="onActionClick({ code: 'edit', row })">
配置
</Button>
</Space>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import type { AgentApi } from '#/api/agent';
import { ref } from 'vue';
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
import { updateAgentProductionConfig } from '#/api/agent';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AgentApi.AgentProductionConfigItem>();
const id = ref<number>();
const [Form, formApi] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
});
const drawerTitle = ref('产品配置');
const [Drawer, drawerApi] = useVbenDrawer({
title: drawerTitle.value,
destroyOnClose: true,
async onConfirm() {
const valid = await formApi.validate();
if (!valid || !id.value) return;
const values = await formApi.getValues();
const params: AgentApi.UpdateAgentProductionConfigParams = {
id: id.value,
cost_price: values.cost_price,
price_range_min: values.price_range_min,
price_range_max: values.price_range_max,
pricing_standard: values.pricing_standard,
overpricing_ratio: values.overpricing_ratio / 100,
};
await updateAgentProductionConfig(params);
emit('success');
drawerApi.close();
},
onOpenChange(isOpen) {
if (isOpen) {
const data = drawerApi.getData<AgentApi.AgentProductionConfigItem>();
formApi.resetForm();
if (data) {
formData.value = data;
id.value = data.id;
formApi.setValues({
...data,
overpricing_ratio: data.overpricing_ratio * 100,
});
} else {
id.value = undefined;
}
}
},
});
</script>
<template>
<Drawer :title="drawerTitle">
<Form />
</Drawer>
</template>

View File

@@ -0,0 +1,74 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
export function useRewardColumns(): VxeTableGridOptions['columns'] {
return [
{
title: '代理ID',
field: 'agent_id',
width: 100,
},
{
title: '奖励类型',
field: 'type',
width: 120,
},
{
title: '奖励金额',
field: 'amount',
width: 120,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
title: '关联订单',
field: 'order_id',
width: 120,
},
{
title: '状态',
field: 'status',
width: 100,
},
{
title: '创建时间',
field: 'create_time',
width: 180,
},
{
title: '发放时间',
field: 'pay_time',
width: 180,
},
];
}
export function useRewardFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'type',
label: '奖励类型',
component: 'Select',
componentProps: {
options: [
{ label: '注册奖励', value: 'register' },
{ label: '首单奖励', value: 'first_order' },
{ label: '升级奖励', value: 'level_up' },
],
allowClear: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: [
{ label: '待发放', value: 'pending' },
{ label: '已发放', value: 'paid' },
{ label: '发放失败', value: 'failed' },
],
allowClear: true,
},
},
];
}

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentRewardList } from '#/api/agent';
import { useRewardColumns, useRewardFormSchema } from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useRewardFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useRewardColumns(),
proxyConfig: {
ajax: {
query: async ({
page,
form,
}: {
form: Record<string, any>;
page: QueryParams;
}) => {
return await getAgentRewardList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '奖励记录列表' : '所有奖励记录'" />
</Page>
</template>

View File

@@ -0,0 +1,107 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
type WithdrawalMethod = 'alipay' | 'bank' | 'wechat';
type WithdrawalStatus = 'approved' | 'failed' | 'paid' | 'pending' | 'rejected';
export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
return [
{
title: '代理ID',
field: 'agent_id',
width: 100,
},
{
title: '提现金额',
field: 'amount',
width: 120,
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
},
{
title: '提现方式',
field: 'method',
width: 120,
formatter: ({ cellValue }: { cellValue: WithdrawalMethod }) => {
const methodMap: Record<WithdrawalMethod, string> = {
alipay: '支付宝',
wechat: '微信',
bank: '银行卡',
};
return methodMap[cellValue] || cellValue;
},
},
{
title: '收款账号',
field: 'account',
width: 180,
},
{
title: '状态',
field: 'status',
width: 100,
formatter: ({ cellValue }: { cellValue: WithdrawalStatus }) => {
const statusMap: Record<WithdrawalStatus, string> = {
pending: '待审核',
approved: '已通过',
rejected: '已拒绝',
paid: '已打款',
failed: '打款失败',
};
return statusMap[cellValue] || cellValue;
},
},
{
title: '申请时间',
field: 'create_time',
width: 180,
},
{
title: '审核时间',
field: 'audit_time',
width: 180,
},
{
title: '打款时间',
field: 'pay_time',
width: 180,
},
{
title: '备注',
field: 'remark',
width: 200,
},
];
}
export function useWithdrawalFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'method',
label: '提现方式',
component: 'Select',
componentProps: {
options: [
{ label: '支付宝', value: 'alipay' },
{ label: '微信', value: 'wechat' },
{ label: '银行卡', value: 'bank' },
],
allowClear: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: [
{ label: '待审核', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已拒绝', value: 'rejected' },
{ label: '已打款', value: 'paid' },
{ label: '打款失败', value: 'failed' },
],
allowClear: true,
},
},
];
}

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentWithdrawalList } from '#/api/agent';
import { useWithdrawalColumns, useWithdrawalFormSchema } from './data';
interface Props {
agentId?: number;
}
interface QueryParams {
currentPage: number;
pageSize: number;
[key: string]: any;
}
const props = defineProps<Props>();
const queryParams = computed(() => ({
...(props.agentId ? { agent_id: props.agentId } : {}),
}));
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useWithdrawalFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useWithdrawalColumns(),
proxyConfig: {
ajax: {
query: async ({
page,
form,
}: {
page: QueryParams;
form: Record<string, any>;
}) => {
return await getAgentWithdrawalList({
...queryParams.value,
...form,
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
props: {
result: 'items',
total: 'total',
},
},
},
});
</script>
<template>
<Page :auto-content-height="!agentId">
<Grid :table-title="agentId ? '提现记录列表' : '所有提现记录'" />
</Page>
</template>