fix: close receipt delivery workflows
This commit is contained in:
@@ -1432,6 +1432,16 @@ export const adminApi = {
|
||||
request<ApplicationCmppParams>(`/admin/enterprise-applications/${applicationId}/cmpp-params`),
|
||||
getApplicationHttpApiConfig: (applicationId: string) => request<HttpApiConfigResponse>(`/admin/enterprise-applications/${applicationId}/http-api`),
|
||||
updateApplicationHttpApiConfig: (applicationId: string, body: Partial<HttpApiConfig> & { ipAllowlist?: string[] }) => request<HttpApiConfigResponse>(`/admin/enterprise-applications/${applicationId}/http-api`, { method: 'PUT', body: JSON.stringify(body) }),
|
||||
listApplicationHttpWebhooks: (applicationId: string) =>
|
||||
request<HttpWebhookEndpoint[]>(`/admin/enterprise-applications/${applicationId}/http-api/webhooks`),
|
||||
saveApplicationHttpWebhook: (
|
||||
applicationId: string,
|
||||
eventType: 'receipt' | 'uplink',
|
||||
body: { url: string; rotateSecret?: boolean; status?: string },
|
||||
) => request<HttpWebhookEndpoint>(`/admin/enterprise-applications/${applicationId}/http-api/webhooks/${eventType}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
listChannels: () => request<AdminChannel[]>('/admin/channels'),
|
||||
listReconciliationReports: (query: { dateFrom?: string; dateTo?: string; tenantId?: string; applicationId?: string; page?: number; pageSize?: number } = {}) =>
|
||||
request<PagedResponse<DailyReconciliationReport>>(withQuery('/admin/reports/reconciliation', query)),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, ManualRechargeDialog, Pagination, Tag, type DateRangeValue } from '@/components/ui';
|
||||
import { Plus, ReceiptText, Search } from 'lucide-react';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, ManualRechargeDialog, Pagination, RechargeReceiptDialog, Tag, type DateRangeValue } from '@/components/ui';
|
||||
import { adminApi, type RechargeOrder, type TenantAccount, type TenantOption } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
@@ -24,6 +24,7 @@ export function AdminRechargeRecordsPage() {
|
||||
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>({});
|
||||
const [manualOpen, setManualOpen] = useState(false);
|
||||
const [receiptRecord, setReceiptRecord] = useState<RechargeOrder | null>(null);
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
@@ -67,6 +68,9 @@ export function AdminRechargeRecordsPage() {
|
||||
const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize));
|
||||
const currentPage = Math.min(page, totalPages);
|
||||
const visibleRows = filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
||||
const receiptTenant = receiptRecord
|
||||
? receiptRecord.tenant ?? tenants.find((tenant) => tenant.id === receiptRecord.tenantId)
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
@@ -107,15 +111,16 @@ export function AdminRechargeRecordsPage() {
|
||||
<th style={{ width: '140px' }}>充值后余额</th>
|
||||
<th style={{ width: '120px' }}>充值类型</th>
|
||||
<th style={{ width: '300px' }}>备注</th>
|
||||
<th style={{ width: '130px' }}>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{error ? (
|
||||
<tr><td className="ui-table__empty" colSpan={6}>{error}</td></tr>
|
||||
<tr><td className="ui-table__empty" colSpan={7}>{error}</td></tr>
|
||||
) : loading ? (
|
||||
<tr><td className="ui-table__empty" colSpan={6}>正在加载真实充值记录...</td></tr>
|
||||
<tr><td className="ui-table__empty" colSpan={7}>正在加载真实充值记录...</td></tr>
|
||||
) : filteredRows.length === 0 ? (
|
||||
<tr><td className="ui-table__empty" colSpan={6}>暂无真实充值记录</td></tr>
|
||||
<tr><td className="ui-table__empty" colSpan={7}>暂无真实充值记录</td></tr>
|
||||
) : visibleRows.map((record) => {
|
||||
const tenantName = record.tenant?.name ?? tenants.find((tenant) => tenant.id === record.tenantId)?.name ?? record.tenantId;
|
||||
return (
|
||||
@@ -126,6 +131,16 @@ export function AdminRechargeRecordsPage() {
|
||||
<td>{record.balanceAfterCents === null || record.balanceAfterCents === undefined ? '-' : `¥${formatCents(record.balanceAfterCents)}`}</td>
|
||||
<td><Tag tone="warning">人工充值</Tag></td>
|
||||
<td><RemarkCell value={record.remark ?? undefined} /></td>
|
||||
<td>
|
||||
<Button
|
||||
icon={<ReceiptText size={15} />}
|
||||
onClick={() => setReceiptRecord(record)}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
查看回执
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
@@ -157,6 +172,12 @@ export function AdminRechargeRecordsPage() {
|
||||
balanceCents: accounts.find((account) => account.tenantId === tenant.id)?.balanceCents ?? 0,
|
||||
}))}
|
||||
/>
|
||||
<RechargeReceiptDialog
|
||||
onClose={() => setReceiptRecord(null)}
|
||||
open={Boolean(receiptRecord)}
|
||||
record={receiptRecord}
|
||||
tenant={receiptTenant}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,13 +15,6 @@ const carrierMeta: Record<Carrier, { label: string; description: string }> = {
|
||||
telecom: { label: '电信', description: '电信号码只会进入电信通道组' },
|
||||
};
|
||||
|
||||
const deliveryModeOptions = [
|
||||
{ label: '仅 CMPP', value: 'cmpp' },
|
||||
{ label: '仅 HTTP', value: 'http' },
|
||||
{ label: 'CMPP + HTTP 双投', value: 'both' },
|
||||
{ label: '不投递', value: 'none' },
|
||||
];
|
||||
|
||||
const httpCapabilityOptions: Array<{ key: keyof HttpApiConfig; label: string }> = [
|
||||
{ key: 'sendEnabled', label: '单条发送' },
|
||||
{ key: 'messageQueryEnabled', label: '状态查询' },
|
||||
@@ -62,6 +55,8 @@ export function AdminSmsApplicationFormPage() {
|
||||
allowClientManualRetry: true, allowClientTest: true,
|
||||
});
|
||||
const [httpIpAddress, setHttpIpAddress] = useState('');
|
||||
const [receiptWebhookUrl, setReceiptWebhookUrl] = useState('');
|
||||
const [uplinkWebhookUrl, setUplinkWebhookUrl] = useState('');
|
||||
const [groups, setGroups] = useState<ChannelGroup[]>([]);
|
||||
const [mobileGroupId, setMobileGroupId] = useState('');
|
||||
const [unicomGroupId, setUnicomGroupId] = useState('');
|
||||
@@ -128,10 +123,15 @@ export function AdminSmsApplicationFormPage() {
|
||||
useEffect(() => {
|
||||
if (!appId) return;
|
||||
let cancelled = false;
|
||||
adminApi.getApplicationHttpApiConfig(appId).then((result) => {
|
||||
Promise.all([
|
||||
adminApi.getApplicationHttpApiConfig(appId),
|
||||
adminApi.listApplicationHttpWebhooks(appId),
|
||||
]).then(([result, webhooks]) => {
|
||||
if (cancelled) return;
|
||||
if (result.config) setHttpConfig(result.config);
|
||||
setHttpIpAddress(result.ipAllowlist.join('\n'));
|
||||
setReceiptWebhookUrl(webhooks.find((item) => item.eventType === 'receipt')?.url ?? '');
|
||||
setUplinkWebhookUrl(webhooks.find((item) => item.eventType === 'uplink')?.url ?? '');
|
||||
}).catch((failure: Error) => {
|
||||
if (!cancelled) setError(failure.message || 'HTTP接口配置加载失败');
|
||||
});
|
||||
@@ -245,6 +245,10 @@ export function AdminSmsApplicationFormPage() {
|
||||
})),
|
||||
});
|
||||
await adminApi.updateApplicationHttpApiConfig(application.id, { ...httpConfig, ipAllowlist: parseIpAllowlist(httpIpAddress) });
|
||||
await Promise.all([
|
||||
adminApi.saveApplicationHttpWebhook(application.id, 'receipt', { url: receiptWebhookUrl.trim() }),
|
||||
adminApi.saveApplicationHttpWebhook(application.id, 'uplink', { url: uplinkWebhookUrl.trim() }),
|
||||
]);
|
||||
goBack();
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '短信应用保存失败');
|
||||
@@ -403,8 +407,6 @@ export function AdminSmsApplicationFormPage() {
|
||||
<Input label="HTTP QPS" onChange={(event) => setHttpConfig((current) => ({ ...current, qpsLimit: Number(event.target.value) || 1 }))} value={String(httpConfig.qpsLimit)} />
|
||||
<Input label="签名时间容差(秒)" onChange={(event) => setHttpConfig((current) => ({ ...current, timestampToleranceSeconds: Number(event.target.value) || 300 }))} value={String(httpConfig.timestampToleranceSeconds)} />
|
||||
<Input label="最多有效凭据数" onChange={(event) => setHttpConfig((current) => ({ ...current, maxCredentialCount: Number(event.target.value) || 2 }))} value={String(httpConfig.maxCredentialCount)} />
|
||||
<Select label="回执投递方式" onChange={(event) => setHttpConfig((current) => ({ ...current, receiptDeliveryMode: event.target.value as HttpApiConfig['receiptDeliveryMode'] }))} options={deliveryModeOptions} value={httpConfig.receiptDeliveryMode} />
|
||||
<Select label="上行投递方式" onChange={(event) => setHttpConfig((current) => ({ ...current, uplinkDeliveryMode: event.target.value as HttpApiConfig['uplinkDeliveryMode'] }))} options={deliveryModeOptions} value={httpConfig.uplinkDeliveryMode} />
|
||||
<Input label="Webhook 超时(秒)" onChange={(event) => setHttpConfig((current) => ({ ...current, webhookTimeoutSeconds: Number(event.target.value) || 10 }))} value={String(httpConfig.webhookTimeoutSeconds)} />
|
||||
<Input label="Webhook 最大尝试次数" onChange={(event) => setHttpConfig((current) => ({ ...current, webhookMaxAttempts: Number(event.target.value) || 7 }))} value={String(httpConfig.webhookMaxAttempts)} />
|
||||
<div className="admin-app-form-row admin-app-form-row--wide"><span>HTTP 安全与重试</span><div className="radio-row">
|
||||
@@ -413,7 +415,26 @@ export function AdminSmsApplicationFormPage() {
|
||||
<label><input checked={httpConfig.allowClientManualRetry} onChange={() => setHttpConfig((current) => ({ ...current, allowClientManualRetry: !current.allowClientManualRetry }))} type="checkbox" />允许客户端手工重投</label>
|
||||
</div></div>
|
||||
</div>
|
||||
) : <div className="admin-app-protocol-empty">HTTP 接口未开通,能力、鉴权和 Webhook 参数已收起。</div>}
|
||||
) : <div className="admin-app-protocol-empty">HTTP 接口未开通,接口能力和鉴权参数已收起。</div>}
|
||||
<div className="admin-app-form-grid admin-app-protocol-body">
|
||||
<Input
|
||||
hint="留空不推送;HTTP接口开通后按该地址推送状态回执。"
|
||||
label="HTTP 回执地址"
|
||||
onChange={(event) => setReceiptWebhookUrl(event.target.value)}
|
||||
placeholder="https://example.com/webhooks/sms/receipt"
|
||||
value={receiptWebhookUrl}
|
||||
/>
|
||||
<Input
|
||||
hint="留空不推送;HTTP接口开通后按该地址推送上行短信。"
|
||||
label="HTTP 上行地址"
|
||||
onChange={(event) => setUplinkWebhookUrl(event.target.value)}
|
||||
placeholder="https://example.com/webhooks/sms/uplink"
|
||||
value={uplinkWebhookUrl}
|
||||
/>
|
||||
<div className="admin-app-form-row admin-app-form-row--wide">
|
||||
<div className="admin-app-form-tip"><Info size={17} /><span>投递通道由接口开通状态自动决定:CMPP开通则走CMPP,HTTP开通且地址非空则走HTTP,两者都开通时双投;运营端无需另选投递方式。</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="ui-detail-section">
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import type { RechargeOrder, TenantOption } from '@/api/adminApi';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { Button } from './Button';
|
||||
import { Modal } from './Modal';
|
||||
|
||||
type RechargeReceiptDialogProps = {
|
||||
open: boolean;
|
||||
record: RechargeOrder | null;
|
||||
tenant?: Pick<TenantOption, 'name' | 'code'>;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function formatReceiptAmount(moneyUnits: number) {
|
||||
return formatCents(Math.abs(moneyUnits)).replace(/\.?0+$/, '');
|
||||
}
|
||||
|
||||
export function RechargeReceiptDialog({
|
||||
open,
|
||||
record,
|
||||
tenant,
|
||||
onClose,
|
||||
}: RechargeReceiptDialogProps) {
|
||||
if (!record) return null;
|
||||
|
||||
const isCorrection = record.amountCents < 0;
|
||||
const balanceAfter = record.balanceAfterCents;
|
||||
const balanceBefore = balanceAfter === null || balanceAfter === undefined
|
||||
? null
|
||||
: balanceAfter - record.amountCents;
|
||||
const enterpriseName = record.tenant?.name ?? tenant?.name ?? record.tenantId;
|
||||
const enterpriseCode = record.tenant?.code ?? tenant?.code ?? '-';
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={<Button onClick={onClose}>完成</Button>}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
title="账户充值回执"
|
||||
>
|
||||
<article className="recharge-receipt">
|
||||
<header className="recharge-receipt__header">
|
||||
<img
|
||||
alt="聆界短信服务平台"
|
||||
className="recharge-receipt__logo"
|
||||
src="/logo/logo1.png"
|
||||
/>
|
||||
<span className={isCorrection ? 'is-correction' : 'is-posted'}>
|
||||
<CheckCircle2 aria-hidden="true" size={16} />
|
||||
{isCorrection ? '已冲正' : '已入账'}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<section className="recharge-receipt__amount">
|
||||
<span>本次{isCorrection ? '冲正' : '充值'}金额</span>
|
||||
<strong>
|
||||
{isCorrection ? '−' : '+'}
|
||||
<small>¥</small>
|
||||
{formatReceiptAmount(record.amountCents)}
|
||||
</strong>
|
||||
</section>
|
||||
|
||||
<section className="recharge-receipt__enterprise">
|
||||
<span>入账企业</span>
|
||||
<strong>{enterpriseName}</strong>
|
||||
<small>企业编号:{enterpriseCode}</small>
|
||||
</section>
|
||||
|
||||
<dl className="recharge-receipt__details">
|
||||
<div>
|
||||
<dt>回执编号</dt>
|
||||
<dd>{record.orderNo}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>入账时间</dt>
|
||||
<dd>{formatDateTime(record.paidAt ?? record.createdAt)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>入账前余额</dt>
|
||||
<dd>{balanceBefore === null ? '-' : `¥${formatCents(balanceBefore)}`}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>入账后余额</dt>
|
||||
<dd>{balanceAfter === null || balanceAfter === undefined ? '-' : `¥${formatCents(balanceAfter)}`}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>入账方式</dt>
|
||||
<dd>平台运营端 · 人工{isCorrection ? '冲正' : '充值'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>交易状态</dt>
|
||||
<dd>{isCorrection ? '冲正完成' : '充值完成'}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<section className="recharge-receipt__remark">
|
||||
<span>备注</span>
|
||||
<p>{record.remark || '无'}</p>
|
||||
</section>
|
||||
|
||||
<footer className="recharge-receipt__note">
|
||||
本回执由系统根据真实入账记录自动生成
|
||||
</footer>
|
||||
</article>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export { RiskAction } from './RiskAction';
|
||||
export { DeleteRiskAction } from './DeleteRiskAction';
|
||||
export { ManualRechargeDialog } from './ManualRechargeDialog';
|
||||
export type { ManualRechargeTarget } from './ManualRechargeDialog';
|
||||
export { RechargeReceiptDialog } from './RechargeReceiptDialog';
|
||||
export { Input } from './Input';
|
||||
export { Modal } from './Modal';
|
||||
export { InlineTextPreview, Pagination, QueryPanel } from './PagePrimitives';
|
||||
|
||||
+189
-1
@@ -9702,7 +9702,7 @@ h3 {
|
||||
}
|
||||
|
||||
.admin-recharge-table {
|
||||
min-width: 1220px;
|
||||
min-width: 1350px;
|
||||
}
|
||||
|
||||
.admin-recharge-table th {
|
||||
@@ -9726,6 +9726,194 @@ h3 {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.recharge-receipt {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(217, 195, 160, 0.18), transparent 42%),
|
||||
var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.recharge-receipt::before {
|
||||
background: linear-gradient(90deg, var(--color-brand), var(--color-accent-strong));
|
||||
content: '';
|
||||
height: 4px;
|
||||
inset: 0 0 auto;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.recharge-receipt__header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-height: 74px;
|
||||
padding: var(--space-6) var(--space-7) var(--space-4);
|
||||
}
|
||||
|
||||
.recharge-receipt__logo {
|
||||
display: block;
|
||||
height: 38px;
|
||||
max-width: 190px;
|
||||
object-fit: contain;
|
||||
object-position: left center;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.recharge-receipt__header > span {
|
||||
align-items: center;
|
||||
border-radius: var(--radius-full);
|
||||
display: inline-flex;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
gap: var(--space-2);
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.recharge-receipt__header .is-posted {
|
||||
background: var(--color-success-soft);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.recharge-receipt__header .is-correction {
|
||||
background: var(--color-warning-soft);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.recharge-receipt__amount {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
padding: var(--space-5) var(--space-7) var(--space-8);
|
||||
}
|
||||
|
||||
.recharge-receipt__amount > span,
|
||||
.recharge-receipt__enterprise > span,
|
||||
.recharge-receipt__remark > span {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.recharge-receipt__amount strong {
|
||||
color: var(--color-text-strong);
|
||||
font-size: clamp(36px, 7vw, 52px);
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.15;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.recharge-receipt__amount small {
|
||||
font-size: 0.58em;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 var(--space-1);
|
||||
}
|
||||
|
||||
.recharge-receipt__enterprise {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-6) var(--space-7);
|
||||
}
|
||||
|
||||
.recharge-receipt__enterprise strong {
|
||||
color: var(--color-text-strong);
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.recharge-receipt__enterprise small {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.recharge-receipt__details {
|
||||
background: var(--color-bg-subtle);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin: 0;
|
||||
padding: var(--space-2) var(--space-7);
|
||||
}
|
||||
|
||||
.recharge-receipt__details > div {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
min-width: 0;
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.recharge-receipt__details > div:nth-child(even) {
|
||||
padding-left: var(--space-6);
|
||||
}
|
||||
|
||||
.recharge-receipt__details dt {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.recharge-receipt__details dd {
|
||||
color: var(--color-text-strong);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.recharge-receipt__remark {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-5) var(--space-7);
|
||||
}
|
||||
|
||||
.recharge-receipt__remark p {
|
||||
color: var(--color-text-strong);
|
||||
line-height: var(--line-height-loose);
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.recharge-receipt__note {
|
||||
background: var(--color-bg-subtle);
|
||||
border-top: 1px dashed var(--color-border-strong);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
padding: var(--space-4) var(--space-7);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.recharge-receipt__header,
|
||||
.recharge-receipt__enterprise,
|
||||
.recharge-receipt__remark {
|
||||
padding-left: var(--space-5);
|
||||
padding-right: var(--space-5);
|
||||
}
|
||||
|
||||
.recharge-receipt__logo {
|
||||
height: 32px;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.recharge-receipt__details {
|
||||
grid-template-columns: 1fr;
|
||||
padding-left: var(--space-5);
|
||||
padding-right: var(--space-5);
|
||||
}
|
||||
|
||||
.recharge-receipt__details > div {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.recharge-receipt__details > div:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.recharge-receipt__details > div:nth-child(even) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.manual-recharge-review,
|
||||
.manual-recharge-result {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user