test-napi / web /src /components /table /subscriptions /SubscriptionsColumnDefs.jsx
Kyou0203's picture
init for HF Space
daa8246
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
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 {
Button,
Modal,
Space,
Tag,
Typography,
Popover,
Divider,
Badge,
Tooltip,
} from '@douyinfe/semi-ui';
import { renderQuota } from '../../../helpers';
import { convertUSDToCurrency } from '../../../helpers/render';
const { Text } = Typography;
function formatDuration(plan, t) {
if (!plan) return '';
const u = plan.duration_unit || 'month';
if (u === 'custom') {
return `${t('自定义')} ${plan.custom_seconds || 0}s`;
}
const unitMap = {
year: t('年'),
month: t('月'),
day: t('日'),
hour: t('小时'),
};
return `${plan.duration_value || 0}${unitMap[u] || u}`;
}
function formatResetPeriod(plan, t) {
const period = plan?.quota_reset_period || 'never';
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 renderPlanTitle = (text, record, t) => {
const subtitle = record?.plan?.subtitle;
const plan = record?.plan;
const popoverContent = (
<div style={{ width: 260 }}>
<Text strong>{text}</Text>
{subtitle && (
<Text type='tertiary' style={{ display: 'block', marginTop: 4 }}>
{subtitle}
</Text>
)}
<Divider margin={12} />
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
<Text type='tertiary'>{t('价格')}</Text>
<Text strong style={{ color: 'var(--semi-color-success)' }}>
{convertUSDToCurrency(Number(plan?.price_amount || 0), 2)}
</Text>
<Text type='tertiary'>{t('总额度')}</Text>
{plan?.total_amount > 0 ? (
<Tooltip content={`${t('原生额度')}:${plan.total_amount}`}>
<Text>{renderQuota(plan.total_amount)}</Text>
</Tooltip>
) : (
<Text>{t('不限')}</Text>
)}
<Text type='tertiary'>{t('升级分组')}</Text>
<Text>{plan?.upgrade_group ? plan.upgrade_group : t('不升级')}</Text>
<Text type='tertiary'>{t('购买上限')}</Text>
<Text>
{plan?.max_purchase_per_user > 0
? plan.max_purchase_per_user
: t('不限')}
</Text>
<Text type='tertiary'>{t('有效期')}</Text>
<Text>{formatDuration(plan, t)}</Text>
<Text type='tertiary'>{t('重置')}</Text>
<Text>{formatResetPeriod(plan, t)}</Text>
</div>
</div>
);
return (
<Popover content={popoverContent} position='rightTop' showArrow>
<div style={{ cursor: 'pointer', maxWidth: 180 }}>
<Text strong ellipsis={{ showTooltip: false }}>
{text}
</Text>
{subtitle && (
<Text
type='tertiary'
ellipsis={{ showTooltip: false }}
style={{ display: 'block' }}
>
{subtitle}
</Text>
)}
</div>
</Popover>
);
};
const renderPrice = (text) => {
return (
<Text strong style={{ color: 'var(--semi-color-success)' }}>
{convertUSDToCurrency(Number(text || 0), 2)}
</Text>
);
};
const renderPurchaseLimit = (text, record, t) => {
const limit = Number(record?.plan?.max_purchase_per_user || 0);
return (
<Text type={limit > 0 ? 'secondary' : 'tertiary'}>
{limit > 0 ? limit : t('不限')}
</Text>
);
};
const renderDuration = (text, record, t) => {
return <Text type='secondary'>{formatDuration(record?.plan, t)}</Text>;
};
const renderEnabled = (text, record, t) => {
return text ? (
<Tag
color='white'
shape='circle'
type='light'
prefixIcon={<Badge dot type='success' />}
>
{t('启用')}
</Tag>
) : (
<Tag
color='white'
shape='circle'
type='light'
prefixIcon={<Badge dot type='danger' />}
>
{t('禁用')}
</Tag>
);
};
const renderTotalAmount = (text, record, t) => {
const total = Number(record?.plan?.total_amount || 0);
return (
<Text type={total > 0 ? 'secondary' : 'tertiary'}>
{total > 0 ? (
<Tooltip content={`${t('原生额度')}:${total}`}>
<span>{renderQuota(total)}</span>
</Tooltip>
) : (
t('不限')
)}
</Text>
);
};
const renderUpgradeGroup = (text, record, t) => {
const group = record?.plan?.upgrade_group || '';
return (
<Text type={group ? 'secondary' : 'tertiary'}>
{group ? group : t('不升级')}
</Text>
);
};
const renderResetPeriod = (text, record, t) => {
const period = record?.plan?.quota_reset_period || 'never';
const isNever = period === 'never';
return (
<Text type={isNever ? 'tertiary' : 'secondary'}>
{formatResetPeriod(record?.plan, t)}
</Text>
);
};
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}>
{hasStripe && (
<Tag color='violet' shape='circle'>
Stripe
</Tag>
)}
{hasCreem && (
<Tag color='cyan' shape='circle'>
Creem
</Tag>
)}
{hasEpay && (
<Tag color='light-green' shape='circle'>
{t('易支付')}
</Tag>
)}
</Space>
);
};
const renderOperations = (text, record, { openEdit, setPlanEnabled, t }) => {
const isEnabled = record?.plan?.enabled;
const handleToggle = () => {
if (isEnabled) {
Modal.confirm({
title: t('确认禁用'),
content: t('禁用后用户端不再展示,但历史订单不受影响。是否继续?'),
centered: true,
onOk: () => setPlanEnabled(record, false),
});
} else {
Modal.confirm({
title: t('确认启用'),
content: t('启用后套餐将在用户端展示。是否继续?'),
centered: true,
onOk: () => setPlanEnabled(record, true),
});
}
};
return (
<Space spacing={8}>
<Button
theme='light'
type='tertiary'
size='small'
onClick={() => openEdit(record)}
>
{t('编辑')}
</Button>
{isEnabled ? (
<Button theme='light' type='danger' size='small' onClick={handleToggle}>
{t('禁用')}
</Button>
) : (
<Button
theme='light'
type='primary'
size='small'
onClick={handleToggle}
>
{t('启用')}
</Button>
)}
</Space>
);
};
export const getSubscriptionsColumns = ({
t,
openEdit,
setPlanEnabled,
enableEpay,
}) => {
return [
{
title: 'ID',
dataIndex: ['plan', 'id'],
width: 60,
render: (text) => <Text type='tertiary'>#{text}</Text>,
},
{
title: t('套餐'),
dataIndex: ['plan', 'title'],
width: 200,
render: (text, record) => renderPlanTitle(text, record, t),
},
{
title: t('价格'),
dataIndex: ['plan', 'price_amount'],
width: 100,
render: (text) => renderPrice(text),
},
{
title: t('购买上限'),
width: 90,
render: (text, record) => renderPurchaseLimit(text, record, t),
},
{
title: t('优先级'),
dataIndex: ['plan', 'sort_order'],
width: 80,
render: (text) => <Text type='tertiary'>{Number(text || 0)}</Text>,
},
{
title: t('有效期'),
width: 100,
render: (text, record) => renderDuration(text, record, t),
},
{
title: t('重置'),
width: 80,
render: (text, record) => renderResetPeriod(text, record, t),
},
{
title: t('状态'),
dataIndex: ['plan', 'enabled'],
width: 80,
render: (text, record) => renderEnabled(text, record, t),
},
{
title: t('支付渠道'),
width: 180,
render: (text, record) =>
renderPaymentConfig(text, record, t, enableEpay),
},
{
title: t('总额度'),
width: 100,
render: (text, record) => renderTotalAmount(text, record, t),
},
{
title: t('升级分组'),
width: 100,
render: (text, record) => renderUpgradeGroup(text, record, t),
},
{
title: t('操作'),
dataIndex: 'operate',
fixed: 'right',
width: 160,
render: (text, record) =>
renderOperations(text, record, { openEdit, setPlanEnabled, t }),
},
];
};