1
1
<template >
2
- <div ref =" messageContainer" style = " height : 100% ; overflow-y : auto ; position : relative " >
2
+ <div ref =" messageContainer" class = " h- 100% overflow-y relative" >
3
3
<div class =" chat-list" v-for =" (item, index) in list" :key =" index" >
4
- <!-- 靠左 message -->
4
+ <!-- 靠左 message:system、assistant 类型 -->
5
5
<div class =" left-message message-item" v-if =" item.type !== 'user'" >
6
6
<div class =" avatar" >
7
7
<el-avatar :src =" roleAvatar" />
14
14
<MarkdownView class =" left-text" :content =" item.content" />
15
15
</div >
16
16
<div class =" left-btns" >
17
- <el-button class =" btn-cus" link @click =" noCopy (item.content)" >
17
+ <el-button class =" btn-cus" link @click =" copyContent (item.content)" >
18
18
<img class =" btn-image" src =" @/assets/ai/copy.svg" />
19
19
</el-button >
20
20
<el-button v-if =" item.id > 0" class =" btn-cus" link @click =" onDelete(item.id)" >
21
- <img class =" btn-image" src =" @/assets/ai/delete.svg" style = " height : 17 px " />
21
+ <img class =" btn-image h-17px " src =" @/assets/ai/delete.svg" />
22
22
</el-button >
23
23
</div >
24
24
</div >
25
25
</div >
26
- <!-- 靠右 message -->
26
+ <!-- 靠右 message:user 类型 -->
27
27
<div class =" right-message message-item" v-if =" item.type === 'user'" >
28
28
<div class =" avatar" >
29
29
<el-avatar :src =" userAvatar" />
36
36
<div class =" right-text" >{{ item.content }}</div >
37
37
</div >
38
38
<div class =" right-btns" >
39
- <el-button class =" btn-cus" link @click =" noCopy (item.content)" >
39
+ <el-button class =" btn-cus" link @click =" copyContent (item.content)" >
40
40
<img class =" btn-image" src =" @/assets/ai/copy.svg" />
41
41
</el-button >
42
42
<el-button class =" btn-cus" link @click =" onDelete(item.id)" >
43
- <img
44
- class =" btn-image"
45
- src =" @/assets/ai/delete.svg"
46
- style =" height : 17px ; margin-right : 12px "
47
- />
43
+ <img class =" btn-image h-17px mr-12px" src =" @/assets/ai/delete.svg" />
48
44
</el-button >
49
45
<el-button class =" btn-cus" link @click =" onRefresh(item)" >
50
46
<el-icon size =" 17" ><RefreshRight /></el-icon >
63
59
</div >
64
60
</template >
65
61
<script setup lang="ts">
62
+ import { PropType } from ' vue'
66
63
import { formatDate } from ' @/utils/formatTime'
67
64
import MarkdownView from ' @/components/MarkdownView/index.vue'
68
- import { ChatMessageApi , ChatMessageVO } from ' @/api/ai/chat/message'
69
65
import { useClipboard } from ' @vueuse/core'
70
- import { PropType } from ' vue'
71
66
import { ArrowDownBold , Edit , RefreshRight } from ' @element-plus/icons-vue'
67
+ import { ChatMessageApi , ChatMessageVO } from ' @/api/ai/chat/message'
72
68
import { ChatConversationVO } from ' @/api/ai/chat/conversation'
73
69
import { useUserStore } from ' @/store/modules/user'
74
70
import userAvatarDefaultImg from ' @/assets/imgs/avatar.gif'
75
71
import roleAvatarDefaultImg from ' @/assets/ai/gpt.svg'
76
72
73
+ const message = useMessage () // 消息弹窗
77
74
const { copy } = useClipboard () // 初始化 copy 到粘贴板
78
- // 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
75
+ const userStore = useUserStore ()
76
+
77
+ // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
79
78
const messageContainer: any = ref (null )
80
79
const isScrolling = ref (false ) // 用于判断用户是否在滚动
81
80
82
- const userStore = useUserStore ()
83
81
const userAvatar = computed (() => userStore .user .avatar ?? userAvatarDefaultImg )
84
82
const roleAvatar = computed (() => props .conversation .roleAvatar ?? roleAvatarDefaultImg )
85
83
@@ -95,12 +93,16 @@ const props = defineProps({
95
93
}
96
94
})
97
95
96
+ const { list } = toRefs (props ) // 消息列表
97
+
98
+ const emits = defineEmits ([' onDeleteSuccess' , ' onRefresh' , ' onEdit' ]) // 定义 emits
99
+
98
100
// ============ 处理对话滚动 ==============
99
101
102
+ /** 滚动到底部 */
100
103
const scrollToBottom = async (isIgnore ? : boolean ) => {
104
+ // 注意要使用 nextTick 以免获取不到dom
101
105
await nextTick (() => {
102
- // TODO @fan:中文写作习惯,中英文之间要有空格;另外,nextick 哈,idea 如果有绿色波兰线,可以关注下
103
- // 注意要使用nexttick以免获取不到dom
104
106
if (isIgnore || ! isScrolling .value ) {
105
107
messageContainer .value .scrollTop =
106
108
messageContainer .value .scrollHeight - messageContainer .value .offsetHeight
@@ -122,75 +124,48 @@ function handleScroll() {
122
124
}
123
125
}
124
126
125
- /**
126
- * 复制
127
- */
128
- const noCopy = async (content ) => {
129
- copy (content )
130
- ElMessage ({
131
- message: ' 复制成功!' ,
132
- type: ' success'
133
- })
127
+ /** 回到底部 */
128
+ const handleGoBottom = async () => {
129
+ const scrollContainer = messageContainer .value
130
+ scrollContainer .scrollTop = scrollContainer .scrollHeight
131
+ }
132
+
133
+ /** 回到顶部 */
134
+ const handlerGoTop = async () => {
135
+ const scrollContainer = messageContainer .value
136
+ scrollContainer .scrollTop = 0
137
+ }
138
+
139
+ defineExpose ({ scrollToBottom , handlerGoTop }) // 提供方法给 parent 调用
140
+
141
+ // ============ 处理消息操作 ==============
142
+
143
+ /** 复制 */
144
+ const copyContent = async (content ) => {
145
+ await copy (content )
146
+ message .success (' 复制成功!' )
134
147
}
135
148
136
- /**
137
- * 删除
138
- */
149
+ /** 删除 */
139
150
const onDelete = async (id ) => {
140
151
// 删除 message
141
152
await ChatMessageApi .deleteChatMessage (id )
142
- ElMessage ({
143
- message: ' 删除成功!' ,
144
- type: ' success'
145
- })
153
+ message .success (' 删除成功!' )
146
154
// 回调
147
155
emits (' onDeleteSuccess' )
148
156
}
149
157
150
- /**
151
- * 刷新
152
- */
158
+ /** 刷新 */
153
159
const onRefresh = async (message : ChatMessageVO ) => {
154
160
emits (' onRefresh' , message )
155
161
}
156
162
157
- /**
158
- * 编辑
159
- */
163
+ /** 编辑 */
160
164
const onEdit = async (message : ChatMessageVO ) => {
161
165
emits (' onEdit' , message )
162
166
}
163
167
164
- /**
165
- * 回到底部
166
- */
167
- const handleGoBottom = async () => {
168
- const scrollContainer = messageContainer .value
169
- scrollContainer .scrollTop = scrollContainer .scrollHeight
170
- }
171
-
172
- /**
173
- * 回到顶部
174
- */
175
- const handlerGoTop = async () => {
176
- const scrollContainer = messageContainer .value
177
- scrollContainer .scrollTop = 0
178
- }
179
-
180
- // 监听 list
181
- // TODO @fan:这个木有,是不是删除啦
182
- const { list, conversationId } = toRefs (props )
183
- watch (list , async (newValue , oldValue ) => {
184
- console .log (' watch list' , list )
185
- })
186
-
187
- // 提供方法给 parent 调用
188
- defineExpose ({ scrollToBottom , handlerGoTop })
189
-
190
- // 定义 emits
191
- const emits = defineEmits ([' onDeleteSuccess' , ' onRefresh' , ' onEdit' ])
192
-
193
- // onMounted
168
+ /** 初始化 */
194
169
onMounted (async () => {
195
170
messageContainer .value .addEventListener (' scroll' , handleScroll )
196
171
})
@@ -199,15 +174,7 @@ onMounted(async () => {
199
174
<style scoped lang="scss">
200
175
.message-container {
201
176
position : relative ;
202
- // top: 0;
203
- // bottom: 0;
204
- // left: 0;
205
- // right: 0;
206
- // width: 100%;
207
- // height: 100%;
208
177
overflow-y : scroll ;
209
- // padding: 0 15px;
210
- // z-index: -1;
211
178
}
212
179
213
180
// 中间
@@ -231,11 +198,6 @@ onMounted(async () => {
231
198
justify-content : flex-start ;
232
199
}
233
200
234
- .avatar {
235
- // height: 170px;
236
- // width: 170px;
237
- }
238
-
239
201
.message {
240
202
display : flex ;
241
203
flex-direction : column ;
@@ -272,7 +234,6 @@ onMounted(async () => {
272
234
color : #fff ;
273
235
display : inline ;
274
236
background-color : #267fff ;
275
- color : #fff ;
276
237
box-shadow : 0 0 0 1px #267fff ;
277
238
border-radius : 10px ;
278
239
padding : 10px ;
0 commit comments