import { useEffect, useMemo, useState } from 'react'; import { Search } from 'lucide-react'; import { AppModal } from '@/components/ui/app-modal'; import { PrimaryButton, ResourceField, ResourceTextarea, SecondaryButton, } from '@/features/shared/components/resource-primitives'; import { cn } from '@/lib/utils/cn'; import { normalizeItems } from './helpers'; import type { CountryOption, RuleListType, RuleDimension, RuleModalState } from './types'; export function RuleEntryModal({ state, countryOptions, pending, onClose, onChange, onSubmit, }: { state: RuleModalState; countryOptions: CountryOption[]; pending: boolean; onClose: () => void; onChange: (patch: Partial) => void; onSubmit: () => void; }) { const [keyword, setKeyword] = useState(''); useEffect(() => { if (!state.open) { return; } setKeyword(''); }, [state.dimension, state.open]); const selectedCountrySet = useMemo( () => new Set(state.countryValues), [state.countryValues], ); const filteredCountries = useMemo(() => { const normalized = keyword.trim().toLowerCase(); return countryOptions .filter((option) => !normalized || option.searchText.includes(normalized)) .sort((left, right) => { const leftSelected = selectedCountrySet.has(left.code) ? 1 : 0; const rightSelected = selectedCountrySet.has(right.code) ? 1 : 0; return ( rightSelected - leftSelected || left.code.localeCompare(right.code) ); }); }, [countryOptions, keyword, selectedCountrySet]); const toggleCountry = (code: string) => { const values = selectedCountrySet.has(code) ? state.countryValues.filter((item) => item !== code) : normalizeItems([...state.countryValues, code]); onChange({ countryValues: values }); }; const selectFiltered = () => { onChange({ countryValues: normalizeItems([ ...state.countryValues, ...filteredCountries.map((option) => option.code), ]), }); }; const clearCountries = () => onChange({ countryValues: [] }); const typeLabel = state.listType === 'whitelist' ? '白名单' : '黑名单'; const dimensionLabel = state.dimension === 'ip' ? 'IP' : '地域'; return ( 取消 {pending ? '处理中...' : '添加'} } >
{[ { value: 'blacklist', label: '黑名单' }, { value: 'whitelist', label: '白名单' }, ].map((option) => ( ))}
{[ { value: 'ip', label: 'IP' }, { value: 'country', label: '地域' }, ].map((option) => ( ))}
{state.dimension === 'ip' ? ( onChange({ ipValue: event.target.value })} /> ) : (
setKeyword(event.target.value)} placeholder="搜索国家代码或中文名" className="min-w-0 flex-1 bg-transparent text-sm text-[var(--foreground-primary)] outline-none placeholder:text-[var(--foreground-muted)]" />

地域多选

选项显示为「国家代码 国家中文名」。

已选 {state.countryValues.length}
{filteredCountries.map((option) => { const selected = selectedCountrySet.has(option.code); return ( ); })}
)}
); }