feat: 完整集成JWLLL搜索推荐系统到Merge项目

新增功能:
- 完整的JWLLL搜索推荐后端服务 (back_jwlll/)
- 前端智能搜索和推荐功能集成
- HomeFeed组件增强: 数据源切换(原始数据 ↔ 智能推荐)
- 新增PostDetailJWLLL和UploadPageJWLLL组件
- 新增search_jwlll.js API接口

技术特性:
- 标签推荐和协同过滤推荐算法
- 中文分词和Word2Vec语义搜索
- 100%向后兼容,原功能完全保留
- 独立服务架构,无冲突部署

集成内容:
- JWLLL后端服务配置和依赖
- 前端路由和组件更新
- 样式文件和API集成
- 项目文档和启动工具

Change-Id: I1d008cf04eee40e7d81bfb9109f933d3447d1760
diff --git a/Merge/front/src/components/CreatePost.jsx b/Merge/front/src/components/CreatePost.jsx
index 1d2f306..c11e247 100644
--- a/Merge/front/src/components/CreatePost.jsx
+++ b/Merge/front/src/components/CreatePost.jsx
@@ -13,10 +13,8 @@
   const navigate = useNavigate()
   const { postId } = useParams()
   const isEdit = Boolean(postId)
-
   // 步骤:新帖先上传,编辑则直接到 detail
   const [step, setStep] = useState(isEdit ? 'detail' : 'upload')
-  const [files, setFiles] = useState([])
   const [mediaUrls, setMediaUrls] = useState([])
 
   // 表单字段
@@ -53,10 +51,8 @@
       .catch(err => setError(err.message))
       .finally(() => setLoading(false))
   }, [isEdit, postId])
