Browse Source

🕒 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`.
t0ng7u 6 months ago
parent
commit
6dcf954bfe

+ 20 - 17
web/src/components/dashboard/AnnouncementsPanel.jsx

@@ -68,26 +68,29 @@ const AnnouncementsPanel = ({
     >
       <ScrollableContainer maxHeight="24rem">
         {announcementData.length > 0 ? (
-          <Timeline mode="alternate">
-            {announcementData.map((item, idx) => (
-              <Timeline.Item
-                key={idx}
-                type={item.type || 'default'}
-                time={item.time}
-              >
-                <div>
-                  <div
-                    dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
-                  />
-                  {item.extra && (
+          <Timeline mode="left">
+            {announcementData.map((item, idx) => {
+              const htmlExtra = item.extra ? marked.parse(item.extra) : '';
+              return (
+                <Timeline.Item
+                  key={idx}
+                  type={item.type || 'default'}
+                  time={`${item.relative ? item.relative + ' ' : ''}${item.time}`}
+                  extra={item.extra ? (
                     <div
                       className="text-xs text-gray-500"
-                      dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
+                      dangerouslySetInnerHTML={{ __html: htmlExtra }}
                     />
-                  )}
-                </div>
-              </Timeline.Item>
-            ))}
+                  ) : null}
+                >
+                  <div>
+                    <div
+                      dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
+                    />
+                  </div>
+                </Timeline.Item>
+              );
+            })}
           </Timeline>
         ) : (
           <div className="flex justify-center items-center py-8">

+ 12 - 4
web/src/components/dashboard/index.jsx

@@ -108,10 +108,18 @@ const Dashboard = () => {
 
   // ========== 数据准备 ==========
   const apiInfoData = statusState?.status?.api_info || [];
-  const announcementData = (statusState?.status?.announcements || []).map(item => ({
-    ...item,
-    time: getRelativeTime(item.publishDate)
-  }));
+  const announcementData = (statusState?.status?.announcements || []).map(item => {
+    const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
+    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 uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(([status, info]) => ({

+ 24 - 18
web/src/components/layout/NoticeModal.jsx

@@ -41,14 +41,21 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
   const getKeyForItem = (item) => `${item?.publishDate || ''}-${(item?.content || '').slice(0, 30)}`;
 
   const processedAnnouncements = useMemo(() => {
-    return (announcements || []).slice(0, 20).map(item => ({
-      key: getKeyForItem(item),
-      type: item.type || 'default',
-      time: getRelativeTime(item.publishDate),
-      content: item.content,
-      extra: item.extra,
-      isUnread: unreadSet.has(getKeyForItem(item))
-    }));
+    return (announcements || []).slice(0, 20).map(item => {
+      const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
+      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 || '');
+      return ({
+        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]);
 
   const handleCloseTodayNotice = () => {
@@ -131,7 +138,7 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
 
     return (
       <div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
-        <Timeline mode="alternate">
+        <Timeline mode="left">
           {processedAnnouncements.map((item, idx) => {
             const htmlContent = marked.parse(item.content || '');
             const htmlExtra = item.extra ? marked.parse(item.extra) : '';
@@ -139,7 +146,13 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
               <Timeline.Item
                 key={idx}
                 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 ? '' : ''}
               >
                 <div>
@@ -147,12 +160,6 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
                     className={item.isUnread ? 'shine-text' : ''}
                     dangerouslySetInnerHTML={{ __html: htmlContent }}
                   />
-                  {item.extra && (
-                    <div
-                      className="text-xs text-gray-500"
-                      dangerouslySetInnerHTML={{ __html: htmlExtra }}
-                    />
-                  )}
                 </div>
               </Timeline.Item>
             );
@@ -177,8 +184,7 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
           <Tabs
             activeKey={activeTab}
             onChange={setActiveTab}
-            type='card'
-            size='small'
+            type='button'
           >
             <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' />

+ 1 - 1
web/src/components/table/model-pricing/layout/PricingSidebar.jsx

@@ -92,7 +92,7 @@ const PricingSidebar = ({
     });
 
   return (
-    <div className="p-4">
+    <div className="p-2">
       <div className="flex items-center justify-between mb-6">
         <div className="text-lg font-semibold text-gray-800">
           {t('筛选')}

+ 1 - 1
web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx

@@ -26,7 +26,7 @@ const PricingCardSkeleton = ({
   showRatio = false
 }) => {
   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">
         {Array.from({ length: skeletonCount }).map((_, index) => (
           <Card

+ 1 - 1
web/src/components/table/model-pricing/view/card/PricingCardView.jsx

@@ -202,7 +202,7 @@ const PricingCardView = ({
   }
 
   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">
         {paginatedModels.map((model, index) => {
           const modelKey = getModelKey(model);

+ 1 - 1
web/src/index.css

@@ -754,7 +754,7 @@ html.dark .with-pastel-balls::before {
 }
 
 .pricing-search-header {
-  padding: 1rem;
+  padding: 0.5rem;
   background-color: var(--semi-color-bg-0);
   flex-shrink: 0;
   position: sticky;