495 lines
12 KiB
Vue
495 lines
12 KiB
Vue
<template>
|
|
<div
|
|
class="preview-container flex"
|
|
:class="type === 'sketch' ? 'sketch-preview' : 'report-preview'"
|
|
>
|
|
<template v-if="type === 'sketch'">
|
|
<div
|
|
class="sketch-item"
|
|
v-for="(item, index) in sketchList"
|
|
:key="'sketch-item-' + index"
|
|
>
|
|
<el-dropdown trigger="click" class="menu-btn">
|
|
<Menu />
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item class="sketch-item flex align-center">
|
|
<img
|
|
src="@/assets/images/restore-sketch.png"
|
|
class="dropdown-icon restore"
|
|
/>
|
|
<span class="dropdown-txt">Quote</span>
|
|
</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"
|
|
/>
|
|
<span class="dropdown-txt del">Delete</span>
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
<div
|
|
class="edit-btn flex align-center space-between"
|
|
@click="handleClickEdit(item)"
|
|
>
|
|
<div>Edit</div>
|
|
<img src="@/assets/images/arrow-top-right.png" />
|
|
</div>
|
|
<!-- 已加载完成的 sketch 显示实际图片 -->
|
|
<img
|
|
v-show="!pendingSketchIndexes.includes(index)"
|
|
v-img-loading="getImageSrc(item, index)"
|
|
@load="handleImageLoad(index)"
|
|
/>
|
|
<!-- 正在加载的 sketch 显示 loading gif overlay -->
|
|
<div v-if="pendingSketchIndexes.includes(index)" class="loading-wrapper">
|
|
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else-if="type === 'url'">
|
|
<div class="url-list flex">
|
|
<div class="url-item flex flex-col" v-for="item in urlList" :key="item">
|
|
<div class="url-title" v-ellipsis="3" @click="handleClickUrl(item)">
|
|
{{ item.title }}
|
|
<img src="@/assets/images/link-outer.png" class="link-outer" />
|
|
</div>
|
|
<div class="url-link" v-ellipsis="3">{{ item.url }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<div v-else class="reportBorder">
|
|
<div class="report">
|
|
<!-- <div v-if="false" class="report-content-null">
|
|
<img :src="reportNull" alt="" />
|
|
</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>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
<VueMarkdown
|
|
:custom-attrs="customAttrs"
|
|
:markdown="markdownContent"
|
|
:rehype-plugins="[rehypeRaw]"
|
|
>
|
|
</VueMarkdown>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
|
|
import { deleteSketchFlowCanvas } from '@/api/flow-canvas'
|
|
import { useProjectStore } from '@/stores'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { VueMarkdown } from '@crazydos/vue-markdown'
|
|
import type { CustomAttrs } from '@crazydos/vue-markdown'
|
|
import rehypeRaw from 'rehype-raw'
|
|
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'
|
|
|
|
const { t } = useI18n()
|
|
const emits = defineEmits(['deleteSketch'])
|
|
|
|
// 存储每个图片的加载状态
|
|
const loadedStatus = ref<boolean[]>([])
|
|
|
|
// 存储正在加载的 sketch 索引
|
|
const pendingSketchIndexes = ref<number[]>([])
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
type: 'sketch' | 'report' | 'url'
|
|
sketchList: Array<string>
|
|
}>(),
|
|
{
|
|
type: 'sketch',
|
|
sketchList: []
|
|
}
|
|
)
|
|
|
|
const customAttrs: CustomAttrs = {
|
|
heading: {
|
|
style: {
|
|
fontFamily: 'Regular',
|
|
lineHeight: 2
|
|
// fontSize: '1.4rem'
|
|
}
|
|
},
|
|
p: {
|
|
style: {
|
|
fontSize: '1.4rem',
|
|
lineHeight: 1.5
|
|
}
|
|
},
|
|
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('')
|
|
const urlList = ref([])
|
|
const reportType = ref<'report' | 'urls'>('report')
|
|
const setSessionId = (id: string) => {
|
|
reportType.value = 'report'
|
|
sessionId.value = id
|
|
}
|
|
const setUrls = async (list: string[]) => {
|
|
reportType.value = 'urls'
|
|
const res = await fetchUrlTitle(list)
|
|
urlList.value = res
|
|
}
|
|
watch(
|
|
() => sessionId.value,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
markdownContent.value = sessionStorage.getItem(`reportsContent_${newVal}`)
|
|
}
|
|
}
|
|
)
|
|
|
|
// 图片加载完成时触发
|
|
const handleImageLoad = (index: number) => {
|
|
loadedStatus.value[index] = true
|
|
pendingSketchIndexes.value = pendingSketchIndexes.value.filter((i) => i !== index)
|
|
}
|
|
|
|
// 图片加载失败时触发
|
|
const handleImageError = (index: number) => {
|
|
pendingSketchIndexes.value = pendingSketchIndexes.value.filter((i) => i !== index)
|
|
console.error(`Failed to load sketch at index ${index}`)
|
|
}
|
|
|
|
// 获取当前显示的图片源
|
|
const getImageSrc = (item: string, index: number) => {
|
|
if (item === null) {
|
|
return null
|
|
}
|
|
if (typeof item === 'string') {
|
|
return item
|
|
}
|
|
if (typeof item === 'object') {
|
|
return Object.values(item)[0]
|
|
}
|
|
}
|
|
|
|
const handleClickEdit = (item: string | Object) => {
|
|
let url = ''
|
|
let imgId = ''
|
|
let nodeId = ''
|
|
if (typeof item === 'string') {
|
|
url = item
|
|
}
|
|
if (typeof item === 'object') {
|
|
url = Object.values(item)[0]
|
|
imgId = Object.keys(item)[0]
|
|
nodeId = projectStore.state.nodeId
|
|
}
|
|
myEvent.emit('openFlowCanvas', { url, imgId, nodeId })
|
|
}
|
|
|
|
const handleClickDelete = (item: string | Object) => {
|
|
deleteSketchFlowCanvas({
|
|
id: Object.keys(item)[0],
|
|
versionNodeId: projectStore.state.nodeId
|
|
}).then((res) => {
|
|
if (res) {
|
|
ElMessage.success(t('agent.deleteSuccess'))
|
|
emits('deleteSketch', Object.keys(item)[0])
|
|
}
|
|
})
|
|
}
|
|
|
|
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
|
|
link.download = `report-${Date.now()}.md`
|
|
document.body.appendChild(link)
|
|
link.click()
|
|
document.body.removeChild(link)
|
|
URL.revokeObjectURL(url)
|
|
}
|
|
|
|
const showLoading = ref(false)
|
|
const handleLoadingSketch = (sketchIndex?: number) => {
|
|
showLoading.value = true
|
|
// 记录正在加载的 sketch 索引
|
|
if (sketchIndex !== undefined) {
|
|
pendingSketchIndexes.value.push(sketchIndex)
|
|
}
|
|
}
|
|
|
|
const handleClickUrl = (item: { url: string; title: string }) => {
|
|
window.open(item.url, '_blank')
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
// 关键:如果图片地址是动态变化的,需要监听更新
|
|
// updated(el, binding) {
|
|
// if (binding.value !== binding.oldValue) {
|
|
// // 重复上面的加载逻辑...
|
|
// }
|
|
// }
|
|
}
|
|
onMounted(() => {
|
|
MyEvent.add('loading-sketch', handleLoadingSketch)
|
|
})
|
|
onUnmounted(() => {
|
|
MyEvent.remove('loading-sketch', handleLoadingSketch)
|
|
})
|
|
|
|
defineExpose({
|
|
setSessionId,
|
|
setUrls
|
|
})
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.preview-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
gap: 1.2rem;
|
|
flex-wrap: wrap;
|
|
overflow-y: auto;
|
|
align-content: flex-start;
|
|
.sketch-item {
|
|
position: relative;
|
|
width: calc((100% - 1.2rem * 3) / 4);
|
|
//设置比例
|
|
aspect-ratio: 1 / 1;
|
|
border-radius: 1.6rem;
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
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;
|
|
}
|
|
}
|
|
:deep(.menu-btn) {
|
|
position: absolute;
|
|
top: 2.1rem;
|
|
right: 1.5rem;
|
|
|
|
.dropdown-icon {
|
|
&.restore {
|
|
width: 1.4rem;
|
|
height: 1.3rem;
|
|
}
|
|
&.delete {
|
|
width: 1.19rem;
|
|
height: 1.3rem;
|
|
}
|
|
}
|
|
}
|
|
.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;
|
|
padding: 0 0.9rem 0 1.4rem;
|
|
cursor: pointer;
|
|
img {
|
|
width: 1.8rem;
|
|
height: 1.8rem;
|
|
}
|
|
}
|
|
}
|
|
.url-list {
|
|
flex: 1;
|
|
flex-wrap: wrap;
|
|
column-gap: 4rem;
|
|
row-gap: 3rem;
|
|
.url-item {
|
|
width: 24rem;
|
|
height: 28.7rem;
|
|
line-height: 2rem;
|
|
word-break: break-all;
|
|
background: url('@/assets/images/web-card.png') no-repeat;
|
|
background-size: 100% 100%;
|
|
padding: 5rem 1.5rem;
|
|
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;
|
|
// }
|
|
.url-title {
|
|
cursor: pointer;
|
|
font-family: 'Medium';
|
|
font-size: 1.6rem;
|
|
color: #232323;
|
|
max-height: 4rem;
|
|
.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;
|
|
}
|
|
}
|
|
}
|
|
.reportBorder {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
--border-width: 3px;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
height: 100%;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
background: linear-gradient(
|
|
119.03deg,
|
|
rgba(233, 121, 60, 0.3) 1.61%,
|
|
rgba(255, 207, 144, 0.3) 101.01%
|
|
);
|
|
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;
|
|
.report-content-null {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.report-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
.downBtnBox {
|
|
padding: 2.2rem 5.2rem 0;
|
|
.downBtn {
|
|
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;
|
|
.icon {
|
|
margin-right: 0.02rem;
|
|
}
|
|
span {
|
|
font-weight: 500;
|
|
font-size: 1.2rem;
|
|
}
|
|
}
|
|
}
|
|
.content {
|
|
word-break: break-word;
|
|
white-space: pre-wrap;
|
|
overflow-y: auto;
|
|
margin: 2rem;
|
|
padding: 0 8.8rem 8.8rem;
|
|
user-select: text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
: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;
|
|
}
|
|
}
|
|
}
|
|
</style>
|