mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:05:21 +00:00
Merge branch 'sub' into feature/subscription
This commit is contained in:
@@ -207,21 +207,23 @@ func AdminUpdateSubscriptionPlan(c *gin.Context) {
|
||||
err := model.DB.Transaction(func(tx *gorm.DB) error {
|
||||
// update plan (allow zero values updates with map)
|
||||
updateMap := map[string]interface{}{
|
||||
"title": req.Plan.Title,
|
||||
"subtitle": req.Plan.Subtitle,
|
||||
"price_amount": req.Plan.PriceAmount,
|
||||
"currency": req.Plan.Currency,
|
||||
"duration_unit": req.Plan.DurationUnit,
|
||||
"duration_value": req.Plan.DurationValue,
|
||||
"custom_seconds": req.Plan.CustomSeconds,
|
||||
"enabled": req.Plan.Enabled,
|
||||
"sort_order": req.Plan.SortOrder,
|
||||
"stripe_price_id": req.Plan.StripePriceId,
|
||||
"creem_product_id": req.Plan.CreemProductId,
|
||||
"max_purchase_per_user": req.Plan.MaxPurchasePerUser,
|
||||
"total_amount": req.Plan.TotalAmount,
|
||||
"upgrade_group": req.Plan.UpgradeGroup,
|
||||
"updated_at": common.GetTimestamp(),
|
||||
"title": req.Plan.Title,
|
||||
"subtitle": req.Plan.Subtitle,
|
||||
"price_amount": req.Plan.PriceAmount,
|
||||
"currency": req.Plan.Currency,
|
||||
"duration_unit": req.Plan.DurationUnit,
|
||||
"duration_value": req.Plan.DurationValue,
|
||||
"custom_seconds": req.Plan.CustomSeconds,
|
||||
"enabled": req.Plan.Enabled,
|
||||
"sort_order": req.Plan.SortOrder,
|
||||
"stripe_price_id": req.Plan.StripePriceId,
|
||||
"creem_product_id": req.Plan.CreemProductId,
|
||||
"max_purchase_per_user": req.Plan.MaxPurchasePerUser,
|
||||
"total_amount": req.Plan.TotalAmount,
|
||||
"upgrade_group": req.Plan.UpgradeGroup,
|
||||
"quota_reset_period": req.Plan.QuotaResetPeriod,
|
||||
"quota_reset_custom_seconds": req.Plan.QuotaResetCustomSeconds,
|
||||
"updated_at": common.GetTimestamp(),
|
||||
}
|
||||
if err := tx.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Updates(updateMap).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -54,7 +54,15 @@ func SubscriptionRequestCreemPay(c *gin.Context) {
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
user, _ := model.GetUserById(userId, false)
|
||||
user, err := model.GetUserById(userId, false)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
common.ApiErrorMsg(c, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if plan.MaxPurchasePerUser > 0 {
|
||||
count, err := model.CountUserSubscriptionsByPlan(userId, plan.Id)
|
||||
|
||||
@@ -61,8 +61,16 @@ func SubscriptionRequestEpay(c *gin.Context) {
|
||||
}
|
||||
|
||||
callBackAddress := service.GetCallbackAddress()
|
||||
returnUrl, _ := url.Parse(callBackAddress + "/api/subscription/epay/return")
|
||||
notifyUrl, _ := url.Parse(callBackAddress + "/api/subscription/epay/notify")
|
||||
returnUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/return")
|
||||
if err != nil {
|
||||
common.ApiErrorMsg(c, "回调地址配置错误")
|
||||
return
|
||||
}
|
||||
notifyUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/notify")
|
||||
if err != nil {
|
||||
common.ApiErrorMsg(c, "回调地址配置错误")
|
||||
return
|
||||
}
|
||||
|
||||
tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
|
||||
tradeNo = fmt.Sprintf("SUBUSR%dNO%s", userId, tradeNo)
|
||||
|
||||
@@ -51,7 +51,15 @@ func SubscriptionRequestStripePay(c *gin.Context) {
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
user, _ := model.GetUserById(userId, false)
|
||||
user, err := model.GetUserById(userId, false)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
common.ApiErrorMsg(c, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if plan.MaxPurchasePerUser > 0 {
|
||||
count, err := model.CountUserSubscriptionsByPlan(userId, plan.Id)
|
||||
@@ -110,7 +118,7 @@ func genStripeSubscriptionLink(referenceId string, customerId string, email stri
|
||||
Quantity: stripe.Int64(1),
|
||||
},
|
||||
},
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
|
||||
}
|
||||
|
||||
if "" == customerId {
|
||||
|
||||
@@ -9,7 +9,7 @@ func GetDBTimestamp() int64 {
|
||||
var err error
|
||||
switch {
|
||||
case common.UsingPostgreSQL:
|
||||
err = DB.Raw("SELECT EXTRACT(EPOCH FROM NOW())").Scan(&ts).Error
|
||||
err = DB.Raw("SELECT EXTRACT(EPOCH FROM NOW())::bigint").Scan(&ts).Error
|
||||
case common.UsingSQLite:
|
||||
err = DB.Raw("SELECT strftime('%s','now')").Scan(&ts).Error
|
||||
default:
|
||||
|
||||
@@ -508,7 +508,7 @@ func PostConsumeQuota(relayInfo *relaycommon.RelayInfo, quota int, preConsumedQu
|
||||
if relayInfo.SubscriptionId == 0 {
|
||||
return errors.New("subscription id is missing")
|
||||
}
|
||||
delta := int64(quota) - relayInfo.SubscriptionPreConsumed
|
||||
delta := int64(quota)
|
||||
if delta != 0 {
|
||||
if err := model.PostConsumeUserSubscriptionDelta(relayInfo.SubscriptionId, delta); err != nil {
|
||||
return err
|
||||
|
||||
@@ -202,9 +202,10 @@ const renderResetPeriod = (text, record, t) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderPaymentConfig = (text, record, t) => {
|
||||
const renderPaymentConfig = (text, record, t, enableEpay) => {
|
||||
const hasStripe = !!record?.plan?.stripe_price_id;
|
||||
const hasCreem = !!record?.plan?.creem_product_id;
|
||||
const hasEpay = !!enableEpay;
|
||||
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
@@ -218,9 +219,11 @@ const renderPaymentConfig = (text, record, t) => {
|
||||
Creem
|
||||
</Tag>
|
||||
)}
|
||||
<Tag color='light-green' shape='circle'>
|
||||
{t('易支付')}
|
||||
</Tag>
|
||||
{hasEpay && (
|
||||
<Tag color='light-green' shape='circle'>
|
||||
{t('易支付')}
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
@@ -274,7 +277,12 @@ const renderOperations = (text, record, { openEdit, setPlanEnabled, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getSubscriptionsColumns = ({ t, openEdit, setPlanEnabled }) => {
|
||||
export const getSubscriptionsColumns = ({
|
||||
t,
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
enableEpay,
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
title: 'ID',
|
||||
@@ -324,7 +332,8 @@ export const getSubscriptionsColumns = ({ t, openEdit, setPlanEnabled }) => {
|
||||
{
|
||||
title: t('支付渠道'),
|
||||
width: 180,
|
||||
render: (text, record) => renderPaymentConfig(text, record, t),
|
||||
render: (text, record) =>
|
||||
renderPaymentConfig(text, record, t, enableEpay),
|
||||
},
|
||||
{
|
||||
title: t('总额度'),
|
||||
|
||||
@@ -27,16 +27,24 @@ import {
|
||||
import { getSubscriptionsColumns } from './SubscriptionsColumnDefs';
|
||||
|
||||
const SubscriptionsTable = (subscriptionsData) => {
|
||||
const { plans, loading, compactMode, openEdit, setPlanEnabled, t } =
|
||||
subscriptionsData;
|
||||
const {
|
||||
plans,
|
||||
loading,
|
||||
compactMode,
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
t,
|
||||
enableEpay,
|
||||
} = subscriptionsData;
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return getSubscriptionsColumns({
|
||||
t,
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
enableEpay,
|
||||
});
|
||||
}, [t, openEdit, setPlanEnabled]);
|
||||
}, [t, openEdit, setPlanEnabled, enableEpay]);
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return compactMode
|
||||
|
||||
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { Banner } from '@douyinfe/semi-ui';
|
||||
import CardPro from '../../common/ui/CardPro';
|
||||
import SubscriptionsTable from './SubscriptionsTable';
|
||||
@@ -27,10 +27,13 @@ import AddEditSubscriptionModal from './modals/AddEditSubscriptionModal';
|
||||
import { useSubscriptionsData } from '../../../hooks/subscriptions/useSubscriptionsData';
|
||||
import { useIsMobile } from '../../../hooks/common/useIsMobile';
|
||||
import { createCardProPagination } from '../../../helpers/utils';
|
||||
import { StatusContext } from '../../../context/Status';
|
||||
|
||||
const SubscriptionsPage = () => {
|
||||
const subscriptionsData = useSubscriptionsData();
|
||||
const isMobile = useIsMobile();
|
||||
const [statusState] = useContext(StatusContext);
|
||||
const enableEpay = !!statusState?.status?.enable_online_topup;
|
||||
|
||||
const {
|
||||
showEdit,
|
||||
@@ -91,7 +94,7 @@ const SubscriptionsPage = () => {
|
||||
})}
|
||||
t={t}
|
||||
>
|
||||
<SubscriptionsTable {...subscriptionsData} />
|
||||
<SubscriptionsTable {...subscriptionsData} enableEpay={enableEpay} />
|
||||
</CardPro>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -423,7 +423,7 @@ const AddEditSubscriptionModal = ({
|
||||
field='custom_seconds'
|
||||
label={t('自定义秒数')}
|
||||
required
|
||||
min={0}
|
||||
min={1}
|
||||
precision={0}
|
||||
rules={[{ required: true, message: t('请输入秒数') }]}
|
||||
style={{ width: '100%' }}
|
||||
|
||||
@@ -35,45 +35,13 @@ import { API, showError, showSuccess, renderQuota } from '../../helpers';
|
||||
import { getCurrencyConfig } from '../../helpers/render';
|
||||
import { Crown, RefreshCw, Sparkles } from 'lucide-react';
|
||||
import SubscriptionPurchaseModal from './modals/SubscriptionPurchaseModal';
|
||||
import {
|
||||
formatSubscriptionDuration,
|
||||
formatSubscriptionResetPeriod,
|
||||
} from '../../helpers/subscriptionFormat';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// 格式化有效期显示
|
||||
function formatDuration(plan, t) {
|
||||
const unit = plan?.duration_unit || 'month';
|
||||
const value = plan?.duration_value || 1;
|
||||
const unitLabels = {
|
||||
year: t('年'),
|
||||
month: t('个月'),
|
||||
day: t('天'),
|
||||
hour: t('小时'),
|
||||
custom: t('自定义'),
|
||||
};
|
||||
if (unit === 'custom') {
|
||||
const seconds = plan?.custom_seconds || 0;
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return `${value} ${unitLabels[unit] || unit}`;
|
||||
}
|
||||
|
||||
function formatResetPeriod(plan, t) {
|
||||
const period = plan?.quota_reset_period || 'never';
|
||||
if (period === 'never') return t('不重置');
|
||||
if (period === 'daily') return t('每天');
|
||||
if (period === 'weekly') return t('每周');
|
||||
if (period === 'monthly') return t('每月');
|
||||
if (period === 'custom') {
|
||||
const seconds = Number(plan?.quota_reset_custom_seconds || 0);
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
if (seconds >= 60) return `${Math.floor(seconds / 60)} ${t('分钟')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return t('不重置');
|
||||
}
|
||||
|
||||
// 过滤易支付方式
|
||||
function getEpayMethods(payMethods = []) {
|
||||
return (payMethods || []).filter(
|
||||
@@ -473,8 +441,9 @@ const SubscriptionPlansCard = ({
|
||||
const totalAmount = Number(plan?.total_amount || 0);
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
const price = Number(plan?.price_amount || 0);
|
||||
const displayPrice = (price * rate).toFixed(
|
||||
price % 1 === 0 ? 0 : 2,
|
||||
const convertedPrice = price * rate;
|
||||
const displayPrice = convertedPrice.toFixed(
|
||||
Number.isInteger(convertedPrice) ? 0 : 2,
|
||||
);
|
||||
const isPopular = index === 0 && plans.length > 1;
|
||||
const limit = Number(plan?.max_purchase_per_user || 0);
|
||||
@@ -487,11 +456,13 @@ const SubscriptionPlansCard = ({
|
||||
? `${t('升级分组')}: ${plan.upgrade_group}`
|
||||
: null;
|
||||
const resetLabel =
|
||||
formatResetPeriod(plan, t) === t('不重置')
|
||||
formatSubscriptionResetPeriod(plan, t) === t('不重置')
|
||||
? null
|
||||
: `${t('额度重置')}: ${formatResetPeriod(plan, t)}`;
|
||||
: `${t('额度重置')}: ${formatSubscriptionResetPeriod(plan, t)}`;
|
||||
const planBenefits = [
|
||||
{ label: `${t('有效期')}: ${formatDuration(plan, t)}` },
|
||||
{
|
||||
label: `${t('有效期')}: ${formatSubscriptionDuration(plan, t)}`,
|
||||
},
|
||||
resetLabel ? { label: resetLabel } : null,
|
||||
totalAmount > 0
|
||||
? {
|
||||
|
||||
@@ -356,6 +356,7 @@ const TopUp = () => {
|
||||
};
|
||||
|
||||
const updateBillingPreference = async (pref) => {
|
||||
const previousPref = billingPreference;
|
||||
setBillingPreference(pref);
|
||||
try {
|
||||
const res = await API.put('/api/subscription/self/preference', {
|
||||
@@ -363,11 +364,16 @@ const TopUp = () => {
|
||||
});
|
||||
if (res.data?.success) {
|
||||
showSuccess(t('更新成功'));
|
||||
const normalizedPref =
|
||||
res.data?.data?.billing_preference || pref || previousPref;
|
||||
setBillingPreference(normalizedPref);
|
||||
} else {
|
||||
showError(res.data?.message || t('更新失败'));
|
||||
setBillingPreference(previousPref);
|
||||
}
|
||||
} catch (e) {
|
||||
showError(t('请求失败'));
|
||||
setBillingPreference(previousPref);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,45 +33,13 @@ import { SiStripe } from 'react-icons/si';
|
||||
import { IconCreditCard } from '@douyinfe/semi-icons';
|
||||
import { renderQuota } from '../../../helpers';
|
||||
import { getCurrencyConfig } from '../../../helpers/render';
|
||||
import {
|
||||
formatSubscriptionDuration,
|
||||
formatSubscriptionResetPeriod,
|
||||
} from '../../../helpers/subscriptionFormat';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// 格式化有效期显示
|
||||
function formatDuration(plan, t) {
|
||||
const unit = plan?.duration_unit || 'month';
|
||||
const value = plan?.duration_value || 1;
|
||||
const unitLabels = {
|
||||
year: t('年'),
|
||||
month: t('个月'),
|
||||
day: t('天'),
|
||||
hour: t('小时'),
|
||||
custom: t('自定义'),
|
||||
};
|
||||
if (unit === 'custom') {
|
||||
const seconds = plan?.custom_seconds || 0;
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return `${value} ${unitLabels[unit] || unit}`;
|
||||
}
|
||||
|
||||
function formatResetPeriod(plan, t) {
|
||||
const period = plan?.quota_reset_period || 'never';
|
||||
if (period === 'never') return t('不重置');
|
||||
if (period === 'daily') return t('每天');
|
||||
if (period === 'weekly') return t('每周');
|
||||
if (period === 'monthly') return t('每月');
|
||||
if (period === 'custom') {
|
||||
const seconds = Number(plan?.quota_reset_custom_seconds || 0);
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
if (seconds >= 60) return `${Math.floor(seconds / 60)} ${t('分钟')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return t('不重置');
|
||||
}
|
||||
|
||||
const SubscriptionPurchaseModal = ({
|
||||
t,
|
||||
visible,
|
||||
@@ -93,7 +61,10 @@ const SubscriptionPurchaseModal = ({
|
||||
const totalAmount = Number(plan?.total_amount || 0);
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
const price = plan ? Number(plan.price_amount || 0) : 0;
|
||||
const displayPrice = (price * rate).toFixed(price % 1 === 0 ? 0 : 2);
|
||||
const convertedPrice = price * rate;
|
||||
const displayPrice = convertedPrice.toFixed(
|
||||
Number.isInteger(convertedPrice) ? 0 : 2,
|
||||
);
|
||||
// 只有当管理员开启支付网关 AND 套餐配置了对应的支付ID时才显示
|
||||
const hasStripe = enableStripeTopUp && !!plan?.stripe_price_id;
|
||||
const hasCreem = enableCreemTopUp && !!plan?.creem_product_id;
|
||||
@@ -142,17 +113,17 @@ const SubscriptionPurchaseModal = ({
|
||||
<div className='flex items-center'>
|
||||
<CalendarClock size={14} className='mr-1 text-slate-500' />
|
||||
<Text className='text-slate-900 dark:text-slate-100'>
|
||||
{formatDuration(plan, t)}
|
||||
{formatSubscriptionDuration(plan, t)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
{formatResetPeriod(plan, t) !== t('不重置') && (
|
||||
{formatSubscriptionResetPeriod(plan, t) !== t('不重置') && (
|
||||
<div className='flex justify-between items-center'>
|
||||
<Text strong className='text-slate-700 dark:text-slate-200'>
|
||||
{t('重置周期')}:
|
||||
</Text>
|
||||
<Text className='text-slate-900 dark:text-slate-100'>
|
||||
{formatResetPeriod(plan, t)}
|
||||
{formatSubscriptionResetPeriod(plan, t)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
34
web/src/helpers/subscriptionFormat.js
Normal file
34
web/src/helpers/subscriptionFormat.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export function formatSubscriptionDuration(plan, t) {
|
||||
const unit = plan?.duration_unit || 'month';
|
||||
const value = plan?.duration_value || 1;
|
||||
const unitLabels = {
|
||||
year: t('年'),
|
||||
month: t('个月'),
|
||||
day: t('天'),
|
||||
hour: t('小时'),
|
||||
custom: t('自定义'),
|
||||
};
|
||||
if (unit === 'custom') {
|
||||
const seconds = plan?.custom_seconds || 0;
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return `${value} ${unitLabels[unit] || unit}`;
|
||||
}
|
||||
|
||||
export function formatSubscriptionResetPeriod(plan, t) {
|
||||
const period = plan?.quota_reset_period || 'never';
|
||||
if (period === 'never') return t('不重置');
|
||||
if (period === 'daily') return t('每天');
|
||||
if (period === 'weekly') return t('每周');
|
||||
if (period === 'monthly') return t('每月');
|
||||
if (period === 'custom') {
|
||||
const seconds = Number(plan?.quota_reset_custom_seconds || 0);
|
||||
if (seconds >= 86400) return `${Math.floor(seconds / 86400)} ${t('天')}`;
|
||||
if (seconds >= 3600) return `${Math.floor(seconds / 3600)} ${t('小时')}`;
|
||||
if (seconds >= 60) return `${Math.floor(seconds / 60)} ${t('分钟')}`;
|
||||
return `${seconds} ${t('秒')}`;
|
||||
}
|
||||
return t('不重置');
|
||||
}
|
||||
@@ -2660,7 +2660,7 @@
|
||||
"推荐": "おすすめ",
|
||||
"已达到购买上限": "購入上限に達しました",
|
||||
"已达上限": "上限に達しました",
|
||||
"立即订阅": "今すぐ購読",
|
||||
"立即订阅": "今すぐサブスクリプション",
|
||||
"暂无可购买套餐": "購入可能なプランがありません",
|
||||
"该套餐未配置 Stripe": "このプランには Stripe が設定されていません",
|
||||
"已打开支付页面": "決済ページを開きました",
|
||||
|
||||
@@ -2616,7 +2616,7 @@
|
||||
"格式化 JSON": "Форматировать JSON",
|
||||
"关闭提示": "Закрыть уведомление",
|
||||
"说明:本页测试为非流式请求;若渠道仅支持流式返回,可能出现测试失败,请以实际使用为准。": "Примечание: тесты на этой странице используют нестриминговые запросы. Если канал поддерживает только стриминговые ответы, тест может завершиться неудачей. Ориентируйтесь на реальное использование.",
|
||||
"提示:端点映射仅用于模型广场展示,不会影响模型真实调用。如需配置真实调用,请前往「渠道管理」。": "Примечание: сопоставление endpoint'ов используется только для отображения в «Маркетплейсе моделей» и не влияет на реальный вызов. Чтобы настроить реальное поведение вызовов, перейдите в «Управление каналами».",
|
||||
"提示:端点映射仅用于模型广场展示,不会影响模型真实调用。如需配置真实调用,请前往「渠道管理」。": "Примечание: сопоставление эндпоинтов используется только для отображения в «Маркетплейсе моделей» и не влияет на реальный вызов. Чтобы настроить реальное поведение вызовов, перейдите в «Управление каналами».",
|
||||
"Stripe/Creem 需在第三方平台创建商品并填入 ID": "Товары Stripe/Creem нужно создать на сторонней платформе и указать их ID",
|
||||
"暂无订阅套餐": "Нет тарифных планов",
|
||||
"订阅管理": "Управление подписками",
|
||||
|
||||
@@ -3228,9 +3228,9 @@
|
||||
"仅用订阅": "Chỉ dùng đăng ký",
|
||||
"仅用钱包": "Chỉ dùng ví",
|
||||
"我的订阅": "Đăng ký của tôi",
|
||||
"个生效中": "đang hiệu lực",
|
||||
"无生效": "Không có hiệu lực",
|
||||
"个已过期": "đã hết hạn",
|
||||
"个生效中": "gói đăng ký đang hiệu lực",
|
||||
"无生效": "Không có gói đăng ký hiệu lực",
|
||||
"个已过期": "gói đăng ký đã hết hạn",
|
||||
"订阅": "Đăng ký",
|
||||
"过期于": "Hết hạn vào",
|
||||
"购买套餐后即可享受模型权益": "Mua gói để nhận quyền lợi mô hình",
|
||||
|
||||
@@ -18,12 +18,12 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import SubscriptionsTable from '../../components/table/subscriptions';
|
||||
import SubscriptionsPage from '../../components/table/subscriptions';
|
||||
|
||||
const Subscription = () => {
|
||||
return (
|
||||
<div className='mt-[60px] px-2'>
|
||||
<SubscriptionsTable />
|
||||
<SubscriptionsPage />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user