feat: remaining frontend views (groups/calls/sms/sync) + messageout media fields
Backend (Part A): - MessageOut: add file_th_url, file_hd_url, duration, transcription_text, md5 (room_id + sender_head_url already existed) so the chat timeline can render image thumbnails, voice duration/transcription, and avatars from a single response. - PhoneCallOut: add record_file so the Calls view can offer recording playback. - SmsOut: add content (full body) so the SMS detail dialog shows the whole text. - Tests: red->green for each new field (media fields default None + populated, record_file round-trip, content round-trip). Suite 267 -> 271. Frontend (Part B): - api/index.ts: wrappers getGroups/getGroupMembers/getCalls/getSms/getSyncStatus + Group/GroupMember/PhoneCall/Sms/SyncState/SyncRun/SyncStatus types in types.ts (mirror the *Out pydantic schemas). - views/GroupList.vue (/groups): table + channel filter + pagination; row click opens the group timeline. - views/CallList.vue (/calls): table + call-type filter (phone_call/voice_call/ all); "播放录音" action opens an el-dialog with <audio controls>. - views/SmsList.vue (/sms): table; row click opens a detail dialog with the full content. - views/SyncStatus.vue (/sync): two tables (states + recent_runs) with a manual refresh; run status color-coded. - ChatTimeline.vue: reused for both 1:1 DM (talkerId) and group (roomId) by branching the query; group title + per-member sender already handled by MessageBubble. - router: add /groups /calls /sms /sync + /group/:channel/:ownerId/:roomId. - layouts/MainLayout.vue: replace the single 客户列表 button with an el-menu (客户 / 群 / 通话 / 短信 / 同步); org tree kept on the left. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,18 @@ const selectedDeptId = computed<string | null>(
|
||||
() => (route.query.department_id as string | undefined) ?? null,
|
||||
)
|
||||
|
||||
// `el-menu` with `router` mode emits when an item is clicked; we drive
|
||||
// `default-active` from the current path so the active item highlights on load
|
||||
// and on browser back/forward. Group/DM chat routes don't appear in the menu —
|
||||
// the active item falls back to none there, which is fine.
|
||||
const activeMenu = computed(() => {
|
||||
// Map chat timeline routes back to their entry list so the nav reflects the
|
||||
// "owning" section while a conversation is open.
|
||||
if (route.name === 'chat') return '/customers'
|
||||
if (route.name === 'group-chat') return '/groups'
|
||||
return route.path
|
||||
})
|
||||
|
||||
const treeProps = { label: 'label', children: 'children' }
|
||||
|
||||
// el-tree's `filter-node-method` is called for every node on each keystroke.
|
||||
@@ -53,10 +65,6 @@ function onChannelChange(value: string | number | boolean | undefined): void {
|
||||
channelStore.setChannel(value as Channel | 'all')
|
||||
}
|
||||
|
||||
function goCustomers(): void {
|
||||
router.push({ name: 'customers' })
|
||||
}
|
||||
|
||||
async function reloadTree(): Promise<void> {
|
||||
try {
|
||||
await org.ensureLoaded(true)
|
||||
@@ -100,10 +108,34 @@ onMounted(async () => {
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span class="yk-header__spacer" />
|
||||
<el-button text @click="goCustomers">
|
||||
<el-icon><ChatLineSquare /></el-icon>
|
||||
<span style="margin-left: 4px">客户列表</span>
|
||||
</el-button>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
mode="horizontal"
|
||||
:ellipsis="false"
|
||||
class="yk-header__nav"
|
||||
router
|
||||
>
|
||||
<el-menu-item index="/customers">
|
||||
<el-icon><ChatLineSquare /></el-icon>
|
||||
<span>客户</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/groups">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>群</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/calls">
|
||||
<el-icon><Phone /></el-icon>
|
||||
<span>通话</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/sms">
|
||||
<el-icon><Message /></el-icon>
|
||||
<span>短信</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/sync">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
<span>同步</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-header>
|
||||
|
||||
<el-container>
|
||||
@@ -190,4 +222,17 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* The horizontal nav lives inside the app header, so strip the default menu
|
||||
* border/background that would otherwise clash with the header's own styling. */
|
||||
.yk-header__nav.el-menu--horizontal {
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.yk-header__nav.el-menu--horizontal :deep(.el-menu-item) {
|
||||
height: var(--yk-header-height);
|
||||
line-height: var(--yk-header-height);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user