fix: complete first version issue remediation
This commit is contained in:
@@ -50,7 +50,6 @@ function mapParams(params: ApplicationCmppParams): ParamRow[] {
|
||||
{ label: '接入号', value: params.srcId || '-' },
|
||||
{ label: '连接数', value: String(params.maxConnections || '-') },
|
||||
{ label: '心跳间隔', value: `${params.heartbeatSeconds} 秒` },
|
||||
{ label: '窗口大小', value: String(params.windowSize) },
|
||||
{ label: '协议版本', value: params.protocolVersion },
|
||||
];
|
||||
}
|
||||
@@ -169,6 +168,8 @@ export function ClientApplicationsPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={applications.length}
|
||||
/>
|
||||
|
||||
@@ -298,6 +298,8 @@ export function ClientBatchTasksPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={filteredTasks.length}
|
||||
/>
|
||||
|
||||
@@ -64,6 +64,8 @@ export function ClientBillingPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={plans.length}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { Button, FileActions, Input, Select, Textarea } from '@/components/ui';
|
||||
import { clientApi, type EnterpriseCertification, type FileObject, type FileRef } from '@/api/adminApi';
|
||||
import { displayFileName } from '@/utils/fileName';
|
||||
|
||||
type AuthStep = 'overview' | 'profile' | 'method' | 'recharge' | 'face' | 'faceScan' | 'pending' | 'success' | 'failed';
|
||||
type AuthMethod = 'face' | 'recharge';
|
||||
@@ -59,7 +60,7 @@ function UploadPanel({ file, uploading, onFile }: { file: FileObject | null; upl
|
||||
return (
|
||||
<label className="enterprise-upload">
|
||||
<Upload size={38} />
|
||||
<strong>{uploading ? '上传中...' : file?.fileName ?? '点击上传'}</strong>
|
||||
<strong>{uploading ? '上传中...' : file ? displayFileName(file.fileName) : '点击上传'}</strong>
|
||||
<FileActions file={fileRef} />
|
||||
<input
|
||||
accept="image/png,image/jpeg,image/webp,application/pdf"
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
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';
|
||||
|
||||
type RecentTaskRow = {
|
||||
id: string;
|
||||
@@ -59,7 +60,7 @@ export function ClientHome() {
|
||||
scene: String(task.category ?? task.content ?? '短信发送'),
|
||||
count: Number(task.phoneTotal ?? task.progressTotal ?? 0),
|
||||
channel: Array.isArray(task.messages) && task.messages[0]?.channel?.name ? String(task.messages[0].channel.name) : '待路由',
|
||||
createdAt: task.createdAt ? new Date(String(task.createdAt)).toLocaleString('zh-CN') : '',
|
||||
createdAt: formatDateTime(task.createdAt ? String(task.createdAt) : null),
|
||||
status: String(task.status ?? 'unknown'),
|
||||
})), [dashboard]);
|
||||
const latestRecharge = dashboard?.recentRecharges[0];
|
||||
|
||||
@@ -229,6 +229,8 @@ export function ClientSendDetailPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={filteredRows.length}
|
||||
/>
|
||||
|
||||
@@ -157,6 +157,8 @@ export function ClientSignaturesPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={filteredSignatures.length}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type TableColumn,
|
||||
} from '@/components/ui';
|
||||
import { clientApi, type OperationLogItem } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
|
||||
type LogLevel = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
@@ -61,7 +62,7 @@ export function ClientSystemLogsPage() {
|
||||
const currentPage = Math.min(page, totalPages);
|
||||
|
||||
const columns = useMemo<Array<TableColumn<OperationLogItem>>>(() => [
|
||||
{ key: 'time', title: '时间', width: '190px', render: (record) => <span className="muted">{new Date(record.time).toLocaleString('zh-CN')}</span> },
|
||||
{ key: 'time', title: '时间', width: '190px', render: (record) => <span className="muted">{formatDateTime(record.time)}</span> },
|
||||
{ key: 'level', title: '级别', width: '120px', render: (record) => <Tag tone={levelToneMap[record.level]}>{levelLabelMap[record.level]}</Tag> },
|
||||
{ key: 'module', title: '模块', width: '150px', render: (record) => <strong>{record.module}</strong> },
|
||||
{ key: 'operator', title: '操作人', width: '130px', render: (record) => <strong>{record.operator}</strong> },
|
||||
@@ -127,6 +128,8 @@ export function ClientSystemLogsPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={total}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { Edit3, MessageSquare, Plus, Search, Trash2 } from 'lucide-react';
|
||||
import { Button, Input, Modal, Pagination, Select, Tag, Textarea } from '@/components/ui';
|
||||
import { clientApi, type ClientSmsApplication, type ClientSmsSignature, type ClientSmsTemplate } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
|
||||
type TemplateVariable = {
|
||||
name: string;
|
||||
@@ -276,7 +277,7 @@ export function ClientTemplatesPage() {
|
||||
{variables.length > 0 ? variables.map((item) => <strong key={item}>${`{${item}}`}</strong>) : <span className="muted">无变量</span>}
|
||||
</div>
|
||||
<div className="template-card-footer">
|
||||
<span>{new Date(template.updatedAt).toLocaleString('zh-CN')}</span>
|
||||
<span>{formatDateTime(template.updatedAt)}</span>
|
||||
<div>
|
||||
<button onClick={() => setModalTemplate(template)} type="button">
|
||||
<Edit3 size={14} />
|
||||
@@ -297,6 +298,8 @@ export function ClientTemplatesPage() {
|
||||
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
|
||||
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
|
||||
page={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
previousDisabled={currentPage <= 1}
|
||||
total={filteredTemplates.length}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Edit3, KeyRound, Plus, Search, Trash2, Users } from 'lucide-react';
|
||||
import { clientApi, type ManagedUser, type UserPayload } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { readSession } from '@/api/session';
|
||||
import { Button, Input, Modal, Select, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
|
||||
@@ -50,6 +51,7 @@ export function ClientUsersPage() {
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmAction, setConfirmAction] = useState<ConfirmAction | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
async function load() {
|
||||
if (!tenantId) return;
|
||||
@@ -79,6 +81,11 @@ export function ClientUsersPage() {
|
||||
}
|
||||
|
||||
async function saveUser() {
|
||||
if (!form.displayName.trim() || (!form.email.trim() && !form.phone.trim()) || (creating && form.password.length < 6)) {
|
||||
setError('请填写姓名、邮箱或手机号;新增用户密码至少 6 位');
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
const body: UserPayload = {
|
||||
displayName: form.displayName,
|
||||
username: form.username || form.email || form.phone,
|
||||
@@ -88,14 +95,20 @@ export function ClientUsersPage() {
|
||||
roleCode: 'enterprise_admin',
|
||||
operatorId: session?.user.id,
|
||||
};
|
||||
if (creating) {
|
||||
await clientApi.createUser({ ...body, password: form.password }, tenantId);
|
||||
} else if (editingUser) {
|
||||
await clientApi.updateUser(editingUser.id, body, tenantId);
|
||||
try {
|
||||
if (creating) {
|
||||
await clientApi.createUser({ ...body, password: form.password }, tenantId);
|
||||
} else if (editingUser) {
|
||||
await clientApi.updateUser(editingUser.id, body, tenantId);
|
||||
}
|
||||
setCreating(false);
|
||||
setEditingUser(null);
|
||||
await load();
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '用户保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
setCreating(false);
|
||||
setEditingUser(null);
|
||||
await load();
|
||||
}
|
||||
|
||||
async function runConfirm() {
|
||||
@@ -122,7 +135,7 @@ export function ClientUsersPage() {
|
||||
{ key: 'phone', title: '手机号', width: '160px', render: (record) => <span className="muted">{record.phone ?? '-'}</span> },
|
||||
{ key: 'role', title: '角色', width: '130px', render: () => <Tag tone="info">企业管理员</Tag> },
|
||||
{ key: 'status', title: '状态', width: '120px', render: (record) => <Tag tone={record.status === 'active' ? 'success' : 'neutral'}>{record.status === 'active' ? '正常' : '禁用'}</Tag> },
|
||||
{ key: 'lastLoginAt', title: '最后登录时间', width: '190px', render: (record) => <span className="muted">{record.lastLoginAt ? new Date(record.lastLoginAt).toLocaleString('zh-CN') : '-'}</span> },
|
||||
{ key: 'lastLoginAt', title: '最后登录时间', width: '190px', render: (record) => <span className="muted">{formatDateTime(record.lastLoginAt)}</span> },
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
@@ -131,7 +144,7 @@ export function ClientUsersPage() {
|
||||
<div className="inline-actions">
|
||||
<Button icon={<Edit3 size={15} />} onClick={() => openEditor(record)} size="sm" variant="ghost">编辑</Button>
|
||||
<Button icon={<KeyRound size={15} />} onClick={() => { setPasswordUser(record); setNewPassword(''); }} size="sm" variant="ghost">改密</Button>
|
||||
<Button onClick={() => setConfirmAction({ type: 'status', user: record })} size="sm" variant="ghost">{record.status === 'active' ? '禁用' : '启用'}</Button>
|
||||
<Button onClick={() => setConfirmAction({ type: 'status', user: record })} size="sm" variant={record.status === 'active' ? 'warning' : 'success'}>{record.status === 'active' ? '禁用' : '启用'}</Button>
|
||||
<Button icon={<Trash2 size={15} />} onClick={() => setConfirmAction({ type: 'delete', user: record })} size="sm" variant="danger">删除</Button>
|
||||
</div>
|
||||
),
|
||||
@@ -158,7 +171,7 @@ export function ClientUsersPage() {
|
||||
|
||||
{(creating || editingUser) ? (
|
||||
<Modal
|
||||
footer={<><Button onClick={() => { setCreating(false); setEditingUser(null); }} variant="secondary">取消</Button><Button onClick={() => void saveUser()}>保存</Button></>}
|
||||
footer={<><Button disabled={saving} onClick={() => { setCreating(false); setEditingUser(null); }} variant="secondary">取消</Button><Button disabled={saving} onClick={() => void saveUser()}>{saving ? '保存中...' : '保存'}</Button></>}
|
||||
onClose={() => { setCreating(false); setEditingUser(null); }}
|
||||
open
|
||||
size="xl"
|
||||
|
||||
Reference in New Issue
Block a user