280e418ccf
- 反馈:新增 GET /admin/api/feedbacks/summary(待审核/已采纳/未采纳/合计计数); 列表联表带出完整手机号 + 昵称(供 admin 点手机号查该用户全部反馈) - 低价审核:列表加 sort_by/sort_order(提交时间排序);联表带出手机号/昵称, 并按 comparison_record_id 关联比价记录带出 trace_id/trace_url + 机型/ROM/Android/app 版本 - feedback 表新增 app_version/device_model/rom_name/android_version 四列 + alembic 迁移; /api/v1/feedback 提交接口接收并落库,供 admin 反馈页展示「提交版本号」「机型OS版本」 (字段可空,旧端不传不影响提交) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.6 KiB
Python
52 lines
2.6 KiB
Python
"""用户反馈表(帮助与反馈)。
|
|
|
|
每条 = 用户一次提交。content 必填;contact 原为必填(微信/QQ/手机),原型改版后客户端不再采集,
|
|
新数据存空串(列保持 NOT NULL,免迁移;历史数据仍有值);images 为可选的截图 URL 列表
|
|
(/media/feedback/...,JSON 存)。status: pending(审核中)/adopted(已采纳)/rejected(未采纳)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Text, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class Feedback(Base):
|
|
__tablename__ = "feedback"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
user_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("user.id"), index=True, nullable=False
|
|
)
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
contact: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
# 截图 URL 列表(相对路径,如 ["/media/feedback/u1_ab12.jpg"]);无图为 None
|
|
images: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
# 提交时的端环境快照(admin 排查用;客户端改版带上后的新反馈才有,历史数据为 NULL)
|
|
app_version: Mapped[str | None] = mapped_column(String(32), nullable=True) # 我们 app versionName
|
|
device_model: Mapped[str | None] = mapped_column(String(64), nullable=True) # Build.MODEL
|
|
rom_name: Mapped[str | None] = mapped_column(String(32), nullable=True) # OemDetector os:ColorOS/MIUI/...
|
|
android_version: Mapped[str | None] = mapped_column(String(16), nullable=True) # Build.VERSION.RELEASE
|
|
# pending(审核中) / adopted(已采纳) / rejected(未采纳)
|
|
status: Mapped[str] = mapped_column(String(16), nullable=False, default="pending", index=True)
|
|
reject_reason: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
reward_coins: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
# 审核批注:采纳时可写采纳要点,未采纳时也可保留运营侧备注
|
|
review_note: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
reviewed_by_admin_id: Mapped[int | None] = mapped_column(
|
|
Integer, ForeignKey("admin_user.id"), nullable=True
|
|
)
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
|
|
)
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return f"<Feedback id={self.id} user_id={self.user_id} status={self.status}>"
|