Files
FiDA_Front/src/views/home/agent/components/Preview.vue

519 lines
12 KiB
Vue
Raw Normal View History

2026-02-11 17:27:51 +08:00
<template>
<div
2026-04-14 09:34:33 +08:00
ref="containerRef"
2026-02-11 17:27:51 +08:00
class="preview-container flex"
:class="type === 'sketch' ? 'sketch-preview' : 'report-preview'"
>
<template v-if="type === 'sketch'">
2026-02-25 15:10:03 +08:00
<div
class="sketch-item"
2026-03-30 11:03:52 +08:00
v-for="(item, index) in combineSketchList"
2026-02-25 15:10:03 +08:00
:key="'sketch-item-' + index"
>
2026-03-11 10:53:25 +08:00
<el-dropdown trigger="click" class="menu-btn">
<Menu />
<template #dropdown>
<el-dropdown-menu>
2026-03-24 16:57:40 +08:00
<el-dropdown-item
class="sketch-item flex align-center"
@click="handleClickQuote(item)"
>
2026-03-11 10:53:25 +08:00
<img
src="@/assets/images/restore-sketch.png"
class="dropdown-icon restore"
/>
2026-03-24 16:57:40 +08:00
<span class="dropdown-txt">{{ $t('agent.quote') }}</span>
2026-03-11 10:53:25 +08:00
</el-dropdown-item>
<el-dropdown-item
class="sketch-item flex align-center"
@click="handleClickDelete(item)"
>
<img
src="@/assets/images/delete.png"
class="dropdown-icon delete"
/>
2026-03-24 16:57:40 +08:00
<span class="dropdown-txt del">{{ $t('agent.delete') }}</span>
2026-03-11 10:53:25 +08:00
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div
class="edit-btn flex align-center space-between"
@click="handleClickEdit(item)"
>
2026-03-24 16:57:40 +08:00
<div>{{ $t('agent.edit') }}</div>
2026-02-25 15:10:03 +08:00
<img src="@/assets/images/arrow-top-right.png" />
2026-02-11 17:27:51 +08:00
</div>
<!-- 已加载完成的 sketch 显示实际图片 -->
2026-04-01 16:59:22 +08:00
<img v-img-loading="getImageSrc(item, index)" @load="handleImageLoad(index)" />
<!-- 正在加载的 sketch 显示 loading gif overlay -->
2026-03-31 14:00:27 +08:00
<!-- <div v-if="pendingSketchIndexes.includes(index)" class="loading-wrapper">
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
2026-03-31 14:00:27 +08:00
</div> -->
2026-03-11 14:32:13 +08:00
</div>
2026-02-11 17:27:51 +08:00
</template>
2026-03-16 14:11:01 +08:00
<template v-else-if="type === 'url'">
<div class="url-list flex">
2026-03-17 15:39:44 +08:00
<div class="url-item flex flex-col" v-for="item in urlList" :key="item">
<div class="url-title" v-ellipsis="3" @click="handleClickUrl(item)">
2026-03-19 16:49:52 +08:00
{{ item.title }}
2026-03-16 14:11:01 +08:00
<img src="@/assets/images/link-outer.png" class="link-outer" />
</div>
2026-03-19 16:49:52 +08:00
<div class="url-link" v-ellipsis="3">{{ item.url }}</div>
2026-03-16 14:11:01 +08:00
</div>
</div>
</template>
2026-02-23 13:28:43 +08:00
<div v-else class="reportBorder">
<div class="report">
2026-03-16 11:43:19 +08:00
<!-- <div v-if="false" class="report-content-null">
2026-02-23 13:28:43 +08:00
<img :src="reportNull" alt="" />
2026-03-16 11:43:19 +08:00
</div> -->
<template v-if="reportType === 'report'">
<div class="report-content">
<div class="downBtnBox">
<div class="downBtn" @click="handleDownloadMd">
<div class="icon">
<SvgIcon name="reportDown" size="16"></SvgIcon>
</div>
<span>{{ $t('agent.Download') }}</span>
2026-02-23 13:28:43 +08:00
</div>
2026-03-16 11:43:19 +08:00
</div>
<div class="content">
<VueMarkdown
:custom-attrs="customAttrs"
:markdown="markdownContent"
:rehype-plugins="[rehypeRaw]"
>
</VueMarkdown>
2026-02-23 13:28:43 +08:00
</div>
</div>
2026-03-16 11:43:19 +08:00
</template>
2026-02-23 13:28:43 +08:00
</div>
</div>
2026-02-11 17:27:51 +08:00
</div>
</template>
<script setup lang="ts">
2026-04-14 09:34:33 +08:00
import { ref, reactive, onMounted, onUnmounted, watch, computed, nextTick } from 'vue'
2026-03-11 10:53:25 +08:00
import { deleteSketchFlowCanvas } from '@/api/flow-canvas'
import { useProjectStore } from '@/stores'
import { useI18n } from 'vue-i18n'
2026-03-13 17:23:56 +08:00
import { VueMarkdown } from '@crazydos/vue-markdown'
import type { CustomAttrs } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw'
2026-03-19 16:49:52 +08:00
import Menu from './Menu.vue'
import LoadingImg from '@/assets/images/sketch-loading.gif'
import reportNull from '@/assets/images/reportNull.png'
import myEvent from '@/utils/myEvent'
import { fetchUrlTitle } from '@/api/agent'
const projectStore = useProjectStore()
import MyEvent from '@/utils/myEvent'
2026-03-13 17:23:56 +08:00
const { t } = useI18n()
2026-03-11 10:53:25 +08:00
const emits = defineEmits(['deleteSketch'])
2026-02-25 15:10:03 +08:00
// 存储每个图片的加载状态
const loadedStatus = ref<boolean[]>([])
2026-04-14 09:34:33 +08:00
const containerRef = ref<HTMLElement>()
2026-02-11 17:27:51 +08:00
const props = withDefaults(
defineProps<{
2026-03-16 14:11:01 +08:00
type: 'sketch' | 'report' | 'url'
2026-03-30 11:03:52 +08:00
sketchList: Array<Record<string, string>>
2026-02-11 17:27:51 +08:00
}>(),
{
2026-02-25 15:10:03 +08:00
type: 'sketch',
sketchList: []
2026-02-11 17:27:51 +08:00
}
)
2026-03-30 11:03:52 +08:00
watch(
() => props.sketchList,
() => {
// 当 sketchList 变化时,重置加载状态
loadedStatus.value = new Array(combineSketchList.value.length).fill(false)
2026-04-14 09:34:33 +08:00
// 当 type 为 sketch 时,自动滚动到最底部
if (props.type === 'sketch') {
nextTick(() => {
containerRef.value?.scrollTo({ top: containerRef.value.scrollHeight, behavior: 'smooth' })
})
}
2026-03-30 11:03:52 +08:00
}
)
const combineSketchList = computed(() => {
const res: Array<{ id: string; url: string }> = []
props.sketchList.forEach((group) => {
Object.entries(group).forEach(([id, url]) => {
res.push({ id, url })
})
})
return res
})
2026-03-13 17:23:56 +08:00
const customAttrs: CustomAttrs = {
heading: {
style: {
fontFamily: 'Regular',
lineHeight: 2
// fontSize: '1.4rem'
}
},
p: {
style: {
fontSize: '1.4rem',
lineHeight: 1.5
}
},
2026-03-13 17:23:56 +08:00
img: {
style: 'max-width: 100%;display:block;'
},
a: (node, combinedAttrs) => {
if (typeof node.properties.href === 'string') {
return { target: '_blank', rel: 'noopener noreferrer' }
} else {
return {}
}
}
}
const sessionId = ref('')
const markdownContent = ref('')
2026-03-16 11:43:19 +08:00
const urlList = ref([])
const reportType = ref<'report' | 'urls'>('report')
2026-03-27 16:49:29 +08:00
const reportTitle = ref('')
2026-03-16 11:43:19 +08:00
const setSessionId = (id: string) => {
reportType.value = 'report'
2026-03-13 17:23:56 +08:00
sessionId.value = id
}
2026-03-27 16:49:29 +08:00
2026-04-01 16:59:22 +08:00
const setReport = (title: string, content: string) => {
2026-03-27 16:49:29 +08:00
reportTitle.value = title
2026-04-01 16:59:22 +08:00
markdownContent.value = content
2026-03-27 16:49:29 +08:00
}
2026-03-19 16:49:52 +08:00
const setUrls = async (list: string[]) => {
2026-03-16 11:43:19 +08:00
reportType.value = 'urls'
2026-03-19 16:49:52 +08:00
const res = await fetchUrlTitle(list)
urlList.value = res
2026-03-16 11:43:19 +08:00
}
2026-04-01 16:59:22 +08:00
// watch(
// () => sessionId.value,
// (newVal) => {
// if (newVal) {
// markdownContent.value = sessionStorage.getItem(`reportsContent_${newVal}`)
// }
// }
// )
2026-03-13 17:23:56 +08:00
2026-02-25 15:10:03 +08:00
// 图片加载完成时触发
const handleImageLoad = (index: number) => {
loadedStatus.value[index] = true
}
// 图片加载失败时触发
const handleImageError = (index: number) => {
console.error(`Failed to load sketch at index ${index}`)
2026-02-25 15:10:03 +08:00
}
2026-02-11 17:27:51 +08:00
2026-02-25 15:10:03 +08:00
// 获取当前显示的图片源
2026-03-30 11:03:52 +08:00
const getImageSrc = (item: { id: string; url: string }, index: number) => {
return item.url
2026-02-25 15:10:03 +08:00
}
2026-02-11 17:27:51 +08:00
2026-03-30 11:03:52 +08:00
const handleClickEdit = (item: { id: string; url: string }) => {
const url = item.url
const imgId = item.id
const nodeId = projectStore.state.nodeId
myEvent.emit('openFlowCanvas', { url, imgId, nodeId })
2026-02-25 15:10:03 +08:00
}
2026-03-11 10:53:25 +08:00
2026-03-30 11:03:52 +08:00
const handleClickQuote = (item: { id: string; url: string }) => {
const url = item.url
2026-03-24 16:57:40 +08:00
MyEvent.emit('quote', url)
}
2026-03-31 11:39:42 +08:00
const handleClickDelete = (item: { id: string; url: string }) => {
ElMessageBox.confirm(t('agent.deleteSketchTip'), t('agent.confirm'), {
confirmButtonText: t('agent.confirm'),
cancelButtonText: t('agent.cancel'),
type: 'warning'
}).then(() => {
deleteSketchFlowCanvas({
id: item.id,
versionNodeId: projectStore.state.nodeId
}).then((res) => {
if (res) {
ElMessage.success(t('agent.deleteSuccess'))
emits('deleteSketch', item.id)
}
})
2026-03-11 10:53:25 +08:00
})
2026-02-25 15:10:03 +08:00
}
2026-03-11 14:32:13 +08:00
2026-03-13 17:23:56 +08:00
const handleDownloadMd = () => {
if (!markdownContent.value) return
const blob = new Blob([markdownContent.value], { type: 'text/markdown' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
2026-03-27 16:49:29 +08:00
link.download = reportTitle.value ?? `report-${Date.now()}.md`
2026-03-13 17:23:56 +08:00
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
const handleClickUrl = (item: { url: string; title: string }) => {
2026-03-19 16:49:52 +08:00
window.open(item.url, '_blank')
2026-03-16 14:11:01 +08:00
}
const vImgLoading = {
mounted(el, binding) {
// 1. 设置初始状态:显示 Loading 图(或者你可以从 binding.arg 传进来)
const loadingUrl = LoadingImg
const finalSrc = binding.value // 真正要加载的图
el.src = loadingUrl
// 2. 预加载真实图片
const img = new Image()
img.src = finalSrc
img.onload = () => {
// 3. 加载完成后替换
el.src = finalSrc
el.style.opacity = 1
}
2026-03-30 11:03:52 +08:00
},
// 关键:如果图片地址是动态变化的,需要监听更新
2026-03-30 11:03:52 +08:00
updated(el, binding) {
if (binding.value !== binding.oldValue) {
console.log('value', binding.value, 'oldValue', binding.oldValue)
// 重复上面的加载逻辑...
const loadingUrl = LoadingImg
const finalSrc = binding.value // 真正要加载的图
el.src = loadingUrl
// 2. 预加载真实图片
const img = new Image()
img.src = finalSrc
img.onload = () => {
// 3. 加载完成后替换
el.src = finalSrc
el.style.opacity = 1
}
}
}
}
2026-03-13 17:23:56 +08:00
defineExpose({
2026-03-16 11:43:19 +08:00
setSessionId,
2026-03-27 16:49:29 +08:00
setUrls,
2026-04-01 16:59:22 +08:00
setReport
2026-03-13 17:23:56 +08:00
})
2026-02-11 17:27:51 +08:00
</script>
<style lang="less" scoped>
.preview-container {
2026-02-23 13:28:43 +08:00
width: 100%;
height: 100%;
2026-02-11 17:27:51 +08:00
gap: 1.2rem;
flex-wrap: wrap;
2026-03-03 17:29:11 +08:00
overflow-y: auto;
align-content: flex-start;
2026-02-11 17:27:51 +08:00
.sketch-item {
position: relative;
2026-03-03 17:29:11 +08:00
width: calc((100% - 1.2rem * 3) / 4);
//设置比例
aspect-ratio: 1 / 1;
border-radius: 1.6rem;
2026-02-11 17:27:51 +08:00
img {
2026-03-03 17:29:11 +08:00
width: 100%;
height: 100%;
2026-02-11 17:27:51 +08:00
border-radius: 1.6rem;
}
.loading-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 1.6rem;
img {
width: 100%;
height: 100%;
border-radius: 1.6rem;
}
}
2026-03-11 10:53:25 +08:00
:deep(.menu-btn) {
2026-02-11 17:27:51 +08:00
position: absolute;
top: 2.1rem;
right: 1.5rem;
2026-03-11 10:53:25 +08:00
.dropdown-icon {
&.restore {
width: 1.4rem;
height: 1.3rem;
}
&.delete {
width: 1.19rem;
2026-03-11 10:53:25 +08:00
height: 1.3rem;
}
}
2026-02-11 17:27:51 +08:00
}
.edit-btn {
position: absolute;
right: 1rem;
bottom: 1.1rem;
width: 7.8rem;
height: 3.59rem;
border-radius: 2rem;
background-color: #fff;
border: 0.2rem solid #e5e5e5;
font-size: 1.4rem;
2026-02-23 13:28:43 +08:00
padding: 0 0.9rem 0 1.4rem;
cursor: pointer;
2026-02-25 15:10:03 +08:00
img {
2026-02-23 13:28:43 +08:00
width: 1.8rem;
height: 1.8rem;
}
}
}
2026-03-16 14:11:01 +08:00
.url-list {
flex: 1;
flex-wrap: wrap;
column-gap: 4rem;
row-gap: 3rem;
.url-item {
width: 24rem;
height: 28.7rem;
2026-03-17 15:39:44 +08:00
line-height: 2rem;
2026-03-16 14:11:01 +08:00
word-break: break-all;
background: url('@/assets/images/web-card.png') no-repeat;
background-size: 100% 100%;
padding: 5rem 1.5rem;
2026-03-17 15:39:44 +08:00
row-gap: 0.6rem;
// .url-title,.url-link{
// // 两行省略
// display: -webkit-box;
// -webkit-line-clamp: 2;
// line-clamp: 2;
// -webkit-box-orient: vertical;
// overflow: hidden;
// text-overflow: ellipsis;
// }
2026-03-16 14:11:01 +08:00
.url-title {
cursor: pointer;
font-family: 'Medium';
font-size: 1.6rem;
color: #232323;
2026-03-17 15:39:44 +08:00
max-height: 4rem;
2026-03-16 14:11:01 +08:00
.link-outer {
width: 1.2rem;
height: 1.2rem;
}
}
.url-link {
font-family: 'Medium';
font-style: italic;
font-size: 1.2rem;
color: #7c7c7c;
user-select: text;
}
}
}
2026-02-25 15:10:03 +08:00
.reportBorder {
2026-02-23 13:28:43 +08:00
position: relative;
display: flex;
align-items: center;
justify-content: center;
--border-width: 3px;
flex: 1;
overflow: hidden;
height: 100%;
2026-03-16 11:43:19 +08:00
2026-02-23 13:28:43 +08:00
&::before {
content: '';
position: absolute;
2026-02-25 15:10:03 +08:00
background: linear-gradient(
119.03deg,
rgba(233, 121, 60, 0.3) 1.61%,
rgba(255, 207, 144, 0.3) 101.01%
);
2026-02-23 13:28:43 +08:00
border-radius: 2.3rem;
z-index: -1;
width: 100%;
height: 100%;
left: -50%;
top: -50%;
transform: translate(50%, 50%);
}
.report {
background-color: #fff;
width: calc(100% - var(--border-width) * 2);
height: calc(100% - var(--border-width) * 2);
border-radius: 2rem;
display: flex;
2026-02-25 15:10:03 +08:00
.report-content-null {
2026-02-23 13:28:43 +08:00
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
2026-02-25 15:10:03 +08:00
.report-content {
2026-02-23 13:28:43 +08:00
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
2026-02-25 15:10:03 +08:00
.downBtnBox {
2026-02-23 13:28:43 +08:00
padding: 2.2rem 5.2rem 0;
2026-02-25 15:10:03 +08:00
.downBtn {
2026-02-23 13:28:43 +08:00
display: flex;
width: 11.2rem;
justify-content: center;
margin-left: auto;
line-height: 3.2rem;
border-radius: 5px;
background-color: #ff7a51;
color: #fff;
cursor: pointer;
2026-02-25 15:10:03 +08:00
.icon {
margin-right: 0.02rem;
2026-02-23 13:28:43 +08:00
}
2026-02-25 15:10:03 +08:00
span {
2026-02-23 13:28:43 +08:00
font-weight: 500;
font-size: 1.2rem;
}
}
}
2026-02-25 15:10:03 +08:00
.content {
2026-02-23 13:28:43 +08:00
word-break: break-word;
white-space: pre-wrap;
overflow-y: auto;
margin: 2rem;
2026-03-16 11:43:19 +08:00
padding: 0 8.8rem 8.8rem;
user-select: text;
2026-02-23 13:28:43 +08:00
}
}
2026-02-11 17:27:51 +08:00
}
}
}
2026-03-11 10:53:25 +08:00
:deep(.el-dropdown-menu__item) {
column-gap: 1.2rem;
padding: 1.2rem 1.4rem;
.dropdown-txt {
font-size: 1.3rem;
font-family: 'Medium';
color: #000;
&.del {
color: #ff4747;
}
}
2026-03-11 10:53:25 +08:00
}
2026-02-11 17:27:51 +08:00
</style>