agilebuilder-ui 1.1.45 → 1.1.46-sit1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{401-1bde8dc9.js → 401-17c1ba58.js} +1 -1
- package/lib/{404-48d76996.js → 404-0bb0c21e.js} +1 -1
- package/lib/{iframe-page-77e184a0.js → iframe-page-200e9f78.js} +1 -1
- package/lib/index-fbc950ca.js +92788 -0
- package/lib/super-ui.css +1 -1
- package/lib/super-ui.js +33 -32
- package/lib/super-ui.umd.cjs +172 -133
- package/lib/{tab-content-iframe-index-39745d49.js → tab-content-iframe-index-23a2150d.js} +1 -1
- package/lib/{tab-content-index-65696e56.js → tab-content-index-55c47eb2.js} +1 -1
- package/lib/{tache-subprocess-history-ef943f95.js → tache-subprocess-history-9ffc5f04.js} +1 -1
- package/package.json +7 -2
- package/packages/chat-embed/index.ts +6 -0
- package/packages/chat-embed/src/chat-embed-message.ts +79 -0
- package/packages/chat-embed/src/chat-embed.css +117 -0
- package/packages/chat-embed/src/chat-sender.vue +240 -0
- package/packages/chat-embed/src/header.vue +50 -0
- package/packages/chat-embed/src/index.vue +425 -0
- package/packages/chat-embed/src/recommendation-message.vue +37 -0
- package/packages/chat-embed/src/util.ts +33 -0
- package/packages/fs-preview/src/fs-preview.vue +5 -1
- package/packages/fs-upload-list/src/fs-upload-list.vue +6 -1
- package/packages/fs-upload-new/src/fs-button-upload.vue +8 -1
- package/packages/fs-upload-new/src/fs-drag-upload.vue +8 -1
- package/packages/fs-upload-new/src/fs-upload-new.vue +17 -0
- package/packages/index.js +16 -13
- package/packages/json-view/index.ts +3 -0
- package/packages/json-view/json-view-dialog.vue +53 -0
- package/packages/json-view/json-view.vue +126 -0
- package/packages/super-grid/src/apis.js +11 -0
- package/packages/super-grid/src/custom-formatter.js +15 -2
- package/packages/super-grid/src/dynamic-input.vue +46 -4
- package/packages/super-grid/src/formatter.js +5 -1
- package/packages/super-grid/src/normal-column-content.vue +30 -37
- package/packages/super-grid/src/normal-column.vue +8 -1
- package/packages/super-grid/src/row-operation.vue +13 -9
- package/packages/super-grid/src/super-grid.vue +21 -8
- package/src/assets/chat-embed/avatar.png +0 -0
- package/src/i18n/langs/cn.js +17 -3
- package/src/i18n/langs/en.js +16 -2
- package/src/store/modules/chat-ai-store.ts +78 -0
- package/src/store/modules/tab-content.js +9 -3
- package/src/styles/element-ui.scss +8 -7
- package/src/styles/index.scss +19 -0
- package/src/utils/chat-ai-util.ts +31 -0
- package/src/utils/common-util.js +56 -8
- package/src/utils/insert_css.js +14 -1
- package/src/utils/jump-page-utils.js +8 -4
- package/lib/index-465b0d69.js +0 -73558
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- <div v-permission="'mc.ragFlow.converseWithChat'"> -->
|
|
3
|
+
<div v-if="enableAiChat">
|
|
4
|
+
<div ref="chatIcon" :style="style" style="position: fixed; cursor: pointer; z-index: 100">
|
|
5
|
+
<img src="https://maxkb.fit2cloud.com/ui/MaxKB.gif" @click="showChatDialog" />
|
|
6
|
+
</div>
|
|
7
|
+
<transition name="slide-up">
|
|
8
|
+
<div v-show="showChatVisable" ref="chatContainerRef" class="chat-container">
|
|
9
|
+
<!-- 头部 -->
|
|
10
|
+
<Header
|
|
11
|
+
:aiAvatar="aiAvatar"
|
|
12
|
+
:dialogFullScreen="dialogFullScreen"
|
|
13
|
+
@expandDialog="expandDialog"
|
|
14
|
+
@showChatDialog="showChatDialog"
|
|
15
|
+
/>
|
|
16
|
+
<!-- 聊天内容展示 -->
|
|
17
|
+
<!-- <div ref="talkMainRef" :key="talkMainRefKey" class="chat-embed__main"> -->
|
|
18
|
+
<div ref="talkMainRef" class="chat-embed__main" :key="talkMainRefKey">
|
|
19
|
+
<div class="chat-embed__main_content">
|
|
20
|
+
<BubbleList :list="messageList" :max-height="bubbleMaxHeight" :is-fog="true">
|
|
21
|
+
<template #content="{ item }">
|
|
22
|
+
<template v-if="!item.isRecommend && item.thinkingContent !== null">
|
|
23
|
+
<!-- ai消息正在回复中 -->
|
|
24
|
+
<Thinking
|
|
25
|
+
style="margin-bottom: 5px"
|
|
26
|
+
v-model="item.showThinkingContent"
|
|
27
|
+
:status="item.thinkingStatus"
|
|
28
|
+
auto-collapse
|
|
29
|
+
:content="item.thinkingContent"
|
|
30
|
+
max-width="100%"
|
|
31
|
+
:typing="{ interval: 20 }"
|
|
32
|
+
duration=".3s"
|
|
33
|
+
class="thinking-chain-warp"
|
|
34
|
+
>
|
|
35
|
+
</Thinking>
|
|
36
|
+
</template>
|
|
37
|
+
<template v-if="item.content">
|
|
38
|
+
<!-- ai消息和用户消息 -->
|
|
39
|
+
<div
|
|
40
|
+
:class="item.role === 'ai' ? 'content-container' : 'content-borderless-container'"
|
|
41
|
+
:style="{ maxWidth: bubbleMaxWidth }"
|
|
42
|
+
>
|
|
43
|
+
<Typewriter
|
|
44
|
+
v-if="item.role === 'ai' && !item.isRecommend"
|
|
45
|
+
:content="item.content"
|
|
46
|
+
:typing="{ interval: 10 }"
|
|
47
|
+
:is-markdown="true"
|
|
48
|
+
/>
|
|
49
|
+
<template v-else-if="item.role === 'user'">
|
|
50
|
+
{{ item.content }}
|
|
51
|
+
<template v-if="item.additionalData?.length > 0">
|
|
52
|
+
<el-divider border-style="dashed" />
|
|
53
|
+
<el-tag type="primary">
|
|
54
|
+
<template #default>
|
|
55
|
+
<div
|
|
56
|
+
style="
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
min-width: max-content;
|
|
61
|
+
margin-left: auto;
|
|
62
|
+
gap: 10px;
|
|
63
|
+
"
|
|
64
|
+
>
|
|
65
|
+
<el-icon><Paperclip /></el-icon>
|
|
66
|
+
{{ item.additionalDataLabel }}
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
</el-tag>
|
|
70
|
+
<div
|
|
71
|
+
style="
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
margin-top: 10px;
|
|
76
|
+
gap: 10px;
|
|
77
|
+
"
|
|
78
|
+
>
|
|
79
|
+
<el-button plain round size="small" @click="viewJson(item.additionalData)">
|
|
80
|
+
<el-icon><View /></el-icon> <span>预览数据</span>
|
|
81
|
+
</el-button>
|
|
82
|
+
<el-button
|
|
83
|
+
plain
|
|
84
|
+
round
|
|
85
|
+
size="small"
|
|
86
|
+
@click="downloadJsonData(item.additionalData, item.menuName)"
|
|
87
|
+
>
|
|
88
|
+
<el-icon><Download /></el-icon> 下载数据
|
|
89
|
+
</el-button>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
</template>
|
|
93
|
+
<template v-else> {{ item.content }} </template>
|
|
94
|
+
</div>
|
|
95
|
+
<template v-if="item.recommendations && item.recommendations.length > 0">
|
|
96
|
+
<!-- 推荐内容的消息 -->
|
|
97
|
+
<RecommendationMessage :item="item" @clickRecommendation="clickRecommendation" />
|
|
98
|
+
</template>
|
|
99
|
+
</template>
|
|
100
|
+
</template>
|
|
101
|
+
<template #footer="{ item }">
|
|
102
|
+
<div
|
|
103
|
+
v-if="item.role === 'ai' && !item.isRecommend && item.thinkingStatus !== 'thinking'"
|
|
104
|
+
class="footer-container"
|
|
105
|
+
>
|
|
106
|
+
<el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.regenerate')">
|
|
107
|
+
<el-button type="info" :icon="Refresh" size="small" circle @click="regenerateQuestion(item)" />
|
|
108
|
+
</el-tooltip>
|
|
109
|
+
<el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.copy')">
|
|
110
|
+
<el-button
|
|
111
|
+
type="info"
|
|
112
|
+
color="#626aef"
|
|
113
|
+
:icon="DocumentCopy"
|
|
114
|
+
size="small"
|
|
115
|
+
circle
|
|
116
|
+
@click="cpoyContent(item)"
|
|
117
|
+
/>
|
|
118
|
+
</el-tooltip>
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
121
|
+
</BubbleList>
|
|
122
|
+
</div>
|
|
123
|
+
<!-- 对话输入框 -->
|
|
124
|
+
<div ref="talkInputRef" class="chat-embed__input_container">
|
|
125
|
+
<ChatSender
|
|
126
|
+
:loading="loading"
|
|
127
|
+
@submit-question="sendQuestion"
|
|
128
|
+
@new-chat-session="newChatSession"
|
|
129
|
+
@handle-cancel="handleCancel"
|
|
130
|
+
@view-json="viewJson"
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</transition>
|
|
136
|
+
<json-view-dialog v-model="jsonDialogVisible" :json-object="jsonObject" :append-to-body="true" title="预览数据" />
|
|
137
|
+
</div>
|
|
138
|
+
</template>
|
|
139
|
+
<script setup lang="ts">
|
|
140
|
+
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
141
|
+
import { Refresh, DocumentCopy } from '@element-plus/icons-vue'
|
|
142
|
+
import { BubbleList, Thinking, useXStream, Typewriter } from 'vue-element-plus-x'
|
|
143
|
+
import { useDraggable } from '@vueuse/core'
|
|
144
|
+
import { ElMessage } from 'element-plus'
|
|
145
|
+
import { getMessageTemplate, defaultMessage } from './chat-embed-message'
|
|
146
|
+
import RecommendationMessage from './recommendation-message.vue'
|
|
147
|
+
import { BubbleProps } from 'vue-element-plus-x/types/Bubble'
|
|
148
|
+
import { useI18n } from 'vue-i18n'
|
|
149
|
+
import Header from './header.vue'
|
|
150
|
+
import ChatSender from './chat-sender.vue'
|
|
151
|
+
import { JsonViewDialog } from '../../json-view'
|
|
152
|
+
import { generateFileName, downloadJsonFile } from './util.ts'
|
|
153
|
+
|
|
154
|
+
const enableAiChat = ref(false)
|
|
155
|
+
if (window.$vueApp.config.globalProperties.enableAiChat) {
|
|
156
|
+
enableAiChat.value = true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { t } = useI18n()
|
|
160
|
+
const chatIcon = ref()
|
|
161
|
+
let isActualClick = ref(true)
|
|
162
|
+
const isActualClickTimer = ref(null)
|
|
163
|
+
|
|
164
|
+
const jsonDialogVisible = ref(false)
|
|
165
|
+
const jsonObject = ref({})
|
|
166
|
+
|
|
167
|
+
const initialX = document.documentElement.clientWidth - 100
|
|
168
|
+
const initialY = document.documentElement.clientHeight - 100
|
|
169
|
+
const { x, y, style } = useDraggable(chatIcon, {
|
|
170
|
+
// 聊天图标的初始位置
|
|
171
|
+
initialValue: { x: initialX, y: initialY },
|
|
172
|
+
preventDefault: true,
|
|
173
|
+
onStart() {
|
|
174
|
+
// 拖拽开始时重置状态
|
|
175
|
+
isActualClick.value = true
|
|
176
|
+
},
|
|
177
|
+
onMove(position) {
|
|
178
|
+
isActualClick.value = false
|
|
179
|
+
// 限制拖拽范围 防止拖出屏幕外
|
|
180
|
+
const maxX = window.innerWidth - chatIcon.value.offsetWidth
|
|
181
|
+
const maxY = window.innerHeight - chatIcon.value.offsetHeight
|
|
182
|
+
position.x = Math.max(0, Math.min(position.x, maxX))
|
|
183
|
+
position.y = Math.max(0, Math.min(position.y, maxY))
|
|
184
|
+
},
|
|
185
|
+
onEnd() {
|
|
186
|
+
adjustChatIconPosition()
|
|
187
|
+
// 延迟重置确保 click 事件先处理
|
|
188
|
+
setTimeout(() => (isActualClick.value = true), 50)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
function adjustChatIconPosition() {
|
|
193
|
+
if (!chatIcon.value) return
|
|
194
|
+
const maxX = window.innerWidth - chatIcon.value.offsetWidth
|
|
195
|
+
const maxY = window.innerHeight - chatIcon.value.offsetHeight
|
|
196
|
+
if (x.value > maxX) x.value = maxX
|
|
197
|
+
if (y.value > maxY) y.value = maxY
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const talkInputRef = ref()
|
|
201
|
+
// 显示聊天窗口的状态
|
|
202
|
+
const showChatVisable = ref(false)
|
|
203
|
+
// 聊天窗口的引用
|
|
204
|
+
const chatContainerRef = ref()
|
|
205
|
+
// 聊天窗口主区域的引用
|
|
206
|
+
const talkMainRef = ref()
|
|
207
|
+
// 聊天输入框状态
|
|
208
|
+
const loading = ref(false)
|
|
209
|
+
// 聊天窗口是否全屏
|
|
210
|
+
const dialogFullScreen = ref(false)
|
|
211
|
+
|
|
212
|
+
const talkMainRefKey = ref(0)
|
|
213
|
+
|
|
214
|
+
const bubbleMaxWidth = ref('')
|
|
215
|
+
const bubbleMaxHeight = ref('500px')
|
|
216
|
+
|
|
217
|
+
const aiAvatar = ref('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png')
|
|
218
|
+
|
|
219
|
+
// 聊天内容
|
|
220
|
+
const messageList: any = ref([])
|
|
221
|
+
// 默认ai提示消息
|
|
222
|
+
messageList.value.push(defaultMessage)
|
|
223
|
+
// messageList.value.push(getMessageTemplate('user', '11111111111', true))
|
|
224
|
+
// 正在生成的ai消息
|
|
225
|
+
const answerContent: any = ref({})
|
|
226
|
+
// 后端会话session_id
|
|
227
|
+
const session_id = ref(null)
|
|
228
|
+
|
|
229
|
+
onMounted(() => {
|
|
230
|
+
window.addEventListener('resize', adjustChatIconPosition)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
onUnmounted(() => {
|
|
234
|
+
window.removeEventListener('resize', adjustChatIconPosition)
|
|
235
|
+
// 清除定时器
|
|
236
|
+
if (isActualClickTimer.value) {
|
|
237
|
+
clearTimeout(isActualClickTimer.value)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// 处理聊天窗口的显示和隐藏
|
|
242
|
+
function showChatDialog() {
|
|
243
|
+
if (isActualClick.value) {
|
|
244
|
+
showChatVisable.value = !showChatVisable.value
|
|
245
|
+
talkMainRefKey.value++
|
|
246
|
+
nextTick(() => {
|
|
247
|
+
setHeightAndWidth()
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const { cancel, data } = useXStream()
|
|
253
|
+
|
|
254
|
+
// 发送聊天内容
|
|
255
|
+
function sendQuestion(submitData: any) {
|
|
256
|
+
const question = submitData.question
|
|
257
|
+
if (!question) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
data.value = []
|
|
261
|
+
const message = getMessageTemplate('user', question, true)
|
|
262
|
+
if (submitData.additionalData && submitData.additionalData.length > 0) {
|
|
263
|
+
message.additionalData = submitData.additionalData
|
|
264
|
+
message.additionalDataLabel = submitData.additionalDataLabel
|
|
265
|
+
message.menuName = submitData.menuName
|
|
266
|
+
message.menuCode = submitData.menuCode
|
|
267
|
+
}
|
|
268
|
+
messageList.value.push(message)
|
|
269
|
+
// console.log('submit', question)
|
|
270
|
+
loading.value = true
|
|
271
|
+
if (!session_id.value) {
|
|
272
|
+
session_id.value = new Date().getTime().toString()
|
|
273
|
+
}
|
|
274
|
+
// 创建一个新的消息对象
|
|
275
|
+
answerContent.value = getMessageTemplate('ai', '', false)
|
|
276
|
+
// 设置消息的状态为正在思考 并存下历史的 question信息
|
|
277
|
+
answerContent.value.thinkingStatus = 'thinking'
|
|
278
|
+
answerContent.value.question = question
|
|
279
|
+
answerContent.value.menuName = submitData.menuName
|
|
280
|
+
answerContent.value.menuCode = submitData.menuCode
|
|
281
|
+
answerContent.value.askAdditionalData = submitData.additionalData
|
|
282
|
+
messageList.value.push(answerContent.value)
|
|
283
|
+
try {
|
|
284
|
+
window.$vueApp.config.globalProperties.$http
|
|
285
|
+
.post(window.$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck', {
|
|
286
|
+
question: question,
|
|
287
|
+
messageId: session_id.value,
|
|
288
|
+
data: JSON.stringify(submitData.additionalData)
|
|
289
|
+
})
|
|
290
|
+
.then((res) => {
|
|
291
|
+
// 创建一个新的消息对象
|
|
292
|
+
answerContent.value.thinkingStatus = 'end'
|
|
293
|
+
answerContent.value.content = res
|
|
294
|
+
answerContent.value = {}
|
|
295
|
+
loading.value = false
|
|
296
|
+
})
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.error('Fetch error:', err)
|
|
299
|
+
messageList.push(getMessageTemplate('ai', t('chatEmbed.requestFailed'), false))
|
|
300
|
+
loading.value = false
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 处理聊天内容的取消
|
|
305
|
+
function handleCancel() {
|
|
306
|
+
loading.value = false
|
|
307
|
+
messageList.value[messageList.value.length - 1].thinkingStatus = 'end'
|
|
308
|
+
cancel()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 处理聊天窗口的全屏和缩小
|
|
312
|
+
function expandDialog() {
|
|
313
|
+
dialogFullScreen.value = !dialogFullScreen.value
|
|
314
|
+
setHeightAndWidth()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function setHeightAndWidth() {
|
|
318
|
+
let heightVh = 100
|
|
319
|
+
let widthVw = 40
|
|
320
|
+
if (dialogFullScreen.value) {
|
|
321
|
+
heightVh = 100
|
|
322
|
+
widthVw = 100
|
|
323
|
+
}
|
|
324
|
+
chatContainerRef.value.style.height = `calc(${heightVh}vh - var(--chat-padding) * 2)`
|
|
325
|
+
chatContainerRef.value.style.width = `calc(${widthVw}vw - var(--chat-padding) * 2)`
|
|
326
|
+
// talkMainRef.value.style.height = `calc(${heightVh}vh - var(--header-height) - var(--chat-padding) * 2)`
|
|
327
|
+
const talkHeight = talkInputRef.value.offsetHeight
|
|
328
|
+
// alert(`talkHeight: ${talkHeight}`)
|
|
329
|
+
// bubbleMaxHeight.value = `calc(${heightVh}vh - var(--header-height) - var(--chat-padding) * 5 - 144px)`
|
|
330
|
+
nextTick(() => {
|
|
331
|
+
const talkHeight = talkInputRef.value?.offsetHeight || 80
|
|
332
|
+
// 定义具体的像素值
|
|
333
|
+
const headerHeight = 56
|
|
334
|
+
const chatPadding = 12
|
|
335
|
+
// 更清晰的计算:总高度 - 头部 - 输入框 - 各种padding和间距
|
|
336
|
+
bubbleMaxHeight.value = `calc(${heightVh}vh - ${headerHeight}px - ${talkHeight}px - ${chatPadding * 4}px)`
|
|
337
|
+
talkMainRefKey.value++
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 点击推荐内容的处理函数
|
|
342
|
+
// 这里可以根据需要进行处理,比如发送请求、更新状态等
|
|
343
|
+
function clickRecommendation(item: BubbleProps) {
|
|
344
|
+
console.log('clickRecommendation', item)
|
|
345
|
+
sendQuestion(item.content)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function regenerateQuestion(answerData) {
|
|
349
|
+
sendQuestion({
|
|
350
|
+
question: answerData.question,
|
|
351
|
+
additionalData: answerData.additionalData,
|
|
352
|
+
additionalDataLabel: answerData.additionalDataLabel,
|
|
353
|
+
menuName: answerData.menuName,
|
|
354
|
+
menuCode: answerData.menuCode
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function cpoyContent(item: BubbleProps) {
|
|
359
|
+
// 复制内容到剪贴板
|
|
360
|
+
if (!item.content) {
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
navigator.clipboard.writeText(item.content).then(() => {
|
|
364
|
+
ElMessage({
|
|
365
|
+
message: t('chatEmbed.copySuccess'),
|
|
366
|
+
type: 'success'
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function newChatSession() {
|
|
372
|
+
// 清空聊天记录
|
|
373
|
+
messageList.value = []
|
|
374
|
+
if (session_id.value) {
|
|
375
|
+
try {
|
|
376
|
+
window.$vueApp.config.globalProperties.$http
|
|
377
|
+
.post(window.$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck/clear/' + session_id.value)
|
|
378
|
+
.then((res) => {
|
|
379
|
+
//
|
|
380
|
+
})
|
|
381
|
+
} catch (err) {
|
|
382
|
+
console.error('Fetch error:', err)
|
|
383
|
+
messageList.push(getMessageTemplate('ai', t('chatEmbed.requestFailed'), false))
|
|
384
|
+
loading.value = false
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
session_id.value = null
|
|
388
|
+
// 重置加载状态
|
|
389
|
+
loading.value = false
|
|
390
|
+
messageList.value.push(defaultMessage)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function viewJson(viewData: any) {
|
|
394
|
+
jsonObject.value = viewData
|
|
395
|
+
jsonDialogVisible.value = true
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 下载 JSON 数据功能
|
|
399
|
+
function downloadJsonData(data: any, fileName: string) {
|
|
400
|
+
try {
|
|
401
|
+
downloadJsonFile(data, generateFileName(fileName || 'data'))
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error('下载失败:', error)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
</script>
|
|
407
|
+
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
408
|
+
@import url('./chat-embed.css');
|
|
409
|
+
:root {
|
|
410
|
+
--header-height: 56px;
|
|
411
|
+
--chat-padding: 12px;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
h4 {
|
|
415
|
+
font-size: 16px;
|
|
416
|
+
margin-top: 0px;
|
|
417
|
+
margin-bottom: 0px;
|
|
418
|
+
}
|
|
419
|
+
:deep(.el-bubble-content-wrapper .el-bubble-content) {
|
|
420
|
+
padding: 0 !important;
|
|
421
|
+
}
|
|
422
|
+
:deep(.el-divider--horizontal) {
|
|
423
|
+
margin: 5px 0;
|
|
424
|
+
}
|
|
425
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Bubble
|
|
3
|
+
v-for="(recommendation, index) in item.recommendations"
|
|
4
|
+
class="chat-embed__recommendation"
|
|
5
|
+
:no-style="true"
|
|
6
|
+
variant="borderless"
|
|
7
|
+
>
|
|
8
|
+
<template v-if="index === 0" #header>
|
|
9
|
+
<div class="content-container-header">推荐内容</div>
|
|
10
|
+
</template>
|
|
11
|
+
<template #content>
|
|
12
|
+
<div class="content-borderless-container" @click="clickRecommendation(recommendation)">
|
|
13
|
+
{{ recommendation.content }}
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
</Bubble>
|
|
17
|
+
</template>
|
|
18
|
+
<script lang="ts" setup>
|
|
19
|
+
import { Bubble } from 'vue-element-plus-x'
|
|
20
|
+
import { defineProps, defineEmits } from 'vue'
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
item: {
|
|
23
|
+
type: Object,
|
|
24
|
+
required: true
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits(['clickRecommendation'])
|
|
29
|
+
|
|
30
|
+
const clickRecommendation = (recommendation: any) => {
|
|
31
|
+
emit('clickRecommendation', recommendation)
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
@import url('./chat-embed.css');
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// 生成文件名
|
|
2
|
+
function generateFileName(fileName: string): string {
|
|
3
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
4
|
+
const safeName = fileName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_')
|
|
5
|
+
return `${safeName}_${timestamp}.json`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 核心下载方法
|
|
9
|
+
function downloadJsonFile(data: any, fileName: string) {
|
|
10
|
+
// 将数据转换为格式化的 JSON 字符串
|
|
11
|
+
const jsonString = JSON.stringify(data, null, 2)
|
|
12
|
+
|
|
13
|
+
// 创建 Blob 对象
|
|
14
|
+
const blob = new Blob([jsonString], {
|
|
15
|
+
type: 'application/json;charset=utf-8'
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// 创建下载链接
|
|
19
|
+
const url = URL.createObjectURL(blob)
|
|
20
|
+
const link = document.createElement('a')
|
|
21
|
+
link.href = url
|
|
22
|
+
link.download = fileName
|
|
23
|
+
|
|
24
|
+
// 触发下载
|
|
25
|
+
document.body.appendChild(link)
|
|
26
|
+
link.click()
|
|
27
|
+
|
|
28
|
+
// 清理
|
|
29
|
+
document.body.removeChild(link)
|
|
30
|
+
URL.revokeObjectURL(url)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { generateFileName, downloadJsonFile }
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
</span>
|
|
12
12
|
</template>
|
|
13
13
|
<div v-if="firstFileName" ref="toolRef" class="fs-preview-tool">
|
|
14
|
+
<slot name="tool-prefix" :item="getFirstFile()"></slot>
|
|
14
15
|
<el-tooltip v-if="enableDelete && !disabled" :content="$t('fsUpload.delete')">
|
|
15
16
|
<super-icon @click="deleteFirst()" iconValue="amb-color-icon-shanchu" />
|
|
16
17
|
</el-tooltip>
|
|
@@ -20,11 +21,12 @@
|
|
|
20
21
|
<el-tooltip v-if="!disabled || !fileSetObj.disabledNoDownload" :content="$t('fsUpload.download')">
|
|
21
22
|
<super-icon @click="downloadFirst()" iconValue="amb-color-icon-xiazai" />
|
|
22
23
|
</el-tooltip>
|
|
24
|
+
<slot name="tool-suffix" :item="getFirstFile()"></slot>
|
|
23
25
|
<el-divider v-if="showMultipleFile" direction="vertical" />
|
|
24
26
|
<el-popover :width="450" trigger="click">
|
|
25
27
|
<template #reference>
|
|
26
28
|
<el-button v-if="showMultipleFile" type="primary" round size="small" @click="showMultiView">
|
|
27
|
-
|
|
29
|
+
{{ $t('fsUpload.more') }}
|
|
28
30
|
<el-icon><ArrowDownBold /></el-icon>
|
|
29
31
|
</el-button>
|
|
30
32
|
</template>
|
|
@@ -53,6 +55,7 @@
|
|
|
53
55
|
<el-link type="primary" v-if="!disabled || !fileSetObj.disabledNoDownload" underline>
|
|
54
56
|
{{ $t('fsUpload.download') }}
|
|
55
57
|
</el-link> -->
|
|
58
|
+
<slot name="tool-more-prefix" :item="item"></slot>
|
|
56
59
|
<el-tooltip v-if="enableDelete" :content="$t('fsUpload.delete')">
|
|
57
60
|
<super-icon @click="deleteFile(item)" iconValue="amb-color-icon-shanchu" />
|
|
58
61
|
</el-tooltip>
|
|
@@ -375,6 +378,7 @@ export default {
|
|
|
375
378
|
})
|
|
376
379
|
}
|
|
377
380
|
}
|
|
381
|
+
return this.fileList
|
|
378
382
|
},
|
|
379
383
|
deleteFirst() {
|
|
380
384
|
const fileInfo = this.getFirstFile()
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
:show-file-list="false"
|
|
22
22
|
:headers="headers"
|
|
23
23
|
:on-success="uploadOnSuccess"
|
|
24
|
+
:on-error="onError"
|
|
24
25
|
:with-credentials="true"
|
|
25
26
|
name="file"
|
|
26
27
|
>
|
|
@@ -310,9 +311,13 @@ export default {
|
|
|
310
311
|
},
|
|
311
312
|
getFileIconByName(fileName) {
|
|
312
313
|
return getFileIconByName(fileName)
|
|
314
|
+
},
|
|
315
|
+
onError(error) {
|
|
316
|
+
$emit(this, 'on-error', error)
|
|
317
|
+
|
|
313
318
|
}
|
|
314
319
|
},
|
|
315
|
-
emits: ['close', 'upload-success', 'delete-success', 'delete', 'update:value', 'close', 'update:value']
|
|
320
|
+
emits: ['close', 'upload-success', 'delete-success', 'delete', 'update:value', 'close', 'update:value', 'on-error']
|
|
316
321
|
}
|
|
317
322
|
</script>
|
|
318
323
|
<style lang="scss" scoped>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
:before-upload="handleBeforeUpload"
|
|
15
15
|
:before-remove="beforeRemove"
|
|
16
16
|
:on-success="onSuccess"
|
|
17
|
+
:on-error="onError"
|
|
17
18
|
>
|
|
18
19
|
<el-button size="small" type="primary">{{ placeholder }}</el-button>
|
|
19
20
|
<template #tip>
|
|
@@ -40,13 +41,15 @@
|
|
|
40
41
|
</template>
|
|
41
42
|
|
|
42
43
|
<script setup lang="ts">
|
|
43
|
-
import { ref, defineProps } from 'vue'
|
|
44
|
+
import { ref, defineProps, defineEmits } from 'vue'
|
|
44
45
|
import { getSystemFrontendUrl, isPlateSys } from '../../../src/utils/common-util'
|
|
45
46
|
import { getToken } from '../../../src/utils/auth'
|
|
46
47
|
import type { UploadFile, UploadFiles } from 'element-plus'
|
|
47
48
|
import fsPreviewNew from './fs-preview-new.vue'
|
|
48
49
|
import { ElMessage } from 'element-plus'
|
|
49
50
|
import { useI18n } from 'vue-i18n'
|
|
51
|
+
|
|
52
|
+
const emits = defineEmits(['on-error'])
|
|
50
53
|
const { t } = useI18n()
|
|
51
54
|
const props = defineProps({
|
|
52
55
|
openFsUpload: {
|
|
@@ -200,4 +203,8 @@ const onSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFil
|
|
|
200
203
|
})
|
|
201
204
|
props.onSuccess(response, uploadFile, uploadFiles)
|
|
202
205
|
}
|
|
206
|
+
|
|
207
|
+
const onError = (error: any) => {
|
|
208
|
+
emits('on-error', error)
|
|
209
|
+
}
|
|
203
210
|
</script>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
:before-upload="handleBeforeUpload"
|
|
13
13
|
:on-success="onUploadSuccess"
|
|
14
14
|
:before-remove="beforeRemove"
|
|
15
|
+
:on-error="onError"
|
|
15
16
|
>
|
|
16
17
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
|
17
18
|
<div class="el-upload__text">{{ placeholder }}</div>
|
|
@@ -38,13 +39,15 @@
|
|
|
38
39
|
|
|
39
40
|
<script setup lang="ts">
|
|
40
41
|
import { UploadFilled } from '@element-plus/icons-vue'
|
|
41
|
-
import { ref, defineProps } from 'vue'
|
|
42
|
+
import { ref, defineProps, defineEmits } from 'vue'
|
|
42
43
|
import { getSystemFrontendUrl, isPlateSys } from '../../../src/utils/common-util'
|
|
43
44
|
import { getToken } from '../../../src/utils/auth'
|
|
44
45
|
import type { UploadFile, UploadFiles } from 'element-plus'
|
|
45
46
|
import fsPreviewNew from './fs-preview-new.vue'
|
|
46
47
|
import { ElMessage } from 'element-plus'
|
|
47
48
|
import { useI18n } from 'vue-i18n'
|
|
49
|
+
|
|
50
|
+
const emits = defineEmits(['on-error'])
|
|
48
51
|
const { t } = useI18n()
|
|
49
52
|
const props = defineProps({
|
|
50
53
|
openFsUpload: {
|
|
@@ -197,4 +200,8 @@ const onUploadSuccess = (response: any, uploadFile: UploadFile, uploadFiles: Upl
|
|
|
197
200
|
})
|
|
198
201
|
props.onSuccess(response, uploadFile, uploadFiles)
|
|
199
202
|
}
|
|
203
|
+
|
|
204
|
+
const onError = (error: any) => {
|
|
205
|
+
emits('on-error', error)
|
|
206
|
+
}
|
|
200
207
|
</script>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
}"
|
|
18
18
|
@upload-success="onInputUploadSuccess"
|
|
19
19
|
@delete-success="onInputDeleteSuccess"
|
|
20
|
+
@on-error="onError"
|
|
20
21
|
/>
|
|
21
22
|
<fs-button-upload
|
|
22
23
|
v-else-if="!isMobile && displayType === 'button'"
|
|
@@ -135,6 +136,9 @@ import FileUploadInputMobile from './file-upload-mobile/file-upload-input.vue'
|
|
|
135
136
|
import FsUploadList from '../../fs-upload-list/src/fs-upload-list.vue'
|
|
136
137
|
import { ref, defineProps } from 'vue'
|
|
137
138
|
import { getReplaceUrlDomain, isMobileBrowser, isPlateSys } from '../../../src/utils/common-util'
|
|
139
|
+
import { useI18n } from 'vue-i18n'
|
|
140
|
+
import { ElMessage } from 'element-plus'
|
|
141
|
+
const { t } = useI18n()
|
|
138
142
|
const props = defineProps({
|
|
139
143
|
openFsUpload: {
|
|
140
144
|
type: Object,
|
|
@@ -377,5 +381,18 @@ function uploadFileDone(data) {
|
|
|
377
381
|
fileUploadRef.value.uploadFileDone(data)
|
|
378
382
|
}
|
|
379
383
|
}
|
|
384
|
+
|
|
385
|
+
function onError(error) {
|
|
386
|
+
console.error('Upload error:', error)
|
|
387
|
+
if (error.message) {
|
|
388
|
+
let message = JSON.parse(error.message)
|
|
389
|
+
if (message && message.message) {
|
|
390
|
+
message = JSON.parse(message.message)
|
|
391
|
+
}
|
|
392
|
+
ElMessage.error(message)
|
|
393
|
+
} else {
|
|
394
|
+
ElMessage.error(t('fsUpload.uploadFailed'))
|
|
395
|
+
}
|
|
396
|
+
}
|
|
380
397
|
defineExpose({ pickFileDone, uploadFileDone })
|
|
381
398
|
</script>
|