支持在 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

@@ -131,7 +131,8 @@ type SystemConfig struct {
Title string `json:"title,omitempty"` // 网站标题 Title string `json:"title,omitempty"` // 网站标题
Slogan string `json:"slogan,omitempty"` // 网站 slogan Slogan string `json:"slogan,omitempty"` // 网站 slogan
AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题 AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题
Logo string `json:"logo,omitempty"` // 形 Logo Logo string `json:"logo,omitempty"` // 形 Logo
BarLogo string `json:"bar_logo,omitempty"` // 条形 Logo
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值 InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力 DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力
InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值 InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值

View File

@@ -92,6 +92,8 @@ func (h *ChatHandler) sendOpenAiMessage(
if strings.HasPrefix(req.Model, "o1-") { if strings.HasPrefix(req.Model, "o1-") {
content := fmt.Sprintf("AI 思考结束,耗时:%d 秒。\n\n", time.Now().Unix()-session.Start) content := fmt.Sprintf("AI 思考结束,耗时:%d 秒。\n\n", time.Now().Unix()-session.Start)
contents = append(contents, "> AI 正在思考中...\n")
contents = append(contents, content)
utils.SendChunkMsg(ws, content) utils.SendChunkMsg(ws, content)
} }

View File

@@ -134,6 +134,7 @@ func (h *WebsocketHandler) Client(c *gin.Context) {
if err != nil { if err != nil {
logger.Error(err, chatModel) logger.Error(err, chatModel)
} }
session.Model.Id = chatModel.Id
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
h.chatHandler.ReqCancelFunc.Put(clientId, cancel) h.chatHandler.ReqCancelFunc.Put(clientId, cancel)
err = h.chatHandler.sendMessage(ctx, session, chatRole, chatMessage.Content, client) err = h.chatHandler.sendMessage(ctx, session, chatRole, chatMessage.Content, client)

View File

@@ -8,4 +8,7 @@ VUE_APP_KEY_PREFIX=GeekAI_DEV_
VUE_APP_TITLE="Geek-AI 创作系统" VUE_APP_TITLE="Geek-AI 创作系统"
VUE_APP_VERSION=v4.1.9 VUE_APP_VERSION=v4.1.9
VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_DOCS_URL=https://docs.geekai.me
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai VUE_APP_GITHUB_URL=https://github.com/yangjian102621/geekai
VUE_APP_GITEE_URL=https://gitee.com/blackfox/geekai
VUE_APP_GITCODE_URL=https://gitcode.com/yangjian102621/geekai

View File

@@ -3,4 +3,6 @@ VUE_APP_WS_HOST=
VUE_APP_KEY_PREFIX=GeekAI_ VUE_APP_KEY_PREFIX=GeekAI_
VUE_APP_VERSION=v4.1.9 VUE_APP_VERSION=v4.1.9
VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_DOCS_URL=https://docs.geekai.me
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai VUE_APP_GITHUB_URL=https://github.com/yangjian102621/geekai
VUE_APP_GITEE_URL=https://gitee.com/blackfox/geekai
VUE_APP_GITCODE_URL=https://gitcode.com/yangjian102621/geekai

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -30,14 +30,6 @@
margin-bottom: 10px margin-bottom: 10px
} }
//
::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
.content { .content {
width: 100% width: 100%
overflow-y: scroll overflow-y: scroll
@@ -130,7 +122,7 @@
.chat-container { .chat-container {
min-width: 0; min-width: 0;
flex: 1; flex: 1;
background-color: var(--el-bg-color) background-color: var(--chat-bg)
color var(--text-fb) color var(--text-fb)
.chat-config { .chat-config {
@@ -186,54 +178,33 @@
width: 100%; width: 100%;
position relative position relative
background: var(--chat-bg) background: var(--chat-bg)
display: flex;
::-webkit-scrollbar { justify-content: center;
width: 12px /* */ padding 0 20px
background #F1F1F1 max-width: 960px;
}
::-webkit-scrollbar-track {
background-color: #e1e1e1;
}
::-webkit-scrollbar-thumb {
background-color: #c1c1c1;
border-radius 12px
}
::-webkit-scrollbar-thumb:hover {
background-color: #A8A8A8;
}
.chat-box { .chat-box {
overflow-y: auto; overflow-y: auto;
//border-bottom: 1px solid #4f4f4f
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IEEdge */
// //
--content-font-size: 16px; --content-font-size: 16px;
--content-color: #c1c1c1; --content-color: #c1c1c1;
font-family: 'Microsoft YaHei', '', Arial, sans-serif; font-family: 'Microsoft YaHei', '', Arial, sans-serif;
padding: 0 0 50px 0; //padding: 0 0 50px 0;
width: 100%;
.chat-line { .chat-line {
font-size: 14px; font-size: 14px;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
} }
::-webkit-scrollbar {
display: none; /* Webkit */
}
} }
.input-box { .input-box {
position absolute position absolute
bottom 0 bottom 0
width 100% width 100%
max-width: 800px;
.input-box-inner { .input-box-inner {
display flex display flex
@@ -363,11 +334,6 @@
} }
} }
#container::-webkit-scrollbar {
width: 0;
height: 0;
}
} }
} }
} }

