Merge pull request #2838 from QuantumNous/fix/subscription-epay

 fix: Improve subscription payment handling and card layout consistency
This commit is contained in:
Calcium-Ion
2026-02-05 01:46:18 +08:00
committed by GitHub
4 changed files with 83 additions and 61 deletions

View File

@@ -108,7 +108,7 @@ func SubscriptionRequestEpay(c *gin.Context) {
common.ApiErrorMsg(c, "拉起支付失败") common.ApiErrorMsg(c, "拉起支付失败")
return return
} }
common.ApiSuccess(c, gin.H{"data": params, "url": uri}) c.JSON(http.StatusOK, gin.H{"message": "success", "data": params, "url": uri})
} }
func SubscriptionEpayNotify(c *gin.Context) { func SubscriptionEpayNotify(c *gin.Context) {

View File

@@ -128,7 +128,11 @@ const SubscriptionPlansCard = ({
showSuccess(t('已打开支付页面')); showSuccess(t('已打开支付页面'));
closeBuy(); closeBuy();
} else { } else {
showError(res.data?.data || res.data?.message || t('支付失败')); const errorMsg =
typeof res.data?.data === 'string'
? res.data.data
: res.data?.message || t('支付失败');
showError(errorMsg);
} }
} catch (e) { } catch (e) {
showError(t('支付请求失败')); showError(t('支付请求失败'));
@@ -152,7 +156,11 @@ const SubscriptionPlansCard = ({
showSuccess(t('已打开支付页面')); showSuccess(t('已打开支付页面'));
closeBuy(); closeBuy();
} else { } else {
showError(res.data?.data || res.data?.message || t('支付失败')); const errorMsg =
typeof res.data?.data === 'string'
? res.data.data
: res.data?.message || t('支付失败');
showError(errorMsg);
} }
} catch (e) { } catch (e) {
showError(t('支付请求失败')); showError(t('支付请求失败'));
@@ -177,7 +185,11 @@ const SubscriptionPlansCard = ({
showSuccess(t('已发起支付')); showSuccess(t('已发起支付'));
closeBuy(); closeBuy();
} else { } else {
showError(res.data?.data || res.data?.message || t('支付失败')); const errorMsg =
typeof res.data?.data === 'string'
? res.data.data
: res.data?.message || t('支付失败');
showError(errorMsg);
} }
} catch (e) { } catch (e) {
showError(t('支付请求失败')); showError(t('支付请求失败'));
@@ -269,9 +281,13 @@ const SubscriptionPlansCard = ({
</div> </div>
</Card> </Card>
{/* 套餐列表骨架屏 */} {/* 套餐列表骨架屏 */}
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'> <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<Card key={i} className='!rounded-xl' bodyStyle={{ padding: 16 }}> <Card
key={i}
className='!rounded-xl w-full h-full'
bodyStyle={{ padding: 16 }}
>
<Skeleton.Title <Skeleton.Title
active active
style={{ width: '60%', height: 24, marginBottom: 8 }} style={{ width: '60%', height: 24, marginBottom: 8 }}
@@ -435,7 +451,7 @@ const SubscriptionPlansCard = ({
{/* 可购买套餐 - 标准定价卡片 */} {/* 可购买套餐 - 标准定价卡片 */}
{plans.length > 0 ? ( {plans.length > 0 ? (
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'> <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
{plans.map((p, index) => { {plans.map((p, index) => {
const plan = p?.plan; const plan = p?.plan;
const totalAmount = Number(plan?.total_amount || 0); const totalAmount = Number(plan?.total_amount || 0);
@@ -477,15 +493,15 @@ const SubscriptionPlansCard = ({
return ( return (
<Card <Card
key={plan?.id} key={plan?.id}
className={`!rounded-xl transition-all hover:shadow-lg ${ className={`!rounded-xl transition-all hover:shadow-lg w-full h-full ${
isPopular ? 'ring-2 ring-purple-500' : '' isPopular ? 'ring-2 ring-purple-500' : ''
}`} }`}
bodyStyle={{ padding: 0 }} bodyStyle={{ padding: 0 }}
> >
<div className='p-4'> <div className='p-4 h-full flex flex-col'>
{/* 推荐标签 */} {/* 推荐标签 */}
{isPopular && ( {isPopular && (
<div className='text-center mb-2'> <div className='mb-2'>
<Tag color='purple' shape='circle' size='small'> <Tag color='purple' shape='circle' size='small'>
<Sparkles size={10} className='mr-1' /> <Sparkles size={10} className='mr-1' />
{t('推荐')} {t('推荐')}
@@ -493,7 +509,7 @@ const SubscriptionPlansCard = ({
</div> </div>
)} )}
{/* 套餐名称 */} {/* 套餐名称 */}
<div className='text-center mb-3'> <div className='mb-3'>
<Typography.Title <Typography.Title
heading={5} heading={5}
ellipsis={{ rows: 1, showTooltip: true }} ellipsis={{ rows: 1, showTooltip: true }}
@@ -514,8 +530,8 @@ const SubscriptionPlansCard = ({
</div> </div>
{/* 价格区域 */} {/* 价格区域 */}
<div className='text-center py-2'> <div className='py-2'>
<div className='flex items-baseline justify-center'> <div className='flex items-baseline justify-start'>
<span className='text-xl font-bold text-purple-600'> <span className='text-xl font-bold text-purple-600'>
{symbol} {symbol}
</span> </span>
@@ -526,7 +542,7 @@ const SubscriptionPlansCard = ({
</div> </div>
{/* 套餐权益描述 */} {/* 套餐权益描述 */}
<div className='flex flex-col items-center gap-1 pb-2'> <div className='flex flex-col items-start gap-1 pb-2'>
{planBenefits.map((item) => { {planBenefits.map((item) => {
const content = ( const content = (
<div className='flex items-center gap-2 text-xs text-gray-500'> <div className='flex items-center gap-2 text-xs text-gray-500'>
@@ -538,7 +554,7 @@ const SubscriptionPlansCard = ({
return ( return (
<div <div
key={item.label} key={item.label}
className='w-full flex justify-center' className='w-full flex justify-start'
> >
{content} {content}
</div> </div>
@@ -546,7 +562,7 @@ const SubscriptionPlansCard = ({
} }
return ( return (
<Tooltip key={item.label} content={item.tooltip}> <Tooltip key={item.label} content={item.tooltip}>
<div className='w-full flex justify-center'> <div className='w-full flex justify-start'>
{content} {content}
</div> </div>
</Tooltip> </Tooltip>
@@ -554,36 +570,38 @@ const SubscriptionPlansCard = ({
})} })}
</div> </div>
<Divider margin={12} /> <div className='mt-auto'>
<Divider margin={12} />
{/* 购买按钮 */} {/* 购买按钮 */}
{(() => { {(() => {
const count = getPlanPurchaseCount(p?.plan?.id); const count = getPlanPurchaseCount(p?.plan?.id);
const reached = limit > 0 && count >= limit; const reached = limit > 0 && count >= limit;
const tip = reached const tip = reached
? t('已达到购买上限') + ` (${count}/${limit})` ? t('已达到购买上限') + ` (${count}/${limit})`
: ''; : '';
const buttonEl = ( const buttonEl = (
<Button <Button
theme='outline' theme='outline'
type='tertiary' type='tertiary'
block block
disabled={reached} disabled={reached}
onClick={() => { onClick={() => {
if (!reached) openBuy(p); if (!reached) openBuy(p);
}} }}
> >
{reached ? t('已达上限') : t('立即订阅')} {reached ? t('已达上限') : t('立即订阅')}
</Button> </Button>
); );
return reached ? ( return reached ? (
<Tooltip content={tip} position='top'> <Tooltip content={tip} position='top'>
{buttonEl} {buttonEl}
</Tooltip> </Tooltip>
) : ( ) : (
buttonEl buttonEl
); );
})()} })()}
</div>
</div> </div>
</Card> </Card>
); );

View File

@@ -249,7 +249,9 @@ const TopUp = () => {
document.body.removeChild(form); document.body.removeChild(form);
} }
} else { } else {
showError(data); const errorMsg =
typeof data === 'string' ? data : message || t('支付失败');
showError(errorMsg);
} }
} else { } else {
showError(res); showError(res);
@@ -293,7 +295,9 @@ const TopUp = () => {
if (message === 'success') { if (message === 'success') {
processCreemCallback(data); processCreemCallback(data);
} else { } else {
showError(data); const errorMsg =
typeof data === 'string' ? data : message || t('支付失败');
showError(errorMsg);
} }
} else { } else {
showError(res); showError(res);

View File

@@ -170,21 +170,21 @@ export const getModelCategories = (() => {
gemini: { gemini: {
label: 'Gemini', label: 'Gemini',
icon: <Gemini.Color />, icon: <Gemini.Color />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('gemini') || model.model_name.toLowerCase().includes('gemini') ||
model.model_name.toLowerCase().includes('gemma') || model.model_name.toLowerCase().includes('gemma') ||
model.model_name.toLowerCase().includes('learnlm') || model.model_name.toLowerCase().includes('learnlm') ||
model.model_name.toLowerCase().startsWith('embedding-') || model.model_name.toLowerCase().startsWith('embedding-') ||
model.model_name.toLowerCase().includes('text-embedding-004') || model.model_name.toLowerCase().includes('text-embedding-004') ||
model.model_name.toLowerCase().includes('imagen-4') || model.model_name.toLowerCase().includes('imagen-4') ||
model.model_name.toLowerCase().includes('veo-') || model.model_name.toLowerCase().includes('veo-') ||
model.model_name.toLowerCase().includes('aqa') , model.model_name.toLowerCase().includes('aqa'),
}, },
moonshot: { moonshot: {
label: 'Moonshot', label: 'Moonshot',
icon: <Moonshot />, icon: <Moonshot />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('moonshot') || model.model_name.toLowerCase().includes('moonshot') ||
model.model_name.toLowerCase().includes('kimi'), model.model_name.toLowerCase().includes('kimi'),
}, },
zhipu: { zhipu: {
@@ -192,8 +192,8 @@ export const getModelCategories = (() => {
icon: <Zhipu.Color />, icon: <Zhipu.Color />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('chatglm') || model.model_name.toLowerCase().includes('chatglm') ||
model.model_name.toLowerCase().includes('glm-') || model.model_name.toLowerCase().includes('glm-') ||
model.model_name.toLowerCase().includes('cogview') || model.model_name.toLowerCase().includes('cogview') ||
model.model_name.toLowerCase().includes('cogvideo'), model.model_name.toLowerCase().includes('cogvideo'),
}, },
qwen: { qwen: {
@@ -209,8 +209,8 @@ export const getModelCategories = (() => {
minimax: { minimax: {
label: 'MiniMax', label: 'MiniMax',
icon: <Minimax.Color />, icon: <Minimax.Color />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('abab') || model.model_name.toLowerCase().includes('abab') ||
model.model_name.toLowerCase().includes('minimax'), model.model_name.toLowerCase().includes('minimax'),
}, },
baidu: { baidu: {
@@ -236,7 +236,7 @@ export const getModelCategories = (() => {
cohere: { cohere: {
label: 'Cohere', label: 'Cohere',
icon: <Cohere.Color />, icon: <Cohere.Color />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('command') || model.model_name.toLowerCase().includes('command') ||
model.model_name.toLowerCase().includes('c4ai-') || model.model_name.toLowerCase().includes('c4ai-') ||
model.model_name.toLowerCase().includes('embed-'), model.model_name.toLowerCase().includes('embed-'),
@@ -259,7 +259,7 @@ export const getModelCategories = (() => {
mistral: { mistral: {
label: 'Mistral AI', label: 'Mistral AI',
icon: <Mistral.Color />, icon: <Mistral.Color />,
filter: (model) => filter: (model) =>
model.model_name.toLowerCase().includes('mistral') || model.model_name.toLowerCase().includes('mistral') ||
model.model_name.toLowerCase().includes('codestral') || model.model_name.toLowerCase().includes('codestral') ||
model.model_name.toLowerCase().includes('pixtral') || model.model_name.toLowerCase().includes('pixtral') ||