Files
geekai/web/src/views/MarkMap.vue
2025-08-01 17:32:06 +08:00

304 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="page-mark-map">
<div class="inner custom-scroll">
<div class="mark-map-box" :style="{ height: leftBoxHeight + 'px' }">
<h2>思维导图创作中心</h2>
<div class="mark-map-params">
<el-form label-width="80px" label-position="left">
<div class="param-line">你的需求</div>
<div class="param-line">
<el-input
v-model="prompt"
:autosize="{ minRows: 4, maxRows: 6 }"
type="textarea"
placeholder="请给AI输入提示词让AI帮你完善"
/>
</div>
<div class="param-line">请选择生成思维导图的AI模型</div>
<div class="param-line">
<el-select v-model="modelID" placeholder="请选择模型" style="width: 100%">
<el-option
v-for="item in models"
:key="item.id"
:label="item.name"
:value="item.id"
>
<span>{{ item.name }}</span>
<el-tag
style="margin-left: 5px; position: relative; top: -2px"
type="info"
size="small"
>{{ item.power }}算力
</el-tag>
</el-option>
</el-select>
</div>
<div class="text-info">
<el-text type="primary"
>当前可用算力<el-text type="warning">{{ loginUser.power }}</el-text></el-text
>
</div>
<div class="submit-btn flex-center">
<el-button
style="width: 200px"
type="primary"
:dark="false"
round
@click="generateAI"
:loading="loading"
>
生成思维导图
</el-button>
</div>
<div class="param-line">使用已有内容生成</div>
<div class="param-line">
<el-input
v-model="content"
:autosize="{ minRows: 4, maxRows: 6 }"
type="textarea"
placeholder="请用markdown语法输入您想要生成思维导图的内容"
/>
</div>
<div class="param-line flex-center">
<el-button
color="rgb(78, 51, 254)"
style="width: 200px"
:dark="false"
round
@click="generate"
>直接生成免费</el-button
>
</div>
</el-form>
</div>
</div>
<div class="chat-box">
<!-- <div class="top-bar">
<el-button @click="downloadImage" type="primary">
<el-icon>
<Download />
</el-icon>
<span>下载图片-</span>
</el-button>
</div> -->
<div class="body" id="markmap">
<svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }" />
<div id="toolbar">
<el-button @click="downloadImage" type="primary">
<el-icon>
<Download />
</el-icon>
<span>下载图片</span>
</el-button>
</div>
</div>
</div>
<!-- end task list box -->
</div>
</div>
</div>
</template>
<script setup>
import { checkSession, getSystemInfo } from '@/store/cache'
import { useSharedStore } from '@/store/sharedata'
import { httpGet, httpPost } from '@/utils/http'
import { Download } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { Transformer } from 'markmap-lib'
import { Toolbar } from 'markmap-toolbar'
import { Markmap } from 'markmap-view'
import { nextTick, onMounted, ref } from 'vue'
const leftBoxHeight = ref(window.innerHeight - 105)
//const rightBoxHeight = ref(window.innerHeight - 115);
const rightBoxHeight = ref(window.innerHeight)
const prompt = ref('')
const text = ref('')
const content = ref(text.value)
const html = ref('')
const isLogin = ref(false)
const loginUser = ref({ power: 0 })
const transformer = new Transformer()
const store = useSharedStore()
const loading = ref(false)
const svgRef = ref(null)
const markMap = ref(null)
const models = ref([])
const modelID = ref(0)
const cacheKey = ref('MarkMapCache')
onMounted(async () => {
const cache = localStorage.getItem(cacheKey.value)
if (cache) {
text.value = cache
} else {
const res = await getSystemInfo().catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
text.value = res.data['mark_map_text']
content.value = text.value
}
initData()
nextTick(() => {
try {
markMap.value = Markmap.create(svgRef.value)
const { el } = Toolbar.create(markMap.value)
document.getElementById('toolbar').append(el)
update()
} catch (e) {
console.error(e)
}
})
})
const initData = () => {
httpGet('/api/model/list')
.then((res) => {
for (let v of res.data) {
models.value.push(v)
}
modelID.value = models.value[0].id
})
.catch((e) => {
ElMessage.error('获取模型失败:' + e.message)
})
checkSession()
.then((user) => {
loginUser.value = user
isLogin.value = true
})
.catch(() => {})
}
const update = () => {
try {
const { root } = transformer.transform(processContent(text.value))
markMap.value.setData(root)
markMap.value.fit()
} catch (e) {
console.error(e)
}
}
const processContent = (text) => {
if (!text) {
return text
}
const arr = []
const lines = text.split('\n')
for (let line of lines) {
if (line.indexOf('```') !== -1) {
continue
}
line = line.replace(/([*_~`>])|(\d+\.)\s/g, '')
arr.push(line)
}
return arr.join('\n')
}
window.onresize = () => {
leftBoxHeight.value = window.innerHeight - 145
rightBoxHeight.value = window.innerHeight - 85
}
const generate = () => {
text.value = content.value
update()
}
// 使用 AI 智能生成
const generateAI = () => {
html.value = ''
text.value = ''
if (prompt.value === '') {
return ElMessage.error('请输入你的需求')
}
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
loading.value = true
httpPost('/api/markMap/gen', {
prompt: prompt.value,
model_id: modelID.value,
})
.then((res) => {
text.value = res.data
content.value = processContent(text.value)
const model = getModelById(modelID.value)
loginUser.value.power -= model.power
nextTick(() => update())
loading.value = false
// 缓存结果
localStorage.setItem(cacheKey.value, text.value)
})
.catch((e) => {
ElMessage.error('生成思维导图失败:' + e.message)
loading.value = false
})
}
const getModelById = (modelId) => {
for (let m of models.value) {
if (m.id === modelId) {
return m
}
}
}
// download SVG to png file
const downloadImage = async () => {
// 先自适应思维导图到可视化区域
await markMap.value.fit()
const svgElement = document.getElementById('markmap')
const serializer = new XMLSerializer()
const source =
'<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgRef.value)
const image = new Image()
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
// 分辨率倍数,越高图片越清晰,但文件越大
const scale = 4
const canvas = document.createElement('canvas')
canvas.width = svgElement.offsetWidth * scale
canvas.height = svgElement.offsetHeight * scale
let context = canvas.getContext('2d')
context.clearRect(0, 0, canvas.width, canvas.height)
context.fillStyle = 'white'
context.fillRect(0, 0, canvas.width, canvas.height)
image.onload = function () {
// 关键:缩放 context
context.setTransform(scale, 0, 0, scale, 0, 0)
context.drawImage(image, 0, 0)
const a = document.createElement('a')
a.download = 'geek-ai-xmind.png'
a.href = canvas.toDataURL('image/png')
a.click()
}
}
</script>
<style lang="scss">
@use '../assets/css/mark-map.scss' as *;
@use '../assets/css/custom-scroll.scss' as *;
</style>