feat: improve application access and money precision
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { Database, ListFilter, Plus, RotateCcw, Search, Trash2 } from 'lucide-react';
|
||||
import { Breadcrumb, Button, Input, Modal, Pagination, Select, Table, Tabs, type TableColumn } from '@/components/ui';
|
||||
import { Breadcrumb, Button, Input, Modal, Pagination, Select, Table, Tabs, Tag, type TableColumn } from '@/components/ui';
|
||||
import { adminApi, type DictionaryItem } from '@/api/adminApi';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
|
||||
@@ -18,6 +18,32 @@ type CarrierRule = DictionaryItem & {
|
||||
remark?: string | null;
|
||||
};
|
||||
|
||||
const carrierTone: Record<string, 'success' | 'info' | 'warning' | 'neutral'> = {
|
||||
中国移动: 'success',
|
||||
中国联通: 'info',
|
||||
中国电信: 'warning',
|
||||
};
|
||||
|
||||
type PhoneSegmentSummaryProps = {
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
total: number;
|
||||
};
|
||||
|
||||
function PhoneSegmentSummary({ icon, label, description, total }: PhoneSegmentSummaryProps) {
|
||||
return (
|
||||
<section className="phone-segment-summary" aria-label={label}>
|
||||
<span className="phone-segment-summary__icon">{icon}</span>
|
||||
<div className="phone-segment-summary__value">
|
||||
<span>{label}</span>
|
||||
<strong>{total.toLocaleString('zh-CN')}</strong>
|
||||
</div>
|
||||
<p>{description}</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminPhoneSegmentsPage() {
|
||||
const pageSize = 25;
|
||||
const [segments, setSegments] = useState<PhoneSegment[]>([]);
|
||||
@@ -121,12 +147,12 @@ export function AdminPhoneSegmentsPage() {
|
||||
}
|
||||
|
||||
const columns = useMemo<Array<TableColumn<PhoneSegment>>>(() => [
|
||||
{ key: 'segment', title: '手机号段(手机号码前7位)', width: '230px', render: (record) => <strong>{record.prefix}</strong> },
|
||||
{ key: 'carrier', title: '运营商', width: '150px', render: (record) => record.carrier ?? '-' },
|
||||
{ key: 'province', title: '省份', width: '140px', render: (record) => record.province ?? '-' },
|
||||
{ key: 'city', title: '城市', width: '140px', render: (record) => record.city ?? '-' },
|
||||
{ key: 'createdAt', title: '创建时间', width: '190px', render: (record) => formatDateTime(record.createdAt) },
|
||||
{ key: 'actions', title: '操作', width: '120px', align: 'right', render: (record) => <Button icon={<Trash2 size={14} />} onClick={() => setDeleteTarget(record)} size="sm" variant="danger">删除</Button> },
|
||||
{ key: 'segment', title: '手机号段(前7位)', width: '190px', render: (record) => <strong className="phone-segment-prefix">{record.prefix}</strong> },
|
||||
{ key: 'carrier', title: '运营商', width: '130px', render: (record) => record.carrier ? <Tag tone={carrierTone[record.carrier] ?? 'neutral'}>{record.carrier}</Tag> : '-' },
|
||||
{ key: 'province', title: '省份', width: '110px', render: (record) => record.province ?? '-' },
|
||||
{ key: 'city', title: '城市', width: '110px', render: (record) => record.city ?? '-' },
|
||||
{ key: 'createdAt', title: '创建时间', width: '170px', render: (record) => formatDateTime(record.createdAt) },
|
||||
{ key: 'actions', title: '操作', width: '90px', align: 'right', render: (record) => <Button className="phone-segment-delete" icon={<Trash2 size={14} />} onClick={() => setDeleteTarget(record)} size="sm" variant="ghost">删除</Button> },
|
||||
], []);
|
||||
|
||||
const ruleColumns = useMemo<Array<TableColumn<CarrierRule>>>(() => [
|
||||
@@ -138,7 +164,7 @@ export function AdminPhoneSegmentsPage() {
|
||||
|
||||
const queryPanel = (
|
||||
<div className="phone-segment-query">
|
||||
<Input label="关键词" onChange={(event) => setKeyword(event.target.value)} placeholder={activeTab === 'segments' ? '手机号段、运营商、省份或城市' : '运营商、正则或备注'} prefix={<Search size={16} />} value={keyword} />
|
||||
<Input aria-label="关键词" onChange={(event) => setKeyword(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') query(); }} placeholder={activeTab === 'segments' ? '搜索手机号段、运营商、省份或城市' : '搜索运营商、正则或备注'} prefix={<Search size={16} />} value={keyword} />
|
||||
<div className="phone-segment-query__actions">
|
||||
<Button icon={<Search size={16} />} onClick={query}>查询</Button>
|
||||
<Button icon={<RotateCcw size={16} />} onClick={reset} variant="ghost">重置</Button>
|
||||
@@ -149,9 +175,9 @@ export function AdminPhoneSegmentsPage() {
|
||||
return (
|
||||
<section className="page-stack admin-system-page phone-segment-workbench">
|
||||
<div className="page-heading">
|
||||
<div>
|
||||
<div className="phone-segment-heading">
|
||||
<Breadcrumb items={['系统管理', '手机号段库']} />
|
||||
<h1>手机号段库</h1>
|
||||
<p>维护号码前七位归属,用于运营商识别与路由判断</p>
|
||||
</div>
|
||||
<Button icon={<Plus size={16} />} onClick={() => activeTab === 'segments' ? setCreating(true) : setCreatingRule(true)}>
|
||||
{activeTab === 'segments' ? '新增号段' : '新增规则'}
|
||||
@@ -159,7 +185,7 @@ export function AdminPhoneSegmentsPage() {
|
||||
</div>
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
|
||||
<div className="surface admin-system-table-card">
|
||||
<div className="surface admin-system-table-card phone-segment-panel">
|
||||
<Tabs
|
||||
className="phone-segment-workbench__tabs"
|
||||
onChange={(value) => {
|
||||
@@ -172,12 +198,7 @@ export function AdminPhoneSegmentsPage() {
|
||||
value: 'segments',
|
||||
content: (
|
||||
<div className="phone-segment-tab-content">
|
||||
<div className="phone-segment-overview phone-segment-overview--single" aria-label="手机号段统计">
|
||||
<section>
|
||||
<span><Database size={20} /></span>
|
||||
<div><strong>{segmentTotal.toLocaleString('zh-CN')}</strong><p>已收录手机号段</p></div>
|
||||
</section>
|
||||
</div>
|
||||
<PhoneSegmentSummary description="当前库中可查询的号码前七位记录" icon={<Database size={22} />} label="已收录手机号段" total={segmentTotal} />
|
||||
{queryPanel}
|
||||
<Table columns={columns} data={segments} emptyText={loading ? '加载中...' : '暂无手机号段'} pagination={false} rowKey="id" />
|
||||
<Pagination
|
||||
@@ -198,12 +219,7 @@ export function AdminPhoneSegmentsPage() {
|
||||
value: 'rules',
|
||||
content: (
|
||||
<div className="phone-segment-tab-content">
|
||||
<div className="phone-segment-overview phone-segment-overview--single" aria-label="运营商区分规则统计">
|
||||
<section>
|
||||
<span><ListFilter size={20} /></span>
|
||||
<div><strong>{ruleTotal.toLocaleString('zh-CN')}</strong><p>运营商识别规则</p></div>
|
||||
</section>
|
||||
</div>
|
||||
<PhoneSegmentSummary description="按优先级匹配号码前缀的识别规则" icon={<ListFilter size={22} />} label="运营商识别规则" total={ruleTotal} />
|
||||
{queryPanel}
|
||||
<Table columns={ruleColumns} data={rules} emptyText={loading ? '加载中...' : '暂无运营商区分规则'} pagination={false} rowKey="id" />
|
||||
<Pagination
|
||||
|
||||
Reference in New Issue
Block a user