View File

@@ -1,24 +1,30 @@
.layout{ .layout {
display: flex; display: flex;
position: relative; position: relative;
height: 100vh; height: 100vh;
.big-top-title{
.big-top-title {
padding-top: 10px; padding-top: 10px;
} }
.top-collapse{
.top-collapse {
padding-top: 10px padding-top: 10px
img{
img {
width 24px !important width 24px !important
height: 24px !important height: 24px !important
} }
} }
.tab-box{
.tab-box {
align-items: center align-items: center
background-color: var(--card-bg) background-color: var(--card-bg)
border-right: 1px solid var(--line-box);
// height: 100% // height: 100%
// position: fixed; // position: fixed;
height: 100vh; height: 100vh;
.title{
.title {
font-size: 28px font-size: 28px
height: 40px height: 40px
width 120px width 120px
@@ -26,51 +32,48 @@
word-wrap break-all; word-wrap break-all;
overflow hidden overflow hidden
font-weight: 700 font-weight: 700
color:var(--text-theme-color) color: var(--text-theme-color)
} }
img{
img {
height: 44px height: 44px
object-fit: cover object-fit: cover
border-radius: 50% border-radius: 50%
border: 2px solid #754ff6; border: 2px solid #754ff6;
background: #fff background: #fff
} }
.marr{
.marr {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.flex-center-col{
.flex-center-col {
display flex display flex
align-items center align-items center
flex-direction column flex-direction column
.iconfont { .iconfont {
font-size 22px
}
.icon-expand {
font-size 24px font-size 24px
margin-bottom 10px
cursor pointer cursor pointer
color var(--text-color) color var(--text-color)
} }
.icon-colspan { .icon-new-chat {
font-size 18px color #ffffff
margin-left 3px
cursor pointer
color var(--text-color)
} }
} }
.menu-list-collapse{
.flex-center-col{ .menu-list-collapse {
.flex-center-col {
flex-direction: row; flex-direction: row;
} }
.menu-list-item{
.menu-list-item {
height: 38px; height: 38px;
line-height: 38px; line-height: 38px;
@@ -80,52 +83,49 @@
} }
} }
.menu-list-item:hover, .menu-list-item:hover,
.active{ .active {
background: rgba(79, 89, 102, .122); background: rgba(79, 89, 102, .122);
border-radius: 8px; border-radius: 8px;
.el-icon{
.el-icon {
background: transparent !important; background: transparent !important;
} }
} }
.menu-title{
.menu-title {
font-size: 15px !important; font-size: 15px !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
} }
.menu-list {
width: 65px;
.openicon{ .svg-icon {
font-size: 40px; svg {
color: #754ff6;
}
.menuIcon{
.openicon{
font-size: 28px;
color: #754ff6;
}
}
.menu-list{
margin-top: 20px;
.svg-icon{
svg{
width: 30px; width: 30px;
height: 30px; height: 30px;
} }
} }
.menu-list-item{
.menu-list-item {
// margin-bottom: 10px; // margin-bottom: 10px;
margin: 0 8px 8px; margin: 0 8px 8px;
cursor: pointer; cursor: pointer;
font-weight: 550; font-weight: 550;
&:hover{ color: var(--text-theme-color);
.el-icon{
&:hover {
.el-icon {
background: rgba(79, 89, 102, .122); background: rgba(79, 89, 102, .122);
} }
} }
.el-icon{
.el-icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
padding: 4px; padding: 4px;
@@ -138,94 +138,97 @@
// } // }
} }
&.active{
color: var(--text-color); &.active {
color: var(--text-theme-color);
font-weight: 700; font-weight: 700;
filter: none !important;
.el-icon{
background: rgba(79, 89, 102, .122);
filter: invert(100%);
}
} }
} }
.bot{
.bot {
position: absolute; position: absolute;
bottom: 6px; bottom: 6px;
width 65px;
}
} .bot-line {
.bot-line{
width : 100%; width: 100%;
height: 1px; height: 1px;
background: var(--line-box) background: var(--line-box)
margin: 20px 0 10px 0; margin: 20px 0 10px 0;
} }
.menu-title{
.menu-title {
font-size: 12px; font-size: 12px;
margin-bottom: 6px; margin-bottom: 6px;
} }
.icon-house, .icon-house,
.icon-github{ .icon-github {
font-size: 20px; font-size: 20px;
color: #754ff6; color: #754ff6;
cursor pointer cursor pointer
} }
.menu-bot-item{
display: flex; .menu-bot-item {
align-items: center; display: flex;
justify-content: space-around; align-items: center;
align-items: center; justify-content: space-around;
a{
// margin-right: 46px;
}
} }
} }
::v-deep(.theme-box){
::v-deep(.theme-box) {
position: relative !important; position: relative !important;
right: initial; right: initial;
bottom: initial; bottom: initial;
width: 20px; width: 20px;
height: 20px; height: 20px;
line-height: 20px; line-height: 18px;
.iconfont{
font-size: 15px !important;} .iconfont {
font-size: 15px !important;
}
} }
.right-main{
.right-main {
height: 100%; height: 100%;
// background: #f5f7fd; // background: #f5f7fd;
background: var(--theme-bg-all); background: var(--theme-bg-all);
// background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35)); // background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
width: 100%; width: 100%;
.loginMask{
.loginMask {
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 999; z-index: 999;
} }
.topheader{
.topheader {
display: flex; display: flex;
position: fixed; position: fixed;
right: 8px; right: 8px;
z-index : 999; z-index: 999;
top:0; top: 0;
// width 100%; // width 100%;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
} }
.btn-go{
.btn-go {
background: #754ff6; background: #754ff6;
margin: 10px 10px 0; margin: 10px 10px 0;
} }
} }
.el-popper { .el-popper {
.more-menus { .more-menus {
li { li {
@@ -251,18 +254,21 @@
background: rgba(79, 89, 102, 0.1); background: rgba(79, 89, 102, 0.1);
} }
} }
.setting-menus{
.title{ .setting-menus {
.title {
color: #222226; color: #222226;
} }
.el-icon, .el-icon,
.iconfont{ .iconfont {
font-size: 18px font-size: 18px
margin-right: 6px margin-right: 6px
} }
color: #222226; color: #222226;
} }
.username{
.username {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
@@ -271,21 +277,25 @@
} }
.rightHeightMax{
.rightHeightMax {
height: 100vh; height: 100vh;
max-height: 100vh; max-height: 100vh;
overflow: hidden; overflow: hidden;
} }
.rightHeight{
.rightHeight {
height: calc(100vh - 42px); height: calc(100vh - 42px);
max-height: calc(100vh - 42px); max-height: calc(100vh - 42px);
overflow: hidden; overflow: hidden;
.content{
.content {
padding-top: 42px; padding-top: 42px;
} }
} }
.content{
.content {
height: 100%; height: 100%;
overflow: scroll; overflow: scroll;
} }

View File

@@ -43,7 +43,7 @@
--el-bg-color-overlay: rgba(17, 28, 68, 1); --el-bg-color-overlay: rgba(17, 28, 68, 1);
--el-border-color-light: rgba(255, 255, 255, 0.2); --el-border-color-light: rgba(255, 255, 255, 0.2);
--chat-content-bg:rgba(86, 86, 95, .2); --chat-content-bg:rgba(86, 86, 95, .2);
--chat-content-bg-list:rgba(86, 86, 95, .2); --chat-user-content-bg: #762AA4;
--hover-deep-color:#30323c; --hover-deep-color:#30323c;
//layout //layout
.more-menus li.moreTitle, .more-menus li.moreTitle,
@@ -52,7 +52,7 @@
.setting-menus li .el-icon, .setting-menus li .el-icon,
.setting-menus li .iconfont, .setting-menus li .iconfont,
.layout .tab-box .menu-list-item{ .layout .tab-box .menu-list-item{
filter: invert(100%); //filter: invert(100%);
} }
.more-menus span.title{ .more-menus span.title{
color:#000; color:#000;

View File

@@ -31,8 +31,8 @@
--theme-text-primary: #000; --theme-text-primary: #000;
--theme-text-color-secondary: #666; --theme-text-color-secondary: #666;
--chat-content-bg:#f5f7fc; --chat-content-bg:#f5f7fc;
--chat-user-content-bg: #e0dfff;
--chat-list-bg: #0302020a; --chat-list-bg: #0302020a;
--chat-content-bg-list:#fff;
--chat-wel-bg:rgba(247, 247, 248, 1); --chat-wel-bg:rgba(247, 247, 248, 1);
--hover-deep-color:#fff; --hover-deep-color:#fff;
--el-bg-color-overlay: #fff; --el-bg-color-overlay: #fff;

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4125778 */ font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1734934068681') format('woff2'), src: url('iconfont.woff2?t=1736144380052') format('woff2'),
url('iconfont.woff?t=1734934068681') format('woff'), url('iconfont.woff?t=1736144380052') format('woff'),
url('iconfont.ttf?t=1734934068681') format('truetype'); url('iconfont.ttf?t=1736144380052') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-gitee:before {
content: "\e6d0";
}
.icon-redeem:before { .icon-redeem:before {
content: "\e61a"; content: "\e61a";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,13 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "6905420",
"name": "码云",
"font_class": "gitee",
"unicode": "e6d0",
"unicode_decimal": 59088
},
{ {
"icon_id": "3624396", "icon_id": "3624396",
"name": "兑换码", "name": "兑换码",

Binary file not shown.

View File

@@ -2,22 +2,25 @@
<div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'"> <div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'">
<div class="chat-line-inner"> <div class="chat-line-inner">
<div class="chat-icon"> <div class="chat-icon">
<img :src="data.icon" alt="User" /> <img :src="data.icon" alt="User"/>
</div> </div>
<div class="chat-item"> <div class="chat-item">
<div v-if="files.length > 0" class="file-list-box"> <div v-if="files.length > 0" class="file-list-box">
<div v-for="file in files"> <div v-for="file in files">
<div class="image" v-if="isImage(file.ext)"> <div class="image" v-if="isImage(file.ext)">
<el-image :src="file.url" fit="cover" /> <el-image :src="file.url" fit="cover"/>
</div> </div>
<div class="item" v-else> <div class="item" v-else>
<div class="icon"> <div class="icon">
<el-image :src="GetFileIcon(file.ext)" fit="cover" /> <el-image :src="GetFileIcon(file.ext)" fit="cover"/>
</div> </div>
<div class="body"> <div class="body">
<div class="title"> <div class="title">
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{ file.name }}</el-link> <el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{
file.name
}}
</el-link>
</div> </div>
<div class="info"> <div class="info">
<span>{{ GetFileType(file.ext) }}</span> <span>{{ GetFileType(file.ext) }}</span>
@@ -30,7 +33,7 @@
<div class="content" v-html="content"></div> <div class="content" v-html="content"></div>
<div class="bar" v-if="data.created_at > 0"> <div class="bar" v-if="data.created_at > 0">
<span class="bar-item" <span class="bar-item"
><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span ><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span
> >
<span class="bar-item">tokens: {{ finalTokens }}</span> <span class="bar-item">tokens: {{ finalTokens }}</span>
</div> </div>
@@ -41,22 +44,25 @@
<div class="chat-line chat-line-prompt-chat" v-else> <div class="chat-line chat-line-prompt-chat" v-else>
<div class="chat-line-inner"> <div class="chat-line-inner">
<div class="chat-icon"> <div class="chat-icon">
<img :src="data.icon" alt="User" /> <img :src="data.icon" alt="User"/>
</div> </div>
<div class="chat-item"> <div class="chat-item">
<div v-if="files.length > 0" class="file-list-box"> <div v-if="files.length > 0" class="file-list-box">
<div v-for="file in files"> <div v-for="file in files">
<div class="image" v-if="isImage(file.ext)"> <div class="image" v-if="isImage(file.ext)">
<el-image :src="file.url" fit="cover" /> <el-image :src="file.url" fit="cover"/>
</div> </div>
<div class="item" v-else> <div class="item" v-else>
<div class="icon"> <div class="icon">
<el-image :src="GetFileIcon(file.ext)" fit="cover" /> <el-image :src="GetFileIcon(file.ext)" fit="cover"/>
</div> </div>
<div class="body"> <div class="body">
<div class="title"> <div class="title">
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{ file.name }}</el-link> <el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{
file.name
}}
</el-link>
</div> </div>
<div class="info"> <div class="info">
<span>{{ GetFileType(file.ext) }}</span> <span>{{ GetFileType(file.ext) }}</span>
@@ -71,7 +77,7 @@
</div> </div>
<div class="bar" v-if="data.created_at > 0"> <div class="bar" v-if="data.created_at > 0">
<span class="bar-item" <span class="bar-item"
><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span ><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span
> >
<!-- <span class="bar-item">tokens: {{ finalTokens }}</span>--> <!-- <span class="bar-item">tokens: {{ finalTokens }}</span>-->
</div> </div>
@@ -81,12 +87,12 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import {onMounted, ref} from "vue";
import { Clock } from "@element-plus/icons-vue"; import {Clock} from "@element-plus/icons-vue";
import { httpPost } from "@/utils/http"; import {httpPost} from "@/utils/http";
import hl from "highlight.js"; import hl from "highlight.js";
import { dateFormat, isImage, processPrompt } from "@/utils/libs"; import {dateFormat, isImage, processPrompt} from "@/utils/libs";
import { FormatFileSize, GetFileIcon, GetFileType } from "@/store/system"; import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
import emoji from "markdown-it-emoji"; import emoji from "markdown-it-emoji";
import mathjaxPlugin from "markdown-it-mathjax3"; import mathjaxPlugin from "markdown-it-mathjax3";
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
@@ -101,8 +107,8 @@ const md = new MarkdownIt({
// 显示复制代码按钮 // 显示复制代码按钮
const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span> const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace( <textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
/<\/textarea>/g, /<\/textarea>/g,
"&lt;/textarea>" "&lt;/textarea>"
)}</textarea>`; )}</textarea>`;
if (lang && hl.getLanguage(lang)) { if (lang && hl.getLanguage(lang)) {
const langHtml = `<span class="lang-name">${lang}</span>`; const langHtml = `<span class="lang-name">${lang}</span>`;
@@ -152,17 +158,18 @@ const processFiles = () => {
const linkRegex = /(https?:\/\/\S+)/g; const linkRegex = /(https?:\/\/\S+)/g;
const links = props.data.content.match(linkRegex); const links = props.data.content.match(linkRegex);
if (links) { if (links) {
httpPost("/api/upload/list", { urls: links }) httpPost("/api/upload/list", {urls: links})
.then((res) => { .then((res) => {
files.value = res.data.items; files.value = res.data.items;
for (let link of links) { for (let link of links) {
if (isExternalImg(link, files.value)) { if (isExternalImg(link, files.value)) {
files.value.push({ url: link, ext: ".png" }); files.value.push({url: link, ext: ".png"});
}
} }
} })
}) .catch(() => {
.catch(() => {}); });
for (let link of links) { for (let link of links) {
content.value = content.value.replace(link, ""); content.value = content.value.replace(link, "");
@@ -177,11 +184,11 @@ const isExternalImg = (link, files) => {
<style lang="stylus"> <style lang="stylus">
@import '@/assets/css/markdown/vue.css'; @import '@/assets/css/markdown/vue.css';
.chat-page,.chat-export { .chat-page, .chat-export {
.chat-line-prompt-list { .chat-line-prompt-list {
background-color:var( --chat-content-bg-list); background-color: var(--chat-content-bg-list);
color:var(--theme-text-color-primary); color: var(--theme-text-color-primary);
justify-content: center; justify-content: center;
width 100% width 100%
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
@@ -213,6 +220,7 @@ const isExternalImg = (link, files) => {
.file-list-box { .file-list-box {
display flex display flex
flex-flow column flex-flow column
.image { .image {
display flex display flex
flex-flow row flex-flow row
@@ -225,13 +233,14 @@ const isExternalImg = (link, files) => {
margin-bottom 10px margin-bottom 10px
} }
} }
.item { .item {
display flex display flex
flex-flow row flex-flow row
border-radius 10px border-radius 10px
background-color:var(--chat-content-bg); background-color: var(--chat-content-bg);
border 1px solid #e3e3e3 border 1px solid #e3e3e3
color:var(--theme-text-color-primary); color: var(--theme-text-color-primary);
padding 6px padding 6px
margin-bottom 10px margin-bottom 10px
@@ -241,14 +250,17 @@ const isExternalImg = (link, files) => {
height 40px height 40px
} }
} }
.body { .body {
margin-left 8px margin-left 8px
font-size 14px font-size 14px
.title { .title {
font-weight bold font-weight bold
line-height 24px line-height 24px
color #0D0D0D color #0D0D0D
} }
.info { .info {
color #B4B4B4 color #B4B4B4
@@ -263,7 +275,7 @@ const isExternalImg = (link, files) => {
.content { .content {
word-break break-word; word-break break-word;
padding: 0; padding: 0;
color:var(--theme-text-color-primary); color: var(--theme-text-color-primary);
font-size: var(--content-font-size); font-size: var(--content-font-size);
border-radius: 5px; border-radius: 5px;
overflow: auto; overflow: auto;
@@ -336,11 +348,12 @@ const isExternalImg = (link, files) => {
.chat-item { .chat-item {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
max-width 60% max-width calc(100% - 110px);
.file-list-box { .file-list-box {
display flex display flex
flex-flow column flex-flow column
.image { .image {
display flex display flex
flex-flow row flex-flow row
@@ -353,12 +366,13 @@ const isExternalImg = (link, files) => {
margin-bottom 10px margin-bottom 10px
} }
} }
.item { .item {
display flex display flex
flex-flow row flex-flow row
border-radius 10px border-radius 10px
background-color:var(--chat-content-bg); background-color: var(--chat-content-bg);
color:var(--theme-text-color-primary); color: var(--theme-text-color-primary);
border 1px solid #e3e3e3 border 1px solid #e3e3e3
padding 6px padding 6px
margin-bottom 10px margin-bottom 10px
@@ -369,14 +383,17 @@ const isExternalImg = (link, files) => {
height 40px height 40px
} }
} }
.body { .body {
margin-left 8px margin-left 8px
font-size 14px font-size 14px
.title { .title {
font-weight bold font-weight bold
line-height 24px line-height 24px
color #0D0D0D color #0D0D0D
} }
.info { .info {
color #B4B4B4 color #B4B4B4
@@ -392,35 +409,37 @@ const isExternalImg = (link, files) => {
.content-wrapper { .content-wrapper {
display flex display flex
flex-flow row-reverse flex-flow row-reverse
.content { .content {
word-break break-word; word-break break-word;
padding: 1rem padding: 1rem
color var(--theme-text-primary); color var(--theme-text-primary);
font-size: var(--content-font-size); font-size: var(--content-font-size);
overflow: auto; overflow: auto;
background-color :var(--chat-content-bg); background-color: var(--chat-user-content-bg);
border-radius: 10px 0 10px 10px; border-radius: 10px 0 10px 10px;
img { img {
max-width: 600px; max-width: 600px;
border-radius: 10px; border-radius: 10px;
margin 10px 0 margin 10px 0
}
p {
line-height 1.5
}
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
} }
p {
line-height 1.5
}
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
}
} }
.bar { .bar {
padding 10px 10px 10px 0; padding 10px 10px 10px 0;

View File

@@ -90,9 +90,9 @@
</template> </template>
<script setup> <script setup>
import { Clock, DocumentCopy, Refresh } from "@element-plus/icons-vue"; import {Clock, DocumentCopy, Refresh} from "@element-plus/icons-vue";
import { ElMessage } from "element-plus"; import {ElMessage} from "element-plus";
import { dateFormat, processContent } from "@/utils/libs"; import {dateFormat, processContent} from "@/utils/libs";
import hl from "highlight.js"; import hl from "highlight.js";
import emoji from "markdown-it-emoji"; import emoji from "markdown-it-emoji";
import mathjaxPlugin from "markdown-it-mathjax3"; import mathjaxPlugin from "markdown-it-mathjax3";
@@ -237,7 +237,6 @@ const reGenerate = (prompt) => {
table { table {
width 100% width 100%
margin-bottom 1rem margin-bottom 1rem
color #212529
border-collapse collapse; border-collapse collapse;
border 1px solid #dee2e6; border 1px solid #dee2e6;
background-color:var(--chat-content-bg); background-color:var(--chat-content-bg);
@@ -266,7 +265,7 @@ const reGenerate = (prompt) => {
padding: 0.8rem 1.5rem; padding: 0.8rem 1.5rem;
color: var(--quote-text-color); color: var(--quote-text-color);
border-left: 0.4rem solid #6b50e1; /* 紫色边框 */ border-left: 0.4rem solid #6b50e1; /* 紫色边框 */
font-size: 1.1rem; font-size: 16px;
line-height: 1.6; line-height: 1.6;
} }
} }
@@ -275,7 +274,7 @@ const reGenerate = (prompt) => {
.chat-line-reply-list { .chat-line-reply-list {
justify-content: center; justify-content: center;
background-color: var(--chat-list-bg); background-color: var(--chat-content-bg);
color:var(--theme-text-color-primary); color:var(--theme-text-color-primary);
width 100% width 100%
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
@@ -376,7 +375,8 @@ const reGenerate = (prompt) => {
position: relative; position: relative;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
max-width 70% width 100%
max-width calc(100% - 110px)
.content-wrapper { .content-wrapper {
display flex display flex
@@ -391,6 +391,7 @@ const reGenerate = (prompt) => {
// background-color #F5F5F5 // background-color #F5F5F5
background-color :var(--chat-content-bg); background-color :var(--chat-content-bg);
border-radius: 0 10px 10px 10px; border-radius: 0 10px 10px 10px;
width 100%
} }
} }

View File

@@ -45,14 +45,15 @@
</template> </template>
<script setup> <script setup>
import { reactive, ref } from "vue"; import {reactive, ref} from "vue";
import { ElMessage } from "element-plus"; import {ElMessage} from "element-plus";
import { httpGet, httpPost } from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import { Delete, Plus } from "@element-plus/icons-vue"; import {Delete, Plus} from "@element-plus/icons-vue";
import { isImage, removeArrayItem } from "@/utils/libs"; import {isImage, removeArrayItem} from "@/utils/libs";
import { GetFileIcon } from "@/store/system"; import {GetFileIcon} from "@/store/system";
import { checkSession } from "@/store/cache"; import {checkSession} from "@/store/cache";
import { useSharedStore } from "@/store/sharedata"; import {useSharedStore} from "@/store/sharedata";
const props = defineProps({ const props = defineProps({
userId: Number, userId: Number,
}); });
@@ -151,6 +152,7 @@ const insertURL = (file) => {
.file-upload-img { .file-upload-img {
.iconfont { .iconfont {
font-size: 19px; font-size: 19px;
cursor pointer;
} }
} }

View File

@@ -14,14 +14,13 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import {ref} from "vue";
import { httpGet } from "@/utils/http"; import {showMessageError} from "@/utils/dialog";
import { showMessageError } from "@/utils/dialog"; import {getLicenseInfo, getSystemInfo} from "@/store/cache";
import { getLicenseInfo, getSystemInfo } from "@/store/cache";
const title = ref(""); const title = ref("");
const version = ref(process.env.VUE_APP_VERSION); const version = ref(process.env.VUE_APP_VERSION);
const gitURL = ref(process.env.VUE_APP_GIT_URL); const gitURL = ref(process.env.VUE_APP_GITHUB_URL);
const copyRight = ref(""); const copyRight = ref("");
const license = ref({}); const license = ref({});
const props = defineProps({ const props = defineProps({

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="theme-box" @click="toggleTheme"> <div class="theme-box" @click="toggleTheme">
<span class="iconfont icon-yueliang">{{ themePage === "light" ? "&#xe679;" : "&#xe60b;" }}</span> <i class="iconfont" :class="themePage === 'light'?'icon-yueliang':'icon-taiyang'"></i>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import {ref} from "vue";
import { useSharedStore } from "@/store/sharedata"; import {useSharedStore} from "@/store/sharedata";
// 定义主题状态,初始值从 localStorage 获取 // 定义主题状态,初始值从 localStorage 获取
const store = useSharedStore(); const store = useSharedStore();

View File

@@ -58,9 +58,9 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import {onMounted, ref} from "vue";
import { ElMessage } from "element-plus"; import {ElMessage} from "element-plus";
import { getSystemInfo } from "@/store/cache"; import {getSystemInfo} from "@/store/cache";
const title = ref(process.env.VUE_APP_TITLE); const title = ref(process.env.VUE_APP_TITLE);
const version = ref(process.env.VUE_APP_VERSION); const version = ref(process.env.VUE_APP_VERSION);
@@ -132,7 +132,7 @@ const send = (text) => {
line-height: 2.5rem line-height: 2.5rem
font-weight 600 font-weight 600
margin-bottom: 4rem margin-bottom: 4rem
color var( --theme-textcolor-normal) color var(--text-color)
} }
.grid-content { .grid-content {

View File

@@ -1,4 +1,4 @@
import { defineStore } from "pinia"; import {defineStore} from "pinia";
import Storage from "good-storage"; import Storage from "good-storage";
export const useSharedStore = defineStore("shared", { export const useSharedStore = defineStore("shared", {
@@ -9,6 +9,7 @@ export const useSharedStore = defineStore("shared", {
socket: { conn: null, handlers: {} }, socket: { conn: null, handlers: {} },
theme: Storage.get("theme", "light"), theme: Storage.get("theme", "light"),
isLogin: false, isLogin: false,
chatListExtend: Storage.get("chat_list_extend", true),
}), }),
getters: {}, getters: {},
actions: { actions: {
@@ -29,6 +30,10 @@ export const useSharedStore = defineStore("shared", {
} }
this.socket.conn = value; this.socket.conn = value;
}, },
setChatListExtend(value) {
this.chatListExtend = value;
Storage.set("chat_list_extend", value);
},
addMessageHandler(key, callback) { addMessageHandler(key, callback) {
if (!this.socket.handlers[key]) { if (!this.socket.handlers[key]) {
this.socket.handlers[key] = callback; this.socket.handlers[key] = callback;

View File

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

View File

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

View File

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

View File

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