|
128 | 128 | isProcessedVisible && isOriginalVisible, |
129 | 129 | }" |
130 | 130 | > |
131 | | - <template v-for="[processed = {}, original = {}] in data" :key="processed.id"> |
| 131 | + <template v-for="[processed, original] in data" :key="processed.id"> |
132 | 132 | <tr |
133 | 133 | v-if="isProcessedVisible" |
134 | 134 | class="compare-table-row processed-tr" |
| 135 | + :class="{ 'processed-tr--unpaired': !hasNodeDisplayInfo(original) }" |
135 | 136 | @click="openNodeInfoPanel(processed)" |
136 | 137 | > |
137 | 138 | <td class="processed-item"> |
|
140 | 141 | <nut-tag class="type-tag">{{ processed.type }}</nut-tag |
141 | 142 | >{{ processed.name }} |
142 | 143 | </div> |
143 | | - <div>{{ processed.server || processed.addresses?.join(',') }}:{{ processed.port || processed["local-port"] }}</div> |
| 144 | + <div v-if="formatNodeEndpoint(processed)">{{ formatNodeEndpoint(processed) }}</div> |
144 | 145 | </div> |
145 | 146 | </td> |
146 | 147 | <td> |
|
178 | 179 | </td> |
179 | 180 | </tr> |
180 | 181 | <tr |
181 | | - v-if="isOriginalVisible" |
| 182 | + v-if="isOriginalVisible && hasNodeDisplayInfo(original)" |
182 | 183 | class="compare-table-row original-tr" |
183 | 184 | @click="openNodeInfoPanel(original)" |
184 | 185 | > |
|
187 | 188 | <div> |
188 | 189 | {{ original.name }} |
189 | 190 | </div> |
190 | | - <div>{{ original.server || original.addresses?.join(',') }}:{{ original.port || original["local-port"] }}</div> |
| 191 | + <div v-if="formatNodeEndpoint(original)">{{ formatNodeEndpoint(original) }}</div> |
191 | 192 | </div> |
192 | 193 | </td> |
193 | 194 | <td> |
|
265 | 266 | <nut-tag class="type-tag">{{ node.type }} </nut-tag |
266 | 267 | >{{ node.name }} |
267 | 268 | </div> |
268 | | - <div>{{ node.server || node.addresses?.join(',') }}:{{ node.port || node["local-port"] }}</div> |
| 269 | + <div v-if="formatNodeEndpoint(node)">{{ formatNodeEndpoint(node) }}</div> |
269 | 270 | </div> |
270 | 271 | </td> |
271 | 272 | <td> |
|
443 | 444 | return result; |
444 | 445 | }); |
445 | 446 |
|
| 447 | + const normalizeEndpointPart = (value: unknown) => { |
| 448 | + if (value == null) { |
| 449 | + return ''; |
| 450 | + } |
| 451 | +
|
| 452 | + if (Array.isArray(value)) { |
| 453 | + return value |
| 454 | + .map(item => normalizeEndpointPart(item)) |
| 455 | + .filter(Boolean) |
| 456 | + .join(','); |
| 457 | + } |
| 458 | +
|
| 459 | + return String(value).trim(); |
| 460 | + }; |
| 461 | +
|
| 462 | + const formatNodeEndpoint = (node: Record<string, any> | null | undefined) => { |
| 463 | + const host = normalizeEndpointPart(node?.server) |
| 464 | + || normalizeEndpointPart(node?.addresses); |
| 465 | + const port = normalizeEndpointPart(node?.port ?? node?.['local-port']); |
| 466 | +
|
| 467 | + if (host && port) { |
| 468 | + return `${host}:${port}`; |
| 469 | + } |
| 470 | +
|
| 471 | + return host || port; |
| 472 | + }; |
| 473 | +
|
| 474 | + const hasNodeDisplayInfo = (node: Record<string, any> | null | undefined) => { |
| 475 | + return Boolean(node?.name || node?.type || formatNodeEndpoint(node)); |
| 476 | + }; |
| 477 | +
|
446 | 478 | // 被过滤掉的节点:original 中未被 processed 匹配到的 |
447 | 479 | const filteredOriginalData = computed(() => { |
448 | 480 | const original = originalData.value; |
|
605 | 637 | border-bottom: none; |
606 | 638 | } |
607 | 639 |
|
| 640 | + .processed-tr--unpaired { |
| 641 | + padding-bottom: 20px; |
| 642 | + border-bottom: 1px solid var(--divider-color); |
| 643 | + } |
| 644 | +
|
608 | 645 | .original-tr { |
609 | 646 | padding-top: 10px; |
610 | 647 | } |
|
0 commit comments