|
@@ -163,30 +163,24 @@ export const DEFAULT_PLANS = [
|
|
|
priceYearly: 0,
|
|
priceYearly: 0,
|
|
|
description: '适合轻度体验',
|
|
description: '适合轻度体验',
|
|
|
features: JSON.stringify([
|
|
features: JSON.stringify([
|
|
|
- '每天3次生成',
|
|
|
|
|
'每月10分钟音频',
|
|
'每月10分钟音频',
|
|
|
- '每次最多2分钟',
|
|
|
|
|
'基础音色5种',
|
|
'基础音色5种',
|
|
|
'标准音质'
|
|
'标准音质'
|
|
|
]),
|
|
]),
|
|
|
isRecommended: false,
|
|
isRecommended: false,
|
|
|
sortOrder: 0,
|
|
sortOrder: 0,
|
|
|
dailyGenerations: 3,
|
|
dailyGenerations: 3,
|
|
|
- perGenerationLimit: 300, // ~2分钟音频
|
|
|
|
|
|
|
+ perGenerationLimit: 10000,
|
|
|
monthlyTokens: 1500,
|
|
monthlyTokens: 1500,
|
|
|
- monthlyMinutes: 10, // 音频时长配额
|
|
|
|
|
|
|
+ monthlyMinutes: 10,
|
|
|
yearlyTokens: null,
|
|
yearlyTokens: null,
|
|
|
voiceOptions: 5,
|
|
voiceOptions: 5,
|
|
|
audioQuality: 'standard',
|
|
audioQuality: 'standard',
|
|
|
apiAccess: false,
|
|
apiAccess: false,
|
|
|
batchProcessing: false,
|
|
batchProcessing: false,
|
|
|
teamManagement: false,
|
|
teamManagement: false,
|
|
|
- overageEnabled: false, // 不支持超出配额
|
|
|
|
|
- overagePrice: 0,
|
|
|
|
|
- pricing: {
|
|
|
|
|
- monthlyPricePerMinute: 5.0, // ¥5/分钟(包月)
|
|
|
|
|
- overagePricePerMinute: null // 不支持按量
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overageEnabled: false,
|
|
|
|
|
+ overagePrice: 0
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: '入门版',
|
|
name: '入门版',
|
|
@@ -195,9 +189,7 @@ export const DEFAULT_PLANS = [
|
|
|
priceYearly: 190,
|
|
priceYearly: 190,
|
|
|
description: '适合轻度使用',
|
|
description: '适合轻度使用',
|
|
|
features: JSON.stringify([
|
|
features: JSON.stringify([
|
|
|
- '每天10次生成',
|
|
|
|
|
'每月30分钟音频',
|
|
'每月30分钟音频',
|
|
|
- '每次最多5分钟',
|
|
|
|
|
'全部音色',
|
|
'全部音色',
|
|
|
'高清音质',
|
|
'高清音质',
|
|
|
'超出¥8/分钟'
|
|
'超出¥8/分钟'
|
|
@@ -205,7 +197,7 @@ export const DEFAULT_PLANS = [
|
|
|
isRecommended: false,
|
|
isRecommended: false,
|
|
|
sortOrder: 1,
|
|
sortOrder: 1,
|
|
|
dailyGenerations: 10,
|
|
dailyGenerations: 10,
|
|
|
- perGenerationLimit: 750, // ~5分钟音频
|
|
|
|
|
|
|
+ perGenerationLimit: 10000,
|
|
|
monthlyTokens: 4500,
|
|
monthlyTokens: 4500,
|
|
|
monthlyMinutes: 30,
|
|
monthlyMinutes: 30,
|
|
|
yearlyTokens: null,
|
|
yearlyTokens: null,
|
|
@@ -215,11 +207,7 @@ export const DEFAULT_PLANS = [
|
|
|
batchProcessing: false,
|
|
batchProcessing: false,
|
|
|
teamManagement: false,
|
|
teamManagement: false,
|
|
|
overageEnabled: true,
|
|
overageEnabled: true,
|
|
|
- overagePrice: 8.0, // 超出¥8/分钟(零售价)
|
|
|
|
|
- pricing: {
|
|
|
|
|
- monthlyPricePerMinute: 5.0, // ¥5/分钟(批发)
|
|
|
|
|
- overagePricePerMinute: 8.0 // ¥8/分钟(零售)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overagePrice: 8.0
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: '专业版',
|
|
name: '专业版',
|
|
@@ -228,9 +216,7 @@ export const DEFAULT_PLANS = [
|
|
|
priceYearly: 590,
|
|
priceYearly: 590,
|
|
|
description: '适合内容创作者 ⭐推荐',
|
|
description: '适合内容创作者 ⭐推荐',
|
|
|
features: JSON.stringify([
|
|
features: JSON.stringify([
|
|
|
- '无限制生成',
|
|
|
|
|
'每月100分钟音频',
|
|
'每月100分钟音频',
|
|
|
- '每次最多10分钟',
|
|
|
|
|
'全部音色+定制音色',
|
|
'全部音色+定制音色',
|
|
|
'高清音质',
|
|
'高清音质',
|
|
|
'超出¥7/分钟'
|
|
'超出¥7/分钟'
|
|
@@ -238,7 +224,7 @@ export const DEFAULT_PLANS = [
|
|
|
isRecommended: true,
|
|
isRecommended: true,
|
|
|
sortOrder: 2,
|
|
sortOrder: 2,
|
|
|
dailyGenerations: -1,
|
|
dailyGenerations: -1,
|
|
|
- perGenerationLimit: 1500, // ~10分钟音频
|
|
|
|
|
|
|
+ perGenerationLimit: 10000,
|
|
|
monthlyTokens: 15000,
|
|
monthlyTokens: 15000,
|
|
|
monthlyMinutes: 100,
|
|
monthlyMinutes: 100,
|
|
|
yearlyTokens: null,
|
|
yearlyTokens: null,
|
|
@@ -248,11 +234,7 @@ export const DEFAULT_PLANS = [
|
|
|
batchProcessing: false,
|
|
batchProcessing: false,
|
|
|
teamManagement: false,
|
|
teamManagement: false,
|
|
|
overageEnabled: true,
|
|
overageEnabled: true,
|
|
|
- overagePrice: 7.0, // 超出¥7/分钟
|
|
|
|
|
- pricing: {
|
|
|
|
|
- monthlyPricePerMinute: 5.0,
|
|
|
|
|
- overagePricePerMinute: 7.0
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overagePrice: 7.0
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: '旗舰版',
|
|
name: '旗舰版',
|
|
@@ -261,9 +243,7 @@ export const DEFAULT_PLANS = [
|
|
|
priceYearly: 1990,
|
|
priceYearly: 1990,
|
|
|
description: '适合重度使用',
|
|
description: '适合重度使用',
|
|
|
features: JSON.stringify([
|
|
features: JSON.stringify([
|
|
|
- '无限制生成',
|
|
|
|
|
'每月400分钟音频',
|
|
'每月400分钟音频',
|
|
|
- '每次最多20分钟',
|
|
|
|
|
'全部音色+定制音色',
|
|
'全部音色+定制音色',
|
|
|
'高清音质',
|
|
'高清音质',
|
|
|
'VIP优先队列',
|
|
'VIP优先队列',
|
|
@@ -272,7 +252,7 @@ export const DEFAULT_PLANS = [
|
|
|
isRecommended: false,
|
|
isRecommended: false,
|
|
|
sortOrder: 3,
|
|
sortOrder: 3,
|
|
|
dailyGenerations: -1,
|
|
dailyGenerations: -1,
|
|
|
- perGenerationLimit: 3000, // ~20分钟音频
|
|
|
|
|
|
|
+ perGenerationLimit: 10000,
|
|
|
monthlyTokens: 60000,
|
|
monthlyTokens: 60000,
|
|
|
monthlyMinutes: 400,
|
|
monthlyMinutes: 400,
|
|
|
yearlyTokens: null,
|
|
yearlyTokens: null,
|
|
@@ -282,11 +262,7 @@ export const DEFAULT_PLANS = [
|
|
|
batchProcessing: false,
|
|
batchProcessing: false,
|
|
|
teamManagement: false,
|
|
teamManagement: false,
|
|
|
overageEnabled: true,
|
|
overageEnabled: true,
|
|
|
- overagePrice: 6.0, // 超出¥6/分钟
|
|
|
|
|
- pricing: {
|
|
|
|
|
- monthlyPricePerMinute: 5.0,
|
|
|
|
|
- overagePricePerMinute: 6.0
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overagePrice: 6.0
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: '企业版',
|
|
name: '企业版',
|
|
@@ -295,11 +271,8 @@ export const DEFAULT_PLANS = [
|
|
|
priceYearly: 5990,
|
|
priceYearly: 5990,
|
|
|
description: '适合企业用户',
|
|
description: '适合企业用户',
|
|
|
features: JSON.stringify([
|
|
features: JSON.stringify([
|
|
|
- '无限制生成',
|
|
|
|
|
'每月1500分钟音频',
|
|
'每月1500分钟音频',
|
|
|
- '每次最多60分钟',
|
|
|
|
|
'全部功能',
|
|
'全部功能',
|
|
|
- '高清音质',
|
|
|
|
|
'专属技术支持',
|
|
'专属技术支持',
|
|
|
'批量处理',
|
|
'批量处理',
|
|
|
'团队管理',
|
|
'团队管理',
|
|
@@ -308,7 +281,7 @@ export const DEFAULT_PLANS = [
|
|
|
isRecommended: false,
|
|
isRecommended: false,
|
|
|
sortOrder: 4,
|
|
sortOrder: 4,
|
|
|
dailyGenerations: -1,
|
|
dailyGenerations: -1,
|
|
|
- perGenerationLimit: 9000, // ~60分钟音频
|
|
|
|
|
|
|
+ perGenerationLimit: 10000,
|
|
|
monthlyTokens: 225000,
|
|
monthlyTokens: 225000,
|
|
|
monthlyMinutes: 1500,
|
|
monthlyMinutes: 1500,
|
|
|
yearlyTokens: null,
|
|
yearlyTokens: null,
|
|
@@ -318,11 +291,7 @@ export const DEFAULT_PLANS = [
|
|
|
batchProcessing: true,
|
|
batchProcessing: true,
|
|
|
teamManagement: true,
|
|
teamManagement: true,
|
|
|
overageEnabled: true,
|
|
overageEnabled: true,
|
|
|
- overagePrice: 5.0, // 超出¥5/分钟(最低零售价)
|
|
|
|
|
- pricing: {
|
|
|
|
|
- monthlyPricePerMinute: 5.0,
|
|
|
|
|
- overagePricePerMinute: 5.0
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overagePrice: 5.0
|
|
|
}
|
|
}
|
|
|
];
|
|
];
|
|
|
|
|
|
|
@@ -553,14 +522,8 @@ export const AUDIO_BILLING_CONFIG = {
|
|
|
|
|
|
|
|
// 定价策略:包月=批发价,按量=零售价
|
|
// 定价策略:包月=批发价,按量=零售价
|
|
|
pricing: {
|
|
pricing: {
|
|
|
- // 包月(批发价)
|
|
|
|
|
- monthly: {
|
|
|
|
|
- basePricePerMinute: 5.0, // ¥5/分钟(30%利润)
|
|
|
|
|
- },
|
|
|
|
|
- // 按量(零售价)
|
|
|
|
|
- payAsYouGo: {
|
|
|
|
|
- pricePerMinute: 8.0, // ¥8/分钟(100%利润)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ monthly: 5.0, // 包月批发价:¥5/分钟
|
|
|
|
|
+ overage: 8.0 // 超额零售价:¥8/分钟
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 语速配置(字/分钟)
|
|
// 语速配置(字/分钟)
|
|
@@ -575,22 +538,13 @@ export const AUDIO_BILLING_CONFIG = {
|
|
|
4: 1500 // 企业版:1500分钟/月
|
|
4: 1500 // 企业版:1500分钟/月
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- // 各等级超出配额单价(元/分钟)
|
|
|
|
|
|
|
+ // 各等级超出配额单价(元/分钟),null=不支持超出
|
|
|
overagePrice: {
|
|
overagePrice: {
|
|
|
0: null, // 免费版:不支持超出
|
|
0: null, // 免费版:不支持超出
|
|
|
1: 8.0, // 入门版:¥8/分钟
|
|
1: 8.0, // 入门版:¥8/分钟
|
|
|
2: 7.0, // 专业版:¥7/分钟
|
|
2: 7.0, // 专业版:¥7/分钟
|
|
|
3: 6.0, // 旗舰版:¥6/分钟
|
|
3: 6.0, // 旗舰版:¥6/分钟
|
|
|
- 4: 5.0 // 企业版:¥5/分钟
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 各等级单次生成最大时长(分钟)
|
|
|
|
|
- maxMinutesPerGeneration: {
|
|
|
|
|
- 0: 2, // 免费版:2分钟/次
|
|
|
|
|
- 1: 5, // 入门版:5分钟/次
|
|
|
|
|
- 2: 10, // 专业版:10分钟/次
|
|
|
|
|
- 3: 20, // 旗舰版:20分钟/次
|
|
|
|
|
- 4: 60 // 企业版:60分钟/次
|
|
|
|
|
|
|
+ 4: 5.0 // 企业版:¥5/分钟(最低价)
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -624,7 +578,7 @@ export function calculateAudioCost(
|
|
|
const overageMinutes = Math.max(0, audioMinutes - inQuotaMinutes);
|
|
const overageMinutes = Math.max(0, audioMinutes - inQuotaMinutes);
|
|
|
|
|
|
|
|
// 配额内价格(包月批发价)
|
|
// 配额内价格(包月批发价)
|
|
|
- const inQuotaPrice = inQuotaMinutes * AUDIO_BILLING_CONFIG.pricing.monthly.basePricePerMinute;
|
|
|
|
|
|
|
+ const inQuotaPrice = inQuotaMinutes * AUDIO_BILLING_CONFIG.pricing.monthly;
|
|
|
|
|
|
|
|
// 超额价格(按量零售价)
|
|
// 超额价格(按量零售价)
|
|
|
const overagePrice = overageMinutes > 0 && overagePricePerMinute !== null
|
|
const overagePrice = overageMinutes > 0 && overagePricePerMinute !== null
|
|
@@ -695,29 +649,19 @@ export async function getUserAudioBalance(userId: number) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 检查音频生成配额
|
|
// 检查音频生成配额
|
|
|
|
|
+// 检查音频生成配额(只检查月度总配额,无单次限制)
|
|
|
export async function checkAudioQuota(
|
|
export async function checkAudioQuota(
|
|
|
userId: number,
|
|
userId: number,
|
|
|
textLength: number
|
|
textLength: number
|
|
|
) {
|
|
) {
|
|
|
const audioMinutes = calculateAudioDuration(textLength);
|
|
const audioMinutes = calculateAudioDuration(textLength);
|
|
|
const balance = await getUserAudioBalance(userId);
|
|
const balance = await getUserAudioBalance(userId);
|
|
|
- const maxMinutes = AUDIO_BILLING_CONFIG.maxMinutesPerGeneration[balance.memberLevel] || 2;
|
|
|
|
|
-
|
|
|
|
|
- // 检查单次限制
|
|
|
|
|
- if (audioMinutes > maxMinutes) {
|
|
|
|
|
- return {
|
|
|
|
|
- allowed: false,
|
|
|
|
|
- reason: `单次生成不能超过${maxMinutes}分钟`,
|
|
|
|
|
- audioMinutes,
|
|
|
|
|
- maxMinutes
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 检查月度配额
|
|
|
|
|
|
|
+ // 免费版不支持超出配额
|
|
|
if (balance.remainingMinutes < audioMinutes && !balance.overageEnabled) {
|
|
if (balance.remainingMinutes < audioMinutes && !balance.overageEnabled) {
|
|
|
return {
|
|
return {
|
|
|
allowed: false,
|
|
allowed: false,
|
|
|
- reason: `音频时长不足,本月配额已用完`,
|
|
|
|
|
|
|
+ reason: `音频时长不足,本月配额已用完(${balance.totalMinutes}分钟)`,
|
|
|
audioMinutes,
|
|
audioMinutes,
|
|
|
remainingMinutes: balance.remainingMinutes,
|
|
remainingMinutes: balance.remainingMinutes,
|
|
|
totalMinutes: balance.totalMinutes,
|
|
totalMinutes: balance.totalMinutes,
|