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
View File
@@ -5,6 +5,7 @@
// (the backend treats missing as "no filter").
import { getJson } from './client'
import type {
Account,
CallChannel,
Channel,
Customer,
@@ -102,6 +103,24 @@ export function getStaff(query: StaffQuery = {}): Promise<Page<Staff>> {
)
}
export interface AccountQuery {
/** channel filter; `'all'` (default) drops the param so both channels union. */
channel?: Channel | 'all'
page?: number
page_size?: number
}
export function getAccounts(query: AccountQuery = {}): Promise<Page<Account>> {
return getJson<Page<Account>>(
'/accounts',
clean({
channel: query.channel === 'all' ? undefined : query.channel,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
// ---------------------------------------------------------------------------
// Chat groups
// ---------------------------------------------------------------------------
@@ -197,6 +216,7 @@ export function getSyncStatus(): Promise<SyncStatus> {
}
export type {
Account,
CallChannel,
Channel,
Customer,
+31
View File
@@ -55,6 +55,30 @@ export interface Staff {
sale_phone?: string | null
dept_id?: string | null
is_on_job?: boolean | null
/**
* Verbatim upstream staff record (camelCase keys). Surfaced so the
* all-fields drawer can render every column without enumerating each.
*/
raw_json?: Record<string, unknown> | null
}
/**
* Unified staff messaging account (mirrors backend AccountOut). The two source
* tables (wechat_account / whatsapp_account) are projected onto this single
* shape keyed by `channel` + `account_id`. `raw_json` carries the verbatim
* upstream record (wechatId/alias/sendNum/receiveNum/dbSize/...).
*/
export interface Account {
channel: Channel
account_id: string
nickname?: string | null
user_id?: string | null
user_name?: string | null
phone?: string | null
contacts_num?: number | null
group_num?: number | null
is_on_job?: boolean | null
raw_json?: Record<string, unknown> | null
}
/** Chat group summary row (mirrors backend GroupOut). */
@@ -187,4 +211,11 @@ export interface Message {
/** ms-epoch */
timestamp?: number | null
is_del?: boolean | null
/**
* Verbatim upstream message record (camelCase keys: msgSvrId/keyId, type,
* file/fileTh/fileHd, md5/md5Th/md5Hd, paymsgid, referMsg, talker/oriTalker,
* name/headUrl, wechatId, roomid, ...). Surfaced so the all-fields drawer
* can render every column the 云客 chat detail page shows.
*/
raw_json?: Record<string, unknown> | null
}