fix: standardize currency and input selection styles
This commit is contained in:
@@ -627,7 +627,7 @@ export function AdminChannelsPage() {
|
||||
</div>
|
||||
<div className="sms-channel-carrier-price">
|
||||
<Tag tone={carrierToneMap[channel.carrier]}>{carrierLabelMap[channel.carrier]}</Tag>
|
||||
<strong>{channel.unitPrice.toFixed(1)} 分</strong>
|
||||
<strong>{channel.unitPrice.toFixed(3)} 分</strong>
|
||||
</div>
|
||||
<div className="sms-channel-status-cell">
|
||||
<Tag tone={statusToneMap[channel.status]}>{statusLabelMap[channel.status]}</Tag>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { ArrowLeft, FileText, MessageSquare, Server } from 'lucide-react';
|
||||
import { adminApi, type ClientSmsSignature, type ClientSmsTemplate, type EnterpriseApplication, type TenantAccount, type TenantOption } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
|
||||
export function AdminCustomerDetailPage() {
|
||||
const navigate = useNavigate();
|
||||
@@ -67,7 +68,7 @@ export function AdminCustomerDetailPage() {
|
||||
<div className="dashboard-grid enterprise-summary-grid">
|
||||
<div className="surface mini-status-card"><Server size={22} /><div><span>企业编码</span><strong>{tenant?.code ?? '-'}</strong><small>{tenant?.status ?? '-'}</small></div></div>
|
||||
<div className="surface mini-status-card"><MessageSquare size={22} /><div><span>短信余量</span><strong>{(account?.smsUnits ?? 0).toLocaleString('zh-CN')}</strong><small>真实账户余量</small></div></div>
|
||||
<div className="surface mini-status-card"><FileText size={22} /><div><span>现金余额</span><strong>¥{((account?.balanceCents ?? 0) / 100).toLocaleString('zh-CN')}</strong><small>真实账户余额</small></div></div>
|
||||
<div className="surface mini-status-card"><FileText size={22} /><div><span>现金余额</span><strong>¥{formatCents(account?.balanceCents)}</strong><small>真实账户余额</small></div></div>
|
||||
</div>
|
||||
|
||||
<div className="surface section-stack">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Building2, DollarSign, Plus, Trash2, TrendingDown, TrendingUp } from 'lucide-react';
|
||||
import { adminApi, type TenantManagementRow } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Input, Modal, Select, Table, Tag, Textarea, type TableColumn } from '@/components/ui';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
|
||||
type AdminCustomersPageProps = {
|
||||
basePath?: string;
|
||||
@@ -16,10 +17,6 @@ type RechargeForm = {
|
||||
remark: string;
|
||||
};
|
||||
|
||||
function formatCurrency(cents: number) {
|
||||
return (cents / 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function ConfirmModal({ message, onCancel, onConfirm }: { message: string; onCancel: () => void; onConfirm: () => void }) {
|
||||
return (
|
||||
<Modal footer={<><Button onClick={onCancel} variant="ghost">取消</Button><Button onClick={onConfirm}>确认</Button></>} onClose={onCancel} open title="操作确认">
|
||||
@@ -86,14 +83,14 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
const balance = record.account?.balanceCents ?? 0;
|
||||
return (
|
||||
<span className={balance < 0 ? 'status-danger' : ''}>
|
||||
¥{formatCurrency(balance)}
|
||||
¥{formatCents(balance)}
|
||||
{balance < 0 ? <Tag tone="danger" className="enterprise-inline-tag">欠费</Tag> : null}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ key: 'overdraftLimit', title: '透支限额', width: '150px', align: 'right', render: (record) => `¥${formatCurrency(record.account?.creditCents ?? 0)}` },
|
||||
{ key: 'todaySpend', title: '今日消费', width: '150px', align: 'right', render: (record) => `¥${formatCurrency(record.todaySpendCents)}` },
|
||||
{ key: 'overdraftLimit', title: '透支限额', width: '150px', align: 'right', render: (record) => `¥${formatCents(record.account?.creditCents ?? 0)}` },
|
||||
{ key: 'todaySpend', title: '今日消费', width: '150px', align: 'right', render: (record) => `¥${formatCents(record.todaySpendCents)}` },
|
||||
{ key: 'status', title: '企业状态', width: '130px', render: (record) => <Tag tone={record.status === 'active' ? 'success' : 'warning'}>{record.status === 'active' ? '正常' : '已禁用'}</Tag> },
|
||||
{
|
||||
key: 'actions',
|
||||
@@ -172,7 +169,7 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
<div className="surface mini-status-card"><Building2 size={22} /><div><span>企业总数</span><strong>{records.length}</strong><small>真实租户数量。</small></div></div>
|
||||
<div className="surface mini-status-card"><TrendingUp size={22} /><div><span>正常运营</span><strong>{activeCount}</strong><small>可正常提交发送任务。</small></div></div>
|
||||
<div className="surface mini-status-card"><TrendingDown size={22} /><div><span>已禁用</span><strong>{disabledCount}</strong><small>已暂停发送能力。</small></div></div>
|
||||
<div className="surface mini-status-card"><DollarSign size={22} /><div><span>账户余额</span><strong>¥{formatCurrency(totalBalance)}</strong><small>企业账户余额汇总。</small></div></div>
|
||||
<div className="surface mini-status-card"><DollarSign size={22} /><div><span>账户余额</span><strong>¥{formatCents(totalBalance)}</strong><small>企业账户余额汇总。</small></div></div>
|
||||
</div>
|
||||
|
||||
<div className="surface ui-query-panel">
|
||||
@@ -215,7 +212,7 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
>
|
||||
<div className="admin-system-modal-form">
|
||||
<Input disabled label="企业名称" value={rechargeTarget.name} />
|
||||
<Input disabled label="当前余额" prefix="¥" value={formatCurrency(rechargeTarget.account?.balanceCents ?? 0)} />
|
||||
<Input disabled label="当前余额" prefix="¥" value={formatCents(rechargeTarget.account?.balanceCents ?? 0)} />
|
||||
<Input label="充值金额" onChange={(event) => updateRechargeForm('amount', event.target.value)} prefix="¥" required type="number" value={rechargeForm.amount} />
|
||||
<Input label="操作人" onChange={(event) => updateRechargeForm('operator', event.target.value)} value={rechargeForm.operator} />
|
||||
<Textarea className="admin-system-modal-form__wide" label="充值备注" onChange={(event) => updateRechargeForm('remark', event.target.value)} rows={4} value={rechargeForm.remark} />
|
||||
|
||||
@@ -36,8 +36,8 @@ const balanceTone = {
|
||||
|
||||
function formatCurrency(value: number) {
|
||||
return value.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 3,
|
||||
maximumFractionDigits: 3,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Plus, Search } from 'lucide-react';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Pagination, Select, Textarea, Tag, type DateRangeValue } from '@/components/ui';
|
||||
import { adminApi, type RechargeOrder, type TenantOption } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { formatAmount } from '@/utils/currency';
|
||||
|
||||
type ManualRechargeForm = {
|
||||
tenantId: string;
|
||||
@@ -16,17 +17,6 @@ function getDate(value: string) {
|
||||
return value.slice(0, 10);
|
||||
}
|
||||
|
||||
function formatAmount(value?: number) {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value.toLocaleString('zh-CN', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: Number.isInteger(value) ? 0 : 2,
|
||||
});
|
||||
}
|
||||
|
||||
function RemarkCell({ value }: { value?: string }) {
|
||||
return (
|
||||
<div className={['admin-remark-cell', value ? '' : 'admin-remark-cell--empty'].filter(Boolean).join(' ')}>
|
||||
|
||||
@@ -80,7 +80,7 @@ export function AdminSmsApplicationFormPage() {
|
||||
setAppName(application.name);
|
||||
setScene(application.scene ?? '');
|
||||
setDailyLimit(application.dailyLimit ? String(application.dailyLimit) : '');
|
||||
setCustomerUnitPrice(((application.customerUnitPrice ?? 0) / 100).toFixed(4));
|
||||
setCustomerUnitPrice(((application.customerUnitPrice ?? 0) / 100).toFixed(3));
|
||||
setQueuePriority(application.queuePriority === 'priority' ? 'priority' : 'normal');
|
||||
setCmppAccount(application.cmppAccount ?? '');
|
||||
setCmppEnterpriseCode(application.cmppEnterpriseCode ?? application.tenant?.code ?? '');
|
||||
@@ -185,7 +185,7 @@ export function AdminSmsApplicationFormPage() {
|
||||
<Input label="应用名称" onChange={(event) => setAppName(event.target.value)} placeholder="请输入应用名称" required value={appName} />
|
||||
<Input label="应用场景" onChange={(event) => setScene(event.target.value)} placeholder="行业通知/营销推广/验证码" value={scene} />
|
||||
<Input label="日发送数量限制" onChange={(event) => setDailyLimit(event.target.value)} placeholder="100000" required value={dailyLimit} />
|
||||
<Input label="客户单价(元/条)" onChange={(event) => setCustomerUnitPrice(event.target.value)} placeholder="0.0300" required value={customerUnitPrice} />
|
||||
<Input label="客户单价(元/条)" onChange={(event) => setCustomerUnitPrice(event.target.value)} placeholder="0.030" required value={customerUnitPrice} />
|
||||
<div className="admin-app-form-row admin-app-form-row--wide">
|
||||
<span>发送队列</span>
|
||||
<div className="radio-row">
|
||||
|
||||
@@ -146,7 +146,7 @@ function downloadCsv(records: SmsMessageRecord[]) {
|
||||
record.channel?.sendRegion ?? '',
|
||||
getCarrierLabel(record.channel?.carrier),
|
||||
record.billingUnits,
|
||||
(record.amountCents / 100).toFixed(2),
|
||||
(record.amountCents / 100).toFixed(3),
|
||||
record.channel?.name ?? record.channelId ?? '',
|
||||
getStatusLabel(record.status),
|
||||
getTime(record.deliveredAt),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ClipboardCopy, FileText } from 'lucide-react';
|
||||
import { clientApi, type ApplicationCmppParams, type ClientSmsApplication } from '@/api/adminApi';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
import { Button, Modal, Pagination, Tag } from '@/components/ui';
|
||||
|
||||
type LinkStatus = 'connected' | 'degraded' | 'disconnected' | 'inactive';
|
||||
@@ -33,7 +34,7 @@ function normalizeStatus(application: ClientSmsApplication): LinkStatus {
|
||||
}
|
||||
|
||||
function formatPrice(cents?: number | null) {
|
||||
return `${((cents ?? 0) / 100).toFixed(4)} 元`;
|
||||
return `${formatCents(cents)} 元`;
|
||||
}
|
||||
|
||||
function mapParams(params: ApplicationCmppParams): ParamRow[] {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { CreditCard } from 'lucide-react';
|
||||
import { Button, Pagination, Tag } from '@/components/ui';
|
||||
import { clientApi, type BillingPlan } from '@/api/adminApi';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
|
||||
export function ClientBillingPage() {
|
||||
const [plans, setPlans] = useState<BillingPlan[]>([]);
|
||||
@@ -54,7 +55,7 @@ export function ClientBillingPage() {
|
||||
<strong>{plan.smsUnits.toLocaleString('zh-CN')} 条</strong>
|
||||
<p className="muted">{plan.description ?? '适合阶段性短信发送和活动通知。'}</p>
|
||||
<Button icon={<CreditCard size={16} />} onClick={() => createOrder(plan)} variant={plan.smsUnits >= 100000 ? 'primary' : 'ghost'}>
|
||||
¥{(plan.amountCents / 100).toLocaleString('zh-CN')} 立即充值
|
||||
¥{formatCents(plan.amountCents)} 立即充值
|
||||
</Button>
|
||||
</article>
|
||||
))}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Button, Chart, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import { clientApi, type DashboardResponse } from '@/api/adminApi';
|
||||
import { createLineOption, createPieOption } from '@/theme/chartOptions';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { formatAmount, formatCents } from '@/utils/currency';
|
||||
|
||||
type RecentTaskRow = {
|
||||
id: string;
|
||||
@@ -102,8 +103,8 @@ export function ClientHome() {
|
||||
<div className="dashboard-grid">
|
||||
<div className="surface metric-card metric-card--featured">
|
||||
<span>账户剩余余额</span>
|
||||
<strong>¥{availableBalance.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</strong>
|
||||
<small>今日消费 ¥{todaySpend.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</small>
|
||||
<strong>¥{formatAmount(availableBalance)}</strong>
|
||||
<small>今日消费 ¥{formatAmount(todaySpend)}</small>
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日发送</span>
|
||||
@@ -112,7 +113,7 @@ export function ClientHome() {
|
||||
</div>
|
||||
<div className="surface metric-card">
|
||||
<span>今日返还金额</span>
|
||||
<strong>¥{todayRefund.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</strong>
|
||||
<strong>¥{formatAmount(todayRefund)}</strong>
|
||||
<small>异常回执与退费返还</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,7 +170,7 @@ export function ClientHome() {
|
||||
</div>
|
||||
<div>
|
||||
<span>最近充值</span>
|
||||
<strong>{latestRecharge ? `¥${(latestRecharge.amountCents / 100).toFixed(2)}` : '暂无充值'}</strong>
|
||||
<strong>{latestRecharge ? `¥${formatCents(latestRecharge.amountCents)}` : '暂无充值'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -202,9 +202,20 @@
|
||||
.ui-select:focus-within,
|
||||
.ui-select--open {
|
||||
border-color: var(--color-selected);
|
||||
background: color-mix(in srgb, var(--color-selected) 4%, transparent);
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.ui-input input::selection,
|
||||
.ui-textarea::selection {
|
||||
background: var(--color-selected);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.ui-textarea:focus {
|
||||
background: color-mix(in srgb, var(--color-selected) 4%, transparent);
|
||||
}
|
||||
|
||||
.ui-input--error,
|
||||
.ui-select--error {
|
||||
border-color: var(--color-danger);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export function formatAmount(value: number) {
|
||||
return value.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 3,
|
||||
maximumFractionDigits: 3,
|
||||
});
|
||||
}
|
||||
|
||||
export function formatCents(cents?: number | null) {
|
||||
return formatAmount((cents ?? 0) / 100);
|
||||
}
|
||||
|
||||
export function formatYuan(cents?: number | null) {
|
||||
return `¥${formatCents(cents)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user