feat: 反馈加来源/场景/运营回复 + 提现类型筛选 (#105)

用户反馈后台:区分反馈类型(比价反馈/普通反馈)+ 审核可给用户留言;提现后台:按提现类型筛选。

- feedback 表加 source(profile/comparison)/scene/admin_reply + 迁移;提交接口 /api/v1/feedback
  接收 source/scene,来源判定显式 source 优先、否则据 scene 有无派生(有=comparison);
  /records 带回 scene + admin_reply。
- admin 反馈:列表加「反馈类型」筛选;采纳/拒绝支持存 admin_reply(给用户的回复,用户端可见);
  FeedbackOut 带出 source/scene/admin_reply。
- admin 提现:WithdrawOut 暴露 source,列表加「提现类型」筛选(coin_cash=福利页提现 / invite_cash=邀请提现)。
- 补 4 项测试:来源派生、回复存取、反馈按类型筛选、提现按类型筛选。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #105
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
This commit was merged in pull request #105.
This commit is contained in:
2026-07-02 19:50:14 +08:00
committed by marco
parent 4630fd017b
commit 4512b6ecac
14 changed files with 254 additions and 7 deletions
+11
View File
@@ -23,6 +23,14 @@ class Feedback(Base):
)
content: Mapped[str] = mapped_column(Text, nullable=False)
contact: Mapped[str] = mapped_column(String(128), nullable=False)
# 反馈来源入口:profile(「我的」页反馈入口)/ comparison(比价结果页反馈入口)。
# admin 据此展示/筛选「反馈类型」。旧数据与「我的」页反馈均为 profile(server_default)。
# 客户端显式传 source 优先;未传时由 scene 有无派生(见 api/v1/feedback.submit_feedback)。
source: Mapped[str] = mapped_column(
String(16), nullable=False, default="profile", server_default="profile", index=True
)
# 比价反馈的「问题场景」(找错商品/优惠不对/比价太慢…),比价结果页反馈才有;普通反馈为 NULL。
scene: Mapped[str | None] = mapped_column(String(32), nullable=True)
# 截图 URL 列表(相对路径,如 ["/media/feedback/u1_ab12.jpg"]);无图为 None
images: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
# 提交时的端环境快照(admin 排查用;客户端改版带上后的新反馈才有,历史数据为 NULL)
@@ -36,6 +44,9 @@ class Feedback(Base):
reward_coins: Mapped[int | None] = mapped_column(Integer, nullable=True)
# 审核批注:采纳时可写采纳要点,未采纳时也可保留运营侧备注
review_note: Mapped[str | None] = mapped_column(String(256), nullable=True)
# 运营给用户的回复留言(**用户端可见**,采纳/未采纳都可填,随「我的反馈」历史下发)。
# 与 review_note(内部备注,用户不可见)、reject_reason(未采纳原因)区分开。
admin_reply: 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
)