优惠码Bug修复

This commit is contained in:
2025-11-07 08:13:12 +08:00
parent 5582c8237c
commit bf89506470
4 changed files with 247 additions and 59 deletions

Binary file not shown.

30
app.py
View File

@@ -1443,16 +1443,24 @@ def get_subscription_plans():
'data': [plan.to_dict() for plan in plans] 'data': [plan.to_dict() for plan in plans]
}) })
except Exception as e: except Exception as e:
# 返回默认套餐 # 返回默认套餐包含pricing_options以兼容新前端
default_plans = [ default_plans = [
{ {
'id': 1, 'id': 1,
'name': 'pro', 'name': 'pro',
'display_name': 'Pro版本', 'display_name': 'Pro版本',
'description': '适合个人投资者的基础功能套餐', 'description': '适合个人投资者的基础功能套餐',
'monthly_price': 0.01, 'monthly_price': 198,
'yearly_price': 0.08, 'yearly_price': 2000,
'features': ['基础股票分析工具', '历史数据查询', '基础财务报表'], 'pricing_options': [
{'months': 1, 'price': 198, 'label': '月付', 'cycle_key': 'monthly'},
{'months': 3, 'price': 534, 'label': '3个月', 'cycle_key': '3months', 'discount_percent': 10},
{'months': 6, 'price': 950, 'label': '半年', 'cycle_key': '6months', 'discount_percent': 20},
{'months': 12, 'price': 2000, 'label': '1年', 'cycle_key': 'yearly', 'discount_percent': 16},
{'months': 24, 'price': 3600, 'label': '2年', 'cycle_key': '2years', 'discount_percent': 24},
{'months': 36, 'price': 5040, 'label': '3年', 'cycle_key': '3years', 'discount_percent': 29}
],
'features': ['基础股票分析工具', '历史数据查询', '基础财务报表', '简单投资计划记录', '标准客服支持'],
'is_active': True, 'is_active': True,
'sort_order': 1 'sort_order': 1
}, },
@@ -1461,9 +1469,17 @@ def get_subscription_plans():
'name': 'max', 'name': 'max',
'display_name': 'Max版本', 'display_name': 'Max版本',
'description': '适合专业投资者的全功能套餐', 'description': '适合专业投资者的全功能套餐',
'monthly_price': 0.1, 'monthly_price': 998,
'yearly_price': 0.8, 'yearly_price': 10000,
'features': ['全部Pro版本功能', '高级分析工具', '实时数据推送'], 'pricing_options': [
{'months': 1, 'price': 998, 'label': '月付', 'cycle_key': 'monthly'},
{'months': 3, 'price': 2695, 'label': '3个月', 'cycle_key': '3months', 'discount_percent': 10},
{'months': 6, 'price': 4790, 'label': '半年', 'cycle_key': '6months', 'discount_percent': 20},
{'months': 12, 'price': 10000, 'label': '1年', 'cycle_key': 'yearly', 'discount_percent': 17},
{'months': 24, 'price': 18000, 'label': '2年', 'cycle_key': '2years', 'discount_percent': 25},
{'months': 36, 'price': 25200, 'label': '3年', 'cycle_key': '3years', 'discount_percent': 30}
],
'features': ['全部Pro版本功能', '高级分析工具', '实时数据推送', 'API访问', '优先客服支持'],
'is_active': True, 'is_active': True,
'sort_order': 2 'sort_order': 2
} }

View File

@@ -0,0 +1,59 @@
-- 数据库迁移:添加 pricing_options 字段,支持灵活的计费周期
-- 执行时间2025-01-XX
-- 说明:支持用户选择"包N个月"或"包N年"的套餐
-- 1. 添加 pricing_options 字段
ALTER TABLE subscription_plans
ADD COLUMN pricing_options TEXT NULL COMMENT 'JSON格式的计费周期选项';
-- 2. 为Pro套餐配置多种计费周期基于现有价格198元/月2000元/年)
UPDATE subscription_plans
SET pricing_options = '[
{"months": 1, "price": 198, "label": "月付", "cycle_key": "monthly"},
{"months": 3, "price": 534, "label": "3个月", "cycle_key": "3months", "discount_percent": 10},
{"months": 6, "price": 950, "label": "半年", "cycle_key": "6months", "discount_percent": 20},
{"months": 12, "price": 2000, "label": "1年", "cycle_key": "yearly", "discount_percent": 16},
{"months": 24, "price": 3600, "label": "2年", "cycle_key": "2years", "discount_percent": 24},
{"months": 36, "price": 5040, "label": "3年", "cycle_key": "3years", "discount_percent": 29}
]'
WHERE name = 'pro';
-- 3. 为Max套餐配置多种计费周期基于现有价格998元/月10000元/年)
UPDATE subscription_plans
SET pricing_options = '[
{"months": 1, "price": 998, "label": "月付", "cycle_key": "monthly"},
{"months": 3, "price": 2695, "label": "3个月", "cycle_key": "3months", "discount_percent": 10},
{"months": 6, "price": 4790, "label": "半年", "cycle_key": "6months", "discount_percent": 20},
{"months": 12, "price": 10000, "label": "1年", "cycle_key": "yearly", "discount_percent": 17},
{"months": 24, "price": 18000, "label": "2年", "cycle_key": "2years", "discount_percent": 25},
{"months": 36, "price": 25200, "label": "3年", "cycle_key": "3years", "discount_percent": 30}
]'
WHERE name = 'max';
-- ========================================
-- 价格计算说明
-- ========================================
-- Pro版198元/月2000元/年):
-- - 月付198元
-- - 3个月198×3×0.9 = 534元打9折省10%
-- - 半年198×6×0.8 = 950元打8折省20%
-- - 1年2000元已有年付价格约省16%
-- - 2年198×24×0.76 = 3600元省24%
-- - 3年198×36×0.7 = 5040元省30%
-- Max版998元/月10000元/年):
-- - 月付998元
-- - 3个月998×3×0.9 = 2695元打9折省10%
-- - 半年998×6×0.8 = 4790元打8折省20%
-- - 1年10000元已有年付价格约省17%
-- - 2年998×24×0.75 = 18000元省25%
-- - 3年998×36×0.7 = 25200元省30%
-- ========================================
-- 注意事项
-- ========================================
-- 1. 上述价格仅为示例,请根据实际营销策略调整
-- 2. 折扣力度建议:时间越长,优惠越大
-- 3. 如果不想提供某个周期,直接从数组中删除即可
-- 4. 前端会自动渲染所有可用的计费周期选项
-- 5. 用户可以通过优惠码获得额外折扣

View File

