diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/frontend.iml b/.idea/frontend.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/frontend.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..6752ffb
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,448 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..ade1ecd
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f3d93d7
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/business/SystemMessageItem.tsx b/src/components/business/SystemMessageItem.tsx
index d8f0b9b..64894ec 100644
--- a/src/components/business/SystemMessageItem.tsx
+++ b/src/components/business/SystemMessageItem.tsx
@@ -13,7 +13,6 @@ import { colors, spacing, borderRadius } from '../../theme';
import { SystemMessageResponse, SystemMessageType } from '../../types/dto';
import Text from '../common/Text';
import Avatar from '../common/Avatar';
-import { useResponsive, useResponsiveValue } from '../../hooks/useResponsive';
interface SystemMessageItemProps {
message: SystemMessageResponse;
@@ -165,14 +164,6 @@ const SystemMessageItem: React.FC = ({
onRequestAction,
requestActionLoading = false,
}) => {
- // 响应式适配
- const { isWideScreen, isDesktop } = useResponsive();
- const containerPadding = useResponsiveValue({ xs: spacing.md, lg: spacing.lg, xl: spacing.xl });
- const avatarSize = useResponsiveValue({ xs: 40, lg: 48, xl: 52 });
- const iconSize = useResponsiveValue({ xs: 20, lg: 22, xl: 24 });
- const contentFontSize = useResponsiveValue({ xs: 14, lg: 15, xl: 16 });
- const titleFontSize = useResponsiveValue({ xs: 14, lg: 15, xl: 16 });
- const timeFontSize = useResponsiveValue({ xs: 12, lg: 13, xl: 14 });
// 格式化时间
const formatTime = (dateString: string): string => {
try {
@@ -198,7 +189,15 @@ const SystemMessageItem: React.FC = ({
requestStatus === 'pending' &&
!!onRequestAction;
- const styles = React.useMemo(() => StyleSheet.create({
+ // 使用固定的响应式值,兼容移动端和Web端
+ const containerPadding = spacing.md;
+ const avatarSize = 44;
+ const iconSize = 22;
+ const contentFontSize = 14;
+ const titleFontSize = 14;
+ const timeFontSize = 12;
+
+ const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'flex-start',
@@ -208,7 +207,7 @@ const SystemMessageItem: React.FC = ({
borderBottomColor: colors.divider,
},
iconContainer: {
- marginRight: isWideScreen ? spacing.lg : spacing.md,
+ marginRight: spacing.md,
},
iconWrapper: {
width: avatarSize,
@@ -220,6 +219,7 @@ const SystemMessageItem: React.FC = ({
content: {
flex: 1,
justifyContent: 'center',
+ minWidth: 0,
},
titleRow: {
flexDirection: 'row',
@@ -237,7 +237,7 @@ const SystemMessageItem: React.FC = ({
fontSize: timeFontSize,
},
messageContent: {
- lineHeight: isWideScreen ? 22 : 18,
+ lineHeight: 20,
fontSize: contentFontSize,
},
extraInfo: {
@@ -245,7 +245,7 @@ const SystemMessageItem: React.FC = ({
alignItems: 'center',
marginTop: spacing.xs,
backgroundColor: colors.background.default,
- paddingHorizontal: isWideScreen ? spacing.md : spacing.sm,
+ paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
borderRadius: borderRadius.sm,
alignSelf: 'flex-start',
@@ -259,8 +259,8 @@ const SystemMessageItem: React.FC = ({
marginTop: spacing.sm,
},
actionBtn: {
- paddingVertical: isWideScreen ? spacing.sm : spacing.xs,
- paddingHorizontal: isWideScreen ? spacing.lg : spacing.md,
+ paddingVertical: spacing.xs,
+ paddingHorizontal: spacing.md,
borderRadius: borderRadius.sm,
borderWidth: 1,
marginRight: spacing.sm,
@@ -278,8 +278,9 @@ const SystemMessageItem: React.FC = ({
justifyContent: 'center',
alignItems: 'center',
height: 20,
+ width: 24,
},
- }), [containerPadding, isWideScreen, avatarSize, titleFontSize, timeFontSize, contentFontSize]);
+ });
// 判断是否显示操作者头像
const showOperatorAvatar = ['follow', 'like_post', 'like_comment', 'like_reply', 'favorite_post', 'comment', 'reply', 'mention', 'group_join_apply'].includes(
diff --git a/src/screens/message/NotificationsScreen.tsx b/src/screens/message/NotificationsScreen.tsx
index c3ca10a..6a05791 100644
--- a/src/screens/message/NotificationsScreen.tsx
+++ b/src/screens/message/NotificationsScreen.tsx
@@ -13,6 +13,7 @@ import {
TouchableOpacity,
RefreshControl,
ActivityIndicator,
+ Dimensions,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useIsFocused } from '@react-navigation/native';
@@ -25,7 +26,6 @@ import { messageService } from '../../services/messageService';
import { commentService } from '../../services/commentService';
import { SystemMessageItem } from '../../components/business';
import { Text, EmptyState, ResponsiveContainer } from '../../components/common';
-import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive';
import { RootStackParamList } from '../../navigation/types';
import { useMessageManagerSystemUnreadCount, useUserStore } from '../../stores';
@@ -48,9 +48,27 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
const { setSystemUnreadCount, decrementSystemUnreadCount } = useMessageManagerSystemUnreadCount();
const navigation = useNavigation>();
- // 响应式布局
- const { isDesktop, isTablet } = useResponsive();
- const isWideScreen = useBreakpointGTE('lg');
+ // 修复竞态条件:使用本地状态管理窗口尺寸,避免 hydration 问题
+ const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
+ const [isHydrated, setIsHydrated] = useState(false);
+
+ useEffect(() => {
+ // 延迟设置窗口尺寸,确保在客户端完全加载后再获取
+ const timer = setTimeout(() => {
+ const { width, height } = Dimensions.get('window');
+ setWindowSize({ width, height });
+ setIsHydrated(true);
+ }, 100);
+ return () => clearTimeout(timer);
+ }, []);
+
+ // 使用本地尺寸状态计算断点 (lg = 768)
+ const isWideScreen = windowSize.width >= 768;
+ const isDesktop = windowSize.width >= 1024;
+ const isTablet = windowSize.width >= 768 && windowSize.width < 1024;
+
+ // Web端使用更大的容器宽度
+ const containerMaxWidth = isDesktop ? 1200 : isTablet ? 1000 : 900;
const [messages, setMessages] = useState([]);
const [activeType, setActiveType] = useState('all');
@@ -320,7 +338,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
const shouldShowBackButton = onBack && !isWideScreen;
return (
-
+
{shouldShowBackButton ? (
void }> = ({ onBack
// 渲染空状态
const renderEmpty = () => (
-
+
+
+
);
+ // 如果还未hydrated,显示加载占位符,避免竞态条件导致的渲染问题
+ if (!isHydrated) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
{isWideScreen ? (
-
+
{/* 头部 - 大屏模式下隐藏返回按钮 */}
{renderHeader()}
{/* 分类筛选 */}
-
+
{MESSAGE_TYPES.map(type => {
const count = type.key === 'all'
? displayMessages.length
@@ -518,10 +549,11 @@ const styles = StyleSheet.create({
borderBottomWidth: 1,
borderBottomColor: colors.divider,
},
- filterContainerWide: {
+ filterContainerWideWeb: {
paddingHorizontal: spacing.xl,
paddingVertical: spacing.md,
justifyContent: 'center',
+ backgroundColor: colors.background.paper,
},
filterTag: {
flexDirection: 'row',
@@ -596,4 +628,14 @@ const styles = StyleSheet.create({
loadingMoreText: {
marginLeft: spacing.sm,
},
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ emptyContainerWeb: {
+ minHeight: 500,
+ justifyContent: 'center',
+ paddingTop: 100,
+ },
});
diff --git a/src/screens/schedule/ScheduleScreen.tsx b/src/screens/schedule/ScheduleScreen.tsx
index f73a915..b9e44b0 100644
--- a/src/screens/schedule/ScheduleScreen.tsx
+++ b/src/screens/schedule/ScheduleScreen.tsx
@@ -30,6 +30,7 @@ import {
TimeSlot,
getCourseColor,
hasCourseInWeek,
+ getCurrentWeek,
} from '../../types/schedule';
import type { ScheduleStackParamList } from '../../navigation/types';
import { scheduleService } from '../../services/scheduleService';
@@ -130,9 +131,17 @@ const getWeekDates = (weekOffset: number = 0) => {
return getWeekInfo(weekOffset).dates;
};
+// 学期起始日期字符串(用于 getCurrentWeek 函数)
+const SEMESTER_START_DATE = '2026-03-09';
+
+// 计算当前实际周数
+const getInitialWeek = (): number => {
+ return getCurrentWeek(SEMESTER_START_DATE, TOTAL_WEEKS);
+};
+
export const ScheduleScreen: React.FC = () => {
const navigation = useNavigation>();
- const [currentWeek, setCurrentWeek] = useState(1);
+ const [currentWeek, setCurrentWeek] = useState(getInitialWeek);
const [courses, setCourses] = useState([]);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
const [pendingDay, setPendingDay] = useState(0);
@@ -163,8 +172,10 @@ export const ScheduleScreen: React.FC = () => {
};
}, []);
const todayColumnIndex = getTodayColumnIndex();
- // currentWeek === 1 对应今天所在的真实周(offset 0),其他周不高亮今日列
- const isViewingCurrentWeek = currentWeek === 1;
+ // 获取当前实际周数,用于判断是否在查看当前周
+ const actualCurrentWeek = getInitialWeek();
+ // 只有在查看当前实际周时才高亮今日列
+ const isViewingCurrentWeek = currentWeek === actualCurrentWeek;
const activeTodayColumn = isViewingCurrentWeek ? todayColumnIndex : -1;
const dayColumnWidth = getDayColumnWidth();
diff --git a/src/services/scheduleService.ts b/src/services/scheduleService.ts
index 875d20c..1b6c551 100644
--- a/src/services/scheduleService.ts
+++ b/src/services/scheduleService.ts
@@ -45,22 +45,42 @@ interface SyncScheduleResponse {
error_message?: string;
}
-const toCourse = (dto: ScheduleCourseDTO): Course => ({
- id: dto.id,
- name: dto.name,
- teacher: dto.teacher,
- location: dto.location,
- dayOfWeek: dto.day_of_week,
- startSection: dto.start_section,
- endSection: dto.end_section,
- weeks: dto.weeks ?? [],
- color: dto.color,
-});
+const toCourse = (dto: ScheduleCourseDTO): Course => {
+ // 调试日志:检查后端返回的 weeks 字段
+ console.log('[ScheduleService] 转换课程 DTO:', {
+ id: dto.id,
+ name: dto.name,
+ day_of_week: dto.day_of_week,
+ weeks: dto.weeks,
+ weeksType: typeof dto.weeks,
+ weeksLength: dto.weeks?.length,
+ });
+ return {
+ id: dto.id,
+ name: dto.name,
+ teacher: dto.teacher,
+ location: dto.location,
+ dayOfWeek: dto.day_of_week,
+ startSection: dto.start_section,
+ endSection: dto.end_section,
+ weeks: dto.weeks ?? [],
+ color: dto.color,
+ };
+};
class ScheduleService {
async getCourses(week?: number): Promise {
const params = week ? { week } : undefined;
+ console.log('[ScheduleService] 获取课程列表, params:', params);
const response = await api.get('/schedule/courses', params);
+ console.log('[ScheduleService] 课程列表响应, 数量:', response.data.list.length);
+ // 打印原始响应数据中的 weeks 字段
+ response.data.list.forEach((item, index) => {
+ console.log(`[ScheduleService] 课程[${index}] ${item.name}:`, {
+ day_of_week: item.day_of_week,
+ weeks: item.weeks,
+ });
+ });
return response.data.list.map(toCourse);
}
@@ -79,7 +99,9 @@ class ScheduleService {
}
async syncSchedule(req: SyncScheduleRequest): Promise {
+ console.log('[ScheduleService] 开始同步教务系统, username:', req.username);
const response = await api.post('/schedule/sync', req);
+ console.log('[ScheduleService] 同步响应:', response.data);
return response.data;
}
}