feat:chat style
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
web/public/images/menu/app1.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
web/public/images/menu/bbs1.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 2.3 KiB |
BIN
web/public/images/menu/docs1.png
Normal file
|
After Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 3.5 KiB |
BIN
web/public/images/menu/log1.png
Normal file
|
After Width: | Height: | Size: 939 B |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
web/public/images/menu/member1.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
web/public/images/menu/share1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
web/public/images/menu/xmind1.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -6,9 +6,13 @@ $borderColor = #4676d0;
|
|||||||
|
|
||||||
.chat-page {
|
.chat-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
::v-deep (.el-message-box__message){
|
||||||
|
font-size: 18px !important
|
||||||
|
}
|
||||||
// left side
|
// left side
|
||||||
|
.el-container{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.el-aside {
|
.el-aside {
|
||||||
//background-color: $sideBgColor;
|
//background-color: $sideBgColor;
|
||||||
padding 10px
|
padding 10px
|
||||||
@@ -23,15 +27,16 @@ $borderColor = #4676d0;
|
|||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
flex-wrap: wrap
|
flex-wrap: wrap
|
||||||
padding: 10px 0;
|
margin-bottom: 10px
|
||||||
|
// padding: 10px 0;
|
||||||
|
|
||||||
.search-input {
|
// .search-input {
|
||||||
--el-input-bg-color: #363535
|
// --el-input-bg-color: #363535
|
||||||
--el-input-border-color: #464545
|
// --el-input-border-color: #464545
|
||||||
--el-input-focus-border-color: #47fff1
|
// --el-input-focus-border-color: #47fff1
|
||||||
--el-input-hover-border-color: #2DA39A
|
// --el-input-hover-border-color: #2DA39A
|
||||||
box-shadow: none
|
// box-shadow: none
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
@@ -53,12 +58,14 @@ $borderColor = #4676d0;
|
|||||||
padding: 8px 12px
|
padding: 8px 12px
|
||||||
//border-bottom: 1px solid #3c3c3c
|
//border-bottom: 1px solid #3c3c3c
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
border: 1px solid #3c3c3c
|
// border: 1px solid #3c3c3c
|
||||||
margin-bottom 6px
|
margin-bottom 6px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #343540
|
// background-color :rgba(239, 241, 246, 0.64);
|
||||||
|
border: 1px solid var(--border-active);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
@@ -78,7 +85,7 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-title {
|
.chat-title {
|
||||||
color: #c1c1c1
|
color: var(--el-text-color-regular);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
max-width 220px;
|
max-width 220px;
|
||||||
font-size 14px;
|
font-size 14px;
|
||||||
@@ -92,10 +99,11 @@ $borderColor = #4676d0;
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
color #ffffff
|
color var(--text-fb)
|
||||||
|
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
color #ffffff
|
color var(--text-fb)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
@@ -105,8 +113,10 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-item.active {
|
.chat-list-item.active {
|
||||||
background-color: #343540;
|
background-color :var(--theme-bg);
|
||||||
border-color #21aa93
|
box-shadow: 0px 3px 9px rgba(112,144,176,0.12);
|
||||||
|
|
||||||
|
border: 1px solid var(--border-active);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +126,7 @@ $borderColor = #4676d0;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-top 12px
|
padding-top 12px
|
||||||
border-top 1px solid #3c3c3c;
|
// border-top 0.5px solid var(--el-border-color);
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
margin-right 5px
|
margin-right 5px
|
||||||
@@ -134,14 +144,14 @@ $borderColor = #4676d0;
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: var(--el-bg-color)
|
background-color: var(--el-bg-color)
|
||||||
color var(--el-text-color-primary)
|
color var(--el-text-color-primary)
|
||||||
|
|
||||||
.chat-config {
|
.chat-config {
|
||||||
height 30px
|
height 30px
|
||||||
padding 10px 30px
|
padding 10px 30px
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
justify-items center
|
justify-items center
|
||||||
border-bottom 1px solid #d9d9e3
|
// border-bottom 1px solid var(--el-border-color);
|
||||||
|
|
||||||
.role-select-label {
|
.role-select-label {
|
||||||
color #ffffff
|
color #ffffff
|
||||||
@@ -157,18 +167,22 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.setting {
|
.setting {
|
||||||
padding 5px
|
// padding 5px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color #f2f2f2
|
width: 26px;
|
||||||
margin-right 10px
|
height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 26px;
|
||||||
|
// background-color #f2f2f2
|
||||||
|
// margin-right 10px
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-size 18px
|
font-size 16px
|
||||||
color #19c37d
|
color var(--el-color-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #D5FAD3
|
background-color var(--text--hover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +197,8 @@ $borderColor = #4676d0;
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position relative
|
position relative
|
||||||
|
background: var(--chat-bg)
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 12px /* 滚动条宽度 */
|
width: 12px /* 滚动条宽度 */
|
||||||
background #F1F1F1
|
background #F1F1F1
|
||||||
@@ -217,6 +232,7 @@ $borderColor = #4676d0;
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,10 +244,11 @@ $borderColor = #4676d0;
|
|||||||
|
|
||||||
.input-box-inner {
|
.input-box-inner {
|
||||||
display flex
|
display flex
|
||||||
background-color: #ffffff
|
background-color:var(--chat-bg);
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
// box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||||
padding 0 15px;
|
padding 0 15px;
|
||||||
|
|
||||||
.tool-item {
|
.tool-item {
|
||||||
@@ -243,7 +260,7 @@ $borderColor = #4676d0;
|
|||||||
justify-items center
|
justify-items center
|
||||||
padding 6px
|
padding 6px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background #F2F2F2
|
// background #F2F2F2
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background #D5FAD3
|
background #D5FAD3
|
||||||
@@ -274,13 +291,17 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-border {
|
.input-border {
|
||||||
display flex
|
// display flex
|
||||||
width 100%
|
width 100%
|
||||||
overflow hidden
|
overflow hidden
|
||||||
border: 2px solid #21AA93
|
border: 2px solid var( --theme-border-primary)
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
padding 10px
|
padding 10px
|
||||||
background-color #F4F4F4
|
// background-color #F4F4F4
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
border-color var(--theme-border-hover)
|
||||||
|
}
|
||||||
|
|
||||||
.input-inner {
|
.input-inner {
|
||||||
display flex
|
display flex
|
||||||
@@ -296,6 +317,7 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prompt-input {
|
.prompt-input {
|
||||||
|
min-height: 58px;
|
||||||
width 100%
|
width 100%
|
||||||
line-height: 24px
|
line-height: 24px
|
||||||
border none
|
border none
|
||||||
@@ -312,12 +334,34 @@ $borderColor = #4676d0;
|
|||||||
.send-btn {
|
.send-btn {
|
||||||
width 32px
|
width 32px
|
||||||
margin-left 10px
|
margin-left 10px
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
padding 8px 5px;
|
padding 8px 5px;
|
||||||
border-radius 6px;
|
border-radius 6px;
|
||||||
font-size 20px;
|
font-size 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.little-btns{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
.tool-item-btn{
|
||||||
|
|
||||||
|
.iconfont{
|
||||||
|
font-size: 19px;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new{
|
||||||
|
.el-icon{
|
||||||
|
font-size: 20px;
|
||||||
|
color: #754ff6;
|
||||||
|
}
|
||||||
|
cursor:pointer
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +424,8 @@ $borderColor = #4676d0;
|
|||||||
width 40px
|
width 40px
|
||||||
height 40px
|
height 40px
|
||||||
border-radius 100%
|
border-radius 100%
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,9 +462,13 @@ $borderColor = #4676d0;
|
|||||||
line-height 1.8
|
line-height 1.8
|
||||||
font-size 16px
|
font-size 16px
|
||||||
overflow auto
|
overflow auto
|
||||||
height 100%
|
height: 70vh
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.dialog-footer{
|
||||||
|
margin-right: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,4 +485,5 @@ $borderColor = #4676d0;
|
|||||||
.el-icon {
|
.el-icon {
|
||||||
margin-left 5px;
|
margin-left 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,34 @@
|
|||||||
--sm-txt:rgba(163, 174, 208, 1);
|
--sm-txt:rgba(163, 174, 208, 1);
|
||||||
--text-secondary: #8a939d;
|
--text-secondary: #8a939d;
|
||||||
--el-color-primary: rgb(107, 80, 225);
|
--el-color-primary: rgb(107, 80, 225);
|
||||||
--el-component-size: 48px;
|
|
||||||
--el-border-radius-base: 8px;
|
--el-border-radius-base: 8px;
|
||||||
--el-color-primary-light-5:rgb(107, 85, 255);
|
--el-color-primary-light-5:rgb(107, 85, 255);
|
||||||
--el-color-primary-light-3:rgb(78, 51, 254);
|
--el-color-primary-light-3:rgb(78, 51, 254);
|
||||||
--theme-btn-color:rgba(117, 81, 255, 1)
|
--theme-btn-color:rgba(117, 81, 255, 1)
|
||||||
--common-text-color:#6e4ef9
|
--common-text-color:#6e4ef9;
|
||||||
|
--el-component-size: 36px;
|
||||||
|
--el-color-primary-dark-2:rgb(169 152 247);
|
||||||
|
--el-button-active-border-color:rgb(169 152 247);
|
||||||
|
--el-color-success-light-9:#EAFFFC;
|
||||||
|
--el-color-success-light-8:#A7F0D9;
|
||||||
|
--el-message-text-color:#0ECD8B;
|
||||||
|
--el-color-success:#0ECD8B;
|
||||||
|
|
||||||
--text-fff:#fff
|
--text-fff:#fff
|
||||||
}
|
--theme-border-primary: rgba(86, 86, 95, .322); //主题边框颜色
|
||||||
|
--theme-border-hover: rgb(107, 85, 255);//主题边框hover颜色
|
||||||
|
--text--hover:rgba(215, 211, 240, 0.581) //主题色hover色系
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.el-dialog{
|
||||||
|
--el-border-radius-base: 20px;
|
||||||
|
}
|
||||||
|
.login-box{
|
||||||
|
--el-component-size: 48px;
|
||||||
|
|
||||||
|
}
|
||||||
.btn-go{
|
.btn-go{
|
||||||
background: var(--btnColor);
|
background: var(--btnColor);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -55,4 +73,46 @@
|
|||||||
}
|
}
|
||||||
.el-input__wrapper{
|
.el-input__wrapper{
|
||||||
background: var( --card-bg)
|
background: var( --card-bg)
|
||||||
|
}
|
||||||
|
.el-dialog__title{
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
.el-button--primary{
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
/* 设置滚动条的宽度 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px; /* 垂直滚动条宽度 */
|
||||||
|
height: 12px; /* 水平滚动条高度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条的轨道背景颜色 */
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1; /* 滚动条轨道颜色 */
|
||||||
|
border-radius: 6px; /* 可选:轨道圆角 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条滑块的颜色 */
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #888; /* 滑块颜色 */
|
||||||
|
border-radius: 6px; /* 可选:滑块圆角 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 鼠标悬停在滑块上的颜色 */
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555; /* 悬停时的滑块颜色 */
|
||||||
|
}
|
||||||
|
//.el-message-box
|
||||||
|
.el-message-box{
|
||||||
|
--el-messagebox-border-radius:18px
|
||||||
|
}
|
||||||
|
.el-message-box__container{
|
||||||
|
border-top: 1px solid #dbd3f4;
|
||||||
|
padding-top: 7px;
|
||||||
|
.el-message-box__message{
|
||||||
|
--text-color:var(--theme-text-color-primary)
|
||||||
|
font-size: 16px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,166 +1,205 @@
|
|||||||
.home {
|
.layout{
|
||||||
display: flex;
|
display: flex;
|
||||||
height 100vh
|
position: relative;
|
||||||
width 100%
|
height: 100vh;
|
||||||
flex-flow column
|
.big-top-title{
|
||||||
|
padding-top: 10px;
|
||||||
.header {
|
}
|
||||||
display flex
|
.top-collapse{
|
||||||
justify-content space-between
|
padding-top: 10px
|
||||||
height 50px
|
img{
|
||||||
line-height 50px
|
width 24px !important
|
||||||
background-color #1E1F22
|
height: 24px !important
|
||||||
padding-right 20px
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
display flex
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display flex
|
|
||||||
padding 5px
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
width 48px
|
|
||||||
height 48px
|
|
||||||
border-radius 50%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 20px;
|
|
||||||
padding 0 10px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
display flex
|
|
||||||
flex-flow row
|
|
||||||
|
|
||||||
.link-button {
|
|
||||||
margin-right 15px
|
|
||||||
color #e1e1e1
|
|
||||||
padding 0 10px
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color #414141
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
font-size 24px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
width 100%
|
|
||||||
padding 5px 0;
|
|
||||||
|
|
||||||
.el-dropdown-link {
|
|
||||||
width 100%;
|
|
||||||
cursor: pointer
|
|
||||||
display flex
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
color: #cccccc;
|
|
||||||
line-height 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tab-box{
|
||||||
|
align-items: center
|
||||||
.main {
|
background-color: var(--card-bg)
|
||||||
width 100%
|
height: 100%
|
||||||
display flex
|
.title{
|
||||||
flex-flow row
|
font-size: 28px
|
||||||
|
font-weight: 700
|
||||||
.navigator {
|
margin-right: 6px
|
||||||
display flex
|
color:var(--text-theme-color)
|
||||||
flex-flow column
|
|
||||||
width 60px
|
|
||||||
padding 10px 1px
|
|
||||||
border-right: 1px solid #3c3c3c
|
|
||||||
background-color: #1E1F22
|
|
||||||
|
|
||||||
.nav-items {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding 0 5px
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom 15px
|
|
||||||
display flex
|
|
||||||
flex-flow column
|
|
||||||
|
|
||||||
a {
|
|
||||||
color #DADBDC
|
|
||||||
border-radius 10px
|
|
||||||
width 48px
|
|
||||||
height 48px
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
cursor pointer
|
|
||||||
background-color #414348
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
border-radius 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
font-size 20px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover, a.active {
|
|
||||||
color #47fff1
|
|
||||||
background-color #0F7A71
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 12px
|
|
||||||
padding-top: 6px
|
|
||||||
color: #e5e7eb;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap; /* 防止文本换行 */
|
|
||||||
overflow: hidden; /* 隐藏溢出内容 */
|
|
||||||
text-overflow: unset; /* 使用省略号表示溢出内容 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
color #47fff1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
img{
|
||||||
.content {
|
width 30px
|
||||||
width: 100%
|
height: 30px
|
||||||
overflow auto
|
object-fit: cover
|
||||||
box-sizing: border-box
|
border-radius: 50%
|
||||||
background-color #282c34
|
padding: 4px
|
||||||
|
border: 2px solid #754ff6;
|
||||||
}
|
}
|
||||||
|
.marr{
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.flex-center-col{
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
flex-direction column
|
||||||
|
}
|
||||||
|
.menu-list-collapse{
|
||||||
|
.flex-center-col{
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.menu-list-item{
|
||||||
|
|
||||||
|
height: 38px;
|
||||||
|
line-height: 38px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.menu-list-item:hover,
|
||||||
|
.active{
|
||||||
|
background: rgba(79, 89, 102, .122);
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
.el-icon{
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-icon{
|
||||||
|
margin: 0 4px;
|
||||||
|
width 26px !important;
|
||||||
|
height: 26px !important;
|
||||||
|
}
|
||||||
|
.menu-title{
|
||||||
|
font-size: 15px !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.openicon{
|
||||||
|
font-size: 40px;
|
||||||
|
color: #754ff6;
|
||||||
|
}
|
||||||
|
.menuIcon{
|
||||||
|
.openicon{
|
||||||
|
font-size: 28px;
|
||||||
|
color: #754ff6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-list{
|
||||||
|
margin-top: 20px;
|
||||||
|
.svg-icon{
|
||||||
|
svg{
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-list-item{
|
||||||
|
// margin-bottom: 10px;
|
||||||
|
margin: 0 8px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
.el-icon{
|
||||||
|
background: rgba(79, 89, 102, .122);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.el-icon{
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 20px;
|
||||||
|
// img{
|
||||||
|
// width: 24px;
|
||||||
|
// height: 24px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
&.active{
|
||||||
|
.el-icon{
|
||||||
|
background: rgba(79, 89, 102, .122);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.bot{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.bot-line{
|
||||||
|
|
||||||
|
width : 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--line-box)
|
||||||
|
margin: 20px 0 10px 0;
|
||||||
|
}
|
||||||
|
.menu-title{
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.icon-house,
|
||||||
|
.icon-github{
|
||||||
|
font-size: 20px;
|
||||||
|
color: #754ff6;
|
||||||
|
cursor pointer
|
||||||
|
}
|
||||||
|
.menu-bot-item{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
a{
|
||||||
|
// margin-right: 46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep(.theme-box){
|
||||||
|
position: relative !important;
|
||||||
|
right: initial;
|
||||||
|
bottom: initial;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
.iconfont{
|
||||||
|
font-size: 15px !important;}
|
||||||
|
}
|
||||||
|
.right-main{
|
||||||
|
height: 100%;
|
||||||
|
// background: #f5f7fd;
|
||||||
|
background: var(--theme-bg-all);
|
||||||
|
// background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
|
||||||
|
width: 100%;
|
||||||
|
.topheader{
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
right: 8px;
|
||||||
|
z-index : 999;
|
||||||
|
top:0;
|
||||||
|
// width 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.btn-go{
|
||||||
|
background: #754ff6;
|
||||||
|
margin: 10px 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.el-popper {
|
.el-popper {
|
||||||
.more-menus {
|
.more-menus {
|
||||||
li {
|
li {
|
||||||
padding 10px 15px
|
padding: 0px 15px;
|
||||||
cursor pointer
|
cursor: pointer;
|
||||||
border-radius 5px
|
border-radius: 5px;
|
||||||
margin 5px 0
|
margin: 5px 0;
|
||||||
|
height: 38px;
|
||||||
|
line-height: 38px;
|
||||||
|
|
||||||
.el-image {
|
.el-image {
|
||||||
position: relative
|
position: relative
|
||||||
@@ -169,26 +208,49 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #f1f1f1
|
background: rgba(79, 89, 102, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li.active {
|
li.active {
|
||||||
background-color #f1f1f1
|
background: rgba(79, 89, 102, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.setting-menus{
|
||||||
.user-info-menu {
|
.title{
|
||||||
li {
|
color: #222226;
|
||||||
a {
|
|
||||||
width 100%
|
|
||||||
justify-content left
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration none !important
|
|
||||||
color var(--el-primary-text-color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.el-icon,
|
||||||
|
.iconfont{
|
||||||
|
font-size: 18px
|
||||||
|
margin-right: 6px
|
||||||
|
}
|
||||||
|
color: #222226;
|
||||||
}
|
}
|
||||||
|
.username{
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.rightHeightMax{
|
||||||
|
height: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
}
|
||||||
|
.rightHeight{
|
||||||
|
height: calc(100vh - 42px);
|
||||||
|
max-height: calc(100vh - 42px);
|
||||||
|
overflow: hidden;
|
||||||
|
.content{
|
||||||
|
padding-top: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
height: 100%;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
@@ -199,3 +199,14 @@
|
|||||||
transition: color 0.3s ease; /* 平滑的颜色过渡 */
|
transition: color 0.3s ease; /* 平滑的颜色过渡 */
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.logo-box{
|
||||||
|
width: 60px
|
||||||
|
height: 60px
|
||||||
|
background: #fff
|
||||||
|
border-radius: 50%
|
||||||
|
img{
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
@import 'font.styl'
|
@import 'font.styl'
|
||||||
:root[data-theme="dark"]{
|
:root[data-theme="dark"]{
|
||||||
|
--text-fb:#fff;
|
||||||
--text-color: rgba(255, 255, 255, 1) !important; // 主要的文本颜色
|
--text-color: rgba(255, 255, 255, 1) !important; // 主要的文本颜色
|
||||||
--normal-color: rgba(163, 174, 208, 1); // 普通颜色
|
--normal-color: rgba(163, 174, 208, 1); // 普通颜色
|
||||||
|
--el-text-color-primary: #fff;
|
||||||
p, h1, h2, h3, h4, h5, h6, article {
|
p, h1, h2, h3, h4, h5, h6, article {
|
||||||
color: var(--text-color) !important;
|
// color: var(--text-color) !important;
|
||||||
font-family: $font-regular;
|
font-family: $font-regular;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,14 +19,42 @@
|
|||||||
font-family: $font-regular;
|
font-family: $font-regular;
|
||||||
}
|
}
|
||||||
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
||||||
|
--border-active:rgba(255, 255, 255, 0.1);
|
||||||
--card-bg: rgba(17, 28, 68, 1);
|
--card-bg: rgba(17, 28, 68, 1);
|
||||||
--theme-bg:rgb(13, 20, 53);
|
--theme-bg:rgb(13, 20, 53);
|
||||||
|
--theme-bg-all:rgb(13, 20, 53);
|
||||||
--sign-bg: rgba(27, 37, 75, 1);
|
--sign-bg: rgba(27, 37, 75, 1);
|
||||||
--text-theme-color: #fff;
|
--text-theme-color: #fff;
|
||||||
--text-color-primary: #d1c7ff;
|
--text-color-primary: #d1c7ff;
|
||||||
--el-text-color-regular: rgba(163, 174, 208, 1)
|
--el-text-color-regular: rgba(163, 174, 208, 1)
|
||||||
--el-border-color:rgb(79, 80, 85)
|
--el-border-color:rgb(79, 80, 85)
|
||||||
--el-text-color-primary: #fff;
|
--el-text-color-primary: #fff;
|
||||||
|
--el-bg-color-overlay: rgba(17, 28, 68, 1);
|
||||||
|
--el-border-color-light: rgba(255, 255, 255, 0.2);
|
||||||
|
--line-box:rgba(255, 255, 255, 0.1);
|
||||||
|
--chat-bg:#141a36;
|
||||||
|
--el-bg-color:#141a36;
|
||||||
|
--el-fill-color-blank: rgba(17, 28, 68, 1);
|
||||||
|
--el-fill-color-light: rgba(86, 86, 95, .2);
|
||||||
|
--el-color-primary-light-9:rgba(86, 86, 95, .2);
|
||||||
|
--chat-wel-bg:#2d2f388a;
|
||||||
|
--theme-text-color-secondary: #a3aed0;
|
||||||
|
|
||||||
|
//layout
|
||||||
|
.more-menus li.moreTitle,
|
||||||
|
.twoTittle .title,
|
||||||
|
.setting-menus span.title,
|
||||||
|
.setting-menus li .el-icon,
|
||||||
|
.setting-menus li .iconfont,
|
||||||
|
.layout .tab-box .menu-list-item{
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
.more-menus span.title{
|
||||||
|
color:#000;
|
||||||
|
}
|
||||||
|
|
||||||
|
--theme-text-color-primary: #fff;
|
||||||
|
--theme-text-primary: #f3f3f3;
|
||||||
|
--chat-content-bg:rgba(86, 86, 95, .2);
|
||||||
|
--chat-content-bg-list:rgba(86, 86, 95, .2);
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
@import 'font.styl'
|
@import 'font.styl'
|
||||||
:root[data-theme="light"] {
|
:root[data-theme="light"] {
|
||||||
|
--text-fb:#000;
|
||||||
// rgba(43, 54, 116, 1)
|
// rgba(43, 54, 116, 1)
|
||||||
--text-color: #5b62ce; // 主要的文本颜色
|
--text-color: #5b62ce; // 主要的文本颜色
|
||||||
--normal-color: rgba(43, 54, 116, 1); // 普通颜色
|
--normal-color: rgba(43, 54, 116, 1); // 普通颜色
|
||||||
p, h1, h2, h3, h4, h5, h6, article {
|
p, h1, h2, h3, h4, h5, h6, article {
|
||||||
color: var(--text-color) !important;
|
// color: var(--text-color) !important;
|
||||||
font-family: $font-regular;
|
font-family: $font-regular;
|
||||||
}
|
}
|
||||||
html,
|
html,
|
||||||
@@ -22,12 +23,28 @@
|
|||||||
}//#6b61f6
|
}//#6b61f6
|
||||||
|
|
||||||
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
||||||
|
--border-active:rgba(134, 140, 255, 1);
|
||||||
--code-btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
--code-btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
||||||
--card-bg:#fff;
|
--card-bg:#fff;
|
||||||
--theme-bg:linear-gradient(88deg, #fff3f3 1.44%, #e7e8ff);
|
--theme-bg:linear-gradient(88deg, #fff3f3 1.44%, #e7e8ff);
|
||||||
|
--theme-bg-all:#f5f7fd;
|
||||||
--sign-bg: rgba(244, 247, 254, 1);
|
--sign-bg: rgba(244, 247, 254, 1);
|
||||||
--text-theme-color: rgba(43, 54, 116, 1)
|
--text-theme-color: rgba(43, 54, 116, 1)
|
||||||
--text-color-primary: rgba(67, 24, 255, 1);
|
--text-color-primary: rgba(67, 24, 255, 1);
|
||||||
|
--line-box:rgba(79, 89, 102, 0.122);
|
||||||
|
--el-bg-color-overlay: #fff;
|
||||||
|
--el-bg-color:#fff;
|
||||||
|
--el-fill-color-blank: #fff;
|
||||||
|
|
||||||
|
--theme-text-color-primary: #000;
|
||||||
|
--theme-text-primary: #000;
|
||||||
|
--theme-text-color-secondary: #666;
|
||||||
|
--chat-bg: #fff;
|
||||||
|
|
||||||
|
--chat-content-bg:#f5f7fc;
|
||||||
|
--chat-list-bg: #0302020a;
|
||||||
|
--chat-content-bg-list:#fff;
|
||||||
|
--chat-wel-bg:rgba(247, 247, 248, 1);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
|
/* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4125778 */
|
font-family: 'iconfont'; /* Project id 4125778 */
|
||||||
src: url('//at.alicdn.com/t/c/font_4125778_gs96jfl3hlc.woff2?t=1732009095144') format('woff2'),
|
src: url('//at.alicdn.com/t/c/font_4125778_t6hhjiqu67.woff2?t=1733221702650') format('woff2'),
|
||||||
url('//at.alicdn.com/t/c/font_4125778_gs96jfl3hlc.woff?t=1732009095144') format('woff'),
|
url('//at.alicdn.com/t/c/font_4125778_t6hhjiqu67.woff?t=1733221702650') format('woff'),
|
||||||
url('//at.alicdn.com/t/c/font_4125778_gs96jfl3hlc.ttf?t=1732009095144') format('truetype');
|
url('//at.alicdn.com/t/c/font_4125778_t6hhjiqu67.ttf?t=1733221702650') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -418,3 +419,6 @@
|
|||||||
content: "\e66f";
|
content: "\e66f";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-house:before {
|
||||||
|
content: "\e619";
|
||||||
|
}
|
||||||
|
|||||||
BIN
web/src/assets/img/371731933156_.pic.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
web/src/assets/img/avatar.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -56,5 +56,5 @@ import FooterBar from "@/components/FooterBar.vue";
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<button v-if="showButton" @click="scrollToTop" class="scroll-to-top" :style="{bottom: bottom + 'px', right: right + 'px', backgroundColor: bgColor}">
|
<button
|
||||||
|
v-if="showButton"
|
||||||
|
@click="scrollToTop"
|
||||||
|
class="scroll-to-top"
|
||||||
|
:style="{
|
||||||
|
bottom: bottom + 'px',
|
||||||
|
right: right + 'px',
|
||||||
|
backgroundColor: bgColor
|
||||||
|
}"
|
||||||
|
>
|
||||||
<el-icon><ArrowUpBold /></el-icon>
|
<el-icon><ArrowUpBold /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ArrowUpBold} from "@element-plus/icons-vue";
|
import { ArrowUpBold } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BackTop',
|
name: "BackTop",
|
||||||
components: {ArrowUpBold},
|
components: { ArrowUpBold },
|
||||||
props: {
|
props: {
|
||||||
bottom: {
|
bottom: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 30
|
default: 155
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -21,7 +30,7 @@ export default {
|
|||||||
},
|
},
|
||||||
bgColor: {
|
bgColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#007bff'
|
default: "#b6aaf9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -31,19 +40,19 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.checkScroll();
|
this.checkScroll();
|
||||||
window.addEventListener('resize', this.checkScroll);
|
window.addEventListener("resize", this.checkScroll);
|
||||||
this.$el.parentElement.addEventListener('scroll', this.checkScroll);
|
this.$el.parentElement.addEventListener("scroll", this.checkScroll);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener('resize', this.checkScroll);
|
window.removeEventListener("resize", this.checkScroll);
|
||||||
this.$el.parentElement.removeEventListener('scroll', this.checkScroll);
|
this.$el.parentElement.removeEventListener("scroll", this.checkScroll);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
const container = this.$el.parentElement;
|
const container = this.$el.parentElement;
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior: 'smooth'
|
behavior: "smooth"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
checkScroll() {
|
checkScroll() {
|
||||||
@@ -51,7 +60,7 @@ export default {
|
|||||||
this.showButton = container.scrollTop > 50;
|
this.showButton = container.scrollTop > 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
@@ -63,15 +72,15 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
width 40px
|
width 30px
|
||||||
height 40px
|
height 30px
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
align-items center
|
align-items center
|
||||||
font-size 20px
|
font-size 18px
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,65 +1,77 @@
|
|||||||
<template>
|
<template>
|
||||||
<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 class="item" v-else>
|
||||||
|
<div class="icon">
|
||||||
|
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div class="item" v-else>
|
<div class="body">
|
||||||
<div class="icon">
|
<div class="title">
|
||||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
<el-link
|
||||||
|
:href="file.url"
|
||||||
|
target="_blank"
|
||||||
|
style="--el-font-weight-primary: bold"
|
||||||
|
>{{ file.name }}</el-link
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="info">
|
||||||
<div class="title">
|
<span>{{ GetFileType(file.ext) }}</span>
|
||||||
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
|
<span>{{ FormatFileSize(file.size) }}</span>
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span>{{GetFileType(file.ext)}}</span>
|
|
||||||
<span>{{FormatFileSize(file.size)}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" v-html="content"></div>
|
</div>
|
||||||
<div class="bar" v-if="data.created_at > 0">
|
<div class="content" v-html="content"></div>
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
<div class="bar" v-if="data.created_at > 0">
|
||||||
<span class="bar-item">tokens: {{ finalTokens }}</span>
|
<span class="bar-item"
|
||||||
</div>
|
><el-icon><Clock /></el-icon>
|
||||||
|
{{ dateFormat(data.created_at) }}</span
|
||||||
|
>
|
||||||
|
<span class="bar-item">tokens: {{ finalTokens }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
<span>{{FormatFileSize(file.size)}}</span>
|
<span>{{ FormatFileSize(file.size) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,8 +81,11 @@
|
|||||||
<div class="content" v-html="content"></div>
|
<div class="content" v-html="content"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bar" v-if="data.created_at > 0">
|
<div class="bar" v-if="data.created_at > 0">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
<span class="bar-item"
|
||||||
<!-- <span class="bar-item">tokens: {{ finalTokens }}</span>-->
|
><el-icon><Clock /></el-icon>
|
||||||
|
{{ dateFormat(data.created_at) }}</span
|
||||||
|
>
|
||||||
|
<!-- <span class="bar-item">tokens: {{ finalTokens }}</span>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,104 +93,110 @@
|
|||||||
</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";
|
||||||
|
|
||||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
const mathjaxPlugin = require("markdown-it-mathjax3");
|
||||||
const md = require('markdown-it')({
|
const md = require("markdown-it")({
|
||||||
breaks: true,
|
breaks: true,
|
||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
const codeIndex =
|
||||||
|
parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
|
||||||
// 显示复制代码按钮
|
// 显示复制代码按钮
|
||||||
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>/g, '</textarea>')}</textarea>`
|
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
|
||||||
|
/<\/textarea>/g,
|
||||||
|
"</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>`;
|
||||||
// 处理代码高亮
|
// 处理代码高亮
|
||||||
const preCode = hl.highlight(lang, str, true).value
|
const preCode = hl.highlight(lang, str, true).value;
|
||||||
// 将代码包裹在 pre 中
|
// 将代码包裹在 pre 中
|
||||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
|
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理代码高亮
|
// 处理代码高亮
|
||||||
const preCode = md.utils.escapeHtml(str)
|
const preCode = md.utils.escapeHtml(str);
|
||||||
// 将代码包裹在 pre 中
|
// 将代码包裹在 pre 中
|
||||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
md.use(mathjaxPlugin)
|
md.use(mathjaxPlugin);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: {
|
default: {
|
||||||
content: '',
|
content: "",
|
||||||
created_at: '',
|
created_at: "",
|
||||||
tokens: 0,
|
tokens: 0,
|
||||||
model: '',
|
model: "",
|
||||||
icon: '',
|
icon: ""
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
listStyle: {
|
listStyle: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'list',
|
default: "list"
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
const finalTokens = ref(props.data.tokens)
|
const finalTokens = ref(props.data.tokens);
|
||||||
const content =ref(processPrompt(props.data.content))
|
const content = ref(processPrompt(props.data.content));
|
||||||
const files = ref([])
|
const files = ref([]);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
processFiles()
|
processFiles();
|
||||||
})
|
});
|
||||||
|
|
||||||
const processFiles = () => {
|
const processFiles = () => {
|
||||||
if (!props.data.content) {
|
if (!props.data.content) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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}).then(res => {
|
httpPost("/api/upload/list", { urls: links })
|
||||||
files.value = res.data.items
|
.then((res) => {
|
||||||
|
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, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
content.value = md.render(content.value.trim())
|
content.value = md.render(content.value.trim());
|
||||||
}
|
};
|
||||||
const isExternalImg = (link, files) => {
|
const isExternalImg = (link, files) => {
|
||||||
return isImage(link) && !files.find(file => file.url === link)
|
return isImage(link) && !files.find((file) => file.url === link);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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 #ffffff;
|
|
||||||
|
background-color:var( --chat-content-bg-list);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width 100%
|
width 100%
|
||||||
padding-bottom: 1.5rem;
|
padding-bottom: 1.5rem;
|
||||||
padding-top: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
border-bottom: 1px solid #d9d9e3;
|
border-bottom: 0.5px solid var(--el-border-color);
|
||||||
|
|
||||||
.chat-line-inner {
|
.chat-line-inner {
|
||||||
display flex;
|
display flex;
|
||||||
@@ -189,7 +210,7 @@ const isExternalImg = (link, files) => {
|
|||||||
img {
|
img {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 10px;
|
border-radius: 50%;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +239,9 @@ const isExternalImg = (link, files) => {
|
|||||||
display flex
|
display flex
|
||||||
flex-flow row
|
flex-flow row
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
border 1px solid #e3e3e3
|
border 1px solid #e3e3e3
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
padding 6px
|
padding 6px
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
|
|
||||||
@@ -251,7 +273,7 @@ const isExternalImg = (link, files) => {
|
|||||||
.content {
|
.content {
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color #374151;
|
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;
|
||||||
@@ -279,7 +301,7 @@ const isExternalImg = (link, files) => {
|
|||||||
padding 10px 10px 10px 0;
|
padding 10px 10px 10px 0;
|
||||||
|
|
||||||
.bar-item {
|
.bar-item {
|
||||||
background-color #f7f7f8;
|
// background-color #f7f7f8;
|
||||||
color #888
|
color #888
|
||||||
padding 3px 5px;
|
padding 3px 5px;
|
||||||
margin-right 10px;
|
margin-right 10px;
|
||||||
@@ -298,7 +320,7 @@ const isExternalImg = (link, files) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-line-prompt-chat {
|
.chat-line-prompt-chat {
|
||||||
background-color #ffffff;
|
background: var(--chat-bg);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width 100%
|
width 100%
|
||||||
padding-bottom: 1.5rem;
|
padding-bottom: 1.5rem;
|
||||||
@@ -345,7 +367,8 @@ const isExternalImg = (link, files) => {
|
|||||||
display flex
|
display flex
|
||||||
flex-flow row
|
flex-flow row
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
border 1px solid #e3e3e3
|
border 1px solid #e3e3e3
|
||||||
padding 6px
|
padding 6px
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
@@ -382,10 +405,10 @@ const isExternalImg = (link, files) => {
|
|||||||
.content {
|
.content {
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 1rem
|
padding: 1rem
|
||||||
color #222222;
|
color var(--theme-text-primary);
|
||||||
font-size: var(--content-font-size);
|
font-size: var(--content-font-size);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color #98e165
|
background-color :var(--chat-content-bg);
|
||||||
border-radius: 10px 0 10px 10px;
|
border-radius: 10px 0 10px 10px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -2,48 +2,54 @@
|
|||||||
<div class="chat-line chat-line-reply-list" v-if="listStyle === 'list'">
|
<div class="chat-line chat-line-reply-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="ChatGPT">
|
<img :src="data.icon" alt="ChatGPT" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
<div class="content" v-html="md.render(processContent(data.content))"></div>
|
<div
|
||||||
|
class="content"
|
||||||
|
v-html="md.render(processContent(data.content))"
|
||||||
|
></div>
|
||||||
<div class="bar" v-if="data.created_at">
|
<div class="bar" v-if="data.created_at">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
<span class="bar-item"
|
||||||
<span class="bar-item">tokens: {{ data.tokens }}</span>
|
><el-icon><Clock /></el-icon>
|
||||||
|
{{ dateFormat(data.created_at) }}</span
|
||||||
|
>
|
||||||
|
<span class="bar-item">tokens: {{ data.tokens }}</span>
|
||||||
<span class="bar-item">
|
<span class="bar-item">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="复制回答"
|
content="复制回答"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
||||||
<DocumentCopy/>
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="!readOnly">
|
<span v-if="!readOnly">
|
||||||
<span class="bar-item" @click="reGenerate(data.prompt)">
|
<span class="bar-item" @click="reGenerate(data.prompt)">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="重新生成"
|
content="重新生成"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<el-icon><Refresh/></el-icon>
|
<el-icon><Refresh /></el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="bar-item" @click="synthesis(data.content)">
|
<span class="bar-item" @click="synthesis(data.content)">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="生成语音朗读"
|
content="生成语音朗读"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-speaker"></i>
|
<i class="iconfont icon-speaker"></i>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<!-- <span class="bar-item">-->
|
<!-- <span class="bar-item">-->
|
||||||
<!-- <el-dropdown trigger="click">-->
|
<!-- <el-dropdown trigger="click">-->
|
||||||
@@ -65,49 +71,55 @@
|
|||||||
<div class="chat-line chat-line-reply-chat" v-else>
|
<div class="chat-line chat-line-reply-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="ChatGPT">
|
<img :src="data.icon" alt="ChatGPT" />
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="content" v-html="md.render(processContent(data.content))"></div>
|
<div
|
||||||
|
class="content"
|
||||||
|
v-html="md.render(processContent(data.content))"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bar" v-if="data.created_at">
|
<div class="bar" v-if="data.created_at">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
<span class="bar-item"
|
||||||
<!-- <span class="bar-item">tokens: {{ data.tokens }}</span>-->
|
><el-icon><Clock /></el-icon>
|
||||||
|
{{ dateFormat(data.created_at) }}</span
|
||||||
|
>
|
||||||
|
<!-- <span class="bar-item">tokens: {{ data.tokens }}</span>-->
|
||||||
<span class="bar-item bg">
|
<span class="bar-item bg">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="复制回答"
|
content="复制回答"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
||||||
<DocumentCopy/>
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="!readOnly">
|
<span v-if="!readOnly">
|
||||||
<span class="bar-item bg" @click="reGenerate(data.prompt)">
|
<span class="bar-item bg" @click="reGenerate(data.prompt)">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="重新生成"
|
content="重新生成"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<el-icon><Refresh/></el-icon>
|
<el-icon><Refresh /></el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="bar-item bg" @click="synthesis(data.content)">
|
<span class="bar-item bg" @click="synthesis(data.content)">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="生成语音朗读"
|
content="生成语音朗读"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-speaker"></i>
|
<i class="iconfont icon-speaker"></i>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,9 +128,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";
|
||||||
// eslint-disable-next-line no-undef,no-unused-vars
|
// eslint-disable-next-line no-undef,no-unused-vars
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -128,8 +140,8 @@ const props = defineProps({
|
|||||||
icon: "",
|
icon: "",
|
||||||
content: "",
|
content: "",
|
||||||
created_at: "",
|
created_at: "",
|
||||||
tokens: 0,
|
tokens: 0
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -137,53 +149,57 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
listStyle: {
|
listStyle: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'list',
|
default: "list"
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
const mathjaxPlugin = require("markdown-it-mathjax3");
|
||||||
const md = require('markdown-it')({
|
const md = require("markdown-it")({
|
||||||
breaks: true,
|
breaks: true,
|
||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
const codeIndex =
|
||||||
|
parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
|
||||||
// 显示复制代码按钮
|
// 显示复制代码按钮
|
||||||
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>/g, '</textarea>')}</textarea>`
|
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
|
||||||
|
/<\/textarea>/g,
|
||||||
|
"</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>`;
|
||||||
// 处理代码高亮
|
// 处理代码高亮
|
||||||
const preCode = hl.highlight(lang, str, true).value
|
const preCode = hl.highlight(lang, str, true).value;
|
||||||
// 将代码包裹在 pre 中
|
// 将代码包裹在 pre 中
|
||||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
|
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理代码高亮
|
// 处理代码高亮
|
||||||
const preCode = md.utils.escapeHtml(str)
|
const preCode = md.utils.escapeHtml(str);
|
||||||
// 将代码包裹在 pre 中
|
// 将代码包裹在 pre 中
|
||||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
md.use(mathjaxPlugin)
|
md.use(mathjaxPlugin);
|
||||||
|
|
||||||
const emits = defineEmits(['regen']);
|
const emits = defineEmits(["regen"]);
|
||||||
|
|
||||||
if (!props.data.icon) {
|
if (!props.data.icon) {
|
||||||
props.data.icon = "images/gpt-icon.png"
|
props.data.icon = "images/gpt-icon.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
const synthesis = (text) => {
|
const synthesis = (text) => {
|
||||||
console.log(text)
|
console.log(text);
|
||||||
ElMessage.info("语音合成功能暂不可用")
|
ElMessage.info("语音合成功能暂不可用");
|
||||||
}
|
};
|
||||||
|
|
||||||
// 重新生成
|
// 重新生成
|
||||||
const reGenerate = (prompt) => {
|
const reGenerate = (prompt) => {
|
||||||
console.log(prompt)
|
console.log(prompt);
|
||||||
emits('regen', prompt)
|
emits("regen", prompt);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@@ -191,11 +207,12 @@ const reGenerate = (prompt) => {
|
|||||||
.chat-page,.chat-export {
|
.chat-page,.chat-export {
|
||||||
.chat-line-reply-list {
|
.chat-line-reply-list {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: rgba(247, 247, 248, 1);
|
background-color: var(--chat-list-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
width 100%
|
width 100%
|
||||||
padding-bottom: 1.5rem;
|
padding-bottom: 1.5rem;
|
||||||
padding-top: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
border-bottom: 1px solid #d9d9e3;
|
border-bottom: 0.5px solid var(--el-border-color);
|
||||||
|
|
||||||
.chat-line-inner {
|
.chat-line-inner {
|
||||||
display flex;
|
display flex;
|
||||||
@@ -209,7 +226,7 @@ const reGenerate = (prompt) => {
|
|||||||
img {
|
img {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 10px;
|
border-radius: 50%;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +241,7 @@ const reGenerate = (prompt) => {
|
|||||||
min-height 20px;
|
min-height 20px;
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 0
|
padding: 0
|
||||||
color #374151;
|
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;
|
||||||
@@ -238,7 +255,7 @@ const reGenerate = (prompt) => {
|
|||||||
line-height 1.5
|
line-height 1.5
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color #374151
|
color:var(--theme-text-color-primary);
|
||||||
background-color #e7e7e8
|
background-color #e7e7e8
|
||||||
padding 0 3px;
|
padding 0 3px;
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
@@ -296,7 +313,8 @@ const reGenerate = (prompt) => {
|
|||||||
color #212529
|
color #212529
|
||||||
border-collapse collapse;
|
border-collapse collapse;
|
||||||
border 1px solid #dee2e6;
|
border 1px solid #dee2e6;
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
th {
|
th {
|
||||||
@@ -396,10 +414,13 @@ const reGenerate = (prompt) => {
|
|||||||
min-height 20px;
|
min-height 20px;
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 1rem
|
padding: 1rem
|
||||||
color #374151;
|
color var(--theme-text-primary);
|
||||||
|
|
||||||
font-size: var(--content-font-size);
|
font-size: var(--content-font-size);
|
||||||
overflow auto;
|
overflow auto;
|
||||||
background-color #F5F5F5
|
// background-color #F5F5F5
|
||||||
|
background-color :var(--chat-content-bg);
|
||||||
|
|
||||||
border-radius: 0 10px 10px 10px;
|
border-radius: 0 10px 10px 10px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -411,7 +432,7 @@ const reGenerate = (prompt) => {
|
|||||||
line-height 1.5
|
line-height 1.5
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color #374151
|
color:var(--theme-text-color-primary);
|
||||||
background-color #e7e7e8
|
background-color #e7e7e8
|
||||||
padding 0 3px;
|
padding 0 3px;
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
@@ -469,7 +490,8 @@ const reGenerate = (prompt) => {
|
|||||||
color #212529
|
color #212529
|
||||||
border-collapse collapse;
|
border-collapse collapse;
|
||||||
border 1px solid #dee2e6;
|
border 1px solid #dee2e6;
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
th {
|
th {
|
||||||
@@ -541,5 +563,4 @@ const reGenerate = (prompt) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,22 +2,27 @@
|
|||||||
<el-container class="chat-file-list">
|
<el-container class="chat-file-list">
|
||||||
<div v-for="file in fileList" :key="file.url">
|
<div v-for="file in fileList" :key="file.url">
|
||||||
<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 class="action">
|
<div class="action">
|
||||||
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</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">{{substr(file.name, 30)}}</el-link>
|
<el-link
|
||||||
|
:href="file.url"
|
||||||
|
target="_blank"
|
||||||
|
style="--el-font-weight-primary: bold"
|
||||||
|
>{{ substr(file.name, 30) }}</el-link
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span>{{GetFileType(file.ext)}}</span>
|
<span>{{ GetFileType(file.ext) }}</span>
|
||||||
<span>{{FormatFileSize(file.size)}}</span>
|
<span>{{ FormatFileSize(file.size) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
@@ -29,26 +34,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref} from "vue";
|
import { ref } from "vue";
|
||||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
import { CircleCloseFilled } from "@element-plus/icons-vue";
|
||||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
|
import { isImage, removeArrayItem, substr } from "@/utils/libs";
|
||||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
import { FormatFileSize, GetFileIcon, GetFileType } from "@/store/system";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
files: {
|
files: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default:[],
|
default: []
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
const emits = defineEmits(['removeFile']);
|
const emits = defineEmits(["removeFile"]);
|
||||||
const fileList = ref(props.files)
|
const fileList = ref(props.files);
|
||||||
|
|
||||||
|
|
||||||
const removeFile = (file) => {
|
const removeFile = (file) => {
|
||||||
fileList.value = removeArrayItem(fileList.value, file, (v1,v2) => v1.url===v2.url)
|
fileList.value = removeArrayItem(
|
||||||
emits('removeFile', file)
|
fileList.value,
|
||||||
}
|
file,
|
||||||
|
(v1, v2) => v1.url === v2.url
|
||||||
|
);
|
||||||
|
emits("removeFile", file);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
@@ -75,7 +82,8 @@ const removeFile = (file) => {
|
|||||||
display flex
|
display flex
|
||||||
flex-flow row
|
flex-flow row
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
background-color #ffffff
|
background-color:var(--chat-content-bg);
|
||||||
|
color:var(--theme-text-color-primary);
|
||||||
border 1px solid #e3e3e3
|
border 1px solid #e3e3e3
|
||||||
padding 6px
|
padding 6px
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
@@ -112,5 +120,4 @@ const removeFile = (file) => {
|
|||||||
font-size 20px
|
font-size 20px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -4,27 +4,32 @@
|
|||||||
<i class="iconfont icon-attachment-st"></i>
|
<i class="iconfont icon-attachment-st"></i>
|
||||||
</a>
|
</a>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
class="file-list-dialog"
|
class="file-list-dialog"
|
||||||
v-model="show"
|
v-model="show"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
:show-close="true"
|
:show-close="true"
|
||||||
:width="800"
|
:width="800"
|
||||||
title="文件管理"
|
title="文件管理"
|
||||||
>
|
>
|
||||||
<el-scrollbar ref="scrollbarRef" max-height="80vh" style="height: 100%;" @scroll="onScroll">
|
<el-scrollbar
|
||||||
|
ref="scrollbarRef"
|
||||||
|
max-height="80vh"
|
||||||
|
style="height: 100%"
|
||||||
|
@scroll="onScroll"
|
||||||
|
>
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="3">
|
<el-col :span="3">
|
||||||
<div class="grid-content">
|
<div class="grid-content">
|
||||||
<el-upload
|
<el-upload
|
||||||
class="avatar-uploader"
|
class="avatar-uploader"
|
||||||
:auto-upload="true"
|
:auto-upload="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:http-request="afterRead"
|
:http-request="afterRead"
|
||||||
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
|
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
|
||||||
>
|
>
|
||||||
<el-icon class="avatar-uploader-icon">
|
<el-icon class="avatar-uploader-icon">
|
||||||
<Plus/>
|
<Plus />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,116 +37,139 @@
|
|||||||
<el-col :span="3" v-for="file in fileData.items" :key="file.url">
|
<el-col :span="3" v-for="file in fileData.items" :key="file.url">
|
||||||
<div class="grid-content">
|
<div class="grid-content">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
:content="file.name"
|
:content="file.name"
|
||||||
placement="top">
|
placement="top"
|
||||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
|
>
|
||||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
<el-image
|
||||||
|
:src="file.url"
|
||||||
|
fit="cover"
|
||||||
|
v-if="isImage(file.ext)"
|
||||||
|
@click="insertURL(file)"
|
||||||
|
/>
|
||||||
|
<el-image
|
||||||
|
:src="GetFileIcon(file.ext)"
|
||||||
|
fit="cover"
|
||||||
|
v-else
|
||||||
|
@click="insertURL(file)"
|
||||||
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
<el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
:icon="Delete"
|
||||||
|
@click="removeFile(file)"
|
||||||
|
circle
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row justify="center" v-if="!fileData.isLastPage" @click="fetchFiles(fileData.page)">
|
<el-row
|
||||||
|
justify="center"
|
||||||
|
v-if="!fileData.isLastPage"
|
||||||
|
@click="fetchFiles(fileData.page)"
|
||||||
|
>
|
||||||
<el-link>加载更多</el-link>
|
<el-link>加载更多</el-link>
|
||||||
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</el-container>
|
</el-container>
|
||||||
</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";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
userId: Number,
|
userId: Number
|
||||||
});
|
});
|
||||||
const emits = defineEmits(['selected']);
|
const emits = defineEmits(["selected"]);
|
||||||
const show = ref(false)
|
const show = ref(false);
|
||||||
const scrollbarRef = ref(null)
|
const scrollbarRef = ref(null);
|
||||||
const fileData = reactive({
|
const fileData = reactive({
|
||||||
items:[],
|
items: [],
|
||||||
page: 1,
|
page: 1,
|
||||||
isLastPage: true,
|
isLastPage: true
|
||||||
})
|
});
|
||||||
|
|
||||||
const fetchFiles = (pageNo) => {
|
const fetchFiles = (pageNo) => {
|
||||||
if(pageNo === 1) show.value = true
|
if (pageNo === 1) show.value = true;
|
||||||
httpPost("/api/upload/list", { page: pageNo || 1, page_size: 30 }).then(res => {
|
httpPost("/api/upload/list", { page: pageNo || 1, page_size: 30 })
|
||||||
const { items, page, total_page } = res.data
|
.then((res) => {
|
||||||
|
const { items, page, total_page } = res.data;
|
||||||
|
|
||||||
if(page === 1){
|
if (page === 1) {
|
||||||
fileData.items = items
|
fileData.items = items;
|
||||||
}else{
|
} else {
|
||||||
fileData.items = [...fileData.items, ...items]
|
fileData.items = [...fileData.items, ...items];
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData.isLastPage = (page === total_page)
|
fileData.isLastPage = page === total_page;
|
||||||
|
|
||||||
if(!fileData.isLastPage){
|
if (!fileData.isLastPage) {
|
||||||
fileData.page = page + 1
|
fileData.page = page + 1;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}).catch(() => {
|
.catch(() => {});
|
||||||
})
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// el-scrollbar 滚动回调
|
// el-scrollbar 滚动回调
|
||||||
const onScroll = (options) => {
|
const onScroll = (options) => {
|
||||||
const wrapRef = scrollbarRef.value.wrapRef
|
const wrapRef = scrollbarRef.value.wrapRef;
|
||||||
scrollbarRef.value.moveY = wrapRef.scrollTop * 100 / wrapRef.clientHeight
|
scrollbarRef.value.moveY = (wrapRef.scrollTop * 100) / wrapRef.clientHeight;
|
||||||
scrollbarRef.value.moveX = wrapRef.scrollLeft * 100 / wrapRef.clientWidth
|
scrollbarRef.value.moveX = (wrapRef.scrollLeft * 100) / wrapRef.clientWidth;
|
||||||
const poor = wrapRef.scrollHeight - wrapRef.clientHeight
|
const poor = wrapRef.scrollHeight - wrapRef.clientHeight;
|
||||||
// 判断滚动到底部 自动加载数据
|
// 判断滚动到底部 自动加载数据
|
||||||
if (options.scrollTop + 2 >= poor && !fileData.isLastPage) {
|
if (options.scrollTop + 2 >= poor && !fileData.isLastPage) {
|
||||||
fetchFiles(fileData.page)
|
fetchFiles(fileData.page);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const afterRead = (file) => {
|
const afterRead = (file) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file.file, file.name);
|
formData.append("file", file.file, file.name);
|
||||||
// 执行上传操作
|
// 执行上传操作
|
||||||
httpPost('/api/upload', formData).then((res) => {
|
httpPost("/api/upload", formData)
|
||||||
fileData.items.unshift(res.data)
|
.then((res) => {
|
||||||
ElMessage.success({message: "上传成功", duration: 500})
|
fileData.items.unshift(res.data);
|
||||||
}).catch((e) => {
|
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||||
ElMessage.error('图片上传失败:' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("图片上传失败:" + e.message);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFile = (file) => {
|
const removeFile = (file) => {
|
||||||
httpGet('/api/upload/remove?id=' + file.id).then(() => {
|
httpGet("/api/upload/remove?id=" + file.id)
|
||||||
fileData.items = removeArrayItem(fileData.items, file, (v1, v2) => {
|
.then(() => {
|
||||||
return v1.id === v2.id
|
fileData.items = removeArrayItem(fileData.items, file, (v1, v2) => {
|
||||||
|
return v1.id === v2.id;
|
||||||
|
});
|
||||||
|
ElMessage.success("文件删除成功!");
|
||||||
|
fetchFiles(1);
|
||||||
})
|
})
|
||||||
ElMessage.success("文件删除成功!")
|
.catch((e) => {
|
||||||
fetchFiles(1)
|
ElMessage.error("文件删除失败:" + e.message);
|
||||||
}).catch((e) => {
|
});
|
||||||
ElMessage.error('文件删除失败:' + e.message)
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertURL = (file) => {
|
const insertURL = (file) => {
|
||||||
show.value = false
|
show.value = false;
|
||||||
// 如果是相对路径,处理成绝对路径
|
// 如果是相对路径,处理成绝对路径
|
||||||
if (file.url.indexOf("http") === -1) {
|
if (file.url.indexOf("http") === -1) {
|
||||||
file.url = location.protocol + "//" + location.host + file.url
|
file.url = location.protocol + "//" + location.host + file.url;
|
||||||
}
|
}
|
||||||
emits('selected', file)
|
emits("selected", file);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@@ -149,7 +177,7 @@ const insertURL = (file) => {
|
|||||||
.file-select-box {
|
.file-select-box {
|
||||||
.file-upload-img {
|
.file-upload-img {
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-size: 24px;
|
font-size: 19px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,4 +243,4 @@ const insertURL = (file) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
|
|
||||||
<div class="list-box">
|
<div class="list-box">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="item in samples" :key="item"><a @click="send(item)">{{ item }}</a></li>
|
<li v-for="item in samples" :key="item">
|
||||||
|
<a @click="send(item)">{{ item }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +29,9 @@
|
|||||||
|
|
||||||
<div class="list-box">
|
<div class="list-box">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="item in plugins" :key="item.value"><a @click="send(item.value)">{{ item.text }}</a></li>
|
<li v-for="item in plugins" :key="item.value">
|
||||||
|
<a @click="send(item.value)">{{ item.text }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,19 +58,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { getSystemInfo } from "@/store/cache";
|
||||||
|
|
||||||
import {onMounted, ref} from "vue";
|
const title = ref(process.env.VUE_APP_TITLE);
|
||||||
import {ElMessage} from "element-plus";
|
const version = ref(process.env.VUE_APP_VERSION);
|
||||||
import {getSystemInfo} from "@/store/cache";
|
|
||||||
|
|
||||||
const title = ref(process.env.VUE_APP_TITLE)
|
|
||||||
const version = ref(process.env.VUE_APP_VERSION)
|
|
||||||
|
|
||||||
const samples = ref([
|
const samples = ref([
|
||||||
"用小学生都能听懂的术语解释什么是量子纠缠",
|
"用小学生都能听懂的术语解释什么是量子纠缠",
|
||||||
"能给一位6岁男孩的生日会提供一些创造性的建议吗?",
|
"能给一位6岁男孩的生日会提供一些创造性的建议吗?",
|
||||||
"如何用 Go 语言实现支持代理 Http client 请求?"
|
"如何用 Go 语言实现支持代理 Http client 请求?"
|
||||||
])
|
]);
|
||||||
|
|
||||||
const plugins = ref([
|
const plugins = ref([
|
||||||
{
|
{
|
||||||
@@ -81,7 +84,7 @@ const plugins = ref([
|
|||||||
value: "今日头条",
|
value: "今日头条",
|
||||||
text: "今日头条:给用户推荐当天的头条新闻,周榜热文"
|
text: "今日头条:给用户推荐当天的头条新闻,周榜热文"
|
||||||
}
|
}
|
||||||
])
|
]);
|
||||||
|
|
||||||
const capabilities = ref([
|
const capabilities = ref([
|
||||||
{
|
{
|
||||||
@@ -96,20 +99,22 @@ const capabilities = ref([
|
|||||||
text: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2",
|
text: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2",
|
||||||
value: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2"
|
value: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2"
|
||||||
}
|
}
|
||||||
])
|
]);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
title.value = res.data.title
|
.then((res) => {
|
||||||
}).catch(e => {
|
title.value = res.data.title;
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
})
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['send']);
|
const emits = defineEmits(["send"]);
|
||||||
const send = (text) => {
|
const send = (text) => {
|
||||||
emits('send', text)
|
emits("send", text);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
.welcome {
|
.welcome {
|
||||||
@@ -148,10 +153,9 @@ const send = (text) => {
|
|||||||
font-size 14px;
|
font-size 14px;
|
||||||
padding .75rem
|
padding .75rem
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
background-color: rgba(247, 247, 248, 1);
|
background-color: var(--chat-wel-bg);
|
||||||
|
color:var( --theme-text-color-secondary);
|
||||||
line-height 1.5
|
line-height 1.5
|
||||||
color #666666
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
cursor pointer
|
cursor pointer
|
||||||
@@ -165,4 +169,4 @@ const send = (text) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,39 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="'admin-header '+theme">
|
<div :class="'admin-header ' + theme">
|
||||||
<!-- 折叠按钮 -->
|
<!-- 折叠按钮 -->
|
||||||
<div class="collapse-btn" @click="collapseChange">
|
<div class="collapse-btn" @click="collapseChange">
|
||||||
<el-icon v-if="sidebar.collapse">
|
<el-icon v-if="sidebar.collapse">
|
||||||
<Expand/>
|
<Expand />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<el-icon v-else>
|
<el-icon v-else>
|
||||||
<Fold/>
|
<Fold />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb">
|
||||||
<el-breadcrumb :separator-icon="ArrowRight">
|
<el-breadcrumb :separator-icon="ArrowRight">
|
||||||
<el-breadcrumb-item v-for="item in breadcrumb">{{ item.title }}</el-breadcrumb-item>
|
<el-breadcrumb-item v-for="item in breadcrumb">{{
|
||||||
|
item.title
|
||||||
|
}}</el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="header-user-con">
|
<div class="header-user-con">
|
||||||
<!-- 切换主题 -->
|
<!-- 切换主题 -->
|
||||||
<el-switch
|
<el-switch
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
v-model="dark"
|
v-model="dark"
|
||||||
inline-prompt
|
inline-prompt
|
||||||
:active-action-icon="Moon"
|
:active-action-icon="Moon"
|
||||||
:inactive-action-icon="Sunny"
|
:inactive-action-icon="Sunny"
|
||||||
@change="changeTheme"
|
@change="changeTheme"
|
||||||
/>
|
/>
|
||||||
<!-- 用户名下拉菜单 -->
|
<!-- 用户名下拉菜单 -->
|
||||||
<el-dropdown class="user-name" :hide-on-click="true" trigger="click">
|
<el-dropdown class="user-name" :hide-on-click="true" trigger="click">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<el-avatar class="user-avatar" :size="30" :src="avatar"/>
|
<el-avatar class="user-avatar" :size="30" :src="avatar" />
|
||||||
<el-icon class="el-icon--right">
|
<el-icon class="el-icon--right">
|
||||||
<arrow-down/>
|
<arrow-down />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item>
|
<el-dropdown-item>
|
||||||
@@ -48,82 +50,91 @@
|
|||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref, watch} from 'vue';
|
import { onMounted, ref, watch } from "vue";
|
||||||
import {getMenuItems, useSidebarStore} from '@/store/sidebar';
|
import { getMenuItems, useSidebarStore } from "@/store/sidebar";
|
||||||
import {useRouter} from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import {ArrowDown, ArrowRight, Expand, Fold, Moon, Sunny} from "@element-plus/icons-vue";
|
import {
|
||||||
import {httpGet} from "@/utils/http";
|
ArrowDown,
|
||||||
import {ElMessage} from "element-plus";
|
ArrowRight,
|
||||||
import {removeAdminToken} from "@/store/session";
|
Expand,
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
Fold,
|
||||||
|
Moon,
|
||||||
|
Sunny
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import { httpGet } from "@/utils/http";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { removeAdminToken } from "@/store/session";
|
||||||
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
|
|
||||||
const version = ref(process.env.VUE_APP_VERSION)
|
const version = ref(process.env.VUE_APP_VERSION);
|
||||||
const avatar = ref('/images/user-info.jpg')
|
const avatar = ref("/images/user-info.jpg");
|
||||||
const sidebar = useSidebarStore();
|
const sidebar = useSidebarStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const breadcrumb = ref([])
|
const breadcrumb = ref([]);
|
||||||
|
|
||||||
const store = useSharedStore()
|
const store = useSharedStore();
|
||||||
const dark = ref(store.adminTheme === 'dark')
|
const dark = ref(store.adminTheme === "dark");
|
||||||
const theme = ref(store.adminTheme)
|
const theme = ref(store.adminTheme);
|
||||||
watch(() => store.adminTheme, (val) => {
|
watch(
|
||||||
theme.value = val
|
() => store.adminTheme,
|
||||||
})
|
(val) => {
|
||||||
|
theme.value = val;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const changeTheme = () => {
|
const changeTheme = () => {
|
||||||
store.setAdminTheme(dark.value ? 'dark' : 'light')
|
store.setAdminTheme(dark.value ? "dark" : "light");
|
||||||
}
|
};
|
||||||
|
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
initBreadCrumb(to.path)
|
initBreadCrumb(to.path);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initBreadCrumb(router.currentRoute.value.path)
|
initBreadCrumb(router.currentRoute.value.path);
|
||||||
})
|
});
|
||||||
|
|
||||||
// 初始化面包屑导航
|
// 初始化面包屑导航
|
||||||
const initBreadCrumb = (path) => {
|
const initBreadCrumb = (path) => {
|
||||||
breadcrumb.value = [{title: "首页"}]
|
breadcrumb.value = [{ title: "首页" }];
|
||||||
const items = getMenuItems()
|
const items = getMenuItems();
|
||||||
if (items) {
|
if (items) {
|
||||||
let bk = false
|
let bk = false;
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].index === path) {
|
if (items[i].index === path) {
|
||||||
breadcrumb.value.push({
|
breadcrumb.value.push({
|
||||||
title: items[i].title,
|
title: items[i].title,
|
||||||
path: items[i].index
|
path: items[i].index
|
||||||
})
|
});
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
if (bk) {
|
if (bk) {
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items[i]['subs']) {
|
if (items[i]["subs"]) {
|
||||||
const subs = items[i]['subs']
|
const subs = items[i]["subs"];
|
||||||
for (let j = 0; j < subs.length; j++) {
|
for (let j = 0; j < subs.length; j++) {
|
||||||
if (subs[j].index === path) {
|
if (subs[j].index === path) {
|
||||||
breadcrumb.value.push({
|
breadcrumb.value.push({
|
||||||
title: items[i].title,
|
title: items[i].title,
|
||||||
path: items[i].index
|
path: items[i].index
|
||||||
})
|
});
|
||||||
breadcrumb.value.push({
|
breadcrumb.value.push({
|
||||||
title: subs[j].title,
|
title: subs[j].title,
|
||||||
path: subs[j].index
|
path: subs[j].index
|
||||||
})
|
});
|
||||||
bk = true
|
bk = true;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 侧边栏折叠
|
// 侧边栏折叠
|
||||||
const collapseChange = () => {
|
const collapseChange = () => {
|
||||||
@@ -137,13 +148,15 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const logout = function () {
|
const logout = function () {
|
||||||
httpGet("/api/admin/logout").then(() => {
|
httpGet("/api/admin/logout")
|
||||||
removeAdminToken()
|
.then(() => {
|
||||||
router.replace('/admin/login')
|
removeAdminToken();
|
||||||
}).catch((e) => {
|
router.replace("/admin/login");
|
||||||
ElMessage.error("注销失败: " + e.message);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("注销失败: " + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
.admin-header {
|
.admin-header {
|
||||||
@@ -152,8 +165,8 @@ const logout = function () {
|
|||||||
overflow hidden
|
overflow hidden
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: #303133;
|
background-color:var(--chat-content-bg);
|
||||||
background-color #ffffff
|
color:var(--theme-text-color-primary);
|
||||||
|
|
||||||
.collapse-btn {
|
.collapse-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -260,5 +273,4 @@ const logout = function () {
|
|||||||
.admin-header {
|
.admin-header {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-message-mj">
|
<div class="mobile-message-mj">
|
||||||
<div class="chat-icon">
|
<div class="chat-icon">
|
||||||
<van-image :src="icon"/>
|
<van-image :src="icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
@@ -11,21 +11,30 @@
|
|||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<div class="text" v-html="data.html"></div>
|
<div class="text" v-html="data.html"></div>
|
||||||
<div class="images" v-if="data.image?.url !== ''">
|
<div class="images" v-if="data.image?.url !== ''">
|
||||||
<el-image :src="data.image?.url"
|
<el-image
|
||||||
:zoom-rate="1.2"
|
:src="data.image?.url"
|
||||||
:preview-src-list="[data.image?.url]"
|
:zoom-rate="1.2"
|
||||||
fit="cover"
|
:preview-src-list="[data.image?.url]"
|
||||||
:initial-index="0" loading="lazy">
|
fit="cover"
|
||||||
|
:initial-index="0"
|
||||||
|
loading="lazy"
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="image-slot"
|
<div
|
||||||
:style="{height: height+'px', lineHeight:height+'px'}">
|
class="image-slot"
|
||||||
正在加载图片<span class="dot">...</span></div>
|
:style="{
|
||||||
|
height: height + 'px',
|
||||||
|
lineHeight: height + 'px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
正在加载图片<span class="dot">...</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="opt" v-if="data.showOpt &&data.image?.hash !== ''">
|
<div class="opt" v-if="data.showOpt && data.image?.hash !== ''">
|
||||||
<div class="opt-line">
|
<div class="opt-line">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a @click="upscale(1)">U1</a></li>
|
<li><a @click="upscale(1)">U1</a></li>
|
||||||
@@ -54,17 +63,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref, watch} from "vue";
|
import { ref, watch } from "vue";
|
||||||
import {Picture} from "@element-plus/icons-vue";
|
import { Picture } from "@element-plus/icons-vue";
|
||||||
import {httpPost} from "@/utils/http";
|
import { httpPost } from "@/utils/http";
|
||||||
import {getSessionId} from "@/store/session";
|
import { getSessionId } from "@/store/session";
|
||||||
import {showNotify} from "vant";
|
import { showNotify } from "vant";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
content: Object,
|
content: Object,
|
||||||
@@ -74,36 +82,40 @@ const props = defineProps({
|
|||||||
createdAt: String
|
createdAt: String
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = ref(props.content)
|
const data = ref(props.content);
|
||||||
const cacheKey = "img_placeholder_height"
|
const cacheKey = "img_placeholder_height";
|
||||||
const item = localStorage.getItem(cacheKey);
|
const item = localStorage.getItem(cacheKey);
|
||||||
const loading = ref(false)
|
const loading = ref(false);
|
||||||
const height = ref(0)
|
const height = ref(0);
|
||||||
if (item) {
|
if (item) {
|
||||||
height.value = parseInt(item)
|
height.value = parseInt(item);
|
||||||
}
|
}
|
||||||
if (data.value["image"]?.width > 0) {
|
if (data.value["image"]?.width > 0) {
|
||||||
height.value = 350 * data.value["image"]?.height / data.value["image"]?.width
|
height.value =
|
||||||
localStorage.setItem(cacheKey, height.value)
|
(350 * data.value["image"]?.height) / data.value["image"]?.width;
|
||||||
|
localStorage.setItem(cacheKey, height.value);
|
||||||
}
|
}
|
||||||
data.value["showOpt"] = data.value["content"]?.indexOf("- Image #") === -1;
|
data.value["showOpt"] = data.value["content"]?.indexOf("- Image #") === -1;
|
||||||
// console.log(data.value)
|
// console.log(data.value)
|
||||||
|
|
||||||
watch(() => props.content, (newVal) => {
|
watch(
|
||||||
data.value = newVal;
|
() => props.content,
|
||||||
});
|
(newVal) => {
|
||||||
const emits = defineEmits(['disable-input', 'disable-input']);
|
data.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const emits = defineEmits(["disable-input", "disable-input"]);
|
||||||
const upscale = (index) => {
|
const upscale = (index) => {
|
||||||
send('/api/mj/upscale', index)
|
send("/api/mj/upscale", index);
|
||||||
}
|
};
|
||||||
|
|
||||||
const variation = (index) => {
|
const variation = (index) => {
|
||||||
send('/api/mj/variation', index)
|
send("/api/mj/variation", index);
|
||||||
}
|
};
|
||||||
|
|
||||||
const send = (url, index) => {
|
const send = (url, index) => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
emits('disable-input')
|
emits("disable-input");
|
||||||
httpPost(url, {
|
httpPost(url, {
|
||||||
index: index,
|
index: index,
|
||||||
src: "chat",
|
src: "chat",
|
||||||
@@ -114,15 +126,20 @@ const send = (url, index) => {
|
|||||||
prompt: data.value?.["prompt"],
|
prompt: data.value?.["prompt"],
|
||||||
chat_id: props.chatId,
|
chat_id: props.chatId,
|
||||||
role_id: props.roleId,
|
role_id: props.roleId,
|
||||||
icon: props.icon,
|
icon: props.icon
|
||||||
}).then(() => {
|
|
||||||
showNotify({type: "success", message: "任务推送成功,请耐心等待任务执行..."})
|
|
||||||
loading.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
showNotify({type: "danger", message: "任务推送失败:" + e.message})
|
|
||||||
emits('disable-input')
|
|
||||||
})
|
})
|
||||||
}
|
.then(() => {
|
||||||
|
showNotify({
|
||||||
|
type: "success",
|
||||||
|
message: "任务推送成功,请耐心等待任务执行..."
|
||||||
|
});
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
showNotify({ type: "danger", message: "任务推送失败:" + e.message });
|
||||||
|
emits("disable-input");
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@@ -268,4 +285,4 @@ const send = (url, index) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,122 +1,195 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="layout">
|
||||||
<div class="header">
|
<div class="tab-box">
|
||||||
<div class="banner">
|
<div class="flex-center-col big-top-title xxx">
|
||||||
<div class="logo">
|
<div class="flex-center-col" @click="isCollapse = !isCollapse">
|
||||||
<el-image :src="logo" @click="router.push('/')"/>
|
<el-icon v-if="isCollapse" class="openicon">
|
||||||
|
<svg
|
||||||
|
t="1733138242826"
|
||||||
|
class="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="1853"
|
||||||
|
id="mx_n_1733138242827"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M715 267c135.31 0 245 109.69 245 245S850.31 757 715 757H309C173.69 757 64 647.31 64 512s109.69-245 245-245h406zM309 367c-80.081 0-145 64.919-145 145s64.919 145 145 145 145-64.919 145-145-64.919-145-145-145z"
|
||||||
|
fill="#754ff6"
|
||||||
|
p-id="1854"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="flex" :class="{ 'top-collapse': !isCollapse }">
|
||||||
<span>{{ title }}</span>
|
<div class="top-avatar flex">
|
||||||
|
<span class="title" v-if="!isCollapse">GeekAI</span>
|
||||||
|
<img
|
||||||
|
v-if="loginUser.id"
|
||||||
|
:src="!!loginUser.avatar ? loginUser.avatar : avatarImg"
|
||||||
|
alt=""
|
||||||
|
:class="{ marr: !isCollapse }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="menuIcon xxx" @click="isCollapse = !isCollapse">
|
||||||
|
<el-icon v-if="!isCollapse" class="openicon">
|
||||||
|
<svg
|
||||||
|
t="1733138405307"
|
||||||
|
class="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="2064"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M715 267c135.31 0 245 109.69 245 245S850.31 757 715 757H309C173.69 757 64 647.31 64 512s109.69-245 245-245h406z m0 100c-80.081 0-145 64.919-145 145s64.919 145 145 145 145-64.919 145-145-64.919-145-145-145z"
|
||||||
|
fill="#754ff6"
|
||||||
|
p-id="2065"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navbar">
|
<div
|
||||||
<el-tooltip
|
class="menu-list"
|
||||||
v-if="!license.de_copy"
|
:style="{ width: isCollapse ? '65px' : '170px' }"
|
||||||
class="box-item"
|
:class="{ 'menu-list-collapse': !isCollapse }"
|
||||||
effect="light"
|
>
|
||||||
content="部署文档"
|
<ul>
|
||||||
placement="bottom">
|
<li
|
||||||
<a :href="docsURL" class="link-button" target="_blank">
|
class="menu-list-item flex-center-col"
|
||||||
<i class="iconfont icon-book"></i>
|
v-for="item in mainNavs"
|
||||||
</a>
|
:key="item.url"
|
||||||
</el-tooltip>
|
@click="changeNav(item)"
|
||||||
|
:class="item.url === curPath ? 'active' : ''"
|
||||||
<el-tooltip
|
>
|
||||||
v-if="!license.de_copy"
|
<el-image :src="item.icon" class="el-icon" />
|
||||||
class="box-item"
|
<div
|
||||||
effect="light"
|
class="menu-title"
|
||||||
content="项目源码"
|
:class="{ 'menu-title-collapse': !isCollapse }"
|
||||||
placement="bottom">
|
>
|
||||||
<a href="https://github.com/yangjian102621/chatgpt-plus" class="link-button" target="_blank">
|
{{ item.name }}
|
||||||
<i class="iconfont icon-github"></i>
|
</div>
|
||||||
</a>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="loginUser.id">
|
|
||||||
<span class="el-dropdown-link">
|
|
||||||
<el-image :src="loginUser.avatar"/>
|
|
||||||
</span>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu class="user-info-menu">
|
|
||||||
<el-dropdown-item @click="showConfigDialog = true">
|
|
||||||
<el-icon>
|
|
||||||
<UserFilled/>
|
|
||||||
</el-icon>
|
|
||||||
<span class="username">{{ loginUser.nickname }}</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
|
|
||||||
<div v-if="!license.de_copy">
|
|
||||||
<el-dropdown-item>
|
|
||||||
<i class="iconfont icon-book"></i>
|
|
||||||
<a :href="docsURL" target="_blank">
|
|
||||||
用户手册
|
|
||||||
</a>
|
|
||||||
</el-dropdown-item>
|
|
||||||
|
|
||||||
<el-dropdown-item>
|
|
||||||
<i class="iconfont icon-github"></i>
|
|
||||||
<a :href="gitURL" target="_blank">
|
|
||||||
GeekAI {{ version }}
|
|
||||||
</a>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</div>
|
|
||||||
<el-divider style="margin: 2px 0"/>
|
|
||||||
<el-dropdown-item @click="logout">
|
|
||||||
<i class="iconfont icon-logout"></i>
|
|
||||||
<span>退出登录</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<el-button size="small" color="#21aa93" @click="store.setShowLoginDialog(true)" round>登录</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="main">
|
|
||||||
<div class="navigator">
|
|
||||||
<ul class="nav-items">
|
|
||||||
<li v-for="item in mainNavs" :key="item.url">
|
|
||||||
<el-tooltip
|
|
||||||
effect="light"
|
|
||||||
:content="item.name"
|
|
||||||
placement="right">
|
|
||||||
<a @click="changeNav(item)" :class="item.url === curPath ? 'active' : ''">
|
|
||||||
<el-image :src="item.icon" style="width: 30px;height: 30px"/>
|
|
||||||
</a>
|
|
||||||
</el-tooltip>
|
|
||||||
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<el-popover
|
<!-- <li
|
||||||
|
class="menu-list-item flex-center-col"
|
||||||
|
v-for="item in 5"
|
||||||
|
:key="item"
|
||||||
|
>
|
||||||
|
<el-icon><Location /></el-icon>
|
||||||
|
<div>首页</div>
|
||||||
|
</li> -->
|
||||||
|
|
||||||
|
<!-- 更多 -->
|
||||||
|
<div class="bot" :style="{ width: isCollapse ? '65px' : '170px' }">
|
||||||
|
<div class="bot-line"></div>
|
||||||
|
|
||||||
|
<el-popover
|
||||||
v-if="moreNavs.length > 0"
|
v-if="moreNavs.length > 0"
|
||||||
placement="right-end"
|
placement="right-end"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<li>
|
<li class="menu-list-item flex-center-col">
|
||||||
<a class="active">
|
<el-icon><CirclePlus /></el-icon>
|
||||||
<el-image src="/images/menu/more.png" style="width: 30px;height: 30px"/>
|
<div class="menu-title">更多</div>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<ul class="more-menus">
|
|
||||||
<li v-for="item in moreNavs" :key="item.url" :class="item.url === curPath ? 'active' : ''">
|
|
||||||
<a @click="changeNav(item)">
|
|
||||||
<el-image :src="item.icon" style="width: 20px;height: 20px"/>
|
|
||||||
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</template>
|
||||||
</template>
|
<template #default>
|
||||||
</el-popover>
|
<ul class="more-menus">
|
||||||
|
<li
|
||||||
|
v-for="(item, index) in moreNavs"
|
||||||
|
:key="item.url"
|
||||||
|
:class="{
|
||||||
|
active: item.url === curPath,
|
||||||
|
moreTitle: index !== 3 && index !== 4,
|
||||||
|
twoTittle: index === 3 || index === 4
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a @click="changeNav(item)">
|
||||||
|
<el-image
|
||||||
|
:src="item.icon"
|
||||||
|
style="width: 20px; height: 20px"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
</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 />
|
||||||
|
<!-- <div v-if="!isCollapse">会员</div> -->
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="content custom-scroll" :style="{height: mainWinHeight+'px'}">
|
<div class="right-main">
|
||||||
|
<div class="topheader" v-if="loginUser.id === undefined || !loginUser.id">
|
||||||
|
<el-button
|
||||||
|
@click="router.push('/login')"
|
||||||
|
class="btn-go animate__animated animate__pulse animate__infinite"
|
||||||
|
round
|
||||||
|
>登录</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="content custom-scroll">
|
||||||
<router-view :key="routerViewKey" v-slot="{ Component }">
|
<router-view :key="routerViewKey" v-slot="{ Component }">
|
||||||
<transition name="move" mode="out-in">
|
<transition name="move" mode="out-in">
|
||||||
<component :is="Component"></component>
|
<component :is="Component"></component>
|
||||||
@@ -124,120 +197,143 @@
|
|||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<config-dialog
|
||||||
<login-dialog :show="show" @hide="store.setShowLoginDialog(false)" @success="loginCallback"/>
|
v-if="loginUser.id"
|
||||||
<config-dialog v-if="loginUser.id" :show="showConfigDialog" @hide="showConfigDialog = false"/>
|
:show="showConfigDialog"
|
||||||
|
@hide="showConfigDialog = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { CirclePlus, Setting } from "@element-plus/icons-vue";
|
||||||
import {useRouter} from "vue-router";
|
import ThemeChange from "@/components/ThemeChange.vue";
|
||||||
import {onMounted, ref, watch} from "vue";
|
import { avatarImg } from "@/assets/img/avatar.jpg";
|
||||||
import {httpGet} from "@/utils/http";
|
import { useRouter } from "vue-router";
|
||||||
import {ElMessage} from "element-plus";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import {UserFilled} from "@element-plus/icons-vue";
|
import { httpGet } from "@/utils/http";
|
||||||
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
|
import { ElMessage } from "element-plus";
|
||||||
import {removeUserToken} from "@/store/session";
|
import { UserFilled } from "@element-plus/icons-vue";
|
||||||
|
import { checkSession, getLicenseInfo, getSystemInfo } from "@/store/cache";
|
||||||
|
import { removeUserToken } from "@/store/session";
|
||||||
import LoginDialog from "@/components/LoginDialog.vue";
|
import LoginDialog from "@/components/LoginDialog.vue";
|
||||||
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";
|
||||||
|
|
||||||
|
const isCollapse = ref(true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const logo = ref('');
|
const logo = ref("");
|
||||||
const mainNavs = ref([])
|
const mainNavs = ref([]);
|
||||||
const moreNavs = ref([])
|
const moreNavs = ref([]);
|
||||||
const curPath = ref(router.currentRoute.value.path)
|
const curPath = ref(router.currentRoute.value.path);
|
||||||
const title = ref("")
|
const title = ref("");
|
||||||
const mainWinHeight = window.innerHeight - 50
|
// const mainWinHeight = window.innerHeight - 50;
|
||||||
const loginUser = ref({})
|
|
||||||
const version = ref(process.env.VUE_APP_VERSION)
|
const loginUser = ref({});
|
||||||
const routerViewKey = ref(0)
|
const mainWinHeight = loginUser.value.id
|
||||||
const showConfigDialog = ref(false)
|
? window.innerHeight
|
||||||
const license = ref({de_copy: true})
|
: window.innerHeight;
|
||||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
|
|
||||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
|
const version = ref(process.env.VUE_APP_VERSION);
|
||||||
|
const routerViewKey = ref(0);
|
||||||
|
const showConfigDialog = ref(false);
|
||||||
|
const license = ref({ de_copy: true });
|
||||||
|
const docsURL = ref(process.env.VUE_APP_DOCS_URL);
|
||||||
|
const gitURL = ref(process.env.VUE_APP_GIT_URL);
|
||||||
|
|
||||||
const store = useSharedStore();
|
const store = useSharedStore();
|
||||||
const show = ref(false)
|
const show = ref(false);
|
||||||
watch(() => store.showLoginDialog, (newValue) => {
|
watch(
|
||||||
show.value = newValue
|
() => store.showLoginDialog,
|
||||||
});
|
(newValue) => {
|
||||||
|
show.value = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 监听路由变化
|
// 监听路由变化
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
curPath.value = to.path
|
curPath.value = to.path;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (curPath.value === "/external") {
|
if (curPath.value === "/external") {
|
||||||
curPath.value = router.currentRoute.value.query.url
|
curPath.value = router.currentRoute.value.query.url;
|
||||||
}
|
}
|
||||||
const changeNav = (item) => {
|
const changeNav = (item) => {
|
||||||
curPath.value = item.url
|
curPath.value = item.url;
|
||||||
if (item.url.indexOf("http") !== -1) { // 外部链接
|
if (item.url.indexOf("http") !== -1) {
|
||||||
router.push({name: 'ExternalLink', query: {url: item.url}})
|
// 外部链接
|
||||||
|
router.push({ name: "ExternalLink", query: { url: item.url } });
|
||||||
} else {
|
} else {
|
||||||
router.push(item.url)
|
router.push(item.url);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
logo.value = res.data.logo
|
.then((res) => {
|
||||||
title.value = res.data.title
|
logo.value = res.data.logo;
|
||||||
}).catch(e => {
|
title.value = res.data.title;
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
// 获取菜单
|
// 获取菜单
|
||||||
httpGet("/api/menu/list").then(res => {
|
httpGet("/api/menu/list")
|
||||||
mainNavs.value = res.data
|
.then((res) => {
|
||||||
// 根据窗口的高度计算应该显示多少菜单
|
mainNavs.value = res.data;
|
||||||
const rows = Math.floor((window.innerHeight - 100) / 90)
|
// 根据窗口的高度计算应该显示多少菜单
|
||||||
if (res.data.length > rows) {
|
const rows = Math.floor((window.innerHeight - 100) / 90);
|
||||||
mainNavs.value = res.data.slice(0, rows)
|
if (res.data.length > rows) {
|
||||||
moreNavs.value = res.data.slice(rows)
|
mainNavs.value = res.data.slice(0, rows);
|
||||||
}
|
moreNavs.value = res.data.slice(rows);
|
||||||
}).catch(e => {
|
}
|
||||||
ElMessage.error("获取系统菜单失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统菜单失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
getLicenseInfo().then(res => {
|
getLicenseInfo()
|
||||||
license.value = res.data
|
.then((res) => {
|
||||||
}).catch(e => {
|
license.value = res.data;
|
||||||
license.value = {de_copy: false}
|
})
|
||||||
showMessageError("获取 License 配置:" + e.message)
|
.catch((e) => {
|
||||||
})
|
license.value = { de_copy: false };
|
||||||
|
showMessageError("获取 License 配置:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
init()
|
init();
|
||||||
})
|
});
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
checkSession().then(user => {
|
checkSession()
|
||||||
loginUser.value = user
|
.then((user) => {
|
||||||
}).catch(() => {
|
loginUser.value = user;
|
||||||
})
|
})
|
||||||
}
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
const logout = function () {
|
const logout = function () {
|
||||||
httpGet('/api/user/logout').then(() => {
|
httpGet("/api/user/logout")
|
||||||
removeUserToken()
|
.then(() => {
|
||||||
store.setShowLoginDialog(true)
|
removeUserToken();
|
||||||
store.setIsLogin(false)
|
store.setShowLoginDialog(true);
|
||||||
loginUser.value = {}
|
store.setIsLogin(false);
|
||||||
// 刷新组件
|
loginUser.value = {};
|
||||||
routerViewKey.value += 1
|
// 刷新组件
|
||||||
}).catch(() => {
|
routerViewKey.value += 1;
|
||||||
ElMessage.error('注销失败!');
|
})
|
||||||
})
|
.catch(() => {
|
||||||
}
|
ElMessage.error("注销失败!");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const loginCallback = () => {
|
const loginCallback = () => {
|
||||||
init()
|
init();
|
||||||
// 刷新组件
|
// 刷新组件
|
||||||
routerViewKey.value += 1
|
routerViewKey.value += 1;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|||||||
297
web/src/views/Home_副本.vue
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<div class="header">
|
||||||
|
<div class="banner">
|
||||||
|
<div class="logo">
|
||||||
|
<el-image :src="logo" @click="router.push('/')" />
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar">
|
||||||
|
<el-tooltip
|
||||||
|
v-if="!license.de_copy"
|
||||||
|
class="box-item"
|
||||||
|
effect="light"
|
||||||
|
content="部署文档"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<a :href="docsURL" class="link-button" target="_blank">
|
||||||
|
<i class="iconfont icon-book"></i>
|
||||||
|
</a>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip
|
||||||
|
v-if="!license.de_copy"
|
||||||
|
class="box-item"
|
||||||
|
effect="light"
|
||||||
|
content="项目源码"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://github.com/yangjian102621/chatgpt-plus"
|
||||||
|
class="link-button"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-github"></i>
|
||||||
|
</a>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-dropdown
|
||||||
|
:hide-on-click="true"
|
||||||
|
class="user-info"
|
||||||
|
trigger="click"
|
||||||
|
v-if="loginUser.id"
|
||||||
|
>
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
<el-image :src="loginUser.avatar" />
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu class="user-info-menu">
|
||||||
|
<el-dropdown-item @click="showConfigDialog = true">
|
||||||
|
<el-icon>
|
||||||
|
<UserFilled />
|
||||||
|
</el-icon>
|
||||||
|
<span class="username">{{ loginUser.nickname }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
|
||||||
|
<div v-if="!license.de_copy">
|
||||||
|
<el-dropdown-item>
|
||||||
|
<i class="iconfont icon-book"></i>
|
||||||
|
<a :href="docsURL" target="_blank"> 用户手册 </a>
|
||||||
|
</el-dropdown-item>
|
||||||
|
|
||||||
|
<el-dropdown-item>
|
||||||
|
<i class="iconfont icon-github"></i>
|
||||||
|
<a :href="gitURL" target="_blank"> GeekAI {{ version }} </a>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</div>
|
||||||
|
<el-divider style="margin: 2px 0" />
|
||||||
|
<el-dropdown-item @click="logout">
|
||||||
|
<i class="iconfont icon-logout"></i>
|
||||||
|
<span>退出登录</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
color="#21aa93"
|
||||||
|
@click="store.setShowLoginDialog(true)"
|
||||||
|
round
|
||||||
|
>登录</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<div class="navigator">
|
||||||
|
<ul class="nav-items">
|
||||||
|
<li v-for="item in mainNavs" :key="item.url">
|
||||||
|
<el-tooltip effect="light" :content="item.name" placement="right">
|
||||||
|
<a
|
||||||
|
@click="changeNav(item)"
|
||||||
|
:class="item.url === curPath ? 'active' : ''"
|
||||||
|
>
|
||||||
|
<el-image :src="item.icon" style="width: 30px; height: 30px" />
|
||||||
|
</a>
|
||||||
|
</el-tooltip>
|
||||||
|
<span :class="item.url === curPath ? 'title active' : 'title'">{{
|
||||||
|
item.name
|
||||||
|
}}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<el-popover
|
||||||
|
v-if="moreNavs.length > 0"
|
||||||
|
placement="right-end"
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<li>
|
||||||
|
<a class="active">
|
||||||
|
<el-image
|
||||||
|
src="/images/menu/more.png"
|
||||||
|
style="width: 30px; height: 30px"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<ul class="more-menus">
|
||||||
|
<li
|
||||||
|
v-for="item in moreNavs"
|
||||||
|
:key="item.url"
|
||||||
|
:class="item.url === curPath ? 'active' : ''"
|
||||||
|
>
|
||||||
|
<a @click="changeNav(item)">
|
||||||
|
<el-image
|
||||||
|
:src="item.icon"
|
||||||
|
style="width: 20px; height: 20px"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
:class="item.url === curPath ? 'title active' : 'title'"
|
||||||
|
>{{ item.name }}</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="content custom-scroll"
|
||||||
|
:style="{ height: mainWinHeight + 'px' }"
|
||||||
|
>
|
||||||
|
<router-view :key="routerViewKey" v-slot="{ Component }">
|
||||||
|
<transition name="move" mode="out-in">
|
||||||
|
<component :is="Component"></component>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<login-dialog
|
||||||
|
:show="show"
|
||||||
|
@hide="store.setShowLoginDialog(false)"
|
||||||
|
@success="loginCallback"
|
||||||
|
/>
|
||||||
|
<config-dialog
|
||||||
|
v-if="loginUser.id"
|
||||||
|
:show="showConfigDialog"
|
||||||
|
@hide="showConfigDialog = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import { httpGet } from "@/utils/http";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { UserFilled } from "@element-plus/icons-vue";
|
||||||
|
import { checkSession, getLicenseInfo, getSystemInfo } from "@/store/cache";
|
||||||
|
import { removeUserToken } from "@/store/session";
|
||||||
|
import LoginDialog from "@/components/LoginDialog.vue";
|
||||||
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
|
import ConfigDialog from "@/components/UserInfoDialog.vue";
|
||||||
|
import { showMessageError } from "@/utils/dialog";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const logo = ref("");
|
||||||
|
const mainNavs = ref([]);
|
||||||
|
const moreNavs = ref([]);
|
||||||
|
const curPath = ref(router.currentRoute.value.path);
|
||||||
|
const title = ref("");
|
||||||
|
const mainWinHeight = window.innerHeight - 50;
|
||||||
|
const loginUser = ref({});
|
||||||
|
const version = ref(process.env.VUE_APP_VERSION);
|
||||||
|
const routerViewKey = ref(0);
|
||||||
|
const showConfigDialog = ref(false);
|
||||||
|
const license = ref({ de_copy: true });
|
||||||
|
const docsURL = ref(process.env.VUE_APP_DOCS_URL);
|
||||||
|
const gitURL = ref(process.env.VUE_APP_GIT_URL);
|
||||||
|
|
||||||
|
const store = useSharedStore();
|
||||||
|
const show = ref(false);
|
||||||
|
watch(
|
||||||
|
() => store.showLoginDialog,
|
||||||
|
(newValue) => {
|
||||||
|
show.value = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
curPath.value = to.path;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (curPath.value === "/external") {
|
||||||
|
curPath.value = router.currentRoute.value.query.url;
|
||||||
|
}
|
||||||
|
const changeNav = (item) => {
|
||||||
|
curPath.value = item.url;
|
||||||
|
if (item.url.indexOf("http") !== -1) {
|
||||||
|
// 外部链接
|
||||||
|
router.push({ name: "ExternalLink", query: { url: item.url } });
|
||||||
|
} else {
|
||||||
|
router.push(item.url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getSystemInfo()
|
||||||
|
.then((res) => {
|
||||||
|
logo.value = res.data.logo;
|
||||||
|
title.value = res.data.title;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
|
// 获取菜单
|
||||||
|
httpGet("/api/menu/list")
|
||||||
|
.then((res) => {
|
||||||
|
mainNavs.value = res.data;
|
||||||
|
// 根据窗口的高度计算应该显示多少菜单
|
||||||
|
const rows = Math.floor((window.innerHeight - 100) / 90);
|
||||||
|
if (res.data.length > rows) {
|
||||||
|
mainNavs.value = res.data.slice(0, rows);
|
||||||
|
moreNavs.value = res.data.slice(rows);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统菜单失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
getLicenseInfo()
|
||||||
|
.then((res) => {
|
||||||
|
license.value = res.data;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
license.value = { de_copy: false };
|
||||||
|
showMessageError("获取 License 配置:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
checkSession()
|
||||||
|
.then((user) => {
|
||||||
|
loginUser.value = user;
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = function () {
|
||||||
|
httpGet("/api/user/logout")
|
||||||
|
.then(() => {
|
||||||
|
removeUserToken();
|
||||||
|
store.setShowLoginDialog(true);
|
||||||
|
store.setIsLogin(false);
|
||||||
|
loginUser.value = {};
|
||||||
|
// 刷新组件
|
||||||
|
routerViewKey.value += 1;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage.error("注销失败!");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginCallback = () => {
|
||||||
|
init();
|
||||||
|
// 刷新组件
|
||||||
|
routerViewKey.value += 1;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import "@/assets/css/custom-scroll.styl"
|
||||||
|
@import "@/assets/css/home.styl"
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,10 @@
|
|||||||
<div class="menu-box">
|
<div class="menu-box">
|
||||||
<el-menu mode="horizontal" :ellipsis="false">
|
<el-menu mode="horizontal" :ellipsis="false">
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<el-image :src="logo" class="logo" alt="Geek-AI" />
|
<!-- <el-image :src="logo" class="logo" alt="Geek-AI" /> -->
|
||||||
|
<div class="logo-box">
|
||||||
|
<img src="@/assets/img/logo.png" alt="" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<span v-if="!license.de_copy">
|
<span v-if="!license.de_copy">
|
||||||
|
|||||||