diff --git a/src/components/business/PostCard/PostCard.tsx b/src/components/business/PostCard/PostCard.tsx index 3ba632c..498f4be 100644 --- a/src/components/business/PostCard/PostCard.tsx +++ b/src/components/business/PostCard/PostCard.tsx @@ -474,6 +474,13 @@ const PostCardInner: React.FC = (normalizedProps) => { }; const getResponsiveMaxLength = () => { + // 搜索高亮模式下展示更多内容 + if (highlightKeyword) { + if (isWideScreen) return 500; + if (isDesktop) return 400; + if (isTablet) return 350; + return 220; + } if (isWideScreen) return 300; if (isDesktop) return 250; if (isTablet) return 200; @@ -481,15 +488,53 @@ const PostCardInner: React.FC = (normalizedProps) => { }; const contentNumberOfLines = useMemo(() => { + if (highlightKeyword) { + if (isWideScreen) return 12; + if (isDesktop) return 10; + if (isTablet) return 8; + return 5; + } if (isWideScreen) return 8; if (isDesktop) return 6; if (isTablet) return 5; return 3; - }, [isWideScreen, isDesktop, isTablet]); + }, [isWideScreen, isDesktop, isTablet, highlightKeyword]); + + const getSearchExcerpt = (value: string, keyword: string): string => { + const maxLength = getResponsiveMaxLength(); + if (value.length <= maxLength) return value; + + const lowerValue = value.toLowerCase(); + const lowerKeyword = keyword.toLowerCase(); + const index = lowerValue.indexOf(lowerKeyword); + + if (index === -1) { + return `${value.substring(0, maxLength)}...`; + } + + const context = Math.floor((maxLength - keyword.length) / 2); + let start = Math.max(0, index - context); + let end = Math.min(value.length, index + keyword.length + context); + + if (start > 0) { + while (start > 0 && value[start] !== ' ' && value[start] !== '\n') start--; + if (value[start] === ' ' || value[start] === '\n') start++; + } + if (end < value.length) { + while (end < value.length && value[end] !== ' ' && value[end] !== '\n') end++; + } + + const prefix = start > 0 ? '...' : ''; + const suffix = end < value.length ? '...' : ''; + return `${prefix}${value.substring(start, end)}${suffix}`; + }; const getTruncatedContent = (value: string): string => { const maxLength = getResponsiveMaxLength(); if (value.length <= maxLength || isExpanded) return value; + if (highlightKeyword) { + return getSearchExcerpt(value, highlightKeyword); + } return `${value.substring(0, maxLength)}...`; }; @@ -666,6 +711,8 @@ const PostCardInner: React.FC = (normalizedProps) => { numberOfLines={isExpanded ? undefined : contentNumberOfLines} style={styles.contentRenderer} textStyle={styles.content} + highlightKeyword={highlightKeyword} + highlightStyle={styles.highlight} /> ) : ( void; style?: any; textStyle?: any; + highlightKeyword?: string; + highlightStyle?: StyleProp; } const PostContentRenderer: React.FC = ({ @@ -30,13 +33,19 @@ const PostContentRenderer: React.FC = ({ onPostRefPress, style, textStyle, + highlightKeyword, + highlightStyle, }) => { const colors = useAppColors(); if (!segments || segments.length === 0) { return ( - {content || ''} + {highlightKeyword && content ? ( + + ) : ( + content || '' + )} ); } @@ -53,7 +62,11 @@ const PostContentRenderer: React.FC = ({ if (text) { elements.push( - {text} + {highlightKeyword ? ( + + ) : ( + text + )} ); } @@ -126,7 +139,11 @@ const PostContentRenderer: React.FC = ({ if (elements.length === 0) { return ( - {content || ''} + {highlightKeyword && content ? ( + + ) : ( + content || '' + )} ); } diff --git a/src/components/business/SearchBar.tsx b/src/components/business/SearchBar.tsx index 2ccc101..225a4e3 100644 --- a/src/components/business/SearchBar.tsx +++ b/src/components/business/SearchBar.tsx @@ -24,22 +24,22 @@ function createSearchBarStyles(colors: AppColors, compact: boolean) { container: { flexDirection: 'row', alignItems: 'center', - backgroundColor: 'transparent', + backgroundColor: `${colors.primary.main}08`, borderRadius: borderRadius.full, - paddingHorizontal: compact ? spacing.sm : spacing.xs, - height: compact ? 38 : 46, - borderWidth: compact ? 1 : 1, - borderColor: colors.divider, + paddingHorizontal: compact ? spacing.sm : spacing.md, + height: compact ? 38 : 48, + borderWidth: 1.5, + borderColor: 'transparent', }, containerFocused: { - borderColor: colors.primary.main, - backgroundColor: 'transparent', + borderColor: `${colors.primary.main}50`, + backgroundColor: `${colors.primary.main}10`, }, searchIconWrap: { - width: compact ? 22 : 30, - height: compact ? 22 : 30, - marginLeft: compact ? 2 : spacing.xs, - marginRight: compact ? 6 : spacing.xs, + width: compact ? 22 : 32, + height: compact ? 22 : 32, + marginLeft: compact ? 2 : 0, + marginRight: compact ? 6 : spacing.sm, borderRadius: borderRadius.full, backgroundColor: 'transparent', alignItems: 'center', @@ -50,17 +50,18 @@ function createSearchBarStyles(colors: AppColors, compact: boolean) { }, input: { flex: 1, - fontSize: compact ? fontSizes.md : fontSizes.md, + fontSize: compact ? fontSizes.md : fontSizes.lg, color: colors.text.primary, - paddingVertical: compact ? 0 : spacing.sm + 1, - paddingHorizontal: compact ? 2 : spacing.xs, + paddingVertical: compact ? 0 : spacing.sm, + paddingHorizontal: compact ? 2 : 0, + fontWeight: '500', }, clearButton: { - width: 24, - height: 24, - marginHorizontal: spacing.xs, + width: 22, + height: 22, + marginLeft: spacing.xs, borderRadius: borderRadius.full, - backgroundColor: `${colors.text.secondary}14`, + backgroundColor: `${colors.text.secondary}20`, alignItems: 'center', justifyContent: 'center', }, diff --git a/src/screens/home/SearchScreen.tsx b/src/screens/home/SearchScreen.tsx index a8c06e5..d97b225 100644 --- a/src/screens/home/SearchScreen.tsx +++ b/src/screens/home/SearchScreen.tsx @@ -265,7 +265,7 @@ export const SearchScreen: React.FC = ({ onBack }) => { post={post} onAction={(action) => handlePostAction(post, action)} variant="list" - features={isMobile ? 'compact' : 'full'} + features="full" highlightKeyword={currentKeyword} /> ))} @@ -288,7 +288,7 @@ export const SearchScreen: React.FC = ({ onBack }) => { post={item} onAction={(action) => handlePostAction(item, action)} variant="list" - features="compact" + features="full" highlightKeyword={currentKeyword} /> )} @@ -542,11 +542,12 @@ export const SearchScreen: React.FC = ({ onBack }) => { activeIndex={activeIndex} onTabChange={(index) => { setActiveIndex(index); - // 如果已经搜索过,切换Tab时重新搜索 if (currentKeyword && hasSearched) { performSearch(currentKeyword); } }} + variant="modern" + icons={['file-document-outline', 'account-outline']} /> @@ -571,22 +572,12 @@ function createSearchScreenStyles(colors: AppColors) { }, searchShell: { flex: 1, - borderRadius: borderRadius.xl, - backgroundColor: `${colors.primary.main}08`, - paddingHorizontal: spacing.xs, - paddingVertical: spacing.xs, - // 移除阴影效果 - shadowColor: 'transparent', - shadowOffset: { width: 0, height: 0 }, - shadowOpacity: 0, - shadowRadius: 0, - elevation: 0, }, cancelButton: { - backgroundColor: `${colors.primary.main}14`, + backgroundColor: `${colors.primary.main}12`, borderRadius: borderRadius.full, paddingHorizontal: spacing.md, - paddingVertical: spacing.xs, + paddingVertical: spacing.sm, }, cancelText: { fontSize: fontSizes.md, @@ -610,7 +601,7 @@ function createSearchScreenStyles(colors: AppColors) { marginBottom: spacing.sm, }, sectionTitle: { - fontWeight: '600', + fontWeight: '700', color: colors.text.primary, }, tagContainer: { @@ -620,13 +611,14 @@ function createSearchScreenStyles(colors: AppColors) { tag: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.background.paper, - borderRadius: borderRadius.lg, - borderWidth: 1, - borderColor: colors.divider, + backgroundColor: `${colors.primary.main}10`, + borderRadius: borderRadius.full, + borderWidth: 0, }, tagText: { marginLeft: spacing.xs, + color: colors.primary.main, + fontWeight: '500', }, userCard: { backgroundColor: colors.background.paper, diff --git a/src/screens/message/GroupInfoScreen.tsx b/src/screens/message/GroupInfoScreen.tsx index 3133d97..f274ca7 100644 --- a/src/screens/message/GroupInfoScreen.tsx +++ b/src/screens/message/GroupInfoScreen.tsx @@ -297,6 +297,11 @@ const GroupInfoScreen: React.FC = () => { await groupService.setGroupAvatar(groupId, editAvatar); } + // 如果描述有变化,更新群描述 + if (editDescription !== (group?.description || '')) { + await groupService.setGroupDescription(groupId, editDescription.trim()); + } + // 重新获取群信息 const updatedGroup = await groupManager.getGroup(groupId, true); setGroup(updatedGroup); diff --git a/src/screens/message/components/ChatScreen/styles.ts b/src/screens/message/components/ChatScreen/styles.ts index 62d7655..cc1ad71 100644 --- a/src/screens/message/components/ChatScreen/styles.ts +++ b/src/screens/message/components/ChatScreen/styles.ts @@ -297,7 +297,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { }, senderName: { fontSize: 14, - color: colors.chat.textSecondary, // 使用主题次要文字颜色 + color: textPrimary, marginBottom: 4, marginLeft: 4, fontWeight: '600', diff --git a/src/screens/profile/UserProfileScreen.tsx b/src/screens/profile/UserProfileScreen.tsx index be27e4c..80c9e11 100644 --- a/src/screens/profile/UserProfileScreen.tsx +++ b/src/screens/profile/UserProfileScreen.tsx @@ -104,7 +104,7 @@ export const UserProfileScreen: React.FC = ({ mode, user ); diff --git a/src/services/message/groupService.ts b/src/services/message/groupService.ts index 27a34a1..cdf0dd4 100644 --- a/src/services/message/groupService.ts +++ b/src/services/message/groupService.ts @@ -301,6 +301,20 @@ class GroupService { return normalizeGroup(response.data); } + /** + * 设置群描述 + * PUT /api/v1/groups/:id/description + */ + async setGroupDescription(groupId: string, description: string): Promise { + const response = await api.put( + `/groups/${encodeURIComponent(groupId)}/description`, + { + description, + } + ); + return normalizeGroup(response.data); + } + // ==================== 群公告 ==================== /**