feat: full-field display for message/staff/accounts (raw_json + drawers)

Replicates the Customer all-fields pattern across the remaining three
entities. Backend now exposes `raw_json` on MessageOut / StaffOut /
AccountOut so the frontend can render every 云客 source field without a
second endpoint. Each entity gains a Chinese-label field utility + an
el-drawer surfacing all fields (structured-first, raw_json fallback,
unknown keys under 其他).

Backend:
- schemas.py: add `raw_json: dict | None` to MessageOut / StaffOut /
  AccountOut. MessageOut/StaffOut pick the column up via from_attributes;
  AccountOut is built explicitly in the router, which now threads
  `raw_json` through from both WechatAccount and WhatsappAccount rows.
- tests: new raw_json serialization tests for messages, staff, accounts
  (assert populated dict round-trips verbatim + NULL serializes as null).

Frontend (yunkes/):
- New utils: messageFields.ts, staffFields.ts, accountFields.ts — each
  with a 中文 label map, ordered groups, structured-column preference,
  and an 其他 fallback for unknown raw_json keys.
- Message: clicking a chat bubble opens an all-fields drawer (referMsg
  renders as pretty-printed JSON). Image preview + file download links
  stopPropagation so their own interactions still work.
- Staff: row click → drawer; columns relabeled (员工ID, 销售手机).
- Accounts: new AccountList.vue at /accounts with a 账号 nav item,
  unified WeChat/WhatsApp table (渠道/账号ID/昵称/微信号/归属员工/好友数/
  群数/收发/在职/最后更新), channel filter, pagination, row-click drawer.
- api: add Account type + getAccounts(); add raw_json to Message/Staff.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-01 17:47:50 +08:00
parent e5af1a2bc2
commit 6ac47a1203
16 changed files with 1562 additions and 13 deletions
+20 -3
View File
@@ -68,7 +68,12 @@ class DepartmentOut(BaseModel):
class StaffOut(BaseModel):
"""Staff member (employee) row."""
"""Staff member (employee) row.
The structured columns power the list table; the verbatim upstream record
is exposed as ``raw_json`` so the frontend can render the all-fields drawer
without a second endpoint. Picked up automatically via ``from_attributes``.
"""
model_config = ConfigDict(from_attributes=True)
@@ -78,6 +83,7 @@ class StaffOut(BaseModel):
sale_phone: str | None = None
dept_id: str | None = None
is_on_job: bool | None = None
raw_json: dict[str, Any] | None = None
class AccountOut(BaseModel):
@@ -87,7 +93,9 @@ class AccountOut(BaseModel):
have different PK / column names; this view projects both onto a single
shape keyed by ``channel`` + ``account_id`` so the frontend can render one
list. Built explicitly in the route handler (not via ``from_attributes``)
because no single ORM row carries this schema.
because no single ORM row carries this schema. ``raw_json`` carries the
verbatim upstream record (wechatId/alias/sendNum/receiveNum/dbSize/...) so
the all-fields drawer can render every column the 云客 微信号管理 page shows.
"""
channel: str
@@ -99,6 +107,7 @@ class AccountOut(BaseModel):
contacts_num: int | None = None
group_num: int | None = None
is_on_job: bool | None = None
raw_json: dict[str, Any] | None = None
# ---------------------------------------------------------------------------
@@ -107,7 +116,14 @@ class AccountOut(BaseModel):
class MessageOut(BaseModel):
"""Chat timeline row — a single message (text/media/voice/...)."""
"""Chat timeline row — a single message (text/media/voice/...).
The structured columns power the bubble rendering; the verbatim upstream
record is exposed as ``raw_json`` so the frontend can render the all-fields
drawer (msgSvrId/keyId, file/fileTh/fileHd, md5/md5Th/md5Hd, paymsgid,
referMsg, imgPath, ...) without a second endpoint. Picked up automatically
via ``from_attributes``.
"""
model_config = ConfigDict(from_attributes=True)
@@ -132,6 +148,7 @@ class MessageOut(BaseModel):
sender_name: str | None = None
sender_head_url: str | None = None
timestamp: int | None = None
raw_json: dict[str, Any] | None = None
class GroupOut(BaseModel):