├── bitbit
├── src
│ ├── pages
│ │ ├── Settings.tsx
│ │ ├── ProfileSettings.tsx
│ │ ├── ChatTestPage.tsx
│ │ ├── FollowListPage.tsx
│ │ ├── Login.tsx
│ │ ├── Register.tsx
│ │ ├── Community.tsx
│ │ └── NotificationsNew.tsx
│ ├── shared
│ │ ├── hooks
│ │ │ ├── useToast.ts
│ │ │ ├── index.ts
│ │ │ ├── useMediaQuery.ts
│ │ │ ├── useChatNavigation.ts
│ │ │ └── usePublishStatus.ts
│ │ ├── services
│ │ │ ├── index.ts
│ │ │ └── api.ts
│ │ ├── constants
│ │ │ └── index.ts
│ │ ├── config
│ │ │ ├── index.ts
│ │ │ └── constants.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── cn.ts
│ │ │ ├── date.ts
│ │ │ ├── validation.ts
│ │ │ └── scrollUtils.ts
│ │ ├── index.ts
│ │ ├── components
│ │ │ ├── index.ts
│ │ │ ├── SortSelector
│ │ │ │ └── index.tsx
│ │ │ ├── TabFilter
│ │ │ │ └── index.tsx
│ │ │ ├── SearchBar
│ │ │ │ └── index.tsx
│ │ │ └── SimpleSearchFilter
│ │ │ │ └── index.tsx
│ │ └── types
│ │ │ └── index.ts
│ ├── components
│ │ ├── ui
│ │ │ ├── Toast
│ │ │ │ └── index.tsx
│ │ │ ├── ContentFilter
│ │ │ │ ├── FilterControls.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── exports.ts
│ │ │ │ └── types.ts
│ │ │ ├── FormInput
│ │ │ │ ├── index.ts
│ │ │ │ └── FormInput.tsx
│ │ │ ├── InterestTags
│ │ │ │ ├── index.ts
│ │ │ │ └── InterestTags.tsx
│ │ │ ├── LoadingButton
│ │ │ │ ├── index.ts
│ │ │ │ └── LoadingButton.tsx
│ │ │ ├── GradientBackground
│ │ │ │ ├── index.ts
│ │ │ │ └── GradientBackground.tsx
│ │ │ ├── ItemPreview
│ │ │ │ └── index.ts
│ │ │ ├── VerificationCodeInput
│ │ │ │ ├── index.ts
│ │ │ │ └── VerificationCodeInput.tsx
│ │ │ ├── PublishStatusModal
│ │ │ │ ├── index.ts
│ │ │ │ └── PublishStatusModal.tsx
│ │ │ ├── Switch
│ │ │ │ ├── index.ts
│ │ │ │ └── Switch.tsx
│ │ │ ├── ThemeToggleButton.tsx
│ │ │ ├── cards
│ │ │ │ ├── index.ts
│ │ │ │ ├── ActivityCard
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── constants.ts
│ │ │ │ ├── PostCard
│ │ │ │ │ └── types.ts
│ │ │ │ └── UserCard
│ │ │ │ │ └── types.ts
│ │ │ ├── SectionHeader
│ │ │ │ └── index.tsx
│ │ │ ├── SearchBar
│ │ │ │ └── index.tsx
│ │ │ ├── EmptyState
│ │ │ │ └── index.tsx
│ │ │ ├── Container
│ │ │ │ └── index.tsx
│ │ │ ├── Grid
│ │ │ │ └── index.tsx
│ │ │ ├── Spinner
│ │ │ │ └── index.tsx
│ │ │ ├── BaseCard
│ │ │ │ ├── types.ts
│ │ │ │ ├── variants.ts
│ │ │ │ └── index.tsx
│ │ │ ├── CategoryItem
│ │ │ │ └── index.tsx
│ │ │ ├── Breadcrumb
│ │ │ │ └── index.tsx
│ │ │ ├── Stack
│ │ │ │ └── index.tsx
│ │ │ ├── PageHeader
│ │ │ │ └── index.tsx
│ │ │ ├── Toast.tsx
│ │ │ └── Tag
│ │ │ │ └── index.tsx
│ │ ├── layout
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── navigation
│ │ │ └── index.ts
│ │ ├── common
│ │ │ ├── PublishStatus
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ └── index.ts
│ │ └── debug
│ │ │ └── DebugInfo.tsx
│ ├── features
│ │ ├── activities
│ │ │ ├── hooks
│ │ │ │ └── index.ts
│ │ │ ├── services
│ │ │ │ └── index.ts
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── components
│ │ │ │ ├── index.ts
│ │ │ │ ├── ActivityForm
│ │ │ │ ├── index.ts
│ │ │ │ ├── FormActions.tsx
│ │ │ │ ├── CategorySection.tsx
│ │ │ │ ├── DescriptionSection.tsx
│ │ │ │ ├── BasicInfoSection.tsx
│ │ │ │ └── ImageUploadSection.tsx
│ │ │ │ └── ActivityDetail
│ │ │ │ ├── index.ts
│ │ │ │ ├── OrganizerInfo.tsx
│ │ │ │ ├── ActivityLocation.tsx
│ │ │ │ ├── ActivityHeader.tsx
│ │ │ │ └── ActivityContent.tsx
│ │ ├── auth
│ │ │ ├── components
│ │ │ │ ├── LoginForm
│ │ │ │ │ └── index.ts
│ │ │ │ ├── AuthHeader
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── AuthHeader.tsx
│ │ │ │ ├── RegisterForm
│ │ │ │ │ └── index.ts
│ │ │ │ ├── UserAvatar
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── services
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ └── useVerificationCode.ts
│ │ │ └── types
│ │ │ │ ├── index.ts
│ │ │ │ └── auth.types.ts
│ │ ├── exchange
│ │ │ ├── hooks
│ │ │ │ ├── hooks.ts
│ │ │ │ └── index.ts
│ │ │ ├── components
│ │ │ │ ├── ExchangeCard
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── PublishSteps
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── OrderDetail
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── OrderActions.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ ├── temp-exports.ts
│ │ │ └── index.ts
│ │ ├── profile
│ │ │ ├── components
│ │ │ │ ├── Settings
│ │ │ │ │ ├── SettingsLayout
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── SettingItem
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── SettingSwitch.tsx
│ │ │ │ │ │ ├── SettingSelect.tsx
│ │ │ │ │ │ ├── SettingItem.tsx
│ │ │ │ │ │ └── SettingNavigation.tsx
│ │ │ │ │ ├── modules
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── modals
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── SettingsNavigation.tsx
│ │ │ │ ├── ProfileEdit
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── ActivityFilter
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── OtherUserActivityFilter
│ │ │ │ │ └── index.tsx
│ │ │ │ └── ProfileTabs
│ │ │ │ │ └── index.tsx
│ │ │ ├── index.ts
│ │ │ ├── utils
│ │ │ │ └── index.ts
│ │ │ └── hooks
│ │ │ │ ├── useSettingsNavigation.ts
│ │ │ │ ├── useProfileNavigation.ts
│ │ │ │ └── usePagination.ts
│ │ ├── chat
│ │ │ ├── hooks
│ │ │ │ └── index.ts
│ │ │ ├── mock
│ │ │ │ └── index.ts
│ │ │ ├── services
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── utils
│ │ │ │ └── index.ts
│ │ │ └── components
│ │ │ │ ├── index.ts
│ │ │ │ ├── ChatPageHeader.tsx
│ │ │ │ ├── ConversationListHeader.tsx
│ │ │ │ ├── UnreadMessagesBadge.tsx
│ │ │ │ └── GroupDismissedNotice.tsx
│ │ ├── community
│ │ │ ├── index.ts
│ │ │ ├── services
│ │ │ │ └── index.ts
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ ├── hooks
│ │ │ │ └── index.ts
│ │ │ └── components
│ │ │ │ ├── PostDetail
│ │ │ │ ├── index.ts
│ │ │ │ ├── PostDetailRecommendations.tsx
│ │ │ │ └── PostDetailHeader.tsx
│ │ │ │ ├── CommunityHeader
│ │ │ │ └── index.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── CategorySidebar
│ │ │ │ └── index.tsx
│ │ └── notifications
│ │ │ ├── index.ts
│ │ │ ├── components
│ │ │ ├── index.ts
│ │ │ ├── NotificationTabs.tsx
│ │ │ └── NotificationList.tsx
│ │ │ └── types
│ │ │ └── index.ts
│ ├── vite-env.d.ts
│ ├── main.tsx
│ ├── config
│ │ ├── branch.config.ts
│ │ ├── dev.config.ts
│ │ └── navigation.config.ts
│ ├── store
│ │ ├── index.ts
│ │ └── slices
│ │ │ ├── uiSlice.ts
│ │ │ ├── userSlice.ts
│ │ │ └── settingsSlice.ts
│ ├── App.css
│ ├── types
│ │ ├── index.ts
│ │ └── chat.ts
│ └── utils
│ │ └── testActivityStatusFix.ts
├── .env.development
├── .env.production
├── postcss.config.js
├── tsconfig.json
├── .env.example
├── vercel.json
├── index.html
├── eslint.config.js
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── tsconfig.app.json
├── public
│ └── vite.svg
├── package.json
├── README.md
├── tailwind.config.cjs
└── tailwind.config.js
└── .DS_Store
/bitbit/src/pages/Settings.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/shared/hooks/useToast.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Toast/index.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/services/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/types/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ContentFilter/FilterControls.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/FormInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./FormInput";
2 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guangmingpeng/BitBit-social-platform/HEAD/.DS_Store
--------------------------------------------------------------------------------
/bitbit/src/components/ui/InterestTags/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./InterestTags";
2 |
--------------------------------------------------------------------------------
/bitbit/src/shared/services/index.ts:
--------------------------------------------------------------------------------
1 | // 服务层导出
2 | export { default as api } from "./api";
3 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ContentFilter/index.ts:
--------------------------------------------------------------------------------
1 | // 统一导出所有模块
2 | export * from "./exports";
3 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/LoadingButton/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./LoadingButton";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/LoginForm/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./LoginForm";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/AuthHeader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./AuthHeader";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/RegisterForm/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./RegisterForm";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/UserAvatar/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./UserAvatar";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/hooks/hooks.ts:
--------------------------------------------------------------------------------
1 | // hooks/index.ts 导出文件
2 | export * from "./index";
3 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/hooks/index.ts:
--------------------------------------------------------------------------------
1 | // exchange hooks 导出
2 | export * from "./useExchange";
3 |
--------------------------------------------------------------------------------
/bitbit/src/components/layout/index.ts:
--------------------------------------------------------------------------------
1 | // Layout组件导出
2 | export { default as Header } from "./Header";
3 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/GradientBackground/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./GradientBackground";
2 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ItemPreview/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ItemPreview } from "./ItemPreview";
2 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/VerificationCodeInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./VerificationCodeInput";
2 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/PublishStatusModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PublishStatusModal } from "./PublishStatusModal";
2 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingsLayout/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Settings } from "./Settings";
2 |
--------------------------------------------------------------------------------
/bitbit/.env.development:
--------------------------------------------------------------------------------
1 | # Development environment
2 | # Show debug components in development
3 | VITE_SHOW_DEBUG_COMPONENTS=true
4 |
--------------------------------------------------------------------------------
/bitbit/.env.production:
--------------------------------------------------------------------------------
1 | # Production environment
2 | # Hide debug components in production
3 | VITE_SHOW_DEBUG_COMPONENTS=false
4 |
--------------------------------------------------------------------------------
/bitbit/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/bitbit/src/components/index.ts:
--------------------------------------------------------------------------------
1 | // 导出所有通用组件
2 | export * from './ui'
3 | export * from './layout'
4 | export * from './common'
5 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Switch/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Switch } from "./Switch";
2 | export type { SwitchProps } from "./Switch";
3 |
--------------------------------------------------------------------------------
/bitbit/src/shared/constants/index.ts:
--------------------------------------------------------------------------------
1 | // 常量定义 - 当前为空,可在此处添加全局常量
2 | // export const SOME_CONSTANT = 'value';
3 |
4 | // 导出空对象以避免编译错误
5 | export {};
6 |
--------------------------------------------------------------------------------
/bitbit/src/components/navigation/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SmartBottomNav } from "./SmartBottomNav";
2 | export { default as FloatingNav } from "./FloatingNav";
3 |
--------------------------------------------------------------------------------
/bitbit/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/bitbit/src/components/common/PublishStatus/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PublishStatus } from "./PublishStatus";
2 | export type { PublishStatusProps } from "./types";
3 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/services/index.ts:
--------------------------------------------------------------------------------
1 | // 认证相关服务 - 当前为空,可在此处添加认证API服务
2 | // export { authService } from './authService';
3 |
4 | // 导出空对象以避免编译错误
5 | export {};
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useChatState } from "./useChatState";
2 | export type { UseChatStateOptions, UseChatStateReturn } from "./useChatState";
3 |
--------------------------------------------------------------------------------
/bitbit/src/shared/config/index.ts:
--------------------------------------------------------------------------------
1 | // 配置文件导出
2 | export * from "./constants";
3 | export * from "./routes";
4 | export * from "./theme";
5 | export * from "./themeUtils";
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/index.ts:
--------------------------------------------------------------------------------
1 | // auth 功能模块导出
2 | export * from "./components";
3 | export * from "./hooks";
4 | export * from "./types";
5 | export * from "./services";
6 |
--------------------------------------------------------------------------------
/bitbit/src/shared/utils/index.ts:
--------------------------------------------------------------------------------
1 | // 导出所有通用工具函数
2 | export * from "./cn";
3 | export * from "./date";
4 | export * from "./validation";
5 | export * from "./activityUtils";
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/index.ts:
--------------------------------------------------------------------------------
1 | // community 功能模块导出
2 | export * from './components'
3 | export * from './hooks'
4 | export * from './types'
5 | export * from './services'
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/index.ts:
--------------------------------------------------------------------------------
1 | // activities 功能模块导出
2 | export * from './components'
3 | export * from './hooks'
4 | export * from './types'
5 | export * from './services'
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/services/index.ts:
--------------------------------------------------------------------------------
1 | // 社区相关服务 - 当前为空,可在此处添加社区API服务
2 | // export { communityService } from './communityService';
3 |
4 | // 导出空对象以避免编译错误
5 | export {};
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/components/ExchangeCard/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ExchangeCard } from "./ExchangeCard";
2 | export type { ExchangeCardProps } from "./ExchangeCard";
3 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/mock/index.ts:
--------------------------------------------------------------------------------
1 | // 统一导出所有mock数据
2 | export { mockUsers } from "./users";
3 | export { mockMessages } from "./messages";
4 | export { mockConversations } from "./conversations";
5 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useAuth } from "./useAuth";
2 | export { useVerificationCode } from "./useVerificationCode";
3 | export { useLoginForm, useRegisterForm } from "./useForm";
4 |
--------------------------------------------------------------------------------
/bitbit/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | // 导出所有共享资源
2 | export * from './hooks'
3 | export * from './utils'
4 | export * from './types'
5 | export * from './constants'
6 | export * from './services'
7 | export * from './config'
8 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/types/index.ts:
--------------------------------------------------------------------------------
1 | // 认证相关类型定义 - 当前为空,可在此处添加类型
2 | // export interface User {
3 | // id: string;
4 | // username: string;
5 | // email: string;
6 | // }
7 |
8 | // 导出空对象以避免编译错误
9 | export {};
10 |
--------------------------------------------------------------------------------
/bitbit/src/features/notifications/index.ts:
--------------------------------------------------------------------------------
1 | // 组件
2 | export * from "./components";
3 |
4 | // Hooks
5 | export * from "./hooks";
6 |
7 | // 类型
8 | export * from "./types";
9 |
10 | // 服务
11 | export * from "./services";
12 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/services/index.ts:
--------------------------------------------------------------------------------
1 | // Chat services will be exported here when implemented
2 | // Example: export { chatService } from './chatService';
3 | // Example: export { socketService } from './socketService';
4 |
5 | export {};
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/types/index.ts:
--------------------------------------------------------------------------------
1 | // 社区相关类型定义 - 当前为空,可在此处添加类型
2 | // export interface CommunityPost {
3 | // id: string;
4 | // title: string;
5 | // content: string;
6 | // }
7 |
8 | // 导出空对象以避免编译错误
9 | export {};
10 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/index.ts:
--------------------------------------------------------------------------------
1 | // Components
2 | export * from "./components";
3 |
4 | // Types
5 | export * from "./types";
6 |
7 | // Hooks
8 | export * from "./hooks";
9 |
10 | // Services
11 | export * from "./services";
12 |
--------------------------------------------------------------------------------
/bitbit/.env.example:
--------------------------------------------------------------------------------
1 | # Environment Variables Example
2 | # Copy this file to .env and modify the values as needed
3 |
4 | # Show debug components (true/false)
5 | # Set to true for development/testing, false for production
6 | VITE_SHOW_DEBUG_COMPONENTS=true
7 |
--------------------------------------------------------------------------------
/bitbit/src/pages/ProfileSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Settings } from "../features/profile/components/Settings";
3 |
4 | const ProfileSettings: React.FC = () => {
5 | return ;
6 | };
7 |
8 | export default ProfileSettings;
9 |
--------------------------------------------------------------------------------
/bitbit/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/index.html"
6 | }
7 | ],
8 | "buildCommand": "npm run build",
9 | "outputDirectory": "dist",
10 | "installCommand": "npm install"
11 | }
12 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LoginForm } from "./LoginForm";
2 | export { default as RegisterForm } from "./RegisterForm";
3 | export { default as AuthHeader } from "./AuthHeader";
4 | export { default as UserAvatar } from "./UserAvatar";
5 |
--------------------------------------------------------------------------------
/bitbit/src/pages/ChatTestPage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ChatTestComponent from "@/features/chat/components/ChatTestComponent";
3 |
4 | const ChatTestPage: React.FC = () => {
5 | return ;
6 | };
7 |
8 | export default ChatTestPage;
9 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingItem/index.ts:
--------------------------------------------------------------------------------
1 | export { SettingItem } from "./SettingItem";
2 | export { SettingSwitch } from "./SettingSwitch";
3 | export { SettingSelect } from "./SettingSelect";
4 | export { SettingNavigation } from "./SettingNavigation";
5 |
--------------------------------------------------------------------------------
/bitbit/src/features/notifications/components/index.ts:
--------------------------------------------------------------------------------
1 | export { NotificationHeader } from "./NotificationHeader";
2 | export { NotificationTabs } from "./NotificationTabs";
3 | export { NotificationItem } from "./NotificationItem";
4 | export { NotificationList } from "./NotificationList";
5 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/hooks/index.ts:
--------------------------------------------------------------------------------
1 | // 社区相关hooks
2 | export { useCommunity } from "./useCommunity";
3 | export type { CommunityPost } from "./useCommunity";
4 |
5 | export { useCommunityUsers } from "./useCommunityUsers";
6 | export type { CommunityUser } from "./useCommunityUsers";
7 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/index.ts:
--------------------------------------------------------------------------------
1 | // 导出所有组件
2 | export * from "./components";
3 |
4 | // 导出类型定义
5 | export * from "./types";
6 |
7 | // 导出store
8 | export { chatReducer } from "./store/chatSlice";
9 |
10 | // 导出主要组件的便捷导入
11 | export { ChatContainer, ChatTestComponent } from "./components";
12 |
--------------------------------------------------------------------------------
/bitbit/src/shared/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | /**
5 | * 用于合并和处理CSS类名的工具函数
6 | * 结合clsx和tailwind-merge,确保类名合并的正确性
7 | */
8 | export function cn(...inputs: ClassValue[]) {
9 | return twMerge(clsx(inputs));
10 | }
11 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/utils/index.ts:
--------------------------------------------------------------------------------
1 | // Chat utilities
2 | export {
3 | navigateToChat,
4 | navigateToChatFromUserCard,
5 | navigateToChatFromExchange,
6 | navigateToChatFromNotification,
7 | navigateToChatFromActivity,
8 | parseChatUrlParams,
9 | type ChatNavigationParams,
10 | } from "./navigation";
11 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/components/PublishSteps/index.ts:
--------------------------------------------------------------------------------
1 | export { BasicInfoStep } from "./BasicInfoStep";
2 | export { DetailInfoStep } from "./DetailInfoStep";
3 | export { ProductDisplayStep } from "./ProductDisplayStep";
4 | export { ContactInfoStep } from "./ContactInfoStep";
5 | export { PreviewStep } from "./PreviewStep";
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/modules/index.ts:
--------------------------------------------------------------------------------
1 | export { AccountSecurity } from "./AccountSecurity";
2 | export { NotificationSettings } from "./NotificationSettings";
3 | export { PrivacySettings } from "./PrivacySettings";
4 | export { ApplicationSettings } from "./ApplicationSettings";
5 | export { AboutSettings } from "./AboutSettings";
6 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/components/index.ts:
--------------------------------------------------------------------------------
1 | // exchange 组件导出
2 | export { default as PublishWizard } from "./PublishWizard";
3 | export { default as ExchangeList } from "./ExchangeList";
4 | export { default as ExchangeFilters } from "./ExchangeFilters";
5 | export { ExchangeCard } from "./ExchangeCard";
6 |
7 | // 发布步骤组件
8 | export * from "./PublishSteps";
9 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/modals/index.ts:
--------------------------------------------------------------------------------
1 | export { ChangePasswordModal } from "./ChangePasswordModal";
2 | export { PhoneBindingModal } from "./PhoneBindingModal";
3 | export { EmailBindingModal } from "./EmailBindingModal";
4 | export { TwoFactorAuthModal } from "./TwoFactorAuthModal";
5 | export { DeviceManagementModal } from "./DeviceManagementModal";
6 | export { SecurityLogsModal } from "./SecurityLogsModal";
7 |
--------------------------------------------------------------------------------
/bitbit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | bitbit-寻找你的伙伴
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/bitbit/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import { Provider } from "react-redux";
4 | import { store } from "./store";
5 | import "./index.css";
6 | import App from "./App.tsx";
7 |
8 | createRoot(document.getElementById("root")!).render(
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/PostDetail/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PostDetailHeader } from "./PostDetailHeader";
2 | export { default as PostDetailContent } from "./PostDetailContent";
3 | export { default as PostDetailActions } from "./PostDetailActions";
4 | export { default as PostDetailComments } from "./PostDetailComments";
5 | export { default as PostDetailRecommendations } from "./PostDetailRecommendations";
6 | export { default as ImageGallery } from "./ImageGallery";
7 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/types/index.ts:
--------------------------------------------------------------------------------
1 | // exchange 类型定义导出
2 | export type {
3 | ExchangeItem,
4 | ExchangeCategory,
5 | ExchangeCondition,
6 | PublishFormData,
7 | ExchangeFormData,
8 | PurchaseFormData,
9 | ExchangeListProps,
10 | ExchangeCardProps,
11 | PublishStepProps,
12 | PublishStep,
13 | PublishStepConfig,
14 | ExchangeFilters as ExchangeFiltersType,
15 | // 订单相关类型
16 | OrderDetail,
17 | OrderProgress,
18 | OrderAction,
19 | } from "./types";
20 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/components/OrderDetail/index.ts:
--------------------------------------------------------------------------------
1 | // OrderDetail 组件导出
2 | export { default as OrderStatusBanner } from "./OrderStatusBanner";
3 | export { default as OrderItemInfo } from "./OrderItemInfo";
4 | export { default as OrderTransactionDetails } from "./OrderTransactionDetails";
5 | export { default as OrderOtherParty } from "./OrderOtherParty";
6 | export { default as OrderProgressTimeline } from "./OrderProgressTimeline";
7 | export { default as OrderActions } from "./OrderActions";
8 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/ProfileEdit/index.ts:
--------------------------------------------------------------------------------
1 | export { ProfileEditNavigation } from "./ProfileEditNavigation";
2 | export { BasicInfoForm } from "./BasicInfoForm";
3 | export { InterestsForm } from "./InterestsForm";
4 | export { PrivacyForm } from "./PrivacyForm";
5 | export { SocialLinksForm } from "./SocialLinksForm";
6 | export { VerificationForm } from "./VerificationForm";
7 | export { useProfileEdit } from "./useProfileEdit";
8 |
9 | export type { ProfileEditModule } from "./ProfileEditNavigation";
10 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/index.ts:
--------------------------------------------------------------------------------
1 | // activities 组件导出
2 | // ActivityCard 已迁移到 @/components/ui/cards,请使用统一的UI组件
3 |
4 | // ActivityDetail 子组件导出
5 | export {
6 | ActivityHeader,
7 | ActivityBasicInfo,
8 | OrganizerInfo,
9 | ActivityContent,
10 | ActivityLocation,
11 | ParticipantStats,
12 | ParticipantAvatars,
13 | ParticipantDetails,
14 | ActivityActions,
15 | } from "./ActivityDetail";
16 |
17 | export { ActivityForm } from "./ActivityForm";
18 | export type { ActivityFormData, ActivityFormProps } from "./ActivityForm";
19 |
20 | export { default as ActivityPreview } from "./ActivityPreview";
21 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/index.ts:
--------------------------------------------------------------------------------
1 | export { BasicInfoSection } from "./BasicInfoSection";
2 | export { ImageUploadSection } from "./ImageUploadSection";
3 | export { CategorySection } from "./CategorySection";
4 | export { DescriptionSection } from "./DescriptionSection";
5 | export { TimeLocationSection } from "./TimeLocationSection";
6 | export { RegistrationSection } from "./RegistrationSection";
7 | export { ScheduleSection } from "./ScheduleSection";
8 | export { NoticesSection } from "./NoticesSection";
9 | export { TagsSection } from "./TagsSection";
10 | export { FormActions } from "./FormActions";
11 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/CommunityHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Breadcrumb } from "@/components/ui";
3 |
4 | interface CommunityHeaderProps {
5 | className?: string;
6 | }
7 |
8 | const CommunityHeader: React.FC = ({ className }) => {
9 | const breadcrumbItems = [
10 | { label: "首页", href: "/" },
11 | { label: "社区", current: true },
12 | ];
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default CommunityHeader;
22 |
--------------------------------------------------------------------------------
/bitbit/src/shared/hooks/index.ts:
--------------------------------------------------------------------------------
1 | // 导出所有通用 hooks
2 | export { useNavigationFilters } from "./useNavigationStore";
3 | export { useSmartNavigation } from "./useSmartNavigation";
4 | export { useConfirmExit } from "./useConfirmExit";
5 | export { usePublishStatus } from "./usePublishStatus";
6 | export { useExchangeActions } from "./useExchangeActions";
7 | export type {
8 | ExchangeItem,
9 | UseExchangeActionsProps,
10 | UseExchangeActionsReturn,
11 | } from "./useExchangeActions";
12 | // export { default as useLocalStorage } from './useLocalStorage'
13 | // export { default as useDebounce } from './useDebounce'
14 | // 根据实际文件添加更多导出
15 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ThemeToggleButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useTheme } from "../../shared/hooks/useTheme";
3 |
4 | export const ThemeToggleButton: React.FC = () => {
5 | const { theme, actualTheme, toggleTheme } = useTheme();
6 |
7 | return (
8 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/GradientBackground/GradientBackground.tsx:
--------------------------------------------------------------------------------
1 | import { type FC, type ReactNode } from "react";
2 |
3 | interface GradientBackgroundProps {
4 | children: ReactNode;
5 | className?: string;
6 | }
7 |
8 | const GradientBackground: FC = ({
9 | children,
10 | className = "",
11 | }) => {
12 | return (
13 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default GradientBackground;
25 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ContentFilter/exports.ts:
--------------------------------------------------------------------------------
1 | // 导出主要组件
2 | export { default as ContentFilter } from "./index.tsx";
3 | export { default } from "./index.tsx";
4 |
5 | // 导出类型
6 | export type {
7 | FilterOption,
8 | SortOption,
9 | FilterConfig,
10 | SortConfig,
11 | SearchConfig,
12 | ContentFilterProps,
13 | ProfileFilterConfigs,
14 | } from "./types";
15 |
16 | // 导出配置
17 | export {
18 | profileFilterConfigs,
19 | favoritesConfig,
20 | postsConfig,
21 | tradesConfig,
22 | activitiesConfig,
23 | draftsConfig,
24 | getProfileFilterConfig,
25 | createCustomFilterFn,
26 | } from "./configs";
27 |
28 | // 导出Hook
29 | export { useContentFilter } from "./useContentFilter";
30 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/index.ts:
--------------------------------------------------------------------------------
1 | // 统一的卡片组件导出
2 | export { ActivityCard } from "./ActivityCard";
3 | export { PostCard } from "./PostCard";
4 | export { UserCard } from "./UserCard";
5 |
6 | // 导出类型
7 | export type { ActivityCardProps, Activity } from "./ActivityCard/types";
8 | export type { PostCardProps, Post } from "./PostCard/types";
9 | export type { UserCardProps, User } from "./UserCard/types";
10 |
11 | // 基础卡片组件
12 | export { BaseCard } from "../BaseCard";
13 | export type {
14 | BaseCardProps,
15 | CardLayout,
16 | CardActions,
17 | CardStats,
18 | CardMeta,
19 | } from "../BaseCard/types";
20 | export { getCardVariantClasses, getLayoutClasses } from "../BaseCard/variants";
21 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/index.ts:
--------------------------------------------------------------------------------
1 | // Settings 组件导出
2 | export { Settings } from "./SettingsLayout";
3 | export { SettingsNavigation } from "./SettingsNavigation";
4 |
5 | // Setting Item 组件导出 - 使用明确的重命名
6 | export {
7 | SettingItem as SettingsItemComponent,
8 | SettingSwitch,
9 | SettingSelect,
10 | SettingNavigation,
11 | } from "./SettingItem";
12 |
13 | // Settings 模块导出 - 使用命名导出避免冲突
14 | export {
15 | AccountSecurity as SettingsAccountSecurity,
16 | NotificationSettings as SettingsNotificationSettings,
17 | PrivacySettings as SettingsPrivacySettings,
18 | ApplicationSettings as SettingsApplicationSettings,
19 | AboutSettings as SettingsAboutSettings,
20 | } from "./modules";
21 |
--------------------------------------------------------------------------------
/bitbit/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 | import { globalIgnores } from 'eslint/config'
7 |
8 | export default tseslint.config([
9 | globalIgnores(['dist']),
10 | {
11 | files: ['**/*.{ts,tsx}'],
12 | extends: [
13 | js.configs.recommended,
14 | tseslint.configs.recommended,
15 | reactHooks.configs['recommended-latest'],
16 | reactRefresh.configs.vite,
17 | ],
18 | languageOptions: {
19 | ecmaVersion: 2020,
20 | globals: globals.browser,
21 | },
22 | },
23 | ])
24 |
--------------------------------------------------------------------------------
/bitbit/src/components/common/index.ts:
--------------------------------------------------------------------------------
1 | // 通用组件导出
2 | export { default as BackButton } from "./BackButton";
3 | export { default as ImageCarousel } from "./ImageCarousel";
4 | export { default as FloatingBackButton } from "./FloatingBackButton";
5 | export { default as ConfirmExitDialog } from "./ConfirmExitDialog";
6 | export { default as ConfirmClearDialog } from "./ConfirmClearDialog";
7 | export { default as ConfirmBatchDeleteDialog } from "./ConfirmBatchDeleteDialog";
8 |
9 | export { default as ConfirmActionDialog } from "./ConfirmActionDialog";
10 | export { PublishStatus } from "./PublishStatus";
11 | export type { PublishStatusProps } from "./PublishStatus";
12 | export type { ActionType } from "./ConfirmActionDialog";
13 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/temp-exports.ts:
--------------------------------------------------------------------------------
1 | // Exchange功能模块统一导出文件
2 |
3 | // Hooks导出
4 | export {
5 | useExchangeItems,
6 | useExchangeItem,
7 | useExchangeRequest,
8 | useFavorites,
9 | useViewHistory,
10 | } from "./hooks/useExchange";
11 |
12 | // 组件导出
13 | export { default as PublishWizard } from "./components/PublishWizard";
14 | export { default as ExchangeList } from "./components/ExchangeList";
15 | export { default as ExchangeFilters } from "./components/ExchangeFilters";
16 | export { ExchangeCard } from "./components/ExchangeCard";
17 |
18 | // 发布步骤组件
19 | export * from "./components/PublishSteps";
20 |
21 | // 类型导出
22 | export * from "./types/types";
23 |
24 | // 常量导出
25 | export * from "./constants";
26 |
--------------------------------------------------------------------------------
/bitbit/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import path from "path";
4 |
5 | // https://vite.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 | resolve: {
9 | alias: {
10 | "@": path.resolve(__dirname, "./src"),
11 | "@/components": path.resolve(__dirname, "./src/components"),
12 | "@/features": path.resolve(__dirname, "./src/features"),
13 | "@/shared": path.resolve(__dirname, "./src/shared"),
14 | "@/pages": path.resolve(__dirname, "./src/pages"),
15 | "@/assets": path.resolve(__dirname, "./src/assets"),
16 | "@/store": path.resolve(__dirname, "./src/store"),
17 | },
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityDetail/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ActivityHeader } from "./ActivityHeader";
2 | export { default as ActivityBasicInfo } from "./ActivityBasicInfo";
3 | export { default as OrganizerInfo } from "./OrganizerInfo";
4 | export { default as ActivityContent } from "./ActivityContent";
5 | export { default as ActivityLocation } from "./ActivityLocation";
6 | export { default as ParticipantStats } from "./ParticipantStats";
7 | export { default as ParticipantAvatars } from "./ParticipantAvatars";
8 | export { default as ParticipantCard } from "./ParticipantCard";
9 | export { default as ParticipantDetails } from "./ParticipantDetails";
10 | export { default as ActivityActions } from "./ActivityActions";
11 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/index.ts:
--------------------------------------------------------------------------------
1 | // exchange 功能模块导出
2 |
3 | // 组件导出
4 | export { default as PublishWizard } from "./components/PublishWizard";
5 | export { default as ExchangeList } from "./components/ExchangeList";
6 | export { default as ExchangeFiltersComponent } from "./components/ExchangeFilters";
7 | export { ExchangeCard } from "./components/ExchangeCard";
8 |
9 | // 步骤组件
10 | export * from "./components/PublishSteps";
11 |
12 | // 订单详情组件
13 | export * from "./components/OrderDetail";
14 |
15 | // 页面组件
16 | export { default as OrderDetailPage } from "./pages/OrderDetailPage";
17 |
18 | // hooks导出
19 | export * from "./hooks/index";
20 |
21 | // 类型导出
22 | export * from "./types/index";
23 |
24 | // 常量导出
25 | export * from "./constants";
26 |
--------------------------------------------------------------------------------
/bitbit/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2023",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "verbatimModuleSyntax": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "erasableSyntaxOnly": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/index.ts:
--------------------------------------------------------------------------------
1 | // community 组件导出
2 | // PostCard 已迁移到 @/components/ui/cards,请使用统一的UI组件
3 | export * from "./PostDetail";
4 |
5 | export { default as CommunityPostForm } from "./CommunityPostForm";
6 | export type {
7 | CommunityPostFormData,
8 | CommunityPostFormProps,
9 | } from "./CommunityPostForm";
10 |
11 | export { default as PostPreview } from "./PostPreview";
12 |
13 | // 新增的Community页面组件
14 | export { default as CommunityHeader } from "./CommunityHeader";
15 | export { default as CommunitySearch } from "./CommunitySearch";
16 | export { default as CategorySidebar } from "./CategorySidebar";
17 | export { default as CommunitySidebar } from "./CommunitySidebar";
18 | export { default as PostList } from "./PostList";
19 |
--------------------------------------------------------------------------------
/bitbit/src/pages/FollowListPage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useParams, useLocation } from "react-router-dom";
3 | import { FollowList } from "@/features/profile/components/FollowList";
4 |
5 | /**
6 | * 关注/粉丝列表页面
7 | * 根据路径判断显示关注列表还是粉丝列表
8 | */
9 | export const FollowListPage: React.FC = () => {
10 | const { userId } = useParams<{ userId?: string }>();
11 | const location = useLocation();
12 |
13 | // 根据路径判断列表类型
14 | const isFollowingList = location.pathname.includes("following");
15 | const type = isFollowingList ? "following" : "followers";
16 |
17 | return (
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default FollowListPage;
25 |
--------------------------------------------------------------------------------
/bitbit/src/shared/components/index.ts:
--------------------------------------------------------------------------------
1 | // 用户卡片弹出框组件
2 | export { default as UserCardPopover } from "./UserCardPopover";
3 | export type { UserInfo } from "./UserCardPopover";
4 |
5 | // 其他组件
6 | export { default as ParticipantCardEnhanced } from "./ParticipantCardEnhanced";
7 | export { default as TagsInput } from "./TagsInput";
8 |
9 | // 搜索和筛选相关组件
10 | export { SearchBar } from "./SearchBar";
11 | export type { SearchBarProps } from "./SearchBar";
12 |
13 | export { SortSelector } from "./SortSelector";
14 | export type { SortSelectorProps, SortOption } from "./SortSelector";
15 |
16 | export { SimpleSearchFilter } from "./SimpleSearchFilter";
17 | export type { SimpleSearchFilterProps } from "./SimpleSearchFilter";
18 |
19 | export { TabFilter } from "./TabFilter";
20 | export type { TabFilterProps, TabOption } from "./TabFilter";
21 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/index.ts:
--------------------------------------------------------------------------------
1 | export { UserProfile } from "./UserProfile";
2 | export { ProfileTabs } from "./ProfileTabs";
3 | export { ActivityFilter } from "./ActivityFilter";
4 | export { OtherUserActivityFilter } from "./OtherUserActivityFilter";
5 | export { AchievementBadges } from "./AchievementBadges";
6 | export { QuickAccess } from "./QuickAccess";
7 | export { LoadingState } from "./LoadingState";
8 | export { Pagination } from "./Pagination";
9 | export { ActivityNotificationModal } from "./ActivityNotificationModal";
10 | export { FollowList } from "./FollowList";
11 | export { OtherUserProfilePage } from "./OtherUserProfile";
12 | export { OtherUserProfileHeader } from "./OtherUserProfileHeader";
13 |
14 | // ProfileEdit 相关组件
15 | export * from "./ProfileEdit";
16 |
17 | // Settings 相关组件
18 | export * from "./Settings";
19 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/ActivityCard/README.md:
--------------------------------------------------------------------------------
1 | /\*\*
2 |
3 | - ActivityCard Component Architecture
4 | -
5 | - 这是一个复杂但功能完整的活动卡片组件,支持多种布局和状态。
6 | -
7 | - ## 组件特性
8 | - - 支持 5 种布局模式:default, horizontal, list, compact, minimal
9 | - - 统一的状态管理使用 activityUtils
10 | - - 高度可配置的显示选项
11 | - - 完整的用户交互支持
12 | -
13 | - ## 代码结构
14 | - 1. 数据适配 (lines 50-70)
15 | - 2. 配置获取 (lines 71-220)
16 | - 3. 状态计算 (lines 221-260)
17 | - 4. 事件处理 (lines 261-270)
18 | - 5. 渲染函数 (lines 271-600)
19 | - - renderImages: 图片渲染
20 | - - renderHeader: 头部信息
21 | - - renderContent: 主要内容
22 | - - renderActions: 操作按钮
23 | - 6. 主渲染逻辑 (lines 601-950)
24 | -
25 | - ## 性能考虑
26 | - - 组件使用了合理的条件渲染
27 | - - 避免了不必要的重新计算
28 | - - 使用了 useMemo 优化(如果需要的话)
29 | -
30 | - ## 维护建议
31 | - - 避免在此文件中添加新的布局模式
32 | - - 新的状态逻辑应该添加到 activityUtils
33 | - - 样式更改优先考虑使用 CSS 变量
34 | \*/
35 |
36 | // 当前代码行数: ~950 行
37 | // 复杂度: 高,但可维护
38 | // 状态: 稳定,建议保持现状
39 |
--------------------------------------------------------------------------------
/bitbit/src/config/branch.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 分支特定配置
3 | * 用于控制不同分支(dev/main)的功能差异
4 | */
5 |
6 | // 获取当前分支信息(如果可用)
7 | const getCurrentBranch = () => {
8 | // 在生产环境中,这可以通过构建时注入的环境变量来获取
9 | return import.meta.env.VITE_CURRENT_BRANCH || "dev";
10 | };
11 |
12 | // 检查是否为dev分支
13 | const isDevBranch = () => {
14 | const branch = getCurrentBranch();
15 | return branch === "dev" || branch === "development";
16 | };
17 |
18 | // 检查是否为生产环境
19 | const isProduction = () => {
20 | return import.meta.env.PROD;
21 | };
22 |
23 | export const branchConfig = {
24 | // 当前分支
25 | currentBranch: getCurrentBranch(),
26 |
27 | // 是否为dev分支
28 | isDevBranch: isDevBranch(),
29 |
30 | // 是否为生产环境
31 | isProduction: isProduction(),
32 |
33 | // 是否显示调试组件
34 | // dev分支或开发环境显示,生产环境的主分支隐藏
35 | showDebugComponents: isDevBranch() || !isProduction(),
36 | } as const;
37 |
38 | export default branchConfig;
39 |
--------------------------------------------------------------------------------
/bitbit/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # Development documentation
27 | docs/development/
28 | docs/
29 |
30 | # Environment files
31 | .env
32 | .env.local
33 | .env.development.local
34 | .env.test.local
35 | .env.production.local
36 |
37 | # Test coverage
38 | coverage/
39 | *.lcov
40 |
41 | # Temporary files
42 | *.tmp
43 | *.temp
44 | *_temp.*
45 | *_backup.*
46 | *_old.*
47 |
48 | # Build artifacts
49 | build/
50 | out/
51 |
52 | # Cache directories
53 | .cache/
54 | .parcel-cache/
55 | .eslintcache
56 |
57 | # OS generated files
58 | Thumbs.db
59 | *.DS_Store
60 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingItem/SettingSwitch.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SettingItem } from "./SettingItem";
3 | import { Switch } from "../../../../../components/ui/Switch";
4 |
5 | interface SettingSwitchProps {
6 | icon?: string;
7 | iconColor?: string;
8 | title: string;
9 | description?: string;
10 | checked: boolean;
11 | disabled?: boolean;
12 | onChange: (checked: boolean) => void;
13 | }
14 |
15 | export const SettingSwitch: React.FC = ({
16 | icon,
17 | iconColor,
18 | title,
19 | description,
20 | checked,
21 | disabled = false,
22 | onChange,
23 | }) => {
24 | return (
25 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/bitbit/src/config/dev.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 开发环境配置
3 | * 用于控制开发时的功能开关
4 | */
5 |
6 | import { branchConfig } from "./branch.config";
7 |
8 | // 检查是否为开发环境
9 | const isDevelopment = import.meta.env.MODE === "development";
10 |
11 | // 检查环境变量设置
12 | const envShowDebugComponents = import.meta.env.VITE_SHOW_DEBUG_COMPONENTS;
13 |
14 | // 决定是否显示调试组件的逻辑:
15 | // 1. 如果明确设置了环境变量,使用环境变量的值
16 | // 2. 否则,dev分支或开发环境下显示,生产环境的main分支隐藏
17 | const shouldShowDebugComponents =
18 | envShowDebugComponents !== undefined
19 | ? envShowDebugComponents === "true"
20 | : branchConfig.showDebugComponents;
21 |
22 | export const devConfig = {
23 | // 是否显示调试组件(右下角的测试组件)
24 | showDebugComponents: shouldShowDebugComponents,
25 |
26 | // 其他开发配置
27 | enableConsoleLogging: isDevelopment,
28 | enableDevTools: isDevelopment,
29 |
30 | // 分支信息
31 | branch: branchConfig.currentBranch,
32 | isDevBranch: branchConfig.isDevBranch,
33 | } as const;
34 |
35 | export default devConfig;
36 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/SectionHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Button from "../Button";
3 | import { cn } from "@/shared/utils/cn";
4 |
5 | export interface SectionHeaderProps {
6 | title: string;
7 | actionText?: string;
8 | onActionClick?: () => void;
9 | className?: string;
10 | }
11 |
12 | const SectionHeader: React.FC = ({
13 | title,
14 | actionText,
15 | onActionClick,
16 | className,
17 | }) => {
18 | return (
19 |
20 |
{title}
21 | {actionText && (
22 |
30 | )}
31 |
32 | );
33 | };
34 |
35 | export default SectionHeader;
36 |
--------------------------------------------------------------------------------
/bitbit/src/components/debug/DebugInfo.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 开发调试信息组件
3 | * 仅在开发环境显示当前配置状态
4 | */
5 |
6 | import React from "react";
7 | import { devConfig } from "@/config/dev.config";
8 |
9 | const DebugInfo: React.FC = () => {
10 | // 只在开发环境显示
11 | if (import.meta.env.PROD) {
12 | return null;
13 | }
14 |
15 | return (
16 |
17 |
Debug Info:
18 |
Mode: {import.meta.env.MODE}
19 |
20 | Show Debug Components:{" "}
21 | {devConfig.showDebugComponents ? "true" : "false"}
22 |
23 |
Branch: {devConfig.branch}
24 |
Is Dev Branch: {devConfig.isDevBranch ? "true" : "false"}
25 |
26 | ENV Variable:{" "}
27 | {import.meta.env.VITE_SHOW_DEBUG_COMPONENTS || "undefined"}
28 |
29 |
30 | );
31 | };
32 |
33 | export default DebugInfo;
34 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | const SearchBar = () => {
4 | const [searchText, setSearchText] = useState("");
5 |
6 | const handleSearch = (e: React.FormEvent) => {
7 | e.preventDefault();
8 | // TODO: 实现搜索功能
9 | console.log("Search for:", searchText);
10 | };
11 |
12 | return (
13 |
14 |
23 |
24 | );
25 | };
26 |
27 | export default SearchBar;
28 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/FormActions.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@/components/ui";
3 |
4 | interface FormActionsProps {
5 | isValid: boolean;
6 | isSubmitting: boolean;
7 | onSubmit: () => void;
8 | onCancel: () => void;
9 | }
10 |
11 | export const FormActions: React.FC = ({
12 | isValid,
13 | isSubmitting,
14 | onSubmit,
15 | onCancel,
16 | }) => {
17 | return (
18 |
19 |
27 |
36 |
37 | );
38 | };
39 |
40 | export default FormActions;
41 |
--------------------------------------------------------------------------------
/bitbit/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { setupListeners } from "@reduxjs/toolkit/query";
3 | import { authReducer } from "./slices/authSlice";
4 | import { uiReducer } from "./slices/uiSlice";
5 | import { userReducer } from "./slices/userSlice";
6 | import { settingsReducer } from "./slices/settingsSlice";
7 | import { chatReducer } from "@/features/chat/store/chatSlice";
8 |
9 | export const store = configureStore({
10 | reducer: {
11 | auth: authReducer,
12 | ui: uiReducer,
13 | user: userReducer,
14 | settings: settingsReducer,
15 | chat: chatReducer,
16 | },
17 | middleware: (getDefaultMiddleware) =>
18 | getDefaultMiddleware({
19 | serializableCheck: {
20 | // Ignore these paths in the state
21 | ignoredActions: ["persist/PERSIST"],
22 | ignoredPaths: ["ui.toast"],
23 | },
24 | }),
25 | });
26 |
27 | setupListeners(store.dispatch);
28 |
29 | export type RootState = ReturnType;
30 | export type AppDispatch = typeof store.dispatch;
31 |
--------------------------------------------------------------------------------
/bitbit/src/components/common/PublishStatus/types.ts:
--------------------------------------------------------------------------------
1 | export interface PublishStatusProps {
2 | status: "idle" | "loading" | "success" | "error";
3 | error?: string;
4 | onRetry: () => void;
5 | onReset: () => void;
6 | successConfig: {
7 | title: string;
8 | description: string;
9 | publishedItemId?: string; // 发布成功后的ID
10 | previewData: {
11 | title: string;
12 | category?: string;
13 | condition?: string;
14 | price?: number;
15 | image?: string | File;
16 | type: "activity" | "post" | "item";
17 | };
18 | // 完整的表单数据,用于渲染详细预览
19 | fullPreviewData?: T;
20 | actions: {
21 | primary: {
22 | label: string;
23 | href?: string;
24 | generateHref?: (id: string) => string;
25 | };
26 | secondary: {
27 | label: string;
28 | href: string;
29 | };
30 | tertiary?: {
31 | label: string;
32 | onClick: () => void;
33 | };
34 | };
35 | };
36 | loadingConfig: {
37 | title: string;
38 | description: string;
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/bitbit/src/pages/Login.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { GradientBackground } from "@/components/ui";
3 | import { AuthHeader, LoginForm } from "@/features/auth/components";
4 |
5 | const Login: FC = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {/* 底部装饰 */}
16 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Login;
28 |
--------------------------------------------------------------------------------
/bitbit/src/pages/Register.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { GradientBackground } from "@/components/ui";
3 | import { AuthHeader, RegisterForm } from "@/features/auth/components";
4 |
5 | const Register: FC = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {/* 底部装饰 */}
16 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Register;
28 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/utils/index.ts:
--------------------------------------------------------------------------------
1 | import type { Activity } from "../types";
2 |
3 | /**
4 | * 格式化日期显示
5 | */
6 | export const formatDate = (dateStr: string): string => {
7 | const date = new Date(dateStr);
8 | const now = new Date();
9 | const diffTime = date.getTime() - now.getTime();
10 | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
11 |
12 | if (diffDays < 0) {
13 | return "已结束";
14 | } else if (diffDays === 0) {
15 | return "今天";
16 | } else if (diffDays === 1) {
17 | return "明天";
18 | } else {
19 | return `${diffDays}天后`;
20 | }
21 | };
22 |
23 | /**
24 | * 获取活动状态的显示文本
25 | */
26 | export const getActivityStatusText = (status: Activity["status"]): string => {
27 | switch (status) {
28 | case "registered":
29 | return "已报名";
30 | case "organized":
31 | return "已组织";
32 | case "ended":
33 | return "已结束";
34 | default:
35 | return "未知";
36 | }
37 | };
38 |
39 | /**
40 | * 计算经验值百分比
41 | */
42 | export const calculateExpPercentage = (
43 | current: number,
44 | total: number
45 | ): number => {
46 | return Math.max(0, Math.min(100, (current / total) * 100));
47 | };
48 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/EmptyState/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@/components/ui";
3 |
4 | export interface EmptyStateProps {
5 | icon?: string | React.ReactNode;
6 | title: string;
7 | description?: string;
8 | actionText?: string;
9 | onAction?: () => void;
10 | className?: string;
11 | }
12 |
13 | export const EmptyState: React.FC = ({
14 | icon = "📭",
15 | title,
16 | description,
17 | actionText,
18 | onAction,
19 | className = "",
20 | }) => {
21 | return (
22 |
23 |
24 | {typeof icon === "string" ? icon : icon}
25 |
26 |
{title}
27 | {description &&
{description}
}
28 | {actionText && onAction && (
29 |
35 | )}
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Container/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface ContainerProps extends React.HTMLAttributes {
5 | size?: "sm" | "md" | "lg" | "xl" | "full";
6 | padding?: "none" | "sm" | "md" | "lg";
7 | center?: boolean;
8 | }
9 |
10 | const Container: React.FC = ({
11 | className,
12 | size = "lg",
13 | padding = "md",
14 | center = true,
15 | children,
16 | ...props
17 | }) => {
18 | const sizeConfig = {
19 | sm: "max-w-screen-sm",
20 | md: "max-w-screen-md",
21 | lg: "max-w-screen-lg",
22 | xl: "max-w-screen-xl",
23 | full: "max-w-full",
24 | };
25 |
26 | const paddingConfig = {
27 | none: "",
28 | sm: "px-4",
29 | md: "px-6",
30 | lg: "px-8",
31 | };
32 |
33 | const centerConfig = center ? "mx-auto" : "";
34 |
35 | return (
36 |
46 | {children}
47 |
48 | );
49 | };
50 |
51 | export default Container;
52 |
--------------------------------------------------------------------------------
/bitbit/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2022",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "erasableSyntaxOnly": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true,
25 |
26 | /* Path mapping */
27 | "baseUrl": ".",
28 | "paths": {
29 | "@/*": ["./src/*"],
30 | "@/components/*": ["./src/components/*"],
31 | "@/features/*": ["./src/features/*"],
32 | "@/shared/*": ["./src/shared/*"],
33 | "@/pages/*": ["./src/pages/*"],
34 | "@/assets/*": ["./src/assets/*"],
35 | "@/store/*": ["./src/store/*"]
36 | }
37 | },
38 | "include": ["src"]
39 | }
40 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/components/index.ts:
--------------------------------------------------------------------------------
1 | // 聊天核心组件
2 | export { default as ChatContainer } from "./ChatContainer";
3 | export { default as ChatHeader } from "./ChatHeader";
4 | export { default as ChatMain } from "./ChatMain";
5 | export { default as ChatPageHeader } from "./ChatPageHeader";
6 | export { default as ChatTestComponent } from "./ChatTestComponent";
7 |
8 | // 消息相关组件
9 | export { default as MessageList } from "./MessageList";
10 | export { default as MessageBubble } from "./MessageBubble";
11 | export { default as MessageInput } from "./MessageInput";
12 | export { default as UnreadMessagesBadge } from "./UnreadMessagesBadge";
13 |
14 | // 会话相关组件
15 | export { default as ConversationList } from "./ConversationList";
16 | export { default as ConversationListHeader } from "./ConversationListHeader";
17 |
18 | // 群聊相关组件
19 | export { default as GroupSettings } from "./GroupSettings";
20 | export { default as GroupMembersList } from "./GroupMembersList";
21 | export { default as GroupDismissedNotice } from "./GroupDismissedNotice";
22 | export { default as GroupChatHeader } from "./GroupChatHeader";
23 | export { default as CreateGroupChat } from "./CreateGroupChat";
24 |
25 | // 导出组件的类型
26 | export type { ChatContainerRef } from "./ChatContainer";
27 |
--------------------------------------------------------------------------------
/bitbit/src/store/slices/uiSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
2 |
3 | interface UiState {
4 | theme: "light" | "dark";
5 | sidebarOpen: boolean;
6 | toast: {
7 | show: boolean;
8 | message: string;
9 | type: "success" | "error" | "info" | "warning";
10 | };
11 | }
12 |
13 | const initialState: UiState = {
14 | theme: "light",
15 | sidebarOpen: false,
16 | toast: {
17 | show: false,
18 | message: "",
19 | type: "info",
20 | },
21 | };
22 |
23 | const uiSlice = createSlice({
24 | name: "ui",
25 | initialState,
26 | reducers: {
27 | toggleTheme: (state) => {
28 | state.theme = state.theme === "light" ? "dark" : "light";
29 | },
30 | toggleSidebar: (state) => {
31 | state.sidebarOpen = !state.sidebarOpen;
32 | },
33 | showToast: (
34 | state,
35 | action: PayloadAction>
36 | ) => {
37 | state.toast = {
38 | ...action.payload,
39 | show: true,
40 | };
41 | },
42 | hideToast: (state) => {
43 | state.toast.show = false;
44 | },
45 | },
46 | });
47 |
48 | export const { toggleTheme, toggleSidebar, showToast, hideToast } =
49 | uiSlice.actions;
50 | export const uiReducer = uiSlice.reducer;
51 |
--------------------------------------------------------------------------------
/bitbit/src/features/notifications/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface Notification {
2 | id: string;
3 | type: "activity" | "social" | "system" | "message";
4 | title: string;
5 | content: string;
6 | time: string;
7 | isRead: boolean;
8 | avatar?: string;
9 | actionUrl?: string;
10 | createdAt: string;
11 | updatedAt?: string;
12 | // 消息通知聚合相关字段
13 | messageData?: {
14 | senders: Array<{
15 | userId: string;
16 | userName: string;
17 | userAvatar?: string;
18 | lastMessage: string;
19 | timestamp: string;
20 | }>;
21 | totalCount: number;
22 | lastSenderName: string;
23 | lastMessage: string;
24 | lastSenderId: string;
25 | lastSenderAvatar?: string;
26 | };
27 | }
28 |
29 | export type NotificationType = "all" | "activity" | "social" | "system";
30 |
31 | export interface NotificationStats {
32 | total: number;
33 | unread: number;
34 | byType: Record;
35 | }
36 |
37 | export interface NotificationFilter {
38 | type: NotificationType;
39 | onlyUnread?: boolean;
40 | }
41 |
42 | export interface NotificationActions {
43 | markAsRead: (id: string) => void;
44 | markAllAsRead: () => void;
45 | clearAllNotifications: () => void;
46 | deleteNotification: (id: string) => void;
47 | }
48 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/PostCard/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | BaseCardProps,
3 | CardLayout,
4 | CardActions,
5 | } from "../../BaseCard/types";
6 |
7 | export interface Post {
8 | id: string;
9 | author: {
10 | name: string;
11 | avatar?: string;
12 | isVerified?: boolean;
13 | };
14 | content: string;
15 | images?: string[];
16 | category?: "music" | "food" | "learning" | "reading";
17 | tags?: string[];
18 | publishTime: string;
19 | likes: number;
20 | comments: number;
21 | shares: number;
22 | isLiked?: boolean;
23 | isBookmarked?: boolean;
24 | }
25 |
26 | export interface PostCardProps
27 | extends Omit,
28 | CardLayout,
29 | CardActions {
30 | post: Post;
31 |
32 | // 帖子特定的回调
33 | onLike?: () => void;
34 | onComment?: () => void;
35 | onShare?: () => void;
36 | onBookmark?: () => void;
37 | onTagClick?: (tag: string) => void;
38 | onViewDetail?: () => void;
39 |
40 | // 管理相关回调(仅对自己的帖子显示)
41 | onEdit?: () => void;
42 | onDelete?: () => void;
43 |
44 | // 显示控制
45 | showAuthor?: boolean;
46 | showImages?: boolean;
47 | showCategory?: boolean;
48 | showTags?: boolean;
49 | showPublishTime?: boolean;
50 | showInteractionStats?: boolean;
51 | showManagementActions?: boolean; // 是否显示编辑和删除按钮
52 | }
53 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Grid/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface GridProps extends React.HTMLAttributes {
5 | cols?: 1 | 2 | 3 | 4 | 5 | 6;
6 | gap?: "none" | "sm" | "md" | "lg" | "xl";
7 | responsive?: boolean;
8 | }
9 |
10 | const Grid: React.FC = ({
11 | className,
12 | cols = 1,
13 | gap = "md",
14 | responsive = true,
15 | children,
16 | ...props
17 | }) => {
18 | const colsConfig = {
19 | 1: "grid-cols-1",
20 | 2: responsive ? "grid-cols-1 md:grid-cols-2" : "grid-cols-2",
21 | 3: responsive ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" : "grid-cols-3",
22 | 4: responsive ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-4" : "grid-cols-4",
23 | 5: responsive ? "grid-cols-1 md:grid-cols-3 lg:grid-cols-5" : "grid-cols-5",
24 | 6: responsive ? "grid-cols-1 md:grid-cols-3 lg:grid-cols-6" : "grid-cols-6",
25 | };
26 |
27 | const gapConfig = {
28 | none: "gap-0",
29 | sm: "gap-2",
30 | md: "gap-4",
31 | lg: "gap-6",
32 | xl: "gap-8",
33 | };
34 |
35 | return (
36 |
40 | {children}
41 |
42 | );
43 | };
44 |
45 | export default Grid;
46 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/CategorySection.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, CardContent, CategorySelector } from "@/components/ui";
3 | import type { ActivityFormData } from "../ActivityForm";
4 |
5 | interface CategorySectionProps {
6 | formData: ActivityFormData;
7 | onFieldChange: (
8 | field: K,
9 | value: ActivityFormData[K]
10 | ) => void;
11 | onFieldFocus: (fieldName: string) => void;
12 | }
13 |
14 | export const CategorySection: React.FC = ({
15 | formData,
16 | onFieldChange,
17 | onFieldFocus,
18 | }) => {
19 | return (
20 |
21 |
22 |
23 |
26 |
onFieldFocus("category")}>
27 | onFieldChange("category", categoryId)}
30 | allowCustom={true}
31 | />
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default CategorySection;
40 |
--------------------------------------------------------------------------------
/bitbit/src/shared/config/constants.ts:
--------------------------------------------------------------------------------
1 | // API endpoints
2 | export const API_BASE_URL =
3 | import.meta.env.VITE_API_BASE_URL || "http://localhost:3000/api";
4 |
5 | // Authentication
6 | export const TOKEN_KEY = "bitbit_token";
7 | export const USER_KEY = "bitbit_user";
8 |
9 | // Pagination
10 | export const DEFAULT_PAGE_SIZE = 10;
11 | export const INFINITE_SCROLL_THRESHOLD = 0.8;
12 |
13 | // File upload
14 | export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
15 | export const ALLOWED_FILE_TYPES = ["image/jpeg", "image/png", "image/gif"];
16 |
17 | // Date format
18 | export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD HH:mm:ss";
19 |
20 | // Validation
21 | export const PASSWORD_MIN_LENGTH = 8;
22 | export const USERNAME_MIN_LENGTH = 3;
23 | export const USERNAME_MAX_LENGTH = 20;
24 |
25 | // UI Constants
26 | export const MOBILE_BREAKPOINT = 768;
27 | export const TABLET_BREAKPOINT = 1024;
28 | export const DESKTOP_BREAKPOINT = 1280;
29 |
30 | // Cache
31 | export const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
32 | export const STALE_TIME = 60 * 1000; // 1 minute
33 |
34 | // Error Messages
35 | export const ERROR_MESSAGES = {
36 | NETWORK_ERROR: "网络连接失败,请检查网络后重试",
37 | UNAUTHORIZED: "请先登录后再进行操作",
38 | FORBIDDEN: "您没有权限执行此操作",
39 | NOT_FOUND: "请求的资源不存在",
40 | SERVER_ERROR: "服务器出现错误,请稍后重试",
41 | VALIDATION_ERROR: "输入的信息有误,请检查后重试",
42 | } as const;
43 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingItem/SettingSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SettingItem } from "./SettingItem";
3 |
4 | interface SettingSelectProps {
5 | icon?: string;
6 | iconColor?: string;
7 | title: string;
8 | description?: string;
9 | value: string;
10 | options: { label: string; value: string }[];
11 | disabled?: boolean;
12 | onChange: (value: string) => void;
13 | }
14 |
15 | export const SettingSelect: React.FC = ({
16 | icon,
17 | iconColor,
18 | title,
19 | description,
20 | value,
21 | options,
22 | disabled = false,
23 | onChange,
24 | }) => {
25 | return (
26 |
32 |
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/bitbit/src/shared/utils/date.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_DATE_FORMAT } from "../config/constants";
2 | import dayjs from "dayjs";
3 | import relativeTime from "dayjs/plugin/relativeTime";
4 | dayjs.extend(relativeTime);
5 |
6 | export const formatDate = (
7 | date: Date | string,
8 | format = DEFAULT_DATE_FORMAT
9 | ): string => {
10 | return dayjs(date).format(format);
11 | };
12 |
13 | export const isValidDate = (date: Date | string): boolean => {
14 | return dayjs(date).isValid();
15 | };
16 |
17 | export const getRelativeTime = (date: Date | string): string => {
18 | return dayjs(date).fromNow();
19 | };
20 |
21 | export const addDays = (date: Date | string, days: number): Date => {
22 | return dayjs(date).add(days, "day").toDate();
23 | };
24 |
25 | export const subtractDays = (date: Date | string, days: number): Date => {
26 | return dayjs(date).subtract(days, "day").toDate();
27 | };
28 |
29 | export const isSameDay = (
30 | date1: Date | string,
31 | date2: Date | string
32 | ): boolean => {
33 | return dayjs(date1).isSame(date2, "day");
34 | };
35 |
36 | export const isBefore = (
37 | date1: Date | string,
38 | date2: Date | string
39 | ): boolean => {
40 | return dayjs(date1).isBefore(date2);
41 | };
42 |
43 | export const isAfter = (
44 | date1: Date | string,
45 | date2: Date | string
46 | ): boolean => {
47 | return dayjs(date1).isAfter(date2);
48 | };
49 |
--------------------------------------------------------------------------------
/bitbit/src/store/slices/userSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
2 | import type { User } from "../../types";
3 |
4 | interface UserState {
5 | currentUser: User | null;
6 | loading: boolean;
7 | error: string | null;
8 | }
9 |
10 | const initialState: UserState = {
11 | currentUser: null,
12 | loading: false,
13 | error: null,
14 | };
15 |
16 | const userSlice = createSlice({
17 | name: "user",
18 | initialState,
19 | reducers: {
20 | setUser: (state, action: PayloadAction) => {
21 | state.currentUser = action.payload;
22 | state.error = null;
23 | },
24 | clearUser: (state) => {
25 | state.currentUser = null;
26 | },
27 | updateUserStart: (state) => {
28 | state.loading = true;
29 | state.error = null;
30 | },
31 | updateUserSuccess: (state, action: PayloadAction) => {
32 | state.currentUser = action.payload;
33 | state.loading = false;
34 | state.error = null;
35 | },
36 | updateUserFailure: (state, action: PayloadAction) => {
37 | state.loading = false;
38 | state.error = action.payload;
39 | },
40 | },
41 | });
42 |
43 | export const {
44 | setUser,
45 | clearUser,
46 | updateUserStart,
47 | updateUserSuccess,
48 | updateUserFailure,
49 | } = userSlice.actions;
50 | export const userReducer = userSlice.reducer;
51 |
--------------------------------------------------------------------------------
/bitbit/src/App.css:
--------------------------------------------------------------------------------
1 | .app-container {
2 | display: flex;
3 | flex-direction: column;
4 | min-height: 100vh;
5 | padding-bottom: 72px; /* 为固定底部导航留出空间 */
6 | }
7 |
8 | .app-header {
9 | position: sticky;
10 | top: 0;
11 | z-index: 100;
12 | background-color: #ffffff;
13 | height: 70px;
14 | display: flex;
15 | align-items: center;
16 | border-bottom: 1px solid rgba(0, 0, 0, 0.05);
17 | }
18 |
19 | .header-content {
20 | width: 100%;
21 | padding: 0 16px;
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | }
26 |
27 | .header-title {
28 | margin: 0;
29 | font-size: 18px;
30 | font-weight: 600;
31 | color: #222222;
32 | }
33 |
34 | .main-content {
35 | flex: 1;
36 | overflow-y: auto;
37 | width: 100%;
38 | }
39 |
40 | .bottom-nav {
41 | display: flex;
42 | justify-content: space-around;
43 | align-items: center;
44 | height: 56px;
45 | background-color: #ffffff;
46 | border-top: 1px solid #e0e0e6;
47 | position: fixed;
48 | bottom: 0;
49 | left: 0;
50 | right: 0;
51 | padding: 8px;
52 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.05);
53 | }
54 |
55 | .bottom-nav a {
56 | color: #666666;
57 | text-decoration: none;
58 | display: flex;
59 | flex-direction: column;
60 | align-items: center;
61 | font-size: 12px;
62 | transition: color 0.25s ease;
63 | }
64 |
65 | .bottom-nav a.active {
66 | color: #4e6fff;
67 | }
68 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/DescriptionSection.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, CardContent } from "@/components/ui";
3 | import type { ActivityFormData } from "../ActivityForm";
4 |
5 | interface DescriptionSectionProps {
6 | formData: ActivityFormData;
7 | onFieldChange: (
8 | field: K,
9 | value: ActivityFormData[K]
10 | ) => void;
11 | onFieldFocus: (fieldName: string) => void;
12 | }
13 |
14 | export const DescriptionSection: React.FC = ({
15 | formData,
16 | onFieldChange,
17 | onFieldFocus,
18 | }) => {
19 | return (
20 |
21 |
22 |
23 |
26 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default DescriptionSection;
40 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/components/ChatPageHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | interface ChatPageHeaderProps {
5 | title?: string;
6 | showBackButton?: boolean;
7 | onBack?: () => void;
8 | className?: string;
9 | }
10 |
11 | const ChatPageHeader: React.FC = ({
12 | title = "消息",
13 | showBackButton = true,
14 | onBack,
15 | className,
16 | }) => {
17 | return (
18 |
24 | {/* 返回按钮 */}
25 | {showBackButton && (
26 |
45 | )}
46 |
47 | {/* 标题 */}
48 |
{title}
49 |
50 | );
51 | };
52 |
53 | export default ChatPageHeader;
54 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/hooks/useSettingsNavigation.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from "react";
2 | import type { SettingsModule } from "../types";
3 |
4 | export interface SettingsNavigationItem {
5 | id: SettingsModule;
6 | title: string;
7 | icon: string;
8 | description: string;
9 | badge?: string | number;
10 | }
11 |
12 | const navigationItems: SettingsNavigationItem[] = [
13 | {
14 | id: "account",
15 | title: "账户安全",
16 | icon: "🔐",
17 | description: "密码、验证、设备管理",
18 | },
19 | {
20 | id: "notifications",
21 | title: "通知设置",
22 | icon: "🔔",
23 | description: "推送、邮件、短信通知",
24 | },
25 | {
26 | id: "privacy",
27 | title: "隐私控制",
28 | icon: "🔒",
29 | description: "资料可见性、权限管理",
30 | },
31 | {
32 | id: "app",
33 | title: "应用设置",
34 | icon: "🎨",
35 | description: "主题、语言、界面设置",
36 | },
37 | {
38 | id: "about",
39 | title: "关于帮助",
40 | icon: "❓",
41 | description: "版本、帮助、反馈",
42 | },
43 | ];
44 |
45 | export const useSettingsNavigation = (
46 | initialModule: SettingsModule = "account"
47 | ) => {
48 | const [activeModule, setActiveModule] =
49 | useState(initialModule);
50 |
51 | const handleModuleChange = useCallback((module: SettingsModule) => {
52 | setActiveModule(module);
53 | }, []);
54 |
55 | return {
56 | navigationItems,
57 | activeModule,
58 | setActiveModule: handleModuleChange,
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/bitbit/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bitbit/src/shared/utils/validation.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PASSWORD_MIN_LENGTH,
3 | USERNAME_MIN_LENGTH,
4 | USERNAME_MAX_LENGTH,
5 | } from "../config/constants";
6 |
7 | export const isValidEmail = (email: string): boolean => {
8 | const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
9 | return emailRegex.test(email);
10 | };
11 |
12 | export const isValidPassword = (password: string): boolean => {
13 | return (
14 | password.length >= PASSWORD_MIN_LENGTH &&
15 | /[A-Z]/.test(password) && // 至少一个大写字母
16 | /[a-z]/.test(password) && // 至少一个小写字母
17 | /[0-9]/.test(password) && // 至少一个数字
18 | /[!@#$%^&*]/.test(password) // 至少一个特殊字符
19 | );
20 | };
21 |
22 | export const isValidUsername = (username: string): boolean => {
23 | return (
24 | username.length >= USERNAME_MIN_LENGTH &&
25 | username.length <= USERNAME_MAX_LENGTH &&
26 | /^[a-zA-Z0-9_-]+$/.test(username)
27 | );
28 | };
29 |
30 | export const isValidPhoneNumber = (phone: string): boolean => {
31 | const phoneRegex = /^1[3-9]\d{9}$/; // 中国大陆手机号格式
32 | return phoneRegex.test(phone);
33 | };
34 |
35 | export const isValidURL = (url: string): boolean => {
36 | try {
37 | new URL(url);
38 | return true;
39 | } catch {
40 | return false;
41 | }
42 | };
43 |
44 | export const stripHtml = (html: string): string => {
45 | const tmp = document.createElement("div");
46 | tmp.innerHTML = html;
47 | return tmp.textContent || tmp.innerText || "";
48 | };
49 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface SpinnerProps extends React.HTMLAttributes {
5 | size?: "xs" | "sm" | "md" | "lg" | "xl";
6 | color?: "primary" | "white" | "gray";
7 | }
8 |
9 | const Spinner = forwardRef(
10 | ({ className, size = "md", color = "primary", ...props }, ref) => {
11 | const sizes = {
12 | xs: "w-3 h-3",
13 | sm: "w-4 h-4",
14 | md: "w-6 h-6",
15 | lg: "w-8 h-8",
16 | xl: "w-10 h-10",
17 | };
18 |
19 | const colors = {
20 | primary: "border-primary-500",
21 | white: "border-white",
22 | gray: "border-gray-400",
23 | };
24 |
25 | return (
26 |
37 |
38 | Loading...
39 |
40 |
41 | );
42 | }
43 | );
44 |
45 | Spinner.displayName = "Spinner";
46 |
47 | export default Spinner;
48 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/BasicInfoSection.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, CardContent, Input } from "@/components/ui";
3 | import type { ActivityFormData } from "../ActivityForm";
4 |
5 | interface BasicInfoSectionProps {
6 | formData: ActivityFormData;
7 | onFieldChange: (
8 | field: K,
9 | value: ActivityFormData[K]
10 | ) => void;
11 | onFieldFocus: (fieldName: string) => void;
12 | }
13 |
14 | export const BasicInfoSection: React.FC = ({
15 | formData,
16 | onFieldChange,
17 | onFieldFocus,
18 | }) => {
19 | return (
20 |
21 |
22 |
23 | 基本信息
24 |
25 |
26 |
27 |
28 |
31 | onFieldChange("title", e.target.value)}
35 | onFocus={() => onFieldFocus("title")}
36 | className="text-lg"
37 | />
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default BasicInfoSection;
46 |
--------------------------------------------------------------------------------
/bitbit/src/shared/components/SortSelector/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export interface SortOption {
4 | key: string;
5 | label: string;
6 | }
7 |
8 | export interface SortSelectorProps {
9 | /** 排序选项列表 */
10 | options: SortOption[];
11 | /** 当前选中的排序值 */
12 | value: string;
13 | /** 排序值变化回调 */
14 | onChange: (value: string) => void;
15 | /** 标签文本 */
16 | label?: string;
17 | /** 是否禁用 */
18 | disabled?: boolean;
19 | /** 自定义样式类名 */
20 | className?: string;
21 | }
22 |
23 | /**
24 | * 通用排序选择器组件
25 | * 提供统一的排序选择样式和交互行为
26 | */
27 | export const SortSelector: React.FC = ({
28 | options,
29 | value,
30 | onChange,
31 | label = "排序:",
32 | disabled = false,
33 | className = "",
34 | }) => {
35 | return (
36 |
37 | {label && (
38 | {label}
39 | )}
40 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/bitbit/src/features/notifications/components/NotificationTabs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { TabFilter, type TabOption } from "@/shared/components";
3 | import type { NotificationType, NotificationStats } from "../types";
4 |
5 | interface NotificationTabsProps {
6 | activeType: NotificationType;
7 | onTypeChange: (type: NotificationType) => void;
8 | stats: NotificationStats;
9 | }
10 |
11 | export const NotificationTabs: React.FC = ({
12 | activeType,
13 | onTypeChange,
14 | stats,
15 | }) => {
16 | const options: TabOption[] = [
17 | {
18 | key: "all",
19 | label: "全部",
20 | count: stats.byType.all,
21 | },
22 | {
23 | key: "activity",
24 | label: "活动",
25 | count: stats.byType.activity,
26 | },
27 | {
28 | key: "social",
29 | label: "社交",
30 | count: stats.byType.social,
31 | },
32 | {
33 | key: "system",
34 | label: "系统",
35 | count: stats.byType.system,
36 | },
37 | ];
38 |
39 | const handleChange = (value: string) => {
40 | onTypeChange(value as NotificationType);
41 | };
42 |
43 | return (
44 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/ActivityFilter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type ActivityFilterType = "all" | "organized" | "registered" | "ended";
4 |
5 | interface ActivityFilterProps {
6 | activeFilter: ActivityFilterType;
7 | onFilterChange: (filter: ActivityFilterType) => void;
8 | }
9 |
10 | const filters = [
11 | { key: "all" as const, label: "全部活动" },
12 | { key: "organized" as const, label: "我组织的" },
13 | { key: "registered" as const, label: "我参加的" },
14 | { key: "ended" as const, label: "已结束" },
15 | ];
16 |
17 | export const ActivityFilter: React.FC = ({
18 | activeFilter,
19 | onFilterChange,
20 | }) => {
21 | return (
22 |
23 |
24 | {filters.map((filter) => (
25 |
39 | ))}
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityDetail/OrganizerInfo.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { Button, Card, CardContent } from "@/components/ui";
3 | import type { Activity } from "@/shared/types";
4 |
5 | interface OrganizerInfoProps {
6 | activity: Activity;
7 | onFollow?: () => void;
8 | }
9 |
10 | const OrganizerInfo: FC = ({ activity, onFollow }) => {
11 | return (
12 |
13 |
组织者
14 |
15 |
16 |
17 |
18 |
19 | {activity.organizer.username.slice(0, 2).toUpperCase()}
20 |
21 |
22 |
23 | {activity.organizer.username}
24 |
25 |
26 | 已组织32次活动 | 4.9星评分
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default OrganizerInfo;
41 |
--------------------------------------------------------------------------------
/bitbit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitbit",
3 | "private": true,
4 | "version": "2.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "dev:debug": "VITE_SHOW_DEBUG_COMPONENTS=true vite",
9 | "dev:clean": "VITE_SHOW_DEBUG_COMPONENTS=false vite",
10 | "build": "vite build",
11 | "build:dev": "VITE_SHOW_DEBUG_COMPONENTS=true vite build",
12 | "build:prod": "VITE_SHOW_DEBUG_COMPONENTS=false vite build",
13 | "build:with-types": "tsc -b && vite build",
14 | "lint": "eslint .",
15 | "preview": "vite preview"
16 | },
17 | "dependencies": {
18 | "@reduxjs/toolkit": "^2.8.2",
19 | "@types/node": "^24.3.0",
20 | "clsx": "^2.1.1",
21 | "date-fns": "^4.1.0",
22 | "dayjs": "^1.11.13",
23 | "react": "^19.1.1",
24 | "react-day-picker": "^9.9.0",
25 | "react-dom": "^19.1.1",
26 | "react-redux": "^9.2.0",
27 | "react-router-dom": "^6.30.1",
28 | "tailwind-merge": "^3.3.1"
29 | },
30 | "devDependencies": {
31 | "@eslint/js": "^9.33.0",
32 | "@types/react": "^19.1.10",
33 | "@types/react-dom": "^19.1.7",
34 | "@vitejs/plugin-react": "^5.0.0",
35 | "autoprefixer": "^10.4.21",
36 | "eslint": "^9.33.0",
37 | "eslint-plugin-react-hooks": "^5.2.0",
38 | "eslint-plugin-react-refresh": "^0.4.20",
39 | "globals": "^16.3.0",
40 | "postcss": "^8.5.6",
41 | "postcss-nested": "^7.0.2",
42 | "tailwindcss": "^3.4.17",
43 | "typescript": "~5.8.3",
44 | "typescript-eslint": "^8.39.1",
45 | "vite": "^7.1.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityForm/ImageUploadSection.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, CardContent, ImageUpload } from "@/components/ui";
3 | import type { ActivityFormData } from "../ActivityForm";
4 |
5 | interface ImageUploadSectionProps {
6 | formData: ActivityFormData;
7 | onFieldChange: (
8 | field: K,
9 | value: ActivityFormData[K]
10 | ) => void;
11 | onFieldFocus: (fieldName: string) => void;
12 | }
13 |
14 | export const ImageUploadSection: React.FC = ({
15 | formData,
16 | onFieldChange,
17 | onFieldFocus,
18 | }) => {
19 | return (
20 |
21 |
22 |
23 |
26 |
onFieldFocus("images")}>
27 | onFieldChange("images", images)}
30 | onRemove={(index) =>
31 | onFieldChange(
32 | "images",
33 | formData.images.filter((_, i) => i !== index)
34 | )
35 | }
36 | maxImages={6}
37 | description="最多上传 6 张图片,支持 JPG、PNG 格式"
38 | />
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default ImageUploadSection;
47 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/BaseCard/types.ts:
--------------------------------------------------------------------------------
1 | export interface BaseCardProps {
2 | className?: string;
3 | children?: React.ReactNode;
4 | variant?: "default" | "outlined" | "elevated" | "flat";
5 | size?: "sm" | "md" | "lg";
6 | clickable?: boolean;
7 | disabled?: boolean;
8 | loading?: boolean;
9 | onClick?: () => void;
10 | onHover?: () => void;
11 | }
12 |
13 | export interface CardLayout {
14 | layout?: "default" | "compact" | "minimal" | "horizontal" | "list";
15 | }
16 |
17 | export interface CardActions {
18 | showActions?: boolean;
19 | primaryAction?: {
20 | label: string;
21 | onClick: () => void;
22 | variant?: "primary" | "secondary" | "danger";
23 | disabled?: boolean;
24 | };
25 | secondaryActions?: Array<{
26 | label: string;
27 | icon?: string;
28 | onClick: () => void;
29 | variant?: "ghost" | "outline";
30 | disabled?: boolean;
31 | }>;
32 | }
33 |
34 | export interface CardStats {
35 | showStats?: boolean;
36 | stats?: Array<{
37 | icon?: string;
38 | label: string;
39 | value: number | string;
40 | onClick?: () => void;
41 | }>;
42 | }
43 |
44 | export interface CardMeta {
45 | showMeta?: boolean;
46 | meta?: {
47 | author?: {
48 | name: string;
49 | avatar?: string;
50 | isVerified?: boolean;
51 | };
52 | publishTime?: string;
53 | category?: string;
54 | tags?: string[];
55 | status?: {
56 | text: string;
57 | variant?: "success" | "warning" | "error" | "info" | "default";
58 | };
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/CategoryItem/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Card, { CardContent } from "../Card";
3 | import { cn } from "@/shared/utils/cn";
4 |
5 | export interface CategoryItemProps {
6 | id: string;
7 | name: string;
8 | icon: string;
9 | color?: string;
10 | onClick?: () => void;
11 | className?: string;
12 | }
13 |
14 | const CategoryItem: React.FC = ({
15 | name,
16 | icon,
17 | color = "primary",
18 | onClick,
19 | className,
20 | }) => {
21 | const colorClasses = {
22 | primary: "bg-primary-100 text-primary-600",
23 | coral: "bg-coral-100 text-coral-500",
24 | mint: "bg-mint-100 text-mint-500",
25 | lavender: "bg-lavender-100 text-lavender-500",
26 | sunflower: "bg-sunflower-100 text-sunflower-500",
27 | };
28 |
29 | return (
30 |
38 |
39 |
46 | {icon}
47 |
48 | {name}
49 |
50 |
51 | );
52 | };
53 |
54 | export default CategoryItem;
55 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingItem/SettingItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | interface SettingItemProps {
5 | icon?: string;
6 | iconColor?: string;
7 | title: string;
8 | description?: string;
9 | children: React.ReactNode;
10 | className?: string;
11 | }
12 |
13 | export const SettingItem: React.FC = ({
14 | icon,
15 | iconColor = "bg-blue-100",
16 | title,
17 | description,
18 | children,
19 | className,
20 | }) => {
21 | return (
22 |
28 | {icon && (
29 |
30 |
36 | {icon}
37 |
38 |
39 | )}
40 |
41 |
42 |
43 | {title}
44 |
45 | {description && (
46 |
47 | {description}
48 |
49 | )}
50 |
51 |
52 |
{children}
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/BaseCard/variants.ts:
--------------------------------------------------------------------------------
1 | import { cn } from "@/shared/utils/cn";
2 |
3 | export const getCardVariantClasses = (
4 | variant: "default" | "outlined" | "elevated" | "flat" = "default",
5 | size: "sm" | "md" | "lg" = "md",
6 | clickable: boolean = false,
7 | disabled: boolean = false,
8 | loading: boolean = false
9 | ) => {
10 | const baseClasses =
11 | "rounded-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2";
12 |
13 | const variantClasses = {
14 | default:
15 | "bg-white border border-gray-100 hover:border-gray-200 hover:shadow-sm",
16 | outlined: "bg-white border-2 border-gray-200 hover:border-gray-300",
17 | elevated: "bg-white shadow-md hover:shadow-lg border-0",
18 | flat: "bg-gray-50 border-0 hover:bg-gray-100",
19 | };
20 |
21 | const sizeClasses = {
22 | sm: "p-3",
23 | md: "p-4",
24 | lg: "p-6",
25 | };
26 |
27 | return cn(
28 | baseClasses,
29 | variantClasses[variant],
30 | sizeClasses[size],
31 | clickable && "cursor-pointer",
32 | disabled && "opacity-50 cursor-not-allowed",
33 | loading && "animate-pulse"
34 | );
35 | };
36 |
37 | export const getLayoutClasses = (
38 | layout: "default" | "compact" | "minimal" | "horizontal" | "list" = "default"
39 | ) => {
40 | const layoutClasses = {
41 | default: "space-y-4",
42 | compact: "space-y-2",
43 | minimal: "space-y-1",
44 | horizontal: "flex items-center space-x-4 space-y-0",
45 | list: "flex items-start space-x-3 space-y-0",
46 | };
47 |
48 | return layoutClasses[layout];
49 | };
50 |
--------------------------------------------------------------------------------
/bitbit/src/shared/hooks/useMediaQuery.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | /**
4 | * 用于检测媒体查询匹配状态的 Hook
5 | * @param query - 媒体查询字符串,例如 '(min-width: 768px)'
6 | * @returns boolean - 媒体查询是否匹配
7 | */
8 | export function useMediaQuery(query: string): boolean {
9 | const [matches, setMatches] = useState(() => {
10 | // 在服务端渲染时返回 false,避免水合不匹配
11 | if (typeof window === "undefined") {
12 | return false;
13 | }
14 | return window.matchMedia(query).matches;
15 | });
16 |
17 | useEffect(() => {
18 | // 再次检查是否在浏览器环境
19 | if (typeof window === "undefined") {
20 | return;
21 | }
22 |
23 | const mediaQueryList = window.matchMedia(query);
24 |
25 | // 设置初始值
26 | setMatches(mediaQueryList.matches);
27 |
28 | // 创建事件处理器
29 | const handler = (event: MediaQueryListEvent) => {
30 | setMatches(event.matches);
31 | };
32 |
33 | // 添加监听器
34 | mediaQueryList.addEventListener("change", handler);
35 |
36 | // 清理函数
37 | return () => {
38 | mediaQueryList.removeEventListener("change", handler);
39 | };
40 | }, [query]);
41 |
42 | return matches;
43 | }
44 |
45 | /**
46 | * 预定义的响应式断点 hooks
47 | */
48 | export const useIsDesktop = () => useMediaQuery("(min-width: 1024px)");
49 | export const useIsTablet = () =>
50 | useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
51 | export const useIsMobile = () => useMediaQuery("(max-width: 767px)");
52 | export const useIsMediumAndUp = () => useMediaQuery("(min-width: 768px)");
53 | export const useIsSmallAndDown = () => useMediaQuery("(max-width: 767px)");
54 |
--------------------------------------------------------------------------------
/bitbit/src/config/navigation.config.ts:
--------------------------------------------------------------------------------
1 | // 导航配置
2 | export const navigationConfig = {
3 | // 默认导航类型: 'fixed' | 'smart' | 'floating'
4 | defaultType: "fixed" as const,
5 |
6 | // 智能隐藏导航配置
7 | smartNavigation: {
8 | // 是否启用智能隐藏
9 | enabled: false,
10 | // 隐藏阈值(滚动多少像素后开始隐藏)
11 | hideThreshold: 100,
12 | // 节流间隔(毫秒)
13 | throttleInterval: 10,
14 | },
15 |
16 | // 悬浮导航配置
17 | floatingNavigation: {
18 | // 是否启用悬浮导航
19 | enabled: false,
20 | // 按钮位置: 'bottom-right' | 'bottom-left' | 'bottom-center'
21 | position: "bottom-right" as const,
22 | // 距离底部的偏移量
23 | bottomOffset: 24,
24 | // 距离侧边的偏移量
25 | sideOffset: 24,
26 | },
27 |
28 | // 导航项配置
29 | navigationItems: [
30 | {
31 | path: "/",
32 | label: "首页",
33 | iconName: "home" as const,
34 | end: true,
35 | },
36 | {
37 | path: "/activities",
38 | label: "活动",
39 | iconName: "activity" as const,
40 | },
41 | {
42 | path: "/community",
43 | label: "社区",
44 | iconName: "community" as const,
45 | },
46 | {
47 | path: "/exchange",
48 | label: "二手",
49 | iconName: "exchange" as const,
50 | },
51 | {
52 | path: "/profile",
53 | label: "我的",
54 | iconName: "profile" as const,
55 | },
56 | ],
57 |
58 | // 样式配置
59 | styles: {
60 | // 固定导航栏高度
61 | fixedNavHeight: 72,
62 | // 动画持续时间
63 | transitionDuration: 300,
64 | // 激活状态颜色
65 | activeColor: "#3b82f6",
66 | // 默认颜色
67 | defaultColor: "#6b7280",
68 | },
69 | };
70 |
71 | export type NavigationConfig = typeof navigationConfig;
72 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/UserCard/types.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: string;
3 | username: string;
4 | name?: string;
5 | fullName?: string;
6 | email: string;
7 | avatar?: string;
8 | color?: string;
9 | bio?: string;
10 | profession?: string;
11 | age?: number;
12 | location?: string;
13 | interests?: string[];
14 | tags?: string[];
15 | isOnline?: boolean;
16 | joinedDate?: string;
17 | joinDate?: string;
18 | level?: number;
19 | following?: number;
20 | followers?: number;
21 | followersCount?: number;
22 | followingCount?: number;
23 | activitiesCount?: number;
24 | organizedCount?: number;
25 | postsCount?: number;
26 | stats?: {
27 | totalPosts?: number;
28 | totalExchanges?: number;
29 | };
30 | mutualFriends?: number;
31 | isFollowed?: boolean;
32 | isOrganizer?: boolean;
33 | createdAt: string;
34 | updatedAt: string;
35 | }
36 |
37 | export interface UserCardProps {
38 | user: User;
39 | layout?: "vertical" | "horizontal" | "compact";
40 | showActions?: boolean;
41 | showStats?: boolean;
42 | showMutualFriends?: boolean;
43 | showPopover?: boolean; // 新增:是否显示悬停卡片
44 | isFollowing?: boolean;
45 | className?: string;
46 | onClick?: () => void;
47 | onFollow?: (isFollowing: boolean) => void;
48 | onMessage?: () => void;
49 | onViewProfile?: () => void;
50 | }
51 |
52 | export type UserListItem = Pick<
53 | User,
54 | | "id"
55 | | "username"
56 | | "name"
57 | | "avatar"
58 | | "bio"
59 | | "location"
60 | | "isOnline"
61 | | "profession"
62 | | "age"
63 | | "interests"
64 | > & {
65 | mutualFriends?: number;
66 | isFollowing?: boolean;
67 | };
68 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/OtherUserActivityFilter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type OtherUserActivityFilterType =
4 | | "all"
5 | | "organized"
6 | | "participated"
7 | | "recent";
8 |
9 | interface OtherUserActivityFilterProps {
10 | activeFilter: OtherUserActivityFilterType;
11 | onFilterChange: (filter: OtherUserActivityFilterType) => void;
12 | userName?: string;
13 | }
14 |
15 | const getFilters = (userName?: string) => [
16 | { key: "all" as const, label: "全部活动" },
17 | {
18 | key: "organized" as const,
19 | label: `${userName ? `${userName}组织的` : "TA组织的"}`,
20 | },
21 | {
22 | key: "participated" as const,
23 | label: `${userName ? `${userName}参与的` : "TA参与的"}`,
24 | },
25 | { key: "recent" as const, label: "最近活动" },
26 | ];
27 |
28 | export const OtherUserActivityFilter: React.FC<
29 | OtherUserActivityFilterProps
30 | > = ({ activeFilter, onFilterChange, userName }) => {
31 | const filters = getFilters(userName);
32 |
33 | return (
34 |
35 | {filters.map((filter) => (
36 |
50 | ))}
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/BaseCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 | import type { BaseCardProps } from "./types";
4 | import { getCardVariantClasses } from "./variants";
5 |
6 | export const BaseCard: React.FC = ({
7 | className,
8 | children,
9 | variant = "default",
10 | size = "md",
11 | clickable = false,
12 | disabled = false,
13 | loading = false,
14 | onClick,
15 | onHover,
16 | }) => {
17 | const cardClasses = getCardVariantClasses(
18 | variant,
19 | size,
20 | clickable,
21 | disabled,
22 | loading
23 | );
24 |
25 | const handleClick = () => {
26 | if (!disabled && !loading && onClick) {
27 | onClick();
28 | }
29 | };
30 |
31 | const handleKeyDown = (event: React.KeyboardEvent) => {
32 | if (
33 | !disabled &&
34 | !loading &&
35 | onClick &&
36 | (event.key === "Enter" || event.key === " ")
37 | ) {
38 | event.preventDefault();
39 | onClick();
40 | }
41 | };
42 |
43 | return (
44 |
53 | {loading ? (
54 |
58 | ) : (
59 | children
60 | )}
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/bitbit/src/store/slices/settingsSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
2 |
3 | interface SettingsState {
4 | language: "zh" | "en";
5 | notifications: {
6 | email: boolean;
7 | push: boolean;
8 | activities: boolean;
9 | messages: boolean;
10 | };
11 | privacy: {
12 | profileVisibility: "public" | "private" | "friends";
13 | showOnlineStatus: boolean;
14 | };
15 | }
16 |
17 | const initialState: SettingsState = {
18 | language: "zh",
19 | notifications: {
20 | email: true,
21 | push: true,
22 | activities: true,
23 | messages: true,
24 | },
25 | privacy: {
26 | profileVisibility: "public",
27 | showOnlineStatus: true,
28 | },
29 | };
30 |
31 | const settingsSlice = createSlice({
32 | name: "settings",
33 | initialState,
34 | reducers: {
35 | setLanguage: (state, action: PayloadAction) => {
36 | state.language = action.payload;
37 | },
38 | updateNotificationSettings: (
39 | state,
40 | action: PayloadAction>
41 | ) => {
42 | state.notifications = {
43 | ...state.notifications,
44 | ...action.payload,
45 | };
46 | },
47 | updatePrivacySettings: (
48 | state,
49 | action: PayloadAction>
50 | ) => {
51 | state.privacy = {
52 | ...state.privacy,
53 | ...action.payload,
54 | };
55 | },
56 | },
57 | });
58 |
59 | export const {
60 | setLanguage,
61 | updateNotificationSettings,
62 | updatePrivacySettings,
63 | } = settingsSlice.actions;
64 | export const settingsReducer = settingsSlice.reducer;
65 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingItem/SettingNavigation.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SettingItem } from "./SettingItem";
3 |
4 | interface SettingNavigationProps {
5 | icon?: string;
6 | iconColor?: string;
7 | title: string;
8 | description?: string;
9 | badge?: string | number;
10 | disabled?: boolean;
11 | onClick: () => void;
12 | }
13 |
14 | export const SettingNavigation: React.FC = ({
15 | icon,
16 | iconColor,
17 | title,
18 | description,
19 | badge,
20 | disabled = false,
21 | onClick,
22 | }) => {
23 | return (
24 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityDetail/ActivityLocation.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { Button, Card, CardContent } from "@/components/ui";
3 | import type { Activity } from "@/shared/types";
4 |
5 | interface ActivityLocationProps {
6 | activity: Activity;
7 | onViewDetails?: () => void;
8 | onNavigate?: () => void;
9 | }
10 |
11 | const ActivityLocation: FC = ({
12 | activity,
13 | onViewDetails,
14 | onNavigate,
15 | }) => {
16 | return (
17 |
18 |
活动地点
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {activity.location}
27 |
28 |
活动地点
29 |
30 |
31 |
32 |
40 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default ActivityLocation;
52 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/hooks/useVerificationCode.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from "react";
2 | import { AuthService } from "../services/authService";
3 |
4 | interface UseVerificationCodeReturn {
5 | countdown: number;
6 | isCountingDown: boolean;
7 | loading: boolean;
8 | error: string | null;
9 | sendCode: (phone: string) => Promise;
10 | clearError: () => void;
11 | }
12 |
13 | export const useVerificationCode = (): UseVerificationCodeReturn => {
14 | const [countdown, setCountdown] = useState(0);
15 | const [loading, setLoading] = useState(false);
16 | const [error, setError] = useState(null);
17 |
18 | const sendCode = useCallback(async (phone: string) => {
19 | try {
20 | setLoading(true);
21 | setError(null);
22 |
23 | const response = await AuthService.sendVerificationCode(phone);
24 |
25 | if (response.success && response.countdown) {
26 | // 开始倒计时
27 | setCountdown(response.countdown);
28 |
29 | const timer = setInterval(() => {
30 | setCountdown((prev) => {
31 | if (prev <= 1) {
32 | clearInterval(timer);
33 | return 0;
34 | }
35 | return prev - 1;
36 | });
37 | }, 1000);
38 | }
39 | } catch (error) {
40 | const message = error instanceof Error ? error.message : "发送验证码失败";
41 | setError(message);
42 | throw error;
43 | } finally {
44 | setLoading(false);
45 | }
46 | }, []);
47 |
48 | const clearError = useCallback(() => {
49 | setError(null);
50 | }, []);
51 |
52 | return {
53 | countdown,
54 | isCountingDown: countdown > 0,
55 | loading,
56 | error,
57 | sendCode,
58 | clearError,
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/components/AuthHeader/AuthHeader.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | interface AuthHeaderProps {
5 | title: string;
6 | showBackButton?: boolean;
7 | }
8 |
9 | const AuthHeader: FC = ({ title, showBackButton = false }) => {
10 | const navigate = useNavigate();
11 |
12 | return (
13 |
14 | {/* 状态栏模拟 */}
15 |
16 |
{title}
17 |
18 |
19 | {/* 返回按钮 */}
20 | {showBackButton && (
21 |
27 | )}
28 |
29 | {/* Logo区域 */}
30 |
31 |
32 | BB
33 |
34 |
35 |
36 | {title === "BitBit 登录" ? "BitBit" : "加入BitBit"}
37 |
38 |
39 | {title === "BitBit 登录"
40 | ? "连接你我,分享精彩"
41 | : "开启你的精彩社交生活"}
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default AuthHeader;
49 |
--------------------------------------------------------------------------------
/bitbit/src/shared/hooks/useChatNavigation.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import { useNavigate, useLocation } from "react-router-dom";
3 | import { navigateToChatFromUserCard } from "@/features/chat/utils";
4 | import { useChatState } from "@/features/chat/hooks/useChatState";
5 |
6 | /**
7 | * 统一的聊天导航Hook
8 | * 提供标准化的用户卡片发私信功能
9 | */
10 | export const useChatNavigation = () => {
11 | const navigate = useNavigate();
12 | const location = useLocation();
13 | const { conversations } = useChatState({ currentUserId: "4" }); // 使用当前用户ID
14 |
15 | /**
16 | * 导航到与指定用户的私聊
17 | * 自动处理新会话创建和历史会话恢复
18 | */
19 | const navigateToUserChat = useCallback(
20 | (userId: string, userInfo?: { name?: string; avatar?: string }) => {
21 | // 使用标准的聊天导航函数,传递当前路径信息
22 | navigateToChatFromUserCard(navigate, userId, userInfo, location.pathname);
23 | },
24 | [navigate, location.pathname]
25 | );
26 |
27 | /**
28 | * 检查是否与用户有历史聊天记录
29 | * 这个函数用于判断是否需要加载现有会话还是创建新会话
30 | */
31 | const hasConversationWithUser = useCallback(
32 | (userId: string): boolean => {
33 | // 检查conversations中是否已存在与该用户的私聊会话
34 | const existingConversation = conversations.find(
35 | (conv) =>
36 | conv.type === "private" &&
37 | conv.participants.length === 2 &&
38 | conv.participants.some((p) => p.userId === userId)
39 | );
40 |
41 | const hasHistory = !!existingConversation;
42 | console.log("检查用户历史会话:", {
43 | userId,
44 | hasHistory,
45 | conversationId: existingConversation?.id,
46 | });
47 |
48 | return hasHistory;
49 | },
50 | [conversations]
51 | );
52 |
53 | return {
54 | navigateToUserChat,
55 | hasConversationWithUser,
56 | };
57 | };
58 |
59 | export default useChatNavigation;
60 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Breadcrumb/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import { cn } from "@/shared/utils/cn";
4 |
5 | export interface BreadcrumbItem {
6 | label: string;
7 | href?: string;
8 | current?: boolean;
9 | }
10 |
11 | export interface BreadcrumbProps {
12 | items: BreadcrumbItem[];
13 | className?: string;
14 | }
15 |
16 | const Breadcrumb: React.FC = ({ items, className }) => {
17 | return (
18 |
54 | );
55 | };
56 |
57 | export default Breadcrumb;
58 |
--------------------------------------------------------------------------------
/bitbit/src/features/auth/types/auth.types.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: string;
3 | phone: string;
4 | nickname: string;
5 | avatar?: string;
6 | interests?: string[];
7 | createdAt: string;
8 | }
9 |
10 | export interface LoginData {
11 | phone: string;
12 | verificationCode: string;
13 | rememberMe?: boolean;
14 | }
15 |
16 | export interface RegisterData {
17 | phone: string;
18 | verificationCode: string;
19 | nickname: string;
20 | password: string;
21 | confirmPassword: string;
22 | interests?: string[];
23 | agreeToTerms: boolean;
24 | acceptMarketing?: boolean;
25 | }
26 |
27 | export interface AuthResponse {
28 | token: string;
29 | user: User;
30 | }
31 |
32 | export interface VerificationCodeResponse {
33 | success: boolean;
34 | message: string;
35 | countdown?: number;
36 | }
37 |
38 | // 表单验证错误类型
39 | export interface FormErrors {
40 | phone?: string;
41 | verificationCode?: string;
42 | nickname?: string;
43 | password?: string;
44 | confirmPassword?: string;
45 | agreeToTerms?: string;
46 | }
47 |
48 | // 兴趣标签选项
49 | export interface InterestOption {
50 | id: string;
51 | label: string;
52 | color: string;
53 | bgColor: string;
54 | }
55 |
56 | export const INTEREST_OPTIONS: InterestOption[] = [
57 | { id: "sports", label: "运动", color: "#FFFFFF", bgColor: "#4E6FFF" },
58 | {
59 | id: "music",
60 | label: "音乐",
61 | color: "#FF6B8B",
62 | bgColor: "rgba(255, 107, 139, 0.2)",
63 | },
64 | { id: "food", label: "美食", color: "#666666", bgColor: "#FFFFFF" },
65 | {
66 | id: "study",
67 | label: "学习",
68 | color: "#FFB951",
69 | bgColor: "rgba(255, 185, 81, 0.2)",
70 | },
71 | { id: "reading", label: "阅读", color: "#666666", bgColor: "#FFFFFF" },
72 | { id: "travel", label: "旅行", color: "#666666", bgColor: "#FFFFFF" },
73 | ];
74 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/ActivityCard/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | BaseCardProps,
3 | CardLayout,
4 | CardActions,
5 | } from "../../BaseCard/types";
6 | import type { Activity as SharedActivity } from "@/shared/types";
7 |
8 | export interface Activity {
9 | id: string;
10 | title: string;
11 | description: string;
12 | category: "music" | "food" | "learning" | "reading";
13 | date: string;
14 | time: string;
15 | location: string;
16 | maxParticipants: number;
17 | currentParticipants: number;
18 | organizer: {
19 | username: string;
20 | avatar?: string;
21 | };
22 | images?: string[];
23 | price?: number;
24 | isFree?: boolean;
25 | isJoined?: boolean;
26 | status?:
27 | | "draft"
28 | | "published"
29 | | "ongoing"
30 | | "ended"
31 | | "cancelled"
32 | | "completed"
33 | | "registered"
34 | | "organized";
35 | }
36 |
37 | // 允许使用共享的Activity类型,但转换为UI组件期望的格式
38 | export type ActivityCardActivity =
39 | | Activity
40 | | (SharedActivity & {
41 | maxParticipants: number;
42 | organizer: {
43 | username: string;
44 | avatar?: string;
45 | };
46 | });
47 |
48 | export interface ActivityCardProps
49 | extends Omit,
50 | CardLayout,
51 | CardActions {
52 | activity: ActivityCardActivity;
53 |
54 | // 活动特定的回调
55 | onJoin?: () => void;
56 | onLeave?: () => void;
57 | onEdit?: () => void;
58 | onNotify?: () => void;
59 | onShare?: () => void;
60 | onBookmark?: () => void;
61 | onViewDetail?: () => void;
62 |
63 | // 显示控制
64 | showPrice?: boolean;
65 | showParticipants?: boolean;
66 | showOrganizer?: boolean;
67 | showDate?: boolean;
68 | showLocation?: boolean;
69 | showDescription?: boolean;
70 | showImages?: boolean;
71 |
72 | // 状态控制
73 | isBookmarked?: boolean;
74 | }
75 |
--------------------------------------------------------------------------------
/bitbit/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // 全局类型定义
2 | export interface User {
3 | id: string;
4 | username?: string;
5 | name?: string; // 显示名称
6 | email: string;
7 | avatar?: string;
8 | bio?: string;
9 | location?: string;
10 | joinDate?: string;
11 | joinedDate?: string; // 兼容字段
12 | rating?: number;
13 | totalSales?: number;
14 | badges?: string[];
15 |
16 | // 个人信息
17 | profession?: string;
18 | age?: number;
19 | level?: number;
20 | following?: number;
21 | followers?: number;
22 | interests?: string[];
23 |
24 | // 在线状态
25 | isOnline?: boolean;
26 |
27 | // 活动统计
28 | activitiesCount?: number;
29 | postCount?: number;
30 | exchangeCount?: number;
31 |
32 | preferences?: {
33 | notifications: boolean;
34 | emailUpdates: boolean;
35 | publicProfile: boolean;
36 | };
37 | stats?: {
38 | totalPosts: number;
39 | totalExchanges: number;
40 | totalViews: number;
41 | };
42 | }
43 |
44 | // 通用响应类型
45 | export interface ApiResponse {
46 | success: boolean;
47 | data?: T;
48 | message?: string;
49 | error?: string;
50 | }
51 |
52 | // 分页数据类型
53 | export interface PaginatedResponse {
54 | items: T[];
55 | total: number;
56 | page: number;
57 | limit: number;
58 | hasNext: boolean;
59 | hasPrev: boolean;
60 | }
61 |
62 | // 通用过滤器类型
63 | export interface BaseFilters {
64 | search?: string;
65 | page?: number;
66 | limit?: number;
67 | sortBy?: string;
68 | sortOrder?: "asc" | "desc";
69 | }
70 |
71 | // 通用状态类型
72 | export interface LoadingState {
73 | loading: boolean;
74 | error: string | null;
75 | }
76 |
77 | // 表单验证错误类型
78 | export interface FormErrors {
79 | [key: string]: string | undefined;
80 | }
81 |
82 | // 上传文件类型
83 | export interface UploadFile {
84 | id: string;
85 | name: string;
86 | url: string;
87 | size: number;
88 | type: string;
89 | }
90 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/PostDetail/PostDetailRecommendations.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { PostCard } from "@/components/ui/cards";
3 |
4 | interface Recommendation {
5 | id: string;
6 | author: {
7 | name: string;
8 | avatar?: string;
9 | isVerified?: boolean;
10 | };
11 | content: string;
12 | images?: string[];
13 | category?: "music" | "food" | "learning" | "reading";
14 | tags?: string[]; // 添加标签字段
15 | publishTime: string;
16 | likes: number;
17 | comments: number;
18 | shares: number;
19 | isLiked?: boolean;
20 | isBookmarked?: boolean;
21 | }
22 |
23 | interface PostDetailRecommendationsProps {
24 | recommendations: Recommendation[];
25 | onRecommendationClick: (id: string) => void;
26 | onTagClick?: (tag: string) => void; // 添加标签点击回调
27 | }
28 |
29 | const PostDetailRecommendations: FC = ({
30 | recommendations,
31 | onRecommendationClick,
32 | onTagClick,
33 | }) => {
34 | return (
35 |
36 |
相关动态推荐
37 |
38 |
39 | {recommendations.map((item) => (
40 |
{
45 | console.log("相关推荐卡片被点击,ID:", item.id);
46 | onRecommendationClick(item.id);
47 | }}
48 | onLike={() => console.log(`点赞推荐: ${item.id}`)}
49 | onComment={() => console.log(`评论推荐: ${item.id}`)}
50 | onShare={() => console.log(`分享推荐: ${item.id}`)}
51 | onBookmark={() => console.log(`收藏推荐: ${item.id}`)}
52 | onTagClick={onTagClick} // 添加标签点击回调
53 | />
54 | ))}
55 |
56 |
57 | );
58 | };
59 |
60 | export default PostDetailRecommendations;
61 |
--------------------------------------------------------------------------------
/bitbit/src/utils/testActivityStatusFix.ts:
--------------------------------------------------------------------------------
1 | // 测试活动状态一致性修复
2 | import { getAllActivities } from "@/shared/data/activities";
3 | import { canJoinActivity, isActivityEnded } from "@/shared/utils/activityUtils";
4 |
5 | export const testActivityStatusConsistency = () => {
6 | console.log("=== 测试活动状态一致性修复 ===");
7 |
8 | const activities = getAllActivities();
9 |
10 | // 筛选出状态为 published 的活动
11 | const publishedActivities = activities.filter(
12 | (activity) => activity.status === "published"
13 | );
14 |
15 | console.log(
16 | `\n找到 ${publishedActivities.length} 个状态为 published 的活动:\n`
17 | );
18 |
19 | publishedActivities.forEach((activity, index) => {
20 | const isEnded = isActivityEnded(activity);
21 | const canJoin = canJoinActivity(activity);
22 | const endTime = new Date(activity.endTime);
23 | const now = new Date();
24 | const isPastEndTime = endTime < now;
25 |
26 | console.log(`${index + 1}. ${activity.title}`);
27 | console.log(` 结束时间: ${activity.endTime}`);
28 | console.log(` 当前时间: ${now.toISOString()}`);
29 | console.log(` 时间已过期: ${isPastEndTime}`);
30 | console.log(` isActivityEnded(): ${isEnded}`);
31 | console.log(` canJoinActivity(): ${canJoin}`);
32 | console.log(` 用户已报名: ${activity.isJoined}`);
33 |
34 | // 检查逻辑一致性
35 | if (isPastEndTime && canJoin) {
36 | console.log(` ⚠️ 警告: 已过期活动仍可报名!`);
37 | } else if (isEnded && canJoin) {
38 | console.log(` ⚠️ 警告: 已结束活动仍可报名!`);
39 | } else if (
40 | !isEnded &&
41 | !canJoin &&
42 | !activity.isJoined &&
43 | activity.currentParticipants < activity.maxParticipants
44 | ) {
45 | console.log(` ⚠️ 警告: 进行中活动无法报名!`);
46 | } else {
47 | console.log(` ✅ 状态逻辑正确`);
48 | }
49 |
50 | console.log("");
51 | });
52 |
53 | console.log("=== 测试完成 ===");
54 | };
55 |
56 | // 页面加载时自动执行测试
57 | if (typeof window !== "undefined") {
58 | testActivityStatusConsistency();
59 | }
60 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/components/ConversationListHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | interface ConversationListHeaderProps {
5 | title?: string;
6 | className?: string;
7 | unreadCount?: number;
8 | onMarkAllAsRead?: () => void;
9 | }
10 |
11 | const ConversationListHeader: React.FC = ({
12 | title = "消息",
13 | className,
14 | unreadCount = 0,
15 | onMarkAllAsRead,
16 | }) => {
17 | return (
18 |
24 | {/* 左侧:标题 */}
25 |
26 |
{title}
27 | {unreadCount > 0 && (
28 |
29 | {unreadCount > 99 ? "99+" : unreadCount}
30 |
31 | )}
32 |
33 |
34 | {/* 右侧:一键已读按钮 - 移动端优化 */}
35 | {unreadCount > 0 && onMarkAllAsRead && (
36 |
55 | )}
56 |
57 | );
58 | };
59 |
60 | export default ConversationListHeader;
61 |
--------------------------------------------------------------------------------
/bitbit/src/shared/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface ApiError {
2 | code: string;
3 | message: string;
4 | details?: Record;
5 | }
6 |
7 | export interface PaginationParams {
8 | page: number;
9 | pageSize: number;
10 | sortBy?: string;
11 | sortOrder?: "asc" | "desc";
12 | }
13 |
14 | export interface PaginatedResponse {
15 | data: T[];
16 | total: number;
17 | page: number;
18 | pageSize: number;
19 | totalPages: number;
20 | }
21 |
22 | export interface User {
23 | id: string;
24 | username: string;
25 | email: string;
26 | avatar?: string;
27 | bio?: string;
28 | createdAt: string;
29 | updatedAt: string;
30 | }
31 |
32 | export interface Activity {
33 | id: string;
34 | title: string;
35 | description: string;
36 | category: "music" | "food" | "learning" | "reading";
37 | date: string;
38 | time: string;
39 | location: string;
40 | startTime: string;
41 | endTime: string;
42 | capacity: number;
43 | currentParticipants: number;
44 | maxParticipants: number;
45 | organizer: User;
46 | status: "draft" | "published" | "cancelled" | "completed";
47 | tags: string[];
48 | images?: string[];
49 | coverImage?: string;
50 | price?: number;
51 | isFree?: boolean;
52 | isJoined?: boolean; // 用户是否已报名参加
53 | detailContent?: string;
54 | registrationDeadline?: string;
55 | createdAt: string;
56 | updatedAt: string;
57 | }
58 |
59 | export interface ExchangeItem {
60 | id: string;
61 | title: string;
62 | description: string;
63 | price: number;
64 | category: string;
65 | condition: "new" | "like-new" | "good" | "fair" | "poor";
66 | images: string[];
67 | owner: User;
68 | status: "available" | "reserved" | "sold";
69 | createdAt: string;
70 | updatedAt: string;
71 | }
72 |
73 | export interface Comment {
74 | id: string;
75 | content: string;
76 | author: User;
77 | parentId?: string;
78 | replies?: Comment[];
79 | createdAt: string;
80 | updatedAt: string;
81 | }
82 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Stack/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface StackProps extends React.HTMLAttributes {
5 | direction?: "vertical" | "horizontal";
6 | spacing?: "none" | "xs" | "sm" | "md" | "lg" | "xl";
7 | align?: "start" | "center" | "end" | "stretch";
8 | justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
9 | }
10 |
11 | const Stack: React.FC = ({
12 | className,
13 | direction = "vertical",
14 | spacing = "md",
15 | align = "stretch",
16 | justify = "start",
17 | children,
18 | ...props
19 | }) => {
20 | const directionConfig = {
21 | vertical: "flex-col",
22 | horizontal: "flex-row",
23 | };
24 |
25 | const spacingConfig = {
26 | vertical: {
27 | none: "space-y-0",
28 | xs: "space-y-1",
29 | sm: "space-y-2",
30 | md: "space-y-4",
31 | lg: "space-y-6",
32 | xl: "space-y-8",
33 | },
34 | horizontal: {
35 | none: "space-x-0",
36 | xs: "space-x-1",
37 | sm: "space-x-2",
38 | md: "space-x-4",
39 | lg: "space-x-6",
40 | xl: "space-x-8",
41 | },
42 | };
43 |
44 | const alignConfig = {
45 | start: "items-start",
46 | center: "items-center",
47 | end: "items-end",
48 | stretch: "items-stretch",
49 | };
50 |
51 | const justifyConfig = {
52 | start: "justify-start",
53 | center: "justify-center",
54 | end: "justify-end",
55 | between: "justify-between",
56 | around: "justify-around",
57 | evenly: "justify-evenly",
58 | };
59 |
60 | return (
61 |
72 | {children}
73 |
74 | );
75 | };
76 |
77 | export default Stack;
78 |
--------------------------------------------------------------------------------
/bitbit/src/shared/components/TabFilter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export interface TabOption {
4 | key: string;
5 | label: string;
6 | count?: number;
7 | }
8 |
9 | export interface TabFilterProps {
10 | /** 选项列表 */
11 | options: TabOption[];
12 | /** 当前选中的值 */
13 | value: string;
14 | /** 值变化回调 */
15 | onChange: (value: string) => void;
16 | /** 标签文本 */
17 | label?: string;
18 | /** 是否显示计数 */
19 | showCount?: boolean;
20 | /** 自定义样式类名 */
21 | className?: string;
22 | }
23 |
24 | /**
25 | * Tab风格的筛选组件
26 | * 用于分类筛选,如:全部、活动、帖子、商品等
27 | */
28 | export const TabFilter: React.FC = ({
29 | options,
30 | value,
31 | onChange,
32 | label,
33 | showCount = true,
34 | className = "",
35 | }) => {
36 | return (
37 |
38 |
39 | {label && (
40 |
41 | {label}
42 |
43 | )}
44 | {options.map((option) => (
45 |
62 | ))}
63 |
64 |
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/PageHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | export interface PageHeaderProps {
5 | title: string;
6 | subtitle?: string;
7 | showBackButton?: boolean;
8 | onBack?: () => void;
9 | actions?: React.ReactNode;
10 | className?: string;
11 | }
12 |
13 | export const PageHeader: React.FC = ({
14 | title,
15 | subtitle,
16 | showBackButton = false,
17 | onBack,
18 | actions,
19 | className = "",
20 | }) => {
21 | const navigate = useNavigate();
22 |
23 | const handleGoBack = () => {
24 | if (onBack) {
25 | onBack();
26 | } else {
27 | navigate(-1);
28 | }
29 | };
30 |
31 | return (
32 |
35 |
36 |
37 | {showBackButton && (
38 |
56 | )}
57 |
58 |
{title}
59 | {subtitle &&
{subtitle}
}
60 |
61 |
62 | {actions &&
{actions}
}
63 |
64 |
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/cards/ActivityCard/constants.ts:
--------------------------------------------------------------------------------
1 | // ActivityCard 配置常量
2 | // 将来如果需要重构,可以将这些配置提取出来
3 |
4 | export const CATEGORY_CONFIGS = {
5 | music: {
6 | icon: "🎵",
7 | label: "音乐",
8 | color: "bg-purple-50 text-purple-600",
9 | border: "border-purple-200",
10 | },
11 | food: {
12 | icon: "🍴",
13 | label: "美食",
14 | color: "bg-orange-50 text-orange-600",
15 | border: "border-orange-200",
16 | },
17 | learning: {
18 | icon: "📚",
19 | label: "学习",
20 | color: "bg-blue-50 text-blue-600",
21 | border: "border-blue-200",
22 | },
23 | reading: {
24 | icon: "📖",
25 | label: "阅读",
26 | color: "bg-green-50 text-green-600",
27 | border: "border-green-200",
28 | },
29 | default: {
30 | icon: "📅",
31 | label: "活动",
32 | color: "bg-gray-50 text-gray-600",
33 | border: "border-gray-200",
34 | },
35 | } as const;
36 |
37 | export const STATUS_CONFIGS = {
38 | registered: {
39 | text: "已报名",
40 | bgColor: "bg-blue-50",
41 | textColor: "text-blue-600",
42 | showAsLabel: true,
43 | },
44 | organized: {
45 | text: "已组织",
46 | bgColor: "bg-green-50",
47 | textColor: "text-green-600",
48 | showAsLabel: true,
49 | },
50 | ongoing: {
51 | text: "进行中",
52 | bgColor: "bg-yellow-50",
53 | textColor: "text-yellow-600",
54 | showAsLabel: true,
55 | },
56 | ended: {
57 | text: "已结束",
58 | bgColor: "bg-gray-50",
59 | textColor: "text-gray-500",
60 | showAsLabel: true,
61 | },
62 | completed: {
63 | text: "已结束",
64 | bgColor: "bg-gray-50",
65 | textColor: "text-gray-500",
66 | showAsLabel: true,
67 | },
68 | cancelled: {
69 | text: "已取消",
70 | bgColor: "bg-red-50",
71 | textColor: "text-red-600",
72 | showAsLabel: true,
73 | },
74 | draft: {
75 | text: "草稿",
76 | bgColor: "bg-gray-50",
77 | textColor: "text-gray-500",
78 | showAsLabel: true,
79 | },
80 | default: {
81 | text: "可报名",
82 | bgColor: "bg-green-50",
83 | textColor: "text-green-600",
84 | showAsLabel: false,
85 | },
86 | } as const;
87 |
--------------------------------------------------------------------------------
/bitbit/src/types/chat.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: string;
3 | username: string;
4 | avatar?: string;
5 | displayName: string;
6 | isOnline: boolean;
7 | lastSeen?: Date;
8 | }
9 |
10 | export interface Message {
11 | id: string;
12 | conversationId: string;
13 | senderId: string;
14 | content: string;
15 | type: "text" | "image" | "file" | "system";
16 | timestamp: Date;
17 | isRead: boolean;
18 | replyTo?: string; // 回复的消息ID
19 | editedAt?: Date;
20 | }
21 |
22 | export interface Conversation {
23 | id: string;
24 | type: "private" | "group";
25 | participants: string[]; // 用户ID数组
26 | name?: string; // 群聊名称
27 | avatar?: string; // 群聊头像
28 | lastMessage?: Message;
29 | unreadCount: number;
30 | isPinned: boolean;
31 | isMuted: boolean;
32 | createdAt: Date;
33 | updatedAt: Date;
34 | }
35 |
36 | export interface TypingIndicator {
37 | userId: string;
38 | conversationId: string;
39 | timestamp: Date;
40 | }
41 |
42 | export interface ChatState {
43 | // 当前用户
44 | currentUserId: string | null;
45 |
46 | // 会话列表
47 | conversations: Record;
48 | conversationList: string[]; // 按最新消息排序的会话ID列表
49 |
50 | // 消息数据
51 | messages: Record;
52 | messagesByConversation: Record; // 按会话分组的消息ID列表
53 |
54 | // 当前活跃的会话
55 | activeConversationId: string | null;
56 |
57 | // 用户信息
58 | users: Record;
59 |
60 | // 正在输入的用户
61 | typingUsers: TypingIndicator[];
62 |
63 | // UI 状态
64 | isLoading: boolean;
65 | isConnected: boolean;
66 |
67 | // 搜索和过滤
68 | searchQuery: string;
69 | filteredConversations: string[];
70 |
71 | // 错误状态
72 | error: string | null;
73 | }
74 |
75 | export interface SendMessagePayload {
76 | conversationId: string;
77 | content: string;
78 | type?: Message["type"];
79 | replyTo?: string;
80 | }
81 |
82 | export interface CreateConversationPayload {
83 | type: "private" | "group";
84 | participantIds: string[];
85 | name?: string;
86 | }
87 |
88 | export interface MarkAsReadPayload {
89 | conversationId: string;
90 | messageId?: string; // 如果提供,标记到这条消息为止都已读
91 | }
92 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/ProfileTabs/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { ProfileTab } from "../../types";
3 |
4 | interface ProfileTabsProps {
5 | activeTab: ProfileTab;
6 | onTabChange: (tab: ProfileTab) => void;
7 | visibleTabs?: ProfileTab[]; // 可配置显示的标签
8 | }
9 |
10 | const allTabs = [
11 | { key: "activities" as const, label: "我的活动", publicLabel: "活动" },
12 | { key: "posts" as const, label: "我的帖子", publicLabel: "帖子" },
13 | { key: "trades" as const, label: "我的交易", publicLabel: "商品" },
14 | { key: "favorites" as const, label: "我的收藏", publicLabel: "收藏" },
15 | { key: "drafts" as const, label: "草稿箱", publicLabel: "草稿" },
16 | ];
17 |
18 | export const ProfileTabs: React.FC = ({
19 | activeTab,
20 | onTabChange,
21 | visibleTabs,
22 | }) => {
23 | // 如果指定了visibleTabs,则只显示指定的标签,否则显示所有标签
24 | const tabsToShow = visibleTabs
25 | ? allTabs.filter((tab) => visibleTabs.includes(tab.key))
26 | : allTabs;
27 |
28 | // 判断是否为公开页面(其他用户页面)
29 | const isPublicView = visibleTabs !== undefined;
30 |
31 | return (
32 |
33 |
34 |
35 | {tabsToShow.map((tab) => (
36 |
53 | ))}
54 |
55 |
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/hooks/useProfileNavigation.ts:
--------------------------------------------------------------------------------
1 | import { useNavigate, useParams } from "react-router-dom";
2 | import { useSmartNavigation } from "../../../shared/hooks/useSmartNavigation";
3 |
4 | export const useProfileNavigation = () => {
5 | const navigate = useNavigate();
6 | const params = useParams();
7 | const { navigateWithSource } = useSmartNavigation();
8 |
9 | // 获取当前的标签页
10 | const currentTab = params.tab || "activities";
11 |
12 | const navigateFromProfile = navigateWithSource("profile");
13 |
14 | const navigateToActivityDetail = (activityId: string) => {
15 | navigateFromProfile(`/activities/${activityId}`, {
16 | profileTab: currentTab,
17 | });
18 | };
19 |
20 | const navigateToPostDetail = (postId: string) => {
21 | navigateFromProfile(`/community/${postId}`, { profileTab: currentTab });
22 | };
23 |
24 | const navigateToExchangeDetail = (exchangeId: string) => {
25 | navigateFromProfile(`/exchange/${exchangeId}`, { profileTab: currentTab });
26 | };
27 |
28 | const navigateToOrderDetail = (orderId: string) => {
29 | navigateFromProfile(`/orders/${orderId}`, { profileTab: currentTab });
30 | };
31 |
32 | const navigateToEditActivity = (activityId: string) => {
33 | navigate(`/publish-activity?edit=${activityId}`);
34 | };
35 |
36 | const navigateToEditPost = (postId: string) => {
37 | navigate(`/community/publish?edit=${postId}`);
38 | };
39 |
40 | const navigateToEditExchange = (exchangeId: string) => {
41 | navigate(`/exchange/publish?edit=${exchangeId}`);
42 | };
43 |
44 | const navigateToPublishActivity = () => {
45 | navigate("/publish-activity");
46 | };
47 |
48 | const navigateToPublishPost = () => {
49 | navigate("/community/publish");
50 | };
51 |
52 | const navigateToPublishItem = () => {
53 | navigate("/exchange/publish");
54 | };
55 |
56 | return {
57 | navigateToActivityDetail,
58 | navigateToPostDetail,
59 | navigateToExchangeDetail,
60 | navigateToOrderDetail,
61 | navigateToEditActivity,
62 | navigateToEditPost,
63 | navigateToEditExchange,
64 | navigateToPublishActivity,
65 | navigateToPublishPost,
66 | navigateToPublishItem,
67 | };
68 | };
69 |
--------------------------------------------------------------------------------
/bitbit/src/shared/utils/scrollUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 滚动工具函数
3 | * 提供页面滚动的各种实用功能
4 | */
5 |
6 | /**
7 | * 平滑滚动到页面顶部
8 | */
9 | export const scrollToTop = (behavior: ScrollBehavior = "smooth") => {
10 | window.scrollTo({
11 | top: 0,
12 | behavior,
13 | });
14 | };
15 |
16 | /**
17 | * 平滑滚动到指定元素
18 | * @param selector 元素选择器
19 | * @param offset 偏移量(用于考虑固定头部等)
20 | * @param behavior 滚动行为
21 | */
22 | export const scrollToElement = (
23 | selector: string,
24 | offset: number = 0,
25 | behavior: ScrollBehavior = "smooth"
26 | ) => {
27 | // 延迟执行,确保页面已经渲染
28 | setTimeout(() => {
29 | const element = document.querySelector(selector);
30 | if (element) {
31 | const elementPosition = element.getBoundingClientRect().top;
32 | const offsetPosition = elementPosition + window.pageYOffset - offset;
33 |
34 | window.scrollTo({
35 | top: offsetPosition,
36 | behavior,
37 | });
38 | } else {
39 | // 如果找不到目标元素,回退到滚动到顶部
40 | scrollToTop(behavior);
41 | }
42 | }, 100);
43 | };
44 |
45 | /**
46 | * 滚动到指定的页面部分(通过ID)
47 | * @param sectionId 部分ID
48 | * @param offset 偏移量
49 | * @param behavior 滚动行为
50 | */
51 | export const scrollToSection = (
52 | sectionId: string,
53 | offset: number = 80,
54 | behavior: ScrollBehavior = "smooth"
55 | ) => {
56 | scrollToElement(`#${sectionId}`, offset, behavior);
57 | };
58 |
59 | /**
60 | * 导航后的智能滚动
61 | * 根据不同的页面和路径决定滚动策略
62 | */
63 | export const smartScrollAfterNavigation = (pathname: string) => {
64 | // 延迟执行,确保路由已经完成且页面已渲染
65 | setTimeout(() => {
66 | if (pathname.includes("/profile/trades")) {
67 | // 对于订单页面,滚动到订单区域
68 | scrollToElement('[data-section="trades"]', 100);
69 | } else if (pathname.includes("/profile/following")) {
70 | // 对于关注列表,滚动到列表区域
71 | scrollToElement('[data-section="follow-list"]', 100);
72 | } else if (pathname.includes("/notifications")) {
73 | // 对于通知页面,滚动到通知列表
74 | scrollToElement('[data-section="notifications"]', 100);
75 | } else if (pathname.includes("/profile/settings")) {
76 | // 对于设置页面,滚动到顶部
77 | scrollToTop();
78 | } else {
79 | // 其他页面滚动到顶部
80 | scrollToTop();
81 | }
82 | }, 200);
83 | };
84 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/components/UnreadMessagesBadge.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { Conversation } from "@/features/chat/types";
3 |
4 | interface UnreadMessagesBadgeProps {
5 | activeConversation: Conversation | undefined;
6 | onScrollToBottom: () => void;
7 | isUserAtBottom?: boolean; // 用户是否在聊天底部
8 | hasNewMessages?: boolean; // 是否有新消息到达(不是历史未读消息)
9 | unreadMessagesCount?: number; // 新消息数量
10 | className?: string;
11 | }
12 |
13 | const UnreadMessagesBadge: React.FC = ({
14 | activeConversation,
15 | onScrollToBottom,
16 | isUserAtBottom = true,
17 | hasNewMessages = false,
18 | unreadMessagesCount = 0,
19 | className,
20 | }) => {
21 | console.log("🔔 UnreadMessagesBadge render:", {
22 | activeConversation: activeConversation?.id,
23 | isUserAtBottom,
24 | hasNewMessages,
25 | unreadMessagesCount,
26 | shouldShow:
27 | activeConversation &&
28 | hasNewMessages &&
29 | !isUserAtBottom &&
30 | unreadMessagesCount > 0,
31 | });
32 |
33 | // 只在以下情况显示:
34 | // 1. 有活跃会话
35 | // 2. 有实时新消息到达(不是历史未读消息)
36 | // 3. 用户不在底部
37 | // 4. 有未读消息数量
38 | if (
39 | !activeConversation ||
40 | !hasNewMessages ||
41 | isUserAtBottom ||
42 | unreadMessagesCount <= 0
43 | ) {
44 | return null;
45 | }
46 |
47 | return (
48 |
53 |
72 |
73 | );
74 | };
75 |
76 | export default UnreadMessagesBadge;
77 |
--------------------------------------------------------------------------------
/bitbit/src/shared/hooks/usePublishStatus.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | export interface UsePublishStatusProps {
4 | onSubmit: (data: T) => Promise;
5 | onSuccess?: () => void;
6 | onError?: (error: string) => void;
7 | }
8 |
9 | export interface UsePublishStatusReturn {
10 | publishStatus: "idle" | "loading" | "success" | "error";
11 | publishError: string;
12 | publishedItemId: string | null;
13 | handleSubmit: () => Promise;
14 | handleRetry: () => void;
15 | handleReset: () => void;
16 | setPublishData: (data: T) => void;
17 | }
18 |
19 | export function usePublishStatus({
20 | onSubmit,
21 | onSuccess,
22 | onError,
23 | }: UsePublishStatusProps): UsePublishStatusReturn {
24 | const [publishStatus, setPublishStatus] = useState<
25 | "idle" | "loading" | "success" | "error"
26 | >("idle");
27 | const [publishError, setPublishError] = useState("");
28 | const [publishData, setPublishData] = useState(null);
29 | const [publishedItemId, setPublishedItemId] = useState(null);
30 |
31 | const handleSubmit = async () => {
32 | if (!publishData) {
33 | setPublishStatus("error");
34 | setPublishError("没有要发布的数据");
35 | return;
36 | }
37 |
38 | setPublishStatus("loading");
39 | setPublishError("");
40 |
41 | try {
42 | await onSubmit(publishData);
43 | // 模拟生成ID
44 | const generatedId = `${Date.now()}-${Math.random()
45 | .toString(36)
46 | .substr(2, 9)}`;
47 | setPublishedItemId(generatedId);
48 | setPublishStatus("success");
49 | onSuccess?.();
50 | } catch (error) {
51 | const errorMessage =
52 | error instanceof Error ? error.message : "发布失败,请重试";
53 | setPublishStatus("error");
54 | setPublishError(errorMessage);
55 | onError?.(errorMessage);
56 | }
57 | };
58 |
59 | const handleRetry = () => {
60 | handleSubmit();
61 | };
62 |
63 | const handleReset = () => {
64 | setPublishStatus("idle");
65 | setPublishError("");
66 | setPublishedItemId(null);
67 | };
68 |
69 | return {
70 | publishStatus,
71 | publishError,
72 | publishedItemId,
73 | handleSubmit,
74 | handleRetry,
75 | handleReset,
76 | setPublishData,
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Switch/Switch.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface SwitchProps {
5 | checked: boolean;
6 | disabled?: boolean;
7 | size?: "sm" | "md" | "lg";
8 | onChange: (checked: boolean) => void;
9 | className?: string;
10 | }
11 |
12 | const Switch: React.FC = ({
13 | checked,
14 | disabled = false,
15 | size = "md",
16 | onChange,
17 | className,
18 | }) => {
19 | const sizeClasses = {
20 | sm: {
21 | switch: "h-5 w-9",
22 | thumb: "h-3.5 w-3.5",
23 | translate: checked ? "translate-x-4" : "translate-x-0.5",
24 | padding: "p-0.5",
25 | },
26 | md: {
27 | switch: "h-6 w-11",
28 | thumb: "h-4 w-4",
29 | translate: checked ? "translate-x-5" : "translate-x-1",
30 | padding: "p-1",
31 | },
32 | lg: {
33 | switch: "h-7 w-14",
34 | thumb: "h-5 w-5",
35 | translate: checked ? "translate-x-7" : "translate-x-1",
36 | padding: "p-1",
37 | },
38 | };
39 |
40 | const currentSize = sizeClasses[size];
41 |
42 | return (
43 |
73 | );
74 | };
75 |
76 | export default Switch;
77 |
--------------------------------------------------------------------------------
/bitbit/README.md:
--------------------------------------------------------------------------------
1 | # BitBit 社交活动平台
2 |
3 | 一个现代化的社交活动平台,基于 React + TypeScript + Vite 构建。
4 |
5 | ## 🚀 快速开始
6 |
7 | ### 开发环境
8 |
9 | ```bash
10 | # 安装依赖
11 | npm install
12 |
13 | # 启动开发服务器
14 | npm run dev
15 |
16 | # 构建项目
17 | npm run build
18 | ```
19 |
20 | ### 访问地址
21 |
22 | - 开发环境: http://localhost:5173/
23 |
24 | ## 📚 项目文档
25 |
26 | ### 完整文档
27 |
28 | 查看 [docs/README.md](./docs/README.md) 获取完整的项目文档导航。
29 |
30 | ### 核心文档快速链接
31 |
32 | - [🏗️ 系统架构设计](./docs/architecture/bitbit-架构设计.md)
33 | - [📁 项目目录结构](./docs/architecture/PROJECT_STRUCTURE.md)
34 | - [🎨 设计系统规范](./docs/design/design-system.md)
35 | - [🧩 UI 组件库](./docs/components/ui-components.md)
36 |
37 | ## 🛠️ 技术栈
38 |
39 | - **前端框架**: React 18
40 | - **开发语言**: TypeScript
41 | - **构建工具**: Vite
42 | - **样式方案**: Tailwind CSS
43 | - **状态管理**: Redux Toolkit + React Query
44 | - **路由管理**: React Router v6
45 | - **组件库**: 自研设计系统
46 | - **代码规范**: ESLint + Prettier
47 |
48 | ## 🏗️ 项目结构
49 |
50 | ```
51 | src/
52 | ├── components/ # 通用组件库
53 | │ ├── ui/ # 基础 UI 组件
54 | │ ├── layout/ # 布局组件
55 | │ └── common/ # 通用功能组件
56 | ├── features/ # 功能模块
57 | │ ├── activities/ # 活动模块
58 | │ ├── auth/ # 认证模块
59 | │ ├── community/ # 社区模块
60 | │ └── exchange/ # 交换模块
61 | ├── shared/ # 共享资源
62 | │ ├── config/ # 配置文件
63 | │ ├── utils/ # 工具函数
64 | │ ├── types/ # 类型定义
65 | │ └── services/ # API 服务
66 | ├── pages/ # 页面组件
67 | └── store/ # 全局状态管理
68 | ```
69 |
70 | ## 🎯 功能模块
71 |
72 | ### 核心功能
73 |
74 | - 🎉 **活动管理**: 活动发布、参与、管理
75 | - 🏘️ **社区交流**: 用户社区、帖子分享
76 | - 🔄 **二手交换**: 物品交换、交易
77 | - 👤 **用户系统**: 用户注册、登录、个人资料
78 |
79 | ### 技术特性
80 |
81 | - 📱 **响应式设计**: 适配移动端和桌面端
82 | - 🌓 **主题切换**: 支持明暗主题
83 | - ⚡ **性能优化**: 懒加载、代码分割
84 | - 🛡️ **类型安全**: 完整的 TypeScript 支持
85 |
86 | ## 🤝 贡献指南
87 |
88 | 1. Fork 项目
89 | 2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
90 | 3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
91 | 4. 推送到分支 (`git push origin feature/AmazingFeature`)
92 | 5. 打开 Pull Request
93 |
94 | ## 📄 许可证
95 |
96 | 该项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
97 |
98 | ---
99 |
100 | 💡 **获取帮助**: 如有问题,请查看 [项目文档](./docs/README.md) 或提交 Issue。
101 |
--------------------------------------------------------------------------------
/bitbit/src/shared/services/api.ts:
--------------------------------------------------------------------------------
1 | import { API_BASE_URL } from "../config/constants";
2 |
3 | interface ApiClientConfig {
4 | baseURL: string;
5 | headers?: Record;
6 | }
7 |
8 | class ApiClient {
9 | private baseURL: string;
10 | private headers: Record;
11 |
12 | constructor(config: ApiClientConfig) {
13 | this.baseURL = config.baseURL;
14 | this.headers = {
15 | "Content-Type": "application/json",
16 | ...(config.headers || {}),
17 | };
18 | }
19 |
20 | private async request(
21 | endpoint: string,
22 | options: RequestInit = {}
23 | ): Promise {
24 | const url = `${this.baseURL}${endpoint}`;
25 | const headers = { ...this.headers, ...options.headers };
26 |
27 | const response = await fetch(url, {
28 | ...options,
29 | headers,
30 | });
31 |
32 | if (!response.ok) {
33 | throw new Error(`HTTP error! status: ${response.status}`);
34 | }
35 |
36 | const data = await response.json();
37 | return data as T;
38 | }
39 |
40 | public async get(
41 | endpoint: string,
42 | params?: Record
43 | ): Promise {
44 | const queryString = params
45 | ? `?${new URLSearchParams(params).toString()}`
46 | : "";
47 |
48 | return this.request(`${endpoint}${queryString}`, {
49 | method: "GET",
50 | });
51 | }
52 |
53 | public async post(endpoint: string, data?: unknown): Promise {
54 | return this.request(endpoint, {
55 | method: "POST",
56 | body: data ? JSON.stringify(data) : undefined,
57 | });
58 | }
59 |
60 | public async put(endpoint: string, data?: unknown): Promise {
61 | return this.request(endpoint, {
62 | method: "PUT",
63 | body: data ? JSON.stringify(data) : undefined,
64 | });
65 | }
66 |
67 | public async delete(endpoint: string): Promise {
68 | return this.request(endpoint, {
69 | method: "DELETE",
70 | });
71 | }
72 |
73 | public setToken(token: string) {
74 | this.headers["Authorization"] = `Bearer ${token}`;
75 | }
76 |
77 | public clearToken() {
78 | delete this.headers["Authorization"];
79 | }
80 | }
81 |
82 | export const apiClient = new ApiClient({
83 | baseURL: API_BASE_URL,
84 | });
85 |
86 | export default apiClient;
87 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityDetail/ActivityHeader.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { Icon } from "@/components/ui";
3 | import { ImageCarousel } from "@/components/common";
4 | import type { Activity } from "@/shared/types";
5 |
6 | interface ActivityHeaderProps {
7 | activity: Activity;
8 | onShare: () => void;
9 | onLike: () => void;
10 | isLiked: boolean;
11 | }
12 |
13 | const ActivityHeader: FC = ({
14 | activity,
15 | onShare,
16 | onLike,
17 | isLiked,
18 | }) => {
19 | return (
20 |
21 |
1}
27 | showArrows={activity.images && activity.images.length > 1}
28 | showCounter={activity.images && activity.images.length > 1}
29 | className="absolute inset-0"
30 | rounded={false}
31 | onImageClick={(index, image) => {
32 | // 可以添加图片预览逻辑
33 | console.log("点击图片:", index, image);
34 | }}
35 | />
36 |
37 |
38 | {/* 右上角操作按钮 */}
39 |
40 | {/* 分享按钮 */}
41 |
47 |
48 | {/* 收藏按钮 */}
49 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default ActivityHeader;
65 |
--------------------------------------------------------------------------------
/bitbit/src/shared/components/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export interface SearchBarProps {
4 | /** 搜索框占位符文本 */
5 | placeholder?: string;
6 | /** 当前搜索值 */
7 | value: string;
8 | /** 搜索值变化回调 */
9 | onChange: (value: string) => void;
10 | /** 搜索框最大宽度 */
11 | maxWidth?: string;
12 | /** 是否禁用 */
13 | disabled?: boolean;
14 | /** 自定义样式类名 */
15 | className?: string;
16 | }
17 |
18 | /**
19 | * 通用搜索框组件
20 | * 提供统一的搜索框样式和交互行为
21 | */
22 | export const SearchBar: React.FC = ({
23 | placeholder = "搜索...",
24 | value,
25 | onChange,
26 | maxWidth = "max-w-md",
27 | disabled = false,
28 | className = "",
29 | }) => {
30 | return (
31 |
32 |
33 |
46 |
onChange(e.target.value)}
51 | disabled={disabled}
52 | className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none transition-colors disabled:bg-gray-50 disabled:cursor-not-allowed"
53 | />
54 | {value && !disabled && (
55 |
69 | )}
70 |
71 |
72 | );
73 | };
74 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/hooks/usePagination.ts:
--------------------------------------------------------------------------------
1 | import { useState, useMemo, useEffect, useRef } from "react";
2 |
3 | interface UsePaginationProps {
4 | data: T[];
5 | pageSize?: number;
6 | resetKey?: string; // 添加resetKey参数,当这个key变化时重置分页
7 | }
8 |
9 | interface UsePaginationReturn {
10 | currentPage: number;
11 | totalPages: number;
12 | currentData: T[];
13 | setCurrentPage: (page: number) => void;
14 | nextPage: () => void;
15 | prevPage: () => void;
16 | goToPage: (page: number) => void;
17 | }
18 |
19 | export function usePagination({
20 | data,
21 | pageSize = 10,
22 | resetKey,
23 | }: UsePaginationProps): UsePaginationReturn {
24 | const [currentPage, setCurrentPage] = useState(1);
25 | const prevDataLengthRef = useRef(data.length);
26 | const prevResetKeyRef = useRef(resetKey);
27 |
28 | const totalPages = Math.ceil(data.length / pageSize);
29 |
30 | // 当数据变化时,如果当前页超出了总页数,则重置到第一页
31 | // 或者当数据长度发生变化时(说明筛选条件变了),也重置到第一页
32 | // 或者当resetKey变化时,也重置到第一页
33 | useEffect(() => {
34 | const dataLengthChanged = prevDataLengthRef.current !== data.length;
35 | const currentPageExceedsTotal = totalPages > 0 && currentPage > totalPages;
36 | const resetKeyChanged = prevResetKeyRef.current !== resetKey;
37 |
38 | if (dataLengthChanged || currentPageExceedsTotal || resetKeyChanged) {
39 | setCurrentPage(1);
40 | prevDataLengthRef.current = data.length;
41 | prevResetKeyRef.current = resetKey;
42 | }
43 | }, [data.length, totalPages, currentPage, resetKey]);
44 |
45 | const currentData = useMemo(() => {
46 | const startIndex = (currentPage - 1) * pageSize;
47 | const endIndex = startIndex + pageSize;
48 | return data.slice(startIndex, endIndex);
49 | }, [data, currentPage, pageSize]);
50 |
51 | const goToPage = (page: number) => {
52 | const validPage = Math.max(1, Math.min(page, totalPages));
53 | setCurrentPage(validPage);
54 | };
55 |
56 | const nextPage = () => {
57 | if (currentPage < totalPages) {
58 | setCurrentPage(currentPage + 1);
59 | }
60 | };
61 |
62 | const prevPage = () => {
63 | if (currentPage > 1) {
64 | setCurrentPage(currentPage - 1);
65 | }
66 | };
67 |
68 | return {
69 | currentPage,
70 | totalPages,
71 | currentData,
72 | setCurrentPage: goToPage,
73 | nextPage,
74 | prevPage,
75 | goToPage,
76 | };
77 | }
78 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/LoadingButton/LoadingButton.tsx:
--------------------------------------------------------------------------------
1 | import { type FC, type ButtonHTMLAttributes } from "react";
2 |
3 | interface LoadingButtonProps extends ButtonHTMLAttributes {
4 | loading?: boolean;
5 | variant?: "primary" | "secondary" | "wechat";
6 | size?: "md" | "lg";
7 | children: React.ReactNode;
8 | }
9 |
10 | const LoadingButton: FC = ({
11 | loading = false,
12 | variant = "primary",
13 | size = "lg",
14 | children,
15 | disabled,
16 | className = "",
17 | ...props
18 | }) => {
19 | const getButtonClasses = () => {
20 | const baseClasses =
21 | "w-full rounded-[30px] font-semibold transition-all duration-200 flex items-center justify-center relative overflow-hidden";
22 |
23 | const sizeClasses = {
24 | md: "h-12 text-base",
25 | lg: "h-[60px] text-lg",
26 | };
27 |
28 | const variantClasses = {
29 | primary:
30 | "bg-gradient-to-r from-[#4E6FFF] to-[#7D95FF] text-white hover:from-[#3D5BFF] hover:to-[#6C84FF] focus:ring-4 focus:ring-[#4E6FFF] focus:ring-opacity-25 shadow-[0px_4px_12px_rgba(78,111,255,0.25)]",
31 | secondary:
32 | "bg-white border-[1.5px] border-[#E0E0E6] text-[#222222] hover:bg-gray-50 hover:border-[#CCCCCC]",
33 | wechat:
34 | "bg-white border-[1.5px] border-[#E0E0E6] text-[#222222] hover:bg-gray-50 hover:border-[#CCCCCC]",
35 | };
36 |
37 | return `${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]}`;
38 | };
39 |
40 | const isDisabled = disabled || loading;
41 |
42 | return (
43 |
67 | );
68 | };
69 |
70 | export default LoadingButton;
71 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Toast.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import type { RootState } from "@/store";
4 | import { hideToast } from "@/store/slices/uiSlice";
5 | import { cn } from "@/shared/utils/cn";
6 |
7 | const Toast: React.FC = () => {
8 | const dispatch = useDispatch();
9 | const { show, message, type } = useSelector(
10 | (state: RootState) => state.ui.toast
11 | );
12 |
13 | useEffect(() => {
14 | if (show) {
15 | const timer = setTimeout(() => {
16 | dispatch(hideToast());
17 | }, 3000); // 3秒后自动隐藏
18 |
19 | return () => clearTimeout(timer);
20 | }
21 | }, [show, dispatch]);
22 |
23 | if (!show) return null;
24 |
25 | const getIcon = () => {
26 | switch (type) {
27 | case "success":
28 | return "✅";
29 | case "error":
30 | return "❌";
31 | case "warning":
32 | return "⚠️";
33 | default:
34 | return "ℹ️";
35 | }
36 | };
37 |
38 | const getStyles = () => {
39 | switch (type) {
40 | case "success":
41 | return "bg-green-500 text-white";
42 | case "error":
43 | return "bg-red-500 text-white";
44 | case "warning":
45 | return "bg-yellow-500 text-white";
46 | default:
47 | return "bg-blue-500 text-white";
48 | }
49 | };
50 |
51 | // Toast 居中显示
52 | const toastPosition = {
53 | left: "50%",
54 | top: "50%",
55 | transform: "translate(-50%, -50%)",
56 | };
57 |
58 | return (
59 |
60 |
69 | {getIcon()}
70 | {message}
71 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default Toast;
83 |
--------------------------------------------------------------------------------
/bitbit/src/features/notifications/components/NotificationList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NotificationItem } from "./NotificationItem";
3 | import type { Notification } from "../types";
4 |
5 | interface NotificationListProps {
6 | notifications: Notification[];
7 | loading: boolean;
8 | onMarkAsRead: (id: string) => void;
9 | onDelete?: (id: string) => void;
10 | emptyMessage?: string;
11 | // 多选模式相关属性
12 | isSelectionMode?: boolean;
13 | selectedIds?: string[];
14 | onToggleSelection?: (id: string) => void;
15 | }
16 |
17 | export const NotificationList: React.FC = ({
18 | notifications,
19 | loading,
20 | onMarkAsRead,
21 | onDelete,
22 | emptyMessage = "暂无通知",
23 | isSelectionMode = false,
24 | selectedIds = [],
25 | onToggleSelection,
26 | }) => {
27 | if (loading) {
28 | return (
29 |
37 | );
38 | }
39 |
40 | if (notifications.length === 0) {
41 | return (
42 |
43 |
44 |
🔔
45 |
46 | {emptyMessage}
47 |
48 |
所有通知都会在这里显示
49 |
50 |
51 | );
52 | }
53 |
54 | return (
55 |
56 |
57 | {notifications.map((notification) => (
58 |
67 | ))}
68 |
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/bitbit/src/features/activities/components/ActivityDetail/ActivityContent.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import type { Activity } from "@/shared/types";
3 |
4 | interface ActivityContentProps {
5 | activity: Activity;
6 | }
7 |
8 | const ActivityContent: FC = ({ activity }) => {
9 | return (
10 |
11 |
活动详情
12 |
13 |
{activity.description}
14 |
15 |
16 |
17 |
18 |
行程安排
19 |
20 |
21 | 🕘
22 | 09:00 北门入口集合,签到
23 |
24 |
25 | 🕙
26 | 09:30 开始登山,沿主路线前行
27 |
28 |
29 | 🕐
30 | 12:00 到达山顶休息点,午餐
31 |
32 |
33 | 🕒
34 | 13:30 开始下山,可选择不同难度路线
35 |
36 |
37 | 🕓
38 | 15:00 预计活动结束,自由解散
39 |
40 |
41 |
42 |
43 |
44 |
注意事项
45 |
46 |
47 | 👟
48 | 请穿着舒适的运动鞋和运动服装
49 |
50 |
51 | 🧴
52 | 请自备防晒用品、帽子和足够的水
53 |
54 |
55 | 🍱
56 | 请自备午餐或小零食
57 |
58 |
59 | ☔
60 | 如遇大雨将改期,小雨正常进行
61 |
62 |
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default ActivityContent;
70 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/Tag/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | export interface TagProps extends React.HTMLAttributes {
5 | variant?: "default" | "music" | "food" | "learning" | "reading" | "secondary";
6 | size?: "sm" | "md" | "lg";
7 | removable?: boolean;
8 | onRemove?: () => void;
9 | }
10 |
11 | const Tag = forwardRef(
12 | (
13 | {
14 | className,
15 | variant = "default",
16 | size = "md",
17 | removable = false,
18 | onRemove,
19 | children,
20 | ...props
21 | },
22 | ref
23 | ) => {
24 | const baseStyles = [
25 | "inline-flex",
26 | "items-center",
27 | "gap-1",
28 | "rounded-full",
29 | "font-medium",
30 | "transition-all",
31 | "duration-250",
32 | ];
33 |
34 | const variants = {
35 | default: ["bg-gray-100", "text-text-primary"],
36 | music: ["bg-coral-100", "text-coral-500"],
37 | food: ["bg-mint-100", "text-mint-500"],
38 | learning: ["bg-sunflower-100", "text-sunflower-500"],
39 | reading: ["bg-lavender-100", "text-lavender-500"],
40 | secondary: ["bg-primary-100", "text-primary-500"],
41 | };
42 |
43 | const sizes = {
44 | sm: ["px-2", "py-1", "text-caption"],
45 | md: ["px-3", "py-1.5", "text-body"],
46 | lg: ["px-4", "py-2", "text-body-lg"],
47 | };
48 |
49 | return (
50 |
55 | {children}
56 | {removable && onRemove && (
57 |
78 | )}
79 |
80 | );
81 | }
82 | );
83 |
84 | Tag.displayName = "Tag";
85 |
86 | export default Tag;
87 |
--------------------------------------------------------------------------------
/bitbit/src/pages/Community.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { Container } from "@/components/ui";
4 | import {
5 | CommunityHeader,
6 | CommunitySearch,
7 | CategorySidebar,
8 | CommunitySidebar,
9 | PostList,
10 | } from "@/features/community/components";
11 | import { FloatingBackButton } from "@/components/common";
12 | import { useCommunity } from "@/features/community/hooks";
13 |
14 | const Community: FC = () => {
15 | const navigate = useNavigate();
16 | const {
17 | searchTerm,
18 | selectedCategory,
19 | selectedTag,
20 | sortBy,
21 | loading,
22 | posts,
23 | hotTags,
24 | handleSearchChange,
25 | handleCategoryChange,
26 | handleSortChange,
27 | handleTagChange,
28 | handleClearFilters,
29 | } = useCommunity();
30 |
31 | return (
32 |
33 | {/* 页面头部 */}
34 |
35 |
36 | 社区
37 |
38 |
39 | {/* 左侧边栏 - 分类和排序 */}
40 |
47 |
48 | {/* 主内容区 */}
49 |
50 | {/* 搜索和发帖 */}
51 |
58 |
59 | {/* 帖子列表 */}
60 |
65 |
66 |
67 | {/* 右侧边栏 - 推荐内容 */}
68 |
74 |
75 |
76 | {/* 返回首页浮动按钮 */}
77 | navigate("/")}
80 | variant="elegant"
81 | size="md"
82 | />
83 |
84 | );
85 | };
86 |
87 | export default Community;
88 |
--------------------------------------------------------------------------------
/bitbit/src/pages/NotificationsNew.tsx:
--------------------------------------------------------------------------------
1 | import { type FC, useState } from "react";
2 | import { Container } from "@/components/ui";
3 | import { FloatingBackButton } from "@/components/common";
4 | import { useSmartNavigation } from "@/shared/hooks/useSmartNavigation";
5 | import {
6 | NotificationHeader,
7 | NotificationTabs,
8 | NotificationList,
9 | useNotifications,
10 | useNotificationsByType,
11 | useNotificationActions,
12 | type NotificationType,
13 | } from "@/features/notifications";
14 |
15 | const Notifications: FC = () => {
16 | const { smartGoBack } = useSmartNavigation();
17 | const [activeType, setActiveType] = useState("all");
18 |
19 | // 获取通知数据
20 | const { stats } = useNotifications();
21 | const { notifications, loading } = useNotificationsByType(activeType);
22 | const {
23 | markAsRead,
24 | markAllAsRead,
25 | clearAllNotifications,
26 | deleteNotification,
27 | } = useNotificationActions();
28 |
29 | // 处理清空通知
30 | const handleClearAll = () => {
31 | if (window.confirm("确定要清空所有通知吗?此操作不可恢复。")) {
32 | clearAllNotifications();
33 | }
34 | };
35 |
36 | // 处理标记所有为已读
37 | const handleMarkAllAsRead = () => {
38 | markAllAsRead();
39 | };
40 |
41 | return (
42 | <>
43 |
44 | {/* 头部 */}
45 |
50 |
51 | {/* 筛选标签 */}
52 |
57 |
58 | {/* 通知列表 */}
59 |
76 |
77 |
78 | {/* 悬浮返回按钮 */}
79 |
85 | >
86 | );
87 | };
88 |
89 | export default Notifications;
90 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/CategorySidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface Category {
4 | id: string;
5 | label: string;
6 | value: string;
7 | }
8 |
9 | interface CategorySidebarProps {
10 | selectedCategory: string;
11 | sortBy: string;
12 | onCategoryChange: (category: string) => void;
13 | onSortChange: (sortBy: string) => void;
14 | className?: string;
15 | }
16 |
17 | const categories: Category[] = [
18 | { id: "all", label: "全部话题", value: "" },
19 | { id: "learning", label: "学习分享", value: "learning" },
20 | { id: "food", label: "美食交流", value: "food" },
21 | { id: "music", label: "音乐讨论", value: "music" },
22 | { id: "reading", label: "读书心得", value: "reading" },
23 | ];
24 |
25 | const sortOptions = [
26 | { value: "latest", label: "最新发布" },
27 | { value: "popular", label: "最多点赞" },
28 | { value: "comments", label: "最多评论" },
29 | ];
30 |
31 | const CategorySidebar: React.FC = ({
32 | selectedCategory,
33 | sortBy,
34 | onCategoryChange,
35 | onSortChange,
36 | className,
37 | }) => {
38 | return (
39 |
40 | {/* 分类导航 */}
41 |
42 |
社区分类
43 |
44 | {categories.map((category) => (
45 | - onCategoryChange(category.value)}
53 | >
54 | {category.label}
55 |
56 | ))}
57 |
58 |
59 |
60 | {/* 排序方式 */}
61 |
62 |
排序方式
63 |
74 |
75 |
76 | );
77 | };
78 |
79 | export default CategorySidebar;
80 |
--------------------------------------------------------------------------------
/bitbit/src/features/exchange/components/OrderDetail/OrderActions.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@/components/ui";
3 | import { cn } from "@/shared/utils/cn";
4 | import type { OrderDetail, OrderAction } from "../../types";
5 |
6 | interface OrderActionsProps {
7 | order: OrderDetail;
8 | onAction?: (action: string) => void;
9 | className?: string;
10 | }
11 |
12 | const OrderActions: React.FC = ({
13 | order,
14 | onAction,
15 | className,
16 | }) => {
17 | const handleAction = (action: string) => {
18 | onAction?.(action);
19 | };
20 |
21 | const getButtonVariant = (type: OrderAction["type"]) => {
22 | switch (type) {
23 | case "primary":
24 | return "primary" as const;
25 | case "secondary":
26 | return "secondary" as const;
27 | case "danger":
28 | return "danger" as const;
29 | default:
30 | return "outline" as const;
31 | }
32 | };
33 |
34 | if (!order.availableActions || order.availableActions.length === 0) {
35 | return null;
36 | }
37 |
38 | // 将主要操作和次要操作分开
39 | const primaryActions = order.availableActions.filter(
40 | (action) => action.type === "primary"
41 | );
42 | const secondaryActions = order.availableActions.filter(
43 | (action) => action.type !== "primary"
44 | );
45 |
46 | return (
47 |
48 |
49 | {/* 主要操作按钮 */}
50 | {primaryActions.map((action) => (
51 |
61 | ))}
62 |
63 | {/* 次要操作按钮 */}
64 | {secondaryActions.length > 0 && (
65 |
66 | {secondaryActions.map((action) => (
67 |
76 | ))}
77 |
78 | )}
79 |
80 |
81 | );
82 | };
83 |
84 | export default OrderActions;
85 |
--------------------------------------------------------------------------------
/bitbit/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
3 | theme: {
4 | extend: {
5 | // 自定义颜色系统
6 | colors: {
7 | // 主品牌色 - 活力蓝
8 | primary: {
9 | 50: "#F0F4FF",
10 | 100: "#CCE1FF",
11 | 200: "#7D95FF",
12 | 500: "#4E6FFF",
13 | 600: "#3050E0",
14 | },
15 | // 辅助色
16 | coral: {
17 | 100: "#FFCCD6",
18 | 500: "#FF6B8B",
19 | },
20 | mint: {
21 | 100: "#CCF5E8",
22 | 500: "#65D1AA",
23 | },
24 | sunflower: {
25 | 100: "#FFECCB",
26 | 500: "#FFB951",
27 | },
28 | lavender: {
29 | 100: "#E6DDFF",
30 | 500: "#8F7CFF",
31 | },
32 | // 中性色
33 | text: {
34 | primary: "#222222",
35 | secondary: "#666666",
36 | tertiary: "#999999",
37 | },
38 | // 状态色
39 | error: "#FF5252",
40 | success: "#12B76A",
41 | warning: "#FFC107",
42 | info: "#0096FF",
43 | },
44 | // 字体大小系统
45 | fontSize: {
46 | "title-1": ["32px", { lineHeight: "42px", fontWeight: "700" }],
47 | "title-2": ["28px", { lineHeight: "36px", fontWeight: "700" }],
48 | "title-3": ["24px", { lineHeight: "31px", fontWeight: "600" }],
49 | "title-4": ["20px", { lineHeight: "26px", fontWeight: "600" }],
50 | subtitle: ["18px", { lineHeight: "24px", fontWeight: "500" }],
51 | "body-lg": ["16px", { lineHeight: "24px", fontWeight: "400" }],
52 | body: ["14px", { lineHeight: "21px", fontWeight: "400" }],
53 | caption: ["12px", { lineHeight: "18px", fontWeight: "400" }],
54 | },
55 | // 圆角系统
56 | borderRadius: {
57 | xs: "4px",
58 | sm: "8px",
59 | lg: "16px",
60 | },
61 | // 阴影系统
62 | boxShadow: {
63 | light: "0px 1px 2px rgba(0,0,0,0.05)",
64 | card: "0px 4px 8px rgba(0,0,0,0.08)",
65 | modal: "0px 8px 24px rgba(0,0,0,0.12)",
66 | focus: "0px 12px 32px rgba(0,0,0,0.16)",
67 | },
68 | // 动画时长
69 | transitionDuration: {
70 | 250: "250ms",
71 | },
72 | // 字体族
73 | fontFamily: {
74 | sans: [
75 | "Inter",
76 | "PingFang SC",
77 | "Microsoft YaHei",
78 | "SF Pro",
79 | "Helvetica Neue",
80 | "Arial",
81 | "sans-serif",
82 | ],
83 | },
84 | },
85 | },
86 | plugins: [],
87 | };
88 |
--------------------------------------------------------------------------------
/bitbit/src/features/profile/components/Settings/SettingsNavigation.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 | import type { SettingsModule } from "../../types";
4 | import type { SettingsNavigationItem } from "../../hooks/useSettingsNavigation";
5 |
6 | interface SettingsNavigationProps {
7 | items: SettingsNavigationItem[];
8 | activeModule: SettingsModule;
9 | onModuleChange: (module: SettingsModule) => void;
10 | }
11 |
12 | export const SettingsNavigation: React.FC = ({
13 | items,
14 | activeModule,
15 | onModuleChange,
16 | }) => {
17 | return (
18 |
19 | {items.map((item) => (
20 |
68 | ))}
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/bitbit/src/shared/components/SimpleSearchFilter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SearchBar } from "../SearchBar";
3 | import { SortSelector, type SortSelectorProps } from "../SortSelector";
4 |
5 | export interface SimpleSearchFilterProps {
6 | /** 搜索框配置 */
7 | search: {
8 | placeholder?: string;
9 | value: string;
10 | onChange: (value: string) => void;
11 | maxWidth?: string;
12 | };
13 | /** 排序选择器配置 */
14 | sort?: {
15 | options: SortSelectorProps["options"];
16 | value: string;
17 | onChange: (value: string) => void;
18 | label?: string;
19 | };
20 | /** 结果统计信息 */
21 | resultInfo?: {
22 | total: number;
23 | filtered?: number;
24 | showResultCount?: boolean;
25 | };
26 | /** 容器样式类名 */
27 | className?: string;
28 | /** 是否显示在卡片容器中 */
29 | showInCard?: boolean;
30 | }
31 |
32 | /**
33 | * 简单搜索和筛选组件
34 | * 用于不需要复杂筛选功能的页面,只包含搜索和排序
35 | * 适用于关注/粉丝列表、简单的数据列表等场景
36 | */
37 | export const SimpleSearchFilter: React.FC = ({
38 | search,
39 | sort,
40 | resultInfo,
41 | className = "",
42 | showInCard = true,
43 | }) => {
44 | const content = (
45 |
46 | {/* 搜索和排序控制栏 */}
47 |
48 |
54 |
55 | {sort && (
56 |
62 | )}
63 |
64 |
65 | {/* 搜索结果提示 */}
66 | {resultInfo && search.value && (
67 |
68 | {resultInfo.showResultCount !== false && (
69 | <>
70 | 找到 {resultInfo.filtered ?? resultInfo.total} 个结果
71 | {(resultInfo.filtered ?? resultInfo.total) === 0 && (
72 | 试试其他关键词
73 | )}
74 | >
75 | )}
76 |
77 | )}
78 |
79 | );
80 |
81 | if (showInCard) {
82 | return (
83 |
86 | {content}
87 |
88 | );
89 | }
90 |
91 | return {content}
;
92 | };
93 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/FormInput/FormInput.tsx:
--------------------------------------------------------------------------------
1 | import { type InputHTMLAttributes, forwardRef } from "react";
2 |
3 | interface FormInputProps
4 | extends Omit, "className"> {
5 | label?: string;
6 | error?: string;
7 | required?: boolean;
8 | variant?: "default" | "gray" | "white";
9 | showEyeIcon?: boolean;
10 | onEyeClick?: () => void;
11 | }
12 |
13 | const FormInput = forwardRef(
14 | (
15 | {
16 | label,
17 | error,
18 | required = false,
19 | variant = "default",
20 | showEyeIcon = false,
21 | onEyeClick,
22 | ...props
23 | },
24 | ref
25 | ) => {
26 | const getInputClasses = () => {
27 | const baseClasses =
28 | "w-full h-[60px] px-6 rounded-xl text-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#4E6FFF] focus:ring-opacity-50";
29 |
30 | switch (variant) {
31 | case "gray":
32 | return `${baseClasses} bg-[#F9F9FB] border-[1.5px] border-[#E0E0E6] text-[#222222] placeholder-[#999999]`;
33 | case "white":
34 | return `${baseClasses} bg-white border-[1.5px] border-[#E0E0E6] text-[#222222] placeholder-[#999999]`;
35 | default:
36 | return `${baseClasses} bg-white border-[1.5px] border-[#E0E0E6] text-[#222222] placeholder-[#999999]`;
37 | }
38 | };
39 |
40 | return (
41 |
42 | {label && (
43 |
47 | )}
48 |
49 |
50 |
57 |
58 | {showEyeIcon && (
59 |
66 | )}
67 |
68 |
69 | {error &&
{error}
}
70 |
71 | );
72 | }
73 | );
74 |
75 | FormInput.displayName = "FormInput";
76 |
77 | export default FormInput;
78 |
--------------------------------------------------------------------------------
/bitbit/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | darkMode: "class", // 启用暗色模式支持
5 | theme: {
6 | extend: {
7 | // 自定义颜色系统
8 | colors: {
9 | // 主品牌色 - 活力蓝
10 | primary: {
11 | 50: "#F0F4FF",
12 | 100: "#CCE1FF",
13 | 200: "#7D95FF",
14 | 500: "#4E6FFF",
15 | 600: "#3050E0",
16 | },
17 | // 辅助色
18 | coral: {
19 | 100: "#FFCCD6",
20 | 500: "#FF6B8B",
21 | },
22 | mint: {
23 | 100: "#CCF5E8",
24 | 500: "#65D1AA",
25 | },
26 | sunflower: {
27 | 100: "#FFECCB",
28 | 500: "#FFB951",
29 | },
30 | lavender: {
31 | 100: "#E6DDFF",
32 | 500: "#8F7CFF",
33 | },
34 | // 中性色
35 | text: {
36 | primary: "#222222",
37 | secondary: "#666666",
38 | tertiary: "#999999",
39 | },
40 | // 状态色
41 | error: "#FF5252",
42 | success: "#12B76A",
43 | warning: "#FFC107",
44 | info: "#0096FF",
45 | },
46 | // 字体大小系统
47 | fontSize: {
48 | "title-1": ["32px", { lineHeight: "42px", fontWeight: "700" }],
49 | "title-2": ["28px", { lineHeight: "36px", fontWeight: "700" }],
50 | "title-3": ["24px", { lineHeight: "31px", fontWeight: "600" }],
51 | "title-4": ["20px", { lineHeight: "26px", fontWeight: "600" }],
52 | subtitle: ["18px", { lineHeight: "24px", fontWeight: "500" }],
53 | "body-lg": ["16px", { lineHeight: "24px", fontWeight: "400" }],
54 | body: ["14px", { lineHeight: "21px", fontWeight: "400" }],
55 | caption: ["12px", { lineHeight: "18px", fontWeight: "400" }],
56 | },
57 | // 圆角系统
58 | borderRadius: {
59 | xs: "4px",
60 | sm: "8px",
61 | lg: "16px",
62 | },
63 | // 阴影系统
64 | boxShadow: {
65 | light: "0px 1px 2px rgba(0,0,0,0.05)",
66 | card: "0px 4px 8px rgba(0,0,0,0.08)",
67 | modal: "0px 8px 24px rgba(0,0,0,0.12)",
68 | focus: "0px 12px 32px rgba(0,0,0,0.16)",
69 | },
70 | // 动画时长
71 | transitionDuration: {
72 | 250: "250ms",
73 | },
74 | // 字体族
75 | fontFamily: {
76 | sans: [
77 | "Inter",
78 | "PingFang SC",
79 | "Microsoft YaHei",
80 | "SF Pro",
81 | "Helvetica Neue",
82 | "Arial",
83 | "sans-serif",
84 | ],
85 | },
86 | },
87 | },
88 | plugins: [],
89 | };
90 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/ContentFilter/types.ts:
--------------------------------------------------------------------------------
1 | export interface FilterOption {
2 | key: string;
3 | label: string;
4 | count?: number; // 可选的计数显示
5 | }
6 |
7 | export interface SortOption {
8 | key: string;
9 | label: string;
10 | direction?: "asc" | "desc";
11 | }
12 |
13 | export interface FilterConfig {
14 | type: "tabs" | "dropdown" | "chips"; // 不同的UI表现形式
15 | key?: string; // 筛选器的key,用于标识筛选条件
16 | title?: string;
17 | options: FilterOption[];
18 | allowMultiple?: boolean; // 是否允许多选
19 | showCount?: boolean; // 是否显示计数
20 | }
21 |
22 | export interface SortConfig {
23 | title?: string;
24 | options: SortOption[];
25 | defaultSort?: string;
26 | }
27 |
28 | export interface SearchConfig {
29 | placeholder?: string;
30 | searchFields?: string[]; // 指定搜索的字段
31 | }
32 |
33 | export interface ContentFilterProps> {
34 | // 数据 - 如果不提供,只显示筛选UI
35 | data?: T[];
36 |
37 | // 配置
38 | filterConfigs?: FilterConfig[];
39 | sortConfig?: SortConfig;
40 | searchConfig?: SearchConfig;
41 |
42 | // 当前状态
43 | activeFilters?: Record;
44 | activeSort?: string;
45 | searchQuery?: string;
46 |
47 | // 事件回调
48 | onFilterChange?: (filterKey: string, value: string | string[]) => void;
49 | onSortChange?: (sortKey: string) => void;
50 | onSearchChange?: (query: string) => void;
51 | onDataChange?: (filteredData: T[]) => void;
52 |
53 | // UI配置
54 | layout?: "horizontal" | "vertical";
55 | showFilterCount?: boolean;
56 | showClearButton?: boolean;
57 |
58 | // 自定义渲染
59 | renderCustomFilter?: (
60 | config: FilterConfig,
61 | activeValue: string | string[]
62 | ) => React.ReactNode;
63 |
64 | // 自定义过滤和排序逻辑
65 | customFilterFn?: (
66 | item: unknown,
67 | filters: Record
68 | ) => boolean;
69 | customSortFn?: (items: unknown[], sortKey: string) => unknown[];
70 | }
71 |
72 | // 预定义的配置类型,用于不同的页面场景
73 | export interface ProfileFilterConfigs {
74 | favorites: {
75 | filters: FilterConfig[];
76 | sort: SortConfig;
77 | search: SearchConfig;
78 | };
79 | posts: {
80 | filters: FilterConfig[];
81 | sort: SortConfig;
82 | search: SearchConfig;
83 | };
84 | trades: {
85 | filters: FilterConfig[];
86 | sort: SortConfig;
87 | search: SearchConfig;
88 | };
89 | activities: {
90 | filters: FilterConfig[];
91 | sort: SortConfig;
92 | search: SearchConfig;
93 | };
94 | drafts: {
95 | filters: FilterConfig[];
96 | sort: SortConfig;
97 | search: SearchConfig;
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/InterestTags/InterestTags.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import {
3 | INTEREST_OPTIONS,
4 | type InterestOption,
5 | } from "@/features/auth/types/auth.types";
6 |
7 | interface InterestTagsProps {
8 | selectedInterests: string[];
9 | onChange: (interests: string[]) => void;
10 | className?: string;
11 | }
12 |
13 | const InterestTags: FC = ({
14 | selectedInterests,
15 | onChange,
16 | className = "",
17 | }) => {
18 | const handleTagClick = (interestId: string) => {
19 | const isSelected = selectedInterests.includes(interestId);
20 |
21 | if (isSelected) {
22 | // 取消选择
23 | onChange(selectedInterests.filter((id) => id !== interestId));
24 | } else {
25 | // 添加选择
26 | onChange([...selectedInterests, interestId]);
27 | }
28 | };
29 |
30 | const getTagClasses = (option: InterestOption, isSelected: boolean) => {
31 | const baseClasses =
32 | "inline-flex items-center justify-center px-4 h-9 rounded-[18px] text-sm font-medium cursor-pointer transition-all duration-200 border-[1.5px]";
33 |
34 | if (isSelected) {
35 | if (option.id === "sports") {
36 | return `${baseClasses} bg-[#4E6FFF] text-white border-[#4E6FFF]`;
37 | } else {
38 | return `${baseClasses} border-current`;
39 | }
40 | } else {
41 | return `${baseClasses} bg-white border-[#E0E0E6] text-[#666666] hover:border-[#CCCCCC]`;
42 | }
43 | };
44 |
45 | const getTagStyle = (option: InterestOption, isSelected: boolean) => {
46 | if (isSelected && option.id !== "sports") {
47 | return {
48 | backgroundColor: option.bgColor,
49 | color: option.color,
50 | borderColor: option.color,
51 | };
52 | }
53 | return {};
54 | };
55 |
56 | return (
57 |
58 |
59 | 感兴趣的活动类型{" "}
60 | (可选)
61 |
62 |
63 |
64 | {INTEREST_OPTIONS.map((option) => {
65 | const isSelected = selectedInterests.includes(option.id);
66 |
67 | return (
68 |
77 | );
78 | })}
79 |
80 |
81 | );
82 | };
83 |
84 | export default InterestTags;
85 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/VerificationCodeInput/VerificationCodeInput.tsx:
--------------------------------------------------------------------------------
1 | import { type FC, useState } from "react";
2 | import FormInput from "../FormInput";
3 | import { useVerificationCode } from "@/features/auth/hooks";
4 |
5 | interface VerificationCodeInputProps {
6 | phone: string;
7 | value: string;
8 | onChange: (value: string) => void;
9 | onBlur?: () => void;
10 | error?: string;
11 | disabled?: boolean;
12 | }
13 |
14 | const VerificationCodeInput: FC = ({
15 | phone,
16 | value,
17 | onChange,
18 | onBlur,
19 | error,
20 | disabled = false,
21 | }) => {
22 | const { countdown, isCountingDown, loading, sendCode } =
23 | useVerificationCode();
24 | const [sendError, setSendError] = useState("");
25 |
26 | const handleSendCode = async () => {
27 | if (!phone || phone.length !== 11) {
28 | setSendError("请先输入正确的手机号");
29 | return;
30 | }
31 |
32 | try {
33 | setSendError("");
34 | await sendCode(phone);
35 | } catch {
36 | // Error is already handled by the hook
37 | }
38 | };
39 |
40 | const getButtonText = () => {
41 | if (loading) return "发送中...";
42 | if (isCountingDown) return `${countdown}s`;
43 | return "获取验证码";
44 | };
45 |
46 | const isButtonDisabled = loading || isCountingDown || disabled || !phone;
47 |
48 | return (
49 |
50 |
53 |
54 |
55 |
56 | onChange(e.target.value)}
61 | onBlur={onBlur}
62 | error={error}
63 | disabled={disabled}
64 | maxLength={6}
65 | />
66 |
67 |
68 |
83 |
84 |
85 | {sendError &&
{sendError}
}
86 |
87 | );
88 | };
89 |
90 | export default VerificationCodeInput;
91 |
--------------------------------------------------------------------------------
/bitbit/src/features/community/components/PostDetail/PostDetailHeader.tsx:
--------------------------------------------------------------------------------
1 | import { type FC } from "react";
2 | import { Icon } from "@/components/ui";
3 |
4 | interface PostDetailHeaderProps {
5 | title: string;
6 | onBack: () => void;
7 | onMore: () => void;
8 | }
9 |
10 | const PostDetailHeader: FC = ({
11 | title,
12 | onBack,
13 | onMore,
14 | }) => {
15 | return (
16 |
17 |
18 | {/* 返回按钮 - 参考FloatingBackButton的样式 */}
19 |
41 |
42 | {/* 标题 */}
43 |
{title}
44 |
45 | {/* 更多操作 */}
46 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default PostDetailHeader;
62 |
--------------------------------------------------------------------------------
/bitbit/src/features/chat/components/GroupDismissedNotice.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/shared/utils/cn";
3 |
4 | interface GroupDismissedNoticeProps {
5 | dismissedAt?: Date;
6 | dismissedBy?: string;
7 | dismissedByName?: string;
8 | className?: string;
9 | }
10 |
11 | const GroupDismissedNotice: React.FC = ({
12 | dismissedAt,
13 | dismissedByName,
14 | className,
15 | }) => {
16 | const formatDismissedTime = () => {
17 | if (!dismissedAt) return "";
18 |
19 | const now = new Date();
20 | const diffMs = now.getTime() - dismissedAt.getTime();
21 | const diffMinutes = Math.floor(diffMs / (1000 * 60));
22 | const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
23 | const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
24 |
25 | if (diffMinutes < 1) {
26 | return "刚刚";
27 | } else if (diffMinutes < 60) {
28 | return `${diffMinutes}分钟前`;
29 | } else if (diffHours < 24) {
30 | return `${diffHours}小时前`;
31 | } else if (diffDays < 7) {
32 | return `${diffDays}天前`;
33 | } else {
34 | return dismissedAt.toLocaleDateString("zh-CN", {
35 | year: "numeric",
36 | month: "short",
37 | day: "numeric",
38 | hour: "2-digit",
39 | minute: "2-digit",
40 | });
41 | }
42 | };
43 |
44 | return (
45 |
48 |
49 |
50 | {/* 解散图标 */}
51 |
66 |
67 |
68 |
群聊已解散
69 |
70 | {dismissedByName && 由 {dismissedByName} }
71 | {dismissedAt && 于 {formatDismissedTime()} }
72 | 解散,消息仅供查看
73 |
74 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default GroupDismissedNotice;
82 |
--------------------------------------------------------------------------------
/bitbit/src/components/ui/PublishStatusModal/PublishStatusModal.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "@/components/ui";
3 |
4 | interface PublishStatusModalProps {
5 | status: "loading" | "success" | "error";
6 | error?: string;
7 | onRetry?: () => void;
8 | onSuccess?: () => void;
9 | onContinue?: () => void;
10 | onViewList?: () => void;
11 | }
12 |
13 | const PublishStatusModal: React.FC = ({
14 | status,
15 | error,
16 | onRetry,
17 | onSuccess,
18 | onContinue,
19 | onViewList,
20 | }) => {
21 | if (status === "loading") {
22 | return (
23 |
24 |
27 |
28 | 正在发布商品...
29 |
30 |
请稍候,我们正在处理您的商品信息
31 |
32 | );
33 | }
34 |
35 | if (status === "success") {
36 | return (
37 |
38 |
41 |
42 | 发布成功!
43 |
44 |
您的商品已成功发布到二手市场
45 |
46 |
49 |
52 |
53 |
54 | );
55 | }
56 |
57 | if (status === "error") {
58 | return (
59 |
60 |
63 |
64 | 发布失败
65 |
66 |
67 | {error || "发布过程中出现了问题,请稍后重试"}
68 |
69 |
70 |
73 |
76 |
77 |
78 | );
79 | }
80 |
81 | return null;
82 | };
83 |
84 | export default PublishStatusModal;
85 |
--------------------------------------------------------------------------------