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) { export function DatePicker({ className, value, onChange }: DatePickerProps) {
const [date, setDate] = React.useState<Date | undefined>( const toDate = (val: string | Dayjs | undefined): Date | undefined => {
value ? new Date(typeof value === 'string' ? value : value.toISOString()) : 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) => { const handleSelect = (selectedDate: Date | undefined) => {
setDate(selectedDate)
if (selectedDate && onChange) { if (selectedDate && onChange) {
onChange(dayjs(selectedDate)) onChange(dayjs(selectedDate))
} }

View File

@@ -22,6 +22,24 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } 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 }) => { export const KeyShareSelect = ({ value, onChange }: { value: string; onChange?: (value: string) => void }) => {
return ( return (
<Select value={value || ''} onValueChange={(val) => onChange?.(val)}> <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 ( return (
<TooltipProvider> <TooltipProvider>
<form className={clsx('flex flex-col w-full gap-4', className)}> <form className={clsx('flex flex-col w-full gap-4', className)}>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <LabelWithTooltip label="共享" tips={shareTips} />
<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>
<KeyShareSelect value={formData?.share} onChange={(value) => onChangeValue('share', value)} /> <KeyShareSelect value={formData?.share} onChange={(value) => onChangeValue('share', value)} />
</div> </div>
{keys.map((item: any) => { {keys.map((item: any) => {
const tips = getTips(item); const itemTips = getTips(item);
return ( return (
<div key={item} className="flex flex-col gap-2"> <div key={item} className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <LabelWithTooltip label={item} tips={itemTips} />
<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>
{item === 'expiration-time' && ( {item === 'expiration-time' && (
<DatePicker value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} /> <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 [inputValue, setInputValue] = React.useState("")
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { 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() e.preventDefault()
const newValue = inputValue.trim() if (!value.includes(trimmed)) {
if (newValue && !value.includes(newValue)) { onChange([...value, trimmed])
onChange([...value, newValue])
}
setInputValue("") setInputValue("")
} else {
setInputValue("")
}
} else if (e.key === "Backspace" && !inputValue && value.length > 0) { } else if (e.key === "Backspace" && !inputValue && value.length > 0) {
onChange(value.slice(0, -1)) onChange(value.slice(0, -1))
} }
@@ -34,9 +37,9 @@ export function TagsInput({ value, onChange, placeholder = "输入用户名,
return ( return (
<div className={cn("flex flex-wrap gap-2 w-full", className)}> <div className={cn("flex flex-wrap gap-2 w-full", className)}>
{value.map((tag, index) => ( {value.map((tag) => (
<div <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" 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> <span>{tag}</span>
@@ -44,6 +47,7 @@ export function TagsInput({ value, onChange, placeholder = "输入用户名,
type="button" type="button"
onClick={() => removeTag(tag)} onClick={() => removeTag(tag)}
className="flex items-center justify-center w-4 h-4 rounded hover:bg-muted transition-colors" 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" /> <X className="w-3 h-3" />
</button> </button>

View File

@@ -1,16 +1,7 @@
'use client'; 'use client';
import dayjs from 'dayjs'; 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 = [ export const keysTips = [
{ {
key: 'share', 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 { export class KeyParse {
static parse(metadata: Record<string, any>) { static parse(metadata: Record<string, any>) {
const keys = Object.keys(metadata); return Object.entries(metadata).reduce((acc, [key, value]) => {
const newMetadata = {}; const tip = tipsMap.get(key);
keys.forEach((key) => { acc[key] = tip?.parse ? tip.parse(value) : value;
const tip = keysTips.find((item) => item.key === key); return acc;
if (tip && tip.parse) { }, {} as Record<string, any>);
newMetadata[key] = tip.parse(metadata[key]);
} else {
newMetadata[key] = metadata[key];
}
});
return newMetadata;
} }
static stringify(metadata: Record<string, any>) { static stringify(metadata: Record<string, any>) {
const keys = Object.keys(metadata); return Object.entries(metadata).reduce((acc, [key, value]) => {
const newMetadata = {}; const tip = tipsMap.get(key);
keys.forEach((key) => { acc[key] = tip?.stringify ? tip.stringify(value) : value;
const tip = keysTips.find((item) => item.key === key); return acc;
if (tip && tip.stringify) { }, {} as Record<string, any>);
newMetadata[key] = tip.stringify(metadata[key]);
} else {
newMetadata[key] = metadata[key];
}
});
return newMetadata;
} }
} }