This commit is contained in:
2026-02-01 19:22:54 +08:00
parent 85f742ad2b
commit e42fce5bd1
4 changed files with 62 additions and 81 deletions

View File

@@ -16,19 +16,14 @@ interface DatePickerProps {
}
export function DatePicker({ className, value, onChange }: DatePickerProps) {
const [date, setDate] = React.useState<Date | undefined>(
value ? new Date(typeof value === 'string' ? value : value.toISOString()) : undefined
)
const toDate = (val: string | Dayjs | undefined): Date | undefined => {
if (!val) return undefined
return new Date(typeof val === 'string' ? val : val.toISOString())
}
React.useEffect(() => {
if (value) {
const dateValue = typeof value === 'string' ? value : value.toISOString()
setDate(new Date(dateValue))
}
}, [value])
const date = toDate(value)
const handleSelect = (selectedDate: Date | undefined) => {
setDate(selectedDate)
if (selectedDate && onChange) {
onChange(dayjs(selectedDate))
}

View File

@@ -22,6 +22,24 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip"
const LabelWithTooltip = ({ label, tips }: { label: string; tips?: string }) => (
<div className="flex items-center gap-2">
<label className="text-sm font-medium">{label}</label>
{tips && (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-4 w-4 p-0">
<HelpCircle size={16} />
</Button>
</TooltipTrigger>
<TooltipContent className="whitespace-pre-wrap">
<p>{tips}</p>
</TooltipContent>
</Tooltip>
)}
</div>
);
export const KeyShareSelect = ({ value, onChange }: { value: string; onChange?: (value: string) => void }) => {
return (
<Select value={value || ''} onValueChange={(val) => onChange?.(val)}>
@@ -87,50 +105,22 @@ export const PermissionManager = ({ value, onChange, className }: PermissionMana
}
};
const tips = getTips('share');
const shareTips = getTips('share');
return (
<TooltipProvider>
<form className={clsx('flex flex-col w-full gap-4', className)}>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<label className="text-sm font-medium"></label>
{tips && (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-4 w-4 p-0">
<HelpCircle size={16} />
</Button>
</TooltipTrigger>
<TooltipContent className="whitespace-pre-wrap">
<p>{tips}</p>
</TooltipContent>
</Tooltip>
)}
</div>
<LabelWithTooltip label="共享" tips={shareTips} />
<KeyShareSelect value={formData?.share} onChange={(value) => onChangeValue('share', value)} />
</div>
{keys.map((item: any) => {
const tips = getTips(item);
const itemTips = getTips(item);
return (
<div key={item} className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">{item}</label>
{tips && (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-4 w-4 p-0">
<HelpCircle size={16} />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{tips}</p>
</TooltipContent>
</Tooltip>
)}
</div>
<LabelWithTooltip label={item} tips={itemTips} />
{item === 'expiration-time' && (
<DatePicker value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />
)}

View File

@@ -16,13 +16,16 @@ export function TagsInput({ value, onChange, placeholder = "输入用户名,
const [inputValue, setInputValue] = React.useState("")
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === ",") {
const trimmed = inputValue.trim()
if ((e.key === "Enter" || e.key === ",") && trimmed) {
e.preventDefault()
const newValue = inputValue.trim()
if (newValue && !value.includes(newValue)) {
onChange([...value, newValue])
if (!value.includes(trimmed)) {
onChange([...value, trimmed])
setInputValue("")
} else {
setInputValue("")
}
setInputValue("")
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
onChange(value.slice(0, -1))
}
@@ -34,9 +37,9 @@ export function TagsInput({ value, onChange, placeholder = "输入用户名,
return (
<div className={cn("flex flex-wrap gap-2 w-full", className)}>
{value.map((tag, index) => (
{value.map((tag) => (
<div
key={`${tag}-${index}`}
key={tag}
className="flex items-center gap-1 px-3 py-1 text-sm bg-secondary text-secondary-foreground rounded-md border border-border"
>
<span>{tag}</span>
@@ -44,6 +47,7 @@ export function TagsInput({ value, onChange, placeholder = "输入用户名,
type="button"
onClick={() => removeTag(tag)}
className="flex items-center justify-center w-4 h-4 rounded hover:bg-muted transition-colors"
aria-label={`移除 ${tag}`}
>
<X className="w-3 h-3" />
</button>

View File

@@ -1,16 +1,7 @@
'use client';
import dayjs from 'dayjs';
export const getTips = (key: string, lang?: string) => {
const tip = keysTips.find((item) => item.key === key);
if (tip) {
if (lang === 'en') {
return tip.enTips;
}
return tip.tips;
}
return '';
};
export const keysTips = [
{
key: 'share',
@@ -79,31 +70,32 @@ export const keysTips = [
},
},
];
// 创建缓存Map以提升查找性能
const tipsMap = new Map(keysTips.map(tip => [tip.key, tip]));
export const getTips = (key: string, lang?: string) => {
const tip = tipsMap.get(key);
if (tip) {
return lang === 'en' ? tip.enTips : tip.tips;
}
return '';
};
export class KeyParse {
static parse(metadata: Record<string, any>) {
const keys = Object.keys(metadata);
const newMetadata = {};
keys.forEach((key) => {
const tip = keysTips.find((item) => item.key === key);
if (tip && tip.parse) {
newMetadata[key] = tip.parse(metadata[key]);
} else {
newMetadata[key] = metadata[key];
}
});
return newMetadata;
return Object.entries(metadata).reduce((acc, [key, value]) => {
const tip = tipsMap.get(key);
acc[key] = tip?.parse ? tip.parse(value) : value;
return acc;
}, {} as Record<string, any>);
}
static stringify(metadata: Record<string, any>) {
const keys = Object.keys(metadata);
const newMetadata = {};
keys.forEach((key) => {
const tip = keysTips.find((item) => item.key === key);
if (tip && tip.stringify) {
newMetadata[key] = tip.stringify(metadata[key]);
} else {
newMetadata[key] = metadata[key];
}
});
return newMetadata;
return Object.entries(metadata).reduce((acc, [key, value]) => {
const tip = tipsMap.get(key);
acc[key] = tip?.stringify ? tip.stringify(value) : value;
return acc;
}, {} as Record<string, any>);
}
}