From a47995ea2816798d5c088d4a1c61620982639d79 Mon Sep 17 00:00:00 2001 From: yokowu <18836617@qq.com> Date: Mon, 4 Aug 2025 18:28:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=83=AD?= =?UTF-8?q?=E5=8A=9B=E5=9B=BE=E9=A2=9C=E8=89=B2=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/dashboard/components/memberInfo.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/ui/src/pages/dashboard/components/memberInfo.tsx b/ui/src/pages/dashboard/components/memberInfo.tsx index 7b1ef53..f76eea0 100644 --- a/ui/src/pages/dashboard/components/memberInfo.tsx +++ b/ui/src/pages/dashboard/components/memberInfo.tsx @@ -21,7 +21,6 @@ const getRecent1YearData = ( data: DomainUserHeatmap[] = [], max_count: number ) => { - const average = max_count / 5; const today = dayjs(); const lastYearToday = today.subtract(1, 'year'); const diffInDays = today.diff(lastYearToday, 'day'); @@ -31,17 +30,22 @@ const getRecent1YearData = ( dateMap[dayjs.unix(item.date!).format('YYYY-MM-DD')] = item.count!; }); + const getLevel = (count: number) => { + if (count === 0) return 0; + if (count === 1) return 1; + if (count <= Math.max(2, max_count * 0.25)) return 2; + if (count <= Math.max(3, max_count * 0.6)) return 3; + return 4; + }; + for (let i = 0; i < diffInDays; i++) { const time = today.subtract(i, 'day').format('YYYY-MM-DD'); - if (dateMap[time]) { - result.unshift({ - count: dateMap[time], - date: time, - level: Math.ceil(dateMap[time] / average) - 1, - }); - } else { - result.unshift({ count: 0, date: time, level: 0 }); - } + const count = dateMap[time] || 0; + result.unshift({ + count, + date: time, + level: getLevel(count), + }); } return result; }; @@ -159,11 +163,11 @@ const MemberInfo = ({ blockSize={blockSize} theme={{ light: [ - '#fff', - theme.palette.grey[300], - theme.palette.grey[500], - theme.palette.grey[700], - theme.palette.grey[900], + theme.palette.grey[200], // level 0: 无使用 - 浅灰色背景 + theme.palette.grey[400], // level 1: 轻度使用 - 浅灰色 + theme.palette.grey[600], // level 2: 中度使用 - 中灰色 + theme.palette.grey[800], // level 3: 高度使用 - 深灰色 + theme.palette.grey[900], // level 4: 重度使用 - 最深灰色 ], }} labels={{ From 07dcd0dbb7b94c619ca1c595bd22e7909a153914 Mon Sep 17 00:00:00 2001 From: yokowu <18836617@qq.com> Date: Mon, 4 Aug 2025 18:54:25 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=83=AD?= =?UTF-8?q?=E5=8A=9B=E5=9B=BE=E5=9C=A8=E5=B0=8F=E5=B1=8F=E5=B9=95=E4=B8=8A?= =?UTF-8?q?=E7=9A=84=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/dashboard/components/memberInfo.tsx | 142 ++++++++++++++++-- 1 file changed, 126 insertions(+), 16 deletions(-) diff --git a/ui/src/pages/dashboard/components/memberInfo.tsx b/ui/src/pages/dashboard/components/memberInfo.tsx index f76eea0..7d01be5 100644 --- a/ui/src/pages/dashboard/components/memberInfo.tsx +++ b/ui/src/pages/dashboard/components/memberInfo.tsx @@ -1,10 +1,9 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import Card from '@/components/card'; import dayjs from 'dayjs'; import { Box, Stack, - Typography, Tooltip, useTheme, IconButton, @@ -50,6 +49,113 @@ const getRecent1YearData = ( return result; }; +// 自定义Hook:处理ActivityCalendar自动滚动 +const useActivityCalendarAutoScroll = () => { + const scrollToLatest = useCallback(() => { + // 尝试多种可能的选择器策略 + const selectors = [ + '.react-activity-calendar__scroll-container', + '.react-activity-calendar [style*="overflow"]', + '.react-activity-calendar > div:first-child', + ]; + + let scrollContainer: HTMLElement | null = null; + + // 按优先级尝试找到滚动容器 + for (const selector of selectors) { + scrollContainer = document.querySelector(selector); + if (scrollContainer && scrollContainer.scrollWidth > scrollContainer.clientWidth) { + break; + } + } + + if (scrollContainer) { + // 滚动到最右侧(最新数据) + scrollContainer.scrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth; + } + }, []); + + const setupAutoScroll = useCallback(() => { + // 延迟执行确保组件完全渲染 + const timeoutId = setTimeout(scrollToLatest, 100); + + // 使用ResizeObserver监听容器大小变化 + let resizeObserver: ResizeObserver | null = null; + + const setupResizeObserver = () => { + const container = document.querySelector('.react-activity-calendar'); + if (container && 'ResizeObserver' in window) { + resizeObserver = new ResizeObserver(() => { + scrollToLatest(); + }); + resizeObserver.observe(container); + } else { + // 降级方案:使用window resize事件 + window.addEventListener('resize', scrollToLatest); + } + }; + + // 延迟设置ResizeObserver确保DOM已渲染 + const observerTimeoutId = setTimeout(setupResizeObserver, 150); + + // 清理函数 + return () => { + clearTimeout(timeoutId); + clearTimeout(observerTimeoutId); + if (resizeObserver) { + resizeObserver.disconnect(); + } else { + window.removeEventListener('resize', scrollToLatest); + } + }; + }, [scrollToLatest]); + + return { setupAutoScroll }; +}; + +// 简化的blockSize计算Hook - 保持原有大小 +const useBlockSize = (containerRef: React.RefObject) => { + const [blockSize, setBlockSize] = useState(8); + + useEffect(() => { + const calculateBlockSize = () => { + if (!containerRef.current) return; + + const containerWidth = containerRef.current.offsetWidth; + const baseWidth = 980; + const blockIncrement = 54; + + // 只在桌面端进行计算,保持原有逻辑 + const increment = Math.max(0, Math.ceil((containerWidth - baseWidth) / blockIncrement)); + setBlockSize(increment + 8); + }; + + // 初始计算 + calculateBlockSize(); + + // 使用ResizeObserver监听容器大小变化 + let resizeObserver: ResizeObserver | null = null; + + if ('ResizeObserver' in window && containerRef.current) { + resizeObserver = new ResizeObserver(calculateBlockSize); + resizeObserver.observe(containerRef.current); + } else { + // 降级方案 + window.addEventListener('resize', calculateBlockSize); + } + + return () => { + if (resizeObserver) { + resizeObserver.disconnect(); + } else { + window.removeEventListener('resize', calculateBlockSize); + } + }; + }, [containerRef]); + + return blockSize; +}; + const MemberInfo = ({ data, memberData, @@ -62,22 +168,27 @@ const MemberInfo = ({ onMemberChange?: (data: DomainUser) => void; }) => { const theme = useTheme(); - const [blockSize, setBlockSize] = useState(8); const ref = useRef(null); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); + + // 使用自定义Hooks + const blockSize = useBlockSize(ref as React.RefObject); + const { setupAutoScroll } = useActivityCalendarAutoScroll(); + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; + const handleClose = () => { setAnchorEl(null); }; + + // 设置自动滚动 useEffect(() => { - const innerWidth = ref.current?.offsetWidth; - const dis = Math.max(0, Math.ceil((innerWidth! - 980) / 54)); - console.log(dis); - setBlockSize(dis + 8); - }, []); + const cleanup = setupAutoScroll(); + return cleanup; + }, [setupAutoScroll, data, memberData]); return ( (