feat: 落地打赏(个人码捐赠)模式并将付费面改为免费+赞助(无ICP证合规)

- 新增 Donation 模型/迁移/后端 API(GET,POST /donations)
- 新增前端 /donate 页:收款码+感谢留言+感谢墙
- 会员页/技能广场/用量包 下架付费,改免费开放+赞助入口
- 沙箱用量耗尽引导至 /donate 赞助
- 导航与页脚新增 赞助/联盟返佣 入口,补充 i18n
This commit is contained in:
TradeMate Dev
2026-07-11 15:02:08 +08:00
parent 31cc1e02ce
commit 00a3904eba
17 changed files with 600 additions and 538 deletions
+2
View File
@@ -25,6 +25,7 @@ import { LearningModule } from './modules/learning/learning.module';
import { SkillsModule } from './modules/skills/skills.module';
import { AiAssistantModule } from './modules/ai-assistant/ai-assistant.module';
import { PracticesModule } from './modules/practices/practices.module';
import { DonationsModule } from './modules/donations/donations.module';
import { RedisThrottlerStorage } from './common/redis-throttler-storage';
@Module({
@@ -45,6 +46,7 @@ import { RedisThrottlerStorage } from './common/redis-throttler-storage';
EnterpriseModule, NotificationModule, LearningModule, SkillsModule,
AiAssistantModule,
PracticesModule,
DonationsModule,
],
providers: [
{ provide: APP_GUARD, useClass: ThrottlerGuard },
@@ -0,0 +1,20 @@
import { Controller, Get, Post, Body, Query, HttpCode } from '@nestjs/common';
import { DonationsService } from './donations.service';
import { CreateDonationDto } from './dto/create-donation.dto';
@Controller('donations')
export class DonationsController {
constructor(private readonly donationsService: DonationsService) {}
@Get()
getList(@Query('limit') limit?: string) {
const parsed = limit ? Math.min(parseInt(limit, 10) || 20, 50) : 20;
return this.donationsService.getList(parsed);
}
@Post()
@HttpCode(200)
create(@Body() dto: CreateDonationDto) {
return this.donationsService.create(dto);
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { DonationsService } from './donations.service';
import { DonationsController } from './donations.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [DonationsController],
providers: [DonationsService],
exports: [DonationsService],
})
export class DonationsModule {}
@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class DonationsService {
constructor(private prisma: PrismaService) {}
async getList(limit = 20) {
const donations = await this.prisma.donation.findMany({
orderBy: { createdAt: 'desc' },
take: limit,
select: {
id: true,
name: true,
message: true,
channel: true,
amount: true,
createdAt: true,
},
});
return {
donations: donations.map((d) => ({
...d,
amount: d.amount != null ? Number(d.amount) : null,
})),
};
}
async create(data: {
name?: string;
message?: string;
channel?: 'ALIPAY' | 'WECHAT';
amount?: number;
}) {
const donation = await this.prisma.donation.create({
data: {
name: data.name ?? null,
message: data.message ?? null,
channel: data.channel ?? 'ALIPAY',
amount: data.amount != null ? data.amount : null,
},
});
return {
success: true,
donation: {
...donation,
amount: donation.amount != null ? Number(donation.amount) : null,
},
};
}
}
@@ -0,0 +1,22 @@
import { IsOptional, IsString, IsIn, IsNumber, MaxLength, Min } from 'class-validator';
export class CreateDonationDto {
@IsOptional()
@IsString()
@MaxLength(40)
name?: string;
@IsOptional()
@IsString()
@MaxLength(280)
message?: string;
@IsOptional()
@IsIn(['ALIPAY', 'WECHAT'])
channel?: 'ALIPAY' | 'WECHAT';
@IsOptional()
@IsNumber({ allowNaN: false, allowInfinity: false })
@Min(0)
amount?: number;
}