feat(admin,backend): 新增平台总后台超级管理员最高权限体系与会员列表、续费列表、订阅管理中心

这个提交包含在:
wikiboss
2026-09-21 11:05:44 +08:00
父节点 a7e20c9c5a
当前提交 e73c15bd22
共有 16 个文件被更改,包括 2447 次插入 和 333 次删除

查看文件

@@ -0,0 +1,137 @@
import { Body, Inject, Post, Provide } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { CoolController, BaseController } from '@cool-midway/core';
import { PlatformSubscriptionService } from '../../service/platform-subscription';
import {
ManualRenewDTO,
PlanConfigItem,
PlatformOverviewResult,
TenantPlanType,
UpdateSubscriberDTO,
} from '../../../../comm/types/omni';
export const adminPlatformSubscriptionCurdOption = {
api: [] as Array<'add' | 'delete' | 'update' | 'info' | 'list' | 'page'>,
service: PlatformSubscriptionService,
serviceApis: [
{ method: 'overview', summary: '平台会员与订阅整体概览' },
{ method: 'subscribersPage', summary: '全网会员分页列表' },
{ method: 'subscribersUpdate', summary: '超管修改会员套餐/配额/延期' },
{ method: 'renewalsPage', summary: '全网续费流水列表' },
{ method: 'manualRenew', summary: '超管手动代续费与补录订单' },
{ method: 'plansList', summary: 'SaaS 套餐方案矩阵列表' },
{ method: 'plansUpdate', summary: '配置修改套餐规格与定价' },
],
};
/**
* 平台总后台超级管理员 (Platform Super Admin)
* 专有控制中心:全网会员列表、续费流水、订阅管理与套餐策略
* 路由前缀: /admin/team/platform
*/
@Provide()
@CoolController(adminPlatformSubscriptionCurdOption)
export class AdminPlatformSubscriptionController extends BaseController {
@Inject()
ctx: Context;
@Inject()
platformSubscriptionService: PlatformSubscriptionService;
/**
* 平台概览 KPI
* 路由: POST /admin/team/platform/overview
*/
@Post('/overview', { summary: '平台会员与订阅整体概览' })
async overview() {
const result: PlatformOverviewResult =
await this.platformSubscriptionService.getOverview();
return this.ok(result);
}
/**
* 会员列表分页检索
* 路由: POST /admin/team/platform/subscribers/page
*/
@Post('/subscribers/page', { summary: '全网会员分页列表' })
async subscribersPage(
@Body()
body?: {
page?: number;
size?: number;
planType?: TenantPlanType;
status?: string;
keyword?: string;
}
) {
const result = await this.platformSubscriptionService.getSubscribers(body);
return this.ok(result);
}
/**
* 超管修改会员信息 (套餐、配额、延期、状态)
* 路由: POST /admin/team/platform/subscribers/update
*/
@Post('/subscribers/update', { summary: '超管修改会员套餐/配额/延期' })
async subscribersUpdate(@Body() body: UpdateSubscriberDTO) {
const result = await this.platformSubscriptionService.updateSubscriber(body);
return this.ok(result);
}
/**
* 续费订单流水列表
* 路由: POST /admin/team/platform/renewals/page
*/
@Post('/renewals/page', { summary: '全网续费流水列表' })
async renewalsPage(
@Body()
body?: {
page?: number;
size?: number;
status?: string;
keyword?: string;
}
) {
const result = await this.platformSubscriptionService.getRenewals(body);
return this.ok(result);
}
/**
* 超管手动代续费
* 路由: POST /admin/team/platform/renewals/manual-renew
*/
@Post('/renewals/manual-renew', { summary: '超管手动代续费与补录订单' })
async manualRenew(@Body() body: ManualRenewDTO) {
const result = await this.platformSubscriptionService.manualRenew(body);
return this.ok(result);
}
/**
* 套餐规格列表
* 路由: POST /admin/team/platform/plans/list
*/
@Post('/plans/list', { summary: 'SaaS 套餐方案矩阵列表' })
async plansList() {
const result = await this.platformSubscriptionService.getPlans();
return this.ok(result);
}
/**
* 更新套餐规格
* 路由: POST /admin/team/platform/plans/update
*/
@Post('/plans/update', { summary: '配置修改套餐规格与定价' })
async plansUpdate(
@Body()
body: {
planType: TenantPlanType;
params: Partial<PlanConfigItem>;
}
) {
const result = await this.platformSubscriptionService.updatePlan(
body.planType,
body.params
);
return this.ok(result);
}
}

