实现投稿ui

This commit is contained in:
2026-01-23 11:16:07 +08:00
parent 2bb3db5966
commit 5932adcd9a
8 changed files with 839 additions and 16 deletions

View File

@@ -0,0 +1,177 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
makeStyles,
tokens,
Card,
CardHeader,
Text,
Spinner,
Badge,
Button,
} from '@fluentui/react-components';
import {
CheckmarkCircle20Filled,
DismissCircle20Filled,
ArrowClockwise20Regular
} from '@fluentui/react-icons';
import { testApiStatus, getStatics } from '../api';
import type { StaticsData } from '../api';
const useStyles = makeStyles({
container: {
display: 'flex',
flexDirection: 'column',
gap: tokens.spacingVerticalS,
width: '100%',
},
card: {
width: '100%',
},
statusContainer: {
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
paddingBottom: tokens.spacingVerticalM,
},
statusText: {
display: 'flex',
alignItems: 'center',
gap: tokens.spacingHorizontalSNudge,
},
online: {
color: tokens.colorStatusSuccessForeground1,
fontSize: tokens.fontSizeBase200,
},
offline: {
color: tokens.colorStatusDangerForeground1,
fontSize: tokens.fontSizeBase200,
},
statsContainer: {
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
paddingBottom: tokens.spacingVerticalM,
display: 'flex',
flexDirection: 'column',
gap: '4px',
},
statRow: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
refreshButton: {
minWidth: 'auto',
padding: '2px',
},
labelText: {
fontSize: tokens.fontSizeBase200,
},
headerText: {
fontSize: tokens.fontSizeBase300,
fontWeight: tokens.fontWeightSemibold,
}
});
const StatusDisplay: React.FC = () => {
const styles = useStyles();
const [isApiOnline, setIsApiOnline] = useState<boolean | null>(null);
const [statics, setStatics] = useState<StaticsData | null>(null);
const [isTestLoading, setIsTestLoading] = useState<boolean>(true);
const [isStaticsLoading, setIsStaticsLoading] = useState<boolean>(true);
const checkStatus = useCallback(async () => {
const online = await testApiStatus();
setIsApiOnline(online);
setIsTestLoading(false);
}, []);
const refreshStatics = useCallback(async () => {
setIsStaticsLoading(true);
try {
const data = await getStatics();
setStatics(data);
} catch (error) {
console.error('Failed to refresh statics:', error);
setStatics(null); // 失败时重置数据
} finally {
setIsStaticsLoading(false);
}
}, []);
useEffect(() => {
// 初始加载
checkStatus();
refreshStatics();
// 每 10 秒测试一次后端 API 状态
const testInterval = setInterval(checkStatus, 10000);
return () => {
clearInterval(testInterval);
};
}, [checkStatus, refreshStatics]);
return (
<div className={styles.container}>
<Card className={styles.card}>
<CardHeader
header={<Text className={styles.headerText}></Text>}
/>
<div className={styles.statusContainer}>
{isTestLoading && isApiOnline === null ? (
<Spinner size="tiny" label="检查中..." />
) : (
<div className={styles.statusText}>
{isApiOnline ? (
<>
<CheckmarkCircle20Filled fontSize={16} className={styles.online} />
<Text className={styles.online}>线</Text>
</>
) : (
<>
<DismissCircle20Filled fontSize={16} className={styles.offline} />
<Text className={styles.offline}>线</Text>
</>
)}
</div>
)}
</div>
</Card>
<Card className={styles.card}>
<CardHeader
header={<Text className={styles.headerText}></Text>}
action={
<Button
className={styles.refreshButton}
appearance="subtle"
icon={<ArrowClockwise20Regular fontSize={16} />}
onClick={refreshStatics}
disabled={isStaticsLoading}
title="刷新统计数据"
/>
}
/>
<div className={styles.statsContainer}>
{isStaticsLoading && !statics ? (
<Spinner size="tiny" label="加载中..." />
) : statics ? (
<>
<div className={styles.statRow}>
<Text className={styles.labelText}>稿:</Text>
<Badge size="small" appearance="outline" color="brand">{statics.posts}</Badge>
</div>
<div className={styles.statRow}>
<Text className={styles.labelText}>:</Text>
<Badge size="small" appearance="outline" color="brand">{statics.comments}</Badge>
</div>
<div className={styles.statRow}>
<Text className={styles.labelText}>:</Text>
<Badge size="small" appearance="outline" color="brand">{statics.images}</Badge>
</div>
</>
) : (
<Text className={styles.labelText} italic></Text>
)}
</div>
</Card>
</div>
);
};
export default StatusDisplay;