优惠码Bug修复
This commit is contained in:
Binary file not shown.
30
app.py
30
app.py
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
59
database_migration_add_pricing_options.sql
Normal file
59
database_migration_add_pricing_options.sql
Normal 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. 用户可以通过优惠码获得额外折扣
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user