查看文件

@@ -1,7 +1,6 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export type SupportedCurrency = 'USD' | 'AED' | 'EUR' | 'GBP' | 'JPY' | 'CNY';
import { SupportedCurrency, TaxCalculationResult } from '../../../comm/types/omni';
/**
* 相对 USD 的基准出海商业结算汇率表
@@ -32,14 +31,6 @@ export interface TaxCalculationParams {
vatNumber?: string;
}
export interface TaxCalculationResult {
taxRate: number;
taxAmount: number;
totalAmount: number;
taxName: string;
isReverseCharge?: boolean;
}
export interface InvoiceTaxSummary {
currency: SupportedCurrency;
currencySymbol: string;

查看文件

@@ -19,6 +19,8 @@ const CURRENCY_SYMBOLS: Record<SupportedCurrency, string> = {
EUR: '€',
GBP: '£',
CNY: '¥',
AED: 'AED ',
JPY: '¥',
};
/**

查看文件

@@ -0,0 +1,514 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import * as moment from 'moment';
import { TeamInvoiceEntity } from '../entity/invoice';
import { TeamSubscriptionEntity } from '../entity/subscription';
import {
ManualRenewDTO,
PlanConfigItem,
PlatformOverviewResult,
RenewalOrderItem,
SubscriberItem,
TenantPlanType,
UpdateSubscriberDTO,
} from '../../../comm/types/omni';
/**
* 平台总后台超级管理员 (Platform Super Admin)
* 会员、续费与订阅全周期运营管控服务
*/
@Provide()
export class PlatformSubscriptionService extends BaseService {
@InjectEntityModel(TeamInvoiceEntity)
teamInvoiceEntity?: Repository<TeamInvoiceEntity>;
@InjectEntityModel(TeamSubscriptionEntity)
teamSubscriptionEntity?: Repository<TeamSubscriptionEntity>;
// 内存化种子会员数据池 (保障无缝离线测试与即席演示)
private subscribersStore: SubscriberItem[] = [
{
id: 1,
tenantId: 1,
tenantName: 'Vanguard Global Trade Ltd. (出海先锋外贸)',
adminName: 'David Zhang',
adminEmail: 'david@vanguard-trade.com',
adminPhone: '+86 13800138001',
planType: 'enterprise',
status: 'active',
cardsUsed: 42,
cardsLimit: 100,
seatsUsed: 18,
seatsLimit: 50,
totalPaidAmount: 7176,
currency: 'USD',
startDate: '2025-09-01',
expireDate: '2026-09-01',
createdAt: '2025-08-28 10:20:00',
},
{
id: 2,
tenantId: 2,
tenantName: 'Berlin Precision Robotics GmbH (柏林精密机器人)',
adminName: 'Klaus Schmidt',
adminEmail: 'klaus@berlin-robotics.de',
adminPhone: '+49 30 1234567',
planType: 'pro',
status: 'active',
cardsUsed: 5,
cardsLimit: 10,
seatsUsed: 4,
seatsLimit: 5,
totalPaidAmount: 1176,
currency: 'EUR',
startDate: '2026-01-15',
expireDate: '2027-01-15',
createdAt: '2026-01-10 14:35:12',
},
{
id: 3,
tenantId: 3,
tenantName: 'Dubai Apex Logistics LLC (迪拜巅峰国际物流)',
adminName: 'Ahmed Al-Mansoor',
adminEmail: 'ahmed@apexlogistics.ae',
adminPhone: '+971 4 9876543',
planType: 'enterprise',
status: 'active',
cardsUsed: 35,
cardsLimit: 100,
seatsUsed: 12,
seatsLimit: 50,
totalPaidAmount: 3588,
currency: 'USD',
startDate: '2026-03-01',
expireDate: '2027-03-01',
createdAt: '2026-02-25 09:12:44',
},
{
id: 4,
tenantId: 4,
tenantName: 'Silicon Horizon Tech Inc. (硅谷地平线科技)',
adminName: 'Sarah Jenkins',
adminEmail: 'sarah@siliconhorizon.io',
adminPhone: '+1 415 8009988',
planType: 'pro',
status: 'past_due',
cardsUsed: 8,
cardsLimit: 10,
seatsUsed: 5,
seatsLimit: 5,
totalPaidAmount: 588,
currency: 'USD',
startDate: '2025-09-10',
expireDate: '2026-09-10',
createdAt: '2025-09-05 16:40:22',
},
{
id: 5,
tenantId: 5,
tenantName: 'Nordic Clean Energy AB (北欧洁能科技)',
adminName: 'Erik Lindqvist',
adminEmail: 'erik@nordic-energy.se',
adminPhone: '+46 8 5556677',
planType: 'pro',
status: 'active',
cardsUsed: 4,
cardsLimit: 10,
seatsUsed: 3,
seatsLimit: 5,
totalPaidAmount: 1176,
currency: 'EUR',
startDate: '2025-11-20',
expireDate: '2026-11-20',
createdAt: '2025-11-15 11:20:00',
},
{
id: 6,
tenantId: 6,
tenantName: 'Tokyo Smart Automation KK (东京智能物联)',
adminName: 'Kenji Sato',
adminEmail: 'kenji@tokyo-smart.jp',
adminPhone: '+81 3 33445566',
planType: 'free',
status: 'active',
cardsUsed: 1,
cardsLimit: 1,
seatsUsed: 1,
seatsLimit: 1,
totalPaidAmount: 0,
currency: 'USD',
startDate: '2026-08-01',
expireDate: '2099-12-31',
createdAt: '2026-08-01 10:00:00',
},
];
// 内存化种子续费流水池
private renewalsStore: RenewalOrderItem[] = [
{
id: 1,
orderNo: 'ORD-20260921-0089',
tenantId: 1,
tenantName: 'Vanguard Global Trade Ltd.',
planType: 'enterprise',
billingCycle: 'yearly',
amount: 3588,
currency: 'USD',
channel: 'stripe',
status: 'paid',
paidAt: '2026-09-21 00:15:20',
createdAt: '2026-09-21 00:15:10',
},
{
id: 2,
orderNo: 'ORD-20260920-0088',
tenantId: 2,
tenantName: 'Berlin Precision Robotics GmbH',
planType: 'pro',
billingCycle: 'yearly',
amount: 588,
currency: 'EUR',
channel: 'stripe',
status: 'paid',
paidAt: '2026-09-20 18:42:10',
createdAt: '2026-09-20 18:42:00',
},
{
id: 3,
orderNo: 'ORD-20260920-0087',
tenantId: 3,
tenantName: 'Dubai Apex Logistics LLC',
planType: 'enterprise',
billingCycle: 'monthly',
amount: 299,
currency: 'USD',
channel: 'wire_transfer',
status: 'paid',
paidAt: '2026-09-20 14:22:05',
createdAt: '2026-09-20 14:10:00',
},
{
id: 4,
orderNo: 'ORD-20260919-0086',
tenantId: 4,
tenantName: 'Silicon Horizon Tech Inc.',
planType: 'pro',
billingCycle: 'monthly',
amount: 49,
currency: 'USD',
channel: 'stripe',
status: 'pending',
paidAt: '',
createdAt: '2026-09-19 22:11:34',
},
{
id: 5,
orderNo: 'ORD-20260919-0085',
tenantId: 5,
tenantName: 'Nordic Clean Energy AB',
planType: 'pro',
billingCycle: 'yearly',
amount: 588,
currency: 'EUR',
channel: 'manual_admin',
status: 'paid',
paidAt: '2026-09-19 11:05:49',
createdAt: '2026-09-19 11:05:00',
},
];
// SaaS 套餐规格与定价方案配置
private planConfigs: PlanConfigItem[] = [
{
planType: 'free',
name: 'Free 基础体验版',
description: '适合个人自由职业者与初创独立人,轻量体验出海名片微官网',
monthlyPrice: 0,
yearlyPrice: 0,
cardsLimit: 1,
seatsLimit: 1,
aiTokensPerMonth: 5000,
nfcDynamicRouting: false,
supportCustomDomain: false,
crmLeadLimit: 50,
isPopular: false,
},
{
planType: 'pro',
name: 'Pro 专业商务版',
description: '面向出海业务精英、外贸销售主管与跨国顾问的高性能全功能套件',
monthlyPrice: 49,
yearlyPrice: 490,
cardsLimit: 10,
seatsLimit: 5,
aiTokensPerMonth: 200000,
nfcDynamicRouting: true,
supportCustomDomain: true,
crmLeadLimit: 2000,
isPopular: true,
},
{
planType: 'enterprise',
name: 'Enterprise 商业企业版',
description: '为出海跨国集团、参展销售天团定制的多租户公海池与品牌统一管控',
monthlyPrice: 299,
yearlyPrice: 2990,
cardsLimit: 100,
seatsLimit: 50,
aiTokensPerMonth: 1000000,
nfcDynamicRouting: true,
supportCustomDomain: true,
crmLeadLimit: 50000,
isPopular: false,
},
];
/**
* 1. 平台总览与核心 KPI 概览
*/
async getOverview(): Promise<PlatformOverviewResult> {
const totalSubscribers = this.subscribersStore.length;
const activeSubscribers = this.subscribersStore.filter(
s => s.status === 'active' || s.status === 'trialing'
).length;
const proCount = this.subscribersStore.filter(s => s.planType === 'pro').length;
const enterpriseCount = this.subscribersStore.filter(s => s.planType === 'enterprise').length;
// 即将到期判定 (当前日期后 30 天内到期)
const now = moment();
const expiringSoonCount = this.subscribersStore.filter(s => {
if (s.planType === 'free') return false;
const exp = moment(s.expireDate);
const diffDays = exp.diff(now, 'days');
return diffDays >= -5 && diffDays <= 30;
}).length;
const totalRenewalRevenue = this.renewalsStore
.filter(r => r.status === 'paid')
.reduce((sum, r) => sum + Number(r.amount || 0), 0);
return {
totalSubscribers,
activeSubscribers,
proCount,
enterpriseCount,
expiringSoonCount,
totalRenewalRevenue,
currency: 'USD',
};
}
/**
* 2. 分页及多条件查询全网会员列表
*/
async getSubscribers(query: {
page?: number;
size?: number;
planType?: TenantPlanType;
status?: string;
keyword?: string;
} = {}): Promise<{
list: SubscriberItem[];
pagination: { page: number; size: number; total: number };
}> {
const page = Number(query.page || 1);
const size = Number(query.size || 20);
let filtered = [...this.subscribersStore];
if (query.planType) {
filtered = filtered.filter(s => s.planType === query.planType);
}
if (query.status) {
filtered = filtered.filter(s => s.status === query.status);
}
if (query.keyword) {
const kw = query.keyword.toLowerCase().trim();
filtered = filtered.filter(
s =>
String(s.tenantId).includes(kw) ||
s.tenantName.toLowerCase().includes(kw) ||
s.adminEmail.toLowerCase().includes(kw) ||
s.adminName.toLowerCase().includes(kw)
);
}
const total = filtered.length;
const start = (page - 1) * size;
const list = filtered.slice(start, start + size).map(item => ({ ...item }));
return {
list,
pagination: {
page,
size,
total,
},
};
}
/**
* 3. 超级管理员修改会员信息 (套餐、配额、延期、状态)
*/
async updateSubscriber(dto: UpdateSubscriberDTO): Promise<SubscriberItem> {
const item = this.subscribersStore.find(s => s.tenantId === Number(dto.tenantId));
if (!item) {
throw new Error(`未找到租户 ID 为 ${dto.tenantId} 的会员`);
}
if (dto.planType) {
item.planType = dto.planType;
// 联动规格默认配额
const targetPlan = this.planConfigs.find(p => p.planType === dto.planType);
if (targetPlan) {
item.cardsLimit = targetPlan.cardsLimit;
item.seatsLimit = targetPlan.seatsLimit;
}
}
if (typeof dto.cardsLimit === 'number') {
item.cardsLimit = dto.cardsLimit;
}
if (typeof dto.seatsLimit === 'number') {
item.seatsLimit = dto.seatsLimit;
}
if (dto.status) {
item.status = dto.status;
}
if (dto.extendDays && dto.extendDays > 0) {
const baseDate = moment(item.expireDate).isValid()
? moment(item.expireDate)
: moment();
item.expireDate = baseDate.add(dto.extendDays, 'days').format('YYYY-MM-DD');
item.status = 'active';
} else if (dto.expireDate) {
item.expireDate = dto.expireDate;
}
return { ...item };
}
/**
* 4. 查询全网续费订单流水台账
*/
async getRenewals(query: {
page?: number;
size?: number;
status?: string;
keyword?: string;
} = {}): Promise<{
list: RenewalOrderItem[];
pagination: { page: number; size: number; total: number };
}> {
const page = Number(query.page || 1);
const size = Number(query.size || 20);
let filtered = [...this.renewalsStore];
if (query.status) {
filtered = filtered.filter(r => r.status === query.status);
}
if (query.keyword) {
const kw = query.keyword.toLowerCase().trim();
filtered = filtered.filter(
r =>
r.orderNo.toLowerCase().includes(kw) ||
r.tenantName.toLowerCase().includes(kw) ||
String(r.tenantId).includes(kw)
);
}
const total = filtered.length;
const start = (page - 1) * size;
const list = filtered.slice(start, start + size);
return {
list,
pagination: {
page,
size,
total,
},
};
}
/**
* 5. 超管手动为会员代续费 / 补录订单
*/
async manualRenew(dto: ManualRenewDTO): Promise<{
success: boolean;
orderNo: string;
item: RenewalOrderItem;
subscriber: SubscriberItem;
}> {
const subscriber = this.subscribersStore.find(s => s.tenantId === Number(dto.tenantId));
if (!subscriber) {
throw new Error(`租户 ${dto.tenantId} 不存在`);
}
const orderNo = `ORD-${moment().format('YYYYMMDD')}-${Math.floor(
1000 + Math.random() * 9000
)}`;
const nowStr = moment().format('YYYY-MM-DD HH:mm:ss');
const newOrder: RenewalOrderItem = {
id: this.renewalsStore.length + 1,
orderNo,
tenantId: subscriber.tenantId,
tenantName: subscriber.tenantName,
planType: dto.planType,
billingCycle: dto.billingCycle,
amount: dto.amount,
currency: dto.currency || 'USD',
channel: dto.channel || 'manual_admin',
status: 'paid',
paidAt: nowStr,
createdAt: nowStr,
};
this.renewalsStore.unshift(newOrder);
// 更新会员期限和套餐
subscriber.planType = dto.planType;
const addDays = dto.billingCycle === 'yearly' ? 365 : 30;
const baseExpire = moment(subscriber.expireDate).isAfter(moment())
? moment(subscriber.expireDate)
: moment();
subscriber.expireDate = baseExpire.add(addDays, 'days').format('YYYY-MM-DD');
subscriber.status = 'active';
subscriber.totalPaidAmount = Number(subscriber.totalPaidAmount || 0) + Number(dto.amount);
return {
success: true,
orderNo,
item: newOrder,
subscriber,
};
}
/**
* 6. SaaS 套餐定价规格列表
*/
async getPlans(): Promise<PlanConfigItem[]> {
return [...this.planConfigs];
}
/**
* 7. 更新指定套餐定价与配额参数
*/
async updatePlan(
planType: TenantPlanType,
params: Partial<PlanConfigItem>
): Promise<PlanConfigItem> {
const plan = this.planConfigs.find(p => p.planType === planType);
if (!plan) {
throw new Error(`套餐类型 ${planType} 不存在`);
}
Object.assign(plan, params);
return plan;
}
}