diff --git a/src/components/CustomNode.css b/src/components/CustomNode.css index 7897c2a..9ef5271 100644 --- a/src/components/CustomNode.css +++ b/src/components/CustomNode.css @@ -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); diff --git a/src/components/CustomNode.tsx b/src/components/CustomNode.tsx index ff741bf..8a4add2 100644 --- a/src/components/CustomNode.tsx +++ b/src/components/CustomNode.tsx @@ -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({
- + }} onDoubleClick={e => e.stopPropagation()}>复制配置 +
{[Position.Top, Position.Bottom, Position.Right, Position.Left].map((position) => (