feat: 管理端专家管理完整CRUD(新增创建和删除)
后端:
- ExpertStore.create_expert: 创建专家(含bcrypt密码哈希)
- ExpertStore.delete_expert: 级联删除(咨询+服务关系+专家)
- POST /admin/experts: 创建专家API
- DELETE /admin/experts/{id}: 删除专家API
前端:
- AdminExperts页面加"新增专家"按钮和创建弹窗
- 创建弹窗含姓名/手机号/密码/分类/职称/简介/专长
- 每个专家卡片加删除按钮,二次确认后删除
- api.ts加adminCreateExpert和adminDeleteExpert方法
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1519,6 +1519,74 @@ class ExpertStore:
|
||||
return None
|
||||
return self.get_expert(expert_id)
|
||||
|
||||
def create_expert(self, name: str, phone: str, password: str,
|
||||
category: str, title: str = "", bio: str = "",
|
||||
expertise: str = "", avatar_url: str | None = None,
|
||||
status: str = "active") -> dict:
|
||||
"""创建专家(含登录账号)。
|
||||
|
||||
Args:
|
||||
name: 专家姓名
|
||||
phone: 手机号(登录账号,唯一)
|
||||
password: 明文密码(内部 bcrypt 哈希)
|
||||
category: 分类 legal/decision/emotion/life
|
||||
title: 职称
|
||||
bio: 简介
|
||||
expertise: 擅长领域(逗号分隔)
|
||||
avatar_url: 头像 URL
|
||||
status: active/inactive
|
||||
|
||||
Returns:
|
||||
新建的专家 dict
|
||||
|
||||
Raises:
|
||||
ValueError: 手机号已存在或分类非法
|
||||
"""
|
||||
if category not in self.CATEGORIES:
|
||||
raise ValueError(f"分类应为 {self.CATEGORIES} 之一")
|
||||
if status not in ("active", "inactive"):
|
||||
raise ValueError("状态应为 active 或 inactive")
|
||||
import bcrypt
|
||||
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
with self._conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
# 检查手机号唯一
|
||||
cur.execute("SELECT 1 FROM experts WHERE phone = %s", (phone,))
|
||||
if cur.fetchone():
|
||||
raise ValueError("该手机号已注册为专家")
|
||||
cur.execute(
|
||||
"INSERT INTO experts (name, phone, password, category, title, bio, "
|
||||
"expertise, avatar_url, status) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) "
|
||||
"RETURNING id, name, phone, category, title, bio, expertise, "
|
||||
"avatar_url, rate, rating_count, status, created_at",
|
||||
(name, phone, hashed, category, title or None, bio or None,
|
||||
expertise or None, avatar_url, status),
|
||||
)
|
||||
r = cur.fetchone()
|
||||
return {"id": r[0], "name": r[1], "phone": r[2], "category": r[3],
|
||||
"title": r[4], "bio": r[5], "expertise": r[6], "avatar_url": r[7],
|
||||
"rate": r[8], "rating_count": r[9], "status": r[10],
|
||||
"created_at": str(r[11])}
|
||||
|
||||
def delete_expert(self, expert_id: int) -> bool:
|
||||
"""删除专家(级联删除服务关系和咨询)。
|
||||
|
||||
Returns:
|
||||
True 表示删除成功,False 表示专家不存在
|
||||
"""
|
||||
with self._conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
# 先检查存在
|
||||
cur.execute("SELECT 1 FROM experts WHERE id = %s", (expert_id,))
|
||||
if not cur.fetchone():
|
||||
return False
|
||||
# 级联删除咨询和服务关系
|
||||
cur.execute("DELETE FROM expert_consultations WHERE expert_id = %s", (expert_id,))
|
||||
cur.execute("DELETE FROM expert_services WHERE expert_id = %s", (expert_id,))
|
||||
cur.execute("DELETE FROM experts WHERE id = %s", (expert_id,))
|
||||
return cur.rowcount > 0
|
||||
|
||||
def create_service(self, user_phone: str, expert_id: int,
|
||||
category: str) -> dict:
|
||||
"""用户选定专家,创建服务关系(待专家确认)。
|
||||
|
||||
Reference in New Issue
Block a user