Files
i-tools/app/random-string/page.tsx
yfan 3d175d75af
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Sync to CNB / sync (push) Has been cancelled
Delete old workflow runs / del_runs (push) Has been cancelled
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled
first commit
2026-01-30 16:57:44 +08:00

436 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useCallback } from "react";
import { Settings, RefreshCw, Copy, Shield, Zap } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "sonner";
interface StringConfig {
length: number;
includeUppercase: boolean;
includeLowercase: boolean;
includeNumbers: boolean;
includeSymbols: boolean;
excludeSimilar: boolean;
customChars: string;
batchCount: number;
}
const defaultConfig: StringConfig = {
length: 16,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSymbols: false,
excludeSimilar: false,
customChars: "",
batchCount: 1,
};
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
const NUMBERS = "0123456789";
const SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
const SIMILAR_CHARS = "0O1lI|";
export default function RandomStringGenerator() {
const [config, setConfig] = useState<StringConfig>(defaultConfig);
const [generatedStrings, setGeneratedStrings] = useState<string[]>([]);
const [currentString, setCurrentString] = useState("");
const handleConfigChange = (field: keyof StringConfig, value: any) => {
setConfig((prev) => ({ ...prev, [field]: value }));
};
const getCharacterSet = useCallback(() => {
let chars = "";
if (config.customChars) {
chars = config.customChars;
} else {
if (config.includeUppercase) chars += UPPERCASE;
if (config.includeLowercase) chars += LOWERCASE;
if (config.includeNumbers) chars += NUMBERS;
if (config.includeSymbols) chars += SYMBOLS;
}
if (config.excludeSimilar && !config.customChars) {
chars = chars
.split("")
.filter((char) => !SIMILAR_CHARS.includes(char))
.join("");
}
return chars;
}, [config]);
const generateRandomString = useCallback(
(length: number, charset: string) => {
if (!charset) return "";
let result = "";
const charactersLength = charset.length;
for (let i = 0; i < length; i++) {
result += charset.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
},
[]
);
const generateStrings = useCallback(() => {
const charset = getCharacterSet();
if (!charset) {
toast.error("请至少选择一种字符类型");
return;
}
const newStrings = [];
for (let i = 0; i < config.batchCount; i++) {
const randomStr = generateRandomString(config.length, charset);
newStrings.push(randomStr);
}
setGeneratedStrings(newStrings);
setCurrentString(newStrings[0] || "");
toast.success(`成功生成 ${newStrings.length} 个随机字符串`);
}, [config, getCharacterSet, generateRandomString]);
const copyToClipboard = (text: string) => {
navigator.clipboard
.writeText(text)
.then(() => {
toast.success("已复制到剪贴板");
})
.catch(() => {
toast.error("复制失败");
});
};
const copyAllStrings = () => {
const allStrings = generatedStrings.join("\n");
copyToClipboard(allStrings);
};
const getStrengthInfo = () => {
const charset = getCharacterSet();
const entropy = Math.log2(Math.pow(charset.length, config.length));
let strength = "弱";
let color = "bg-red-500 hover:bg-red-600";
if (entropy >= 60) {
strength = "极强";
color = "bg-emerald-500 hover:bg-emerald-600";
} else if (entropy >= 40) {
strength = "强";
color = "bg-cyan-500 hover:bg-cyan-600";
} else if (entropy >= 25) {
strength = "中等";
color = "bg-amber-500 hover:bg-amber-600";
}
return { strength, entropy: entropy.toFixed(1), color };
};
const strengthInfo = getStrengthInfo();
return (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
{/* Page Header */}
<div className="flex items-center space-x-4 border-b pb-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-linear-to-br from-violet-500 to-purple-600 shadow-lg">
<Zap className="h-6 w-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground">
</p>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
{/* Left: Configuration */}
<div className="space-y-6">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base font-medium flex items-center gap-2">
<Settings className="h-4 w-4" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="length"></Label>
<div className="relative">
<Input
id="length"
type="number"
min={1}
max={1000}
value={config.length}
onChange={(e) =>
handleConfigChange("length", parseInt(e.target.value) || 1)
}
className="pr-8"
/>
<span className="absolute right-3 top-2.5 text-xs text-muted-foreground"></span>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="batchCount"></Label>
<div className="relative">
<Input
id="batchCount"
type="number"
min={1}
max={100}
value={config.batchCount}
onChange={(e) =>
handleConfigChange("batchCount", parseInt(e.target.value) || 1)
}
className="pr-8"
/>
<span className="absolute right-3 top-2.5 text-xs text-muted-foreground"></span>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base font-medium"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center space-x-2">
<Checkbox
id="uppercase"
checked={config.includeUppercase}
onCheckedChange={(checked) =>
handleConfigChange("includeUppercase", checked)
}
/>
<Label htmlFor="uppercase"> (A-Z)</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="lowercase"
checked={config.includeLowercase}
onCheckedChange={(checked) =>
handleConfigChange("includeLowercase", checked)
}
/>
<Label htmlFor="lowercase"> (a-z)</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="numbers"
checked={config.includeNumbers}
onCheckedChange={(checked) =>
handleConfigChange("includeNumbers", checked)
}
/>
<Label htmlFor="numbers"> (0-9)</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="symbols"
checked={config.includeSymbols}
onCheckedChange={(checked) =>
handleConfigChange("includeSymbols", checked)
}
/>
<Label htmlFor="symbols"></Label>
</div>
<div className="flex items-center space-x-2 col-span-2">
<Checkbox
id="excludeSimilar"
checked={config.excludeSimilar}
onCheckedChange={(checked) =>
handleConfigChange("excludeSimilar", checked)
}
/>
<Label htmlFor="excludeSimilar"> (0O1lI|)</Label>
</div>
</div>
<Separator />
<div className="space-y-2">
<Label htmlFor="customChars"></Label>
<Input
id="customChars"
value={config.customChars}
onChange={(e) =>
handleConfigChange("customChars", e.target.value)
}
placeholder="输入自定义字符集(将覆盖上述选择)"
/>
</div>
</CardContent>
</Card>
</div>
{/* Right: Generate & Result */}
<div className="space-y-6">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base font-medium flex items-center gap-2">
<Shield className="h-4 w-4" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-3 gap-4 text-center">
<div className="space-y-1">
<div className="text-xs text-muted-foreground"></div>
<div className="text-xl font-bold">{getCharacterSet().length}</div>
</div>
<div className="space-y-1">
<div className="text-xs text-muted-foreground"></div>
<div className="text-xl font-bold">{strengthInfo.entropy}</div>
</div>
<div className="space-y-1">
<div className="text-xs text-muted-foreground"></div>
<Badge className={strengthInfo.color}>{strengthInfo.strength}</Badge>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base font-medium"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Button
onClick={generateStrings}
className="w-full h-12 text-lg gap-2"
>
<RefreshCw className="h-5 w-5" />
</Button>
{generatedStrings.length > 0 && (
<div className="grid grid-cols-2 gap-4">
<Button
variant="outline"
onClick={() => copyToClipboard(currentString)}
className="gap-2"
>
<Copy className="h-4 w-4" />
</Button>
<Button
variant="outline"
onClick={copyAllStrings}
className="gap-2"
>
<Copy className="h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
{generatedStrings.length > 0 && (
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base font-medium"></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{config.batchCount === 1 ? (
<div className="relative">
<Input
value={currentString}
readOnly
className="font-mono text-lg pr-12"
/>
<Button
variant="ghost"
size="icon"
className="absolute right-1 top-1 h-8 w-8"
onClick={() => copyToClipboard(currentString)}
>
<Copy className="h-4 w-4" />
</Button>
</div>
) : (
<Textarea
value={generatedStrings.join("\n")}
readOnly
className="font-mono min-h-50"
/>
)}
<div className="text-xs text-muted-foreground text-right">
: {new Date().toLocaleTimeString()}
</div>
</div>
</CardContent>
</Card>
)}
</div>
</div>
{/* Info Card */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<span className="text-xl">💡</span> 使
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-3">
<div className="space-y-2">
<h4 className="font-semibold text-sm"></h4>
<ul className="list-disc pl-4 text-sm text-muted-foreground space-y-1">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div className="space-y-2">
<h4 className="font-semibold text-sm"></h4>
<ul className="list-disc pl-4 text-sm text-muted-foreground space-y-1">
<li>12</li>
<li>使16</li>
<li></li>
<li></li>
</ul>
</div>
<div className="space-y-2">
<h4 className="font-semibold text-sm"></h4>
<ul className="list-disc pl-4 text-sm text-muted-foreground space-y-1">
<li></li>
<li>API密钥</li>
<li></li>
<li></li>
</ul>
</div>
</div>
<div className="mt-4 p-3 bg-muted rounded-md text-xs text-muted-foreground">
💡
</div>
</CardContent>
</Card>
</div>
);
}