Commit 42f38b82 by t0ng7u

💄 fix(ui): improve uptime card layout and request count icon

- Remove flex layout from uptime service card to eliminate bottom spacing
- Remove flex-1 and mt-auto classes that caused unnecessary stretching
- Replace IconRotate with IconSend for request count to better represent the semantic meaning
- Legend now sits directly below content instead of being pushed to bottom

Fixes the visual issue where uptime service availability card had unwanted white space at the bottom when content was minimal.
parent 38c5e0e9
...@@ -1630,6 +1630,7 @@ ...@@ -1630,6 +1630,7 @@
"请联系管理员在系统设置中配置Uptime": "Please contact the administrator to configure Uptime in the system settings.", "请联系管理员在系统设置中配置Uptime": "Please contact the administrator to configure Uptime in the system settings.",
"确定要删除此API信息吗?": "Are you sure you want to delete this API information?", "确定要删除此API信息吗?": "Are you sure you want to delete this API information?",
"测速": "Speed Test", "测速": "Speed Test",
"跳转": "Jump",
"批量删除": "Batch Delete", "批量删除": "Batch Delete",
"常见问答": "FAQ", "常见问答": "FAQ",
"进行中": "Ongoing", "进行中": "Ongoing",
......
...@@ -25,7 +25,6 @@ import { ...@@ -25,7 +25,6 @@ import {
IconSearch, IconSearch,
IconMoneyExchangeStroked, IconMoneyExchangeStroked,
IconHistogram, IconHistogram,
IconRotate,
IconCoinMoneyStroked, IconCoinMoneyStroked,
IconTextStroked, IconTextStroked,
IconPulse, IconPulse,
...@@ -33,7 +32,8 @@ import { ...@@ -33,7 +32,8 @@ import {
IconTypograph, IconTypograph,
IconPieChart2Stroked, IconPieChart2Stroked,
IconPlus, IconPlus,
IconMinus IconMinus,
IconSend
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations'; import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
import { VChart } from '@visactor/react-vchart'; import { VChart } from '@visactor/react-vchart';
...@@ -541,7 +541,7 @@ const Detail = (props) => { ...@@ -541,7 +541,7 @@ const Detail = (props) => {
{ {
title: t('请求次数'), title: t('请求次数'),
value: userState.user?.request_count, value: userState.user?.request_count,
icon: <IconRotate size="large" />, icon: <IconSend size="large" />,
avatarColor: 'green', avatarColor: 'green',
trendData: [], trendData: [],
trendColor: '#10b981' trendColor: '#10b981'
...@@ -1176,487 +1176,488 @@ const Detail = (props) => { ...@@ -1176,487 +1176,488 @@ const Detail = (props) => {
</Form> </Form>
</Modal> </Modal>
<Spin spinning={loading}> <div className="mb-4">
<div className="mb-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> {groupedStatsData.map((group, idx) => (
{groupedStatsData.map((group, idx) => ( <Card
<Card key={idx}
key={idx} {...CARD_PROPS}
{...CARD_PROPS} className={`${group.color} border-0 !rounded-2xl w-full`}
className={`${group.color} border-0 !rounded-2xl w-full`} title={group.title}
title={group.title} >
> <div className="space-y-4">
<div className="space-y-4"> {group.items.map((item, itemIdx) => (
{group.items.map((item, itemIdx) => ( <div
<div key={itemIdx}
key={itemIdx} className="flex items-center justify-between cursor-pointer"
className="flex items-center justify-between cursor-pointer" onClick={item.onClick}
onClick={item.onClick} >
> <div className="flex items-center">
<div className="flex items-center"> <Avatar
<Avatar className="mr-3"
className="mr-3" size="small"
size="small" color={item.avatarColor}
color={item.avatarColor} >
> {item.icon}
{item.icon} </Avatar>
</Avatar> <div>
<div> <div className="text-xs text-gray-500">{item.title}</div>
<div className="text-xs text-gray-500">{item.title}</div> <div className="text-lg font-semibold">{item.value}</div>
<div className="text-lg font-semibold">{item.value}</div>
</div>
</div> </div>
{item.trendData && item.trendData.length > 0 && (
<div className="w-24 h-10">
<VChart
spec={getTrendSpec(item.trendData, item.trendColor)}
option={CHART_CONFIG}
/>
</div>
)}
</div> </div>
))} {item.trendData && item.trendData.length > 0 && (
</div> <div className="w-24 h-10">
</Card> <VChart
))} spec={getTrendSpec(item.trendData, item.trendColor)}
</div> option={CHART_CONFIG}
/>
</div>
)}
</div>
))}
</div>
</Card>
))}
</div> </div>
</div>
<div className="mb-4">
<div className={`grid grid-cols-1 gap-4 ${hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}>
<Card
{...CARD_PROPS}
className={`shadow-sm !rounded-2xl ${hasApiInfoPanel ? 'lg:col-span-3' : ''}`}
title={
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between w-full gap-3">
<div className={FLEX_CENTER_GAP2}>
<PieChart size={16} />
{t('模型数据分析')}
</div>
<Tabs
type="button"
activeKey={activeChartTab}
onChange={setActiveChartTab}
>
<TabPane tab={
<span>
<IconHistogram />
{t('消耗分布')}
</span>
} itemKey="1" />
<TabPane tab={
<span>
<IconPulse />
{t('消耗趋势')}
</span>
} itemKey="2" />
<TabPane tab={
<span>
<IconPieChart2Stroked />
{t('调用次数分布')}
</span>
} itemKey="3" />
<TabPane tab={
<span>
<IconHistogram />
{t('调用次数排行')}
</span>
} itemKey="4" />
</Tabs>
</div>
}
bodyStyle={{ padding: 0 }}
>
<div className="h-96 p-2">
{activeChartTab === '1' && (
<VChart
spec={spec_line}
option={CHART_CONFIG}
/>
)}
{activeChartTab === '2' && (
<VChart
spec={spec_model_line}
option={CHART_CONFIG}
/>
)}
{activeChartTab === '3' && (
<VChart
spec={spec_pie}
option={CHART_CONFIG}
/>
)}
{activeChartTab === '4' && (
<VChart
spec={spec_rank_bar}
option={CHART_CONFIG}
/>
)}
</div>
</Card>
<div className="mb-4"> {hasApiInfoPanel && (
<div className={`grid grid-cols-1 gap-4 ${hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}>
<Card <Card
{...CARD_PROPS} {...CARD_PROPS}
className={`shadow-sm !rounded-2xl ${hasApiInfoPanel ? 'lg:col-span-3' : ''}`} className="bg-gray-50 border-0 !rounded-2xl"
title={ title={
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between w-full gap-3"> <div className={FLEX_CENTER_GAP2}>
<div className={FLEX_CENTER_GAP2}> <Server size={16} />
<PieChart size={16} /> {t('API信息')}
{t('模型数据分析')}
</div>
<Tabs
type="button"
activeKey={activeChartTab}
onChange={setActiveChartTab}
>
<TabPane tab={
<span>
<IconHistogram />
{t('消耗分布')}
</span>
} itemKey="1" />
<TabPane tab={
<span>
<IconPulse />
{t('消耗趋势')}
</span>
} itemKey="2" />
<TabPane tab={
<span>
<IconPieChart2Stroked />
{t('调用次数分布')}
</span>
} itemKey="3" />
<TabPane tab={
<span>
<IconHistogram />
{t('调用次数排行')}
</span>
} itemKey="4" />
</Tabs>
</div> </div>
} }
bodyStyle={{ padding: 0 }}
> >
<div style={{ height: 400 }}> <div className="card-content-container">
{activeChartTab === '1' && ( <div
<VChart ref={apiScrollRef}
spec={spec_line} className="p-2 max-h-96 overflow-y-auto card-content-scroll"
option={CHART_CONFIG} onScroll={handleApiScroll}
/> >
)} {apiInfoData.length > 0 ? (
{activeChartTab === '2' && ( apiInfoData.map((api) => (
<VChart <>
spec={spec_model_line} <div key={api.id} className="flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer">
option={CHART_CONFIG} <div className="flex-shrink-0 mr-3">
/> <Avatar
)} size="extra-small"
{activeChartTab === '3' && ( color={api.color}
<VChart >
spec={spec_pie} {api.route.substring(0, 2)}
option={CHART_CONFIG} </Avatar>
/> </div>
)} <div className="flex-1">
{activeChartTab === '4' && ( <div className="flex flex-wrap items-center justify-between mb-1 w-full gap-2">
<VChart <span className="text-sm font-medium text-gray-900 !font-bold break-all">
spec={spec_rank_bar} {api.route}
option={CHART_CONFIG} </span>
/> <div className="flex items-center gap-1 mt-1 lg:mt-0">
)} <Tag
</div> prefixIcon={<Gauge size={12} />}
</Card> size="small"
color="white"
{hasApiInfoPanel && ( shape='circle'
<Card onClick={() => handleSpeedTest(api.url)}
{...CARD_PROPS} className="cursor-pointer hover:opacity-80 text-xs"
className="bg-gray-50 border-0 !rounded-2xl" >
title={ {t('测速')}
<div className={FLEX_CENTER_GAP2}> </Tag>
<Server size={16} /> <Tag
{t('API信息')} prefixIcon={<ExternalLink size={12} />}
</div> size="small"
} color="white"
> shape='circle'
<div className="card-content-container"> onClick={() => window.open(api.url, '_blank', 'noopener,noreferrer')}
<div className="cursor-pointer hover:opacity-80 text-xs"
ref={apiScrollRef} >
className="space-y-3 max-h-96 overflow-y-auto card-content-scroll" {t('跳转')}
onScroll={handleApiScroll} </Tag>
>
{apiInfoData.length > 0 ? (
apiInfoData.map((api) => (
<>
<div key={api.id} className="flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer">
<div className="flex-shrink-0 mr-3">
<Avatar
size="extra-small"
color={api.color}
>
{api.route.substring(0, 2)}
</Avatar>
</div>
<div className="flex-1">
<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
prefixIcon={<Gauge size={12} />}
size="small"
color="white"
shape='circle'
onClick={() => handleSpeedTest(api.url)}
className="cursor-pointer hover:opacity-80 text-xs"
>
{t('测速')}
</Tag>
<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
className="!text-semi-color-primary break-all cursor-pointer hover:underline mb-1"
onClick={() => handleCopyUrl(api.url)}
>
{api.url}
</div>
<div className="text-gray-500">
{api.description}
</div> </div>
</div> </div>
<div
className="!text-semi-color-primary break-all cursor-pointer hover:underline mb-1"
onClick={() => handleCopyUrl(api.url)}
>
{api.url}
</div>
<div className="text-gray-500">
{api.description}
</div>
</div> </div>
<Divider /> </div>
</> <Divider />
)) </>
) : ( ))
<div className="flex justify-center items-center py-8"> ) : (
<Empty <div className="flex justify-center items-center py-8">
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />} <Empty
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />} image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
title={t('暂无API信息')} darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
description={t('请联系管理员在系统设置中配置API信息')} title={t('暂无API信息')}
/> description={t('请联系管理员在系统设置中配置API信息')}
</div> />
)} </div>
</div> )}
<div
className="card-content-fade-indicator"
style={{ opacity: showApiScrollHint ? 1 : 0 }}
/>
</div> </div>
</Card> <div
)} className="card-content-fade-indicator"
</div> style={{ opacity: showApiScrollHint ? 1 : 0 }}
/>
</div>
</Card>
)}
</div> </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">
{/* 公告卡片 */} {/* 公告卡片 */}
{announcementsEnabled && ( {announcementsEnabled && (
<Card <Card
{...CARD_PROPS} {...CARD_PROPS}
className="shadow-sm !rounded-2xl lg:col-span-2" className="shadow-sm !rounded-2xl lg:col-span-2"
title={ title={
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 w-full"> <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 w-full">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Bell size={16} /> <Bell size={16} />
{t('系统公告')} {t('系统公告')}
<Tag color="grey" shape="circle"> <Tag color="white" shape="circle">
{t('显示最新20条')} {t('显示最新20条')}
</Tag> </Tag>
</div>
{/* 图例 */}
<div className="flex flex-wrap gap-3 text-xs">
{announcementLegendData.map((legend, index) => (
<div key={index} className="flex items-center gap-1">
<div
className="w-2 h-2 rounded-full"
style={{
backgroundColor: legend.color === 'grey' ? '#8b9aa7' :
legend.color === 'blue' ? '#3b82f6' :
legend.color === 'green' ? '#10b981' :
legend.color === 'orange' ? '#f59e0b' :
legend.color === 'red' ? '#ef4444' : '#8b9aa7'
}}
/>
<span className="text-gray-600">{legend.label}</span>
</div>
))}
</div>
</div> </div>
} {/* 图例 */}
> <div className="flex flex-wrap gap-3 text-xs">
<div className="card-content-container"> {announcementLegendData.map((legend, index) => (
<div <div key={index} className="flex items-center gap-1">
ref={announcementScrollRef} <div
className="p-2 max-h-96 overflow-y-auto card-content-scroll" className="w-2 h-2 rounded-full"
onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)} style={{
> backgroundColor: legend.color === 'grey' ? '#8b9aa7' :
{announcementData.length > 0 ? ( legend.color === 'blue' ? '#3b82f6' :
<Timeline mode="alternate"> legend.color === 'green' ? '#10b981' :
{announcementData.map((item, idx) => ( legend.color === 'orange' ? '#f59e0b' :
<Timeline.Item legend.color === 'red' ? '#ef4444' : '#8b9aa7'
key={idx} }}
type={item.type || 'default'}
time={item.time}
>
<div>
<div
dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
/>
{item.extra && (
<div
className="text-xs text-gray-500"
dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
/>
)}
</div>
</Timeline.Item>
))}
</Timeline>
) : (
<div className="flex justify-center items-center py-8">
<Empty
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
title={t('暂无系统公告')}
description={t('请联系管理员在系统设置中配置公告信息')}
/> />
<span className="text-gray-600">{legend.label}</span>
</div> </div>
)} ))}
</div> </div>
<div
className="card-content-fade-indicator"
style={{ opacity: showAnnouncementScrollHint ? 1 : 0 }}
/>
</div> </div>
</Card> }
)} bodyStyle={{ padding: 0 }}
>
{/* 常见问答卡片 */} <div className="card-content-container">
{faqEnabled && ( <div
<Card ref={announcementScrollRef}
{...CARD_PROPS} className="p-2 max-h-96 overflow-y-auto card-content-scroll"
className="shadow-sm !rounded-2xl lg:col-span-1" onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)}
title={ >
<div className={FLEX_CENTER_GAP2}> {announcementData.length > 0 ? (
<HelpCircle size={16} /> <Timeline mode="alternate">
{t('常见问答')} {announcementData.map((item, idx) => (
</div> <Timeline.Item
} key={idx}
bodyStyle={{ padding: 0 }} type={item.type || 'default'}
> time={item.time}
<div className="card-content-container"> >
<div <div>
ref={faqScrollRef}
className="p-2 max-h-96 overflow-y-auto card-content-scroll"
onScroll={() => handleCardScroll(faqScrollRef, setShowFaqScrollHint)}
>
{faqData.length > 0 ? (
<Collapse
accordion
expandIcon={<IconPlus />}
collapseIcon={<IconMinus />}
>
{faqData.map((item, index) => (
<Collapse.Panel
key={index}
header={item.question}
itemKey={index.toString()}
>
<div <div
dangerouslySetInnerHTML={{ __html: marked.parse(item.answer || '') }} dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
/> />
</Collapse.Panel> {item.extra && (
))} <div
</Collapse> className="text-xs text-gray-500"
) : ( dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
<div className="flex justify-center items-center py-8"> />
<Empty )}
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
title={t('暂无常见问答')}
description={t('请联系管理员在系统设置中配置常见问答')}
/>
</div>
)}
</div>
<div
className="card-content-fade-indicator"
style={{ opacity: showFaqScrollHint ? 1 : 0 }}
/>
</div>
</Card>
)}
{/* 服务可用性卡片 */}
{uptimeEnabled && (
<Card
{...CARD_PROPS}
className="shadow-sm !rounded-2xl lg:col-span-1 flex flex-col"
title={
<div className="flex items-center justify-between w-full gap-2">
<div className="flex items-center gap-2">
<Gauge size={16} />
{t('服务可用性')}
</div>
<Button
icon={<IconRefresh />}
onClick={loadUptimeData}
loading={uptimeLoading}
size="small"
theme="borderless"
type='tertiary'
className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full"
/>
</div>
}
bodyStyle={{ padding: 0 }}
>
{/* 内容区域 */}
<div className="flex-1 relative">
<Spin spinning={uptimeLoading}>
{uptimeData.length > 0 ? (
uptimeData.length === 1 ? (
<div className="card-content-container">
<div
ref={uptimeScrollRef}
className="p-2 max-h-[24rem] overflow-y-auto card-content-scroll"
onScroll={() => handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)}
>
{renderMonitorList(uptimeData[0].monitors)}
</div> </div>
</Timeline.Item>
))}
</Timeline>
) : (
<div className="flex justify-center items-center py-8">
<Empty
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
title={t('暂无系统公告')}
description={t('请联系管理员在系统设置中配置公告信息')}
/>
</div>
)}
</div>
<div
className="card-content-fade-indicator"
style={{ opacity: showAnnouncementScrollHint ? 1 : 0 }}
/>
</div>
</Card>
)}
{/* 常见问答卡片 */}
{faqEnabled && (
<Card
{...CARD_PROPS}
className="shadow-sm !rounded-2xl lg:col-span-1"
title={
<div className={FLEX_CENTER_GAP2}>
<HelpCircle size={16} />
{t('常见问答')}
</div>
}
bodyStyle={{ padding: 0 }}
>
<div className="card-content-container">
<div
ref={faqScrollRef}
className="p-2 max-h-96 overflow-y-auto card-content-scroll"
onScroll={() => handleCardScroll(faqScrollRef, setShowFaqScrollHint)}
>
{faqData.length > 0 ? (
<Collapse
accordion
expandIcon={<IconPlus />}
collapseIcon={<IconMinus />}
>
{faqData.map((item, index) => (
<Collapse.Panel
key={index}
header={item.question}
itemKey={index.toString()}
>
<div <div
className="card-content-fade-indicator" dangerouslySetInnerHTML={{ __html: marked.parse(item.answer || '') }}
style={{ opacity: showUptimeScrollHint ? 1 : 0 }}
/> />
</div> </Collapse.Panel>
) : ( ))}
<Tabs </Collapse>
type="card" ) : (
collapsible <div className="flex justify-center items-center py-8">
activeKey={activeUptimeTab} <Empty
onChange={setActiveUptimeTab} image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
size="small" darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
title={t('暂无常见问答')}
description={t('请联系管理员在系统设置中配置常见问答')}
/>
</div>
)}
</div>
<div
className="card-content-fade-indicator"
style={{ opacity: showFaqScrollHint ? 1 : 0 }}
/>
</div>
</Card>
)}
{/* 服务可用性卡片 */}
{uptimeEnabled && (
<Card
{...CARD_PROPS}
className="shadow-sm !rounded-2xl lg:col-span-1"
title={
<div className="flex items-center justify-between w-full gap-2">
<div className="flex items-center gap-2">
<Gauge size={16} />
{t('服务可用性')}
</div>
<Button
icon={<IconRefresh />}
onClick={loadUptimeData}
loading={uptimeLoading}
size="small"
theme="borderless"
type='tertiary'
className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full"
/>
</div>
}
bodyStyle={{ padding: 0 }}
>
{/* 内容区域 */}
<div className="relative">
<Spin spinning={uptimeLoading}>
{uptimeData.length > 0 ? (
uptimeData.length === 1 ? (
<div className="card-content-container">
<div
ref={uptimeScrollRef}
className="p-2 max-h-[24rem] overflow-y-auto card-content-scroll"
onScroll={() => handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)}
> >
{uptimeData.map((group, groupIdx) => { {renderMonitorList(uptimeData[0].monitors)}
if (!uptimeTabScrollRefs.current[group.categoryName]) { </div>
uptimeTabScrollRefs.current[group.categoryName] = React.createRef(); <div
} className="card-content-fade-indicator"
const tabScrollRef = uptimeTabScrollRefs.current[group.categoryName]; style={{ opacity: showUptimeScrollHint ? 1 : 0 }}
/>
return ( </div>
<TabPane ) : (
tab={ <Tabs
<span className="flex items-center gap-2"> type="card"
<Gauge size={14} /> collapsible
{group.categoryName} activeKey={activeUptimeTab}
<Tag onChange={setActiveUptimeTab}
color={activeUptimeTab === group.categoryName ? 'red' : 'grey'} size="small"
size='small' >
shape='circle' {uptimeData.map((group, groupIdx) => {
> if (!uptimeTabScrollRefs.current[group.categoryName]) {
{group.monitors ? group.monitors.length : 0} uptimeTabScrollRefs.current[group.categoryName] = React.createRef();
</Tag> }
</span> const tabScrollRef = uptimeTabScrollRefs.current[group.categoryName];
}
itemKey={group.categoryName} return (
key={groupIdx} <TabPane
> tab={
<div className="card-content-container"> <span className="flex items-center gap-2">
<div <Gauge size={14} />
ref={tabScrollRef} {group.categoryName}
className="p-2 max-h-[21.5rem] overflow-y-auto card-content-scroll" <Tag
onScroll={() => handleCardScroll(tabScrollRef, setShowUptimeScrollHint)} color={activeUptimeTab === group.categoryName ? 'red' : 'grey'}
size='small'
shape='circle'
> >
{renderMonitorList(group.monitors)} {group.monitors ? group.monitors.length : 0}
</div> </Tag>
<div </span>
className="card-content-fade-indicator" }
style={{ opacity: activeUptimeTab === group.categoryName ? showUptimeScrollHint ? 1 : 0 : 0 }} itemKey={group.categoryName}
/> key={groupIdx}
>
<div className="card-content-container">
<div
ref={tabScrollRef}
className="p-2 max-h-[21.5rem] overflow-y-auto card-content-scroll"
onScroll={() => handleCardScroll(tabScrollRef, setShowUptimeScrollHint)}
>
{renderMonitorList(group.monitors)}
</div> </div>
</TabPane> <div
); className="card-content-fade-indicator"
})} style={{ opacity: activeUptimeTab === group.categoryName ? showUptimeScrollHint ? 1 : 0 : 0 }}
</Tabs> />
) </div>
) : ( </TabPane>
<div className="flex justify-center items-center py-8"> );
<Empty })}
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />} </Tabs>
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />} )
title={t('暂无监控数据')} ) : (
description={t('请联系管理员在系统设置中配置Uptime')} <div className="flex justify-center items-center py-8">
<Empty
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
title={t('暂无监控数据')}
description={t('请联系管理员在系统设置中配置Uptime')}
/>
</div>
)}
</Spin>
</div>
{/* 图例 */}
{uptimeData.length > 0 && (
<div className="p-3 bg-gray-50 rounded-b-2xl">
<div className="flex flex-wrap gap-3 text-xs justify-center">
{uptimeLegendData.map((legend, index) => (
<div key={index} className="flex items-center gap-1">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: legend.color }}
/> />
<span className="text-gray-600">{legend.label}</span>
</div> </div>
)} ))}
</Spin>
</div>
{/* 固定在底部的图例 */}
{uptimeData.length > 0 && (
<div className="p-3 mt-auto bg-gray-50 rounded-b-2xl">
<div className="flex flex-wrap gap-3 text-xs justify-center">
{uptimeLegendData.map((legend, index) => (
<div key={index} className="flex items-center gap-1">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: legend.color }}
/>
<span className="text-gray-600">{legend.label}</span>
</div>
))}
</div>
</div> </div>
)} </div>
</Card> )}
)} </Card>
</div> )}
</div> </div>
) </div>
} )
</Spin > }
</div > </div >
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment