07ee86dc65
- Fix 48x @typescript-eslint/no-explicit-any: err: any → err: unknown with type narrowing - Fix 3x @typescript-eslint/no-unused-vars: remove unused imports/variables - Fix 2x react-refresh/only-export-components: add eslint-disable comments - Fix 1x react-hooks/exhaustive-deps: wrap in useCallback - 28 files modified across app/ and src/pages/ - ESLint now passes with 0 errors + 0 warnings (--max-warnings 0)
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
/**
|
|
* 账单管理页面
|
|
* 展示当前订阅信息 + 自动续费开关
|
|
*/
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Switch, message, Spin } from 'antd';
|
|
import { getCurrentSubscription, toggleAutoRenew } from '@/api/subscription';
|
|
import type { SubscriptionInfo } from '@/api/subscription';
|
|
import './Billing.css';
|
|
|
|
const formatDate = (iso: string): string => {
|
|
const d = new Date(iso);
|
|
return d.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
};
|
|
|
|
|
|
const Billing: React.FC = () => {
|
|
const [subscription, setSubscription] = useState<SubscriptionInfo | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [autoRenewChecked, setAutoRenewChecked] = useState(false);
|
|
const [autoRenewLoading, setAutoRenewLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
const data = await getCurrentSubscription();
|
|
setSubscription(data);
|
|
setAutoRenewChecked(data.auto_renew);
|
|
} catch (err: unknown) {
|
|
if (!(err as { __msgShown?: boolean })?.__msgShown) message.error('加载订阅数据失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleToggleAutoRenew = async (checked: boolean) => {
|
|
setAutoRenewLoading(true);
|
|
try {
|
|
const res = await toggleAutoRenew(checked);
|
|
message.success(res.message);
|
|
setAutoRenewChecked(checked);
|
|
if (subscription) {
|
|
setSubscription({ ...subscription, auto_renew: checked });
|
|
}
|
|
} catch (err: unknown) {
|
|
if (!(err as { __msgShown?: boolean })?.__msgShown) message.error('操作失败');
|
|
} finally {
|
|
setAutoRenewLoading(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="xx-billing-page">
|
|
<Spin size="large" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="xx-billing-page">
|
|
{subscription && (
|
|
<>
|
|
{/* 当前订阅概览 */}
|
|
<div className="xx-billing-overview">
|
|
<h2>当前订阅</h2>
|
|
<div className="xx-overview-details">
|
|
<div className="xx-overview-item">
|
|
<span className="xx-label">套餐</span>
|
|
<span className="xx-value">{subscription.plan_name}</span>
|
|
</div>
|
|
<div className="xx-overview-item">
|
|
<span className="xx-label">计费周期</span>
|
|
<span className="xx-value">
|
|
{subscription.billing_cycle === 'monthly' ? '月付' : '年付'}
|
|
</span>
|
|
</div>
|
|
<div className="xx-overview-item">
|
|
<span className="xx-label">下次扣费</span>
|
|
<span className="xx-value">{formatDate(subscription.current_period_end)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 自动续费 */}
|
|
<div className="xx-billing-auto-renew">
|
|
<h2>自动续费</h2>
|
|
<div className="xx-auto-renew-row">
|
|
<div className="xx-auto-renew-info">
|
|
<p className="xx-auto-renew-title">到期自动续费</p>
|
|
<p className="xx-auto-renew-desc">
|
|
开启后,将在每个计费周期结束时自动扣费续期,避免服务中断。
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={autoRenewChecked}
|
|
onChange={handleToggleAutoRenew}
|
|
loading={autoRenewLoading}
|
|
checkedChildren="开"
|
|
unCheckedChildren="关"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Billing;
|
|
export const Component = Billing;
|