修改前端推荐搜素样式,链接推荐搜索前后端

Change-Id: I76d3be2a2cb6f553fc6c4512e11b6de3341ad0c4
diff --git a/JWLLL/API_front/README.md b/JWLLL/API_front/README.md
new file mode 100644
index 0000000..6f35930
--- /dev/null
+++ b/JWLLL/API_front/README.md
@@ -0,0 +1,185 @@
+# 小红书内容创作平台
+
+这是一个基于 React + Vite 构建的小红书内容创作平台界面,完全复制了小红书官方创作服务平台的设计和功能。
+
+## 功能特性 ✨
+
+### 🎨 界面设计
+- **完全还原小红书官方设计**:精确复制了小红书创作服务平台的视觉风格
+- **响应式布局**:支持桌面端和移动端适配
+- **现代化UI**:使用 Lucide React 图标库,提供清晰美观的界面
+
+### 📤 上传功能
+- **双模式上传**:支持图片上传和视频上传两种模式
+- **拖拽上传**:支持文件拖拽到上传区域
+- **点击上传**:点击按钮选择文件上传
+- **文件验证**:
+  - 图片:支持 JPEG、JPG、PNG、WebP 格式,最大 32MB
+  - 视频:支持 MP4、MOV、AVI 格式,最大 2GB
+- **实时进度显示**:带有动画效果的上传进度条
+
+### 🖼️ 文件管理
+- **文件预览**:上传后实时显示文件缩略图
+- **文件信息**:显示文件名、大小等详细信息
+- **批量管理**:支持单个删除和批量清除
+- **文件计数**:实时显示已上传文件数量
+
+### 🎯 交互体验
+- **拖拽反馈**:拖拽时提供视觉反馈效果
+- **加载状态**:上传过程中的加载动画
+- **操作提示**:完成上传后的成功提示
+- **悬停效果**:按钮和文件项的悬停交互
+
+### 🗂️ 导航系统
+- **侧边栏导航**:完整的功能菜单
+- **可展开子菜单**:数据看板等功能的子选项
+- **活跃状态**:当前选中页面的高亮显示
+- **固定布局**:头部和侧边栏固定定位
+
+## 技术栈 🛠️
+
+- **前端框架**:React 19.1.0
+- **构建工具**:Vite 6.0.5
+- **图标库**:Lucide React 0.468.0
+- **样式**:纯 CSS(无预处理器)
+- **开发语言**:JavaScript + JSX
+
+## 安装运行 🚀
+
+1. **安装依赖**
+   ```bash
+   npm install
+   ```
+
+2. **启动开发服务器**
+   ```bash
+   npm run dev
+   ```
+
+3. **打开浏览器**
+   ```
+   http://localhost:5173
+   ```
+
+4. **构建生产版本**
+   ```bash
+   npm run build
+   ```
+
+## 项目结构 📁
+
+```
+发布页面/
+├── public/              # 静态资源
+│   └── vite.svg        # Vite 图标
+├── src/
+│   ├── App.jsx         # 主应用组件
+│   ├── App.css         # 主样式文件
+│   ├── index.css       # 全局样式
+│   └── main.jsx        # 应用入口
+├── index.html          # HTML 入口文件
+├── package.json        # 项目配置
+├── vite.config.js      # Vite 配置
+└── README.md          # 项目说明
+```
+
+## 核心功能实现 💡
+
+### 文件上传处理
+```javascript
+const handleFileUpload = () => {
+  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()
+}
+```
+
+### 拖拽上传实现
+```javascript
+const handleDrop = (e) => {
+  e.preventDefault()
+  e.stopPropagation()
+  setIsDragOver(false)
+  
+  const files = Array.from(e.dataTransfer.files)
+  if (files.length > 0 && validateFiles(files)) {
+    simulateUpload(files)
+  }
+}
+```
+
+### 文件验证机制
+```javascript
+const validateFiles = (files) => {
+  const validImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
+  const validVideoTypes = ['video/mp4', 'video/mov', 'video/avi']
+  
+  const validTypes = activeTab === 'video' ? validVideoTypes : validImageTypes
+  const maxSize = activeTab === 'video' ? 2 * 1024 * 1024 * 1024 : 32 * 1024 * 1024
+  
+  return files.every(file => 
+    validTypes.includes(file.type) && file.size <= maxSize
+  )
+}
+```
+
+## 样式特色 🎨
+
+### 响应式设计
+- 桌面端:固定侧边栏布局
+- 移动端:隐藏侧边栏,堆叠布局
+- 自适应文件网格:根据屏幕大小调整列数
+
+### 动画效果
+- 拖拽时的放大效果
+- 进度条的流光动画
+- 文件项的悬停过渡
+- 页面切换的淡入效果
+
+### 色彩方案
+- 主色调:#ff4757(小红书红)
+- 背景色:#f5f7fa(浅灰蓝)
+- 文字色:#333(深灰)
+- 边框色:#e8eaed(浅灰)
+
+## 待扩展功能 🔮
+
+- **后端集成**:连接真实的文件上传 API
+- **用户认证**:登录注册功能
+- **内容编辑**:笔记内容编辑器
+- **数据统计**:真实的数据看板功能
+- **社交功能**:评论、点赞等互动功能
+
+## 开发说明 📝
+
+这个项目完全基于前端实现,所有的上传功能都是模拟的。文件预览使用了 `URL.createObjectURL()` 来生成本地预览链接。在实际部署时,需要:
+
+1. 集成后端文件上传 API
+2. 实现用户认证系统
+3. 添加数据持久化
+4. 优化性能和安全性
+
+## 浏览器兼容性 🌐
+
+- Chrome 90+
+- Firefox 88+
+- Safari 14+
+- Edge 90+
+
+## 许可证 📄
+
+MIT License
+
+---
+
+**注意**:本项目仅用于学习和演示目的,请遵守相关法律法规和平台使用条款。
diff --git a/JWLLL/API_front/index.html b/JWLLL/API_front/index.html
new file mode 100644
index 0000000..b919940
--- /dev/null
+++ b/JWLLL/API_front/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>小红书创作服务平台</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>
diff --git a/JWLLL/API_front/package.json b/JWLLL/API_front/package.json
new file mode 100644
index 0000000..b1d8b25
--- /dev/null
+++ b/JWLLL/API_front/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "xiaohongshu-creator-platform",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "lucide-react": "^0.468.0",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "react-router-dom": "^7.6.2"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-react": "^4.3.4",
+    "vite": "^6.0.5"
+  }
+}
diff --git a/JWLLL/API_front/public/vite.svg b/JWLLL/API_front/public/vite.svg
new file mode 100644
index 0000000..ee9fada
--- /dev/null
+++ b/JWLLL/API_front/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
diff --git a/JWLLL/API_front/src/App.css b/JWLLL/API_front/src/App.css
new file mode 100644
index 0000000..8fdd8d7
--- /dev/null
+++ b/JWLLL/API_front/src/App.css
@@ -0,0 +1,585 @@
+.app {
+  display: flex;
+  min-height: 100vh;
+  background-color: #f5f7fa;
+}
+
+/* Header */
+.header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 60px;
+  background: #fff;
+  border-bottom: 1px solid #e8eaed;
+  display: flex;
+  align-items: center;
+  padding: 0 20px;
+  z-index: 1000;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.logo {
+  background: #ff4757;
+  color: white;
+  padding: 6px 12px;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: bold;
+}
+
+.header-title {
+  font-size: 18px;
+  font-weight: 500;
+  color: #333;
+}
+
+.header-right {
+  margin-left: auto;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.user-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #666;
+  font-size: 14px;
+}
+
+/* Sidebar */
+.sidebar {
+  position: fixed;
+  left: 0;
+  top: 60px;
+  width: 200px;
+  height: calc(100vh - 60px);
+  background: #fff;
+  border-right: 1px solid #e8eaed;
+  overflow-y: auto;
+  z-index: 999;
+}
+
+.publish-btn {
+  margin: 16px;
+  background: #ff4757;
+  color: white;
+  padding: 10px 16px;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  text-align: center;
+  transition: background 0.2s;
+}
+
+.publish-btn:hover {
+  background: #ff3742;
+}
+
+.nav-menu {
+  padding: 0;
+  list-style: none;
+}
+
+.nav-item {
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.nav-link {
+  display: flex;
+  align-items: center;
+  padding: 12px 20px;
+  color: #333;
+  font-size: 14px;
+  transition: all 0.2s;
+  gap: 8px;
+}
+
+.nav-link:hover {
+  background: #f8f9fa;
+  color: #ff4757;
+}
+
+.nav-link.active {
+  background: linear-gradient(135deg, #ff4757, #ff6b7a);
+  color: white;
+  font-weight: 500;
+}
+
+.nav-link.active .lucide {
+  color: white;
+}
+
+.nav-submenu {
+  padding-left: 20px;
+  background: #fafafa;
+}
+
+.nav-submenu .nav-link {
+  padding: 8px 20px;
+  font-size: 13px;
+  color: #666;
+}
+
+.nav-submenu .nav-link:hover {
+  color: #ff4757;
+}
+
+/* Main Content */
+.main-content {
+  margin-left: 200px;
+  padding-top: 60px;
+  flex: 1;
+  min-height: 100vh;
+}
+
+.content-wrapper {
+  padding: 20px;
+  /* 原来是 max-width:1200px; */
+  max-width: none;      /* 或者直接注释掉这一行 */
+  width: auto;          /* 确保它能撑满父级 */
+  margin: 0;            /* 取消水平 auto 居中 */
+}
+
+/* Upload Area */
+.upload-tabs {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 30px;
+  border-bottom: 1px solid #e8eaed;
+}
+
+.upload-tab {
+  padding: 12px 0;
+  font-size: 16px;
+  color: #666;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+  transition: all 0.2s;
+}
+
+.upload-tab.active {
+  color: #ff4757;
+  border-bottom-color: #ff4757;
+  font-weight: 500;
+}
+
+.upload-area {
+  background: #fff;
+  border-radius: 8px;
+  padding: 80px 40px;
+  text-align: center;
+  border: 2px dashed #ddd;
+  margin-bottom: 40px;
+  transition: all 0.2s;
+  min-height: 300px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+}
+
+.upload-area:hover {
+  border-color: #ff4757;
+  background: #fff8f8;
+}
+
+.upload-area.drag-over {
+  border-color: #ff4757;
+  background: #fff0f0;
+  transform: scale(1.02);
+}
+
+.upload-icon {
+  width: 100px;
+  height: 100px;
+  margin: 0 auto 30px;
+  background: #f8f9fa;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 40px;
+  color: #ccc;
+  transition: all 0.3s ease;
+}
+
+.upload-area:hover .upload-icon {
+  background: #ff475710;
+  color: #ff4757;
+  transform: scale(1.1);
+}
+
+.upload-area.drag-over .upload-icon {
+  background: #ff475720;
+  color: #ff4757;
+  transform: scale(1.2);
+}
+
+.upload-title {
+  font-size: 20px;
+  color: #333;
+  margin-bottom: 12px;
+  font-weight: 500;
+}
+
+.upload-subtitle {
+  font-size: 14px;
+  color: #999;
+  margin-bottom: 30px;
+}
+
+.upload-btn {
+  background: #ff4757;
+  color: white;
+  padding: 14px 28px;
+  border-radius: 6px;
+  font-size: 16px;
+  font-weight: 500;
+  transition: background 0.2s;
+  min-width: 120px;
+}
+
+.upload-btn:hover:not(:disabled) {
+  background: #ff3742;
+}
+
+.upload-btn:disabled {
+  background: #ccc;
+  cursor: not-allowed;
+}
+
+.upload-btn.uploading {
+  background: #ff4757;
+  opacity: 0.8;
+}
+
+/* File Preview */
+.file-preview-area {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 40px;
+  border: 1px solid #e8eaed;
+}
+
+/* Preview Header */
+.preview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.preview-title {
+  font-size: 16px;
+  color: #333;
+  margin-bottom: 16px;
+  font-weight: 500;
+}
+
+.clear-files-btn {
+  background: #ff4757;
+  color: white;
+  padding: 6px 12px;
+  border-radius: 4px;
+  font-size: 12px;
+  transition: background 0.2s;
+}
+
+.clear-files-btn:hover {
+  background: #ff3742;
+}
+
+.file-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  gap: 16px;
+}
+
+.file-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 12px;
+  border: 1px solid #f0f0f0;
+  border-radius: 6px;
+  transition: all 0.2s ease;
+  position: relative;
+}
+
+.file-item:hover {
+  border-color: #ff4757;
+  box-shadow: 0 2px 8px rgba(255, 71, 87, 0.1);
+}
+
+.file-item:hover .remove-file-btn {
+  opacity: 1;
+}
+
+.remove-file-btn {
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  background: rgba(255, 71, 87, 0.8);
+  color: white;
+  border-radius: 50%;
+  width: 20px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  font-weight: bold;
+  opacity: 0;
+  transition: all 0.2s;
+}
+
+.file-thumbnail {
+  width: 80px;
+  height: 80px;
+  border-radius: 6px;
+  overflow: hidden;
+  margin-bottom: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f8f9fa;
+}
+
+.file-thumbnail img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.video-thumbnail {
+  color: #666;
+}
+
+.file-info {
+  text-align: center;
+  width: 100%;
+}
+
+.file-name {
+  font-size: 12px;
+  color: #333;
+  margin-bottom: 4px;
+  font-weight: 500;
+}
+
+.file-size {
+  font-size: 11px;
+  color: #999;
+}
+
+/* Upload Progress */
+.progress-container {
+  margin-top: 20px;
+  width: 100%;
+  max-width: 400px;
+}
+
+.progress-bar {
+  width: 100%;
+  height: 8px;
+  background-color: #f0f0f0;
+  border-radius: 4px;
+  overflow: hidden;
+  margin-bottom: 8px;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #ff4757, #ff6b7a);
+  border-radius: 4px;
+  transition: width 0.3s ease;
+  position: relative;
+}
+
+.progress-fill::after {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+  animation: shimmer 1.5s infinite;
+}
+
+@keyframes shimmer {
+  0% { transform: translateX(-100%); }
+  100% { transform: translateX(100%); }
+}
+
+.progress-text {
+  text-align: center;
+  font-size: 12px;
+  color: #666;
+  font-weight: 500;
+}
+
+/* Upload Info */
+.upload-info {
+  display: flex;
+  gap: 60px;
+  justify-content: center;
+  margin-top: 40px;
+  padding: 20px;
+  opacity: 1;
+  transition: opacity 0.3s ease;
+}
+
+.upload-info.fade-in {
+  animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.info-item {
+  text-align: center;
+  flex: 1;
+  max-width: 300px;
+}
+
+.info-title {
+  font-size: 16px;
+  color: #333;
+  margin-bottom: 12px;
+  font-weight: 500;
+}
+
+.info-desc {
+  font-size: 13px;
+  color: #666;
+  line-height: 1.6;
+}
+
+/* Page Content Styles */
+.page-content {
+  padding: 40px;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin: 20px 0;
+  min-height: 500px;
+}
+
+.page-header {
+  margin-bottom: 40px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #e8eaed;
+}
+
+.page-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+  margin: 0;
+}
+
+.page-body {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400px;
+}
+
+.placeholder-content {
+  text-align: center;
+  max-width: 400px;
+}
+
+.placeholder-icon {
+  color: #ff4757;
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: center;
+}
+
+.placeholder-title {
+  font-size: 20px;
+  font-weight: 500;
+  color: #333;
+  margin: 0 0 15px 0;
+}
+
+.placeholder-desc {
+  font-size: 14px;
+  color: #666;
+  line-height: 1.6;
+  margin: 0;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+  .sidebar {
+    transform: translateX(-100%);
+    transition: transform 0.3s;
+  }
+  
+  .main-content {
+    margin-left: 0;
+  }
+  
+  .header-title {
+    display: none;
+  }
+  
+  .upload-area {
+    padding: 60px 20px;
+    margin: 0 10px 30px;
+  }
+  
+  .upload-info {
+    flex-direction: column;
+    gap: 30px;
+    padding: 10px;
+  }
+  
+  .content-wrapper {
+    padding: 15px;
+  }
+  
+  .upload-tabs {
+    gap: 15px;
+  }
+  
+  .page-content {
+    padding: 20px;
+    margin: 10px;
+  }
+  
+  .page-title {
+    font-size: 20px;
+  }
+  
+  .placeholder-title {
+    font-size: 18px;
+  }
+  
+  .placeholder-desc {
+    font-size: 13px;
+  }
+}
diff --git a/JWLLL/API_front/src/App.jsx b/JWLLL/API_front/src/App.jsx
new file mode 100644
index 0000000..9a79c5c
--- /dev/null
+++ b/JWLLL/API_front/src/App.jsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import { BrowserRouter as Router } from 'react-router-dom'
+import Header from './components/Header'
+import Sidebar from './components/Sidebar'
+import AppRouter from './router'
+
+import './App.css'
+
+export default function App() {
+  return (
+    <Router>
+      <div className="app">
+        <Header />
+        <Sidebar />
+        <main className="main-content">
+          <div className="content-wrapper">
+            <AppRouter />
+          </div>
+        </main>
+      </div>
+    </Router>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/components/Header.jsx b/JWLLL/API_front/src/components/Header.jsx
new file mode 100644
index 0000000..60a50b7
--- /dev/null
+++ b/JWLLL/API_front/src/components/Header.jsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import { User } from 'lucide-react'
+import '../App.css' // 或者单独的 Header.css
+
+export default function Header() {
+  return (
+    <header className="header">
+      <div className="header-left">
+        <div className="logo">小红书</div>
+        <h1 className="header-title">创作服务平台</h1>
+      </div>
+      <div className="header-right">
+        <div className="user-info">
+          <User size={16} />
+          <span>小红薯63081EA1</span>
+        </div>
+      </div>
+    </header>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/components/HomeFeed.jsx b/JWLLL/API_front/src/components/HomeFeed.jsx
new file mode 100644
index 0000000..0b17b1f
--- /dev/null
+++ b/JWLLL/API_front/src/components/HomeFeed.jsx
@@ -0,0 +1,212 @@
+import React, { useState, useEffect } from 'react'
+import { ThumbsUp } from 'lucide-react'
+import '../style/HomeFeed.css'
+
+const categories = [
+  '推荐','穿搭','美食','彩妆','影视',
+  '职场','情感','家居','游戏','旅行','健身'
+]
+
+const recommendModes = [
+  { label: '标签推荐', value: 'tag' },
+  { label: '协同过滤推荐', value: 'cf' }
+]
+
+const DEFAULT_USER_ID = '3' // 确保数据库有此用户
+const DEFAULT_TAGS = ['美食','影视','穿搭'] // 可根据实际数据库调整
+
+export default function HomeFeed() {
+  const [activeCat, setActiveCat] = useState('推荐')
+  const [items, setItems] = useState([])
+  const [search, setSearch] = useState('')
+  const [loading, setLoading] = useState(false)
+  const [recMode, setRecMode] = useState('tag')
+  const [userTags, setUserTags] = useState([])
+  const [recCFNum, setRecCFNum] = useState(20)
+
+  // 首次进入首页自动推荐内容
+  useEffect(() => {
+    if (activeCat === '推荐') {
+      fetchUserTagsAndRecommend()
+    } else {
+      fetchContent()
+    }
+    // eslint-disable-next-line
+  }, [])
+
+  // 切换推荐模式或分类时刷新内容
+  useEffect(() => {
+    if (activeCat === '推荐') {
+      fetchUserTagsAndRecommend()
+    } else {
+      fetchContent()
+    }
+    // eslint-disable-next-line
+  }, [activeCat, recMode])
+
+  // 获取用户兴趣标签后再推荐
+  const fetchUserTagsAndRecommend = async () => {
+    setLoading(true)
+    let tags = []
+    try {
+      const res = await fetch(`/user_tags?user_id=${DEFAULT_USER_ID}`)
+      const data = await res.json()
+      tags = Array.isArray(data.tags) && data.tags.length > 0 ? data.tags : DEFAULT_TAGS
+      setUserTags(tags)
+    } catch {
+      tags = DEFAULT_TAGS
+      setUserTags(tags)
+    }
+    if (recMode === 'tag') {
+      await fetchTagRecommend(tags)
+    } else {
+      await fetchCFRecommend()
+    }
+    setLoading(false)
+  }
+
+  const fetchContent = async (keyword = '') => {
+    setLoading(true)
+    try {
+      const res = await fetch('/search', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ keyword: keyword || activeCat, category: activeCat === '推荐' ? undefined : activeCat })
+      })
+      const data = await res.json()
+      setItems(data.results || [])
+    } catch (e) {
+      setItems([])
+    }
+    setLoading(false)
+  }
+
+  // 标签推荐
+  const fetchTagRecommend = async (tags) => {
+    setLoading(true)
+    try {
+      const res = await fetch('/recommend_tags', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ user_id: DEFAULT_USER_ID, tags })
+      })
+      const data = await res.json()
+      setItems(data.recommendations || [])
+    } catch (e) {
+      setItems([])
+    }
+    setLoading(false)
+  }
+
+  // 协同过滤推荐
+  const fetchCFRecommend = async (topN = recCFNum) => {
+    setLoading(true)
+    try {
+      const res = await fetch('/user_based_recommend', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ user_id: DEFAULT_USER_ID, top_n: topN })
+      })
+      const data = await res.json()
+      setItems(data.recommendations || [])
+    } catch (e) {
+      setItems([])
+    }
+    setLoading(false)
+  }
+
+  const handleSearch = e => {
+    e.preventDefault()
+    fetchContent(search)
+  }
+
+  return (
+    <div className="home-feed">
+      {/* 推荐模式切换,仅在推荐页显示 */}
+      {activeCat === '推荐' && (
+        <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); setSearch('') }}
+          >
+            {cat}
+          </button>
+        ))}
+      </nav>
+      {/* 瀑布流卡片区 */}
+      <div className="feed-grid">
+        {loading ? <div style={{padding:32}}>加载中...</div> :
+          items.length === 0 ? <div style={{padding:32, color:'#aaa'}}>暂无推荐内容</div> :
+          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-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.heat || 0}</span>
+                </div>
+              </div>
+            </div>
+          ))}
+      </div>
+    </div>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/components/PlaceholderPage.jsx b/JWLLL/API_front/src/components/PlaceholderPage.jsx
new file mode 100644
index 0000000..b290eb4
--- /dev/null
+++ b/JWLLL/API_front/src/components/PlaceholderPage.jsx
@@ -0,0 +1,55 @@
+import React from 'react'
+import {
+  Home,
+  BookOpen,
+  Activity,
+  Users
+} from 'lucide-react'
+import '../App.css' // 或 PlaceholderPage.css
+
+const icons = {
+  home: Home,
+  notebooks: BookOpen,
+  activity: Activity,
+  notes: BookOpen,
+  creator: Users,
+  journal: BookOpen,
+}
+
+const titles = {
+  home:      '欢迎来到小红书创作平台',
+  notebooks: '笔记管理功能开发中',
+  activity:  '活动中心功能开发中',
+  notes:     '笔记灵感功能开发中',
+  creator:   '创作学院功能开发中',
+  journal:   '创作日刊功能开发中',
+}
+
+const descs = {
+  home:      '在这里您可以管理您的创作内容,查看数据分析,获取创作灵感。',
+  notebooks: '这里将显示您的所有笔记,支持编辑、删除、分类等操作。',
+  activity:  '这里将展示最新的平台活动,让您参与更多有趣的创作活动。',
+  notes:     '这里将为您提供创作灵感和写作建议,帮助您创作更好的内容。',
+  creator:   '这里将提供创作技巧教学和平台规则说明,助您成为优秀创作者。',
+  journal:   '这里将展示创作相关的最新资讯和平台动态。',
+}
+
+export default function PlaceholderPage({ pageId }) {
+  const Icon = icons[pageId] || Home
+  return (
+    <div className="page-content">
+      <div className="page-header">
+        <h1 className="page-title">{titles[pageId]}</h1>
+      </div>
+      <div className="page-body">
+        <div className="placeholder-content">
+          <div className="placeholder-icon">
+            <Icon size={48} />
+          </div>
+          <h3 className="placeholder-title">{titles[pageId]}</h3>
+          <p className="placeholder-desc">{descs[pageId]}</p>
+        </div>
+      </div>
+    </div>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/components/Sidebar.jsx b/JWLLL/API_front/src/components/Sidebar.jsx
new file mode 100644
index 0000000..b6acad5
--- /dev/null
+++ b/JWLLL/API_front/src/components/Sidebar.jsx
@@ -0,0 +1,95 @@
+import React, { useState, useEffect } from 'react'
+import { NavLink, useLocation, useNavigate } from 'react-router-dom'
+import {
+  Home,
+  BookOpen,
+  BarChart3,
+  Activity,
+  Users,
+  ChevronDown,
+} from 'lucide-react'
+
+
+const menuItems = [
+  { id: 'home',      label: '首页',       icon: Home,      path: '/home' },
+  { id: 'notebooks', label: '笔记管理',   icon: BookOpen,  path: '/notebooks' },
+  {
+    id: 'dashboard',
+    label: '数据看板',
+    icon: BarChart3,
+    path: '/dashboard',
+    submenu: [
+      { id: 'overview', label: '账号概况', path: '/dashboard/overview' },
+      { id: 'content',  label: '内容分析', path: '/dashboard/content'  },
+      { id: 'fans',     label: '粉丝数据', path: '/dashboard/fans'     },
+    ]
+  },
+  { id: 'activity', label: '活动中心', icon: Activity, path: '/activity' },
+  { id: 'notes',    label: '笔记灵感', icon: BookOpen, path: '/notes'    },
+  { id: 'creator',  label: '创作学院', icon: Users,    path: '/creator'  },
+  { id: 'journal',  label: '创作日刊', icon: BookOpen, path: '/journal'  },
+]
+
+export default function Sidebar() {
+  const [expandedMenu, setExpandedMenu] = useState(null)
+  const location = useLocation()
+  const navigate = useNavigate()
+
+  useEffect(() => {
+    if (location.pathname.startsWith('/dashboard')) {
+      setExpandedMenu('dashboard')
+    }
+  }, [location.pathname])
+
+  const toggleMenu = item => {
+    if (item.submenu) {
+      setExpandedMenu(expandedMenu === item.id ? null : item.id)
+    } else {
+      navigate(item.path)
+      setExpandedMenu(null)
+    }
+  }
+
+  return (
+    <aside className="sidebar">
+      <button className="publish-btn">发布笔记</button>
+      <nav className="nav-menu">
+        {menuItems.map(item => (
+          <div key={item.id} className="nav-item">
+            <a
+              href="#"
+              className={`nav-link${location.pathname === item.path ? ' active' : ''}`}
+              onClick={e => { e.preventDefault(); toggleMenu(item) }}
+            >
+              <item.icon size={16} />
+              <span>{item.label}</span>
+              {item.submenu && (
+                <ChevronDown
+                  size={16}
+                  style={{
+                    marginLeft: 'auto',
+                    transform: expandedMenu === item.id ? 'rotate(180deg)' : 'rotate(0deg)',
+                    transition: 'transform 0.3s ease'
+                  }}
+                />
+              )}
+            </a>
+            {item.submenu && expandedMenu === item.id && (
+              <div className="nav-submenu">
+                {item.submenu.map(sub => (
+                  <NavLink
+                    key={sub.id}
+                    to={sub.path}
+                    className={({ isActive }) => `nav-link${isActive ? ' active' : ''}`}
+                  >
+                    {sub.label}
+                  </NavLink>
+                ))}
+              </div>
+            )}
+          </div>
+        ))}
+      </nav>
+    </aside>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/components/UploadPage.jsx b/JWLLL/API_front/src/components/UploadPage.jsx
new file mode 100644
index 0000000..e6db861
--- /dev/null
+++ b/JWLLL/API_front/src/components/UploadPage.jsx
@@ -0,0 +1,200 @@
+import React, { useState } from 'react'
+import { Image, Video } from 'lucide-react'
+
+
+export default function UploadPage() {
+  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 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)
+          alert(`成功上传了 ${files.length} 个文件`)
+          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))
+
+  return (
+    <>
+      <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={`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="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>
+    </>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/index.css b/JWLLL/API_front/src/index.css
new file mode 100644
index 0000000..72c144a
--- /dev/null
+++ b/JWLLL/API_front/src/index.css
@@ -0,0 +1,29 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background-color: #f5f7fa;
+}
+
+button {
+  border: none;
+  background: none;
+  cursor: pointer;
+  font-family: inherit;
+}
+
+a {
+  text-decoration: none;
+  color: inherit;
+}
+
+#root {
+  width: 100%;
+  min-height: 100vh;
+}
diff --git a/JWLLL/API_front/src/main.jsx b/JWLLL/API_front/src/main.jsx
new file mode 100644
index 0000000..b9a1a6d
--- /dev/null
+++ b/JWLLL/API_front/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+  <StrictMode>
+    <App />
+  </StrictMode>,
+)
diff --git a/JWLLL/API_front/src/router/index.jsx b/JWLLL/API_front/src/router/index.jsx
new file mode 100644
index 0000000..64191ac
--- /dev/null
+++ b/JWLLL/API_front/src/router/index.jsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import { Routes, Route, Navigate } from 'react-router-dom'
+import UploadPage from '../components/UploadPage'
+import PlaceholderPage from '../components/PlaceholderPage'
+import HomeFeed from '../components/HomeFeed'
+
+export default function AppRouter() {
+  return (
+    <Routes>
+      <Route path="/" element={<Navigate to="/dashboard" replace />} />
+
+      <Route path="/home"      element={<HomeFeed />} />
+      <Route path="/notebooks" element={<PlaceholderPage pageId="notebooks" />} />
+      <Route path="/activity"  element={<PlaceholderPage pageId="activity" />} />
+      <Route path="/notes"     element={<PlaceholderPage pageId="notes" />} />
+      <Route path="/creator"   element={<PlaceholderPage pageId="creator" />} />
+      <Route path="/journal"   element={<PlaceholderPage pageId="journal" />} />
+
+      <Route path="/dashboard/*" element={<UploadPage />} />
+
+      <Route path="*" element={<PlaceholderPage pageId="home" />} />
+    </Routes>
+  )
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/src/style/HomeFeed.css b/JWLLL/API_front/src/style/HomeFeed.css
new file mode 100644
index 0000000..6cd353e
--- /dev/null
+++ b/JWLLL/API_front/src/style/HomeFeed.css
@@ -0,0 +1,153 @@
+/* --------- 容器 & Tabs --------- */
+.home-feed {
+  padding: 20px;
+}
+
+.feed-tabs {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+  margin-bottom: 20px;
+}
+
+.feed-tabs .tab {
+  padding: 6px 12px;
+  border: none;
+  background: #f0f0f0;
+  border-radius: 16px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.feed-tabs .tab.active {
+  background: #ff4757;
+  color: #fff;
+}
+
+/* --------- 瀑布流布局 --------- */
+.feed-grid {
+  display: grid;
+  grid-gap: 16px;
+  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+}
+
+/* --------- 卡片样式及最大高度限制 --------- */
+.feed-card {
+  display: flex;
+  flex-direction: column;
+  max-height: 360px;      /* 卡片最大高度 */
+  overflow: hidden;       /* 超出部分隐藏 */
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.1);
+  transition: transform 0.2s;
+}
+
+.feed-card:hover {
+  transform: translateY(-4px);
+}
+
+/* 封面图固定高度 */
+.card-img {
+  width: 100%;
+  height: 180px;         /* 固定图片区域高度 */
+  object-fit: cover;
+  flex-shrink: 0;        /* 不随容器收缩 */
+}
+
+/* 标题填充剩余空间 */
+.card-title {
+  font-size: 14px;
+  color: #333;
+  margin: 12px;
+  line-height: 1.4;
+  flex: 1;               /* 占满中间区域 */
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /* 最多两行 */
+  -webkit-box-orient: vertical;
+}
+
+/* --------- 底部:作者 + 点赞 --------- */
+.card-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  border-top: 1px solid #f0f0f0;
+  background: #fff;
+  flex-shrink: 0;        /* 保持在底部 */
+}
+
+/* 作者区域 */
+.card-author {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.card-author .avatar {
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  object-fit: cover;
+}
+
+.card-author .username {
+  font-size: 13px;
+  color: #333;
+}
+
+/* 点赞区域 */
+.card-likes {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.card-likes svg {
+  color: #ff4757;
+}
+
+.card-likes .likes-count {
+  font-size: 13px;
+  color: #666;
+}
+
+/* 搜索框美化 */
+.feed-search {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+.search-input {
+  flex: 1;
+  padding: 8px 14px;
+  border: 1.5px solid #e0e0e0;
+  border-radius: 20px;
+  font-size: 15px;
+  outline: none;
+  transition: border 0.2s;
+  background: #fafbfc;
+}
+.search-input:focus {
+  border: 1.5px solid #e84c4a;
+  background: #fff;
+}
+.search-btn {
+  padding: 8px 22px;
+  border: none;
+  border-radius: 20px;
+  background: linear-gradient(90deg,#ff6a6a,#ff4757);
+  color: #fff;
+  font-weight: 600;
+  font-size: 15px;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(255,71,87,0.08);
+  transition: background 0.2s, box-shadow 0.2s;
+}
+.search-btn:hover {
+  background: linear-gradient(90deg,#ff4757,#e84c4a);
+  box-shadow: 0 4px 16px rgba(255,71,87,0.15);
+}
\ No newline at end of file
diff --git a/JWLLL/API_front/vite.config.js b/JWLLL/API_front/vite.config.js
new file mode 100644
index 0000000..1501c80
--- /dev/null
+++ b/JWLLL/API_front/vite.config.js
@@ -0,0 +1,39 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [react()],
+  server: {
+    proxy: {
+      '/search': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/recommend_tags': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/user_tags': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/tags': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/user_based_recommend': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/word2vec_status': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+      '/debug_search': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+      },
+    }
+  }
+})