diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 5ccec6f..25ad5fc 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -193,7 +193,8 @@ def match_pattern(col_number: str, pattern: str) -> bool: # F = 非23457 # G = 非123457 - col_num = col_number[2:] if col_number.startswith('J0') else col_number # 去掉J0前缀 + # 注意:col_number已经是去掉J0前缀后的8位号码,不需要再处理 + col_num = col_number for i, p in enumerate(pattern): if i >= len(col_num): diff --git a/backend/app/routers/news.py b/backend/app/routers/news.py new file mode 100644 index 0000000..c969522 --- /dev/null +++ b/backend/app/routers/news.py @@ -0,0 +1,128 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy import Table, MetaData +from sqlalchemy.orm import Session +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime, date +from app.core.database import get_db, engine +from app.models.models import User +from app.routers.auth import get_current_user + +router = APIRouter(prefix="/api/news", tags=["资讯"]) +metadata = MetaData() + +# 分类表 +categories_table = Table('news_categories', metadata, autoload_with=engine) +news_table = Table('news', metadata, autoload_with=engine) +user_posts_table = Table('user_posts', metadata, autoload_with=engine) +users_table = Table('users', metadata, autoload_with=engine) +deals_table = Table('deals', metadata, autoload_with=engine) +notifications_table = Table('notifications', metadata, autoload_with=engine) + +# ============ 获取分类 ============ +@router.get("/categories") +def get_categories(db: Session = Depends(get_db)): + results = db.query(categories_table).order_by(categories_table.c.sort_order).all() + return [dict(r._mapping) for r in results] + +# ============ 获取资讯 ============ +@router.get("") +def get_news( + category_id: Optional[int] = None, + page: int = 1, + limit: int = 20, + db: Session = Depends(get_db) +): + query = db.query(news_table) + if category_id: + query = query.filter(news_table.c.category_id == category_id) + offset = (page - 1) * limit + results = query.order_by(news_table.c.created_at.desc()).offset(offset).limit(limit).all() + return [dict(r._mapping) for r in results] + +# ============ 获取用户发布 ============ +@router.get("/posts") +def get_posts( + post_type: Optional[str] = None, + status: str = "active", + page: int = 1, + limit: int = 20, + db: Session = Depends(get_db) +): + query = db.query(user_posts_table).filter(user_posts_table.c.status == status) + if post_type: + query = query.filter(user_posts_table.c.post_type == post_type) + offset = (page - 1) * limit + results = query.order_by(user_posts_table.c.created_at.desc()).offset(offset).limit(limit).all() + return [dict(r._mapping) for r in results] + +# ============ 创建发布 ============ +class PostCreate(BaseModel): + post_type: str + title: str + content: Optional[str] = None + zodiac_type: Optional[str] = None + packaging: Optional[str] = None + +@router.post("/posts") +def create_post( + post: PostCreate, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + result = db.execute(user_posts_table.insert().values( + user_id=current_user.f99_90_id, + post_type=post.post_type, + title=post.title, + content=post.content, + zodiac_type=post.zodiac_type, + packaging=post.packaging, + status="pending" + )) + db.commit() + return {"success": True, "id": result.inserted_primary_key[0]} + +# ============ 成交数据 ============ +@router.get("/deals") +def get_deals( + zodiac_type: Optional[str] = None, + limit: int = 20, + db: Session = Depends(get_db) +): + query = db.query(deals_table) + if zodiac_type: + query = query.filter(deals_table.c.zodiac_type == zodiac_type) + results = query.order_by(deals_table.c.deal_date.desc()).limit(limit).all() + return [dict(r._mapping) for r in results] + +# ============ 通知 ============ +@router.get("/notifications") +def get_notifications(limit: int = 10, db: Session = Depends(get_db)): + results = db.query(notifications_table).filter( + notifications_table.c.is_published == True + ).order_by(notifications_table.c.created_at.desc()).limit(limit).all() + return [dict(r._mapping) for r in results] + +# ============ 首页数据 ============ +@router.get("/home") +def get_home(db: Session = Depends(get_db)): + # 推荐发布 + posts = db.query(user_posts_table).filter( + user_posts_table.c.status == "active" + ).order_by(user_posts_table.c.created_at.desc()).limit(10).all() + + # 成交 + deals = db.query(deals_table).order_by( + deals_table.c.deal_date.desc() + ).limit(10).all() + + # 通知 + notices = db.query(notifications_table).filter( + notifications_table.c.is_published == True + ).order_by(notifications_table.c.created_at.desc()).limit(5).all() + + return { + "posts": [dict(p._mapping) for p in posts], + "deals": [dict(d._mapping) for d in deals], + "notices": [dict(n._mapping) for n in notices] + } diff --git a/config/VERSION b/config/VERSION index 93e5fdb..86aee77 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.26 +VERSION=1.2.30 diff --git a/frontend/index.html b/frontend/index.html index 9bc54bb..ae8a1b2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.25 + 甲辰收藏 v1.2.26 diff --git a/frontend/package_new.json b/frontend/package_new.json new file mode 100644 index 0000000..e69de29 diff --git a/frontend/postbuild.js b/frontend/postbuild.js new file mode 100644 index 0000000..0962aea --- /dev/null +++ b/frontend/postbuild.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +const src = path.join(__dirname, 'static', 'images'); +const dst = path.join(__dirname, 'dist', 'static', 'images'); + +if (!fs.existsSync(dst)) { + fs.mkdirSync(dst, { recursive: true }); +} + +if (fs.existsSync(src)) { + fs.readdirSync(src).forEach(f => { + const srcFile = path.join(src, f); + const dstFile = path.join(dst, f); + fs.copyFileSync(srcFile, dstFile); + console.log('Copied:', f); + }); +} + +console.log('Logo复制完成'); \ No newline at end of file diff --git a/frontend/public/images/jiachenlong-logo.png b/frontend/public/images/jiachenlong-logo.png new file mode 100644 index 0000000..247598e Binary files /dev/null and b/frontend/public/images/jiachenlong-logo.png differ diff --git a/frontend/public/images/title_logo.svg b/frontend/public/images/title_logo.svg new file mode 100644 index 0000000..acd9b87 --- /dev/null +++ b/frontend/public/images/title_logo.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + 甲辰收藏 + + + 生肖纪念钞管理系统 + diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 653f65f..2ac413b 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -192,11 +192,11 @@ export default function Admin() {
{user.username}
- {user.role === 'admin' ? '👑 管理员' : '👤 用户'} + {user.role === 'admin' ? '👑 管理员' : (user.role === 'editor' ? '📝 信息员' : '👤 用户')}
@@ -300,6 +300,7 @@ export default function Admin() { > +
@@ -360,6 +361,7 @@ export default function Admin() { > + diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx index c26ccd7..390bdb8 100644 --- a/frontend/src/pages/Info.jsx +++ b/frontend/src/pages/Info.jsx @@ -9,13 +9,14 @@ export default function Info() { } // 顶部tab:寻配号发布 / 发布管理 - const [activeTab, setActiveTab] = useState('publish') + const [activeTab, setActiveTab] = useState('manage') const [showPublish, setShowPublish] = useState(false) const [publishType, setPublishType] = useState('deal') // 'seek' 或 'deal' const [myList, setMyList] = useState([]) const [loading, setLoading] = useState(false) const [editingItem, setEditingItem] = useState(null) const [filterType, setFilterType] = useState('all') // all/seek/deal + const [expandedItems, setExpandedItems] = useState({}) // 展开状态 const [formData, setFormData] = useState({ edition: '龙钞', type: '标百', isGraded: true, category: '成交', source: '直播', title: '' @@ -28,6 +29,14 @@ export default function Info() { const API_BASE = localStorage.getItem('API_BASE') || '' + // 切换展开/收起 + const toggleExpand = (itemId) => { + setExpandedItems(prev => ({ + ...prev, + [itemId]: !prev[itemId] + })) + } + useEffect(() => { if (activeTab === 'manage') fetchMyList() }, [activeTab]) @@ -53,7 +62,7 @@ export default function Info() { setLoading(true) try { const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/my/list`, { + const res = await fetch(`${API_BASE}/api/information/list`, { headers: { Authorization: `Bearer ${token}` } }) if (res.ok) { @@ -218,7 +227,6 @@ export default function Info() {
{[ - { key: 'publish', label: '📝 寻配号发布' }, { key: 'manage', label: '📋 发布管理' } ].map(tab => (
- {/* 寻配号发布页 */} - {activeTab === 'publish' && ( + {/* 寻配号发布页 - 已删除 */} + {false && (

🔍 寻配号发布

@@ -560,9 +568,10 @@ export default function Info() { ) : (
{filteredList.map(item => ( -
+
+ {/* 标题行 */}
-
{item.title}
+
{item.title}
- {item.content &&
{item.content}
} -
-
{formatDate(item.created_at)}
+ {/* 日期+用户名 */} +
+ 📅 {formatDate(item.created_at)}  |  👤 {item.user_name || '匿名用户'} +
+ {/* 正文 - 默认收起,点击展开 */} + {item.content && ( +
+
toggleExpand(item.id)} + style={{ + color: '#10b981', + fontSize: '13px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '6px', + padding: '8px 12px', + background: 'rgba(16,185,129,0.1)', + borderRadius: '6px', + border: '1px solid rgba(16,185,129,0.2)' + }} + > + {expandedItems[item.id] ? '▼' : '▶'} + {expandedItems[item.id] ? '收起详情' : '展开查看详情'} +
+ {expandedItems[item.id] && ( +
+ {item.content.split('\n').map((line, i) => ( +
+ {line || ' '} +
+ ))} +
+ )} +
+ )} + {/* 操作按钮 */} +
diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 1aa3254..05c7ff2 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -27,11 +27,20 @@ export default function News() { }) const [infoList, setInfoList] = useState([]) const [viewMode, setViewMode] = useState('all') + const [expandedItems, setExpandedItems] = useState({}) // 展开状态 const [loading, setLoading] = useState(false) const API_BASE = localStorage.getItem('API_BASE') || '' const currentUser = localStorage.getItem('token') ? { id: 'logged_in' } : null + // 切换展开/收起 + const toggleExpand = (itemId) => { + setExpandedItems(prev => ({ + ...prev, + [itemId]: !prev[itemId] + })) + } + // 获取资讯列表 useEffect(() => { fetchInfoList() @@ -507,10 +516,54 @@ export default function News() {
)} - {/* 正文 */} + {/* 正文 - 默认收起,点击展开 */} {cleanContent && ( -
- {cleanContent} +
+ {/* 展开收起按钮 */} +
toggleExpand(item.id)} + style={{ + color: '#10b981', + fontSize: '13px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '6px', + padding: '8px 12px', + background: 'rgba(16,185,129,0.1)', + borderRadius: '6px', + border: '1px solid rgba(16,185,129,0.2)' + }} + > + {expandedItems[item.id] ? '▼' : '▶'} + {expandedItems[item.id] ? '收起详情' : '展开查看详情'} +
+ {/* 展开后的内容 */} + {expandedItems[item.id] && ( +
+ {cleanContent.split('\n').map((line, i) => { + const isHighlight = line.includes('涨价') || line.includes('下跌') || line.includes('稀缺') || line.includes('热门') + return ( +
+ {line || ' '} +
+ ) + })} +
+ )}
)} {/* 联系方式 - 默认隐藏,显示*** */} diff --git a/frontend/static/images/.gitkeep b/frontend/static/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/static/images/LOGO_GUIDE.md b/frontend/static/images/LOGO_GUIDE.md new file mode 100644 index 0000000..f9ccf7d --- /dev/null +++ b/frontend/static/images/LOGO_GUIDE.md @@ -0,0 +1,240 @@ +# Logo 使用规范 + +**版本**: v1.0.0 +**更新日期**: 2026-03-16 +**状态**: ✅ 官方指定 Logo + +--- + +## 🐉 官方 Logo + +### 主 Logo + +**文件**: `jiachenlong-logo.png` + +**位置**: +- 本地:`/static/images/jiachenlong-logo.png` +- 前端服务器:`/var/www/html/static/images/jiachenlong-logo.png` + +**规格**: +- 格式:PNG +- 大小:606KB +- 尺寸:正方形(适合圆形裁剪) +- 颜色:橙色(中国传统色) +- 设计:龙型环绕 + "甲辰收藏"文字 + +--- + +## 📋 使用场景 + +### 1. 登录页面 + +**文件**: `frontend/src/pages/Login.jsx` + +```jsx +甲辰收藏 +``` + +### 2. 首页 + +**文件**: `frontend/src/pages/Home.jsx` + +```jsx +甲辰收藏 +``` + +### 3. 藏品详情页 + +**文件**: `frontend/src/pages/Detail.jsx` + +```jsx +甲辰收藏 { + e.target.src = '/static/images/jiachenlong-logo.png'; + }} +/> +``` + +--- + +## 🎨 样式规范 + +### 圆形样式(推荐) + +```css +.logo { + width: 200px; + height: 200px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 0 40px rgba(251, 191, 36, 0.4); + background: #fff; +} +``` + +### 小尺寸(导航栏等) + +```css +.logo-small { + width: 48px; + height: 48px; + border-radius: 50%; + object-fit: cover; +} +``` + +### 中等尺寸 + +```css +.logo-medium { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; +} +``` + +--- + +## 📦 部署规范 + +### 部署脚本 + +**文件**: `scripts/deploy.sh` + +部署脚本会自动: +1. ✅ 检查 Logo 文件是否存在 +2. ✅ 部署前端构建文件 +3. ✅ 部署 Logo 到服务器 +4. ✅ 重启 Nginx + +### 部署命令 + +```bash +# 测试环境 +./scripts/deploy.sh 1.0.0 test + +# 生产环境 +./scripts/deploy.sh 1.0.0 production +``` + +### 手动部署 + +```bash +# 1. 构建前端 +cd frontend +npm run build + +# 2. 部署到服务器 +scp -r dist/* root@8.149.137.26:/var/www/html/ +scp static/images/jiachenlong-logo.png root@8.149.137.26:/var/www/html/static/images/ + +# 3. 重启 Nginx +ssh root@8.149.137.26 "nginx -s reload" +``` + +--- + +## ⚠️ 注意事项 + +### 必须遵守 + +1. ✅ **统一使用** `jiachenlong-logo.png` +2. ✅ **禁止使用** 旧版 `logo.jpg`、`dragon-logo.jpg`、`title_logo.svg` +3. ✅ **保持比例** - 始终使用正方形容器 +4. ✅ **圆形裁剪** - 使用 `border-radius: 50%` +5. ✅ **白色背景** - Logo 需要白色背景衬托 + +### 禁止行为 + +- ❌ 不要修改 Logo 颜色 +- ❌ 不要拉伸变形 +- ❌ 不要添加其他效果 +- ❌ 不要使用其他 Logo 文件 + +--- + +## 📁 文件位置 + +### 本地开发 + +``` +jiachenlong/ +└── static/ + └── images/ + └── jiachenlong-logo.png # ✅ 官方 Logo +``` + +### 前端服务器 + +``` +/var/www/html/ +└── static/ + └── images/ + └── jiachenlong-logo.png # ✅ 官方 Logo +``` + +--- + +## 🔄 更新流程 + +如需更新 Logo: + +1. **替换文件** + ```bash + cp new-logo.png /static/images/jiachenlong-logo.png + ``` + +2. **重新构建** + ```bash + cd frontend + npm run build + ``` + +3. **部署到服务器** + ```bash + ./scripts/deploy.sh 1.0.1 production + ``` + +4. **验证部署** + ```bash + curl http://8.149.137.26/static/images/jiachenlong-logo.png -o /tmp/logo-check.png + ``` + +--- + +## 📊 Logo 对比 + +| 文件 | 状态 | 说明 | +|------|------|------| +| `jiachenlong-logo.png` | ✅ **官方指定** | 橙色圆形龙型 Logo | +| `logo.jpg` | ❌ 废弃 | 旧版 Logo | +| `dragon-logo.jpg` | ❌ 废弃 | 旧版龙型 Logo | +| `title_logo.svg` | ❌ 废弃 | 旧版 SVG Logo | + +--- + +**所有部署必须使用 `jiachenlong-logo.png`!** + +**最后更新**: 2026-03-16 diff --git a/frontend/static/images/README.md b/frontend/static/images/README.md new file mode 100644 index 0000000..86b9f31 --- /dev/null +++ b/frontend/static/images/README.md @@ -0,0 +1,36 @@ +# 图片资源 + +本目录存放项目的所有图片资源。 + +## 📁 文件列表 + +- `logo.jpg` - 系统主 Logo(106KB, 512x512) + +## 🎨 使用方式 + +### 前端访问 +```jsx +logo +``` + +### 后端访问(FastAPI) +```python +from fastapi.staticfiles import StaticFiles +app.mount("/static", StaticFiles(directory="static"), name="static") +``` + +## 📐 建议尺寸 + +- **Logo**: 512x512 或更大(用于缩放) +- **背景图**: 1920x1080(全屏背景) +- **头像**: 200x200(用户头像) + +## 📦 格式建议 + +- **Logo**: PNG(透明背景)或 JPG +- **照片**: JPG(压缩比好) +- **图标**: SVG(矢量可缩放)或 PNG + +--- + +**最后更新**: 2026-03-16 diff --git a/frontend/static/images/jiachenlong-logo.png b/frontend/static/images/jiachenlong-logo.png new file mode 100644 index 0000000..247598e Binary files /dev/null and b/frontend/static/images/jiachenlong-logo.png differ diff --git a/frontend/static/images/title_logo.svg b/frontend/static/images/title_logo.svg new file mode 100644 index 0000000..acd9b87 --- /dev/null +++ b/frontend/static/images/title_logo.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + 甲辰收藏 + + + 生肖纪念钞管理系统 +