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:
@@ -12,7 +12,14 @@ const props = defineProps<{ message: Message }>()
|
||||
* `/api/media/{hash}` streaming endpoint exists for cases where the OSS URL is
|
||||
* missing, but the current MessageOut schema exposes `file_url` only, so we
|
||||
* degrade gracefully via `file_th_url || file_url` (forward-compatible).
|
||||
*
|
||||
* Clicking anywhere on the bubble (other than interactive children such as the
|
||||
* image preview or the file download link) emits `open-detail` so the parent
|
||||
* timeline can open the all-fields drawer for this message. The image preview
|
||||
* and download link stop propagation so their own interactions still work.
|
||||
*/
|
||||
const emit = defineEmits<{ (e: 'open-detail', message: Message): void }>()
|
||||
|
||||
const isSend = computed(() => !!props.message.is_send)
|
||||
|
||||
// Centered (system/divider) rows ignore direction.
|
||||
@@ -41,13 +48,15 @@ const imageThumbUrl = computed(
|
||||
'yk-msg--center': isCentered,
|
||||
}"
|
||||
>
|
||||
<!-- Centered: divider / system -->
|
||||
<!-- Centered: divider / system — not clickable. -->
|
||||
<div v-if="isCentered" class="yk-msg__divider">
|
||||
{{ message.text || (message.type === 'divider' ? '—— 分割线 ——' : '系统消息') }}
|
||||
</div>
|
||||
|
||||
<!-- Normal bubble row -->
|
||||
<div v-else class="yk-msg__row">
|
||||
<!-- Normal bubble row — click opens the all-fields drawer. The image
|
||||
preview + file download link stop propagation so their own clicks
|
||||
(zoom / navigate) don't also open the drawer. -->
|
||||
<div v-else class="yk-msg__row yk-msg__row--clickable" @click="emit('open-detail', message)">
|
||||
<div class="yk-msg__meta">
|
||||
<span>{{ senderLabel }}</span>
|
||||
<span v-if="timeLabel">{{ timeLabel }}</span>
|
||||
@@ -59,7 +68,7 @@ const imageThumbUrl = computed(
|
||||
</div>
|
||||
|
||||
<!-- image -->
|
||||
<div v-else-if="message.type === 'image'">
|
||||
<div v-else-if="message.type === 'image'" @click.stop>
|
||||
<el-image
|
||||
v-if="imageThumbUrl"
|
||||
class="yk-msg__image"
|
||||
@@ -98,7 +107,7 @@ const imageThumbUrl = computed(
|
||||
</div>
|
||||
|
||||
<!-- file -->
|
||||
<div v-else-if="message.type === 'file'">
|
||||
<div v-else-if="message.type === 'file'" @click.stop>
|
||||
<a
|
||||
v-if="message.file_url"
|
||||
class="yk-msg__chip yk-msg__chip--download"
|
||||
|
||||
Reference in New Issue
Block a user