v0.1.6 - 成交行情页面增强:新增搜索+版别+包装+号码分类筛选
This commit is contained in:
parent
f695dfbfa0
commit
3eb8aac2be
|
|
@ -10,7 +10,7 @@ from sqlalchemy.orm import Session
|
|||
# 更新:
|
||||
from pydantic import BaseModel
|
||||
# 更新:
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
# 更新:
|
||||
from datetime import datetime, date
|
||||
# 更新:
|
||||
|
|
@ -176,7 +176,7 @@ def generate_deal_no(db: Session):
|
|||
# 更新:
|
||||
# ============ API ============
|
||||
# 更新:
|
||||
@router.get("/list", response_model=list[DealInfoResponse])
|
||||
@router.get("/list", response_model=List[DealInfoResponse])
|
||||
# 更新:
|
||||
def get_deal_list(
|
||||
# 更新:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# purchase - 认购群API路由
|
||||
# Version: 0.0.5 (2026-04-29)
|
||||
# Version: 0.2.0 (2026-05-01)
|
||||
# 认购群、成员、藏品管理
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
|
@ -88,6 +88,11 @@ class MemberResponse(BaseModel):
|
|||
role: str
|
||||
status: str
|
||||
joined_at: Optional[datetime]
|
||||
wechat_name: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
purchase_count: Optional[int] = 0
|
||||
paid_amount: Optional[float] = 0
|
||||
notes: Optional[str] = None
|
||||
|
||||
# ============ 工具函数 ============
|
||||
def generate_group_code(db: Session) -> str:
|
||||
|
|
@ -127,7 +132,7 @@ def get_my_groups(
|
|||
current_user = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取我参与的认购群"""
|
||||
"""获取我参与的认购群(仅已批准的)"""
|
||||
if not current_user:
|
||||
return []
|
||||
|
||||
|
|
@ -296,14 +301,14 @@ def join_group(
|
|||
if existing:
|
||||
if existing.status == "approved":
|
||||
raise HTTPException(status_code=400, detail="您已加入该认购群")
|
||||
elif existing.status == "pending":
|
||||
raise HTTPException(status_code=400, detail="您已申请加入,等待审批")
|
||||
elif existing.status == "rejected":
|
||||
else:
|
||||
# pending/rejected/其他状态:重新激活为pending
|
||||
existing.status = "pending"
|
||||
existing.joined_at = datetime.now()
|
||||
existing.user_name = current_user.f01_01_name
|
||||
group.total_pending += 1
|
||||
db.commit()
|
||||
return {"message": "已重新申请,等待审批"}
|
||||
return {"message": "申请成功,等待审批"}
|
||||
|
||||
member = PurchaseMember(
|
||||
group_id=group_id,
|
||||
|
|
@ -339,7 +344,12 @@ def get_group_members(
|
|||
"user_avatar": m.user_avatar,
|
||||
"role": m.role,
|
||||
"status": m.status,
|
||||
"joined_at": m.joined_at
|
||||
"joined_at": m.joined_at,
|
||||
"wechat_name": m.wechat_name,
|
||||
"contact": m.contact,
|
||||
"purchase_count": m.purchase_count or 0,
|
||||
"paid_amount": float(m.paid_amount or 0),
|
||||
"notes": m.notes
|
||||
}
|
||||
for m in members
|
||||
]
|
||||
|
|
@ -552,6 +562,7 @@ def delete_group(
|
|||
return {"message": "认购群已删除"}
|
||||
|
||||
class MemberUpdate(BaseModel):
|
||||
user_name: Optional[str] = None
|
||||
wechat_name: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
purchase_count: Optional[int] = None
|
||||
|
|
@ -593,6 +604,8 @@ def update_member(
|
|||
if member.user_id != current_user.f99_90_id and group.creator_id != current_user.f99_90_id:
|
||||
raise HTTPException(status_code=403, detail="无权修改")
|
||||
|
||||
if data.user_name is not None:
|
||||
member.user_name = data.user_name
|
||||
if data.wechat_name is not None:
|
||||
member.wechat_name = data.wechat_name
|
||||
if data.contact is not None:
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@
|
|||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from app.core.database import get_db
|
||||
from app.core.auth import get_current_user
|
||||
from app.models.seek_info import SeekInfo
|
||||
from app.models.models import User, Collection
|
||||
from app.routers.information import match_collections_count, match_collections_count_from_coolbot
|
||||
from app.routers.information import match_collections_count, match_collections_count_from_coolbot, match_pattern
|
||||
from app.utils.coolbot_matcher import (
|
||||
match_pattern,
|
||||
match_self_collections_count,
|
||||
match_collections_count_from_coolbot,
|
||||
match_collections_list_from_coolbot
|
||||
|
|
@ -75,7 +74,7 @@ class MatchConfirmRequest(BaseModel):
|
|||
collection_id: Optional[str] = None
|
||||
|
||||
# ============ API ============
|
||||
@router.get("/list", response_model=list[SeekInfoResponse])
|
||||
@router.get("/list", response_model=List[SeekInfoResponse])
|
||||
def get_seek_list(
|
||||
status: str = Query("active"),
|
||||
page: int = Query(1, ge=1),
|
||||
|
|
|
|||
|
|
@ -1,89 +1,194 @@
|
|||
# coolbot_matcher - 一尘数据库号码匹配工具
|
||||
# Version: 0.0.1 (2026-04-24)
|
||||
# Version: 0.0.2 (2026-04-30)
|
||||
# 描述:从一尘数据库(coolbot_data)查询匹配的藏品号码
|
||||
# 更新:号码分类规则修正
|
||||
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.coolbot_db import coolbot_engine
|
||||
|
||||
|
||||
def match_pattern(col_number: str, pattern: str) -> bool:
|
||||
"""匹配号码特征模式
|
||||
def classify_number(number: str) -> str:
|
||||
"""根据号码特征分类
|
||||
|
||||
通配符规则:
|
||||
- X = 任意数字
|
||||
- A = 非4
|
||||
- B = 非47
|
||||
- C = 非347
|
||||
- D = 非247
|
||||
- E = 非2347
|
||||
规则(2026-04-30修正版):
|
||||
|
||||
首先区分标百、标十,单张:
|
||||
- 单张:J后面9位
|
||||
- 标十:J后面8位(最后一位是1)
|
||||
- 标百:J后面7位(最后两位是01)
|
||||
|
||||
分类优先级:
|
||||
| 类型 | 排除 | 可用数字 | 必须包含 |
|
||||
|------|------|----------|----------|
|
||||
| 通货 | - | - | 4 |
|
||||
| 带7号 | 4 | - | 7 |
|
||||
| 永恒号 | 47 | - | - |
|
||||
| 圆圆号 | 123457 | 0689 | - |
|
||||
| 倒置号 | 23457 | 01689 | 1 |
|
||||
| 金马王 | 12347 | 05689 | 5 |
|
||||
| 金马号 | 2347 | 015689 | 1,5 |
|
||||
| 金山王 | 12457 | 03689 | 3 |
|
||||
| 天马王 | 1247 | 035689 | 3,5 |
|
||||
| 金山号 | 2457 | 013689 | 1,3 |
|
||||
| 天马号 | 247 | 0135689 | 1,3,5 |
|
||||
| 朦胧王 | 13457 | - | - |
|
||||
| 朦胧号 | 3457 | - | - |
|
||||
| 如意号 | 1347 | - | - |
|
||||
| 钻石号 | 347 | - | - |
|
||||
"""
|
||||
if not col_number or not pattern:
|
||||
if not number:
|
||||
return "未知"
|
||||
|
||||
if number.startswith('J0'):
|
||||
digits = number[2:]
|
||||
elif number.startswith('J'):
|
||||
digits = number[1:]
|
||||
else:
|
||||
return "未知"
|
||||
|
||||
if len(digits) == 9:
|
||||
d = digits
|
||||
elif len(digits) == 8:
|
||||
d = digits[:8]
|
||||
elif len(digits) == 7:
|
||||
d = digits
|
||||
else:
|
||||
return "未知"
|
||||
|
||||
unique = set(d)
|
||||
|
||||
# 1. 通货:带4
|
||||
if '4' in unique:
|
||||
return "通货"
|
||||
|
||||
# 2. 带7号:无4,有7
|
||||
if '7' in unique:
|
||||
return "带7号"
|
||||
|
||||
# 3. 圆圆号:无123457,0689四个数字任意组合
|
||||
if unique <= {'0', '6', '8', '9'}:
|
||||
return "圆圆号"
|
||||
|
||||
# 4. 永恒号:只有0和1(无47但不符合圆圆号/倒置号)
|
||||
if unique <= {'0', '1'}:
|
||||
return "永恒号"
|
||||
|
||||
# 5. 倒置号:01689组合,必须有1,且包含6/8/9中的至少一个
|
||||
if unique <= {'0', '1', '6', '8', '9'} and '1' in unique and unique & {'6', '8', '9'}:
|
||||
return "倒置号"
|
||||
|
||||
# 6. 金马王:无12347,05689组合,必须有5(排除1)
|
||||
if unique <= {'0', '5', '6', '8', '9'} and '5' in unique and '1' not in unique:
|
||||
return "金马王"
|
||||
|
||||
# 7. 金马号:无2347,015689组合,必须有1和5(包含1)
|
||||
if unique <= {'0', '1', '5', '6', '8', '9'} and '1' in unique and '5' in unique:
|
||||
return "金马号"
|
||||
|
||||
# 8. 金山王:无12457,03689组合,必须有3(排除1和5)
|
||||
if unique <= {'0', '3', '6', '8', '9'} and '3' in unique and '1' not in unique and '5' not in unique:
|
||||
return "金山王"
|
||||
|
||||
# 9. 天马王:无1247,035689组合,必须有3和5(排除1)
|
||||
if unique <= {'0', '3', '5', '6', '8', '9'} and '3' in unique and '5' in unique and '1' not in unique:
|
||||
return "天马王"
|
||||
|
||||
# 10. 金山号:无2457,013689组合,必须有1和3(排除5)
|
||||
if unique <= {'0', '1', '3', '6', '8', '9'} and '1' in unique and '3' in unique and '5' not in unique:
|
||||
return "金山号"
|
||||
|
||||
# 11. 天马号:无247,0135689组合,必须有1、3和5
|
||||
if unique <= {'0', '1', '3', '5', '6', '8', '9'} and '1' in unique and '3' in unique and '5' in unique:
|
||||
return "天马号"
|
||||
|
||||
# 12. 朦胧王:无13457
|
||||
if not unique & {'1', '3', '4', '5', '7'}:
|
||||
return "朦胧王"
|
||||
|
||||
# 13. 朦胧号:无3457
|
||||
if not unique & {'3', '4', '5', '7'}:
|
||||
return "朦胧号"
|
||||
|
||||
# 14. 如意号:无1347
|
||||
if not unique & {'1', '3', '4', '7'}:
|
||||
return "如意号"
|
||||
|
||||
# 15. 钻石号:无347
|
||||
if not unique & {'3', '4', '7'}:
|
||||
return "钻石号"
|
||||
|
||||
# 16. 永恒号兜底:无47
|
||||
if '7' not in unique:
|
||||
return "永恒号"
|
||||
|
||||
return "其他"
|
||||
|
||||
|
||||
def check_match(col_number: str, expect_number: str, expect_category: str) -> bool:
|
||||
"""检查藏品号码是否符合期望的分类"""
|
||||
if not col_number or not expect_category:
|
||||
return False
|
||||
|
||||
if len(col_number) != len(pattern):
|
||||
col_cat = classify_number(col_number)
|
||||
|
||||
if col_cat == expect_category:
|
||||
return True
|
||||
|
||||
if expect_category == "其他":
|
||||
return check_number_pattern(col_number, expect_number)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_number_pattern(col_number: str, expect_number: str) -> bool:
|
||||
"""检查号码特征匹配"""
|
||||
if not col_number or not expect_number:
|
||||
return False
|
||||
|
||||
for i, p in enumerate(pattern):
|
||||
c = col_number[i]
|
||||
if p == 'X':
|
||||
continue # 任意数字
|
||||
elif p == 'A':
|
||||
if c == '4':
|
||||
return False
|
||||
elif p == 'B':
|
||||
if c == '4':
|
||||
return False
|
||||
if i < len(col_number) - 1 and col_number[i+1] == '7' and pattern[i+1] == 'X':
|
||||
return False
|
||||
elif p == 'C':
|
||||
if c in '347':
|
||||
return False
|
||||
elif p == 'D':
|
||||
if c in '247':
|
||||
return False
|
||||
elif p == 'E':
|
||||
if c in '2347':
|
||||
return False
|
||||
elif p == 'L':
|
||||
# L = 带4
|
||||
if c != '4':
|
||||
return False
|
||||
elif p == 'N':
|
||||
# N = 无4
|
||||
if c == '4':
|
||||
return False
|
||||
else:
|
||||
if c != p:
|
||||
return False
|
||||
return True
|
||||
if col_number.startswith('J0'):
|
||||
col_digits = col_number[2:]
|
||||
else:
|
||||
col_digits = col_number[1:] if len(col_number) > 1 else col_number
|
||||
|
||||
if expect_number.startswith('J0'):
|
||||
exp_digits = expect_number[2:]
|
||||
else:
|
||||
exp_digits = expect_number[1:] if len(expect_number) > 1 else expect_number
|
||||
|
||||
min_len = min(len(col_digits), len(exp_digits))
|
||||
return col_digits[:min_len] == exp_digits[:min_len]
|
||||
|
||||
|
||||
def match_self_collections_count(db: Session, user_id: str, expect_number: str) -> int:
|
||||
"""根据号码特征计算匹配藏品数量(从用户自有藏品)
|
||||
def get_match_type(expect_number: str) -> str:
|
||||
"""判断匹配类型"""
|
||||
if not expect_number:
|
||||
return "unknown"
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户ID
|
||||
expect_number: 期望号码,如 "J012345678" (固定J0前缀+8位特征)
|
||||
if expect_number.startswith('J0'):
|
||||
digits = expect_number[2:]
|
||||
elif expect_number.startswith('J'):
|
||||
digits = expect_number[1:]
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
Returns:
|
||||
匹配的藏品数量
|
||||
"""
|
||||
if len(digits) == 8:
|
||||
return "ten"
|
||||
elif len(digits) == 7:
|
||||
return "hundred"
|
||||
elif len(digits) == 9:
|
||||
return "single"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def match_self_collections_count(db: Session, user_id: str, expect_number: str, expect_category: str) -> int:
|
||||
"""计算匹配藏品数量(用户自有藏品)"""
|
||||
from app.models.models import Collection
|
||||
|
||||
if not expect_number or len(expect_number) != 10:
|
||||
if not expect_number:
|
||||
return 0
|
||||
|
||||
if not expect_number.startswith('J0'):
|
||||
return 0
|
||||
|
||||
pattern = expect_number[2:] # 后8位
|
||||
if not pattern:
|
||||
return 0
|
||||
|
||||
# 获取用户所有藏品
|
||||
collections = db.query(Collection).filter(
|
||||
Collection.f99_91_user_id == user_id,
|
||||
Collection.f01_04_status == "in_collection"
|
||||
|
|
@ -92,81 +197,36 @@ def match_self_collections_count(db: Session, user_id: str, expect_number: str)
|
|||
count = 0
|
||||
for c in collections:
|
||||
number = c.f02_10_prefix_serial or ''
|
||||
if len(number) >= 10 and number.startswith('J0'):
|
||||
col_pattern = number[2:10]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
count += 1
|
||||
elif len(number) >= 8:
|
||||
col_pattern = number[:8]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
count += 1
|
||||
if check_match(number, expect_number, expect_category):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def match_collections_count_from_coolbot(expect_number: str) -> int:
|
||||
"""根据号码特征计算匹配藏品数量(从coolbot_data数据库)
|
||||
|
||||
Args:
|
||||
expect_number: 期望号码,如 "J012345678" (固定J0前缀+8位特征)
|
||||
|
||||
Returns:
|
||||
匹配的藏品数量
|
||||
"""
|
||||
if not expect_number or len(expect_number) != 10:
|
||||
return 0
|
||||
|
||||
if not expect_number.startswith('J0'):
|
||||
return 0
|
||||
|
||||
pattern = expect_number[2:] # 后8位
|
||||
if not pattern:
|
||||
def match_collections_count_from_coolbot(expect_number: str, expect_category: str) -> int:
|
||||
"""计算匹配藏品数量(一尘数据库)"""
|
||||
if not expect_number:
|
||||
return 0
|
||||
|
||||
query = text("""
|
||||
SELECT id, crown_code FROM collections
|
||||
WHERE crown_code IS NOT NULL
|
||||
AND crown_code != ''
|
||||
AND LENGTH(crown_code) >= 10
|
||||
AND crown_code LIKE 'J0%'
|
||||
""")
|
||||
|
||||
try:
|
||||
with coolbot_engine.connect() as conn:
|
||||
result = conn.execute(query)
|
||||
|
||||
match_count = 0
|
||||
for row in result:
|
||||
crown_code = row[1]
|
||||
if crown_code and len(crown_code) >= 10:
|
||||
col_pattern = crown_code[2:10]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
match_count += 1
|
||||
|
||||
return match_count
|
||||
return sum(1 for row in result if check_match(row[1], expect_number, expect_category))
|
||||
except Exception as e:
|
||||
print(f"Error querying coolbot_data: {e}")
|
||||
print(f"Error: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
def match_collections_list_from_coolbot(expect_number: str, limit: int = 20) -> List[dict]:
|
||||
"""获取匹配的藏品列表(从coolbot_data数据库)
|
||||
|
||||
Args:
|
||||
expect_number: 期望号码,如 "J012345678"
|
||||
limit: 返回的最大数量
|
||||
|
||||
Returns:
|
||||
匹配的藏品列表
|
||||
"""
|
||||
if not expect_number or len(expect_number) != 10:
|
||||
return []
|
||||
|
||||
if not expect_number.startswith('J0'):
|
||||
return []
|
||||
|
||||
pattern = expect_number[2:] # 后8位
|
||||
if not pattern:
|
||||
def match_collections_list_from_coolbot(expect_number: str, expect_category: str, limit: int = 20) -> List[dict]:
|
||||
"""获取匹配的藏品列表(一尘数据库)"""
|
||||
if not expect_number:
|
||||
return []
|
||||
|
||||
query = text("""
|
||||
|
|
@ -174,35 +234,29 @@ def match_collections_list_from_coolbot(expect_number: str, limit: int = 20) ->
|
|||
FROM collections
|
||||
WHERE crown_code IS NOT NULL
|
||||
AND crown_code != ''
|
||||
AND LENGTH(crown_code) >= 10
|
||||
AND crown_code LIKE 'J0%'
|
||||
""")
|
||||
|
||||
try:
|
||||
with coolbot_engine.connect() as conn:
|
||||
result = conn.execute(query)
|
||||
|
||||
matched = []
|
||||
for row in result:
|
||||
crown_code = row[3]
|
||||
if crown_code and len(crown_code) >= 10:
|
||||
col_pattern = crown_code[2:10]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
matched.append({
|
||||
"id": row[0],
|
||||
"name": row[1],
|
||||
"category": row[2],
|
||||
"crown_code": crown_code,
|
||||
"price": float(row[4]) if row[4] else None,
|
||||
"post_title": row[5],
|
||||
"post_url": row[6],
|
||||
"author": row[7],
|
||||
"post_crawled_at": row[8].isoformat() if row[8] else None
|
||||
})
|
||||
if len(matched) >= limit:
|
||||
break
|
||||
|
||||
if check_match(row[3], expect_number, expect_category):
|
||||
matched.append({
|
||||
"id": row[0],
|
||||
"name": row[1],
|
||||
"category": row[2],
|
||||
"crown_code": row[3],
|
||||
"price": float(row[4]) if row[4] else None,
|
||||
"post_title": row[5],
|
||||
"post_url": row[6],
|
||||
"author": row[7],
|
||||
"post_crawled_at": row[8].isoformat() if row[8] else None
|
||||
})
|
||||
if len(matched) >= limit:
|
||||
break
|
||||
return matched
|
||||
except Exception as e:
|
||||
print(f"Error querying coolbot_data: {e}")
|
||||
print(f"Error: {e}")
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -1,10 +1,56 @@
|
|||
# 版本更新记录
|
||||
|
||||
## v0.1.2 (2026-04-30)
|
||||
## v0.1.6 (2026-05-01)
|
||||
|
||||
### 前端更新
|
||||
- **List.jsx**
|
||||
- 新增:搜索框增强 - 支持搜索冠字号、编号、价格
|
||||
- 新增:版别筛选按钮 - 20版/19版/18版/17版/16版
|
||||
- 新增:包装类型筛选 - 单张/标十/标百
|
||||
- 新增:号码分类筛选 - 圆圆号/倒置号/金马号/天马号/钻石号/永恒号/带7号/带4号
|
||||
- 新增:清除筛选按钮
|
||||
- 优化:筛选结果显示当前已选条件
|
||||
|
||||
## v0.1.5 (2026-05-01)
|
||||
|
||||
### 前端更新
|
||||
- **Purchase.jsx**
|
||||
- 新增:待审批Tab(黄色按钮)- 显示需要审批的成员
|
||||
- 新增:待审批成员列表 - 显示申请人信息+同意/拒绝按钮
|
||||
- 新增:审批后状态 - 变灰+显示已批准/已拒绝
|
||||
- 新增:申请加入后待审批状态 - 列表显示黄色"待审批"
|
||||
- 修复:待审批数量计算准确
|
||||
- 修复:只有群主能看到自己创建的群的待审批
|
||||
|
||||
### 后端更新
|
||||
- **purchase.py**
|
||||
- 修复:join_group接口处理状态
|
||||
- 新增:MemberResponse扩展字段(wechat_name, contact等)
|
||||
|
||||
## v0.1.4 (2026-05-01)
|
||||
|
||||
### 前端更新
|
||||
- **Purchase.jsx**
|
||||
- 修复:列表隐藏群介绍,点进详情后才显示
|
||||
- 修复:未登录不黑屏
|
||||
- 新增:修改群信息弹窗(群主)
|
||||
- 新增:修改个人信息弹窗(成员)
|
||||
- 修复:冠字号输入框maxLength
|
||||
|
||||
## v0.1.3 (2026-04-30)
|
||||
|
||||
### 前端更新
|
||||
- **Home.jsx**
|
||||
- 修复:我的认购入口字体颜色改为蓝色 #3b82f6
|
||||
- **Purchase.jsx**
|
||||
- 新增:权限控制 - 未加入的群只能看到基本信息(群名、编号、群主、介绍)
|
||||
- 新增:申请加入按钮 - 未加入的群可申请加入
|
||||
- 修复:敏感信息(统计、成员、藏品)只有已加入才可见
|
||||
- 修复:user.id改为user.f99_90_id
|
||||
|
||||
## v0.1.2 (2026-04-30)
|
||||
|
||||
### 前端更新
|
||||
- **Purchase.jsx**
|
||||
- 新增:创建群表单6个必填字段
|
||||
- 认购群名称
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
{
|
||||
"version": "0.1.2",
|
||||
"updated": "2026-04-30",
|
||||
"version": "0.1.6",
|
||||
"updated": "2026-05-01",
|
||||
"modules": {
|
||||
"frontend": {
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.6",
|
||||
"pages": {
|
||||
"Home": "0.1.0",
|
||||
"Purchase": "0.0.2"
|
||||
"Purchase": "0.2.0",
|
||||
"List": "0.1.6"
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.6",
|
||||
"routers": {
|
||||
"purchase": "0.1.0"
|
||||
"purchase": "0.2.0"
|
||||
},
|
||||
"models": {
|
||||
"purchase": "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* List - 藏品列表页面
|
||||
* Version: 0.0.1
|
||||
* 更新:
|
||||
* Version: 0.1.6
|
||||
* 更新:新增成交行情页面筛选功能(版别、包装类型、号码分类)
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
|
@ -30,6 +30,10 @@ export default function List() {
|
|||
const [dealSortOrder, setDealSortOrder] = useState('desc')
|
||||
const [myDeals, setMyDeals] = useState([])
|
||||
const [dealsLoading, setDealsLoading] = useState(false)
|
||||
// 行情筛选条件
|
||||
const [dealVersionFilter, setDealVersionFilter] = useState('') // 版别筛选
|
||||
const [dealPackagingFilter, setDealPackagingFilter] = useState('') // 包装类型筛选
|
||||
const [dealNumberCatFilter, setDealNumberCatFilter] = useState('') // 号码分类筛选
|
||||
|
||||
useEffect(() => {
|
||||
// 检查是否管理员
|
||||
|
|
@ -106,10 +110,33 @@ export default function List() {
|
|||
|
||||
// 行情过滤和排序
|
||||
const filteredDeals = myDeals.filter(deal => {
|
||||
if (!dealSearch) return true
|
||||
const s = dealSearch.toLowerCase().trim()
|
||||
const fields = [deal.title, deal.content, deal.category, deal.packaging, deal.grading_company, deal.grading_score, deal.deal_no, deal.seller, deal.platform, deal.buyer, deal.deal_price?.toString(), deal.deal_date].filter(v => v).map(v => v.toString().toLowerCase())
|
||||
return fields.some(f => f.includes(s))
|
||||
// 1. 搜索过滤
|
||||
if (dealSearch) {
|
||||
const s = dealSearch.toLowerCase().trim()
|
||||
const fields = [deal.title, deal.content, deal.category, deal.packaging, deal.grading_company, deal.grading_score, deal.deal_no, deal.seller, deal.platform, deal.buyer, deal.deal_price?.toString(), deal.deal_date].filter(v => v).map(v => v.toString().toLowerCase())
|
||||
if (!fields.some(f => f.includes(s))) return false
|
||||
}
|
||||
// 2. 版别筛选 - 从content中解析
|
||||
if (dealVersionFilter) {
|
||||
let version = ''
|
||||
if (deal.content) {
|
||||
const match = deal.content.match(/版别:\s*([^\n]+)/)
|
||||
if (match) version = match[1].trim()
|
||||
}
|
||||
if (version !== dealVersionFilter) return false
|
||||
}
|
||||
// 3. 包装类型筛选
|
||||
if (dealPackagingFilter && deal.packaging !== dealPackagingFilter) return false
|
||||
// 4. 号码分类筛选 - 从content中解析
|
||||
if (dealNumberCatFilter) {
|
||||
let numberCat = ''
|
||||
if (deal.content) {
|
||||
const match = deal.content.match(/号码分类:\s*([^\n]+)/)
|
||||
if (match) numberCat = match[1].trim()
|
||||
}
|
||||
if (numberCat !== dealNumberCatFilter) return false
|
||||
}
|
||||
return true
|
||||
}).sort((a, b) => {
|
||||
let aVal = a[dealSortField] || ''
|
||||
let bVal = b[dealSortField] || ''
|
||||
|
|
@ -660,13 +687,13 @@ export default function List() {
|
|||
{/* 行情搜索框 */}
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<input type="text"
|
||||
placeholder="🔍 搜索行情..."
|
||||
placeholder="🔍 搜索冠字号、编号、价格..."
|
||||
value={dealSearch}
|
||||
onChange={e => setDealSearch(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
border: dealSearch ? '1px solid rgba(59, 130, 246, 0.5)' : '1px solid rgba(255,255,255,0.1)',
|
||||
color: '#fff',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '8px',
|
||||
|
|
@ -675,6 +702,134 @@ export default function List() {
|
|||
boxSizing: 'border-box'
|
||||
}}
|
||||
/>
|
||||
{dealSearch && (
|
||||
<button
|
||||
onClick={() => setDealSearch('')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '24px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
border: 'none',
|
||||
borderRadius: '50%',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
color: '#fff',
|
||||
fontSize: '12px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 筛选条件按钮 */}
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: 'rgba(255,255,255,0.03)', borderRadius: '8px' }}>
|
||||
{/* 第一行:版别筛选 */}
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: '11px', marginRight: '8px' }}>版别:</span>
|
||||
{[
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '20', label: '20版' },
|
||||
{ value: '19', label: '19版' },
|
||||
{ value: '18', label: '18版' },
|
||||
{ value: '17', label: '17版' },
|
||||
{ value: '16', label: '16版' },
|
||||
].map(item => (
|
||||
<button key={item.value} onClick={() => setDealVersionFilter(item.value)}
|
||||
style={{
|
||||
marginRight: '6px',
|
||||
marginBottom: '4px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
background: dealVersionFilter === item.value ? '#fbbf24' : 'rgba(255,255,255,0.08)',
|
||||
color: dealVersionFilter === item.value ? '#1e293b' : '#94a3b8',
|
||||
border: dealVersionFilter === item.value ? 'none' : '1px solid rgba(255,255,255,0.1)'
|
||||
}}>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 第二行:包装类型筛选 */}
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: '11px', marginRight: '8px' }}>包装:</span>
|
||||
{[
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '单张', label: '单张' },
|
||||
{ value: '标十', label: '标十' },
|
||||
{ value: '标百', label: '标百' },
|
||||
].map(item => (
|
||||
<button key={item.value} onClick={() => setDealPackagingFilter(item.value)}
|
||||
style={{
|
||||
marginRight: '6px',
|
||||
marginBottom: '4px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
background: dealPackagingFilter === item.value ? '#a78bfa' : 'rgba(255,255,255,0.08)',
|
||||
color: dealPackagingFilter === item.value ? '#fff' : '#94a3b8',
|
||||
border: dealPackagingFilter === item.value ? 'none' : '1px solid rgba(255,255,255,0.1)'
|
||||
}}>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 第三行:号码分类筛选 */}
|
||||
<div>
|
||||
<span style={{ color: '#94a3b8', fontSize: '11px', marginRight: '8px' }}>号码:</span>
|
||||
{[
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '圆圆号', label: '圆圆号' },
|
||||
{ value: '倒置号', label: '倒置号' },
|
||||
{ value: '金马号', label: '金马号' },
|
||||
{ value: '天马号', label: '天马号' },
|
||||
{ value: '钻石号', label: '钻石号' },
|
||||
{ value: '永恒号', label: '永恒号' },
|
||||
{ value: '带7号', label: '带7号' },
|
||||
{ value: '带4号', label: '带4号' },
|
||||
].map(item => (
|
||||
<button key={item.value} onClick={() => setDealNumberCatFilter(item.value)}
|
||||
style={{
|
||||
marginRight: '6px',
|
||||
marginBottom: '4px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
background: dealNumberCatFilter === item.value ? '#22c55e' : 'rgba(255,255,255,0.08)',
|
||||
color: dealNumberCatFilter === item.value ? '#fff' : '#94a3b8',
|
||||
border: dealNumberCatFilter === item.value ? 'none' : '1px solid rgba(255,255,255,0.1)'
|
||||
}}>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 清除筛选按钮 */}
|
||||
{(dealVersionFilter || dealPackagingFilter || dealNumberCatFilter) && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setDealVersionFilter('')
|
||||
setDealPackagingFilter('')
|
||||
setDealNumberCatFilter('')
|
||||
}}
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(239, 68, 68, 0.2)',
|
||||
color: '#ef4444',
|
||||
border: 'none'
|
||||
}}>
|
||||
✕ 清除筛选
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{dealsLoading ? (
|
||||
|
|
@ -682,7 +837,16 @@ export default function List() {
|
|||
) : filteredDeals.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '60px 0' }}>
|
||||
<div style={{ fontSize: '50px', opacity: 0.3 }}>📊</div>
|
||||
<div style={{ color: '#64748b', marginTop: '16px' }}>{dealSearch ? '没有匹配的行情' : '暂无行情记录'}</div>
|
||||
<div style={{ color: '#64748b', marginTop: '16px' }}>
|
||||
{(dealSearch || dealVersionFilter || dealPackagingFilter || dealNumberCatFilter)
|
||||
? '没有匹配的行情'
|
||||
: '暂无行情记录'}
|
||||
</div>
|
||||
{(dealSearch || dealVersionFilter || dealPackagingFilter || dealNumberCatFilter) && (
|
||||
<div style={{ color: '#64748b', fontSize: '11px', marginTop: '8px' }}>
|
||||
已选筛选:{dealSearch && `搜索"${dealSearch}" `}{dealVersionFilter && `版别${dealVersionFilter} `}{dealPackagingFilter && `包装${dealPackagingFilter} `}{dealNumberCatFilter && `号码${dealNumberCatFilter}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* Purchase - 我的认购/团购页面
|
||||
* Version: 0.0.2 (2026-04-30)
|
||||
* 认购群管理、加入、查看详情
|
||||
* 更新:创建群表单新增6个字段(群名、介绍、人数、总数、周期、开始日期)
|
||||
* Version: 0.2.0 (2026-05-01)
|
||||
* 认购群管理、加入、待审批
|
||||
* 更新:待审批Tab、成员审批、申请状态显示
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
|
@ -20,6 +20,9 @@ function Purchase() {
|
|||
const [groupMembers, setGroupMembers] = useState([])
|
||||
const [groupCollections, setGroupCollections] = useState([])
|
||||
const [showCollectionForm, setShowCollectionForm] = useState(false)
|
||||
const [editingMember, setEditingMember] = useState(null)
|
||||
const [editingGroup, setEditingGroup] = useState(null)
|
||||
const [viewingMember, setViewingMember] = useState(null)
|
||||
const [newGroup, setNewGroup] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
|
|
@ -30,7 +33,10 @@ function Purchase() {
|
|||
})
|
||||
const [newCollection, setNewCollection] = useState({ number: 'J0________', price: '' })
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeTab, setActiveTab] = useState('my') // my/all
|
||||
const [activeTab, setActiveTab] = useState('my') // my/all/pending
|
||||
const [pendingGroups, setPendingGroups] = useState([]) // 我创建的群中有待审批成员的
|
||||
const [pendingMembersMap, setPendingMembersMap] = useState({}) // 每个群的待审批成员
|
||||
const [processedMembers, setProcessedMembers] = useState({}) // 已审批过的成员
|
||||
|
||||
useEffect(() => {
|
||||
const userData = localStorage.getItem('user')
|
||||
|
|
@ -47,6 +53,16 @@ function Purchase() {
|
|||
const fetchGroups = async () => {
|
||||
setLoading(true)
|
||||
const token = localStorage.getItem('token')
|
||||
const userData = localStorage.getItem('user')
|
||||
|
||||
// 未登录:直接返回,页面不黑屏
|
||||
if (!token || !userData) {
|
||||
setAllGroups([])
|
||||
setMyGroups([])
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取我参与的认购群
|
||||
const myRes = await fetch(`${API_BASE}/api/purchase/my-groups`, {
|
||||
|
|
@ -59,6 +75,34 @@ function Purchase() {
|
|||
const allRes = await fetch(`${API_BASE}/api/purchase/groups`)
|
||||
const allData = await allRes.json()
|
||||
setAllGroups(allData || [])
|
||||
|
||||
// 获取我创建的群中有待审批成员的(只有我创建的群)
|
||||
const userData = localStorage.getItem('user')
|
||||
const currentUser = userData ? JSON.parse(userData) : null
|
||||
|
||||
const pendingRes = await fetch(`${API_BASE}/api/purchase/my-groups`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
const pendingData = await pendingRes.json()
|
||||
// 筛选我是群主且有待审批成员的群
|
||||
const groupsWithPending = (pendingData || []).filter(g => g.creator_id === currentUser?.f99_90_id && g.total_pending > 0)
|
||||
setPendingGroups(groupsWithPending)
|
||||
|
||||
// 获取每个群待审批成员列表
|
||||
const membersMap = {}
|
||||
for (const g of groupsWithPending) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/purchase/groups/${g.id}/pending-members`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
const data = await res.json()
|
||||
// 确保是数组
|
||||
membersMap[g.id] = Array.isArray(data) ? data : []
|
||||
} catch (e) {
|
||||
membersMap[g.id] = []
|
||||
}
|
||||
}
|
||||
setPendingMembersMap(membersMap)
|
||||
} catch (e) {
|
||||
console.error('获取认购群失败:', e)
|
||||
}
|
||||
|
|
@ -110,7 +154,7 @@ function Purchase() {
|
|||
})
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
alert('加入成功!')
|
||||
alert(data.message || '已申请,等待群主审批')
|
||||
fetchGroups()
|
||||
} else {
|
||||
alert(data.detail || '加入失败')
|
||||
|
|
@ -205,6 +249,7 @@ function Purchase() {
|
|||
if (res.ok) {
|
||||
alert('提交成功!')
|
||||
setNewCollection({ number: 'J0________', price: '' })
|
||||
setShowCollectionForm(false)
|
||||
viewGroupDetail(selectedGroup.id)
|
||||
fetchGroups()
|
||||
} else {
|
||||
|
|
@ -240,6 +285,102 @@ function Purchase() {
|
|||
}
|
||||
}
|
||||
|
||||
const approveMember = async (memberId, groupId, action) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token) return
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/purchase/groups/${groupId}/members/${memberId}/approve?action=${action}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
alert(action === 'approve' ? '已批准' : '已拒绝')
|
||||
// 标记为已处理
|
||||
const processed = {...processedMembers}
|
||||
processed[memberId] = action
|
||||
setProcessedMembers(processed)
|
||||
// 从pendingMembersMap中移除该成员
|
||||
const newMap = {...pendingMembersMap}
|
||||
newMap[groupId] = (newMap[groupId] || []).filter(m => m.id !== memberId)
|
||||
setPendingMembersMap(newMap)
|
||||
// 如果该群没有更多待审批成员,从pendingGroups中移除
|
||||
if ((newMap[groupId] || []).length === 0) {
|
||||
setPendingGroups(pendingGroups.filter(g => g.id !== groupId))
|
||||
}
|
||||
// 同时刷新整个列表
|
||||
fetchGroups()
|
||||
} else {
|
||||
alert(data.detail || '操作失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('操作失败:', e)
|
||||
alert('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const updateMember = async () => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token || !editingMember || !selectedGroup) return
|
||||
|
||||
// 找当前用户的成员ID
|
||||
const memberId = groupMembers.find(m => m.user_id === user.f99_90_id)?.id
|
||||
if (!memberId) { alert('您不是该群成员'); return }
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/purchase/groups/${selectedGroup.id}/members/${memberId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(editingMember)
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
alert('更新成功!')
|
||||
setEditingMember(null)
|
||||
viewGroupDetail(selectedGroup.id)
|
||||
} else {
|
||||
alert(data.detail || '更新失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新失败:', e)
|
||||
alert('更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const updateGroup = async () => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token || !editingGroup || !selectedGroup) return
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/purchase/groups/${selectedGroup.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(editingGroup)
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
alert('更新成功!')
|
||||
setEditingGroup(null)
|
||||
fetchGroups()
|
||||
// 刷新详情
|
||||
const group = allGroups.find(g => g.id === selectedGroup.id) || myGroups.find(g => g.id === selectedGroup.id)
|
||||
if (group) setSelectedGroup(group)
|
||||
} else {
|
||||
alert(data.detail || '更新失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新失败:', e)
|
||||
alert('更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const formatMoney = (val) => {
|
||||
if (!val || val === 0) return '¥0'
|
||||
return '¥' + parseFloat(val).toLocaleString()
|
||||
|
|
@ -248,16 +389,18 @@ function Purchase() {
|
|||
// 渲染认购群卡片
|
||||
const renderGroupCard = (group, showJoin = false) => {
|
||||
const isMyGroup = myGroups.some(g => g.id === group.id)
|
||||
const isPendingGroup = pendingGroups.some(g => g.id === group.id) // 待审批
|
||||
const isClosed = group.status === 'closed'
|
||||
const isApplied = isPendingGroup && !isMyGroup
|
||||
return (
|
||||
<div
|
||||
key={group.id}
|
||||
onClick={() => viewGroupDetail(group.id)}
|
||||
style={{
|
||||
background: isClosed ? 'rgba(80,80,80,0.3)' : isMyGroup ? 'linear-gradient(135deg, rgba(16,185,129,0.15) 0%, rgba(16,185,129,0.05) 100%)' : 'linear-gradient(135deg, rgba(59,130,246,0.15) 0%, rgba(59,130,246,0.05) 100%)',
|
||||
background: isClosed ? 'rgba(80,80,80,0.3)' : isMyGroup ? 'linear-gradient(135deg, rgba(16,185,129,0.15) 0%, rgba(16,185,129,0.05) 100%)' : isApplied ? 'linear-gradient(135deg, rgba(251,191,36,0.15) 0%, rgba(251,191,36,0.05) 100%)' : 'linear-gradient(135deg, rgba(59,130,246,0.15) 0%, rgba(59,130,246,0.05) 100%)',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
border: isClosed ? '1px solid rgba(100,100,100,0.3)' : isMyGroup ? '1px solid rgba(16,185,129,0.2)' : '1px solid rgba(59,130,246,0.2)',
|
||||
border: isClosed ? '1px solid rgba(100,100,100,0.3)' : isMyGroup ? '1px solid rgba(16,185,129,0.2)' : isApplied ? '1px solid rgba(251,191,36,0.2)' : '1px solid rgba(59,130,246,0.2)',
|
||||
cursor: 'pointer',
|
||||
marginBottom: '12px',
|
||||
opacity: isClosed ? 0.7 : 1
|
||||
|
|
@ -274,6 +417,8 @@ function Purchase() {
|
|||
<div style={{ background: '#555', color: '#aaa', borderRadius: '6px', padding: '8px 12px', fontSize: '12px' }}>已结束</div>
|
||||
) : isMyGroup ? (
|
||||
<div style={{ background: '#10b981', color: '#fff', borderRadius: '6px', padding: '8px 16px', fontSize: '13px' }}>已加入</div>
|
||||
) : isApplied ? (
|
||||
<div style={{ background: '#fbbf24', color: '#000', borderRadius: '6px', padding: '8px 16px', fontSize: '13px' }}>待审批</div>
|
||||
) : showJoin && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); joinGroup(group.id) }}
|
||||
|
|
@ -287,21 +432,27 @@ function Purchase() {
|
|||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
加入
|
||||
申请加入
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isMyGroup && !isClosed && (
|
||||
<div style={{ display: 'flex', gap: '16px', marginTop: '12px', color: 'rgba(255,255,255,0.7)', fontSize: '13px' }}>
|
||||
<span>藏品: {group.total_collections}件</span>
|
||||
<span>总价: {formatMoney(group.total_amount)}</span>
|
||||
<span>均价: {formatMoney(group.avg_price)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 群详情弹窗
|
||||
if (selectedGroup) {
|
||||
const isMyGroup = myGroups.some(g => g.id === selectedGroup.id)
|
||||
const isClosed = selectedGroup.status === 'closed'
|
||||
const canViewDetails = isMyGroup && !isClosed
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh', overflowY: 'auto', WebkitOverflowScrolling: 'touch',
|
||||
|
|
@ -346,9 +497,70 @@ function Purchase() {
|
|||
结束
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 编辑按钮:群主修改群信息,成员修改个人信息 */}
|
||||
{user && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (selectedGroup.creator_id === user.f99_90_id) {
|
||||
// 群主:修改群信息
|
||||
setEditingGroup({
|
||||
name: selectedGroup.name,
|
||||
description: selectedGroup.description || '',
|
||||
cycle_months: selectedGroup.cycle_months || '',
|
||||
total_members: selectedGroup.total_members || '',
|
||||
total_collections: selectedGroup.total_collections || ''
|
||||
})
|
||||
} else {
|
||||
// 成员:修改个人信息(包含群主可修改的字段)
|
||||
const member = groupMembers.find(m => m.user_id === user.f99_90_id)
|
||||
setEditingMember({
|
||||
user_name: user.f01_01_name,
|
||||
wechat_name: member?.wechat_name || '',
|
||||
contact: member?.contact || '',
|
||||
purchase_count: member?.purchase_count || 0,
|
||||
paid_amount: member?.paid_amount || 0,
|
||||
notes: member?.notes || ''
|
||||
})
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
background: '#3b82f6',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '8px 12px',
|
||||
fontSize: '13px',
|
||||
cursor: 'pointer',
|
||||
marginLeft: '8px'
|
||||
}}
|
||||
>
|
||||
{selectedGroup.creator_id === user.f99_90_id ? '修改群信息' : '修改我的信息'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
{/* 群介绍 - 所有人都可见 */}
|
||||
{selectedGroup.description && (
|
||||
<div style={{ marginBottom: '16px', padding: '12px', background: 'rgba(255,255,255,0.05)', borderRadius: '8px' }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '12px', marginBottom: '4px' }}>群介绍</div>
|
||||
<div style={{ color: '#fff', fontSize: '14px' }}>{selectedGroup.description}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 未加入:显示申请加入按钮 */}
|
||||
{!isMyGroup && !isClosed && (
|
||||
<button
|
||||
onClick={() => joinGroup(selectedGroup.id)}
|
||||
style={{ width: '100%', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '14px', fontSize: '16px', fontWeight: 'bold', cursor: 'pointer', marginBottom: '20px' }}
|
||||
>
|
||||
申请加入该认购群
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 敏感信息:只有已加入的群才可见 */}
|
||||
{canViewDetails ? (
|
||||
<>
|
||||
<div style={{
|
||||
background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)',
|
||||
borderRadius: '12px',
|
||||
|
|
@ -382,13 +594,17 @@ function Purchase() {
|
|||
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px', marginBottom: '12px' }}>参与成员</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||
{groupMembers.map(m => (
|
||||
<div key={m.id} style={{
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
borderRadius: '20px',
|
||||
padding: '6px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px'
|
||||
<div
|
||||
key={m.id}
|
||||
onClick={() => setViewingMember(m)}
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
borderRadius: '20px',
|
||||
padding: '6px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
<div style={{ width: '20px', height: '20px', borderRadius: '50%', background: '#3b82f6', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '10px' }}>
|
||||
{m.user_name?.charAt(0) || '?'}
|
||||
|
|
@ -429,7 +645,7 @@ function Purchase() {
|
|||
<span style={{ color: c.paid ? '#10b981' : '#ef4444', fontSize: '11px' }}>
|
||||
{c.paid ? '✓已结清' : '○未结清'}
|
||||
</span>
|
||||
{user && selectedGroup && selectedGroup.creator_id === user.id && (
|
||||
{user && selectedGroup && selectedGroup.creator_id === user.f99_90_id && (
|
||||
<button
|
||||
onClick={() => setEditCollection({...c})}
|
||||
style={{ background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '4px', padding: '4px 8px', fontSize: '10px' }}
|
||||
|
|
@ -440,10 +656,188 @@ function Purchase() {
|
|||
</div>
|
||||
))}
|
||||
{groupCollections.length === 0 && <div style={{ color: 'rgba(255,255,255,0.3)', fontSize: '13px', textAlign: 'center', padding: '20px' }}>暂无认购藏品</div>}
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.id && selectedGroup.status === 'active' && (
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.f99_90_id && selectedGroup.status === 'active' && (
|
||||
<button onClick={() => setShowCollectionForm(!showCollectionForm)} style={{ marginTop: '12px', width: '100%', background: showCollectionForm ? '#ef4444' : '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}>{showCollectionForm ? '收起' : '录入认购藏品'}</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* 修改成员信息弹窗 */}
|
||||
{editingMember && (
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
|
||||
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxWidth: '400px' }}>
|
||||
<div style={{ color: '#fff', fontSize: '16px', marginBottom: '16px', fontWeight: 'bold' }}>修改个人信息</div>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>用户名</div>
|
||||
<input
|
||||
value={editingMember.user_name || ''}
|
||||
onChange={(e) => setEditingMember({...editingMember, user_name: e.target.value})}
|
||||
placeholder="用户名"
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }}
|
||||
/>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>微信名</div>
|
||||
<input
|
||||
value={editingMember.wechat_name || ''}
|
||||
onChange={(e) => setEditingMember({...editingMember, wechat_name: e.target.value})}
|
||||
placeholder="微信名"
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }}
|
||||
/>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>联系方式</div>
|
||||
<input
|
||||
value={editingMember.contact || ''}
|
||||
onChange={(e) => setEditingMember({...editingMember, contact: e.target.value})}
|
||||
placeholder="手机号或微信"
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }}
|
||||
/>
|
||||
|
||||
{/* 群主可以修改的字段 */}
|
||||
{user && selectedGroup && user.f99_90_id === selectedGroup.creator_id && (
|
||||
<>
|
||||
<div style={{ fontSize: '12px', color: '#fbbf24', marginBottom: '4px' }}>认购数量(仅群主可修改)</div>
|
||||
<input
|
||||
type="number"
|
||||
value={editingMember.purchase_count || 0}
|
||||
onChange={(e) => setEditingMember({...editingMember, purchase_count: parseInt(e.target.value) || 0})}
|
||||
placeholder="认购数量"
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }}
|
||||
/>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#fbbf24', marginBottom: '4px' }}>已付金额(仅群主可修改)</div>
|
||||
<input
|
||||
type="number"
|
||||
value={editingMember.paid_amount || 0}
|
||||
onChange={(e) => setEditingMember({...editingMember, paid_amount: parseFloat(e.target.value) || 0})}
|
||||
placeholder="已付金额"
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }}
|
||||
/>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#fbbf24', marginBottom: '4px' }}>备注(仅群主可修改)</div>
|
||||
<textarea
|
||||
value={editingMember.notes || ''}
|
||||
onChange={(e) => setEditingMember({...editingMember, notes: e.target.value})}
|
||||
placeholder="备注"
|
||||
rows={3}
|
||||
style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px', resize: 'none' }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
onClick={() => setEditingMember(null)}
|
||||
style={{ flex: 1, background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', cursor: 'pointer' }}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={updateMember}
|
||||
style={{ flex: 1, background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 查看成员信息弹窗 */}
|
||||
{viewingMember && (
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
|
||||
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxWidth: '400px' }}>
|
||||
<div style={{ color: '#fff', fontSize: '18px', marginBottom: '16px', fontWeight: 'bold', textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span></span>
|
||||
<span>{viewingMember.user_name}</span>
|
||||
<button onClick={() => setViewingMember(null)} style={{ background: 'transparent', border: 'none', color: '#fff', fontSize: '20px', cursor: 'pointer' }}>×</button>
|
||||
</div>
|
||||
|
||||
<div style={{ background: 'rgba(255,255,255,0.05)', borderRadius: '8px', padding: '12px', marginBottom: '12px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>微信名</span>
|
||||
<span style={{ color: '#fff', fontSize: '14px' }}>{viewingMember.wechat_name || '-'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>联系方式</span>
|
||||
<span style={{ color: '#fff', fontSize: '14px' }}>{viewingMember.contact || '-'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>角色</span>
|
||||
<span style={{ color: viewingMember.role === 'creator' ? '#fbbf24' : '#fff', fontSize: '14px' }}>{viewingMember.role === 'creator' ? '👑 群主' : '成员'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>状态</span>
|
||||
<span style={{ color: viewingMember.status === 'approved' ? '#10b981' : '#fbbf24', fontSize: '14px' }}>{viewingMember.status === 'approved' ? '已确认' : '待审核'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>认购数量</span>
|
||||
<span style={{ color: '#fff', fontSize: '14px' }}>{viewingMember.purchase_count || 0}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>已付金额</span>
|
||||
<span style={{ color: '#10b981', fontSize: '14px' }}>¥{(viewingMember.paid_amount || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{viewingMember.notes && (
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px', marginBottom: '4px' }}>备注</div>
|
||||
)}
|
||||
{viewingMember.notes && (
|
||||
<div style={{ color: '#fff', fontSize: '13px', background: 'rgba(255,255,255,0.05)', borderRadius: '8px', padding: '8px' }}>{viewingMember.notes}</div>
|
||||
)}
|
||||
|
||||
{/* 群主可以编辑该成员 */}
|
||||
{user && selectedGroup && user.f99_90_id === selectedGroup.creator_id && viewingMember.role !== 'creator' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setViewingMember(null)
|
||||
setEditingMember({
|
||||
user_name: viewingMember.user_name,
|
||||
wechat_name: viewingMember.wechat_name || '',
|
||||
contact: viewingMember.contact || '',
|
||||
purchase_count: viewingMember.purchase_count || 0,
|
||||
paid_amount: viewingMember.paid_amount || 0,
|
||||
notes: viewingMember.notes || ''
|
||||
})
|
||||
}}
|
||||
style={{ marginTop: '16px', width: '100%', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}
|
||||
>
|
||||
编辑该成员信息
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 修改群信息弹窗 */}
|
||||
{editingGroup && (
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
|
||||
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxWidth: '400px' }}>
|
||||
<div style={{ color: '#fff', fontSize: '16px', marginBottom: '16px', fontWeight: 'bold' }}>修改群信息</div>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>群名称</div>
|
||||
<input value={editingGroup.name || ''} onChange={(e) => setEditingGroup({...editingGroup, name: e.target.value})} placeholder="群名称" style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }} />
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>群介绍</div>
|
||||
<textarea value={editingGroup.description || ''} onChange={(e) => setEditingGroup({...editingGroup, description: e.target.value})} placeholder="群介绍" rows={3} style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px', resize: 'none' }} />
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>参与人数</div>
|
||||
<input type="number" value={editingGroup.total_members || ''} onChange={(e) => setEditingGroup({...editingGroup, total_members: e.target.value})} placeholder="参与人数" style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }} />
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>总认购数</div>
|
||||
<input type="number" value={editingGroup.total_collections || ''} onChange={(e) => setEditingGroup({...editingGroup, total_collections: e.target.value})} placeholder="总认购数" style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '12px' }} />
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '4px' }}>认购周期(月)</div>
|
||||
<input type="number" value={editingGroup.cycle_months || ''} onChange={(e) => setEditingGroup({...editingGroup, cycle_months: e.target.value})} placeholder="认购周期" style={{ width: '100%', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '8px', padding: '12px', color: '#fff', fontSize: '14px', marginBottom: '16px' }} />
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button onClick={() => setEditingGroup(null)} style={{ flex: 1, background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', cursor: 'pointer' }}>取消</button>
|
||||
<button onClick={updateGroup} style={{ flex: 1, background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 编辑弹窗 */}
|
||||
{editCollection && (
|
||||
|
|
@ -530,7 +924,7 @@ function Purchase() {
|
|||
)}
|
||||
|
||||
{/* 提交认购藏品表单 - 仅群主可提交 */}
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.id && selectedGroup.status === 'active' && showCollectionForm && (
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.f99_90_id && selectedGroup.status === 'active' && showCollectionForm && (
|
||||
<div style={{
|
||||
marginTop: '16px',
|
||||
marginBottom: '80px',
|
||||
|
|
@ -553,18 +947,16 @@ function Purchase() {
|
|||
if (el) window.__numberInputs = window.__numberInputs || [];
|
||||
window.__numberInputs[i] = el;
|
||||
}}
|
||||
maxLength={1}
|
||||
defaultValue={i === 0 ? '0' : ''}
|
||||
onChange={(e) => {
|
||||
let v = e.target.value
|
||||
if (v) {
|
||||
v = v.replace(/\D/g, '')
|
||||
if (v) {
|
||||
const num = newCollection.number || 'J0________'
|
||||
const arr = num.split('')
|
||||
arr[i+1] = v
|
||||
setNewCollection({...newCollection, number: arr.join('')})
|
||||
if (i < 8 && v) window.__numberInputs[i+1]?.focus()
|
||||
}
|
||||
let v = e.target.value.replace(/\D/g, '').slice(-1)
|
||||
if (v || i === 0) {
|
||||
const num = newCollection.number || 'J0________'
|
||||
const arr = num.split('')
|
||||
arr[i+1] = i === 0 ? '0' : (v || '_')
|
||||
setNewCollection({...newCollection, number: arr.join('')})
|
||||
if (i < 8 && v) window.__numberInputs[i+1]?.focus()
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
|
|
@ -658,7 +1050,7 @@ function Purchase() {
|
|||
)}
|
||||
|
||||
{/* 非群主提示 */}
|
||||
{selectedGroup && user && !(selectedGroup.creator_id === user.id) && selectedGroup.status === 'active' && (
|
||||
{selectedGroup && user && !(selectedGroup.creator_id === user.f99_90_id) && selectedGroup.status === 'active' && (
|
||||
<div style={{ marginTop: '16px', marginBottom: '80px', background: '#1e293b', borderRadius: '12px', padding: '16px', textAlign: 'center', color: 'rgba(255,255,255,0.5)' }}>
|
||||
仅群主可提交认购藏品
|
||||
</div>
|
||||
|
|
@ -725,11 +1117,53 @@ function Purchase() {
|
|||
>
|
||||
全部认购群 ({allGroups.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('pending')}
|
||||
style={{
|
||||
flex: 1,
|
||||
background: activeTab === 'pending' ? '#fbbf24' : 'rgba(255,255,255,0.1)',
|
||||
color: activeTab === 'pending' ? '#000' : '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
padding: '12px',
|
||||
fontSize: '14px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
待审批 {pendingGroups.length > 0 && `(${Object.values(pendingMembersMap).reduce((a, arr) => a + (Array.isArray(arr) ? arr.length : 0), 0)})`}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 认购群列表 */}
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: 'rgba(255,255,255,0.5)' }}>加载中...</div>
|
||||
) : activeTab === 'pending' ? (
|
||||
<div>
|
||||
{pendingGroups.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: 'rgba(255,255,255,0.3)' }}>暂无待审批成员</div>
|
||||
) : pendingGroups.map(group =>
|
||||
<div key={group.id} style={{ background: 'rgba(251,191,36,0.15)', borderRadius: '12px', padding: '16px', marginBottom: '16px' }}>
|
||||
<div style={{ color: '#fff', fontSize: '16px', fontWeight: 'bold', marginBottom: '12px' }}>{group.name} - 待审批成员</div>
|
||||
{(pendingMembersMap[group.id] || []).map(m => {
|
||||
const isProcessed = processedMembers[m.id]
|
||||
return (
|
||||
<div key={m.id} style={{ background: isProcessed ? 'rgba(100,100,100,0.3)' : 'rgba(255,255,255,0.05)', borderRadius: '8px', padding: '12px', marginBottom: '8px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', opacity: isProcessed ? 0.6 : 1 }}>
|
||||
<div>
|
||||
<div style={{ color: '#fff', fontSize: '14px', fontWeight: 'bold' }}>{m.user_name}</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px', marginTop: '4px' }}>{m.wechat_name || '未填写微信名'} · {m.contact || '未填写联系方式'}</div>
|
||||
{isProcessed && <div style={{ color: '#fbbf24', fontSize: '12px', marginTop: '4px' }}>已{isProcessed === 'approve' ? '批准' : '拒绝'}</div>}
|
||||
</div>
|
||||
{!isProcessed && (
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button onClick={() => approveMember(m.id, group.id, 'reject')} style={{ background: '#ef4444', color: '#fff', border: 'none', borderRadius: '6px', padding: '8px 16px', fontSize: '12px', cursor: 'pointer' }}>拒绝</button>
|
||||
<button onClick={() => approveMember(m.id, group.id, 'approve')} style={{ background: '#10b981', color: '#fff', border: 'none', borderRadius: '6px', padding: '8px 16px', fontSize: '12px', fontWeight: 'bold', cursor: 'pointer' }}>同意</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{(activeTab === 'my' ? myGroups : allGroups).map(group =>
|
||||
|
|
|
|||
Loading…
Reference in New Issue