update
This commit is contained in:
@@ -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(() => {
|
const date = toDate(value)
|
||||||
if (value) {
|
|
||||||
const dateValue = typeof value === 'string' ? value : value.toISOString()
|
|
||||||
setDate(new Date(dateValue))
|
|
||||||
}
|
|
||||||
}, [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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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("")
|
||||||
|
} else {
|
||||||
|
setInputValue("")
|
||||||
}
|
}
|
||||||
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>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user