feat: 落地打赏(个人码捐赠)模式并将付费面改为免费+赞助(无ICP证合规)
- 新增 Donation 模型/迁移/后端 API(GET,POST /donations) - 新增前端 /donate 页:收款码+感谢留言+感谢墙 - 会员页/技能广场/用量包 下架付费,改免费开放+赞助入口 - 沙箱用量耗尽引导至 /donate 赞助 - 导航与页脚新增 赞助/联盟返佣 入口,补充 i18n
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user