first commit
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

This commit is contained in:
2026-01-30 16:57:44 +08:00
commit 3d175d75af
119 changed files with 35834 additions and 0 deletions

435
app/random-string/page.tsx Normal file
View File

@@ -0,0 +1,435 @@
"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>
);
}