entityClassView.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <template>
  2. <div class="flex-row"
  3. style="height: 100%">
  4. <div id="leftPanel"
  5. class="flex-col road"
  6. v-if="showLeftBar">
  7. <div class="flex1 flex-col panel"
  8. style="height:0">
  9. <b class="panel-header">
  10. <a-icon type="deployment-unit"/>
  11. 显示实体类
  12. </b>
  13. <a-spin :spinning="loadingTree" style="max-height: 500px;" class="auto-overflow flex1">
  14. <a-checkbox v-model="checkAllEntcls" @change="onCheckAllChange"> 全选 </a-checkbox>
  15. <a-tree checkable
  16. :defaultSelectedKeys="selectTreeNodeIdList"
  17. v-model="checkedKeys"
  18. @select="onSelect"
  19. @check="onCheckEntCls"
  20. :treeData="treeData"
  21. :selectedKeys.sync="selectTreeNodeIdList"
  22. :expandedKeys.sync="expandedKeys"
  23. :autoExpandParent="autoExpandParent"
  24. class="entity-class-tree ">
  25. </a-tree>
  26. </a-spin>
  27. </div>
  28. <div class="flex1 flex-col panel" style="height:0">
  29. <b class="panel-header">
  30. <a-icon type="deployment-unit"/>
  31. 选择显示关系类
  32. </b>
  33. <a-spin :spinning="loadingChecked" class="auto-overflow" style="padding: 0 10px; margin: 5px -10px;">
  34. <a-checkbox-group id="mycheck"
  35. :options="allRelClsNameList"
  36. v-model="checkedRelClsNameList"
  37. @change="onChangeRelClsCheckBox">
  38. </a-checkbox-group>
  39. </a-spin>
  40. </div>
  41. </div>
  42. <a-spin :spinning="loadingChecked" class="flex1 panel flex-col" style="height: 100%">
  43. <chart-comp ref="chart"
  44. @="getInfoTooltip"
  45. :isShowContextMenu="true"
  46. @reloadChart="reloadChart"
  47. :nodes="nodes"
  48. :links="links"
  49. :enable="true"
  50. :isShowChart="true"></chart-comp>
  51. </a-spin>
  52. <entity-cls-details-tooltip class="chart_tooltip"
  53. :id="tooltip.entClsID"
  54. :key="tooltip.entClsID"
  55. v-if="tooltip.entClsID"
  56. style="position: absolute;z-index: 1;border-radius: 3px"
  57. :style="{'left': tooltip.x, 'top': tooltip.y}"></entity-cls-details-tooltip>
  58. <link-details-tooltip class="chart_tooltip"
  59. :id="tooltip.relClsID"
  60. :key="tooltip.relClsID"
  61. :entCls="relClsName"
  62. v-if="tooltip.relClsID"
  63. style="position: absolute;z-index: 1;border-radius: 3px"
  64. :style="{'left': tooltip.x, 'top': tooltip.y}"></link-details-tooltip>
  65. </div>
  66. </template>
  67. <script>
  68. import chartComp from '@/views/knowledge/common/chart'
  69. import entityClsDetailsTooltip from '@/views/knowledge/common/entClsDetails.vue'
  70. import linkDetailsTooltip from '@/views/knowledge/common/linkDetails.vue'
  71. import { getAllEntityClass } from '@/api/graph/entityClass'
  72. import { getAllRelationClass } from '@/api/graph/relationClass'
  73. import { getGraphEntClsTree } from '@/api/graph/statisticalMap'
  74. // 获取路径参数
  75. function getQueryVariable(variable) {
  76. let url = window.location.hash
  77. let index = url.indexOf('?')
  78. var query = url.substring(index + 1, url.length)
  79. var vars = query.split('&')
  80. for (var i = 0; i < vars.length; i++) {
  81. var pair = vars[i].split('=')
  82. if (pair[0] == variable) {
  83. return pair[1]
  84. }
  85. }
  86. return ''
  87. }
  88. const gData = [] // 左侧实体类树
  89. const dataList = []
  90. // 根据树生成一个列表
  91. const generateList = (data) => {
  92. for (let i = 0; i < data.length; i++) {
  93. const node = data[i]
  94. const key = node.key
  95. dataList.push({key, title: node.entClsName, entClsName: node.entClsName})
  96. if (node.children) {
  97. generateList(node.children, node.key)
  98. }
  99. }
  100. }
  101. // 得到需要展开的父节点key
  102. const getParentKey = (key, tree) => {
  103. let parentKey
  104. for (let i = 0; i < tree.length; i++) {
  105. const node = tree[i]
  106. if (node.children) {
  107. if (node.children.some(item => item.key === key)) {
  108. parentKey = node.key
  109. } else {
  110. const tp = getParentKey(key, node.children)
  111. if (tp) {
  112. parentKey = tp
  113. }
  114. }
  115. }
  116. }
  117. return parentKey
  118. }
  119. // 遍历一个树节点 找寻所有的子id
  120. const getIdsList = (root) => {
  121. let ids = []
  122. if (root.entClsID) ids.push(root.entClsID)
  123. if (root.children && root.children.length) {
  124. for (let i = 0; i < root.children.length; i++) {
  125. ids = ids.concat(getIdsList(root.children[i]))
  126. }
  127. }
  128. return ids
  129. }
  130. export default {
  131. components: {chartComp, entityClsDetailsTooltip, linkDetailsTooltip},
  132. data() {
  133. return {
  134. loadingTree: false,
  135. loadingChecked: false,
  136. checkAllEntcls: true,
  137. nodes: null,
  138. links: null,
  139. tooltip: {entClsID: false, relClsID: false},
  140. relClsName: '',
  141. expandedKeys: [], // 要展开的节点
  142. searchValue: '', // 搜索的实体类名字
  143. autoExpandParent: true,
  144. gData,
  145. selectTreeNodeIdList: [],
  146. checkedKeys: [], // 默认全部选中
  147. relClsList: [], // 关系类列表
  148. allRelClsNameList: [],// 所有的关系类名列表
  149. checkedRelClsNameList: [], // 去重的关系类名列表
  150. allEntClassCache: [], // 初始页面缓存所有的实体类
  151. allLinksCache: [], // 初始页面缓存所有的路径信息
  152. filEntClass: undefined, // 选择树处理后的实体类
  153. filLinks: undefined, // 处理后的路径信息links
  154. showLeftBar: true //是否显示左侧可选框
  155. }
  156. },
  157. computed: {
  158. /**
  159. * 默认初始全部选中的关系类
  160. **/
  161. checkedRelCls() {
  162. var checkedIdList = []
  163. this.relClsList.forEach((v, i) => {
  164. if (v.checked === undefined || v.checked === true) {
  165. checkedIdList.push(v.value)
  166. }
  167. })
  168. return checkedIdList
  169. },
  170. /**
  171. * 实际绑定在树上的变量,根据rootId可能有不同
  172. **/
  173. treeData() {
  174. var returnData = []
  175. if (this.rootId) {
  176. // 搜索根节点
  177. this.$utils.tree.walk(this.gData, (node) => {
  178. if (node.entClsID === this.rootId) {
  179. returnData = [node]
  180. return false
  181. }
  182. })
  183. } else {
  184. returnData = this.gData
  185. }
  186. // 如果有搜索字符串
  187. var searchStr = this.searchValue
  188. if (searchStr) {
  189. // 向上遍历到所有要展开的父节点
  190. const expandedKeys = dataList.map((item) => {
  191. if (item.title.indexOf(searchStr) > -1) {
  192. return getParentKey(item.key, returnData) // 返回要展开的父节点keys
  193. }
  194. return null
  195. }).filter((item, i, self) => item && self.indexOf(item) === i)
  196. // 设定展开
  197. Object.assign(this, {
  198. expandedKeys,
  199. autoExpandParent: true
  200. })
  201. // 遍历整个树
  202. var showParentNodeList = []
  203. this.$utils.tree.walk(returnData, (node, index, way) => {
  204. if (node.entClsName.indexOf(searchStr) > -1) {
  205. node.style = ''
  206. showParentNodeList = showParentNodeList.concat(way)
  207. } else {
  208. node.style = 'display:none'
  209. }
  210. })
  211. for (var i = 0; i < showParentNodeList.length; i++) {
  212. showParentNodeList[i].style = ''
  213. }
  214. } else {
  215. // 没有搜索串,遍历清空display:none
  216. this.$utils.tree.walk(returnData, (node) => {
  217. node.style = ''
  218. })
  219. }
  220. return returnData
  221. }
  222. },
  223. created() {
  224. this.showLeftBar = getQueryVariable('showLeftBar') === 'false' ? false : true;
  225. let clsId = getQueryVariable('clsId')
  226. this.refreshEntityClassList(clsId)
  227. /**
  228. * 请求数据,获取所有实体类的信息,然后根据pid组装出实体类之间的关系
  229. * */
  230. this.loadingChecked = true
  231. Promise.all([getAllEntityClass(true), getAllRelationClass()]).then((resp) => {
  232. let links = []
  233. resp[0].data.forEach((v) => {
  234. v.id = v.entClsID
  235. v.label = v.entClsName
  236. if (v.pid !== null) {
  237. let fromId = v.pid, toId = v.entClsID
  238. const item = {
  239. id: fromId + '_' + toId
  240. }
  241. item.from = v.pid
  242. item.to = v.entClsID
  243. item.classType = 'dashed'
  244. links.push(item)
  245. }
  246. })
  247. var relClassNameTempMap = {}
  248. resp[1].data.forEach((v) => {
  249. const item = {
  250. label: v.relClsName,
  251. value: v.relClsID,
  252. checked: true
  253. }
  254. v.from = v.startID
  255. v.to = v.endID
  256. v.id = v.relClsID
  257. v.label = v.relClsName
  258. this.relClsList.push(item)
  259. relClassNameTempMap[v.relClsName] = true
  260. })
  261. //去掉重复关系类名
  262. for (let name in relClassNameTempMap) {
  263. this.allRelClsNameList.push({
  264. label: name,
  265. value: name
  266. })
  267. this.checkedRelClsNameList.push(name)
  268. }
  269. links = links.concat(resp[1].data)
  270. this.allEntClassCache = resp[0]
  271. this.filLinks = this.allLinksCache = links
  272. if (clsId) { // 路径中存在实体类id
  273. this.onCheckEntCls(this.checkedKeys)
  274. } else {
  275. this.drawChart(resp[0].data, links)
  276. }
  277. this.loadingChecked = false
  278. })
  279. },
  280. methods: {
  281. reloadChart(nodes, links) {
  282. this.drawChart(nodes, links)
  283. },
  284. /**
  285. * 画图
  286. * @params nodes: 处理好的实体数据, link: 处理好的关系数据
  287. * */
  288. drawChart(nodes, links) {
  289. this.nodes = nodes
  290. this.links = links
  291. this.$refs.chart.destroyChart() // 先清空chart实例,再绘图
  292. this.$refs.chart.reDraw(undefined, nodes, links)
  293. },
  294. /**
  295. * 获取实体或关系的悬浮框
  296. * @params event:zoomchart返回的event,args:zoomchart返回的变量,包含了当前点击的实体或关系的数据
  297. * */
  298. getInfoTooltip(event, args) {
  299. const vm = this
  300. this.$set(this.tooltip, 'x', event.pageX + 'px')
  301. this.$set(this.tooltip, 'y', event.pageY + 'px')
  302. if (args.clickNode) {
  303. vm.tooltip.entClsID = args.clickNode.data.entClsID
  304. vm.tooltip.relClsID = false
  305. } else if (args.clickLink) {
  306. vm.tooltip.entClsID = false
  307. vm.tooltip.relClsID = args.clickLink.data.relClsID
  308. vm.relClsName = args.clickLink.data.relClsName
  309. } else {
  310. vm.tooltip.entClsID = false
  311. vm.tooltip.relClsID = false
  312. }
  313. },
  314. // 全选
  315. onCheckAllChange() {
  316. const allEntCls = []
  317. this.allEntClassCache.map(v => {
  318. allEntCls.push(v.entClsID)
  319. })
  320. this.onCheckEntCls(this.checkAllEntcls ? allEntCls : [])
  321. this.checkedKeys = this.checkAllEntcls ? allEntCls : []
  322. },
  323. onSelect(keys, node) {
  324. let entClsID = node.node.dataRef.entClsID
  325. this.expandedKeys = keys // 选中展开的key
  326. },
  327. /**
  328. * 改变实体类
  329. */
  330. onCheckEntCls(checkedKeys) {
  331. // 过滤缓存中的实体类 得到新的实体类
  332. let entClass = this.allEntClassCache.filter((v, i) => {
  333. return checkedKeys.includes(v.entClsID)
  334. })
  335. // 过滤缓存中的实体类 保存到data中,用于过滤
  336. this.filEntClass = entClass
  337. // 过滤缓存中的路径信息 得到新的路径
  338. let links = this.allLinksCache.filter((v, i) => {
  339. if (v.classType === 'dashed') {
  340. return checkedKeys.includes(v.from) && checkedKeys.includes(v.to)
  341. } else if (v.relClsID) {
  342. return checkedKeys.includes(v.from) && checkedKeys.includes(v.to)
  343. }
  344. })
  345. const relClassNameTempMap = {},
  346. allRelClsNameList = [],
  347. checkedRelClsNameList = []
  348. // 重新计算关系类列表 去掉重名计算
  349. links.forEach((v) => {
  350. if (v.relClsName) {
  351. relClassNameTempMap[v.relClsName] = true;
  352. }
  353. })
  354. for (let name in relClassNameTempMap) {
  355. allRelClsNameList.push({
  356. label: name,
  357. value: name
  358. })
  359. checkedRelClsNameList.push(name)
  360. }
  361. this.allRelClsNameList = allRelClsNameList
  362. this.checkedRelClsNameList = checkedRelClsNameList
  363. // 改变关系类checkbox
  364. // 缓存到data中,方便计算路径信息
  365. this.filLinks = links
  366. // 过滤掉,节点不存在的边
  367. const chatLinks = links.filter((link) => {
  368. return checkedKeys.includes(link.from) && checkedKeys.includes(link.to)
  369. })
  370. // 重画chart
  371. this.drawChart(JSON.parse(JSON.stringify(entClass)), chatLinks)
  372. },
  373. /**
  374. * 进入页面刷新实体类树
  375. */
  376. refreshEntityClassList(clsId) {
  377. this.loadingTree = true
  378. getGraphEntClsTree().then((data) => {
  379. for (let i = 0; i < data.length; i++) {
  380. data[i]['key'] = data[i]['entClsID']
  381. data[i]['title'] = data[i]['entClsName']
  382. data[i]['scopedSlots'] = {title: 'custom'}
  383. if (!data[i]['pid']) {
  384. this.checkedKeys.push(data[i]['entClsID'])
  385. }
  386. }
  387. // 此处传固定的option
  388. const option = {
  389. idName: 'entClsID',
  390. pidName: 'pid',
  391. childName: 'children',
  392. returnList: []
  393. }
  394. let treeData = this.$utils.tree.listToTree(data, option) // 数组转成树
  395. if (clsId) { // 如果路径中存在实体类id
  396. for (let z = 0; z < treeData.length; z++) {
  397. if (treeData[z]['entClsID'] === clsId) {
  398. this.checkedKeys = getIdsList(treeData[z])
  399. }
  400. }
  401. }
  402. this.gData = treeData
  403. generateList(treeData) // 生成个列表
  404. const vm = this
  405. // 遍历树找到父节点下面的所有子节点
  406. this.$utils.tree.walk(treeData, function (node, index, way) {
  407. var id = clsId // 路由中的id
  408. var myWay = [] // 需要找到的展开节点key
  409. if (node.key === id) {
  410. for (let i = 0; i < way.length; i++) {
  411. myWay.push(way[i]['key'])
  412. }
  413. vm.expandedKeys = myWay
  414. return false
  415. }
  416. })
  417. this.loadingTree = false
  418. })
  419. },
  420. /**
  421. *改变关系类checkbox
  422. */
  423. onChangeRelClsCheckBox(checkedValues) {
  424. let entClass, links
  425. if (this.filEntClass !== undefined) {
  426. entClass = this.filEntClass
  427. } else {
  428. entClass = this.allEntClassCache
  429. }
  430. links = this.filLinks.filter((v, i) => {
  431. if (v.classType === 'dashed') {
  432. return true
  433. } else if (v.relClsID) {
  434. return checkedValues.includes(v.relClsName)
  435. }
  436. })
  437. // 过滤掉,节点不存在的边
  438. const chartLinks = links.filter((link) => {
  439. return !!entClass.find(ent => (ent.id === link.from || ent.id === link.to))
  440. })
  441. // 重绘echart
  442. this.drawChart(entClass, chartLinks)
  443. }
  444. },
  445. watch: {},
  446. mounted() {
  447. }
  448. }
  449. </script>
  450. <style>
  451. /*覆盖样式加的id*/
  452. #mycheck.ant-checkbox-group {
  453. max-width: none;
  454. }
  455. .ant-spin-container {
  456. height: 100%;
  457. }
  458. </style>