Просмотр исходного кода

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

✨ fix: Improve subscription payment handling and card layout consistency
Calcium-Ion 2 недель назад
Родитель
Сommit
c4b6f8eef0

+ 1 - 1
controller/subscription_payment_epay.go

@@ -108,7 +108,7 @@ func SubscriptionRequestEpay(c *gin.Context) {
 		common.ApiErrorMsg(c, "拉起支付失败")
 		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) {

+ 63 - 45
web/src/components/topup/SubscriptionPlansCard.jsx

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

+ 6 - 2
web/src/components/topup/index.jsx

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

+ 14 - 14
web/src/helpers/render.jsx

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