Files
soul/lib/modules/distribution/service.ts
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

911 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 分销服务
* 核心功能:绑定追踪、过期检测、佣金计算、自动提现
*/
import type {
DistributionBinding,
Distributor,
WithdrawRecord,
ClickRecord,
DistributionConfig,
ExpireReminder,
DistributionOverview,
} from './types';
// 默认分销配置
export const DEFAULT_DISTRIBUTION_CONFIG: DistributionConfig = {
bindingDays: 30, // 30天绑定期
bindingPriority: 'first', // 首次绑定优先
defaultCommissionRate: 90, // 默认90%佣金
levelRates: {
normal: 90,
silver: 92,
gold: 95,
diamond: 98,
},
minWithdrawAmount: 10, // 最低10元提现
withdrawFeeRate: 0, // 0手续费
autoWithdrawEnabled: true, // 允许自动提现
autoWithdrawTime: '10:00', // 每天10点自动提现
expireRemindDays: 3, // 过期前3天提醒
enabled: true,
};
// 存储键名
const STORAGE_KEYS = {
BINDINGS: 'distribution_bindings',
DISTRIBUTORS: 'distribution_distributors',
WITHDRAWALS: 'distribution_withdrawals',
CLICKS: 'distribution_clicks',
CONFIG: 'distribution_config',
REMINDERS: 'distribution_reminders',
};
/**
* 生成唯一ID
*/
function generateId(prefix: string = ''): string {
return `${prefix}${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 获取配置
*/
export function getDistributionConfig(): DistributionConfig {
if (typeof window === 'undefined') return DEFAULT_DISTRIBUTION_CONFIG;
const stored = localStorage.getItem(STORAGE_KEYS.CONFIG);
return stored ? { ...DEFAULT_DISTRIBUTION_CONFIG, ...JSON.parse(stored) } : DEFAULT_DISTRIBUTION_CONFIG;
}
/**
* 更新配置
*/
export function updateDistributionConfig(config: Partial<DistributionConfig>): DistributionConfig {
if (typeof window === 'undefined') return DEFAULT_DISTRIBUTION_CONFIG;
const current = getDistributionConfig();
const updated = { ...current, ...config };
localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(updated));
return updated;
}
// ============== 绑定管理 ==============
/**
* 获取所有绑定
*/
export function getAllBindings(): DistributionBinding[] {
if (typeof window === 'undefined') return [];
return JSON.parse(localStorage.getItem(STORAGE_KEYS.BINDINGS) || '[]');
}
/**
* 记录链接点击并创建绑定
*/
export function recordClickAndBinding(params: {
referralCode: string;
referrerId: string;
visitorId: string;
visitorPhone?: string;
visitorNickname?: string;
source: 'link' | 'miniprogram' | 'poster' | 'qrcode';
sourceDetail?: string;
deviceInfo?: DistributionBinding['deviceInfo'];
}): { click: ClickRecord; binding: DistributionBinding | null } {
if (typeof window === 'undefined') {
return { click: {} as ClickRecord, binding: null };
}
const config = getDistributionConfig();
const now = new Date();
// 1. 记录点击
const click: ClickRecord = {
id: generateId('click_'),
referralCode: params.referralCode,
referrerId: params.referrerId,
visitorId: params.visitorId,
isNewVisitor: !hasExistingBinding(params.visitorId),
source: params.source,
deviceInfo: params.deviceInfo,
registered: false,
purchased: false,
clickTime: now.toISOString(),
createdAt: now.toISOString(),
};
const clicks = JSON.parse(localStorage.getItem(STORAGE_KEYS.CLICKS) || '[]');
clicks.push(click);
localStorage.setItem(STORAGE_KEYS.CLICKS, JSON.stringify(clicks));
// 2. 检查是否需要创建绑定
let binding: DistributionBinding | null = null;
// 检查现有绑定
const existingBinding = getActiveBindingForVisitor(params.visitorId);
if (!existingBinding || config.bindingPriority === 'last') {
// 创建新绑定(如果没有现有绑定,或策略是"最后绑定"
const expireDate = new Date(now);
expireDate.setDate(expireDate.getDate() + config.bindingDays);
binding = {
id: generateId('bind_'),
referrerId: params.referrerId,
referrerCode: params.referralCode,
visitorId: params.visitorId,
visitorPhone: params.visitorPhone,
visitorNickname: params.visitorNickname,
bindingTime: now.toISOString(),
expireTime: expireDate.toISOString(),
status: 'active',
source: params.source,
sourceDetail: params.sourceDetail,
deviceInfo: params.deviceInfo,
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
};
// 如果是"最后绑定"策略,先作废之前的绑定
if (existingBinding && config.bindingPriority === 'last') {
cancelBinding(existingBinding.id, '新绑定覆盖');
}
const bindings = getAllBindings();
bindings.push(binding);
localStorage.setItem(STORAGE_KEYS.BINDINGS, JSON.stringify(bindings));
// 更新分销商统计
updateDistributorStats(params.referrerId);
}
return { click, binding };
}
/**
* 检查是否有现有绑定
*/
function hasExistingBinding(visitorId: string): boolean {
const bindings = getAllBindings();
return bindings.some(b => b.visitorId === visitorId);
}
/**
* 获取访客的有效绑定
*/
export function getActiveBindingForVisitor(visitorId: string): DistributionBinding | null {
const bindings = getAllBindings();
const now = new Date();
return bindings.find(b =>
b.visitorId === visitorId &&
b.status === 'active' &&
new Date(b.expireTime) > now
) || null;
}
/**
* 获取分销商的所有绑定
*/
export function getBindingsForDistributor(referrerId: string): DistributionBinding[] {
const bindings = getAllBindings();
return bindings.filter(b => b.referrerId === referrerId);
}
/**
* 取消绑定
*/
export function cancelBinding(bindingId: string, reason?: string): boolean {
const bindings = getAllBindings();
const index = bindings.findIndex(b => b.id === bindingId);
if (index === -1) return false;
bindings[index] = {
...bindings[index],
status: 'cancelled',
updatedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.BINDINGS, JSON.stringify(bindings));
return true;
}
/**
* 将绑定标记为已转化(用户付款后调用)
*/
export function convertBinding(params: {
visitorId: string;
orderId: string;
orderAmount: number;
}): { binding: DistributionBinding | null; commission: number } {
const binding = getActiveBindingForVisitor(params.visitorId);
if (!binding) {
return { binding: null, commission: 0 };
}
const config = getDistributionConfig();
const distributor = getDistributor(binding.referrerId);
const commissionRate = distributor?.commissionRate || config.defaultCommissionRate;
const commission = params.orderAmount * (commissionRate / 100);
// 更新绑定状态
const bindings = getAllBindings();
const index = bindings.findIndex(b => b.id === binding.id);
if (index !== -1) {
bindings[index] = {
...bindings[index],
status: 'converted',
convertedAt: new Date().toISOString(),
orderId: params.orderId,
orderAmount: params.orderAmount,
commission,
updatedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.BINDINGS, JSON.stringify(bindings));
// 更新分销商收益
addDistributorEarnings(binding.referrerId, commission);
// 更新点击记录
updateClickPurchaseStatus(binding.referrerId, params.visitorId);
}
return { binding: bindings[index], commission };
}
/**
* 检查并处理过期绑定
*/
export function processExpiredBindings(): {
expired: DistributionBinding[];
expiringSoon: DistributionBinding[];
} {
const bindings = getAllBindings();
const config = getDistributionConfig();
const now = new Date();
const remindThreshold = new Date();
remindThreshold.setDate(remindThreshold.getDate() + config.expireRemindDays);
const expired: DistributionBinding[] = [];
const expiringSoon: DistributionBinding[] = [];
const updatedBindings = bindings.map(binding => {
if (binding.status !== 'active') return binding;
const expireTime = new Date(binding.expireTime);
if (expireTime <= now) {
// 已过期
expired.push(binding);
createExpireReminder(binding, 'expired');
return {
...binding,
status: 'expired' as const,
updatedAt: now.toISOString(),
};
} else if (expireTime <= remindThreshold) {
// 即将过期
const daysRemaining = Math.ceil((expireTime.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
expiringSoon.push(binding);
createExpireReminder(binding, 'expiring_soon', daysRemaining);
}
return binding;
});
localStorage.setItem(STORAGE_KEYS.BINDINGS, JSON.stringify(updatedBindings));
// 更新相关分销商统计
const affectedDistributors = new Set([
...expired.map(b => b.referrerId),
...expiringSoon.map(b => b.referrerId),
]);
affectedDistributors.forEach(distributorId => {
updateDistributorStats(distributorId);
});
return { expired, expiringSoon };
}
// ============== 提醒管理 ==============
/**
* 创建过期提醒
*/
function createExpireReminder(
binding: DistributionBinding,
type: 'expiring_soon' | 'expired',
daysRemaining?: number
): void {
const reminders = JSON.parse(localStorage.getItem(STORAGE_KEYS.REMINDERS) || '[]') as ExpireReminder[];
// 检查是否已存在相同提醒
const exists = reminders.some(r =>
r.bindingId === binding.id &&
r.reminderType === type
);
if (exists) return;
const reminder: ExpireReminder = {
id: generateId('remind_'),
bindingId: binding.id,
distributorId: binding.referrerId,
bindingInfo: {
visitorNickname: binding.visitorNickname,
visitorPhone: binding.visitorPhone,
bindingTime: binding.bindingTime,
expireTime: binding.expireTime,
},
reminderType: type,
daysRemaining,
isRead: false,
createdAt: new Date().toISOString(),
};
reminders.push(reminder);
localStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(reminders));
}
/**
* 获取分销商的提醒
*/
export function getRemindersForDistributor(distributorId: string): ExpireReminder[] {
const reminders = JSON.parse(localStorage.getItem(STORAGE_KEYS.REMINDERS) || '[]') as ExpireReminder[];
return reminders.filter(r => r.distributorId === distributorId);
}
/**
* 获取未读提醒数量
*/
export function getUnreadReminderCount(distributorId: string): number {
const reminders = getRemindersForDistributor(distributorId);
return reminders.filter(r => !r.isRead).length;
}
/**
* 标记提醒已读
*/
export function markReminderRead(reminderId: string): void {
const reminders = JSON.parse(localStorage.getItem(STORAGE_KEYS.REMINDERS) || '[]') as ExpireReminder[];
const index = reminders.findIndex(r => r.id === reminderId);
if (index !== -1) {
reminders[index].isRead = true;
reminders[index].readAt = new Date().toISOString();
localStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(reminders));
}
}
// ============== 分销商管理 ==============
/**
* 获取分销商信息
*/
export function getDistributor(userId: string): Distributor | null {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
return distributors.find(d => d.userId === userId) || null;
}
/**
* 获取或创建分销商
*/
export function getOrCreateDistributor(params: {
userId: string;
nickname: string;
phone: string;
referralCode: string;
}): Distributor {
let distributor = getDistributor(params.userId);
if (!distributor) {
const config = getDistributionConfig();
distributor = {
id: generateId('dist_'),
userId: params.userId,
nickname: params.nickname,
phone: params.phone,
referralCode: params.referralCode,
totalClicks: 0,
totalBindings: 0,
activeBindings: 0,
convertedBindings: 0,
expiredBindings: 0,
totalEarnings: 0,
pendingEarnings: 0,
withdrawnEarnings: 0,
autoWithdraw: false,
autoWithdrawThreshold: config.minWithdrawAmount,
level: 'normal',
commissionRate: config.defaultCommissionRate,
status: 'active',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
distributors.push(distributor);
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
return distributor;
}
/**
* 更新分销商统计
*/
function updateDistributorStats(userId: string): void {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const index = distributors.findIndex(d => d.userId === userId);
if (index === -1) return;
const bindings = getBindingsForDistributor(userId);
const clicks = JSON.parse(localStorage.getItem(STORAGE_KEYS.CLICKS) || '[]') as ClickRecord[];
const userClicks = clicks.filter(c => c.referrerId === userId);
distributors[index] = {
...distributors[index],
totalClicks: userClicks.length,
totalBindings: bindings.length,
activeBindings: bindings.filter(b => b.status === 'active').length,
convertedBindings: bindings.filter(b => b.status === 'converted').length,
expiredBindings: bindings.filter(b => b.status === 'expired').length,
updatedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
/**
* 增加分销商收益
*/
function addDistributorEarnings(userId: string, amount: number): void {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const index = distributors.findIndex(d => d.userId === userId);
if (index === -1) return;
distributors[index] = {
...distributors[index],
totalEarnings: distributors[index].totalEarnings + amount,
pendingEarnings: distributors[index].pendingEarnings + amount,
updatedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
// 检查是否需要自动提现
checkAutoWithdraw(distributors[index]);
}
/**
* 更新点击记录的购买状态
*/
function updateClickPurchaseStatus(referrerId: string, visitorId: string): void {
const clicks = JSON.parse(localStorage.getItem(STORAGE_KEYS.CLICKS) || '[]') as ClickRecord[];
const index = clicks.findIndex(c => c.referrerId === referrerId && c.visitorId === visitorId);
if (index !== -1) {
clicks[index].purchased = true;
clicks[index].purchasedAt = new Date().toISOString();
localStorage.setItem(STORAGE_KEYS.CLICKS, JSON.stringify(clicks));
}
}
/**
* 设置自动提现
*/
export function setAutoWithdraw(params: {
userId: string;
enabled: boolean;
threshold?: number;
account?: Distributor['autoWithdrawAccount'];
}): boolean {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const index = distributors.findIndex(d => d.userId === params.userId);
if (index === -1) return false;
distributors[index] = {
...distributors[index],
autoWithdraw: params.enabled,
autoWithdrawThreshold: params.threshold || distributors[index].autoWithdrawThreshold,
autoWithdrawAccount: params.account || distributors[index].autoWithdrawAccount,
updatedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
return true;
}
// ============== 提现管理 ==============
/**
* 获取所有提现记录
*/
export function getAllWithdrawals(): WithdrawRecord[] {
if (typeof window === 'undefined') return [];
return JSON.parse(localStorage.getItem(STORAGE_KEYS.WITHDRAWALS) || '[]');
}
/**
* 获取分销商的提现记录
*/
export function getWithdrawalsForDistributor(distributorId: string): WithdrawRecord[] {
const withdrawals = getAllWithdrawals();
return withdrawals.filter(w => w.distributorId === distributorId);
}
/**
* 申请提现
*/
export function requestWithdraw(params: {
userId: string;
amount: number;
method: 'wechat' | 'alipay';
account: string;
accountName: string;
}): { success: boolean; withdrawal?: WithdrawRecord; error?: string } {
const config = getDistributionConfig();
const distributor = getDistributor(params.userId);
if (!distributor) {
return { success: false, error: '分销商不存在' };
}
if (params.amount < config.minWithdrawAmount) {
return { success: false, error: `最低提现金额为 ${config.minWithdrawAmount}` };
}
if (params.amount > distributor.pendingEarnings) {
return { success: false, error: '提现金额超过可提现余额' };
}
const fee = params.amount * config.withdrawFeeRate;
const actualAmount = params.amount - fee;
const withdrawal: WithdrawRecord = {
id: generateId('withdraw_'),
distributorId: distributor.id,
userId: params.userId,
userName: distributor.nickname,
amount: params.amount,
fee,
actualAmount,
method: params.method,
account: params.account,
accountName: params.accountName,
status: 'pending',
isAuto: false,
createdAt: new Date().toISOString(),
};
// 保存提现记录
const withdrawals = getAllWithdrawals();
withdrawals.push(withdrawal);
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 扣除待提现金额
deductDistributorPendingEarnings(params.userId, params.amount);
return { success: true, withdrawal };
}
/**
* 扣除分销商待提现金额
*/
function deductDistributorPendingEarnings(userId: string, amount: number): void {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const index = distributors.findIndex(d => d.userId === userId);
if (index !== -1) {
distributors[index].pendingEarnings -= amount;
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
}
/**
* 检查并执行自动提现
*/
function checkAutoWithdraw(distributor: Distributor): void {
if (!distributor.autoWithdraw || !distributor.autoWithdrawAccount) {
return;
}
if (distributor.pendingEarnings >= distributor.autoWithdrawThreshold) {
// 执行自动提现
const result = executeAutoWithdraw(distributor);
if (result.success) {
console.log(`自动提现成功: ${distributor.nickname}, 金额: ${distributor.pendingEarnings}`);
}
}
}
/**
* 执行自动提现
*/
export function executeAutoWithdraw(distributor: Distributor): { success: boolean; withdrawal?: WithdrawRecord; error?: string } {
if (!distributor.autoWithdrawAccount) {
return { success: false, error: '未配置自动提现账户' };
}
const config = getDistributionConfig();
const amount = distributor.pendingEarnings;
const fee = amount * config.withdrawFeeRate;
const actualAmount = amount - fee;
const withdrawal: WithdrawRecord = {
id: generateId('withdraw_'),
distributorId: distributor.id,
userId: distributor.userId,
userName: distributor.nickname,
amount,
fee,
actualAmount,
method: distributor.autoWithdrawAccount.type,
account: distributor.autoWithdrawAccount.account,
accountName: distributor.autoWithdrawAccount.name,
status: 'processing', // 自动提现直接进入处理状态
isAuto: true,
createdAt: new Date().toISOString(),
};
// 保存提现记录
const withdrawals = getAllWithdrawals();
withdrawals.push(withdrawal);
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 扣除待提现金额
deductDistributorPendingEarnings(distributor.userId, amount);
// 这里应该调用实际的支付接口进行打款
// 实际项目中需要对接微信/支付宝的企业付款接口
processWithdrawalPayment(withdrawal.id);
return { success: true, withdrawal };
}
/**
* 处理提现打款(模拟)
* 实际项目中需要对接支付接口
*/
export async function processWithdrawalPayment(withdrawalId: string): Promise<{ success: boolean; error?: string }> {
const withdrawals = getAllWithdrawals();
const index = withdrawals.findIndex(w => w.id === withdrawalId);
if (index === -1) {
return { success: false, error: '提现记录不存在' };
}
const withdrawal = withdrawals[index];
// 模拟支付接口调用
// 实际项目中应该调用:
// - 微信:企业付款到零钱 API
// - 支付宝:单笔转账到支付宝账户 API
try {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
// 更新提现状态为成功
withdrawals[index] = {
...withdrawal,
status: 'completed',
paymentNo: `PAY${Date.now()}`,
paymentTime: new Date().toISOString(),
completedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 更新分销商已提现金额
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const distIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
if (distIndex !== -1) {
distributors[distIndex].withdrawnEarnings += withdrawal.amount;
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
return { success: true };
} catch (error) {
// 打款失败
withdrawals[index] = {
...withdrawal,
status: 'failed',
paymentError: error instanceof Error ? error.message : '打款失败',
};
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 退还金额到待提现余额
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const distIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
if (distIndex !== -1) {
distributors[distIndex].pendingEarnings += withdrawal.amount;
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
return { success: false, error: '打款失败' };
}
}
/**
* 审核通过并打款
*/
export async function approveWithdrawal(withdrawalId: string, reviewedBy?: string): Promise<{ success: boolean; error?: string }> {
const withdrawals = getAllWithdrawals();
const index = withdrawals.findIndex(w => w.id === withdrawalId);
if (index === -1) {
return { success: false, error: '提现记录不存在' };
}
if (withdrawals[index].status !== 'pending') {
return { success: false, error: '该提现申请已处理' };
}
withdrawals[index] = {
...withdrawals[index],
status: 'processing',
reviewedBy,
reviewedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 执行打款
return processWithdrawalPayment(withdrawalId);
}
/**
* 拒绝提现
*/
export function rejectWithdrawal(withdrawalId: string, reason: string, reviewedBy?: string): { success: boolean; error?: string } {
const withdrawals = getAllWithdrawals();
const index = withdrawals.findIndex(w => w.id === withdrawalId);
if (index === -1) {
return { success: false, error: '提现记录不存在' };
}
const withdrawal = withdrawals[index];
if (withdrawal.status !== 'pending') {
return { success: false, error: '该提现申请已处理' };
}
withdrawals[index] = {
...withdrawal,
status: 'rejected',
reviewNote: reason,
reviewedBy,
reviewedAt: new Date().toISOString(),
};
localStorage.setItem(STORAGE_KEYS.WITHDRAWALS, JSON.stringify(withdrawals));
// 退还金额到待提现余额
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
const distIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
if (distIndex !== -1) {
distributors[distIndex].pendingEarnings += withdrawal.amount;
localStorage.setItem(STORAGE_KEYS.DISTRIBUTORS, JSON.stringify(distributors));
}
return { success: true };
}
// ============== 统计概览 ==============
/**
* 获取分销统计概览
*/
export function getDistributionOverview(): DistributionOverview {
const bindings = getAllBindings();
const clicks = JSON.parse(localStorage.getItem(STORAGE_KEYS.CLICKS) || '[]') as ClickRecord[];
const withdrawals = getAllWithdrawals();
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
const weekFromNow = new Date();
weekFromNow.setDate(weekFromNow.getDate() + 7);
// 今日数据
const todayClicks = clicks.filter(c => new Date(c.clickTime) >= today).length;
const todayBindings = bindings.filter(b => new Date(b.createdAt) >= today).length;
const todayConversions = bindings.filter(b =>
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today
).length;
const todayEarnings = bindings
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today)
.reduce((sum, b) => sum + (b.commission || 0), 0);
// 本月数据
const monthClicks = clicks.filter(c => new Date(c.clickTime) >= monthStart).length;
const monthBindings = bindings.filter(b => new Date(b.createdAt) >= monthStart).length;
const monthConversions = bindings.filter(b =>
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart
).length;
const monthEarnings = bindings
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart)
.reduce((sum, b) => sum + (b.commission || 0), 0);
// 总计数据
const totalConversions = bindings.filter(b => b.status === 'converted').length;
const totalEarnings = bindings
.filter(b => b.status === 'converted')
.reduce((sum, b) => sum + (b.commission || 0), 0);
// 即将过期数据
const expiringBindings = bindings.filter(b =>
b.status === 'active' &&
new Date(b.expireTime) <= weekFromNow &&
new Date(b.expireTime) > now
).length;
const expiredToday = bindings.filter(b =>
b.status === 'expired' &&
b.updatedAt && new Date(b.updatedAt) >= today
).length;
// 提现数据
const pendingWithdrawals = withdrawals.filter(w => w.status === 'pending').length;
const pendingWithdrawAmount = withdrawals
.filter(w => w.status === 'pending')
.reduce((sum, w) => sum + w.amount, 0);
// 转化率
const conversionRate = clicks.length > 0 ? (totalConversions / clicks.length) * 100 : 0;
return {
todayClicks,
todayBindings,
todayConversions,
todayEarnings,
monthClicks,
monthBindings,
monthConversions,
monthEarnings,
totalClicks: clicks.length,
totalBindings: bindings.length,
totalConversions,
totalEarnings,
expiringBindings,
expiredToday,
pendingWithdrawals,
pendingWithdrawAmount,
conversionRate,
lastUpdated: now.toISOString(),
};
}
/**
* 获取分销排行榜
*/
export function getDistributionRanking(limit: number = 10): Distributor[] {
const distributors = JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]') as Distributor[];
return distributors
.filter(d => d.status === 'active')
.sort((a, b) => b.totalEarnings - a.totalEarnings)
.slice(0, limit);
}
/**
* 获取所有分销商
*/
export function getAllDistributors(): Distributor[] {
if (typeof window === 'undefined') return [];
return JSON.parse(localStorage.getItem(STORAGE_KEYS.DISTRIBUTORS) || '[]');
}