feat: 新增游客订阅链接与节点拖动排序
游客链接:可建多个带独立 token 的订阅,按节点名勾选白名单 (抗重新抓取),/surge|/clash|/ssr 复用同一路径回落匹配 guest token,三个 generator 接收可选 whitelist 过滤生成。 节点排序:fetched_nodes 新增 sort_order,重抓时按名保留顺序, 选择/节点面板支持原生拖拽排序,输出按此顺序排列。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ export default function NodeSelector() {
|
||||
const [selectedSub, setSelectedSub] = useState<number | null>(null);
|
||||
const [nodeList, setNodeList] = useState<any[]>([]);
|
||||
const [regexInput, setRegexInput] = useState('');
|
||||
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
subsApi.list().then(data => {
|
||||
@@ -52,6 +53,21 @@ export default function NodeSelector() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = async (targetIndex: number) => {
|
||||
if (dragIndex === null || dragIndex === targetIndex) { setDragIndex(null); return; }
|
||||
const reordered = [...nodeList];
|
||||
const [moved] = reordered.splice(dragIndex, 1);
|
||||
reordered.splice(targetIndex, 0, moved);
|
||||
setNodeList(reordered);
|
||||
setDragIndex(null);
|
||||
try {
|
||||
await nodesApi.fetchedReorder(reordered.map(n => n.id));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (selectedSub) subsApi.nodes(selectedSub).then(setNodeList);
|
||||
}
|
||||
};
|
||||
|
||||
const enabledCount = nodeList.filter(n => n.enabled).length;
|
||||
|
||||
return (
|
||||
@@ -129,9 +145,13 @@ export default function NodeSelector() {
|
||||
)}
|
||||
|
||||
{/* Node list */}
|
||||
<p style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 8 }}>
|
||||
拖动 ⠿ 可调整节点顺序,输出配置会按此顺序排列
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: 32 }}></th>
|
||||
<th style={{ width: 50 }}>启用</th>
|
||||
<th>名称</th>
|
||||
<th style={{ width: 80 }}>协议</th>
|
||||
@@ -140,10 +160,25 @@ export default function NodeSelector() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nodeList.map(node => (
|
||||
<tr key={node.id} style={{
|
||||
opacity: node.enabled ? 1 : 0.5,
|
||||
}}>
|
||||
{nodeList.map((node, idx) => (
|
||||
<tr
|
||||
key={node.id}
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={() => handleDrop(idx)}
|
||||
style={{
|
||||
opacity: node.enabled ? 1 : 0.5,
|
||||
background: dragIndex === idx ? 'var(--bg-active)' : undefined,
|
||||
}}
|
||||
>
|
||||
<td
|
||||
draggable
|
||||
onDragStart={() => setDragIndex(idx)}
|
||||
onDragEnd={() => setDragIndex(null)}
|
||||
style={{ cursor: 'grab', textAlign: 'center', color: 'var(--text-muted)' }}
|
||||
title="拖动排序"
|
||||
>
|
||||
⠿
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -189,7 +224,7 @@ export default function NodeSelector() {
|
||||
))}
|
||||
{nodeList.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} style={{ textAlign: 'center', color: 'var(--text-muted)', padding: 40 }}>
|
||||
<td colSpan={6} style={{ textAlign: 'center', color: 'var(--text-muted)', padding: 40 }}>
|
||||
{subs.length === 0
|
||||
? '请先添加订阅源'
|
||||
: '请先抓取订阅源节点'
|
||||
|
||||
Reference in New Issue
Block a user