Просмотр исходного кода

feat: Improve mobile text truncation and sidebar visibility

CalciumIon 1 год назад
Родитель
Сommit
4ce12ea6e3
3 измененных файлов с 78 добавлено и 10 удалено
  1. 6 6
      web/src/components/SiderBar.js
  2. 68 1
      web/src/helpers/render.js
  3. 4 3
      web/src/pages/Playground/Playground.js

+ 6 - 6
web/src/components/SiderBar.js

@@ -80,7 +80,7 @@ const SiderBar = () => {
         itemKey: 'channel',
         to: '/channel',
         icon: <IconLayers />,
-        className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
+        className: isAdmin() ? '' : 'tableHiddle',
       },
       {
         text: t('聊天'),
@@ -101,7 +101,7 @@ const SiderBar = () => {
         icon: <IconCalendarClock />,
         className:
           localStorage.getItem('enable_data_export') === 'true'
-            ? 'semi-navigation-item-normal'
+            ? ''
             : 'tableHiddle',
       },
       {
@@ -109,7 +109,7 @@ const SiderBar = () => {
         itemKey: 'redemption',
         to: '/redemption',
         icon: <IconGift />,
-        className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
+        className: isAdmin() ? '' : 'tableHiddle',
       },
       {
         text: t('钱包'),
@@ -122,7 +122,7 @@ const SiderBar = () => {
         itemKey: 'user',
         to: '/user',
         icon: <IconUser />,
-        className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
+        className: isAdmin() ? '' : 'tableHiddle',
       },
       {
         text: t('日志'),
@@ -137,7 +137,7 @@ const SiderBar = () => {
         icon: <IconImage />,
         className:
           localStorage.getItem('enable_drawing') === 'true'
-            ? 'semi-navigation-item-normal'
+            ? ''
             : 'tableHiddle',
       },
       {
@@ -147,7 +147,7 @@ const SiderBar = () => {
         icon: <IconChecklistStroked />,
         className:
             localStorage.getItem('enable_task') === 'true'
-                ? 'semi-navigation-item-normal'
+                ? ''
                 : 'tableHiddle',
       },
       {

+ 68 - 1
web/src/helpers/render.js

@@ -1,6 +1,6 @@
 import i18next from 'i18next';
 import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
-import { copy, showSuccess } from './utils.js';
+import { copy, isMobile, showSuccess } from './utils.js';
 
 export function renderText(text, limit) {
   if (text.length > limit) {
@@ -67,6 +67,73 @@ export function renderRatio(ratio) {
   return <Tag color={color}>{ratio}x {i18next.t('倍率')}</Tag>;
 }
 
+const measureTextWidth = (text, style = {
+  fontSize: '14px',
+  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
+}, containerWidth) => {
+  const span = document.createElement('span');
+  
+  span.style.visibility = 'hidden';
+  span.style.position = 'absolute';
+  span.style.whiteSpace = 'nowrap';
+  span.style.fontSize = style.fontSize;
+  span.style.fontFamily = style.fontFamily;
+  
+  span.textContent = text;
+  
+  document.body.appendChild(span);
+  const width = span.offsetWidth;
+  
+  document.body.removeChild(span);
+  
+  return width;
+};
+
+export function truncateText(text, maxWidth = 200) {
+  if (!isMobile()) {
+    return text;
+  }
+  if (!text) return text;
+  
+  try {
+    // Handle percentage-based maxWidth
+    let actualMaxWidth = maxWidth;
+    if (typeof maxWidth === 'string' && maxWidth.endsWith('%')) {
+      const percentage = parseFloat(maxWidth) / 100;
+      // Use window width as fallback container width
+      actualMaxWidth = window.innerWidth * percentage;
+    }
+    
+    const width = measureTextWidth(text);
+    if (width <= actualMaxWidth) return text;
+    
+    let left = 0;
+    let right = text.length;
+    let result = text;
+    
+    while (left <= right) {
+      const mid = Math.floor((left + right) / 2);
+      const truncated = text.slice(0, mid) + '...';
+      const currentWidth = measureTextWidth(truncated);
+      
+      if (currentWidth <= actualMaxWidth) {
+        result = truncated;
+        left = mid + 1;
+      } else {
+        right = mid - 1;
+      }
+    }
+    
+    return result;
+  } catch (error) {
+    console.warn('Text measurement failed, falling back to character count', error);
+    if (text.length > 20) {
+      return text.slice(0, 17) + '...';
+    }
+    return text;
+  }
+}
+
 export const renderGroupOption = (item) => {
   const {
     disabled,

+ 4 - 3
web/src/pages/Playground/Playground.js

@@ -7,7 +7,7 @@ import { SSE } from 'sse';
 import { IconSetting } from '@douyinfe/semi-icons';
 import { StyleContext } from '../../context/Style/index.js';
 import { useTranslation } from 'react-i18next';
-import { renderGroupOption } from '../../helpers/render.js';
+import { renderGroupOption, truncateText } from '../../helpers/render.js';
 
 const roleInfo = {
   user: {
@@ -99,9 +99,10 @@ const Playground = () => {
     const { success, message, data } = res.data;
     if (success) {
       let localGroupOptions = Object.entries(data).map(([group, info]) => ({
-        label: info.desc,
+        label: truncateText(info.desc, "50%"),
         value: group,
-        ratio: info.ratio
+        ratio: info.ratio,
+        fullLabel: info.desc // 保存完整文本用于tooltip
       }));
 
       if (localGroupOptions.length === 0) {