使用 i18n.t() 全局方式(避免 hook 定位问题): - 288 目标文件中 135 个成功转换 - 2228 处中文替换为 i18n.t() 调用 - 1006 个新翻译 key 添加到 zh-CN.json + en-US.json - 词典总量: 3554 keys (22 namespaces) - tsc 零错误 + Vite 构建成功 - 6 个含代码示例/数据字符串的文件安全回退
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { useState, useCallback, createContext, useContext } from 'react';
|
|
import type { ReactNode } from 'react';
|
|
import {
|
|
Dialog, DialogContent, DialogHeader, DialogTitle,
|
|
DialogDescription, DialogFooter,
|
|
} from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import i18n from '@/i18n';
|
|
|
|
interface ConfirmOptions {
|
|
title?: string;
|
|
description?: string;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
variant?: 'default' | 'destructive';
|
|
}
|
|
|
|
interface ConfirmContextValue {
|
|
confirm: (options: ConfirmOptions | string) => Promise<boolean>;
|
|
}
|
|
|
|
const ConfirmContext = createContext<ConfirmContextValue | null>(null);
|
|
|
|
export function useConfirm() {
|
|
const ctx = useContext(ConfirmContext);
|
|
if (!ctx) throw new Error('useConfirm must be used within ConfirmProvider');
|
|
return ctx.confirm;
|
|
}
|
|
|
|
export function ConfirmProvider({ children }: { children: ReactNode }) {
|
|
const [open, setOpen] = useState(false);
|
|
const [options, setOptions] = useState<Required<ConfirmOptions>>({
|
|
title: i18n.t('common.confirmOp'),
|
|
description: '',
|
|
confirmText: i18n.t('monitor.alertEvents.acknowledge'),
|
|
cancelText: i18n.t('rbac.jobRole.cancel'),
|
|
variant: 'default',
|
|
});
|
|
const [resolver, setResolver] = useState<((v: boolean) => void) | null>(null);
|
|
|
|
const confirm = useCallback((opts: ConfirmOptions | string) => {
|
|
const normalized = typeof opts === 'string'
|
|
? { title: i18n.t('common.confirmOp'), description: opts }
|
|
: opts;
|
|
setOptions({
|
|
title: normalized.title || i18n.t('common.confirmOp'),
|
|
description: normalized.description || '',
|
|
confirmText: normalized.confirmText || i18n.t('monitor.alertEvents.acknowledge'),
|
|
cancelText: normalized.cancelText || i18n.t('rbac.jobRole.cancel'),
|
|
variant: (normalized.variant || 'default') as 'default' | 'destructive',
|
|
});
|
|
setOpen(true);
|
|
return new Promise<boolean>((resolve) => setResolver(() => resolve));
|
|
}, []);
|
|
|
|
const handleConfirm = () => {
|
|
setOpen(false);
|
|
resolver?.(true);
|
|
setResolver(null);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setOpen(false);
|
|
resolver?.(false);
|
|
setResolver(null);
|
|
};
|
|
|
|
return (
|
|
<ConfirmContext.Provider value={{ confirm }}>
|
|
{children}
|
|
<Dialog open={open} onOpenChange={(v) => { if (!v) handleCancel(); }}>
|
|
<DialogContent className="max-w-sm dg-card" onCloseAutoFocus={(e) => e.preventDefault()}>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-[var(--dg-text-primary)]">{options.title}</DialogTitle>
|
|
{options.description && (
|
|
<DialogDescription className="dg-text-secondary">{options.description}</DialogDescription>
|
|
)}
|
|
</DialogHeader>
|
|
<DialogFooter className="gap-2 sm:gap-2">
|
|
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={handleCancel}>
|
|
{options.cancelText}
|
|
</Button>
|
|
<Button
|
|
variant={options.variant === 'destructive' ? 'destructive' : 'default'}
|
|
className={options.variant === 'destructive'
|
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
|
: 'bg-blue-600 hover:bg-blue-700 text-white'}
|
|
onClick={handleConfirm}
|
|
>
|
|
{options.confirmText}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</ConfirmContext.Provider>
|
|
);
|
|
}
|