fix: enforce application limits and signature format
This commit is contained in:
@@ -4,6 +4,7 @@ import { adminApi, type ApplicationReportField, type ClientSmsApplication, type
|
||||
import { Breadcrumb, Button, DeleteRiskAction, FileActions, Input, Modal, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui';
|
||||
import { displayFileName } from '@/utils/fileName';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { isCompleteSmsSignature } from '@/utils/smsSignature';
|
||||
|
||||
type CarrierStatus = 'approved' | 'pending' | 'rejected' | 'filing';
|
||||
|
||||
@@ -319,7 +320,7 @@ function SignatureFormModal({
|
||||
footer={(
|
||||
<>
|
||||
<Button onClick={onClose} variant="ghost">取消</Button>
|
||||
<Button disabled={!form.tenantId || !form.name || hasMissingRequiredReportValue(reportFields, form.reportValues)} onClick={() => onSubmit(form)}>保存</Button>
|
||||
<Button disabled={!form.tenantId || !isCompleteSmsSignature(form.name) || hasMissingRequiredReportValue(reportFields, form.reportValues)} onClick={() => onSubmit(form)}>保存</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={onClose}
|
||||
@@ -337,7 +338,7 @@ function SignatureFormModal({
|
||||
<h3>基本信息</h3>
|
||||
<div className="signature-alert">
|
||||
<Info size={18} />
|
||||
<span>签名需履行报备,并遵照管理部门审核结果方可使用。请用 PNG、JPG、JPEG 或 PDF 格式上传真实材料。</span>
|
||||
<span>签名名称必须包含完整中文黑括号,例如:【某某科技】。签名需履行报备,并遵照管理部门审核结果方可使用。</span>
|
||||
</div>
|
||||
<div className="signature-form-grid">
|
||||
<Select
|
||||
@@ -360,7 +361,15 @@ function SignatureFormModal({
|
||||
]}
|
||||
value={form.applicationId}
|
||||
/>
|
||||
<Input label="* 短信签名" onChange={(event) => update('name', event.target.value)} placeholder="请输入短信签名,如【XXXX公司】" required value={form.name} />
|
||||
<Input
|
||||
error={form.name.trim() && !isCompleteSmsSignature(form.name) ? '必须填写完整中文黑括号签名,例如:【某某科技】' : undefined}
|
||||
hint="新增和编辑时都必须保留完整的【】"
|
||||
label="短信签名"
|
||||
onChange={(event) => update('name', event.target.value)}
|
||||
placeholder="请输入完整签名,例如:【某某科技】"
|
||||
required
|
||||
value={form.name}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Check, Download, FileText, Plus, Search, Send, Trash2 } from 'lucide-re
|
||||
import { Button, DateTimeInput, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
|
||||
import { clientApi, type ClientSmsApplication, type ClientSmsSignatureView, type ClientSmsTemplate, type ImportPreviewResponse, type SmsBatchTask } from '@/api/adminApi';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
import { replaceLeadingSmsSignature } from '@/utils/smsSignature';
|
||||
|
||||
type Recipient = {
|
||||
id: string;
|
||||
@@ -71,7 +72,7 @@ export function ClientSendPage() {
|
||||
const importedValidCount = importPreview?.validCount ?? 0;
|
||||
const receiverCount = receiverMode === 'manual' ? validRecipients.length : importedValidCount;
|
||||
const previewText = selectedSignature && messageContent
|
||||
? `【${selectedSignature.name}】${messageContent}`
|
||||
? replaceLeadingSmsSignature(messageContent, selectedSignature.name)
|
||||
: messageContent;
|
||||
const wordCount = previewText.length;
|
||||
const smsParts = wordCount > 0 ? Math.max(1, Math.ceil(wordCount / 70)) : 0;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type ClientSmsSignatureView,
|
||||
type FileRef,
|
||||
} from '@/api/adminApi';
|
||||
import { isCompleteSmsSignature } from '@/utils/smsSignature';
|
||||
|
||||
const EMPTY_WORKSPACE: ClientSignatureWorkspace = {
|
||||
items: [],
|
||||
@@ -120,6 +121,10 @@ function SignatureModal({
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!isCompleteSmsSignature(name)) {
|
||||
setError('短信签名必须包含完整中文黑括号,例如:【某某科技】');
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
setError('');
|
||||
try {
|
||||
@@ -138,8 +143,9 @@ function SignatureModal({
|
||||
}
|
||||
|
||||
const missingRequired = fields.some((field) => field.required && !values[field.code]);
|
||||
const signatureNameValid = isCompleteSmsSignature(name);
|
||||
return <Modal
|
||||
footer={<><Button onClick={onClose} variant="ghost">取消</Button><Button disabled={!name.trim() || missingRequired || saving || Boolean(uploadingCode)} onClick={() => void save()}>{saving ? '提交中...' : '提交审核'}</Button></>}
|
||||
footer={<><Button onClick={onClose} variant="ghost">取消</Button><Button disabled={!signatureNameValid || missingRequired || saving || Boolean(uploadingCode)} onClick={() => void save()}>{saving ? '提交中...' : '提交审核'}</Button></>}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
@@ -153,7 +159,15 @@ function SignatureModal({
|
||||
options={[{ label: '不绑定应用', value: '' }, ...applications.map((item) => ({ label: item.name, value: item.id }))]}
|
||||
value={applicationId}
|
||||
/>
|
||||
<Input label="短信签名" onChange={(event) => setName(event.target.value)} placeholder="例如:某某科技(无需填写【】)" required value={name} />
|
||||
<Input
|
||||
error={name.trim() && !signatureNameValid ? '必须填写完整中文黑括号签名,例如:【某某科技】' : undefined}
|
||||
hint="新增和编辑时都必须保留完整的【】"
|
||||
label="短信签名"
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
placeholder="请输入完整签名,例如:【某某科技】"
|
||||
required
|
||||
value={name}
|
||||
/>
|
||||
<Input label="使用场景" onChange={(event) => setPurpose(event.target.value)} placeholder="例如:验证码、订单通知" value={purpose ?? ''} />
|
||||
<section className="client-signature-form-section">
|
||||
<div><h3>审核资料</h3><p>请按要求填写或上传,资料仅用于签名审核。</p></div>
|
||||
|
||||
@@ -6,6 +6,7 @@ const capabilityLabels = [
|
||||
['uplinkQueryEnabled', '上行查询'],
|
||||
['receiptWebhookEnabled', '回执回调'],
|
||||
['uplinkWebhookEnabled', '上行回调'],
|
||||
['credentialSelfServiceEnabled', '客户端自助密钥'],
|
||||
] as const;
|
||||
|
||||
const deliveryModeLabels: Record<string, string> = {
|
||||
@@ -20,6 +21,7 @@ export function formatHttpApiParams(response: HttpApiConfigResponse, origin: str
|
||||
const baseUrl = `${origin.replace(/\/$/, '')}/api/openapi/v1`;
|
||||
return [
|
||||
`应用名称: ${response.applicationName ?? response.applicationId}`,
|
||||
`AppID: ${response.applicationId}`,
|
||||
`HTTP接口: ${config?.enabled ? '开通' : '关闭'}`,
|
||||
`基础地址: ${baseUrl}`,
|
||||
`接口文档: ${origin.replace(/\/$/, '')}/api/client-docs`,
|
||||
|
||||
@@ -5,6 +5,12 @@ export function formatSmsSignature(name?: string | null) {
|
||||
return innerName ? `【${innerName}】` : '';
|
||||
}
|
||||
|
||||
export function isCompleteSmsSignature(name?: string | null) {
|
||||
const value = (name ?? '').trim();
|
||||
const match = value.match(/^【([^【】]+)】$/);
|
||||
return Boolean(match && match[1] === match[1].trim());
|
||||
}
|
||||
|
||||
export function replaceLeadingSmsSignature(content: string, signatureName?: string | null) {
|
||||
const body = content.replace(LEADING_SMS_SIGNATURE, '');
|
||||
return `${formatSmsSignature(signatureName)}${body}`;
|
||||
|
||||
Reference in New Issue
Block a user