🕒 feat(ui): standardize Timelines to left mode and unify time display
- Switch Semi UI Timeline to mode="left" in: - web/src/components/layout/NoticeModal.jsx - web/src/components/dashboard/AnnouncementsPanel.jsx - Show both relative and absolute time in the `time` prop (e.g. "3 days ago 2025-02-18 10:30") - Move auxiliary description to the `extra` prop and remove duplicate rendering from content area - Keep original `extra` data intact; compute and pass: - `time`: absolute time (yyyy-MM-dd HH:mm) - `relative`: relative time (e.g., "3 days ago") - Update data assembly to expose `time` and `relative` without overwriting `extra`: - web/src/components/dashboard/index.jsx - No i18n changes; no linter errors introduced Why: Aligns Timeline layout across the app and clarifies time context by combining relative and absolute timestamps while preserving auxiliary notes via `extra`.
This commit is contained in:
@@ -68,26 +68,29 @@ const AnnouncementsPanel = ({
|
|||||||
>
|
>
|
||||||
<ScrollableContainer maxHeight="24rem">
|
<ScrollableContainer maxHeight="24rem">
|
||||||
{announcementData.length > 0 ? (
|
{announcementData.length > 0 ? (
|
||||||
<Timeline mode="alternate">
|
<Timeline mode="left">
|
||||||
{announcementData.map((item, idx) => (
|
{announcementData.map((item, idx) => {
|
||||||
<Timeline.Item
|
const htmlExtra = item.extra ? marked.parse(item.extra) : '';
|
||||||
key={idx}
|
return (
|
||||||
type={item.type || 'default'}
|
<Timeline.Item
|
||||||
time={item.time}
|
key={idx}
|
||||||
>
|
type={item.type || 'default'}
|
||||||
<div>
|
time={`${item.relative ? item.relative + ' ' : ''}${item.time}`}
|
||||||
<div
|
extra={item.extra ? (
|
||||||
dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
|
|
||||||
/>
|
|
||||||
{item.extra && (
|
|
||||||
<div
|
<div
|
||||||
className="text-xs text-gray-500"
|
className="text-xs text-gray-500"
|
||||||
dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
|
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
||||||
/>
|
/>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
>
|
||||||
</Timeline.Item>
|
<div>
|
||||||
))}
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Timeline.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center items-center py-8">
|
<div className="flex justify-center items-center py-8">
|
||||||
|
|||||||
@@ -108,10 +108,18 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
// ========== 数据准备 ==========
|
// ========== 数据准备 ==========
|
||||||
const apiInfoData = statusState?.status?.api_info || [];
|
const apiInfoData = statusState?.status?.api_info || [];
|
||||||
const announcementData = (statusState?.status?.announcements || []).map(item => ({
|
const announcementData = (statusState?.status?.announcements || []).map(item => {
|
||||||
...item,
|
const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
|
||||||
time: getRelativeTime(item.publishDate)
|
const absoluteTime = pubDate && !isNaN(pubDate.getTime())
|
||||||
}));
|
? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}`
|
||||||
|
: (item?.publishDate || '');
|
||||||
|
const relativeTime = getRelativeTime(item.publishDate);
|
||||||
|
return ({
|
||||||
|
...item,
|
||||||
|
time: absoluteTime,
|
||||||
|
relative: relativeTime
|
||||||
|
});
|
||||||
|
});
|
||||||
const faqData = statusState?.status?.faq || [];
|
const faqData = statusState?.status?.faq || [];
|
||||||
|
|
||||||
const uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(([status, info]) => ({
|
const uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(([status, info]) => ({
|
||||||
|
|||||||
@@ -41,14 +41,21 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
const getKeyForItem = (item) => `${item?.publishDate || ''}-${(item?.content || '').slice(0, 30)}`;
|
const getKeyForItem = (item) => `${item?.publishDate || ''}-${(item?.content || '').slice(0, 30)}`;
|
||||||
|
|
||||||
const processedAnnouncements = useMemo(() => {
|
const processedAnnouncements = useMemo(() => {
|
||||||
return (announcements || []).slice(0, 20).map(item => ({
|
return (announcements || []).slice(0, 20).map(item => {
|
||||||
key: getKeyForItem(item),
|
const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
|
||||||
type: item.type || 'default',
|
const absoluteTime = pubDate && !isNaN(pubDate.getTime())
|
||||||
time: getRelativeTime(item.publishDate),
|
? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}`
|
||||||
content: item.content,
|
: (item?.publishDate || '');
|
||||||
extra: item.extra,
|
return ({
|
||||||
isUnread: unreadSet.has(getKeyForItem(item))
|
key: getKeyForItem(item),
|
||||||
}));
|
type: item.type || 'default',
|
||||||
|
time: absoluteTime,
|
||||||
|
content: item.content,
|
||||||
|
extra: item.extra,
|
||||||
|
relative: getRelativeTime(item.publishDate),
|
||||||
|
isUnread: unreadSet.has(getKeyForItem(item))
|
||||||
|
});
|
||||||
|
});
|
||||||
}, [announcements, unreadSet]);
|
}, [announcements, unreadSet]);
|
||||||
|
|
||||||
const handleCloseTodayNotice = () => {
|
const handleCloseTodayNotice = () => {
|
||||||
@@ -131,7 +138,7 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
|
<div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
|
||||||
<Timeline mode="alternate">
|
<Timeline mode="left">
|
||||||
{processedAnnouncements.map((item, idx) => {
|
{processedAnnouncements.map((item, idx) => {
|
||||||
const htmlContent = marked.parse(item.content || '');
|
const htmlContent = marked.parse(item.content || '');
|
||||||
const htmlExtra = item.extra ? marked.parse(item.extra) : '';
|
const htmlExtra = item.extra ? marked.parse(item.extra) : '';
|
||||||
@@ -139,7 +146,13 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
<Timeline.Item
|
<Timeline.Item
|
||||||
key={idx}
|
key={idx}
|
||||||
type={item.type}
|
type={item.type}
|
||||||
time={item.time}
|
time={`${item.relative ? item.relative + ' ' : ''}${item.time}`}
|
||||||
|
extra={item.extra ? (
|
||||||
|
<div
|
||||||
|
className="text-xs text-gray-500"
|
||||||
|
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
className={item.isUnread ? '' : ''}
|
className={item.isUnread ? '' : ''}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -147,12 +160,6 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
className={item.isUnread ? 'shine-text' : ''}
|
className={item.isUnread ? 'shine-text' : ''}
|
||||||
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
||||||
/>
|
/>
|
||||||
{item.extra && (
|
|
||||||
<div
|
|
||||||
className="text-xs text-gray-500"
|
|
||||||
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
);
|
);
|
||||||
@@ -177,8 +184,7 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
|
|||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
onChange={setActiveTab}
|
onChange={setActiveTab}
|
||||||
type='card'
|
type='button'
|
||||||
size='small'
|
|
||||||
>
|
>
|
||||||
<TabPane tab={<span className="flex items-center gap-1"><Bell size={14} /> {t('通知')}</span>} itemKey='inApp' />
|
<TabPane tab={<span className="flex items-center gap-1"><Bell size={14} /> {t('通知')}</span>} itemKey='inApp' />
|
||||||
<TabPane tab={<span className="flex items-center gap-1"><Megaphone size={14} /> {t('系统公告')}</span>} itemKey='system' />
|
<TabPane tab={<span className="flex items-center gap-1"><Megaphone size={14} /> {t('系统公告')}</span>} itemKey='system' />
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const PricingSidebar = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-2">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="text-lg font-semibold text-gray-800">
|
<div className="text-lg font-semibold text-gray-800">
|
||||||
{t('筛选')}
|
{t('筛选')}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const PricingCardSkeleton = ({
|
|||||||
showRatio = false
|
showRatio = false
|
||||||
}) => {
|
}) => {
|
||||||
const placeholder = (
|
const placeholder = (
|
||||||
<div className="px-4">
|
<div className="px-2">
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4">
|
||||||
{Array.from({ length: skeletonCount }).map((_, index) => (
|
{Array.from({ length: skeletonCount }).map((_, index) => (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ const PricingCardView = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4">
|
<div className="px-2">
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4">
|
||||||
{paginatedModels.map((model, index) => {
|
{paginatedModels.map((model, index) => {
|
||||||
const modelKey = getModelKey(model);
|
const modelKey = getModelKey(model);
|
||||||
|
|||||||
@@ -754,7 +754,7 @@ html.dark .with-pastel-balls::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pricing-search-header {
|
.pricing-search-header {
|
||||||
padding: 1rem;
|
padding: 0.5rem;
|
||||||
background-color: var(--semi-color-bg-0);
|
background-color: var(--semi-color-bg-0);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
|||||||
Reference in New Issue
Block a user