14.6.4 ChatGPT聊天界面页脚
编写文件frontend/src/views/chat/layout/sider/Footer.vue实现ChatGPT聊天界面的底部页脚功能,主要实现代码如下所示。
<script setup lang='ts'>
import { defineAsyncComponent, ref } from 'vue'
import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
const Setting = defineAsyncComponent(() => import('@/components/common/Setting/index.vue'))
const show = ref(false)
</script>
<template>
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
<div class="flex-1 flex-shrink-0 overflow-hidden">
<UserAvatar />
</div>
<HoverButton @click="show = true">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:settings-4-line" />
</span>
</HoverButton>
<Setting v-if="show" v-model:visible="show" />
</footer>
</template>
上述代码用于在聊天窗口底部显示设置按钮和用户头像。上述代码使用了多个Vue.js实用工具来管理底部的状态和显示内容,例如ref等。页脚还包括了一个HoverButton组件,用于在点击时显示设置弹出框,并在用户头像旁边显示“设置”图标。同时,该组件还引用了Setting组件,并在需要时显示该组件的模态对话框。最后,该组件还包括了一个UserAvatar组件,用于在聊天窗口底部显示当前用户的头像。
14.6.5 ChatGPT历史会话列表
编写文件frontend/src/views/chat/layout/sider/List.vue,功能是在聊天窗口左侧边栏中显示历史会话列表。主要实现代码如下所示。
const { isMobile } = useBasicLayout()
const appStore = useAppStore()
const chatStore = useChatStore()
const dataSources = computed(() => chatStore.history)
async function handleSelect({ uuid }: Chat.History) {
if (isActive(uuid))
return
if (chatStore.active)
chatStore.updateHistory(chatStore.active, { isEdit: false })
await chatStore.setActive(uuid)
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
event?.stopPropagation()
chatStore.updateHistory(uuid, { isEdit })
}
function handleDelete(index: number, event?: MouseEvent | TouchEvent) {
event?.stopPropagation()
chatStore.deleteHistory(index)
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
const handleDeleteDebounce = debounce(handleDelete, 600)
function handleEnter({ uuid }: Chat.History, isEdit: boolean, event: KeyboardEvent) {
event?.stopPropagation()
if (event.key === 'Enter')
chatStore.updateHistory(uuid, { isEdit })
}
function isActive(uuid: number) {
return chatStore.active === uuid
}
</script>
<template>
<NScrollbar class="px-4">
<div class="flex flex-col gap-2 text-sm">
<template v-if="!dataSources.length">
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
<span>{{ $t('common.noData') }}</span>
</div>
</template>
<template v-else>
<div v-for="(item, index) of dataSources" :key="index">
<a
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
@click="handleSelect(item)"
>
<span>
<SvgIcon icon="ri:message-3-line" />
</span>
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
<NInput
v-if="item.isEdit"
v-model:value="item.title" size="tiny"
@keypress="handleEnter(item, false, $event)"
/>
<span v-else>{{ item.title }}</span>
</div>
<div v-if="isActive(item.uuid)" class="absolute z-10 flex visible right-1">
<template v-if="item.isEdit">
<button class="p-1" @click="handleEdit(item, false, $event)">
<SvgIcon icon="ri:save-line" />
</button>
</template>
<template v-else>
<button class="p-1">
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
</button>
<NPopconfirm placement="bottom" @positive-click="handleDeleteDebounce(index, $event)">
<template #trigger>
<button class="p-1">
<SvgIcon icon="ri:delete-bin-line" />
</button>
</template>
{{ $t('chat.deleteHistoryConfirm') }}
</NPopconfirm>
</template>
在上述代码中使用了多个Vue.js实用工具来管理历史会话的状态和显示,例如computed等。在上述代码中还提供了一个NScrollbar组件,用于在历史会话列表超出可见区域时提供滚动条。同时,该组件还包括了一个循环结构,用于在历史会话列表中显示每个会话的标题和是否编辑状态,并在选中某个会话时高亮显示该会话的标题。此外,在上述代码中还包括了一些自定义方法,例如handleEdit()、handleDelete()和handleEnter(),分别用于实现编辑单个会话、删除单个会话和保存编辑会话功能。最后,上述代码还引用了NPopconfirm组件,用于在需要删除单个会话时弹出确认提示框。
14.6.6 ChatGPT聊天页面布局
编写文件frontend/src/views/chat/layout/Layout.vue,功能是实现ChatGPT聊天页面的整体布局。主要实现代码如下所示。
const router = useRouter()
const appStore = useAppStore()
const chatStore = useChatStore()
const authStore = useAuthStore()
router.replace({ name: 'Chat', params: { uuid: chatStore.active } })
const { isMobile } = useBasicLayout()
const collapsed = computed(() => appStore.siderCollapsed)
const needPermission = computed(() => !!authStore.session?.auth && !authStore.token)
const getMobileClass = computed(() => {
if (isMobile.value)
return ['rounded-none', 'shadow-none']
return ['border', 'rounded-md', 'shadow-md', 'dark:border-neutral-800']
})
const getContainerClass = computed(() => {
return [
'h-full',
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
]
})
</script>
<template>
<div class="h-full dark:bg-[#24272e] transition-all" :class="[isMobile ? 'p-0' : 'p-4']">
<div class="h-full overflow-hidden" :class="getMobileClass">
<NLayout class="z-40 transition" :class="getContainerClass" has-sider>
<Sider />
<NLayoutContent class="h-full">
<RouterView v-slot="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
</RouterView>
</NLayoutContent>
</NLayout>
</div>
<Permission :visible="needPermission" />
</div>
</template>
对上述代码的具体说明如下:
- 使用computed等Vue组件来管理布局的状态和显示功能。
- 上述代码包括了一个NLayout组件,用于定义基本的应用程序布局,其中包括一个侧边栏(Sider)和一个内容区域(NLayoutContent)。
- 引用了RouterView组件,用于在不同的路由路径下渲染不同的组件。
- 还包括了一些自定义方法,例如getMobileClass()、getContainerClass()和needPermission(),分别用于在移动设备上设置样式、定义容器类和检查用户会话权限。
- 还引用了Permission组件,并在需要时显示该组件的模态对话框,以向未登录用户显示登录提示。