添加另存为配置

This commit is contained in:
limil 2026-02-22 20:04:53 +08:00
parent 0d184600f8
commit 53616d4c1d
2 changed files with 77 additions and 6 deletions

View File

@ -69,7 +69,7 @@
gap: 10px;
}
.gen-btn {
.node-btn {
background: #1677ff;
color: #fff;
border: none;
@ -86,15 +86,15 @@
outline: none; /* remove default focus outline */
}
.gen-btn:hover {
.node-btn:hover {
opacity: 1;
}
.gen-btn:focus {
.node-btn:focus {
outline: none;
}
.gen-btn:active {
.node-btn:active {
transform: translateY(1px) scale(0.995);
background-color: #0f62d6; /* slightly darker on press */
box-shadow: inset 0 1px 2px rgba(0,0,0,0.12);

View File

@ -235,6 +235,73 @@ export default function CustomNode({
}
};
const handleSaveAs = async (node: NodeData) => {
const graph = new AppGraph(getNodes, getEdges);
const nodes = getNodes();
let subnets: SubnetInfo[] = [];
const v4 = getSubnet(nodes, 'ipv4');
const v6 = getSubnet(nodes, 'ipv6');
if(!v4.isValid()) {
toast.error(`ipv4子网配置有误${v4.errorInfo()}`);
return;
}
if(!v6.isValid()) {
toast.error(`ipv6子网配置有误${v6.errorInfo()}`);
return;
}
if(v4.result) subnets.push(v4.result);
if(v6.result) subnets.push(v6.result);
subnets = subnets.concat(settings.subnets);
const result = generateConfig(settings, node, graph, subnets);
if(result.success && result.config) {
const safeName = (node.label || node.id).replace(/[^a-z0-9_\-\.]/gi, '_') + '.conf';
// 优先使用浏览器的保存对话框File System Access API不支持时回退到 blob 下载
try {
if ((window as any).showSaveFilePicker) {
// @ts-ignore
const handle = await (window as any).showSaveFilePicker({
types: [
{
description: 'WireGuard config',
accept: { 'text/plain': ['.conf'] }
}
],
suggestedName: safeName
});
const writable = await handle.createWritable();
await writable.write(result.config);
await writable.close();
toast.success('已保存');
return;
}
} catch (e) {
// 若用户取消或发生错误,回退到传统下载
}
// 回退到创建 Blob 下载
try {
const blob = new Blob([result.config], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = safeName;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
toast.success('配置已下载');
} catch (e) {
toast.error('保存失败,请检查浏览器设置');
}
} else {
toast.error("配置生成失败:" + (result.error || "未知错误"));
}
};
const empty = !(data.ipv4Address || data.ipv6Address);
return (
@ -263,10 +330,14 @@ export default function CustomNode({
</div>
<div className="node-actions">
<button className="gen-btn" onClick={e => {
<button className="node-btn" onClick={e => {
e.stopPropagation();
handleGenerate(data);
}} onDoubleClick={e => e.stopPropagation()}></button>
}} onDoubleClick={e => e.stopPropagation()}></button>
<button className="node-btn" onClick={e => {
e.stopPropagation();
void handleSaveAs(data);
}} onDoubleClick={e => e.stopPropagation()}></button>
</div>
{[Position.Top, Position.Bottom, Position.Right, Position.Left].map((position) => (