🚀 feat(detail): enhance API Info list with jump button & responsive layout

* Added an “Jump” (`ExternalLink`) tag to each API entry that opens the URL in a new tab
* Placed “Speed Test” and “Jump” tags on the same line as the route
  * Route is left-aligned; tags are right-aligned and wrap to next line when space is insufficient
* Inserted `<Divider />` between API items to improve visual separation
* Tweaked flex gaps and utility classes for consistent spacing and readability
This commit is contained in:
t0ng7u
2025-07-14 20:22:09 +08:00
parent 5f011502d1
commit 232ba46b16

View File

@@ -1,14 +1,14 @@
import React, { useContext, useEffect, useRef, useState, useMemo, useCallback } from 'react'; import React, { useContext, useEffect, useRef, useState, useMemo, useCallback } from 'react';
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme'; import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle } from 'lucide-react'; import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle, ExternalLink } from 'lucide-react';
import { marked } from 'marked'; import { marked } from 'marked';
import { import {
Card, Card,
Form, Form,
Spin, Spin,
IconButton, Button,
Modal, Modal,
Avatar, Avatar,
Tabs, Tabs,
@@ -614,7 +614,7 @@ const Detail = (props) => {
const handleSpeedTest = useCallback((apiUrl) => { const handleSpeedTest = useCallback((apiUrl) => {
const encodedUrl = encodeURIComponent(apiUrl); const encodedUrl = encodeURIComponent(apiUrl);
const speedTestUrl = `https://www.tcptest.cn/http/${encodedUrl}`; const speedTestUrl = `https://www.tcptest.cn/http/${encodedUrl}`;
window.open(speedTestUrl, '_blank'); window.open(speedTestUrl, '_blank', 'noopener,noreferrer');
}, []); }, []);
const handleInputChange = useCallback((value, name) => { const handleInputChange = useCallback((value, name) => {
@@ -1108,12 +1108,14 @@ const Detail = (props) => {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-semibold text-gray-800">{getGreeting}</h2> <h2 className="text-2xl font-semibold text-gray-800">{getGreeting}</h2>
<div className="flex gap-3"> <div className="flex gap-3">
<IconButton <Button
type='tertiary'
icon={<IconSearch />} icon={<IconSearch />}
onClick={showSearchModal} onClick={showSearchModal}
className={`bg-green-500 hover:bg-green-600 ${ICON_BUTTON_CLASS}`} className={`bg-green-500 hover:bg-green-600 ${ICON_BUTTON_CLASS}`}
/> />
<IconButton <Button
type='tertiary'
icon={<IconRefresh />} icon={<IconRefresh />}
onClick={refresh} onClick={refresh}
loading={loading} loading={loading}
@@ -1311,6 +1313,7 @@ const Detail = (props) => {
> >
{apiInfoData.length > 0 ? ( {apiInfoData.length > 0 ? (
apiInfoData.map((api) => ( apiInfoData.map((api) => (
<>
<div key={api.id} className="flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer"> <div key={api.id} className="flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer">
<div className="flex-shrink-0 mr-3"> <div className="flex-shrink-0 mr-3">
<Avatar <Avatar
@@ -1321,7 +1324,11 @@ const Detail = (props) => {
</Avatar> </Avatar>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="text-sm font-medium text-gray-900 mb-1 !font-bold flex items-center gap-2"> <div className="flex flex-wrap items-center justify-between mb-1 w-full gap-2">
<span className="text-sm font-medium text-gray-900 !font-bold break-all">
{api.route}
</span>
<div className="flex items-center gap-1 mt-1 lg:mt-0">
<Tag <Tag
prefixIcon={<Gauge size={12} />} prefixIcon={<Gauge size={12} />}
size="small" size="small"
@@ -1332,7 +1339,17 @@ const Detail = (props) => {
> >
{t('测速')} {t('测速')}
</Tag> </Tag>
{api.route} <Tag
prefixIcon={<ExternalLink size={12} />}
size="small"
color="white"
shape='circle'
onClick={() => window.open(api.url, '_blank', 'noopener,noreferrer')}
className="cursor-pointer hover:opacity-80 text-xs"
>
{t('跳转')}
</Tag>
</div>
</div> </div>
<div <div
className="!text-semi-color-primary break-all cursor-pointer hover:underline mb-1" className="!text-semi-color-primary break-all cursor-pointer hover:underline mb-1"
@@ -1345,6 +1362,8 @@ const Detail = (props) => {
</div> </div>
</div> </div>
</div> </div>
<Divider />
</>
)) ))
) : ( ) : (
<div className="flex justify-center items-center py-8"> <div className="flex justify-center items-center py-8">
@@ -1368,7 +1387,8 @@ const Detail = (props) => {
</div> </div>
{/* 系统公告和常见问答卡片 */} {/* 系统公告和常见问答卡片 */}
{hasInfoPanels && ( {
hasInfoPanels && (
<div className="mb-4"> <div className="mb-4">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
{/* 公告卡片 */} {/* 公告卡片 */}
@@ -1520,12 +1540,13 @@ const Detail = (props) => {
<Gauge size={16} /> <Gauge size={16} />
{t('服务可用性')} {t('服务可用性')}
</div> </div>
<IconButton <Button
icon={<IconRefresh />} icon={<IconRefresh />}
onClick={loadUptimeData} onClick={loadUptimeData}
loading={uptimeLoading} loading={uptimeLoading}
size="small" size="small"
theme="borderless" theme="borderless"
type='tertiary'
className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full" className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full"
/> />
</div> </div>
@@ -1633,9 +1654,10 @@ const Detail = (props) => {
)} )}
</div> </div>
</div> </div>
)} )
</Spin> }
</div> </Spin >
</div >
); );
}; };