style: 统一代码格式 (prettier)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
@@ -19,28 +18,61 @@ interface OrderStatusProps {
|
||||
function getStatusConfig(order: PublicOrderStatusSnapshot, locale: Locale) {
|
||||
if (order.rechargeSuccess) {
|
||||
return locale === 'en'
|
||||
? { label: 'Recharge Successful', color: 'text-green-600', icon: '✓', message: 'Your balance has been credited. Thank you for your payment.' }
|
||||
? {
|
||||
label: 'Recharge Successful',
|
||||
color: 'text-green-600',
|
||||
icon: '✓',
|
||||
message: 'Your balance has been credited. Thank you for your payment.',
|
||||
}
|
||||
: { label: '充值成功', color: 'text-green-600', icon: '✓', message: '余额已到账,感谢您的充值!' };
|
||||
}
|
||||
|
||||
if (order.paymentSuccess) {
|
||||
if (order.rechargeStatus === 'paid_pending' || order.rechargeStatus === 'recharging') {
|
||||
return locale === 'en'
|
||||
? { label: 'Recharging', color: 'text-blue-600', icon: '⟳', message: 'Payment received. Recharging your balance...' }
|
||||
? {
|
||||
label: 'Recharging',
|
||||
color: 'text-blue-600',
|
||||
icon: '⟳',
|
||||
message: 'Payment received. Recharging your balance...',
|
||||
}
|
||||
: { label: '充值中', color: 'text-blue-600', icon: '⟳', message: '支付成功,正在充值余额中,请稍候...' };
|
||||
}
|
||||
|
||||
if (order.rechargeStatus === 'failed') {
|
||||
return locale === 'en'
|
||||
? { label: 'Payment Successful', color: 'text-amber-600', icon: '!', message: 'Payment completed, but the balance top-up has not finished yet. The system may retry automatically. Please check the order list later or contact the administrator if it remains unresolved.' }
|
||||
: { label: '支付成功', color: 'text-amber-600', icon: '!', message: '支付已完成,但余额充值暂未完成。系统可能会自动重试,请稍后在订单列表查看;如长时间未到账请联系管理员。' };
|
||||
? {
|
||||
label: 'Payment Successful',
|
||||
color: 'text-amber-600',
|
||||
icon: '!',
|
||||
message:
|
||||
'Payment completed, but the balance top-up has not finished yet. The system may retry automatically. Please check the order list later or contact the administrator if it remains unresolved.',
|
||||
}
|
||||
: {
|
||||
label: '支付成功',
|
||||
color: 'text-amber-600',
|
||||
icon: '!',
|
||||
message:
|
||||
'支付已完成,但余额充值暂未完成。系统可能会自动重试,请稍后在订单列表查看;如长时间未到账请联系管理员。',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (order.status === 'FAILED') {
|
||||
return locale === 'en'
|
||||
? { label: 'Payment Failed', color: 'text-red-600', icon: '✗', message: 'Payment was not completed. Please try again. If funds were deducted but not credited, contact the administrator.' }
|
||||
: { label: '支付失败', color: 'text-red-600', icon: '✗', message: '支付未完成,请重新发起支付;如已扣款未到账,请联系管理员处理。' };
|
||||
? {
|
||||
label: 'Payment Failed',
|
||||
color: 'text-red-600',
|
||||
icon: '✗',
|
||||
message:
|
||||
'Payment was not completed. Please try again. If funds were deducted but not credited, contact the administrator.',
|
||||
}
|
||||
: {
|
||||
label: '支付失败',
|
||||
color: 'text-red-600',
|
||||
icon: '✗',
|
||||
message: '支付未完成,请重新发起支付;如已扣款未到账,请联系管理员处理。',
|
||||
};
|
||||
}
|
||||
|
||||
if (order.status === 'PENDING') {
|
||||
@@ -51,7 +83,12 @@ function getStatusConfig(order: PublicOrderStatusSnapshot, locale: Locale) {
|
||||
|
||||
if (order.status === 'EXPIRED') {
|
||||
return locale === 'en'
|
||||
? { label: 'Order Expired', color: 'text-gray-500', icon: '⏰', message: 'This order has expired. Please create a new one.' }
|
||||
? {
|
||||
label: 'Order Expired',
|
||||
color: 'text-gray-500',
|
||||
icon: '⏰',
|
||||
message: 'This order has expired. Please create a new one.',
|
||||
}
|
||||
: { label: '订单超时', color: 'text-gray-500', icon: '⏰', message: '订单已超时,请重新创建订单。' };
|
||||
}
|
||||
|
||||
@@ -62,7 +99,12 @@ function getStatusConfig(order: PublicOrderStatusSnapshot, locale: Locale) {
|
||||
}
|
||||
|
||||
return locale === 'en'
|
||||
? { label: 'Payment Error', color: 'text-red-600', icon: '✗', message: 'Payment status is abnormal. Please contact the administrator.' }
|
||||
? {
|
||||
label: 'Payment Error',
|
||||
color: 'text-red-600',
|
||||
icon: '✗',
|
||||
message: 'Payment status is abnormal. Please contact the administrator.',
|
||||
}
|
||||
: { label: '支付异常', color: 'text-red-600', icon: '✗', message: '支付状态异常,请联系管理员处理。' };
|
||||
}
|
||||
|
||||
@@ -100,8 +142,7 @@ export default function OrderStatus({
|
||||
if (cancelled) return;
|
||||
setCurrentOrder(nextOrder);
|
||||
onStateChangeRef.current?.(nextOrder);
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
refreshOrder();
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { Locale } from '@/lib/locale';
|
||||
import { formatStatus, formatCreatedAt, getStatusBadgeClass, getPaymentDisplayInfo, type MyOrder } from '@/lib/pay-utils';
|
||||
import {
|
||||
formatStatus,
|
||||
formatCreatedAt,
|
||||
getStatusBadgeClass,
|
||||
getPaymentDisplayInfo,
|
||||
type MyOrder,
|
||||
} from '@/lib/pay-utils';
|
||||
|
||||
interface OrderTableProps {
|
||||
isDark: boolean;
|
||||
@@ -98,7 +104,9 @@ export default function OrderTable({ isDark, locale, loading, error, orders }: O
|
||||
{formatStatus(order.status, locale)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>{formatCreatedAt(order.createdAt, locale)}</div>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>
|
||||
{formatCreatedAt(order.createdAt, locale)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -223,12 +223,15 @@ export default function PaymentForm({
|
||||
!isValid &&
|
||||
(() => {
|
||||
const num = parseFloat(customAmount);
|
||||
let msg = locale === 'en'
|
||||
? 'Amount must be within range and support up to 2 decimal places'
|
||||
: '金额需在范围内,且最多支持 2 位小数(精确到分)';
|
||||
let msg =
|
||||
locale === 'en'
|
||||
? 'Amount must be within range and support up to 2 decimal places'
|
||||
: '金额需在范围内,且最多支持 2 位小数(精确到分)';
|
||||
if (!isNaN(num)) {
|
||||
if (num < minAmount) msg = locale === 'en' ? `Minimum per transaction: ¥${minAmount}` : `单笔最低充值 ¥${minAmount}`;
|
||||
else if (num > effectiveMax) msg = locale === 'en' ? `Maximum per transaction: ¥${effectiveMax}` : `单笔最高充值 ¥${effectiveMax}`;
|
||||
if (num < minAmount)
|
||||
msg = locale === 'en' ? `Minimum per transaction: ¥${minAmount}` : `单笔最低充值 ¥${minAmount}`;
|
||||
else if (num > effectiveMax)
|
||||
msg = locale === 'en' ? `Maximum per transaction: ¥${effectiveMax}` : `单笔最高充值 ¥${effectiveMax}`;
|
||||
}
|
||||
return <div className={['text-xs', dark ? 'text-amber-300' : 'text-amber-700'].join(' ')}>{msg}</div>;
|
||||
})()}
|
||||
@@ -252,7 +255,13 @@ export default function PaymentForm({
|
||||
type="button"
|
||||
disabled={isUnavailable}
|
||||
onClick={() => !isUnavailable && setPaymentType(type)}
|
||||
title={isUnavailable ? (locale === 'en' ? 'Daily limit reached, please use another payment method' : '今日充值额度已满,请使用其他支付方式') : undefined}
|
||||
title={
|
||||
isUnavailable
|
||||
? locale === 'en'
|
||||
? 'Daily limit reached, please use another payment method'
|
||||
: '今日充值额度已满,请使用其他支付方式'
|
||||
: undefined
|
||||
}
|
||||
className={[
|
||||
'relative flex h-[58px] flex-col items-center justify-center rounded-lg border px-3 transition-all sm:flex-1',
|
||||
isUnavailable
|
||||
@@ -260,7 +269,7 @@ export default function PaymentForm({
|
||||
? 'cursor-not-allowed border-slate-700 bg-slate-800/50 opacity-50'
|
||||
: 'cursor-not-allowed border-gray-200 bg-gray-50 opacity-50'
|
||||
: isSelected
|
||||
? `${meta?.selectedBorder || 'border-blue-500'} ${dark ? (meta?.selectedBgDark || 'bg-blue-950') : (meta?.selectedBg || 'bg-blue-50')} ${dark ? 'text-slate-100' : 'text-slate-900'} shadow-sm`
|
||||
? `${meta?.selectedBorder || 'border-blue-500'} ${dark ? meta?.selectedBgDark || 'bg-blue-950' : meta?.selectedBg || 'bg-blue-50'} ${dark ? 'text-slate-100' : 'text-slate-900'} shadow-sm`
|
||||
: dark
|
||||
? 'border-slate-700 bg-slate-900 text-slate-200 hover:border-slate-500'
|
||||
: 'border-gray-300 bg-white text-slate-700 hover:border-gray-400',
|
||||
@@ -271,7 +280,9 @@ export default function PaymentForm({
|
||||
<span className="flex flex-col items-start leading-none">
|
||||
<span className="text-xl font-semibold tracking-tight">{displayInfo.channel || type}</span>
|
||||
{isUnavailable ? (
|
||||
<span className="text-[10px] tracking-wide text-red-400">{locale === 'en' ? 'Daily limit reached' : '今日额度已满'}</span>
|
||||
<span className="text-[10px] tracking-wide text-red-400">
|
||||
{locale === 'en' ? 'Daily limit reached' : '今日额度已满'}
|
||||
</span>
|
||||
) : displayInfo.sublabel ? (
|
||||
<span
|
||||
className={`text-[10px] tracking-wide ${dark ? (isSelected ? 'text-slate-300' : 'text-slate-400') : 'text-slate-600'}`}
|
||||
@@ -292,7 +303,7 @@ export default function PaymentForm({
|
||||
return (
|
||||
<p className={['mt-2 text-xs', dark ? 'text-amber-300' : 'text-amber-600'].join(' ')}>
|
||||
{locale === 'en'
|
||||
? 'The selected payment method has reached today\'s limit. Please switch to another method.'
|
||||
? "The selected payment method has reached today's limit. Please switch to another method."
|
||||
: '所选支付方式今日额度已满,请切换到其他支付方式'}
|
||||
</p>
|
||||
);
|
||||
@@ -331,9 +342,7 @@ export default function PaymentForm({
|
||||
<div
|
||||
className={[
|
||||
'rounded-lg border p-3 text-sm',
|
||||
dark
|
||||
? 'border-amber-700 bg-amber-900/30 text-amber-300'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-700',
|
||||
dark ? 'border-amber-700 bg-amber-900/30 text-amber-300' : 'border-amber-200 bg-amber-50 text-amber-700',
|
||||
].join(' ')}
|
||||
>
|
||||
{locale === 'en'
|
||||
|
||||
@@ -4,12 +4,7 @@ import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import QRCode from 'qrcode';
|
||||
import type { Locale } from '@/lib/locale';
|
||||
import type { PublicOrderStatusSnapshot } from '@/lib/order/status';
|
||||
import {
|
||||
isStripeType,
|
||||
getPaymentMeta,
|
||||
getPaymentIconSrc,
|
||||
getPaymentChannelLabel,
|
||||
} from '@/lib/pay-utils';
|
||||
import { isStripeType, getPaymentMeta, getPaymentIconSrc, getPaymentChannelLabel } from '@/lib/pay-utils';
|
||||
import { buildOrderStatusUrl } from '@/lib/order/status-url';
|
||||
import { TERMINAL_STATUSES } from '@/lib/constants';
|
||||
|
||||
@@ -84,24 +79,38 @@ export default function PaymentQRCode({
|
||||
scanPay: locale === 'en' ? 'Please scan with your payment app' : '请使用支付应用扫码支付',
|
||||
back: locale === 'en' ? 'Back' : '返回',
|
||||
cancelOrder: locale === 'en' ? 'Cancel Order' : '取消订单',
|
||||
h5Hint: locale === 'en' ? 'After payment, please return to this page. The system will confirm automatically.' : '支付完成后请返回此页面,系统将自动确认',
|
||||
h5Hint:
|
||||
locale === 'en'
|
||||
? 'After payment, please return to this page. The system will confirm automatically.'
|
||||
: '支付完成后请返回此页面,系统将自动确认',
|
||||
paid: locale === 'en' ? 'Order Paid' : '订单已支付',
|
||||
paidCancelBlocked:
|
||||
locale === 'en' ? 'This order has already been paid and cannot be cancelled. The recharge will be credited automatically.' : '该订单已支付完成,无法取消。充值将自动到账。',
|
||||
locale === 'en'
|
||||
? 'This order has already been paid and cannot be cancelled. The recharge will be credited automatically.'
|
||||
: '该订单已支付完成,无法取消。充值将自动到账。',
|
||||
backToRecharge: locale === 'en' ? 'Back to Recharge' : '返回充值',
|
||||
credited: locale === 'en' ? 'Credited ¥' : '到账 ¥',
|
||||
stripeLoadFailed: locale === 'en' ? 'Failed to load payment component. Please refresh and try again.' : '支付组件加载失败,请刷新页面重试',
|
||||
initFailed: locale === 'en' ? 'Payment initialization failed. Please go back and try again.' : '支付初始化失败,请返回重试',
|
||||
stripeLoadFailed:
|
||||
locale === 'en'
|
||||
? 'Failed to load payment component. Please refresh and try again.'
|
||||
: '支付组件加载失败,请刷新页面重试',
|
||||
initFailed:
|
||||
locale === 'en' ? 'Payment initialization failed. Please go back and try again.' : '支付初始化失败,请返回重试',
|
||||
loadingForm: locale === 'en' ? 'Loading payment form...' : '正在加载支付表单...',
|
||||
payFailed: locale === 'en' ? 'Payment failed. Please try again.' : '支付失败,请重试',
|
||||
successProcessing: locale === 'en' ? 'Payment successful, processing your order...' : '支付成功,正在处理订单...',
|
||||
processing: locale === 'en' ? 'Processing...' : '处理中...',
|
||||
payNow: locale === 'en' ? 'Pay' : '支付',
|
||||
popupBlocked:
|
||||
locale === 'en' ? 'Popup was blocked by your browser. Please allow popups for this site and try again.' : '弹出窗口被浏览器拦截,请允许本站弹出窗口后重试',
|
||||
locale === 'en'
|
||||
? 'Popup was blocked by your browser. Please allow popups for this site and try again.'
|
||||
: '弹出窗口被浏览器拦截,请允许本站弹出窗口后重试',
|
||||
redirectingPrefix: locale === 'en' ? 'Redirecting to ' : '正在跳转到',
|
||||
redirectingSuffix: locale === 'en' ? '...' : '...',
|
||||
redirectRetryHint: locale === 'en' ? 'If the payment app does not open automatically, go back and try again.' : '如未自动拉起支付应用,请返回上一页后重新发起支付。',
|
||||
redirectRetryHint:
|
||||
locale === 'en'
|
||||
? 'If the payment app does not open automatically, go back and try again.'
|
||||
: '如未自动拉起支付应用,请返回上一页后重新发起支付。',
|
||||
notRedirectedPrefix: locale === 'en' ? 'Not redirected? Open ' : '未跳转?点击前往',
|
||||
goPaySuffix: locale === 'en' ? '' : '',
|
||||
gotoPrefix: locale === 'en' ? 'Open ' : '前往',
|
||||
@@ -327,8 +336,7 @@ export default function PaymentQRCode({
|
||||
onStatusChange(data);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
}, [orderId, onStatusChange, statusAccessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -372,8 +380,7 @@ export default function PaymentQRCode({
|
||||
} else {
|
||||
await pollStatus();
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const meta = getPaymentMeta(paymentType || 'alipay');
|
||||
@@ -412,7 +419,9 @@ export default function PaymentQRCode({
|
||||
{amount.toFixed(2)}
|
||||
</div>
|
||||
)}
|
||||
<div className={`mt-1 text-sm ${expired ? 'text-red-500' : !expired && timeLeftSeconds <= 60 ? 'text-red-500 animate-pulse' : dark ? 'text-slate-400' : 'text-gray-500'}`}>
|
||||
<div
|
||||
className={`mt-1 text-sm ${expired ? 'text-red-500' : !expired && timeLeftSeconds <= 60 ? 'text-red-500 animate-pulse' : dark ? 'text-slate-400' : 'text-gray-500'}`}
|
||||
>
|
||||
{expired ? t.expired : `${t.remaining}: ${timeLeft}`}
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,9 +437,7 @@ export default function PaymentQRCode({
|
||||
dark ? 'border-slate-700' : 'border-gray-300',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className={['text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>
|
||||
{t.initFailed}
|
||||
</p>
|
||||
<p className={['text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>{t.initFailed}</p>
|
||||
</div>
|
||||
) : !stripeLoaded ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
@@ -440,10 +447,14 @@ export default function PaymentQRCode({
|
||||
</span>
|
||||
</div>
|
||||
) : stripeError && !stripeLib ? (
|
||||
<div className={[
|
||||
'rounded-lg border p-3 text-sm',
|
||||
dark ? 'border-red-700 bg-red-900/30 text-red-400' : 'border-red-200 bg-red-50 text-red-600',
|
||||
].join(' ')}>{stripeError}</div>
|
||||
<div
|
||||
className={[
|
||||
'rounded-lg border p-3 text-sm',
|
||||
dark ? 'border-red-700 bg-red-900/30 text-red-400' : 'border-red-200 bg-red-50 text-red-600',
|
||||
].join(' ')}
|
||||
>
|
||||
{stripeError}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
@@ -472,9 +483,7 @@ export default function PaymentQRCode({
|
||||
onClick={handleStripeSubmit}
|
||||
className={[
|
||||
'w-full rounded-lg py-3 font-medium text-white shadow-md transition-colors',
|
||||
stripeSubmitting
|
||||
? 'cursor-not-allowed bg-gray-400'
|
||||
: meta.buttonClass,
|
||||
stripeSubmitting ? 'cursor-not-allowed bg-gray-400' : meta.buttonClass,
|
||||
].join(' ')}
|
||||
>
|
||||
{stripeSubmitting ? (
|
||||
@@ -505,7 +514,10 @@ export default function PaymentQRCode({
|
||||
) : shouldAutoRedirect ? (
|
||||
<>
|
||||
<div className="flex items-center justify-center py-6">
|
||||
<div className={`h-8 w-8 animate-spin rounded-full border-2 border-t-transparent`} style={{ borderColor: meta.color, borderTopColor: 'transparent' }} />
|
||||
<div
|
||||
className={`h-8 w-8 animate-spin rounded-full border-2 border-t-transparent`}
|
||||
style={{ borderColor: meta.color, borderTopColor: 'transparent' }}
|
||||
/>
|
||||
<span className={['ml-3 text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>
|
||||
{`${t.redirectingPrefix}${channelLabel}${t.redirectingSuffix}`}
|
||||
</span>
|
||||
@@ -517,11 +529,11 @@ export default function PaymentQRCode({
|
||||
className={`flex w-full items-center justify-center gap-2 rounded-lg py-3 font-medium text-white shadow-md ${meta.buttonClass}`}
|
||||
>
|
||||
{iconSrc && <img src={iconSrc} alt={channelLabel} className="h-5 w-5 brightness-0 invert" />}
|
||||
{redirected ? `${t.notRedirectedPrefix}${channelLabel}` : `${t.gotoPrefix}${channelLabel}${t.gotoSuffix}`}
|
||||
{redirected
|
||||
? `${t.notRedirectedPrefix}${channelLabel}`
|
||||
: `${t.gotoPrefix}${channelLabel}${t.gotoSuffix}`}
|
||||
</a>
|
||||
<p className={['text-center text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>
|
||||
{t.h5Hint}
|
||||
</p>
|
||||
<p className={['text-center text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>{t.h5Hint}</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -584,9 +596,7 @@ export default function PaymentQRCode({
|
||||
onClick={handleCancel}
|
||||
className={[
|
||||
'flex-1 rounded-lg border py-2 text-sm',
|
||||
dark
|
||||
? 'border-red-700 text-red-400 hover:bg-red-900/30'
|
||||
: 'border-red-300 text-red-600 hover:bg-red-50',
|
||||
dark ? 'border-red-700 text-red-400 hover:bg-red-900/30' : 'border-red-300 text-red-600 hover:bg-red-50',
|
||||
].join(' ')}
|
||||
>
|
||||
{t.cancelOrder}
|
||||
|
||||
@@ -85,7 +85,9 @@ export default function DailyChart({ data, dark, locale = 'zh' }: DailyChartProp
|
||||
<h3 className={['mb-4 text-sm font-semibold', dark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
|
||||
{chartTitle}
|
||||
</h3>
|
||||
<p className={['text-center text-sm py-16', dark ? 'text-slate-500' : 'text-gray-400'].join(' ')}>{emptyText}</p>
|
||||
<p className={['text-center text-sm py-16', dark ? 'text-slate-500' : 'text-gray-400'].join(' ')}>
|
||||
{emptyText}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -121,7 +123,11 @@ export default function DailyChart({ data, dark, locale = 'zh' }: DailyChartProp
|
||||
tickLine={false}
|
||||
width={60}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip dark={dark} currency={currency} amountLabel={amountLabel} countLabel={countLabel} />} />
|
||||
<Tooltip
|
||||
content={
|
||||
<CustomTooltip dark={dark} currency={currency} amountLabel={amountLabel} countLabel={countLabel} />
|
||||
}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="amount"
|
||||
|
||||
@@ -18,9 +18,20 @@ interface DashboardStatsProps {
|
||||
export default function DashboardStats({ summary, dark, locale = 'zh' }: DashboardStatsProps) {
|
||||
const currency = locale === 'en' ? '$' : '¥';
|
||||
const cards = [
|
||||
{ label: locale === 'en' ? 'Today Recharge' : '今日充值', value: `${currency}${summary.today.amount.toLocaleString()}`, accent: true },
|
||||
{ label: locale === 'en' ? 'Today Orders' : '今日订单', value: `${summary.today.paidCount}/${summary.today.orderCount}` },
|
||||
{ label: locale === 'en' ? 'Total Recharge' : '累计充值', value: `${currency}${summary.total.amount.toLocaleString()}`, accent: true },
|
||||
{
|
||||
label: locale === 'en' ? 'Today Recharge' : '今日充值',
|
||||
value: `${currency}${summary.today.amount.toLocaleString()}`,
|
||||
accent: true,
|
||||
},
|
||||
{
|
||||
label: locale === 'en' ? 'Today Orders' : '今日订单',
|
||||
value: `${summary.today.paidCount}/${summary.today.orderCount}`,
|
||||
},
|
||||
{
|
||||
label: locale === 'en' ? 'Total Recharge' : '累计充值',
|
||||
value: `${currency}${summary.total.amount.toLocaleString()}`,
|
||||
accent: true,
|
||||
},
|
||||
{ label: locale === 'en' ? 'Paid Orders' : '累计订单', value: String(summary.total.paidCount) },
|
||||
{ label: locale === 'en' ? 'Success Rate' : '成功率', value: `${summary.successRate}%` },
|
||||
{ label: locale === 'en' ? 'Average Amount' : '平均充值', value: `${currency}${summary.avgAmount.toFixed(2)}` },
|
||||
|
||||
@@ -97,7 +97,8 @@ export default function Leaderboard({ data, dark, locale = 'zh' }: LeaderboardPr
|
||||
<td
|
||||
className={`whitespace-nowrap px-4 py-3 text-sm font-medium ${dark ? 'text-slate-200' : 'text-slate-900'}`}
|
||||
>
|
||||
{currency}{entry.totalAmount.toLocaleString()}
|
||||
{currency}
|
||||
{entry.totalAmount.toLocaleString()}
|
||||
</td>
|
||||
<td className={tdMuted}>{entry.orderCount}</td>
|
||||
</tr>
|
||||
|
||||
@@ -49,77 +49,78 @@ interface OrderDetailProps {
|
||||
|
||||
export default function OrderDetail({ order, onClose, dark, locale = 'zh' }: OrderDetailProps) {
|
||||
const currency = locale === 'en' ? '$' : '¥';
|
||||
const text = locale === 'en'
|
||||
? {
|
||||
title: 'Order Details',
|
||||
auditLogs: 'Audit Logs',
|
||||
operator: 'Operator',
|
||||
emptyLogs: 'No logs',
|
||||
close: 'Close',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
orderId: 'Order ID',
|
||||
userId: 'User ID',
|
||||
userName: 'Username',
|
||||
email: 'Email',
|
||||
amount: 'Amount',
|
||||
status: 'Status',
|
||||
paymentSuccess: 'Payment Success',
|
||||
rechargeSuccess: 'Recharge Success',
|
||||
rechargeStatus: 'Recharge Status',
|
||||
paymentChannel: 'Payment Channel',
|
||||
provider: 'Provider',
|
||||
rechargeCode: 'Recharge Code',
|
||||
paymentTradeNo: 'Payment Trade No.',
|
||||
clientIp: 'Client IP',
|
||||
sourceHost: 'Source Host',
|
||||
sourcePage: 'Source Page',
|
||||
createdAt: 'Created At',
|
||||
expiresAt: 'Expires At',
|
||||
paidAt: 'Paid At',
|
||||
completedAt: 'Completed At',
|
||||
failedAt: 'Failed At',
|
||||
failedReason: 'Failure Reason',
|
||||
refundAmount: 'Refund Amount',
|
||||
refundReason: 'Refund Reason',
|
||||
refundAt: 'Refunded At',
|
||||
forceRefund: 'Force Refund',
|
||||
}
|
||||
: {
|
||||
title: '订单详情',
|
||||
auditLogs: '审计日志',
|
||||
operator: '操作者',
|
||||
emptyLogs: '暂无日志',
|
||||
close: '关闭',
|
||||
yes: '是',
|
||||
no: '否',
|
||||
orderId: '订单号',
|
||||
userId: '用户ID',
|
||||
userName: '用户名',
|
||||
email: '邮箱',
|
||||
amount: '金额',
|
||||
status: '状态',
|
||||
paymentSuccess: '支付成功',
|
||||
rechargeSuccess: '充值成功',
|
||||
rechargeStatus: '充值状态',
|
||||
paymentChannel: '支付渠道',
|
||||
provider: '提供商',
|
||||
rechargeCode: '充值码',
|
||||
paymentTradeNo: '支付单号',
|
||||
clientIp: '客户端IP',
|
||||
sourceHost: '来源域名',
|
||||
sourcePage: '来源页面',
|
||||
createdAt: '创建时间',
|
||||
expiresAt: '过期时间',
|
||||
paidAt: '支付时间',
|
||||
completedAt: '完成时间',
|
||||
failedAt: '失败时间',
|
||||
failedReason: '失败原因',
|
||||
refundAmount: '退款金额',
|
||||
refundReason: '退款原因',
|
||||
refundAt: '退款时间',
|
||||
forceRefund: '强制退款',
|
||||
};
|
||||
const text =
|
||||
locale === 'en'
|
||||
? {
|
||||
title: 'Order Details',
|
||||
auditLogs: 'Audit Logs',
|
||||
operator: 'Operator',
|
||||
emptyLogs: 'No logs',
|
||||
close: 'Close',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
orderId: 'Order ID',
|
||||
userId: 'User ID',
|
||||
userName: 'Username',
|
||||
email: 'Email',
|
||||
amount: 'Amount',
|
||||
status: 'Status',
|
||||
paymentSuccess: 'Payment Success',
|
||||
rechargeSuccess: 'Recharge Success',
|
||||
rechargeStatus: 'Recharge Status',
|
||||
paymentChannel: 'Payment Channel',
|
||||
provider: 'Provider',
|
||||
rechargeCode: 'Recharge Code',
|
||||
paymentTradeNo: 'Payment Trade No.',
|
||||
clientIp: 'Client IP',
|
||||
sourceHost: 'Source Host',
|
||||
sourcePage: 'Source Page',
|
||||
createdAt: 'Created At',
|
||||
expiresAt: 'Expires At',
|
||||
paidAt: 'Paid At',
|
||||
completedAt: 'Completed At',
|
||||
failedAt: 'Failed At',
|
||||
failedReason: 'Failure Reason',
|
||||
refundAmount: 'Refund Amount',
|
||||
refundReason: 'Refund Reason',
|
||||
refundAt: 'Refunded At',
|
||||
forceRefund: 'Force Refund',
|
||||
}
|
||||
: {
|
||||
title: '订单详情',
|
||||
auditLogs: '审计日志',
|
||||
operator: '操作者',
|
||||
emptyLogs: '暂无日志',
|
||||
close: '关闭',
|
||||
yes: '是',
|
||||
no: '否',
|
||||
orderId: '订单号',
|
||||
userId: '用户ID',
|
||||
userName: '用户名',
|
||||
email: '邮箱',
|
||||
amount: '金额',
|
||||
status: '状态',
|
||||
paymentSuccess: '支付成功',
|
||||
rechargeSuccess: '充值成功',
|
||||
rechargeStatus: '充值状态',
|
||||
paymentChannel: '支付渠道',
|
||||
provider: '提供商',
|
||||
rechargeCode: '充值码',
|
||||
paymentTradeNo: '支付单号',
|
||||
clientIp: '客户端IP',
|
||||
sourceHost: '来源域名',
|
||||
sourcePage: '来源页面',
|
||||
createdAt: '创建时间',
|
||||
expiresAt: '过期时间',
|
||||
paidAt: '支付时间',
|
||||
completedAt: '完成时间',
|
||||
failedAt: '失败时间',
|
||||
failedReason: '失败原因',
|
||||
refundAmount: '退款金额',
|
||||
refundReason: '退款原因',
|
||||
refundAt: '退款时间',
|
||||
forceRefund: '强制退款',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
|
||||
@@ -32,37 +32,38 @@ interface OrderTableProps {
|
||||
|
||||
export default function OrderTable({ orders, onRetry, onCancel, onViewDetail, dark, locale = 'zh' }: OrderTableProps) {
|
||||
const currency = locale === 'en' ? '$' : '¥';
|
||||
const text = locale === 'en'
|
||||
? {
|
||||
orderId: 'Order ID',
|
||||
userName: 'Username',
|
||||
email: 'Email',
|
||||
notes: 'Notes',
|
||||
amount: 'Amount',
|
||||
status: 'Status',
|
||||
paymentMethod: 'Payment',
|
||||
source: 'Source',
|
||||
createdAt: 'Created At',
|
||||
actions: 'Actions',
|
||||
retry: 'Retry',
|
||||
cancel: 'Cancel',
|
||||
empty: 'No orders',
|
||||
}
|
||||
: {
|
||||
orderId: '订单号',
|
||||
userName: '用户名',
|
||||
email: '邮箱',
|
||||
notes: '备注',
|
||||
amount: '金额',
|
||||
status: '状态',
|
||||
paymentMethod: '支付方式',
|
||||
source: '来源',
|
||||
createdAt: '创建时间',
|
||||
actions: '操作',
|
||||
retry: '重试',
|
||||
cancel: '取消',
|
||||
empty: '暂无订单',
|
||||
};
|
||||
const text =
|
||||
locale === 'en'
|
||||
? {
|
||||
orderId: 'Order ID',
|
||||
userName: 'Username',
|
||||
email: 'Email',
|
||||
notes: 'Notes',
|
||||
amount: 'Amount',
|
||||
status: 'Status',
|
||||
paymentMethod: 'Payment',
|
||||
source: 'Source',
|
||||
createdAt: 'Created At',
|
||||
actions: 'Actions',
|
||||
retry: 'Retry',
|
||||
cancel: 'Cancel',
|
||||
empty: 'No orders',
|
||||
}
|
||||
: {
|
||||
orderId: '订单号',
|
||||
userName: '用户名',
|
||||
email: '邮箱',
|
||||
notes: '备注',
|
||||
amount: '金额',
|
||||
status: '状态',
|
||||
paymentMethod: '支付方式',
|
||||
source: '来源',
|
||||
createdAt: '创建时间',
|
||||
actions: '操作',
|
||||
retry: '重试',
|
||||
cancel: '取消',
|
||||
empty: '暂无订单',
|
||||
};
|
||||
|
||||
const thCls = `px-4 py-3 text-left text-xs font-medium uppercase ${dark ? 'text-slate-400' : 'text-gray-500'}`;
|
||||
const tdMuted = `whitespace-nowrap px-4 py-3 text-sm ${dark ? 'text-slate-400' : 'text-gray-500'}`;
|
||||
@@ -133,7 +134,8 @@ export default function OrderTable({ orders, onRetry, onCancel, onViewDetail, da
|
||||
<td className={tdMuted}>{order.userEmail || '-'}</td>
|
||||
<td className={tdMuted}>{order.userNotes || '-'}</td>
|
||||
<td className={`whitespace-nowrap px-4 py-3 text-sm font-medium ${dark ? 'text-slate-200' : ''}`}>
|
||||
{currency}{order.amount.toFixed(2)}
|
||||
{currency}
|
||||
{order.amount.toFixed(2)}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-4 py-3 text-sm">
|
||||
<span
|
||||
|
||||
@@ -44,9 +44,7 @@ export default function PaymentMethodChart({ data, dark, locale = 'zh' }: Paymen
|
||||
dark ? 'border-slate-700 bg-slate-800/60' : 'border-slate-200 bg-white shadow-sm',
|
||||
].join(' ')}
|
||||
>
|
||||
<h3 className={['mb-4 text-sm font-semibold', dark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
|
||||
{title}
|
||||
</h3>
|
||||
<h3 className={['mb-4 text-sm font-semibold', dark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>{title}</h3>
|
||||
<div className="space-y-4">
|
||||
{data.map((method) => {
|
||||
const meta = getPaymentMeta(method.paymentType);
|
||||
@@ -56,7 +54,8 @@ export default function PaymentMethodChart({ data, dark, locale = 'zh' }: Paymen
|
||||
<div className="mb-1.5 flex items-center justify-between text-sm">
|
||||
<span className={dark ? 'text-slate-300' : 'text-slate-700'}>{label}</span>
|
||||
<span className={dark ? 'text-slate-400' : 'text-slate-500'}>
|
||||
{currency}{method.amount.toLocaleString()} · {method.percentage}%
|
||||
{currency}
|
||||
{method.amount.toLocaleString()} · {method.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -65,7 +64,10 @@ export default function PaymentMethodChart({ data, dark, locale = 'zh' }: Paymen
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={['h-full rounded-full transition-all', dark ? meta.chartBar.dark : meta.chartBar.light].join(' ')}
|
||||
className={[
|
||||
'h-full rounded-full transition-all',
|
||||
dark ? meta.chartBar.dark : meta.chartBar.light,
|
||||
].join(' ')}
|
||||
style={{ width: `${method.percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -74,10 +74,7 @@ export default function RefundDialog({
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={onCancel}>
|
||||
<div
|
||||
className={[
|
||||
'w-full max-w-md rounded-xl p-6 shadow-xl',
|
||||
dark ? 'bg-slate-900' : 'bg-white',
|
||||
].join(' ')}
|
||||
className={['w-full max-w-md rounded-xl p-6 shadow-xl', dark ? 'bg-slate-900' : 'bg-white'].join(' ')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className={['text-lg font-bold', dark ? 'text-slate-100' : 'text-gray-900'].join(' ')}>{text.title}</h3>
|
||||
@@ -90,7 +87,10 @@ export default function RefundDialog({
|
||||
|
||||
<div className={['rounded-lg p-3', dark ? 'bg-slate-800' : 'bg-gray-50'].join(' ')}>
|
||||
<div className={['text-sm', dark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>{text.amount}</div>
|
||||
<div className="text-lg font-bold text-red-600">{currency}{amount.toFixed(2)}</div>
|
||||
<div className="text-lg font-bold text-red-600">
|
||||
{currency}
|
||||
{amount.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{warning && (
|
||||
|
||||
Reference in New Issue
Block a user