@@ -757,49 +757,107 @@ export default function SubscriptionContent() {
)} )}
{/* 计费周期选择 */} {/* 计费周期选择 */}
<Flex justify="center"> <Box>
<HStack <Text textAlign="center" fontSize="sm" color={secondaryText} mb={3}>
spacing={0} 选择计费周期 · 时长越长优惠越大
bg={bgAccent} </Text>
borderRadius="lg" <Flex justify="center" mb={2}>
p={1} <HStack
border="1px solid" spacing={2}
borderColor={borderColor} bg={bgAccent}
flexWrap="wrap" borderRadius="xl"
> p={2}
{(() => { border="1px solid"
// 获取第一个套餐的pricing_options作为周期选项假设所有套餐都有相同的周期 borderColor={borderColor}
const firstPlan = subscriptionPlans.find(plan => plan.pricing_options); flexWrap="wrap"
const cycleOptions = firstPlan?.pricing_options || [ justify="center"
{ cycle_key: 'monthly', label: '按月付费', months: 1 }, >
{ cycle_key: 'yearly', label: '按年付费', months: 12, discount_percent: 20 } {(() => {
]; // 获取第一个套餐的pricing_options作为周期选项假设所有套餐都有相同的周期
const firstPlan = subscriptionPlans.find(plan => plan.pricing_options);
const cycleOptions = firstPlan?.pricing_options || [
{ cycle_key: 'monthly', label: '月付', months: 1 },
{ cycle_key: 'yearly', label: '年付', months: 12, discount_percent: 20 }
];
return cycleOptions.map((option, index) => { return cycleOptions.map((option, index) => {
const cycleKey = option.cycle_key || (option.months === 1 ? 'monthly' : option.months === 12 ? 'yearly' : `${option.months}months`); const cycleKey = option.cycle_key || (option.months === 1 ? 'monthly' : option.months === 12 ? 'yearly' : `${option.months}months`);
const isSelected = selectedCycle === cycleKey; const isSelected = selectedCycle === cycleKey;
const hasDiscount = option.discount_percent && option.discount_percent > 0;
return ( return (
<Button <VStack
key={index} key={index}
variant={isSelected ? 'solid' : 'ghost'} spacing={0}
colorScheme={isSelected ? 'blue' : 'gray'} position="relative"
size="md" >
onClick={() => setSelectedCycle(cycleKey)} {/* 折扣标签 */}
borderRadius="md" {hasDiscount && (
> <Badge
{option.label || `${option.months}个月`} position="absolute"
{option.discount_percent && ( top="-8px"
<Badge ml={2} colorScheme="red" fontSize="xs"> colorScheme="red"
{option.discount_percent}% fontSize="xs"
</Badge> px={2}
)} borderRadius="full"
</Button> fontWeight="bold"
); >
}); {option.discount_percent}%
})()} </Badge>
</HStack> )}
</Flex>
<Button
variant={isSelected ? 'solid' : 'outline'}
colorScheme={isSelected ? 'blue' : 'gray'}
size="md"
onClick={() => setSelectedCycle(cycleKey)}
borderRadius="lg"
minW="80px"
h="50px"
position="relative"
_hover={{
transform: 'translateY(-2px)',
shadow: 'md'
}}
transition="all 0.2s"
>
<VStack spacing={0}>
<Text fontSize="md" fontWeight="bold">
{option.label || `${option.months}个月`}
</Text>
{hasDiscount && (
<Text fontSize="xs" color={isSelected ? 'white' : 'gray.500'}>
更优惠
</Text>
)}
</VStack>
</Button>
</VStack>
);
});
})()}
</HStack>
</Flex>
{/* 提示文字 */}
{(() => {
const firstPlan = subscriptionPlans.find(plan => plan.pricing_options);
const cycleOptions = firstPlan?.pricing_options || [];
const currentOption = cycleOptions.find(opt =>
opt.cycle_key === selectedCycle ||
(selectedCycle === 'monthly' && opt.months === 1) ||
(selectedCycle === 'yearly' && opt.months === 12)
);
if (currentOption && currentOption.discount_percent > 0) {
return (
<Text textAlign="center" fontSize="sm" color="green.600" fontWeight="medium">
🎉 当前选择可节省 {currentOption.discount_percent}% 的费用
</Text>
);
}
return null;
})()}
</Box>
{/* 订阅套餐 */} {/* 订阅套餐 */}
<Grid <Grid
@@ -991,15 +1049,50 @@ export default function SubscriptionContent() {
</Text> </Text>
</HStack> </HStack>
</Flex> </Flex>
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center" flexWrap="wrap" gap={2}>
<Text fontSize="xs" color={secondaryText} pl={11}> <Text fontSize="xs" color={secondaryText} pl={11} flex={1}>
{plan.description} {plan.description}
</Text> </Text>
{getSavingsText(plan) && ( {(() => {
<Badge colorScheme="green" fontSize="xs" px={2} py={1}> // 获取当前选中的周期信息
{getSavingsText(plan)} if (plan.pricing_options) {
</Badge> const currentOption = plan.pricing_options.find(opt =>
)} opt.cycle_key === selectedCycle ||
(selectedCycle === 'monthly' && opt.months === 1) ||
(selectedCycle === 'yearly' && opt.months === 12)
);
if (currentOption && currentOption.discount_percent > 0) {
// 计算原价和节省金额
const monthlyOption = plan.pricing_options.find(opt => opt.months === 1);
if (monthlyOption) {
const originalPrice = monthlyOption.price * currentOption.months;
const savedAmount = originalPrice - currentOption.price;
return (
<VStack spacing={0} align="flex-end">
<Badge colorScheme="red" fontSize="xs" px={3} py={1} borderRadius="full">
立省 {currentOption.discount_percent}%
</Badge>
<Text fontSize="xs" color="gray.500" textDecoration="line-through">
原价¥{originalPrice.toFixed(0)}
</Text>
<Text fontSize="xs" color="green.600" fontWeight="bold">
¥{savedAmount.toFixed(0)}
</Text>
</VStack>
);
}
return (
<Badge colorScheme="green" fontSize="xs" px={3} py={1} borderRadius="full">
{getSavingsText(plan)}
</Badge>
);
}
}
return null;
})()}
</Flex> </Flex>
</VStack> </VStack>
@@ -1249,7 +1342,7 @@ export default function SubscriptionContent() {
align="center" align="center"
> >
<Text fontWeight="semibold" color={textColor}> <Text fontWeight="semibold" color={textColor}>
是否提供退款 是否支持退款
</Text> </Text>
<Icon <Icon
as={openFaqIndex === 4 ? FaChevronUp : FaChevronDown} as={openFaqIndex === 4 ? FaChevronUp : FaChevronDown}
@@ -1258,9 +1351,29 @@ export default function SubscriptionContent() {
</Flex> </Flex>
<Collapse in={openFaqIndex === 4}> <Collapse in={openFaqIndex === 4}>
<Box p={4} pt={0} color={secondaryText}> <Box p={4} pt={0} color={secondaryText}>
<Text> <VStack spacing={2} align="stretch">
我们提供7天无理由退款保证如果您在订阅后7天内对服务不满意可以申请全额退款超过7天后我们将根据实际使用情况进行评估 <Text>
</Text> 为了保障服务质量和维护公平的商业环境我们<strong>不支持退款</strong>
</Text>
<Text fontSize="sm">
建议您在订阅前
</Text>
<Text fontSize="sm" pl={3}>
充分了解各套餐的功能差异
</Text>
<Text fontSize="sm" pl={3}>
使用免费版体验基础功能
</Text>
<Text fontSize="sm" pl={3}>
根据实际需求选择合适的计费周期
</Text>
<Text fontSize="sm" pl={3}>
如有疑问可联系客服咨询
</Text>
<Text fontSize="sm" color="blue.600" mt={2}>
提示选择长期套餐如半年付年付可享受更大折扣性价比更高
</Text>
</VStack>
</Box> </Box>
</Collapse> </Collapse>
</Box> </Box>