支持在 Chat 页面显示,隐藏对话列表

This commit is contained in:
RockYang
2025-01-06 19:17:18 +08:00
parent cffc722622
commit 7da5b7163c
27 changed files with 404 additions and 393 deletions

View File

@@ -1,12 +1,15 @@
<template>
<div class="chat-page">
<el-container>
<el-aside>
<el-aside v-show="store.chatListExtend">
<div class="flex w-full justify-center pt-3 pb-3">
<img :src="logo" style="max-height: 40px" :alt="title" v-if="logo !== ''"/>
<h2 v-else>{{ title }}</h2>
</div>
<div class="media-page">
<el-button @click="_newChat" type="primary" class="newChat">
<el-icon style="margin-right: 5px">
<Plus />
</el-icon>
<i class="iconfont icon-new-chat mr-1"></i>
新建对话
</el-button>
@@ -19,7 +22,7 @@
</template>
</el-input>
</div>
<el-scrollbar :height="chatBoxHeight">
<el-scrollbar :height="chatListHeight">
<div class="content">
<el-row v-for="chat in chatList" :key="chat.chat_id">
<div :class="chat.chat_id === chatId ? 'chat-list-item active' : 'chat-list-item'" @click="loadChat(chat)">
@@ -111,7 +114,7 @@
</div>
</div>
<div>
<div class="flex justify-center">
<div id="container" :style="{ height: mainWinHeight + 'px' }">
<div class="chat-box" id="chat-box" :style="{ height: chatBoxHeight + 'px' }">
<div v-if="showHello">
@@ -229,26 +232,29 @@
</div>
</template>
<script setup>
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import { Delete, Edit, InfoFilled, More, Plus, Promotion, Search, Share, VideoPause } from "@element-plus/icons-vue";
import {Delete, Edit, InfoFilled, More, Promotion, Search, Share, VideoPause} from "@element-plus/icons-vue";
import "highlight.js/styles/a11y-dark.css";
import { isMobile, randString, removeArrayItem, UUID } from "@/utils/libs";
import { ElMessage, ElMessageBox } from "element-plus";
import { httpGet, httpPost } from "@/utils/http";
import { useRouter } from "vue-router";
import {isMobile, randString, removeArrayItem, UUID} from "@/utils/libs";
import {ElMessage, ElMessageBox} from "element-plus";
import {httpGet, httpPost} from "@/utils/http";
import {useRouter} from "vue-router";
import Clipboard from "clipboard";
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
import Welcome from "@/components/Welcome.vue";
import { useSharedStore } from "@/store/sharedata";
import {useSharedStore} from "@/store/sharedata";
import FileSelect from "@/components/FileSelect.vue";
import FileList from "@/components/FileList.vue";
import ChatSetting from "@/components/ChatSetting.vue";
import BackTop from "@/components/BackTop.vue";
import { closeLoading, showLoading, showMessageError } from "@/utils/dialog";
import {closeLoading, showLoading, showMessageError} from "@/utils/dialog";
import MarkdownIt from "markdown-it";
import emoji from "markdown-it-emoji";
const title = ref("GeekAI-智能助手");
const logo = ref("");
const models = ref([]);
const modelID = ref(0);
const chatData = ref([]);
@@ -256,7 +262,7 @@ const allChats = ref([]); // 会话列表
const chatList = ref(allChats.value);
const mainWinHeight = ref(0); // 主窗口高度
const chatBoxHeight = ref(0); // 聊天内容框高度
const leftBoxHeight = ref(0);
const chatListHeight = ref(0); // 聊天列表高度
const loading = ref(false);
const loginUser = ref(null);
const roles = ref([]);
@@ -311,6 +317,7 @@ if (!chatId.value) {
// 查询对话信息
httpGet("/api/chat/detail", { chat_id: chatId.value })
.then((res) => {
document.title = res.data.title;
roleId.value = res.data.role_id;
modelID.value = res.data.model_id;
})
@@ -324,14 +331,12 @@ getSystemInfo()
.then((res) => {
config.value = res.data;
title.value = config.value.title;
logo.value = res.data.bar_logo;
})
.catch((e) => {
ElMessage.error("获取系统配置失败:" + e.message);
});
import MarkdownIt from "markdown-it";
import emoji from "markdown-it-emoji";
const md = new MarkdownIt({
breaks: true,
html: true,
@@ -540,17 +545,10 @@ const getRoleById = function (rid) {
};
const resizeElement = function () {
chatBoxHeight.value = window.innerHeight - 101 - 82 - 38;
chatListHeight.value = window.innerHeight - 240;
// chatBoxHeight.value = window.innerHeight;
// mainWinHeight.value = window.innerHeight - 101;
mainWinHeight.value = window.innerHeight - 59;
// mainWinHeight.value = window.innerHeight;
// leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
// leftBoxHeight.value = window.innerHeight - 90 - 82;
leftBoxHeight.value = window.innerHeight - 90 - 100;
mainWinHeight.value = window.innerHeight - 50;
chatBoxHeight.value = window.innerHeight - 101 - 82 - 38;
};
const _newChat = () => {

View File

@@ -1,28 +1,20 @@
<template>
<div class="layout">
<div class="tab-box">
<div class="flex-center-col big-top-title xxx">
<div class="flex-center-col" @click="isCollapse = !isCollapse">
<el-tooltip content="展开菜单" placement="right" v-if="isCollapse">
<i class="iconfont icon-expand"></i>
</el-tooltip>
</div>
<div class="flex" :class="{ 'top-collapse': !isCollapse }">
<div class="top-avatar flex">
<span class="title" v-if="!isCollapse">{{ title }}</span>
<el-tooltip :content="title" placement="right">
<img :src="logo" alt="" :class="{ marr: !isCollapse }" v-if="isCollapse" />
</el-tooltip>
</div>
<div class="menuIcon xxx" @click="isCollapse = !isCollapse">
<el-tooltip content="关闭菜单" placement="right" v-if="!isCollapse">
<div class="flex-center-col pt-2 mb-2">
<div class="flex flex-center-col">
<div class="menuIcon" @click="store.setChatListExtend(!store.chatListExtend)">
<el-tooltip content="隐藏对话列表" placement="right" v-if="store.chatListExtend">
<i class="iconfont icon-colspan"></i>
</el-tooltip>
<el-tooltip content="展开对话列表" placement="right" v-else>
<i class="iconfont icon-expand"></i>
</el-tooltip>
</div>
</div>
</div>
<div class="menu-list" :style="{ width: isCollapse ? '65px' : '170px' }" :class="{ 'menu-list-collapse': !isCollapse }">
<div class="menu-list">
<ul>
<li
class="menu-list-item flex-center-col"
@@ -31,29 +23,28 @@
@click="changeNav(item)"
:class="item.url === curPath ? 'active' : ''"
>
<span v-if="item.icon.startsWith('icon')" :class="{ 'mr-1 ml-2': !isCollapse }">
<span v-if="item.icon.startsWith('icon')">
<i class="iconfont" :class="item.icon"></i>
</span>
<el-image :src="item.icon" class="el-icon ml-1" v-else />
<div class="menu-title" :class="{ 'menu-title-collapse': !isCollapse }">
<div class="menu-title">
{{ item.name }}
</div>
</li>
</ul>
<!-- 更多 -->
<div class="bot" :style="{ width: isCollapse ? '65px' : '170px' }">
<div class="bot-line"></div>
<el-popover v-if="moreNavs.length > 0" placement="right-end" trigger="hover">
<template #reference>
<li class="menu-list-item flex-center-col">
<el-icon><CirclePlus /></el-icon>
<div class="menu-title">更多</div>
</li>
</template>
<template #default>
<ul class="more-menus">
<li
<!-- 更多 -->
<div class="bot p-2">
<div class="bot-line"></div>
<el-popover v-if="moreNavs.length > 0" placement="right-end" trigger="hover">
<template #reference>
<li class="menu-list-item flex-center-col">
<i class="iconfont icon-more"/>
</li>
</template>
<template #default>
<ul class="more-menus">
<li
v-for="(item, index) in moreNavs"
:key="item.url"
:class="{
@@ -61,57 +52,52 @@
moreTitle: index !== 3 && index !== 4,
twoTittle: index === 3 || index === 4,
}"
>
<a @click="changeNav(item)">
>
<a @click="changeNav(item)">
<span v-if="item.icon.startsWith('icon')" class="mr-2">
<i class="iconfont" :class="item.icon"></i>
</span>
<el-image :src="item.icon" style="width: 20px; height: 20px" v-else />
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
</a>
</li>
</ul>
</template>
</el-popover>
<el-popover placement="right-end" trigger="hover" v-if="loginUser.id">
<template #reference>
<li class="menu-list-item flex-center-col">
<el-icon><Setting /></el-icon>
<div v-if="!isCollapse">设置</div>
<el-image :src="item.icon" style="width: 20px; height: 20px" v-else/>
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
</a>
</li>
</template>
<template #default>
<ul class="more-menus setting-menus">
<li>
<div @click="showConfigDialog = true" class="flex">
<el-icon>
<UserFilled />
</el-icon>
<span class="username title">{{ loginUser.nickname }}</span>
</div>
</li>
<li>
<a @click="logout" class="flex">
<i class="iconfont icon-logout"></i>
<span class="title">退出登录</span>
</a>
</li>
</ul>
</template>
</el-popover>
<li class="menu-bot-item">
<a :href="gitURL" class="link-button" target="_blank" v-if="!license.de_copy && !isCollapse">
<i class="iconfont icon-github"></i>
</a>
<a @click="router.push('/')" class="link-button">
<i class="iconfont icon-house"></i>
</a>
<ThemeChange />
</li>
</ul>
</template>
</el-popover>
<el-popover placement="right-end" trigger="hover" v-if="loginUser.id">
<template #reference>
<li class="menu-list-item flex-center-col">
<i class="iconfont icon-config"/>
</li>
</template>
<template #default>
<ul class="more-menus setting-menus">
<li>
<div @click="showConfigDialog = true" class="flex">
<el-icon>
<UserFilled/>
</el-icon>
<span class="username title">{{ loginUser.nickname }}</span>
</div>
</li>
<li>
<a @click="logout" class="flex">
<i class="iconfont icon-logout"></i>
<span class="title">退出登录</span>
</a>
</li>
</ul>
</template>
</el-popover>
<div class="menu-bot-item">
<a @click="router.push('/')" class="link-button">
<i class="iconfont icon-house"></i>
</a>
<div class="pl-1">
<ThemeChange/>
</div>
</div>
</ul>
</div>
</div>
</div>
<el-scrollbar class="right-main">
@@ -141,21 +127,19 @@
</template>
<script setup>
import { CirclePlus, Setting, UserFilled } from "@element-plus/icons-vue";
import {UserFilled} from "@element-plus/icons-vue";
import ThemeChange from "@/components/ThemeChange.vue";
import { useRouter } from "vue-router";
import { onMounted, ref, watch } from "vue";
import { httpGet } from "@/utils/http";
import { ElMessage } from "element-plus";
import { checkSession, getLicenseInfo, getSystemInfo } from "@/store/cache";
import { removeUserToken } from "@/store/session";
import { useSharedStore } from "@/store/sharedata";
import {useRouter} from "vue-router";
import {onMounted, ref, watch} from "vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
import {removeUserToken} from "@/store/session";
import {useSharedStore} from "@/store/sharedata";
import ConfigDialog from "@/components/UserInfoDialog.vue";
import { showMessageError } from "@/utils/dialog";
import {showMessageError} from "@/utils/dialog";
import LoginDialog from "@/components/LoginDialog.vue";
import { substr } from "@/utils/libs";
const isCollapse = ref(true);
const router = useRouter();
const logo = ref("");
const mainNavs = ref([]);
@@ -163,13 +147,11 @@ const moreNavs = ref([]);
const curPath = ref();
const title = ref("");
const avatarImg = ref("/images/avatar/default.jpg");
const store = useSharedStore();
const loginUser = ref({});
const routerViewKey = ref(0);
const showConfigDialog = ref(false);
const license = ref({ de_copy: true });
const gitURL = ref(process.env.VUE_APP_GIT_URL);
const showLoginDialog = ref(false);
/**

View File

@@ -9,25 +9,24 @@
</div>
<div class="menu-item">
<span v-if="!license.de_copy">
<el-tooltip v-if="!license.de_copy" class="box-item" content="部署文档" placement="bottom">
<el-tooltip class="box-item" content="部署文档" placement="bottom">
<a :href="docsURL" class="link-button mr-2" target="_blank">
<i class="iconfont icon-book"></i>
</a>
</el-tooltip>
<el-tooltip v-if="!license.de_copy" class="box-item" content="项目源码" placement="bottom">
<el-tooltip class="box-item" content="Github 源码" placement="bottom">
<a :href="gitURL" class="link-button" target="_blank">
<i class="iconfont icon-github"></i>
</a>
</el-tooltip>
<el-tooltip class="box-item" content="Gitee 源码" placement="bottom">
<a :href="gitURL" class="link-button" target="_blank">
<i class="iconfont icon-gitee"></i>
</a>
</el-tooltip>
</span>
<span v-if="!isLogin">
<!-- <el-button @click="router.push('/login')" class="shadow" round
>登录</el-button
>
<el-button @click="router.push('/register')" class="shadow" round
>注册</el-button
> -->
<el-button @click="router.push('/login')" class="btn-go animate__animated animate__pulse animate__infinite" round>登录/注册</el-button>
</span>
</div>
@@ -59,14 +58,14 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import {onMounted, ref} from "vue";
import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue";
import ThemeChange from "@/components/ThemeChange.vue";
import { httpGet } from "@/utils/http";
import { ElMessage } from "element-plus";
import { checkSession, getLicenseInfo, getSystemInfo } from "@/store/cache";
import { isMobile } from "@/utils/libs";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
import {isMobile} from "@/utils/libs";
const router = useRouter();
@@ -81,7 +80,7 @@ const license = ref({ de_copy: true });
const isLogin = ref(false);
const docsURL = ref(process.env.VUE_APP_DOCS_URL);
const gitURL = ref(process.env.VUE_APP_GIT_URL);
const gitURL = ref(process.env.VUE_APP_GITHUB_URL);
const navs = ref([]);
const iconMap = ref({
@@ -150,7 +149,6 @@ const setContent = () => {
displayedChars.value = [];
if (timer) clearInterval(timer);
timer = setInterval(setContent, interTime.value);
return;
} else {
const nextChar = slogan.value.charAt(initAnimation.value.length);
initAnimation.value += slogan.value.charAt(initAnimation.value.length); // 逐字符追加
@@ -164,7 +162,7 @@ const setContent = () => {
const rainbowColor = (index) => {
const hue = (index * 40) % 360; // 每个字符间隔40度形成彩虹色
return `hsl(${hue}, 90%, 50%)`; // 色调(hue),饱和度(70%),亮度(50%)
};
}
</script>
<style lang="stylus" scoped>

View File

@@ -15,8 +15,8 @@
<el-form-item label="网站Slogan" prop="slogan">
<el-input v-model="system['slogan']" />
</el-form-item>
<el-form-item label="网站 LOGO" prop="logo">
<el-input v-model="system['logo']" placeholder="网站LOGO图片">
<el-form-item label="圆形 LOGO" prop="logo">
<el-input v-model="system['logo']" placeholder="正方形或者圆形 Logo">
<template #append>
<el-upload :auto-upload="true" :show-file-list="false" @click="beforeUpload('logo')" :http-request="uploadImg">
<el-icon class="uploader-icon">
@@ -26,7 +26,18 @@
</template>
</el-input>
</el-form-item>
<el-form-item label="条形 LOGO" prop="logo">
<el-input v-model="system['bar_logo']" placeholder="长方形 Logo">
<template #append>
<el-upload :auto-upload="true" :show-file-list="false" @click="beforeUpload('bar_logo')"
:http-request="uploadImg">
<el-icon class="uploader-icon">
<UploadFilled/>
</el-icon>
</el-upload>
</template>
</el-input>
</el-form-item>
<el-form-item>
<template #label>
<div class="label-title">
@@ -359,17 +370,17 @@
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { httpGet, httpPost } from "@/utils/http";
import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs";
import { ElMessage, ElMessageBox } from "element-plus";
import { CloseBold, InfoFilled, Select, UploadFilled } from "@element-plus/icons-vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {CloseBold, InfoFilled, Select, UploadFilled} from "@element-plus/icons-vue";
import MdEditor from "md-editor-v3";
import "md-editor-v3/lib/style.css";
import Menu from "@/views/admin/Menu.vue";
import { copyObj, dateFormat } from "@/utils/libs";
import {copyObj, dateFormat} from "@/utils/libs";
import ItemsInput from "@/components/ui/ItemsInput.vue";
import { useSharedStore } from "@/store/sharedata";
import {useSharedStore} from "@/store/sharedata";
const activeName = ref("basic");
const system = ref({ models: [] });