mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-11-01 19:22:46 +08:00
refactor: change client manager structure [重构:更改客户端管理器结构适配影子客户端] feat: add proxy config table and dao [添加代理配置独立数据表和DAO层] feat: new proxy config entity [新建的代理配置实体] feat: update and delete proxy config [更新和删除代理配置接口] feat: get config api and beautify proxy item [更新获取配置API并美化代理项] feat: change proxy form style [美化修改代理的表单样式] fix: client edit [修复:编辑客户端的问题] fix: shadow copy status error [修复:影子客户端复制状态错误] fix: http proxy bug [修复:HTTP代理类型的错误] fix: cannot update client [修复:无法更新客户端] feat: record trigger refetch [自动重新获取表格内的数据] fix: filter string length [修复:过滤字符串长度] fix: add client error [修复:添加客户端错误] fix: do not notify client when stopped [修复:停止时不通知客户端] fix: delete when proxy duplicate [修复:代理重复时删除] feat: add http proxy location [添加HTTP代理路由路径] chore: edit style [编辑样式美化] fix: remove expired client [修复:自动移除过期客户端] feat: proxy status [新增代理状态提示] fix: build [修复:构建] fix: refetch trigger [修复:重新获取数据的问题] fix: remove all expired client [修复:移除所有过期客户端] feat: i18n for proxy [代理页面的国际化翻译]
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { Badge } from '../ui/badge';
|
||
import { Input } from '../ui/input';
|
||
import { Button } from '../ui/button';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
interface StringListInputProps {
|
||
value: string[];
|
||
onChange: React.Dispatch<React.SetStateAction<string[]>>;
|
||
placeholder?: string;
|
||
}
|
||
|
||
const StringListInput: React.FC<StringListInputProps> = ({ value, onChange, placeholder }) => {
|
||
const { t } = useTranslation();
|
||
const [inputValue, setInputValue] = useState('');
|
||
|
||
const handleAdd = () => {
|
||
if (inputValue.trim()) {
|
||
if (value && value.includes(inputValue)) {
|
||
return;
|
||
}
|
||
|
||
if (value) {
|
||
onChange([...value, inputValue]);
|
||
} else {
|
||
onChange([inputValue]);
|
||
}
|
||
setInputValue('');
|
||
}
|
||
};
|
||
|
||
const handleRemove = (itemToRemove: string) => {
|
||
onChange(value.filter(item => item !== itemToRemove));
|
||
};
|
||
|
||
return (
|
||
<div className="mx-auto">
|
||
<div className="flex items-center mb-4">
|
||
<Input
|
||
type="text"
|
||
value={inputValue}
|
||
onChange={(e) => setInputValue(e.target.value)}
|
||
className="flex-1 px-4 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
|
||
placeholder={placeholder || t('input.list.placeholder')}
|
||
/>
|
||
<Button
|
||
disabled={!inputValue || value && value.includes(inputValue)}
|
||
onClick={handleAdd}
|
||
className="ml-2 px-4 py-2"
|
||
>
|
||
{t('input.list.add')}
|
||
</Button>
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
{value && value.map((item, index) => (
|
||
<Badge key={index} className='flex flex-row items-center justify-start'>{item}
|
||
<div
|
||
onClick={() => handleRemove(item)}
|
||
className="ml-1 h-4 w-4 text-center rounded-full hover:text-red-500 cursor-pointer"
|
||
>
|
||
×
|
||
</div>
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default StringListInput; |