- {/* 聊天头部 */}
-
-
- 应用对话
- {codeGenTypeLabel || '未知类型'}
- {appInfo?.user && (
-
- {appInfo.user.userName?.[0]}
- {appInfo.user.userName}
-
- )}
- {/* WebSocket连接状态 */}
- : }
- >
- {websocket && websocket.isActive() ? "已连接" : "未连接"}
-
- {/* 在线用户列表 */}
-
- }
- onClick={() => setOnlineUsersVisible(true)}
- style={{cursor: 'pointer'}}
- >
- 在线: {onlineUsers.length}
-
-
- {onlineUsers.map((user: User) => (
- {user.userName?.[0]}
- ))}
-
-
-
-
-
-
- <>
- {websocket ? (
-
- ) : (
-
- )}
- >
-
-
-
-
- {/* 主要内容区域 */}
-
- {/* 聊天区域 */}
-
-
- {/* 加载更多消息按钮 */}
- {hasMore && (
-
-
-
- )}
-
- {/* 其他用户正在生成提示 */}
- {isOtherUserGenerating && otherGeneratingUser && (
-
-
- {otherGeneratingUser.userName?.[0]}
-
-
-
-
-
- {otherGeneratingUser.userName} 正在生成代码...
-
-
-
-
- )}
-
- {/* 消息列表 */}
- {infiniteMessages.map(msg => {
- const isCurrentUserMessage = msg.messageType === 'user' && (!msg.user || msg.user.id === loginUser?.id);
- const userAvatar = msg.user?.userAvatar || loginUser?.userAvatar;
- const userName = msg.user?.userName || loginUser?.userName;
-
- return (
-
-
- {/* 用户头像或Logo */}
- {msg.messageType === 'user' ? (
-
{userName?.[0] || 'U'}
- ) : (
-
-
-
- )}
- {/* 消息卡片 */}
-
- {msg.user && msg.user.id !== loginUser?.id && (
-
- {msg.user.userName}
-
- )}
-
-
- {msg.messageContent}
-
-
-
- {msg.createTime ? new Date(msg.createTime).toLocaleTimeString() : ''}
-
-
-
-
- );
- })}
-
- {/* 加载中状态 */}
- {loading && (
-
-
-
-
-
-
-
- AI正在思考中...
-
-
-
- )}
-
-
-
- {/* 聊天输入区域 */}
-
- {/* 可视化编辑器面板 */}
-
- {/* 输入框和发送按钮 */}
-
-
-
提示:Ctrl+Enter 发送
-
- 预估消耗:{estimatedCost} 积分
- {estimateDetail?.modelName && (
-
- ({estimateDetail.modelName}
- {estimateDetail.qualityScoreDescription && ` · ${estimateDetail.qualityScoreDescription}`})
-
- )}
-
-
-
-
- {/* 预览区域 */}
-
-
-
- 应用预览
-
-
-
- {showPreview && appInfo?.codeGenType && appInfo?.id ? (
-
- {/* 预览加载中 */}
- {previewLoading && (
-
-
- 预览加载中...
-
- )}
- {/* 应用预览iframe */}
-
- ) : (
-
-
- {previewLoading ? '预览加载中...' : '请先发送消息,AI将自动生成并部署应用'}
-
-
- )}
-
-
-
-
- {/* 评论区域 */}
- {appInfo?.id && (
-
-
-
- )}
-
- {/* 在线用户列表弹窗 */}
-
setOnlineUsersVisible(false)}
- footer={null}
- width={400}
- >
-
- {onlineUsers.length === 0 ? (
-
- 暂无在线用户
-
- ) : (
-
- {onlineUsers.map((user: User) => (
-
-
{user.userName?.[0]}
-
-
{user.userName}
-
- {currentEditingUser?.id === user.id ? '正在编辑' : '在线'}
-
-
- {currentEditingUser?.id === user.id && (
-
编辑中
- )}
-
- ))}
-
- )}
-
-
-
- >
- );
-};
-
-export default ChatPage;
diff --git a/ai-code-frontend/src/pages/Code/Home/components/AppCard.module.less b/ai-code-frontend/src/pages/Code/Home/components/AppCard.module.less
deleted file mode 100644
index 88d6be6..0000000
--- a/ai-code-frontend/src/pages/Code/Home/components/AppCard.module.less
+++ /dev/null
@@ -1,195 +0,0 @@
-.appCardWrapper {
- position: relative;
- width: 100%;
- min-width: 280px;
- max-width: 400px;
-}
-
-.appCard {
- width: 100%;
- min-width: 280px;
- max-width: 400px;
- text-align: center;
- border-radius: 12px;
- overflow: hidden;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- transition: all 0.3s ease;
-
- &:hover {
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
- transform: translateY(-4px);
- }
-}
-
-.skeletonCard {
- min-width: 200px;
- text-align: center;
- max-width: 282px;
- border-radius: 12px;
-}
-
-.skeletonImage {
- width: 100%;
- height: 200px;
- border-radius: 12px 12px 0 0;
-}
-
-.cardCover {
- height: 200px;
- overflow: hidden;
-
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- transition: transform 0.3s ease;
- }
-
- &:hover img {
- transform: scale(1.05);
- }
-}
-
-.cardEmpty {
- height: 200px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #fafafa;
- position: relative;
- overflow: hidden;
-}
-
-.skeletonCover {
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- position: relative;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 12px;
-
- &::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%);
- animation: shimmer 1.5s infinite;
- }
-}
-
-.skeletonIcon {
- width: 48px;
- height: 48px;
- background: rgba(0, 0, 0, 0.06);
- border-radius: 12px;
- position: relative;
- z-index: 1;
-}
-
-.skeletonTitle {
- width: 120px;
- height: 16px;
- background: rgba(0, 0, 0, 0.06);
- border-radius: 4px;
- position: relative;
- z-index: 1;
-}
-
-.skeletonSubtitle {
- width: 80px;
- height: 12px;
- background: rgba(0, 0, 0, 0.04);
- border-radius: 4px;
- position: relative;
- z-index: 1;
-}
-
-.skeletonShimmer {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.6) 50%, transparent 100%);
- animation: shimmer 1.5s infinite;
-}
-
-@keyframes shimmer {
- 0% {
- transform: translateX(-100%);
- }
- 100% {
- transform: translateX(100%);
- }
-}
-
-.cardTags {
- margin-bottom: 10px;
- margin-left: 50px;
-}
-
-.cardMeta {
- .ant-card-meta-title {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 8px;
- }
-
- .ant-card-meta-description {
- font-size: 13px;
- color: #666;
- }
-}
-
-.cardOverlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: linear-gradient(135deg, rgba(24, 144, 255, 0.9) 0%, rgba(82, 196, 26, 0.85) 100%);
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- gap: 12px;
- opacity: 0;
- transition: all 0.3s ease;
- border-radius: 12px;
- backdrop-filter: blur(4px);
-
- &:hover {
- opacity: 1;
- }
-}
-
-.overlayButton {
- padding: 8px 20px;
- cursor: pointer;
- border-radius: 20px;
- font-weight: 500;
- transition: all 0.3s ease;
- border: 2px solid rgba(255, 255, 255, 0.8);
- background: rgba(255, 255, 255, 0.95);
- color: #1890ff;
-
- &:hover {
- background: #fff;
- transform: scale(1.05);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- }
-}
-
-.pageViews {
- margin-top: 4px;
- color: #666;
- font-size: 12px;
-}
diff --git a/ai-code-frontend/src/pages/Code/Home/components/AppCard.tsx b/ai-code-frontend/src/pages/Code/Home/components/AppCard.tsx
deleted file mode 100644
index 5320d54..0000000
--- a/ai-code-frontend/src/pages/Code/Home/components/AppCard.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { Avatar, Button, Card, Col, Empty, message, Space, Skeleton, Tag } from "antd";
-import { useNavigate } from 'react-router-dom';
-import { getStaticPreviewUrl } from "@/constants/proUrlOperation";
-import { EyeOutlined } from "@ant-design/icons";
-import styles from './AppCard.module.less';
-
-interface AppData {
- id: number,
- appName: string,
- deployKey: string,
- appDesc: string,
- initPrompt: string,
- cover: string,
- pageViews?: number,
- appType?: string,
- user?: { userAvatar: string, userName: string },
- codeGenType?: string,
-}
-
-interface Props {
- app: AppData,
- onCopy: (text: string) => void;
- children?: React.ReactNode;
- loading?: boolean;
-}
-
-const AppCard: React.FC
= (props) => {
- const { app, onCopy, children, loading = false } = props;
- const navigate = useNavigate();
-
- const copyToClipboard = async (text: string) => {
- try {
- await navigator.clipboard.writeText(text);
- onCopy(text);
- message.success('复制成功');
- } catch (err) {
- console.error('复制失败:', err);
- const textArea = document.createElement('textarea');
- textArea.value = text;
- document.body.appendChild(textArea);
- textArea.select();
- try {
- document.execCommand('copy');
- onCopy(text);
- message.success('复制成功');
- } catch (fallbackErr) {
- message.error('复制失败,请手动复制');
- }
- document.body.removeChild(textArea);
- }
- };
-
-
- return (
-
- {loading ? (
-
-
-
-
- ) : (
-
window.open(getStaticPreviewUrl(app.codeGenType, app.id, app.deployKey), '_blank')}
- className={styles.appCard}
- cover={
- app.cover ? (
-
-

-
- ) : (
-
- )
- }
- >
-
- {app.appType && (
- {app.appType}
- )}
- {children}
-
- }
- title={app.appName}
- description={
-
-
{"创建者:" + (app.user?.userName || '')}
- {app.pageViews !== undefined && (
-
-
- {app.pageViews || 0}
-
- )}
-
- }
- />
-
-
- )}
-
- {!loading && (
-
-
-
-
-
- )}
-
- );
-};
-
-export default AppCard;
diff --git a/ai-code-frontend/src/pages/Code/Home/index.module.less b/ai-code-frontend/src/pages/Code/Home/index.module.less
deleted file mode 100644
index f944a65..0000000
--- a/ai-code-frontend/src/pages/Code/Home/index.module.less
+++ /dev/null
@@ -1,270 +0,0 @@
-.homePageContainer {
- padding: 32px;
- width: 1600px;
- margin: 0 auto;
- position: relative;
- z-index: 1;
-
- @media (max-width: 1600px) {
- width: 100%;
- max-width: 1600px;
- }
-
- @media (max-width: 768px) {
- padding: 16px;
- }
-}
-
-.headerSection {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 30px;
-
- @media (max-width: 768px) {
- margin-bottom: 20px;
- }
-}
-
-.title {
- background: linear-gradient(135deg, #1890ff, #52c41a);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- font-weight: bold;
- margin-bottom: 0;
- text-align: center;
- font-size: 42px;
- font-family: Arial, sans-serif;
-
- @media (max-width: 768px) {
- font-size: 28px;
- }
-}
-
-.createCard {
- margin: 50px auto;
- position: relative;
- max-width: 700px;
- border-radius: 30px;
- padding-bottom: 60px;
-
- @media (max-width: 768px) {
- margin: 30px auto;
- max-width: 100%;
- border-radius: 20px;
- padding-bottom: 50px;
- }
-}
-
-.textArea {
- resize: none;
- font-size: 22px;
-
- @media (max-width: 768px) {
- font-size: 18px;
- }
-}
-
-.createButton {
- position: absolute;
- bottom: 20px;
- right: 20px;
- background: linear-gradient(135deg, #1890ff, #52c41a);
- border: none;
-
- &:hover {
- background: linear-gradient(135deg, #40a9ff, #73d13d);
- }
-
- &:active {
- background: linear-gradient(135deg, #096dd9, #389e0d);
- }
-
- @media (max-width: 768px) {
- bottom: 15px;
- right: 15px;
- }
-}
-
-.gradientButton {
- background: linear-gradient(135deg, #1890ff, #52c41a);
- border: none;
- color: #fff;
-
- &:hover {
- background: linear-gradient(135deg, #40a9ff, #73d13d);
- color: #fff;
- }
-
- &:active {
- background: linear-gradient(135deg, #096dd9, #389e0d);
- color: #fff;
- }
-}
-
-.outlineGradientButton {
- background: transparent;
- border: 2px solid transparent;
- background-image: linear-gradient(white, white), linear-gradient(135deg, #1890ff, #52c41a);
- background-origin: border-box;
- background-clip: padding-box, border-box;
-
- &:hover {
- background-image: linear-gradient(white, white), linear-gradient(135deg, #40a9ff, #73d13d);
- }
-
- &:active {
- background-image: linear-gradient(white, white), linear-gradient(135deg, #096dd9, #389e0d);
- }
-}
-
-.presetPromptsContainer {
- margin: 0 auto 50px;
- max-width: 700px;
- padding: 20px;
- background: linear-gradient(135deg, rgba(100, 181, 246, 0.08) 0%, rgba(129, 199, 132, 0.05) 100%);
- border-radius: 16px;
- border: 2px solid rgba(100, 181, 246, 0.15);
- box-shadow: 0 4px 20px rgba(100, 181, 246, 0.1);
- position: relative;
- z-index: 10;
-
- @media (max-width: 768px) {
- margin: 0 auto 30px;
- max-width: 100%;
- padding: 16px;
- border-radius: 12px;
- }
-}
-
-.presetPromptsTitle {
- font-size: 16px;
- font-weight: bold;
- color: #64b5f6;
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- gap: 8px;
-
- @media (max-width: 768px) {
- font-size: 14px;
- margin-bottom: 10px;
- }
-}
-
-.presetTagsWrapper {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
-
- @media (max-width: 768px) {
- gap: 8px;
- }
-}
-
-.presetTag {
- cursor: pointer;
- font-size: 14px;
- padding: 8px 16px;
- border-radius: 20px;
- background: linear-gradient(135deg, #64b5f6 0%, #42a5f5 100%);
- color: #ffffff;
- border: none;
- font-weight: 500;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- box-shadow: 0 2px 8px rgba(100, 181, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2);
- user-select: none;
- position: relative;
- overflow: hidden;
-
- &::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 0;
- height: 0;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.3);
- transform: translate(-50%, -50%);
- transition: width 0.6s ease, height 0.6s ease;
- }
-
- &::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 4px;
- height: 4px;
- background: rgba(255, 255, 255, 0.8);
- border-radius: 50%;
- transform: translate(-50%, -50%);
- opacity: 0;
- pointer-events: none;
- }
-
- .particle {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 6px;
- height: 6px;
- background: rgba(255, 255, 255, 0.9);
- border-radius: 50%;
- pointer-events: none;
- opacity: 0;
- }
-
- &:hover {
- background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%);
- box-shadow: 0 8px 24px rgba(100, 181, 246, 0.5), 0 0 20px rgba(100, 181, 246, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.3);
- transform: translateY(-3px) scale(1.02);
- }
-
- &:active {
- background: linear-gradient(135deg, #1e88e5 0%, #1976d2 100%);
- box-shadow: 0 4px 12px rgba(100, 181, 246, 0.4), 0 0 10px rgba(100, 181, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1);
- transform: translateY(-1px) scale(0.98);
-
- &::before {
- width: 200px;
- height: 200px;
- }
-
- &::after {
- animation: particleBurst 0.6s ease-out forwards;
- }
- }
-
- @media (max-width: 768px) {
- font-size: 12px;
- padding: 6px 12px;
-
- &:active {
- background: linear-gradient(135deg, #1e88e5 0%, #1976d2 100%);
- box-shadow: 0 4px 12px rgba(100, 181, 246, 0.4), 0 0 10px rgba(100, 181, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1);
- transform: scale(0.92);
-
- &::before {
- width: 150px;
- height: 150px;
- }
-
- &::after {
- animation: particleBurst 0.6s ease-out forwards;
- }
- }
- }
-}
-
-@keyframes particleBurst {
- 0% {
- opacity: 1;
- transform: translate(-50%, -50%) scale(1);
- }
- 100% {
- opacity: 0;
- transform: translate(-50%, -50%) scale(0);
- }
-}
diff --git a/ai-code-frontend/src/pages/Code/Home/index.tsx b/ai-code-frontend/src/pages/Code/Home/index.tsx
deleted file mode 100644
index 2875373..0000000
--- a/ai-code-frontend/src/pages/Code/Home/index.tsx
+++ /dev/null
@@ -1,375 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { addApp, listMyAppVoByPage, listFeaturedAppVoByPage, listAllAppTypes, listAllPresetPrompts } from '@/services/backend/appController';
-import {Button, Card, Input, message, Typography, Row, Col, Tag, Space, Pagination, Select, Dropdown} from 'antd';
-import AppCard from "@/pages/Code/Home/components/AppCard";
-import {
- CheckCircleOutlined,
- DoubleRightOutlined,
- DownOutlined,
- LogoutOutlined,
- ProductOutlined,
- UpOutlined
-} from "@ant-design/icons";
-import { CODE_GEN_TYPE_CONFIG, CodeGenTypeEnum } from "@/constants/codeGenTypeEnum";
-import Logo from "@/components/Logo";
-import { createParticleBurst } from "@/utils/animation";
-import InteractiveBackground from "@/components/InteractiveBackground";
-import styles from './index.module.less';
-
-const { Title } = Typography;
-const { TextArea } = Input;
-
-// 常量定义
-const DEFAULT_MY_APPS_PAGE_SIZE = 8; // 默认我的应用分页大小
-const DEFAULT_FEATURED_APPS_PAGE_SIZE = 10; // 默认精选应用分页大小
-const MAX_VISIBLE_APP_TYPES = 6; // 最大可见应用类型数量
-
-/**
- * 主页组件
- * 应用的首页,包含创建应用、展示我的应用和精选应用等功能
- */
-const HomePage: React.FC = () => {
- // 状态管理
- const [prompt, setPrompt] = useState(''); // 提示词
- const [loading, setLoading] = useState(false); // 创建应用加载状态
- const [myApps, setMyApps] = useState<[]>([]); // 我的应用列表
- const [featuredApps, setFeaturedApps] = useState<[]>([]); // 精选应用列表
- const [myAppsTotal, setMyAppsTotal] = useState(0); // 我的应用总数
- const [myAppsPageNum, setMyAppsPageNum] = useState(1); // 我的应用当前页码
- const [myAppsPageSize, setMyAppsPageSize] = useState(DEFAULT_MY_APPS_PAGE_SIZE); // 我的应用每页大小
- const [myAppsLoading, setMyAppsLoading] = useState(false); // 我的应用加载状态
- const [featuredAppsLoading, setFeaturedAppsLoading] = useState(false); // 精选应用加载状态
- const [appTypes, setAppTypes] = useState([]); // 应用类型列表
- const [selectedAppType, setSelectedAppType] = useState('all'); // 选中的应用类型
- const [codeType, setCodeType] = useState(CodeGenTypeEnum.HTML); // 代码生成类型
- const [isExpanded, setIsExpanded] = useState(false); // 应用类型是否展开
- const [presetPrompts, setPresetPrompts] = useState([]); // 预设提示词列表
- const [typingPlaceholder, setTypingPlaceholder] = useState(''); // 打字效果占位符
-
- // 导航钩子
- const navigate = useNavigate();
-
- // 代码类型下拉菜单配置
- const menuItems: MenuProps['items'] = Object.values(CodeGenTypeEnum).map(type => ({
- label: CODE_GEN_TYPE_CONFIG[type].label,
- key: type,
- icon: ,
- }));
-
- /**
- * 处理代码类型选择
- * @param e 菜单点击事件
- */
- const handleMenuClick: MenuProps['onClick'] = (e) => {
- setCodeType(e.key);
- };
-
- /**
- * 处理创建应用
- */
- const handleCreateApp = async () => {
- if (!prompt.trim()) return message.warning('请输入提示词');
- setLoading(true);
- try {
- const { data: appId } = await addApp({
- appName: prompt,
- appDesc: prompt,
- initPrompt: prompt,
- codeGenType: codeType
- });
- message.success('创建成功');
- navigate(`/chat/${appId}?prompt=${encodeURIComponent(prompt)}`);
- } catch (e) {
- message.error('创建失败:' + e.message);
- }
- setLoading(false);
- };
-
- /**
- * 加载我的应用列表
- * @param pageNum 页码
- * @param pageSize 每页大小
- */
- const loadMyApps = async (pageNum: number = 1, pageSize: number = DEFAULT_MY_APPS_PAGE_SIZE) => {
- setMyAppsLoading(true);
- try {
- const { data } = await listMyAppVoByPage({ pageNum, pageSize });
- setMyApps(data?.records || []);
- setMyAppsTotal(data?.total || 0);
- } catch (error) {
- message.error('加载我的应用失败');
- }
- setMyAppsLoading(false);
- };
-
- /**
- * 处理分页变化
- * @param page 页码
- * @param pageSize 每页大小
- */
- const handlePageChange = (page: number, pageSize: number) => {
- setMyAppsPageNum(page);
- setMyAppsPageSize(pageSize);
- loadMyApps(page, pageSize);
- };
-
- /**
- * 加载精选应用列表
- * @param appType 应用类型
- */
- const loadFeaturedApps = async (appType: string) => {
- setFeaturedAppsLoading(true);
- try {
- const res = await listFeaturedAppVoByPage({
- pageNum: 1,
- pageSize: DEFAULT_FEATURED_APPS_PAGE_SIZE,
- appType: appType === 'all' ? undefined : appType
- });
- setFeaturedApps(res.data?.records || []);
- } catch (error) {
- console.error('加载精选应用失败:', error);
- setFeaturedApps([]);
- }
- setFeaturedAppsLoading(false);
- };
-
- /**
- * 加载应用类型列表
- */
- const loadAppTypes = async () => {
- try {
- const res = await listAllAppTypes();
- setAppTypes(Array.isArray(res.data) ? res.data : []);
- } catch (error) {
- console.error('加载应用类型失败:', error);
- setAppTypes([]);
- }
- };
-
- /**
- * 加载预设提示词列表
- */
- const loadPresetPrompts = async () => {
- try {
- const { data } = await listAllPresetPrompts();
- setPresetPrompts(data || []);
- } catch (error) {
- message.error('加载预设提示词失败');
- }
- };
-
- /**
- * 复制应用提示词
- * @param text 提示词文本
- */
- const inputCopy = (text: string) => {
- setPrompt(text);
- };
-
- /**
- * 处理预设标签点击
- * @param text 预设提示词
- * @param event 点击事件
- */
- const handlePresetTagClick = (text: string, event: React.MouseEvent) => {
- createParticleBurst(event);
- setPrompt(text);
- };
-
- /**
- * 初始化加载数据
- */
- useEffect(() => {
- loadMyApps(1, DEFAULT_MY_APPS_PAGE_SIZE);
- loadFeaturedApps(selectedAppType);
- loadAppTypes();
- loadPresetPrompts();
- }, [selectedAppType]);
-
- /**
- * 打字效果动画
- */
- useEffect(() => {
- if (presetPrompts.length === 0) return;
-
- let charIndex = 0;
- let isDeleting = false;
- let currentIndex = 0;
-
- const typeEffect = () => {
- if (!presetPrompts[currentIndex]) {
- currentIndex = 0;
- }
- const currentPrompt = presetPrompts[currentIndex]?.prompt || '';
-
- if (!isDeleting && charIndex < currentPrompt.length) {
- setTypingPlaceholder(currentPrompt.substring(0, charIndex + 1));
- charIndex++;
- setTimeout(typeEffect, 150);
- } else if (!isDeleting && charIndex >= currentPrompt.length) {
- setTimeout(() => {
- isDeleting = true;
- typeEffect();
- }, 3000);
- } else if (isDeleting && charIndex > 0) {
- setTypingPlaceholder(currentPrompt.substring(0, charIndex - 1));
- charIndex--;
- setTimeout(typeEffect, 80);
- } else {
- isDeleting = false;
- charIndex = 0;
- currentIndex = (currentIndex + 1) % presetPrompts.length;
- setTimeout(typeEffect, 500);
- }
- };
-
- typeEffect();
- }, [presetPrompts]);
-
- return (
- <>
-
-
- {/* 头部区域 */}
-
-
-
CodeFree Builder -Nexus Creator
- 构建精美应用--只需靠 说
-
-
-
- {/* 创建应用卡片 */}
-
-
-
- {/* 预设提示词 */}
- {presetPrompts.length > 0 && (
-
-
-
-
- {presetPrompts.map((item) => (
- handlePresetTagClick(item.prompt || '', e)}
- >
- {item.label || '预设'}
-
- ))}
-
-
- )}
-
- {/* 应用商城 */}
-
-
- {/* 代码类型下拉菜单 */}
-
- } size={"large"} className={styles.outlineGradientButton}>
-
- {CODE_GEN_TYPE_CONFIG[codeType as CodeGenTypeEnum]?.label || '选择类型'}
-
-
-
-
-
- {/* 应用类型筛选 */}
-
-
- {appTypes
- .slice(0, isExpanded ? appTypes.length : MAX_VISIBLE_APP_TYPES)
- .map((appType) => (
-
- ))}
- {appTypes.length > MAX_VISIBLE_APP_TYPES && (
-
- )}
-
-
- {/* 全部案例按钮 */}
-
} size={"large"}
- onClick={() => navigate("/cases")} className={styles.gradientButton}>全部案例
-
-
- {/* 精选应用列表 */}
-
- {featuredApps.map(app => (
-
-
- } color="success">
- 精选
-
-
-
- ))}
-
-
-
- {/* 我的应用 */}
-
-
- {myApps.map(app => (
-
-
-
- ))}
-
-
- {/* 分页控件 */}
- {myAppsTotal > 0 && (
-
-
`共 ${total} 条`}
- pageSizeOptions={[8, 12, 16, 20]}
- />
-
- )}
-
- {/* 空状态 */}
- {myAppsTotal === 0 && !myAppsLoading && (
-
- 暂无应用,快去创建一个吧~
-
- )}
-
-
- >
- );
-};
-
-export default HomePage;
diff --git a/ai-code-frontend/src/pages/Forum/Detail/index.module.less b/ai-code-frontend/src/pages/Forum/Detail/index.module.less
deleted file mode 100644
index 48b7c80..0000000
--- a/ai-code-frontend/src/pages/Forum/Detail/index.module.less
+++ /dev/null
@@ -1,284 +0,0 @@
-.forumDetailContainer {
- max-width: 900px;
- margin: 0 auto;
- padding: 24px;
- min-height: calc(100vh - 64px);
-}
-
-.backButton {
- margin-bottom: 16px;
-}
-
-.appCard {
- margin-bottom: 24px;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- cursor: default !important;
-
- :hover {
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- }
-}
-
-.postCard {
- margin-bottom: 24px;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
-}
-
-.postHeader {
- margin-bottom: 16px;
-}
-
-.postTitle {
- font-size: 24px;
- font-weight: 600;
- color: #262626;
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.pinnedTag {
- flex-shrink: 0;
-}
-
-.postMeta {
- .metaItem {
- color: #8c8c8c;
- font-size: 14px;
- display: inline-flex;
- align-items: center;
- gap: 4px;
- transition: color 0.3s;
-
- &.liked {
- color: #ff4d4f;
- }
- }
-}
-
-.postAuthor {
- margin-bottom: 20px;
- color: #595959;
-}
-
-.authorName {
- color: #262626;
- font-weight: 500;
-}
-
-.createTime {
- color: #bfbfbf;
- font-size: 13px;
-}
-
-.postContent {
- font-size: 15px;
- line-height: 1.8;
- color: #262626;
- min-height: 200px;
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- margin-top: 24px;
- margin-bottom: 16px;
- font-weight: 600;
- color: #262626;
- }
-
- h1 {
- font-size: 28px;
- }
-
- h2 {
- font-size: 24px;
- }
-
- h3 {
- font-size: 20px;
- }
-
- p {
- margin-bottom: 16px;
- }
-
- ul,
- ol {
- margin-bottom: 16px;
- padding-left: 24px;
- }
-
- li {
- margin-bottom: 8px;
- }
-
- code {
- background-color: #f5f5f5;
- padding: 2px 6px;
- border-radius: 4px;
- font-family: 'Courier New', monospace;
- font-size: 14px;
- }
-
- pre {
- background-color: #f5f5f5;
- padding: 16px;
- border-radius: 8px;
- overflow-x: auto;
- margin-bottom: 16px;
-
- code {
- background-color: transparent;
- padding: 0;
- }
- }
-
- blockquote {
- border-left: 4px solid #1890ff;
- padding-left: 16px;
- margin: 16px 0;
- color: #595959;
- background-color: #f9f9f9;
- padding: 12px 16px;
- border-radius: 0 4px 4px 0;
- }
-
- a {
- color: #1890ff;
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
- }
-
- img {
- max-width: 100%;
- border-radius: 8px;
- margin: 16px 0;
- }
-
- table {
- width: 100%;
- border-collapse: collapse;
- margin-bottom: 16px;
-
- th,
- td {
- border: 1px solid #f0f0f0;
- padding: 12px;
- text-align: left;
- }
-
- th {
- background-color: #fafafa;
- font-weight: 600;
- }
- }
-}
-
-.relatedApp {
- margin-top: 20px;
-}
-
-.commentsCard {
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
-}
-
-.commentInput {
- margin-bottom: 24px;
-}
-
-.commentActions {
- margin-top: 12px;
- display: flex;
- justify-content: flex-end;
-}
-
-.commentItem {
- padding: 20px 0;
- border-bottom: 1px solid #f0f0f0;
-
- &:last-child {
- border-bottom: none;
- }
-}
-
-.commentContent {
- width: 100%;
-}
-
-.commentHeader {
- margin-bottom: 8px;
-}
-
-.commentAuthor {
- color: #262626;
- font-weight: 500;
- font-size: 14px;
-}
-
-.commentTime {
- color: #bfbfbf;
- font-size: 12px;
-}
-
-.commentBody {
- color: #595959;
- font-size: 14px;
- line-height: 1.6;
- margin-bottom: 12px;
- word-break: break-word;
-}
-
-.commentFooter {
- .commentAction {
- color: #8c8c8c;
- font-size: 13px;
- cursor: pointer;
- transition: color 0.3s;
-
- &:hover {
- color: #1890ff;
- }
-
- &.liked {
- color: #ff4d4f;
- }
- }
-}
-
-.replyInput {
- margin-top: 16px;
- padding: 12px;
- background-color: #fafafa;
- border-radius: 8px;
-}
-
-.replyActions {
- margin-top: 8px;
- display: flex;
- justify-content: flex-end;
- gap: 8px;
-}
-
-.loadMoreContainer {
- text-align: center;
- padding: 20px;
- color: #8c8c8c;
-}
-
-.hintText {
- color: #8c8c8c;
-}
-
-.allLoadedText {
- color: #bfbfbf;
-}
diff --git a/ai-code-frontend/src/pages/Forum/Detail/index.tsx b/ai-code-frontend/src/pages/Forum/Detail/index.tsx
deleted file mode 100644
index 012d0c6..0000000
--- a/ai-code-frontend/src/pages/Forum/Detail/index.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * 论坛详情页面
- * 显示帖子详细内容,支持点赞、评论和应用展示功能
- */
-import React, { useEffect, useState, useCallback } from 'react';
-import { Card, message, Avatar, Space, Button, Tag, Divider } from 'antd';
-import { useParams, Link } from 'react-router-dom';
-import { useNavigate } from 'react-router-dom';
-import { EyeOutlined, LikeOutlined, MessageOutlined, UserOutlined, ArrowLeftOutlined } from '@ant-design/icons';
-import { getForumPostVOById, likeForumPost, unlikeForumPost } from '@/services/backend/forumPostController';
-import InteractiveBackground from '@/components/InteractiveBackground';
-import AppCard from '@/pages/Code/Home/components/AppCard';
-import CommentSection from '@/components/CommentSection';
-import styles from './index.module.less';
-
-/**
- * 论坛详情组件
- */
-const ForumDetail: React.FC = () => {
- const { id } = useParams<{ id: string }>(); // 从URL参数获取帖子ID
- const navigate = useNavigate();
-
- // 状态管理
- const [postLoading, setPostLoading] = useState(false); // 帖子加载状态
- const [post, setPost] = useState(null); // 帖子数据
-
- /**
- * 获取帖子详情
- */
- const fetchPost = useCallback(() => {
- setPostLoading(true);
- getForumPostVOById(id).then((res) => {
- setPost(res.data);
- }).catch((error: any) => {
- message.error(error?.message ?? '加载帖子失败');
- }).finally(() => {
- setPostLoading(false);
- });
- }, [id]);
-
- /**
- * 处理点赞/取消点赞
- */
- const handleLike = () => {
- if (!post) return;
-
- const params: API.ForumPostUpdateRequest = {
- id: post.id,
- };
-
- if (post.isLiked) {
- // 取消点赞
- unlikeForumPost(params).then(() => {
- setPost((prev) => prev ? { ...prev, likeCount: (prev.likeCount || 0) - 1, isLiked: false } : null);
- }).catch((error) => {
- message.error(error?.message ?? '操作失败');
- });
- } else {
- // 点赞
- likeForumPost(params).then(() => {
- setPost((prev) => prev ? { ...prev, likeCount: (prev.likeCount || 0) + 1, isLiked: true } : null);
- }).catch((error) => {
- message.error(error?.message ?? '操作失败');
- });
- }
- };
-
- /**
- * 处理评论数量变化
- * @param count 新的评论数量
- */
- const handleCommentCountChange = (count: number) => {
- setPost((prev) => prev ? { ...prev, commentCount: count } : null);
- };
-
- // 初始加载帖子数据
- useEffect(() => {
- fetchPost();
- }, [fetchPost]);
-
- return (
- <>
-
-
- {/* 返回按钮 */}
-
}
- onClick={() => navigate('/forum')}
- className={styles.backButton}
- >
- 返回列表
-
-
- {/* 应用卡片 */}
- {post?.app && (
-
e.stopPropagation()}>
- {}}
- />
-
- )}
-
- {/* 帖子卡片 */}
-
- {post && (
- <>
- {/* 帖子头部 */}
-
-
- {/* 置顶标签 */}
- {post.isPinned === 1 && (
- 置顶
- )}
- {post.title}
-
- {/* 帖子元数据 */}
-
-
-
- {post.viewCount || 0}
-
-
- {post.likeCount || 0}
-
-
- {post.commentCount || 0}
-
-
-
-
-
-
-
- {/* 作者信息 */}
-
-
-
- } />
- {post.user?.userName}
-
- {post.createTime}
-
-
-
- {/* 帖子内容 */}
-
- >
- )}
-
-
- {/* 评论区 */}
-
-
- >
- );
-};
-
-export default ForumDetail;
\ No newline at end of file
diff --git a/ai-code-frontend/src/pages/Forum/List/components/HotPosts/HotPosts.module.less b/ai-code-frontend/src/pages/Forum/List/components/HotPosts/HotPosts.module.less
deleted file mode 100644
index 69018ef..0000000
--- a/ai-code-frontend/src/pages/Forum/List/components/HotPosts/HotPosts.module.less
+++ /dev/null
@@ -1,102 +0,0 @@
-.hotPostsCard {
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- position: sticky;
- top: 24px;
-}
-
-.hotPostItem {
- cursor: pointer;
- padding: 12px 0;
- border-bottom: 1px solid #f0f0f0;
- transition: all 0.3s;
-
- &:hover {
- background-color: #f8f9fa;
- padding-left: 8px;
- padding-right: 8px;
- border-radius: 4px;
- }
-
- &:last-child {
- border-bottom: none;
- }
-}
-
-.hotPostContent {
- display: flex;
- gap: 12px;
- width: 100%;
-}
-
-.hotPostRank {
- flex-shrink: 0;
- display: flex;
- align-items: flex-start;
- padding-top: 2px;
-}
-
-.rankBadge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- background-color: #f0f0f0;
- color: #8c8c8c;
- font-size: 12px;
- font-weight: 600;
-}
-
-.rankTop1 {
- background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
- color: #d48806;
- box-shadow: 0 2px 4px rgba(255, 215, 0, 0.3);
-}
-
-.rankTop2 {
- background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);
- color: #595959;
- box-shadow: 0 2px 4px rgba(192, 192, 192, 0.3);
-}
-
-.rankTop3 {
- background: linear-gradient(135deg, #cd7f32 0%, #daa06d 100%);
- color: #8c4b31;
- box-shadow: 0 2px 4px rgba(205, 127, 50, 0.3);
-}
-
-.hotPostInfo {
- flex: 1;
- min-width: 0;
-}
-
-.hotPostTitle {
- font-size: 14px;
- font-weight: 500;
- color: #262626;
- margin-bottom: 6px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- display: flex;
- align-items: center;
- gap: 4px;
-}
-
-.pinnedTag {
- color: #ff4d4f;
- font-size: 12px;
- flex-shrink: 0;
-}
-
-.hotPostMeta {
- .metaItem {
- color: #8c8c8c;
- font-size: 12px;
- display: inline-flex;
- align-items: center;
- gap: 2px;
- }
-}
diff --git a/ai-code-frontend/src/pages/Forum/List/components/HotPosts/index.tsx b/ai-code-frontend/src/pages/Forum/List/components/HotPosts/index.tsx
deleted file mode 100644
index b67eb72..0000000
--- a/ai-code-frontend/src/pages/Forum/List/components/HotPosts/index.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useState, useEffect } from 'react';
-import { Card, List, Avatar, Space, Empty, Spin } from 'antd';
-import { EyeOutlined, LikeOutlined, MessageOutlined, FireOutlined } from '@ant-design/icons';
-import { useNavigate } from 'react-router-dom';
-import { listHotPosts } from '@/services/backend/forumPostController';
-import styles from './HotPosts.module.less';
-
-const HotPosts: React.FC = () => {
- const navigate = useNavigate();
- const [loading, setLoading] = useState(false);
- const [hotPosts, setHotPosts] = useState([]);
-
- const fetchHotPosts = () => {
- setLoading(true);
- listHotPosts(10).then((res) => {
- setHotPosts(res.data || []);
- }).catch((error) => {
- console.error('加载热门帖子失败:', error);
- }).finally(() => {
- setLoading(false);
- });
- };
-
- useEffect(() => {
- fetchHotPosts();
- }, []);
-
- const handlePostClick = (postId: string) => {
- navigate(`/forum/detail/${postId}`);
- };
-
- return (
-
-
- 热门帖子
-
- }
- className={styles.hotPostsCard}
- >
- {loading ? (
-
-
-
- ) : (
- {
- const rankClass = index < 3 ? `rankTop${index + 1}` : '';
- return (
- handlePostClick(post.id!)}
- >
-
-
- {index < 3 ? (
-
- {index + 1}
-
- ) : (
- {index + 1}
- )}
-
-
-
- {post.isPinned === 1 && (
- [置顶]
- )}
- {post.title}
-
-
-
-
- {post.user?.userName}
-
- {post.viewCount || 0}
-
-
- {post.likeCount || 0}
-
-
- {post.commentCount || 0}
-
-
-
-
-
-
- );
- }}
- />
- )}
- {!loading && hotPosts.length === 0 && (
-
- )}
-
- );
-};
-
-export default HotPosts;
diff --git a/ai-code-frontend/src/pages/Forum/List/index.module.less b/ai-code-frontend/src/pages/Forum/List/index.module.less
deleted file mode 100644
index 0ea44a2..0000000
--- a/ai-code-frontend/src/pages/Forum/List/index.module.less
+++ /dev/null
@@ -1,217 +0,0 @@
-.forumPageContainer {
- max-width: 1400px;
- margin: 0 auto;
- padding: 24px;
- display: flex;
- gap: 24px;
- min-height: calc(100vh - 64px);
-
- .mainContent {
- flex: 1;
- min-width: 0;
- }
-
- .sidebar {
- width: 320px;
- flex-shrink: 0;
- }
-
- .searchCard {
- margin-bottom: 24px;
- border-radius: 16px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
- border: 1px solid #e8e8e8;
- }
-
- .searchContainer {
- display: flex;
- gap: 12px;
- align-items: center;
- }
-
- .postsCard {
- border-radius: 16px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
- border: 1px solid #e8e8e8;
- min-height: 400px;
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
- }
-
- .postCard {
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
- border: 1px solid #e8e8e8;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
- height: 100%;
- display: flex;
- flex-direction: column;
-
- &:hover {
- transform: translateY(-4px);
- box-shadow: 0 8px 24px rgba(24, 144, 255, 0.15);
- border-color: #1890ff;
- }
-
- :global {
- .ant-card-body {
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 20px;
- }
- }
- }
-
- .postContent {
- width: 100%;
- }
-
- .postHeader {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 16px;
- gap: 12px;
- }
-
- .postTitle {
- flex: 1;
- min-width: 0;
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
- }
-
- .pinnedTag {
- flex-shrink: 0;
- background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
- border: none;
- padding: 2px 10px;
- border-radius: 16px;
- font-weight: 600;
- font-size: 11px;
- box-shadow: 0 2px 6px rgba(255, 77, 79, 0.25);
- }
-
- .titleText {
- font-size: 16px;
- font-weight: 700;
- color: #1f1f1f;
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- letter-spacing: 0.3px;
- line-height: 1.4;
- flex: 1;
- }
-
- .appInfo {
- margin-bottom: 12px;
- padding: 8px 12px;
- background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
- border-radius: 8px;
- border-left: 3px solid #1890ff;
- box-shadow: 0 2px 6px rgba(24, 144, 255, 0.08);
- }
-
- .appName {
- color: #1890ff;
- font-weight: 600;
- font-size: 13px;
- letter-spacing: 0.2px;
- }
-
- .appType {
- font-size: 11px;
- background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
- border: none;
- border-radius: 12px;
- padding: 1px 8px;
- color: white;
- font-weight: 500;
- }
-
- .postBody {
- margin-bottom: 12px;
- flex: 1;
- }
-
- .postExcerpt {
- color: #434343;
- font-size: 13px;
- line-height: 1.6;
- max-height: 60px;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- letter-spacing: 0.2px;
- }
-
- .postFooter {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-top: 12px;
- border-top: 1px solid #f0f0f0;
- margin-top: auto;
- }
-
- .metaItem {
- color: #595959;
- font-size: 13px;
- font-weight: 500;
- display: flex;
- align-items: center;
- gap: 4px;
- transition: color 0.3s;
-
- &:hover {
- color: #1890ff;
- }
- }
-
- .authorName {
- color: #595959;
- font-size: 13px;
- font-weight: 500;
- transition: color 0.3s;
-
- &:hover {
- color: #1890ff;
- }
- }
-
- .createTime {
- color: #bfbfbf;
- font-size: 12px;
- font-weight: 400;
- }
-
- .loadMoreContainer {
- text-align: center;
- padding: 24px;
- color: #8c8c8c;
- }
-
- .hintText {
- color: #8c8c8c;
- font-size: 14px;
- font-weight: 500;
- }
-
- .allLoadedText {
- color: #bfbfbf;
- font-size: 14px;
- }
-
- .loadingText {
- color: #1890ff;
- font-size: 14px;
- font-weight: 500;
- }
-}
\ No newline at end of file
diff --git a/ai-code-frontend/src/pages/Forum/List/index.tsx b/ai-code-frontend/src/pages/Forum/List/index.tsx
deleted file mode 100644
index 4e4b902..0000000
--- a/ai-code-frontend/src/pages/Forum/List/index.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-/**
- * 论坛列表页面
- * 显示帖子列表,支持搜索、无限滚动加载和发布帖子功能
- */
-import React, {useEffect, useState, useRef, useCallback} from 'react';
-import {Card, message, Tag, Avatar, Space, Input, Empty, Spin, Button, Row, Col} from 'antd';
-import {EyeOutlined, LikeOutlined, MessageOutlined, PlusOutlined, AppstoreOutlined} from '@ant-design/icons';
-import {useNavigate, Link} from 'react-router-dom';
-import InteractiveBackground from '@/components/InteractiveBackground';
-import HotPosts from './components/HotPosts';
-import {listForumPostVOByPage} from '@/services/backend/forumPostController';
-import styles from './index.module.less';
-
-const {Search} = Input;
-
-// 常量定义
-const PAGE_SIZE = 20; // 每页显示条数
-const INTERSECTION_THRESHOLD = 0.1; // 交叉观察器阈值
-
-/**
- * 论坛列表组件
- */
-const ForumList: React.FC = () => {
- const navigate = useNavigate();
-
- // 状态管理
- const [loading, setLoading] = useState(false); // 初始加载状态
- const [loadingMore, setLoadingMore] = useState(false); // 加载更多状态
- const [posts, setPosts] = useState([]); // 帖子列表
- const [searchKey, setSearchKey] = useState(''); // 搜索关键词
- const [pageNum, setPageNum] = useState(1); // 当前页码
- const [hasMore, setHasMore] = useState(true); // 是否有更多数据
- const [total, setTotal] = useState(0); // 总数据量
-
- // 引用管理
- const observerRef = useRef(null); // 交叉观察器引用
- const loadMoreRef = useRef(null); // 加载更多元素引用
-
- /**
- * 获取帖子列表
- * @param isLoadMore 是否为加载更多
- * @param currentPageNum 当前页码
- */
- const fetchPosts = useCallback((isLoadMore: boolean = false, currentPageNum: number = 1) => {
- if (isLoadMore) {
- setLoadingMore(true);
- } else {
- setLoading(true);
- }
-
- // 构建查询参数
- const params: API.ForumPostQueryRequest = {
- pageNum: currentPageNum,
- pageSize: PAGE_SIZE,
- searchKey: searchKey || undefined,
- };
-
- // 调用API获取数据
- listForumPostVOByPage(params).then((res) => {
- const newPosts = res.data?.records ?? [];
- const totalCount = res.data?.totalRow ?? 0;
-
- // 更新帖子列表
- setPosts((prev) => {
- const updatedPosts = isLoadMore ? [...prev, ...newPosts] : newPosts;
- setHasMore(updatedPosts.length < totalCount);
- return updatedPosts;
- });
-
- setTotal(totalCount);
-
- // 更新页码
- if (isLoadMore) {
- setPageNum((prev) => prev + 1);
- } else {
- setPageNum(2); // 首次加载后,下次从第2页开始
- }
- }).catch((error) => {
- message.error(error?.message ?? '加载失败');
- }).finally(() => {
- setLoading(false);
- setLoadingMore(false);
- });
- }, [searchKey]);
-
- /**
- * 加载更多数据
- */
- const loadMore = useCallback(() => {
- if (!loadingMore && hasMore) {
- fetchPosts(true, pageNum);
- }
- }, [loadingMore, hasMore, fetchPosts, pageNum]);
-
- /**
- * 处理搜索
- * @param value 搜索关键词
- */
- const handleSearch = (value: string) => {
- setSearchKey(value);
- setPageNum(1);
- setHasMore(true);
- fetchPosts(false, 1);
- };
-
- /**
- * 处理帖子点击
- * @param postId 帖子ID
- */
- const handlePostClick = (postId: string) => {
- navigate(`/forum/detail/${postId}`);
- };
-
- /**
- * 处理点赞
- * @param e 鼠标事件
- * @param postId 帖子ID
- */
- const handleLike = (e: React.MouseEvent, postId: string) => {
- e.stopPropagation();
- //点赞逻辑
- };
-
- // 初始加载数据
- useEffect(() => {
- setPageNum(1);
- setHasMore(true);
- fetchPosts(false, 1);
- }, [fetchPosts]);
-
- // 设置无限滚动
- useEffect(() => {
- const element = loadMoreRef.current;
- if (!element) return;
-
- // 创建交叉观察器
- observerRef.current = new IntersectionObserver(
- (entries) => {
- if (entries[0].isIntersecting) {
- loadMore();
- }
- },
- {threshold: INTERSECTION_THRESHOLD}
- );
-
- observerRef.current.observe(element);
-
- // 清理函数
- return () => {
- if (observerRef.current) {
- observerRef.current.disconnect();
- }
- };
- }, [loadMore]);
-
- return (
- <>
-
-
-
- {/* 搜索卡片 */}
-
-
- {
- const value = e.target.value;
- setSearchKey(value);
- if (!value) {
- handleSearch('');
- }
- }}
- />
-
-
-
-
- {/* 帖子列表卡片 */}
-
-
- {posts.map((post) => (
-
- handlePostClick(post.id!)}
- >
- {/* 帖子头部 */}
-
-
- {post.isPinned === 1 && (
- 置顶
- )}
- {post.title}
-
-
-
- {/* 应用信息 */}
- {post.app && (
-
-
-
- {post.app.appName}
- {post.app.appType && (
- {post.app.appType}
- )}
-
-
- )}
-
- {/* 帖子内容摘要 */}
-
-
- {post.content?.substring(0, 200)}
- {post.content && post.content.length > 200 && '...'}
-
-
-
- {/* 帖子底部信息 */}
-
-
-
- {post.viewCount || 0}
-
-
- {post.likeCount || 0}
-
-
- {post.commentCount || 0}
-
-
-
- e.stopPropagation()}
- style={{display: 'flex', alignItems: 'center'}}
- >
-
- {post.user?.userName}
-
- {post.createTime}
-
-
-
-
- ))}
-
-
- {/* 空状态 */}
- {!posts.length && !loading && (
-
- )}
-
- {/* 加载更多 */}
- {hasMore && posts.length > 0 && (
-
- {loadingMore ? (
-
- ) : (
- 下拉加载更多
- )}
-
- )}
-
- {/* 全部加载完成 */}
- {!hasMore && posts.length > 0 && (
-
- 已加载全部 {total} 条数据
-
- )}
-
-
-
- {/* 侧边栏 */}
-
-
-
-
- >
- );
-};
-
-export default ForumList;
diff --git a/ai-code-frontend/src/pages/Forum/Publish/index.less b/ai-code-frontend/src/pages/Forum/Publish/index.less
deleted file mode 100644
index 501d42b..0000000
--- a/ai-code-frontend/src/pages/Forum/Publish/index.less
+++ /dev/null
@@ -1,145 +0,0 @@
-.forum-publish-container {
- min-height: 100vh;
- padding: 24px;
- background: #f5f5f5;
-
- @media (max-width: 768px) {
- padding: 16px;
- }
-
- .ant-card {
- max-width: 900px;
- margin: 0 auto;
-
- @media (max-width: 768px) {
- max-width: 100%;
- }
- }
-
- .app-list-container {
- max-height: 400px;
- overflow-y: auto;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- padding: 12px;
-
- @media (max-width: 768px) {
- max-height: 300px;
- }
- }
-
- .loading-container {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 40px;
-
- @media (max-width: 768px) {
- padding: 20px;
- }
- }
-
- .app-list {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
-
- .app-item {
- padding: 12px;
- border: 1px solid #e8e8e8;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s;
-
- @media (max-width: 768px) {
- padding: 10px;
- }
-
- &:hover {
- border-color: #1890ff;
- background-color: #f0f7ff;
- }
-
- &.selected {
- border-color: #1890ff;
- background-color: #e6f7ff;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- }
-
- .app-name {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 8px;
- color: #262626;
-
- @media (max-width: 768px) {
- font-size: 14px;
- }
- }
-
- .app-desc {
- font-size: 14px;
- color: #8c8c8c;
- margin-bottom: 8px;
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
-
- @media (max-width: 768px) {
- font-size: 13px;
- -webkit-line-clamp: 3;
- }
- }
-
- .app-tags {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
-
- @media (max-width: 768px) {
- gap: 6px;
- }
- }
- }
-
- .load-more-container {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 16px;
- color: #8c8c8c;
- cursor: pointer;
-
- &:hover {
- color: #1890ff;
- }
- }
-
- .ql-editor {
- min-height: 400px;
- font-size: 16px;
- line-height: 1.8;
-
- @media (max-width: 768px) {
- min-height: 300px;
- font-size: 14px;
- }
- }
-
- .ql-toolbar {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
-
- @media (max-width: 768px) {
- flex-wrap: wrap;
- }
- }
-
- .ql-container {
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- }
-}
diff --git a/ai-code-frontend/src/pages/Forum/Publish/index.tsx b/ai-code-frontend/src/pages/Forum/Publish/index.tsx
deleted file mode 100644
index 03aa892..0000000
--- a/ai-code-frontend/src/pages/Forum/Publish/index.tsx
+++ /dev/null
@@ -1,321 +0,0 @@
-import {useState, useCallback, useRef, useEffect} from 'react';
-import {Form, Input, Button, Card, message, Spin, Tabs, Empty, Tag} from 'antd';
-import {useNavigate} from 'react-router-dom';
-import ReactQuill from 'react-quill';
-import 'react-quill/dist/quill.snow.css';
-import {listFeaturedAppVoByPage, listMyAppVoByPage} from '@/services/backend/appController';
-import {addForumPost} from '@/services/backend/forumPostController';
-import './index.less';
-
-// 常量定义
-const PAGE_SIZE = 10; // 每页应用数量
-
-/**
- * 论坛发布页面组件
- * 用于发布新的论坛帖子,可选择关联应用
- */
-const ForumPublish: React.FC = () => {
- // 导航钩子
- const navigate = useNavigate();
- // 表单实例
- const [form] = Form.useForm();
- // 状态管理
- const [submitting, setSubmitting] = useState(false); // 提交状态
- const [loadingApps, setLoadingApps] = useState(false); // 初始加载应用状态
- const [loadingMoreApps, setLoadingMoreApps] = useState(false); // 加载更多应用状态
- const [featuredApps, setFeaturedApps] = useState([]); // 精选应用列表
- const [myApps, setMyApps] = useState([]); // 我的应用列表
- const [featuredPageNum, setFeaturedPageNum] = useState(1); // 精选应用当前页码
- const [myPageNum, setMyPageNum] = useState(1); // 我的应用当前页码
- const [hasMoreFeaturedApps, setHasMoreFeaturedApps] = useState(true); // 是否有更多精选应用
- const [hasMoreMyApps, setHasMoreMyApps] = useState(true); // 是否有更多我的应用
- const [appType, setAppType] = useState<'featured' | 'my'>('featured'); // 当前选中的应用类型
- const [selectedAppId, setSelectedAppId] = useState(undefined); // 选中的应用ID
- // 引用
- const observerRef = useRef(null); // 交叉观察器引用,用于无限滚动
- const loadMoreRef = useRef(null); // 加载更多元素引用
-
- /**
- * 加载初始应用列表
- */
- const loadInitialApps = useCallback(() => {
- setLoadingApps(true);
- Promise.all([
- listFeaturedAppVoByPage({
- pageNum: 1,
- pageSize: PAGE_SIZE,
- }),
- listMyAppVoByPage({
- pageNum: 1,
- pageSize: PAGE_SIZE,
- }),
- ]).then(([featuredRes, myRes]) => {
- setFeaturedApps(featuredRes.data?.records ?? []);
- setHasMoreFeaturedApps((featuredRes.data?.records?.length ?? 0) >= PAGE_SIZE);
- setFeaturedPageNum(1);
- setMyApps(myRes.data?.records ?? []);
- setHasMoreMyApps((myRes.data?.records?.length ?? 0) >= PAGE_SIZE);
- setMyPageNum(1);
- }).catch((error: any) => {
- message.error(error?.message ?? '加载应用列表失败');
- }).finally(() => {
- setLoadingApps(false);
- });
- }, []);
-
- /**
- * 加载更多精选应用
- */
- const loadMoreFeaturedApps = useCallback(() => {
- if (loadingMoreApps || !hasMoreFeaturedApps) return;
- setLoadingMoreApps(true);
- const nextPageNum = featuredPageNum + 1;
- listFeaturedAppVoByPage({
- pageNum: nextPageNum,
- pageSize: PAGE_SIZE,
- }).then((res) => {
- const newApps = res.data?.records ?? [];
- setFeaturedApps((prev) => [...prev, ...newApps]);
- setHasMoreFeaturedApps(newApps.length >= PAGE_SIZE);
- setFeaturedPageNum(nextPageNum);
- }).catch((error: any) => {
- message.error(error?.message ?? '加载更多失败');
- }).finally(() => {
- setLoadingMoreApps(false);
- });
- }, [featuredPageNum, hasMoreFeaturedApps, loadingMoreApps]);
-
- /**
- * 加载更多我的应用
- */
- const loadMoreMyApps = useCallback(() => {
- if (loadingMoreApps || !hasMoreMyApps) return;
- setLoadingMoreApps(true);
- const nextPageNum = myPageNum + 1;
- listMyAppVoByPage({
- pageNum: nextPageNum,
- pageSize: PAGE_SIZE,
- }).then((res) => {
- const newApps = res.data?.records ?? [];
- setMyApps((prev) => [...prev, ...newApps]);
- setHasMoreMyApps(newApps.length >= PAGE_SIZE);
- setMyPageNum(nextPageNum);
- }).catch((error: any) => {
- message.error(error?.message ?? '加载更多失败');
- }).finally(() => {
- setLoadingMoreApps(false);
- });
- }, [myPageNum, hasMoreMyApps, loadingMoreApps]);
-
- /**
- * 初始化加载应用列表
- */
- useEffect(() => {
- loadInitialApps();
- }, [loadInitialApps]);
-
- /**
- * 设置无限滚动观察器
- */
- useEffect(() => {
- if (observerRef.current) {
- observerRef.current.disconnect();
- }
-
- observerRef.current = new IntersectionObserver((entries) => {
- if (entries[0].isIntersecting) {
- if (appType === 'featured') {
- loadMoreFeaturedApps();
- } else {
- loadMoreMyApps();
- }
- }
- }, {threshold: 0.1});
-
- if (loadMoreRef.current) {
- observerRef.current.observe(loadMoreRef.current);
- }
-
- return () => {
- if (observerRef.current) {
- observerRef.current.disconnect();
- }
- };
- }, [appType, loadMoreFeaturedApps, loadMoreMyApps]);
-
- /**
- * 处理表单提交
- * @param values 表单提交的值
- */
- const handleSubmit = (values: any) => {
- setSubmitting(true);
- const params: API.ForumPostAddRequest = {
- title: values.title,
- appId: selectedAppId,
- content: values.content,
- };
-
- addForumPost(params).then((res) => {
- message.success('发布成功');
- navigate('/forum');
- }).catch((error: any) => {
- message.error(error?.message ?? '发布失败');
- }).finally(() => {
- setSubmitting(false);
- });
- };
-
- /**
- * 处理应用选择
- * @param appId 应用ID
- */
- const handleAppSelect = (appId: number) => {
- setSelectedAppId(appId);
- };
-
- // 当前显示的应用列表和加载状态
- const currentApps = appType === 'featured' ? featuredApps : myApps;
- const hasMore = appType === 'featured' ? hasMoreFeaturedApps : hasMoreMyApps;
-
- // 富文本编辑器配置
- const modules = {
- toolbar: [
- [{'header': [1, 2, 3, 4, 5, 6, false]}],
- ['bold', 'italic', 'underline', 'strike'],
- [{'list': 'ordered'}, {'list': 'bullet'}],
- [{'color': []}, {'background': []}],
- ['link', 'image', 'code-block'],
- ['clean']
- ],
- };
-
- return (
-
-
-
-
-
-
- {/* 关联应用选择 */}
-
- setAppType(key as 'featured' | 'my')}
- items={[
- {
- key: 'featured',
- label: '精选应用',
- children: (
-
- {loadingApps && currentApps.length === 0 ? (
-
-
-
- ) : currentApps.length === 0 ? (
-
- ) : (
-
- {currentApps.map((app) => (
-
handleAppSelect(app.id!)}
- >
-
{app.appName}
-
{app.appDesc}
-
- {app.appType && {app.appType}}
- {app.scoringStrategy &&
- {app.scoringStrategy}}
-
-
- ))}
-
- )}
- {hasMore && (
-
- {loadingMoreApps ? : 加载更多}
-
- )}
-
- ),
- },
- {
- key: 'my',
- label: '我的应用',
- children: (
-
- {loadingApps && currentApps.length === 0 ? (
-
-
-
- ) : currentApps.length === 0 ? (
-
- ) : (
-
- {currentApps.map((app) => (
-
handleAppSelect(app.id!)}
- >
-
{app.appName}
-
{app.appDesc}
-
- {app.appType && {app.appType}}
- {app.scoringStrategy &&
- {app.scoringStrategy}}
-
-
- ))}
-
- )}
- {hasMore && (
-
- {loadingMoreApps ? : 加载更多}
-
- )}
-
- ),
- },
- ]}
- />
-
-
- {/* 内容输入(富文本编辑器) */}
-
-
-
-
- {/* 提交按钮 */}
-
-
-
-
-
-
- );
-};
-
-export default ForumPublish;
diff --git a/ai-code-frontend/src/pages/Space/Detail/index.module.less b/ai-code-frontend/src/pages/Space/Detail/index.module.less
deleted file mode 100644
index 3b22900..0000000
--- a/ai-code-frontend/src/pages/Space/Detail/index.module.less
+++ /dev/null
@@ -1,260 +0,0 @@
-.spaceDetailContainer {
- padding: 24px;
- min-height: 100vh;
- background: #f0f2f5;
-
- .ant-card {
- border-radius: 8px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- border: 1px solid #e8e8e8;
- margin-bottom: 16px;
- }
-
- .ant-card-head {
- border-bottom: 1px solid #e8e8e8;
- background: #fafafa;
-
- .ant-card-head-title {
- color: #262626;
- font-size: 16px;
- font-weight: 600;
- }
- }
-
- .ant-card-body {
- padding: 20px;
- }
-
- .spaceDesc {
- color: #595959;
- margin-bottom: 16px;
- font-size: 14px;
- line-height: 1.6;
- }
-
- .searchSection {
- margin-bottom: 16px;
- }
-
- .appCardWrapper {
- position: relative;
-
- .removeButton {
- position: absolute;
- top: 8px;
- right: 8px;
- z-index: 10;
- opacity: 0;
- transition: opacity 0.3s ease;
- }
-
- &:hover .removeButton {
- opacity: 1;
- }
- }
-
- .emptyState {
- text-align: center;
- padding: 40px 0;
- color: #8c8c8c;
- font-size: 14px;
- }
-
- .memberCard {
- position: sticky;
- top: 24px;
-
- .memberList {
- max-height: 500px;
- overflow-y: auto;
- }
-
- .memberItem {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 0;
- border-bottom: 1px solid #f0f0f0;
-
- &:last-child {
- border-bottom: none;
- }
-
- .memberInfo {
- flex: 1;
- min-width: 0;
-
- .memberName {
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
-
- .emptyMembers {
- text-align: center;
- padding: 32px 0;
- color: #8c8c8c;
-
- p {
- margin-top: 8px;
- font-size: 14px;
- }
- }
- }
-
- .addAppModalContent {
- .modalHeader {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0 0 12px 0;
- border-bottom: 1px solid #f0f0f0;
- margin-bottom: 12px;
-
- .modalTitle {
- font-size: 15px;
- font-weight: 600;
- color: #262626;
- }
- }
- }
-
- .appCardItem {
- transition: all 0.2s ease;
- border: 1px solid #e8e8e8;
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- border-color: #1890ff;
- }
-
- .ant-card-cover {
- border-radius: 8px 8px 0 0;
- }
-
- .ant-card-body {
- padding: 12px;
- }
-
- .ant-card-meta-title {
- margin-bottom: 6px;
- font-size: 14px;
- }
-
- .ant-card-meta-description {
- margin-bottom: 0;
- font-size: 12px;
- }
- }
-
- .selectedAppCard {
- border: 2px solid #1890ff !important;
- box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2) !important;
- transform: translateY(-2px);
- background: #f0f9ff;
-
- .ant-card-meta-title {
- color: #1890ff;
- }
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3) !important;
- }
- }
-}
-
-.container {
- padding: 24px;
-}
-
-.spaceInfoCard {
- background: #fff;
- border: 1px solid #e8e8e8;
-
- .ant-card-body {
- color: #262626;
- }
-
- h3 {
- color: #262626 !important;
- margin-bottom: 8px !important;
- }
-
- p {
- color: #595959 !important;
- margin-bottom: 12px !important;
- }
-
- .ant-tag {
- background: #f0f0f0;
- border-color: #d9d9d9;
- color: #595959;
- }
-}
-
-.membersCard,
-.appsCard {
- .ant-card-head {
- background: #fafafa;
- border-bottom: 1px solid #e8e8e8;
-
- .ant-card-head-title {
- color: #262626;
- }
- }
-}
-
-.membersCard {
- position: sticky;
- top: 24px;
- height: fit-content;
- max-height: calc(100vh - 48px);
- overflow-y: auto;
-
- .memberListItem {
- padding: 8px 0;
- border-bottom: 1px solid #f0f0f0;
-
- &:last-child {
- border-bottom: none;
- }
- }
-
- .memberItem {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
-
- .memberInfo {
- flex: 1;
- min-width: 0;
-
- .memberName {
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
-}
-
-.loadingContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 300px;
- background: #fff;
- border-radius: 8px;
- border: 1px solid #e8e8e8;
-}
diff --git a/ai-code-frontend/src/pages/Space/Detail/index.tsx b/ai-code-frontend/src/pages/Space/Detail/index.tsx
deleted file mode 100644
index 504c54f..0000000
--- a/ai-code-frontend/src/pages/Space/Detail/index.tsx
+++ /dev/null
@@ -1,466 +0,0 @@
-import React, { useEffect, useState, useCallback, useRef } from 'react';
-import { useParams, useNavigate } from 'react-router-dom';
-import { Card, message, Row, Col, Typography, Input, Select, Space, Modal, Button, Avatar, Tag, Spin, Empty, List } from 'antd';
-import { listMyAppVoByPage } from '@/services/backend/appController';
-import {
- getSpaceById,
- listSpaceMembers,
- addAppToSpace,
- removeAppFromSpace,
- listSpaceAppsByPage
-} from '@/services/backend/spaceController';
-import AppCard from '@/pages/Code/Home/components/AppCard';
-import InviteFriendsModal from '@/components/InviteFriendsModal';
-import { SearchOutlined, PlusOutlined, UserOutlined, TeamOutlined, UserAddOutlined, CheckOutlined } from '@ant-design/icons';
-import { SpaceTypeEnum, getSpaceTypeLabel } from '@/constants/spaceTypeEnum';
-import { SpaceUserRoleEnum, getSpaceUserRoleLabel, getSpaceUserRoleColor } from '@/constants/spaceUserRoleEnum';
-import { useScrollLoad } from '@/hooks/useScrollLoad';
-import styles from './index.module.less';
-
-const { Title, Paragraph } = Typography;
-const { Search } = Input;
-const { Option } = Select;
-
-const DEFAULT_PAGE_SIZE = 12;
-
-const SpaceDetailPage: React.FC = () => {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
- const spaceId = id;
-
- const [loading, setLoading] = useState(false);
- const [space, setSpace] = useState(null);
- const [members, setMembers] = useState([]);
- const [apps, setApps] = useState([]);
- const [myApps, setMyApps] = useState([]);
- const [addAppModalVisible, setAddAppModalVisible] = useState(false);
- const [inviteModalVisible, setInviteModalVisible] = useState(false);
- const [selectedAppId, setSelectedAppId] = useState(null);
- const [addAppLoading, setAddAppLoading] = useState(false);
- const [myAppsPageNum, setMyAppsPageNum] = useState(1);
- const [myAppsLoading, setMyAppsLoading] = useState(false);
- const [myAppsHasMore, setMyAppsHasMore] = useState(true);
- const myAppsListRef = React.useRef(null);
-
- /**
- * 加载空间详情
- */
- const loadSpaceDetail = () => {
- setLoading(true);
- getSpaceById(spaceId)
- .then((res) => {
- setSpace(res.data || null);
- })
- .catch(() => {
- message.error('加载空间详情失败');
- })
- .finally(() => {
- setLoading(false);
- });
- };
-
- /**
- * 加载空间成员列表
- */
- const loadSpaceMembers = () => {
- listSpaceMembers(spaceId)
- .then((res) => {
- setMembers(res.data || []);
- })
- .catch(() => {
- message.error('加载空间成员失败');
- });
- };
-
- /**
- * 加载空间应用列表
- */
- const loadSpaceApps = (pageNum = 1, pageSize = DEFAULT_PAGE_SIZE) => {
- setLoading(true);
- listSpaceAppsByPage({
- spaceId,
- pageNum,
- pageSize,
- })
- .then((res) => {
- const appsData = res.data?.records || res.data || [];
- setApps(Array.isArray(appsData) ? appsData : []);
- })
- .catch((error) => {
- console.error('加载空间应用失败:', error);
- message.error('加载空间应用失败');
- setApps([]);
- })
- .finally(() => {
- setLoading(false);
- });
- };
-
- /**
- * 添加应用到空间
- */
- const handleAddAppToSpace = () => {
- if (!selectedAppId) {
- message.warning('请选择要添加的应用');
- return;
- }
-
- setAddAppLoading(true);
- addAppToSpace({
- appId: selectedAppId,
- spaceId: spaceId || '0',
- })
- .then(() => {
- message.success('添加应用成功');
- setAddAppModalVisible(false);
- loadSpaceApps();
- })
- .catch(() => {
- message.error('添加应用失败');
- })
- .finally(() => {
- setAddAppLoading(false);
- });
- };
-
- /**
- * 从空间移除应用
- */
- const handleRemoveAppFromSpace = (appId: string) => {
- removeAppFromSpace({
- appId,
- spaceId: spaceId || '0',
- })
- .then(() => {
- message.success('移除应用成功');
- loadSpaceApps();
- })
- .catch(() => {
- message.error('移除应用失败');
- });
- };
-
- /**
- * 打开邀请好友弹窗
- */
- const handleInviteFriends = () => {
- setInviteModalVisible(true);
- };
-
- /**
- * 邀请好友成功回调
- */
- const handleInviteSuccess = () => {
- message.success('邀请好友成功');
- loadSpaceMembers();
- };
-
- /**
- * 加载我的应用列表
- */
- const loadMyApps = (pageNum = 1, pageSize = 20) => {
- setMyAppsLoading(true);
- listMyAppVoByPage({
- current: pageNum,
- pageSize,
- })
- .then((res) => {
- const newApps = res.data.records || [];
-
- if (pageNum === 1) {
- setMyApps(newApps);
- } else {
- setMyApps(prev => [...prev, ...newApps]);
- }
-
- setMyAppsHasMore(newApps.length === pageSize);
- setMyAppsPageNum(pageNum);
- })
- .catch(() => {
- message.error('加载我的应用失败');
- })
- .finally(() => {
- setMyAppsLoading(false);
- });
- };
-
- /**
- * 打开添加应用弹窗
- */
- const handleOpenAddAppModal = () => {
- setAddAppModalVisible(true);
- setMyAppsPageNum(1);
- loadMyApps(1, 20);
- };
-
- useEffect(() => {
- if (spaceId) {
- loadSpaceDetail();
- loadSpaceMembers();
- loadSpaceApps();
- }
- }, [spaceId]);
-
- useScrollLoad(() => {
- if (!myAppsLoading && myAppsHasMore) {
- loadMyApps(myAppsPageNum + 1, 20);
- }
- }, {
- threshold: 100,
- disabled: !myAppsHasMore || myAppsLoading,
- containerRef: myAppsListRef,
- });
-
- if (loading || !space) {
- return (
-
-
-
- );
- }
-
- return (
-
- {/* 空间信息卡片 */}
-
-
-
- {space.spaceName}
- {space.description || '暂无描述'}
-
-
- {getSpaceTypeLabel(space.spaceType)}
-
-
- 成员: {space.memberCount}
-
-
- 应用: {space.appCount}
-
-
-
-
- {space.spaceType === SpaceTypeEnum.TEAM && (
- }
- onClick={handleInviteFriends}
- >
- 邀请好友
-
- )}
-
-
-
-
- {/* 主要内容区域 */}
-
- {/* 应用列表 */}
-
-
-
- 应用列表
-
- }
- extra={
- }
- onClick={handleOpenAddAppModal}
- >
- 添加应用
-
- }
- >
-
- {apps.length > 0 ? (
- apps.map((app) => (
-
- handleRemoveAppFromSpace(app.id!)}
- >
- 移除
-
- ]}
- >
- {}}
- />
-
-
- ))
- ) : (
-
-
-
- )}
-
-
-
-
- {/* 成员列表 */}
-
-
-
- 成员列表 ({members.length})
-
- }
- >
- {members.length > 0 ? (
- (
-
-
-
}
- />
-
-
{member.userName}
-
- {getSpaceUserRoleLabel(member.role)}
-
-
-
-
- )}
- />
- ) : (
-
- )}
-
-
-
-
- {/* 添加应用弹窗 */}
-
setAddAppModalVisible(false)}
- footer={[
- ,
-
- ]}
- width={900}
- >
-
- {myApps.length > 0 ? (
-
- {myApps.map((app) => (
-
- setSelectedAppId(app.id?.toString())}
- className={selectedAppId === app.id?.toString() ? styles.selectedAppCard : styles.appCardItem}
- style={{ cursor: 'pointer', position: 'relative' }}
- cover={
- app.cover ? (
-
-

-
- ) : (
-
- 无封面
-
- )
- }
- >
-
- {app.appName}
- {selectedAppId === app.id?.toString() && (
-
- )}
-
- }
- description={
-
-
- {app.appDesc || '暂无描述'}
-
- {app.appType && (
-
{app.appType}
- )}
-
- }
- />
-
-
- ))}
-
- ) : (
-
- )}
-
- {/* 加载更多 */}
- {myAppsLoading && (
-
-
-
- )}
-
- {/* 没有更多数据 */}
- {!myAppsHasMore && myApps.length > 0 && (
-
- 没有更多应用了
-
- )}
-
-
-
- {/* 邀请好友弹窗 */}
- setInviteModalVisible(false)}
- spaceId={spaceId}
- onSuccess={handleInviteSuccess}
- />
-
- );
-};
-
-export default SpaceDetailPage;
\ No newline at end of file
diff --git a/ai-code-frontend/src/pages/Space/List/index.module.less b/ai-code-frontend/src/pages/Space/List/index.module.less
deleted file mode 100644
index 0227cae..0000000
--- a/ai-code-frontend/src/pages/Space/List/index.module.less
+++ /dev/null
@@ -1,33 +0,0 @@
-.spaceListContainer {
- padding: 32px;
- min-height: 100vh;
- background: #f5f5f5;
-
- .ant-card {
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- }
-
- .ant-card-head {
- border-bottom: 1px solid #f0f0f0;
- }
-
- .ant-card-head-title {
- font-size: 20px;
- font-weight: 600;
- }
-
- .ant-empty {
- padding: 80px 0;
- }
-
- .ant-empty-description {
- color: #999;
- font-size: 14px;
-
- p {
- margin-bottom: 20px;
- font-size: 18px;
- }
- }
-}
diff --git a/ai-code-frontend/src/pages/Space/List/index.tsx b/ai-code-frontend/src/pages/Space/List/index.tsx
deleted file mode 100644
index 798dac1..0000000
--- a/ai-code-frontend/src/pages/Space/List/index.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import React, {useEffect, useState} from 'react';
-import {listSpaceByPage, addSpace, deleteSpace} from '@/services/backend/spaceController';
-import {Button, Card, message, Modal, Form, Input, Select, Row, Col, Empty, Spin} from 'antd';
-import SpaceCard from "@/components/SpaceCard";
-import {PlusOutlined, TeamOutlined, UserOutlined} from "@ant-design/icons";
-import {SpaceTypeEnum, SPACE_TYPE_CONFIG} from "@/constants/spaceTypeEnum";
-import styles from './index.module.less';
-
-const {TextArea} = Input;
-const {Option} = Select;
-
-const DEFAULT_PAGE_SIZE = 8;
-
-const SpaceListPage: React.FC = () => {
- const [spaces, setSpaces] = useState<[]>([]);
- const [total, setTotal] = useState(0);
- const [loading, setLoading] = useState(false);
- const [createModalVisible, setCreateModalVisible] = useState(false);
- const [createLoading, setCreateLoading] = useState(false);
- const [form] = Form.useForm();
-
- const loadSpaces = async () => {
- setLoading(true);
- try {
- const {data} = await listSpaceByPage({pageNum: 1, pageSize: DEFAULT_PAGE_SIZE});
- setSpaces(data?.records || []);
- setTotal(data?.totalRow || 0);
- } catch (error) {
- message.error('加载空间列表失败');
- }
- setLoading(false);
- };
-
- useEffect(() => {
- loadSpaces();
- }, []);
-
- const handleCreateSpace = async (values) => {
- setCreateLoading(true);
- try {
- await addSpace({
- spaceName: values.spaceName,
- spaceType: values.spaceType,
- spaceDesc: values.spaceDesc,
- });
- message.success('创建成功');
- setCreateModalVisible(false);
- form.resetFields();
- loadSpaces();
- } catch (error) {
- message.error('创建失败:' + error.message);
- }
- setCreateLoading(false);
- };
-
- const handleDeleteSpace = async (spaceId: string) => {
- Modal.confirm({
- title: '确认删除',
- content: '确定要删除这个空间吗?删除后无法恢复。',
- onOk: async () => {
- try {
- await deleteSpace({id: spaceId});
- message.success('删除成功');
- loadSpaces();
- } catch (error) {
- message.error('删除失败:' + error.message);
- }
- },
- });
- };
-
- const handleEditSpace = () => {
- message.info('编辑功能待实现');
- };
-
- return (
-