add 修改余额
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
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
2026-01-04 16:11:11 +08:00
parent 27d63095a9
commit d722860e71
5 changed files with 556 additions and 3 deletions

View File

@@ -341,6 +341,19 @@ export interface GetAgentLinkProductStatisticsParams {}
frozen_balance: number; // 冻结余额 frozen_balance: number; // 冻结余额
total_earnings: number; // 总收益 total_earnings: number; // 总收益
} }
// 修改代理钱包余额请求
export interface UpdateAgentWalletBalanceReq {
agent_id: number; // 代理ID
amount: number; // 修改金额(正数增加,负数减少)
}
// 修改代理钱包余额响应
export interface UpdateAgentWalletBalanceResp {
success: boolean; // 是否成功
balance: number; // 修改后的余额
}
} }
/** /**
@@ -588,6 +601,18 @@ async function getAgentWallet(agentId: number) {
); );
} }
/**
* 修改代理钱包余额
*/
async function updateAgentWalletBalance(params: AgentApi.UpdateAgentWalletBalanceReq) {
return requestClient.post<AgentApi.UpdateAgentWalletBalanceResp>(
'/agent/wallet/update-balance',
params,
);
}
export { export {
batchUnfreezeAgentCommission, batchUnfreezeAgentCommission,
getAgentCommissionDeductionList, getAgentCommissionDeductionList,
@@ -609,4 +634,5 @@ export {
updateAgentCommissionStatus, updateAgentCommissionStatus,
updateAgentMembershipConfig, updateAgentMembershipConfig,
updateAgentProductionConfig, updateAgentProductionConfig,
updateAgentWalletBalance,
}; };

View File

@@ -19,11 +19,13 @@ import { getAgentList } from '#/api/agent';
import { useColumns, useGridFormSchema } from './data'; import { useColumns, useGridFormSchema } from './data';
import CommissionDeductionModal from './modules/commission-deduction-modal.vue'; import CommissionDeductionModal from './modules/commission-deduction-modal.vue';
import CommissionModal from './modules/commission-modal.vue'; import CommissionModal from './modules/commission-modal.vue';
import CommissionHistoryModal from './modules/commission-history-modal.vue';
import Form from './modules/form.vue'; import Form from './modules/form.vue';
import LinkModal from './modules/link-modal.vue'; import LinkModal from './modules/link-modal.vue';
import PlatformDeductionModal from './modules/platform-deduction-modal.vue'; import PlatformDeductionModal from './modules/platform-deduction-modal.vue';
import RewardModal from './modules/reward-modal.vue'; import RewardModal from './modules/reward-modal.vue';
import WithdrawalModal from './modules/withdrawal-modal.vue'; import WithdrawalModal from './modules/withdrawal-modal.vue';
import BalanceModal from './modules/balance-modal.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -72,6 +74,18 @@ const [PlatformDeductionModalComponent, platformDeductionModalApi] =
destroyOnClose: true, destroyOnClose: true,
}); });
// 修改余额弹窗
const [BalanceModalComponent, balanceModalApi] = useVbenModal({
connectedComponent: BalanceModal,
destroyOnClose: true,
});
// 历史佣金记录弹窗
const [CommissionHistoryModalComponent, commissionHistoryModalApi] = useVbenModal({
connectedComponent: CommissionHistoryModal,
destroyOnClose: true,
});
// 表格配置 // 表格配置
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
@@ -144,10 +158,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
// 更多操作菜单项 // 更多操作菜单项
const moreMenuItems = [ const moreMenuItems = [
{
key: 'update-balance',
label: '修改余额',
},
{
key: 'commission-history',
label: '历史佣金记录',
},
{ {
key: 'links', key: 'links',
label: '推广链接', label: '推广链接',
}, },
// { // {
// key: 'commission', // key: 'commission',
// label: '佣金记录', // label: '佣金记录',
@@ -194,6 +217,10 @@ function onActionClick(
onViewCommission(e.row); onViewCommission(e.row);
break; break;
} }
case 'commission-history': {
onViewCommissionHistory(e.row);
break;
}
case 'commission-deduction': { case 'commission-deduction': {
onViewCommissionDeduction(e.row); onViewCommissionDeduction(e.row);
break; break;
@@ -214,6 +241,10 @@ function onActionClick(
onViewReward(e.row); onViewReward(e.row);
break; break;
} }
case 'update-balance': {
onUpdateBalance(e.row);
break;
}
case 'view-sub-agent': { case 'view-sub-agent': {
router.replace({ router.replace({
query: { query: {
@@ -240,6 +271,11 @@ function onViewLinks(row: AgentApi.AgentListItem) {
linkModalApi.setData({ agentId: row.id }).open(); linkModalApi.setData({ agentId: row.id }).open();
} }
// 修改余额
function onUpdateBalance(row: AgentApi.AgentListItem) {
balanceModalApi.setData({ agentId: row.id }).open();
}
// 查看佣金记录 // 查看佣金记录
function onViewCommission(row: AgentApi.AgentListItem) { function onViewCommission(row: AgentApi.AgentListItem) {
commissionModalApi.setData({ agentId: row.id }).open(); commissionModalApi.setData({ agentId: row.id }).open();
@@ -265,6 +301,11 @@ function onViewPlatformDeduction(row: AgentApi.AgentListItem) {
platformDeductionModalApi.setData({ agentId: row.id }).open(); platformDeductionModalApi.setData({ agentId: row.id }).open();
} }
// 查看历史佣金记录
function onViewCommissionHistory(row: AgentApi.AgentListItem) {
commissionHistoryModalApi.setData({ agentId: row.id }).open();
}
// 刷新处理 // 刷新处理
function onRefresh() { function onRefresh() {
gridApi.query(); gridApi.query();
@@ -276,10 +317,12 @@ function onRefresh() {
<FormDrawer @success="onRefresh" /> <FormDrawer @success="onRefresh" />
<LinkModalComponent /> <LinkModalComponent />
<CommissionModalComponent /> <CommissionModalComponent />
<CommissionHistoryModalComponent />
<CommissionDeductionModalComponent /> <CommissionDeductionModalComponent />
<PlatformDeductionModalComponent /> <PlatformDeductionModalComponent />
<RewardModalComponent /> <RewardModalComponent />
<WithdrawalModalComponent /> <WithdrawalModalComponent />
<BalanceModalComponent @success="onRefresh" />
<!-- 上级代理信息卡片 --> <!-- 上级代理信息卡片 -->
<Card v-if="parentAgentId" class="mb-4"> <Card v-if="parentAgentId" class="mb-4">

View File

@@ -0,0 +1,161 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { getAgentWallet, updateAgentWalletBalance } from '#/api/agent';
import type { AgentApi } from '#/api/agent';
interface ModalData {
agentId: number;
}
// 钱包信息
const walletInfo = ref<AgentApi.AgentWalletInfo>({
balance: 0,
frozen_balance: 0,
total_earnings: 0,
});
const loading = ref(false);
const emit = defineEmits<{
(e: 'success'): void;
}>();
const [ModalComponent, modalApi] = useVbenModal({
title: '修改余额',
destroyOnClose: true,
onOpenChange(isOpen) {
if (isOpen) {
fetchWalletInfo();
// 重置表单
formApi.setValues({
operation_type: 'add',
amount: undefined,
});
}
},
});
const modalData = computed(() => modalApi.getData<ModalData>());
// 获取钱包信息
async function fetchWalletInfo() {
const agentId = modalData.value?.agentId;
if (!agentId) return;
try {
loading.value = true;
const data = await getAgentWallet(agentId);
walletInfo.value = data;
} catch (error: any) {
message.error(error.message || '获取钱包信息失败');
} finally {
loading.value = false;
}
}
// 表单引用
const [Form, formApi] = useVbenForm({
schema: [
{
component: 'RadioGroup',
fieldName: 'operation_type',
label: '操作类型',
componentProps: {
options: [
{ label: '增加余额', value: 'add' },
{ label: '减少余额', value: 'subtract' },
],
},
rules: 'required',
defaultValue: 'add',
},
{
component: 'InputNumber',
fieldName: 'amount',
label: '金额',
componentProps: {
precision: 2,
min: 0.01,
placeholder: '请输入金额',
style: { width: '100%' },
},
rules: 'required',
},
],
showDefaultActions: false,
});
// 提交前确认
async function handleSubmit() {
try {
const { valid } = await formApi.validate();
if (!valid) return;
const values = await formApi.getValues();
if (!values) return;
// 根据操作类型确定金额正负
const finalAmount = values.operation_type === 'subtract' ? -Math.abs(values.amount) : Math.abs(values.amount);
// 二次确认
Modal.confirm({
title: '确认修改余额',
content: `您确定要${values.operation_type === 'add' ? '增加' : '减少'} ¥${Math.abs(values.amount).toFixed(2)} 的余额吗?`,
onOk: async () => {
try {
await updateAgentWalletBalance({
agent_id: modalData.value?.agentId || 0,
amount: finalAmount,
});
message.success('修改余额成功');
modalApi.close();
emit('success');
} catch (error: any) {
message.error(error.message || '修改余额失败');
}
},
});
} catch (error) {
// 表单验证失败,不处理
}
}
// 重置表单
function handleReset() {
formApi.setValues({
operation_type: 'add',
amount: undefined,
});
}
</script>
<template>
<ModalComponent width="500px" :footer="false">
<div class="balance-modal">
<Form />
<div class="form-actions">
<a-button @click="handleReset">重置</a-button>
<a-button type="primary" @click="handleSubmit">确认修改</a-button>
</div>
</div>
</ModalComponent>
</template>
<style lang="less" scoped>
.balance-modal {
padding: 16px 0;
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
}
</style>

View File

@@ -0,0 +1,316 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Card, Statistic, Row, Col, DatePicker, Button, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAgentCommissionList } from '#/api/agent';
import type { AgentApi } from '#/api';
import dayjs, { Dayjs } from 'dayjs';
interface ModalData {
agentId: number;
}
const [Modal, modalApi] = useVbenModal({
title: '历史佣金记录',
destroyOnClose: true,
footer: false,
});
const modalData = computed(() => modalApi.getData<ModalData>());
// 时间范围选择
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
// 统计数据
const statistics = ref({
totalAmount: 0,
settledAmount: 0,
frozenAmount: 0,
refundedAmount: 0,
totalCount: 0,
settledCount: 0,
frozenCount: 0,
refundedCount: 0,
});
// 快捷时间范围选择
function selectTimeRange(range: string) {
const now = dayjs();
let startDate: Dayjs;
switch (range) {
case '7d':
startDate = now.subtract(7, 'day');
break;
case '1m':
startDate = now.subtract(1, 'month');
break;
case '3m':
startDate = now.subtract(3, 'month');
break;
case '1y':
startDate = now.subtract(1, 'year');
break;
default:
dateRange.value = undefined;
return;
}
dateRange.value = [startDate, now];
}
// 重置时间范围
function resetTimeRange() {
dateRange.value = undefined;
}
// 表格配置
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
fieldMappingTime: [['create_time', ['create_time_start', 'create_time_end']]],
schema: [
{
component: 'InputNumber',
fieldName: 'order_id',
label: '订单ID',
componentProps: {
placeholder: '请输入订单ID',
style: { width: '100%' },
},
},
{
component: 'Input',
fieldName: 'product_name',
label: '产品名称',
componentProps: {
placeholder: '请输入产品名称',
},
},
{
component: 'Select',
fieldName: 'status',
label: '状态',
componentProps: {
allowClear: true,
placeholder: '请选择状态',
options: [
{ label: '已结算', value: 0 },
{ label: '冻结中', value: 1 },
{ label: '已退款', value: 2 },
],
},
},
],
submitOnChange: true,
},
gridOptions: {
columns: [
{
field: 'id',
title: 'ID',
width: 80,
},
{
field: 'order_id',
title: '订单ID',
width: 100,
},
{
field: 'amount',
title: '佣金金额',
width: 120,
formatter: ({ cellValue }: { cellValue: number }) =>
`¥${cellValue.toFixed(2)}`,
},
{
field: 'product_name',
title: '产品名称',
width: 180,
},
{
field: 'status',
title: '状态',
width: 100,
formatter: ({ cellValue }: { cellValue: number }) => {
const statusMap: Record<number, string> = {
0: '已结算',
1: '冻结中',
2: '已退款',
};
return statusMap[cellValue] || '未知';
},
className: ({ cellValue }: { cellValue: number }) => {
if (cellValue === 0) return 'text-green-600';
if (cellValue === 1) return 'text-orange-600';
if (cellValue === 2) return 'text-red-600';
return '';
},
},
{
field: 'create_time',
title: '创建时间',
width: 160,
sortable: true,
},
],
height: 'auto',
keepSource: true,
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
proxyConfig: {
ajax: {
query: async ({ page }: { page: { currentPage: number; pageSize: number } }, formValues: Record<string, any>) => {
const params: any = {
agent_id: modalData.value?.agentId,
...formValues,
page: page.currentPage,
pageSize: page.pageSize,
};
// 添加时间范围参数
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
params.create_time_start = dateRange.value[0].format('YYYY-MM-DD HH:mm:ss');
params.create_time_end = dateRange.value[1].format('YYYY-MM-DD HH:mm:ss');
}
const result = await getAgentCommissionList(params);
// 更新统计数据
updateStatistics(result.items || []);
return result;
},
},
props: {
result: 'items',
total: 'total',
},
autoLoad: true,
},
},
});
// 更新统计数据
function updateStatistics(items: AgentApi.AgentCommissionListItem[]) {
statistics.value = {
totalCount: items.length,
totalAmount: items.reduce((sum, item) => sum + item.amount, 0),
settledCount: items.filter(item => item.status === 0).length,
settledAmount: items.filter(item => item.status === 0).reduce((sum, item) => sum + item.amount, 0),
frozenCount: items.filter(item => item.status === 1).length,
frozenAmount: items.filter(item => item.status === 1).reduce((sum, item) => sum + item.amount, 0),
refundedCount: items.filter(item => item.status === 2).length,
refundedAmount: items.filter(item => item.status === 2).reduce((sum, item) => sum + item.amount, 0),
};
}
// 监听时间范围变化,自动刷新表格
watch(dateRange, () => {
if (gridApi) {
gridApi.query();
}
});
</script>
<template>
<Modal class="w-[calc(100vw-200px)]">
<div class="commission-history-modal">
<!-- 时间范围选择 -->
<Card class="mb-4" title="时间范围选择">
<Space :size="12">
<span class="text-sm text-gray-600">快速选择</span>
<Button size="small" @click="selectTimeRange('7d')">近7日</Button>
<Button size="small" @click="selectTimeRange('1m')">近1月</Button>
<Button size="small" @click="selectTimeRange('3m')">近3月</Button>
<Button size="small" @click="selectTimeRange('1y')">近1年</Button>
<Button size="small" type="default" @click="resetTimeRange">全部</Button>
<span class="text-sm text-gray-400 ml-4">|</span>
<span class="text-sm text-gray-600">自定义</span>
<DatePicker.RangePicker
v-model:value="dateRange"
show-time
format="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']"
style="width: 380px"
/>
</Space>
</Card>
<!-- 统计卡片 -->
<Card class="mb-4" title="统计信息">
<Row :gutter="[16, 16]">
<Col :span="6">
<Statistic title="总记录数" :value="statistics.totalCount" />
</Col>
<Col :span="6">
<Statistic
title="总佣金金额"
:value="statistics.totalAmount"
:precision="2"
prefix="¥"
:value-style="{ color: '#1890ff' }"
/>
</Col>
<Col :span="6">
<Statistic
title="已结算金额"
:value="statistics.settledAmount"
:precision="2"
prefix="¥"
:value-style="{ color: '#52c41a' }"
/>
<div class="text-sm text-gray-500 mt-1">
{{ statistics.settledCount }} 条记录
</div>
</Col>
<Col :span="6">
<Statistic
title="冻结中金额"
:value="statistics.frozenAmount"
:precision="2"
prefix="¥"
:value-style="{ color: '#fa8c16' }"
/>
<div class="text-sm text-gray-500 mt-1">
{{ statistics.frozenCount }} 条记录
</div>
</Col>
</Row>
<Row :gutter="[16, 16]" class="mt-4">
<Col :span="12">
<Statistic
title="已退款金额"
:value="statistics.refundedAmount"
:precision="2"
prefix="¥"
:value-style="{ color: '#f5222d' }"
/>
<div class="text-sm text-gray-500 mt-1">
{{ statistics.refundedCount }} 条记录
</div>
</Col>
</Row>
</Card>
<!-- 佣金记录列表 -->
<Grid :table-title="`代理商 ID: ${modalData?.agentId} 的历史佣金记录`" />
</div>
</Modal>
</template>
<style lang="less" scoped>
.commission-history-modal {
padding: 16px;
}
</style>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed } from 'vue';
import { Page, useVbenDrawer } from '@vben/common-ui'; import { Page, useVbenDrawer } from '@vben/common-ui';
import { Button, Space } from 'ant-design-vue'; import { Button, Space } from 'ant-design-vue';
@@ -54,7 +54,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
submitOnChange: true, submitOnChange: true,
}, },
gridOptions: { gridOptions: {
columns: useWithdrawalColumns(onActionClick), columns: useWithdrawalColumns(),
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ query: async ({
@@ -64,9 +64,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
page: QueryParams; page: QueryParams;
form: Record<string, any>; form: Record<string, any>;
}) => { }) => {
// 过滤掉 undefined 和 null 值
const filteredForm = Object.fromEntries(
Object.entries(form).filter(
([_, value]) => value !== undefined && value !== null && value !== '',
),
);
return await getAgentWithdrawalList({ return await getAgentWithdrawalList({
...queryParams.value, ...queryParams.value,
...form, ...filteredForm,
page: page.currentPage, page: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
}); });