-
   // 上传回调
   const handleUploadComplete = async uploadedFiles => {
-    setFiles(uploadedFiles)
     // TODO: 真正上传到服务器后替换为服务端 URL
     const urls = await Promise.all(
       uploadedFiles.map(f => URL.createObjectURL(f))
diff --git a/Merge/front/src/components/HomeFeed.jsx b/Merge/front/src/components/HomeFeed.jsx
index c681858..e32a2eb 100644
--- a/Merge/front/src/components/HomeFeed.jsx
+++ b/Merge/front/src/components/HomeFeed.jsx
@@ -1,9 +1,10 @@
 // src/components/HomeFeed.jsx
 
-import React, { useState, useEffect } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
 import { useNavigate } from 'react-router-dom'
 import { ThumbsUp } from 'lucide-react'
 import { fetchPosts, fetchPost } from '../api/posts_wzy'
+import { searchAPI } from '../api/search_jwlll'
 import '../style/HomeFeed.css'
 
 const categories = [
@@ -11,15 +12,120 @@
   '职场','情感','家居','游戏','旅行','健身'
 ]
 
+const recommendModes = [
+  { label: '标签推荐', value: 'tag' },
+  { label: '协同过滤推荐', value: 'cf' }
+]
+
+const DEFAULT_USER_ID = '3' // 确保数据库有此用户
+const DEFAULT_TAGS = ['美食','影视','穿搭'] // 可根据实际数据库调整
+
 export default function HomeFeed() {
   const navigate = useNavigate()
   const [activeCat, setActiveCat] = useState('推荐')
   const [items, setItems]         = useState([])
   const [loading, setLoading]     = useState(true)
   const [error, setError]         = useState(null)
+    // JWLLL 搜索推荐相关状态
+  const [search, setSearch] = useState('')
+  const [recMode, setRecMode] = useState('tag')
+  const [recCFNum, setRecCFNum] = useState(20)
+  const [useSearchRecommend, setUseSearchRecommend] = useState(false) // 是否使用搜索推荐模式  // JWLLL 搜索推荐功能函数
+  
+  // JWLLL搜索推荐内容
+  const fetchSearchContent = useCallback(async (keyword = '') => {
+    setLoading(true)
+    setError(null)
+    try {
+      const data = await searchAPI.search(keyword || activeCat, activeCat === '推荐' ? undefined : activeCat)
+      const formattedItems = (data.results || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        img: item.img || '', 
+        likes: item.heat || 0,
+        content: item.content
+      }))
+      setItems(formattedItems)
+    } catch (e) {
+      console.error('搜索失败:', e)
+      setError('搜索失败')
+      setItems([])
+    }
+    setLoading(false)
+  }, [activeCat])
+
+  // 标签推荐
+  const fetchTagRecommend = useCallback(async (tags) => {
+    setLoading(true)
+    setError(null)
+    try {
+      const data = await searchAPI.recommendByTags(DEFAULT_USER_ID, tags)
+      const formattedItems = (data.recommendations || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        img: item.img || '', 
+        likes: item.heat || 0,
+        content: item.content
+      }))
+      setItems(formattedItems)
+    } catch (e) {
+      console.error('标签推荐失败:', e)
+      setError('标签推荐失败')
+      setItems([])
+    }
+    setLoading(false)
+  }, [])
+
+  // 协同过滤推荐
+  const fetchCFRecommend = useCallback(async (topN = recCFNum) => {
+    setLoading(true)
+    setError(null)
+    try {
+      const data = await searchAPI.userBasedRecommend(DEFAULT_USER_ID, topN)
+      const formattedItems = (data.recommendations || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        img: item.img || '', 
+        likes: item.heat || 0,
+        content: item.content
+      }))
+      setItems(formattedItems)
+    } catch (e) {
+      console.error('协同过滤推荐失败:', e)
+      setError('协同过滤推荐失败')
+      setItems([])
+    }
+    setLoading(false)
+  }, [recCFNum])
+
+  // 获取用户兴趣标签后再推荐
+  const fetchUserTagsAndRecommend = useCallback(async () => {
+    setLoading(true)
+    setError(null)
+    let tags = []
+    try {
+      const data = await searchAPI.getUserTags(DEFAULT_USER_ID)
+      tags = Array.isArray(data.tags) && data.tags.length > 0 ? data.tags : DEFAULT_TAGS
+    } catch {
+      tags = DEFAULT_TAGS
+    }
+    if (recMode === 'tag') {
+      await fetchTagRecommend(tags)
+    } else {
+      await fetchCFRecommend()
+    }
+    setLoading(false)
+  }, [recMode, fetchTagRecommend, fetchCFRecommend])
 
   useEffect(() => {
-    async function loadPosts() {
+    // 原始数据加载函数
+    const loadPosts = async () => {
       try {
         const list = await fetchPosts()  // [{id, title, heat, created_at}, …]
         // 为了拿到 media_urls 和 user_id,这里再拉详情
@@ -43,25 +149,149 @@
         setLoading(false)
       }
     }
-    loadPosts()
-  }, [])
 
+    // 根据模式选择加载方式
+    if (activeCat === '推荐' && useSearchRecommend) {
+      fetchUserTagsAndRecommend()
+    } else {
+      loadPosts()
+    }
+  }, [activeCat, useSearchRecommend, fetchUserTagsAndRecommend])
+  // 切换推荐模式时的额外处理
+  useEffect(() => {
+    if (activeCat === '推荐' && useSearchRecommend) {
+      fetchUserTagsAndRecommend()
+    }
+    // eslint-disable-next-line
+  }, [recMode, fetchUserTagsAndRecommend])
+
+  // 根据模式选择不同的加载方式
+  const handleSearch = e => {
+    e.preventDefault()
+    if (useSearchRecommend) {
+      fetchSearchContent(search)
+    } else {
+      // 切换到搜索推荐模式
+      setUseSearchRecommend(true)
+      fetchSearchContent(search)
+    }
+  }
+
+  const handlePostClick = (postId) => {
+    navigate(`/post/${postId}`)
+  }
   return (
     <div className="home-feed">
+      {/* 数据源切换 */}
+      <div style={{marginBottom:12, display:'flex', alignItems:'center', gap:16}}>
+        <span>数据源:</span>
+        <div style={{display:'flex', gap:8}}>
+          <button
+            className={!useSearchRecommend ? 'rec-btn styled active' : 'rec-btn styled'}
+            onClick={() => {setUseSearchRecommend(false); setActiveCat('推荐')}}
+            type="button"
+            style={{
+              borderRadius: 20,
+              padding: '4px 18px',
+              border: !useSearchRecommend ? '2px solid #e84c4a' : '1px solid #ccc',
+              background: !useSearchRecommend ? '#fff0f0' : '#fff',
+              color: !useSearchRecommend ? '#e84c4a' : '#333',
+              fontWeight: !useSearchRecommend ? 600 : 400,
+              cursor: 'pointer',
+              transition: 'all 0.2s',
+              outline: 'none',
+            }}
+          >原始数据</button>
+          <button
+            className={useSearchRecommend ? 'rec-btn styled active' : 'rec-btn styled'}
+            onClick={() => {setUseSearchRecommend(true); setActiveCat('推荐')}}
+            type="button"
+            style={{
+              borderRadius: 20,
+              padding: '4px 18px',
+              border: useSearchRecommend ? '2px solid #e84c4a' : '1px solid #ccc',
+              background: useSearchRecommend ? '#fff0f0' : '#fff',
+              color: useSearchRecommend ? '#e84c4a' : '#333',
+              fontWeight: useSearchRecommend ? 600 : 400,
+              cursor: 'pointer',
+              transition: 'all 0.2s',
+              outline: 'none',
+            }}
+          >智能推荐</button>
+        </div>
+      </div>
+
+      {/* 推荐模式切换,仅在推荐页显示且使用搜索推荐时 */}
+      {activeCat === '推荐' && useSearchRecommend && (
+        <div style={{marginBottom:12, display:'flex', alignItems:'center', gap:16}}>
+          <span style={{marginRight:8}}>推荐模式:</span>
+          <div style={{display:'flex', gap:8}}>
+            {recommendModes.map(m => (
+              <button
+                key={m.value}
+                className={recMode===m.value? 'rec-btn styled active':'rec-btn styled'}
+                onClick={() => setRecMode(m.value)}
+                type="button"
+                style={{
+                  borderRadius: 20,
+                  padding: '4px 18px',
+                  border: recMode===m.value ? '2px solid #e84c4a' : '1px solid #ccc',
+                  background: recMode===m.value ? '#fff0f0' : '#fff',
+                  color: recMode===m.value ? '#e84c4a' : '#333',
+                  fontWeight: recMode===m.value ? 600 : 400,
+                  cursor: 'pointer',
+                  transition: 'all 0.2s',
+                  outline: 'none',
+                }}
+              >{m.label}</button>
+            ))}
+          </div>
+          {/* 协同过滤推荐数量选择 */}
+          {recMode === 'cf' && (
+            <div style={{display:'flex',alignItems:'center',gap:4}}>
+              <span>推荐数量:</span>
+              <select value={recCFNum} onChange={e => { setRecCFNum(Number(e.target.value)); fetchCFRecommend(Number(e.target.value)) }} style={{padding:'2px 8px',borderRadius:6,border:'1px solid #ccc'}}>
+                {[10, 20, 30, 50].map(n => <option key={n} value={n}>{n}</option>)}
+              </select>
+            </div>
+          )}
+        </div>
+      )}
+
+      {/* 搜索栏 */}
+      <form className="feed-search" onSubmit={handleSearch} style={{marginBottom:16, display:'flex', gap:8, alignItems:'center'}}>
+        <input
+          type="text"
+          className="search-input"
+          placeholder="搜索内容/标题/标签"
+          value={search}
+          onChange={e => setSearch(e.target.value)}
+        />
+        <button type="submit" className="search-btn">搜索</button>
+      </form>
+
       {/* 顶部分类 */}
       <nav className="feed-tabs">
         {categories.map(cat => (
           <button
             key={cat}
             className={cat === activeCat ? 'tab active' : 'tab'}
-            onClick={() => setActiveCat(cat)}
+            onClick={() => { 
+              setActiveCat(cat); 
+              setSearch('');
+              if (useSearchRecommend) {
+                if (cat === '推荐') {
+                  fetchUserTagsAndRecommend()
+                } else {
+                  fetchSearchContent()
+                }
+              }
+            }}
           >
             {cat}
           </button>
         ))}
-      </nav>
-
-      {/* 状态提示 */}
+      </nav>      {/* 状态提示 */}
       {loading ? (
         <div className="loading">加载中…</div>
       ) : error ? (
@@ -69,22 +299,27 @@
       ) : (
         /* 瀑布流卡片区 */
         <div className="feed-grid">
-          {items.map(item => (
-            <div key={item.id} className="feed-card">
-              <img className="card-img" src={item.img} alt={item.title} />
-              <h3 className="card-title">{item.title}</h3>
-              <div className="card-footer">
-                <div className="card-author">
-                  <img className="avatar" src={item.avatar} alt={item.author} />
-                  <span className="username">{item.author}</span>
-                </div>
-                <div className="card-likes">
-                  <ThumbsUp size={16} />
-                  <span className="likes-count">{item.likes}</span>
+          {items.length === 0 ? (
+            <div style={{padding:32, color:'#aaa'}}>暂无内容</div>
+          ) : (
+            items.map(item => (
+              <div key={item.id} className="feed-card" onClick={() => handlePostClick(item.id)}>
+                {item.img && <img className="card-img" src={item.img} alt={item.title} />}
+                <h3 className="card-title">{item.title}</h3>
+                {item.content && <div className="card-content">{item.content.slice(0, 60) || ''}</div>}
+                <div className="card-footer">
+                  <div className="card-author">
+                    <img className="avatar" src={item.avatar} alt={item.author} />
+                    <span className="username">{item.author}</span>
+                  </div>
+                  <div className="card-likes">
+                    <ThumbsUp size={16} />
+                    <span className="likes-count">{item.likes}</span>
+                  </div>
                 </div>
               </div>
-            </div>
-          ))}
+            ))
+          )}
         </div>
       )}
     </div>
diff --git a/Merge/front/src/components/LogsDashboard.js b/Merge/front/src/components/LogsDashboard.js
index 22047e2..6ab4746 100644
--- a/Merge/front/src/components/LogsDashboard.js
+++ b/Merge/front/src/components/LogsDashboard.js
@@ -3,7 +3,9 @@
 import '../style/Admin.css';
 
 function LogsDashboard() {
+  // eslint-disable-next-line no-unused-vars
   const [logs, setLogs] = useState([]);
+  // eslint-disable-next-line no-unused-vars
   const [stats, setStats] = useState({});
 
   useEffect(() => {
diff --git a/Merge/front/src/components/PostDetailJWLLL.jsx b/Merge/front/src/components/PostDetailJWLLL.jsx
new file mode 100644
index 0000000..0dc7289
--- /dev/null
+++ b/Merge/front/src/components/PostDetailJWLLL.jsx
@@ -0,0 +1,322 @@
+import React, { useState, useEffect } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, ThumbsUp, MessageCircle, Share2, BookmarkPlus, Heart, Eye } from 'lucide-react'
+import { searchAPI } from '../api/search_jwlll'
+import '../style/PostDetail.css'
+
+export default function PostDetail() {
+  const { id } = useParams()
+  const navigate = useNavigate()
+  const [post, setPost] = useState(null)
+  const [loading, setLoading] = useState(true)
+  const [error, setError] = useState(null)
+  const [liked, setLiked] = useState(false)
+  const [bookmarked, setBookmarked] = useState(false)
+  const [likeCount, setLikeCount] = useState(0)
+  const [comments, setComments] = useState([])
+  const [newComment, setNewComment] = useState('')
+  const [showComments, setShowComments] = useState(false)
+
+  const DEFAULT_USER_ID = '3' // 默认用户ID
+
+  useEffect(() => {
+    fetchPostDetail()
+    fetchComments()
+  }, [id])
+
+  const fetchPostDetail = async () => {
+    setLoading(true)
+    setError(null)
+    try {
+      const data = await searchAPI.getPostDetail(id)
+      setPost(data)
+      setLikeCount(data.heat || 0)
+    } catch (error) {
+      console.error('获取帖子详情失败:', error)
+      setError('帖子不存在或已被删除')
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const fetchComments = async () => {
+    try {
+      const data = await searchAPI.getComments(id)
+      setComments(data.comments || [])
+    } catch (error) {
+      console.error('获取评论失败:', error)
+    }
+  }
+
+  const handleBack = () => {
+    navigate(-1)
+  }
+
+  const handleLike = async () => {
+    try {
+      const newLiked = !liked
+      if (newLiked) {
+        await searchAPI.likePost(id, DEFAULT_USER_ID)
+      } else {
+        await searchAPI.unlikePost(id, DEFAULT_USER_ID)
+      }
+      setLiked(newLiked)
+      setLikeCount(prev => newLiked ? prev + 1 : prev - 1)
+    } catch (error) {
+      console.error('点赞失败:', error)
+      // 回滚状态
+      setLiked(!liked)
+      setLikeCount(prev => liked ? prev + 1 : prev - 1)
+    }
+  }
+
+  const handleBookmark = () => {
+    setBookmarked(!bookmarked)
+    // 实际项目中这里应该调用后端API保存收藏状态
+  }
+
+  const handleShare = () => {
+    // 分享功能
+    if (navigator.share) {
+      navigator.share({
+        title: post?.title,
+        text: post?.content,
+        url: window.location.href,
+      })
+    } else {
+      // 复制链接到剪贴板
+      navigator.clipboard.writeText(window.location.href)
+      alert('链接已复制到剪贴板')
+    }
+  }
+
+  const handleAddComment = async (e) => {
+    e.preventDefault()
+    if (!newComment.trim()) return
+
+    try {
+      await searchAPI.addComment(id, DEFAULT_USER_ID, newComment)
+      setNewComment('')
+      fetchComments() // 刷新评论列表
+    } catch (error) {
+      console.error('添加评论失败:', error)
+      alert('评论失败,请重试')
+    }
+  }
+
+  if (loading) {
+    return (
+      <div className="post-detail">
+        <div className="loading-container">
+          <div className="loading-spinner"></div>
+          <p>加载中...</p>
+        </div>
+      </div>
+    )
+  }
+
+  if (error) {
+    return (
+      <div className="post-detail">
+        <div className="error-container">
+          <h2>😔 出错了</h2>
+          <p>{error}</p>
+          <button onClick={handleBack} className="back-btn">
+            <ArrowLeft size={20} />
+            返回
+          </button>
+        </div>
+      </div>
+    )
+  }
+
+  if (!post) {
+    return (
+      <div className="post-detail">
+        <div className="error-container">
+          <h2>😔 帖子不存在</h2>
+          <p>该帖子可能已被删除或不存在</p>
+          <button onClick={handleBack} className="back-btn">
+            <ArrowLeft size={20} />
+            返回
+          </button>
+        </div>
+      </div>
+    )
+  }
+
+  return (
+    <div className="post-detail">
+      {/* 顶部导航栏 */}
+      <header className="post-header">
+        <button onClick={handleBack} className="back-btn">
+          <ArrowLeft size={20} />
+          返回
+        </button>
+        <div className="header-actions">
+          <button onClick={handleShare} className="action-btn">
+            <Share2 size={20} />
+          </button>
+          <button 
+            onClick={handleBookmark} 
+            className={`action-btn ${bookmarked ? 'active' : ''}`}
+          >
+            <BookmarkPlus size={20} />
+          </button>
+        </div>
+      </header>
+
+      {/* 主要内容区 */}
+      <main className="post-content">
+        {/* 帖子标题 */}
+        <h1 className="post-title">{post.title}</h1>
+
+        {/* 作者信息和元数据 */}
+        <div className="post-meta">
+          <div className="author-info">
+            <div className="avatar">
+              {post.author ? post.author.charAt(0).toUpperCase() : 'U'}
+            </div>
+            <div className="author-details">
+              <span className="author-name">{post.author || '匿名用户'}</span>
+              <span className="post-date">
+                {post.create_time ? new Date(post.create_time).toLocaleDateString('zh-CN') : '未知时间'}
+              </span>
+            </div>
+          </div>
+          <div className="post-stats">
+            <span className="stat-item">
+              <Eye size={16} />
+              {post.views || 0}
+            </span>
+            <span className="stat-item">
+              <Heart size={16} />
+              {likeCount}
+            </span>
+          </div>
+        </div>
+
+        {/* 标签 */}
+        {post.tags && post.tags.length > 0 && (
+          <div className="post-tags">
+            {post.tags.map((tag, index) => (
+              <span key={index} className="tag">{tag}</span>
+            ))}
+          </div>
+        )}
+
+        {/* 帖子正文 */}
+        <div className="post-body">
+          <p>{post.content}</p>
+        </div>
+
+        {/* 类别信息 */}
+        {(post.category || post.type) && (
+          <div className="post-category">
+            {post.category && (
+              <>
+                <span className="category-label">分类:</span>
+                <span className="category-name">{post.category}</span>
+              </>
+            )}
+            {post.type && (
+              <>
+                <span className="category-label" style={{marginLeft: '1em'}}>类型:</span>
+                <span className="category-name">{post.type}</span>
+              </>
+            )}
+          </div>
+        )}
+
+        {/* 评论区 */}
+        <div className="comments-section">
+          <div className="comments-header">
+            <button 
+              onClick={() => setShowComments(!showComments)}
+              className="comments-toggle"
+            >
+              <MessageCircle size={20} />
+              评论 ({comments.length})
+            </button>
+          </div>
+
+          {showComments && (
+            <div className="comments-content">
+              {/* 添加评论 */}
+              <form onSubmit={handleAddComment} className="comment-form">
+                <textarea
+                  value={newComment}
+                  onChange={(e) => setNewComment(e.target.value)}
+                  placeholder="写下你的评论..."
+                  className="comment-input"
+                  rows={3}
+                />
+                <button type="submit" className="comment-submit">
+                  发布评论
+                </button>
+              </form>
+
+              {/* 评论列表 */}
+              <div className="comments-list">
+                {comments.length === 0 ? (
+                  <p className="no-comments">暂无评论</p>
+                ) : (
+                  comments.map((comment, index) => (
+                    <div key={index} className="comment-item">
+                      <div className="comment-author">
+                        <div className="comment-avatar">
+                          {comment.user_name ? comment.user_name.charAt(0).toUpperCase() : 'U'}
+                        </div>
+                        <span className="comment-name">{comment.user_name || '匿名用户'}</span>
+                        <span className="comment-time">
+                          {comment.create_time ? new Date(comment.create_time).toLocaleString('zh-CN') : ''}
+                        </span>
+                      </div>
+                      <div className="comment-content">
+                        {comment.content}
+                      </div>
+                    </div>
+                  ))
+                )}
+              </div>
+            </div>
+          )}
+        </div>
+      </main>
+
+      {/* 底部操作栏 */}
+      <footer className="post-footer">
+        <div className="action-bar">
+          <button 
+            onClick={handleLike} 
+            className={`action-button ${liked ? 'liked' : ''}`}
+          >
+            <ThumbsUp size={20} />
+            <span>{likeCount}</span>
+          </button>
+          
+          <button 
+            onClick={() => setShowComments(!showComments)}
+            className="action-button"
+          >
+            <MessageCircle size={20} />
+            <span>评论</span>
+          </button>
+          
+          <button onClick={handleShare} className="action-button">
+            <Share2 size={20} />
+            <span>分享</span>
+          </button>
+          
+          <button 
+            onClick={handleBookmark} 
+            className={`action-button ${bookmarked ? 'bookmarked' : ''}`}
+          >
+            <BookmarkPlus size={20} />
+            <span>收藏</span>
+          </button>
+        </div>
+      </footer>
+    </div>
+  )
+}
diff --git a/Merge/front/src/components/Sidebar.jsx b/Merge/front/src/components/Sidebar.jsx
index 92bc8f1..e35db63 100644
--- a/Merge/front/src/components/Sidebar.jsx
+++ b/Merge/front/src/components/Sidebar.jsx
@@ -7,6 +7,8 @@
   Activity,
   Users,
   ChevronDown,
+  Search,
+  Upload,
 } from 'lucide-react'
 import '../App.css'
 
@@ -24,6 +26,7 @@
       { id: 'fans',     label: '粉丝数据', path: '/dashboard/fans'     },
     ]
   },
+  { id: 'upload-jwlll', label: '智能发布', icon: Upload, path: '/upload-jwlll' },
   // { id: 'activity', label: '活动中心', icon: Activity, path: '/activity' },
   // { id: 'notes',    label: '笔记灵感', icon: BookOpen, path: '/notes'    },
   // { id: 'creator',  label: '创作学院', icon: Users,    path: '/creator'  },
diff --git a/Merge/front/src/components/UploadPageJWLLL.jsx b/Merge/front/src/components/UploadPageJWLLL.jsx
new file mode 100644
index 0000000..2d9ee7d
--- /dev/null
+++ b/Merge/front/src/components/UploadPageJWLLL.jsx
@@ -0,0 +1,328 @@
+import React, { useState } from 'react'
+import { Image, Video, Send } from 'lucide-react'
+import { searchAPI } from '../api/search_jwlll'
+import '../style/UploadPage.css'
+
+const categories = [
+  '穿搭','美食','彩妆','影视',
+  '职场','情感','家居','游戏','旅行','健身'
+]
+
+export default function UploadPageJWLLL({ onComplete }) {
+  const [activeTab, setActiveTab] = useState('image')
+  const [isDragOver, setIsDragOver] = useState(false)
+  const [isUploading, setIsUploading] = useState(false)
+  const [uploadedFiles, setUploadedFiles] = useState([])
+  const [uploadProgress, setUploadProgress] = useState(0)
+  
+  // 新增表单字段
+  const [title, setTitle] = useState('')
+  const [content, setContent] = useState('')
+  const [tags, setTags] = useState('')
+  const [category, setCategory] = useState(categories[0])
+  const [isPublishing, setIsPublishing] = useState(false)
+
+  const DEFAULT_USER_ID = '3' // 默认用户ID
+
+  const validateFiles = files => {
+    const imgTypes = ['image/jpeg','image/jpg','image/png','image/webp']
+    const vidTypes = ['video/mp4','video/mov','video/avi']
+    const types = activeTab==='video'? vidTypes : imgTypes
+    const max   = activeTab==='video'? 2*1024*1024*1024 : 32*1024*1024
+
+    const invalid = files.filter(f => !types.includes(f.type) || f.size > max)
+    if (invalid.length) {
+      alert(`发现 ${invalid.length} 个无效文件,请检查文件格式和大小`)
+      return false
+    }
+    return true
+  }
+
+  const simulateUpload = files => {
+    setIsUploading(true)
+    setUploadProgress(0)
+    setUploadedFiles(files)
+    const iv = setInterval(() => {
+      setUploadProgress(p => {
+        if (p >= 100) {
+          clearInterval(iv)
+          setIsUploading(false)
+          if (typeof onComplete === 'function') {
+            onComplete(files)
+          }
+          return 100
+        }
+        return p + 10
+      })
+    }, 200)
+  }
+
+  const handleFileUpload = () => {
+    if (isUploading) return
+    const input = document.createElement('input')
+    input.type = 'file'
+    input.accept = activeTab==='video'? 'video/*' : 'image/*'
+    input.multiple = activeTab==='image'
+    input.onchange = e => {
+      const files = Array.from(e.target.files)
+      if (files.length > 0 && validateFiles(files)) simulateUpload(files)
+    }
+    input.click()
+  }
+
+  const handleDragOver  = e => { e.preventDefault(); e.stopPropagation(); setIsDragOver(true) }
+  const handleDragLeave = e => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false) }
+  const handleDrop      = e => {
+    e.preventDefault(); e.stopPropagation(); setIsDragOver(false)
+    if (isUploading) return
+    const files = Array.from(e.dataTransfer.files)
+    if (files.length > 0 && validateFiles(files)) simulateUpload(files)
+  }
+
+  const clearFiles = () => setUploadedFiles([])
+  const removeFile = idx => setUploadedFiles(f => f.filter((_,i) => i!==idx))
+
+  // 发布帖子
+  const handlePublish = async () => {
+    if (!title.trim()) {
+      alert('请输入标题')
+      return
+    }
+    if (!content.trim()) {
+      alert('请输入内容')
+      return
+    }
+
+    setIsPublishing(true)
+    try {
+      const postData = {
+        user_id: DEFAULT_USER_ID,
+        title: title.trim(),
+        content: content.trim(),
+        tags: tags.split(',').map(t => t.trim()).filter(t => t),
+        category: category,
+        type: activeTab === 'video' ? 'video' : 'image',
+        media_files: uploadedFiles.map(f => f.name) // 实际项目中应该是上传后的URL
+      }
+
+      await searchAPI.uploadPost(postData)
+      alert('发布成功!')
+      
+      // 清空表单
+      setTitle('')
+      setContent('')
+      setTags('')
+      setUploadedFiles([])
+      setActiveTab('image')
+      
+    } catch (error) {
+      console.error('发布失败:', error)
+      alert('发布失败,请重试')
+    } finally {
+      setIsPublishing(false)
+    }
+  }
+
+  return (
+    <div className="upload-page-jwlll">
+      <div className="upload-tabs">
+        <button
+          className={`upload-tab${activeTab==='video'?' active':''}`}
+          onClick={() => setActiveTab('video')}
+        >上传视频</button>
+        <button
+          className={`upload-tab${activeTab==='image'?' active':''}`}
+          onClick={() => setActiveTab('image')}
+        >上传图文</button>
+      </div>
+
+      {/* 内容表单 */}
+      <div className="content-form">
+        <div className="form-group">
+          <label htmlFor="title">标题</label>
+          <input
+            id="title"
+            type="text"
+            value={title}
+            onChange={(e) => setTitle(e.target.value)}
+            placeholder="请输入标题..."
+            className="form-input"
+            maxLength={100}
+          />
+        </div>
+
+        <div className="form-group">
+          <label htmlFor="content">内容</label>
+          <textarea
+            id="content"
+            value={content}
+            onChange={(e) => setContent(e.target.value)}
+            placeholder="请输入内容..."
+            className="form-textarea"
+            rows={4}
+            maxLength={1000}
+          />
+        </div>
+
+        <div className="form-row">
+          <div className="form-group">
+            <label htmlFor="category">分类</label>
+            <select
+              id="category"
+              value={category}
+              onChange={(e) => setCategory(e.target.value)}
+              className="form-select"
+            >
+              {categories.map(cat => (
+                <option key={cat} value={cat}>{cat}</option>
+              ))}
+            </select>
+          </div>
+
+          <div className="form-group">
+            <label htmlFor="tags">标签</label>
+            <input
+              id="tags"
+              type="text"
+              value={tags}
+              onChange={(e) => setTags(e.target.value)}
+              placeholder="用逗号分隔多个标签..."
+              className="form-input"
+            />
+          </div>
+        </div>
+      </div>
+
+      {/* 文件上传区域 */}
+      <div
+        className={`upload-area${isDragOver?' drag-over':''}`}
+        onDragOver={handleDragOver}
+        onDragLeave={handleDragLeave}
+        onDrop={handleDrop}
+      >
+        <div className="upload-icon">
+          {activeTab==='video'? <Video/> : <Image/>}
+        </div>
+        <h2 className="upload-title">
+          {activeTab==='video'
+            ? '拖拽视频到此处或点击上传'
+            : '拖拽图片到此处或点击上传'
+          }
+        </h2>
+        <p className="upload-subtitle">(需支持上传格式)</p>
+        <button
+          className={`upload-btn${isUploading?' uploading':''}`}
+          onClick={handleFileUpload}
+          disabled={isUploading}
+        >
+          {isUploading
+            ? `上传中... ${uploadProgress}%`
+            : activeTab==='video'
+              ? '上传视频'
+              : '上传图片'
+          }
+        </button>
+
+        {isUploading && (
+          <div className="progress-container">
+            <div className="progress-bar">
+              <div
+                className="progress-fill"
+                style={{ width: `${uploadProgress}%` }}
+              />
+            </div>
+            <div className="progress-text">{uploadProgress}%</div>
+          </div>
+        )}
+      </div>
+
+      {uploadedFiles.length > 0 && (
+        <div className="file-preview-area">
+          <div className="preview-header">
+            <h3 className="preview-title">已上传文件 ({uploadedFiles.length})</h3>
+            <button className="clear-files-btn" onClick={clearFiles}>
+              清除所有
+            </button>
+          </div>
+          <div className="file-grid">
+            {uploadedFiles.map((file, i) => (
+              <div key={i} className="file-item">
+                <button
+                  className="remove-file-btn"
+                  onClick={() => removeFile(i)}
+                  title="删除文件"
+                >×</button>
+                {file.type.startsWith('image/') ? (
+                  <div className="file-thumbnail">
+                    <img src={URL.createObjectURL(file)} alt={file.name} />
+                  </div>
+                ) : (
+                  <div className="file-thumbnail video-thumbnail">
+                    <Video size={24} />
+                  </div>
+                )}
+                <div className="file-info">
+                  <div className="file-name" title={file.name}>
+                    {file.name.length > 20
+                      ? file.name.slice(0,17) + '...'
+                      : file.name
+                    }
+                  </div>
+                  <div className="file-size">
+                    {(file.size/1024/1024).toFixed(2)} MB
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      )}
+
+      {/* 发布按钮 */}
+      <div className="publish-section">
+        <button
+          className={`publish-btn${isPublishing?' publishing':''}`}
+          onClick={handlePublish}
+          disabled={isPublishing || !title.trim() || !content.trim()}
+        >
+          <Send size={20} />
+          {isPublishing ? '发布中...' : '发布'}
+        </button>
+      </div>
+
+      <div className="upload-info fade-in">
+        {activeTab==='image' ? (
+          <>
+            <div className="info-item">
+              <h3 className="info-title">图片大小</h3>
+              <p className="info-desc">最大32MB</p>
+            </div>
+            <div className="info-item">
+              <h3 className="info-title">图片格式</h3>
+              <p className="info-desc">png/jpg/jpeg/webp</p>
+            </div>
+            <div className="info-item">
+              <h3 className="info-title">分辨率</h3>
+              <p className="info-desc">建议720×960及以上</p>
+            </div>
+          </>
+        ) : (
+          <>
+            <div className="info-item">
+              <h3 className="info-title">视频大小</h3>
+              <p className="info-desc">最大2GB,时长≤5分钟</p>
+            </div>
+            <div className="info-item">
+              <h3 className="info-title">视频格式</h3>
+              <p className="info-desc">mp4/mov</p>
+            </div>
+            <div className="info-item">
+              <h3 className="info-title">分辨率</h3>
+              <p className="info-desc">建议720P及以上</p>
+            </div>
+          </>
+        )}
+      </div>
+    </div>
+  )
+}