1、fishTool.vue
<template>
<div>
<el-button
type="primary"
@click="handleExport"
>
导出
</el-button>
<div
id="target"
class="fish-bone-div"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import html2canvas from 'html2canvas'
import { ElMessage } from 'element-plus'
const props = defineProps({
dataList: {
type: Array,
default: () => []
}
})
const dataList = ref<any[]>([])
const fishBoneQuery = ref('')
const fishbone = ref<any>()
const feedback = ref<any[]>([])
const srcDatas = ref<any[]>([])
window.onload = () => {
initEchart()
}
const buildTable = (nodes:any[], parentId = '-1', path = '.') => {
const tableItems = []
for (const node of nodes) {
if (node.parentId === parentId) {
const currentPath = `${path}/${node.causalDetailId}`
const tableItem = { path: currentPath, name: node.name, k: node.causalDetailId }
tableItems.push(tableItem)
if (node.child) {
const childTableItems:any[] = buildTable(node.child, node.causalDetailId, currentPath)
tableItems.push(...childTableItems)
}
}
}
tableItems.forEach((item:any) => {
const index = item.path.lastIndexOf('/')
const result = item.path.substring(index + 1)
if (item.k === result) {
item.path = item.path.slice(0, index)
}
})
return tableItems
}
const initEchart = () => {
if (dataList.value && dataList.value.length) {
srcDatas.value = buildTable(dataList.value)
fishBoneQuery.value = ''
drawInContext()
}
}
defineExpose({ initEchart })
const drawInContext = function () {
const srcData = JSON.parse(JSON.stringify(srcDatas.value))
drawFishbone(srcData)
}
const drawFishbone = function (data:any) {
fishbone.value = d3.fishbone(
size(),
function () {
return data
},
function () {
return feedback.value
},
onDblClickBranch,
function () { }
)
const target = d3.select('#target')
target.select('svg').remove()
target
.append('svg')
.attr(size())
.call(fishbone.value.defaultArrow)
.call(fishbone.value.defaultFishStart)
.call(fishbone.value.defaultFishEnd)
.call(fishbone.value)
forceLayout()
}
const onDblClickBranch = function (row:any) {
fishBoneQuery.value = row.k
drawInContext()
}
const forceLayout = function () {
const s = size()
const force = fishbone.value.force()
force.size([s.width, s.height])
force.gravity(0)
force.charge(0)
force.linkDistance(150)
force.linkStrength(1.2)
force.alpha(0)
force.chargeDistance(120)
force.friction(0.9)
force.start()
times(250).forEach(force.tick.bind(force))
force.stop()
force.start()
}
const size = function () {
return {
width: $('#target').width(),
height: $('#target').height()
}
}
const times = function (num:number) {
const r = []
for (let i = 0; i < num; i++) r.push(i)
return r
}
const handleExport = () => {
if (srcDatas.value && srcDatas.value.length) {
const element:any = document.getElementById('target')
html2canvas(element).then((canvas:any) => {
const imgData = canvas.toDataURL('image/png')
const link = document.createElement('a')
link.href = imgData
link.download = '因果分析.png'
link.click()
ElMessage.success('导出成功')
})
} else {
ElMessage.warning('请先预览')
}
}
watchEffect(() => {
dataList.value = props.dataList
})
</script>
<style lang="scss">
.echart-div {
width: 100%;
text-align: center;
}
.fish-bone-div {
height: 600px;
width: 100%;
padding: 20px;
}
.note {
color: gray;
font-size: 0.8em;
}
.label-0 {
font-size: 1.5em;
}
.label-1 {
font-size: 1.2em;
fill: #111;
}
.label-2 {
font-size: 1em;
fill: #444;
}
.label-3 {
font-size: 0.8em;
fill: #888;
}
.label-4 {
font-size: 0.7em;
fill: #aaa;
}
.label-5 {
font-size: 0.6em;
fill: #aaa;
}
.label-6 {
font-size: 0.8em;
fill: #aaa;
}
.link-0 {
stroke: #337ecc;
stroke-width: 6px;
}
.link-1 {
stroke: #333;
stroke-width: 1px;
}
.link-2,
.link-3,
.link-4,
.link-5,
.link-6 {
stroke: #666;
stroke-width: 0.5px;
}
.link-positive {
stroke: #409eff;
}
.link-negative {
stroke: #337ecc;
}
.positive {
fill: green;
stroke: green;
}
.negative {
fill: red;
stroke: red;
}
</style>
2、在index.html中引入,三个文件放在public文件夹下再引入
<script src="/jq1.11.3.min.js"></script>
<script src="/d3.min.js" charset="utf-8"></script>
<script src="/d3.fishbone.js" charset="utf-8"></script>
3、npm i html2canvas实现本地导出图片
4、在global.d.ts中解决ts变量未声明问题
// eslint-disable-next-line no-unused-vars
declare let d3:any
// eslint-disable-next-line no-unused-vars
declare let $:any
5、defineExpose暴露方法在父组件onMounted或者watchEffect中调用,因为fishToo.vue是一个子组件
6、效果图
