index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <template>
  2. <div
  3. ref="ibpsTree"
  4. :style="{ width: width + 'px', position: location }"
  5. class="ibps-tree"
  6. >
  7. <div
  8. v-if="title"
  9. ref="header"
  10. :class="['layout-header--' + $ELEMENT.size]"
  11. class="layout-header"
  12. >
  13. <div
  14. v-show="isExpand"
  15. :style="{ width: width - 55 + 'px' }"
  16. class="layout-header-title ibps-ellipsis"
  17. >
  18. {{ title }}
  19. </div>
  20. <div v-if="angleDouble" class="layout-header-tools">
  21. <el-tooltip
  22. :content="isExpand ? '收缩' : '展开'"
  23. placement="bottom-start"
  24. >
  25. <a
  26. herf="javascript:void(0);"
  27. class="pinBtn"
  28. @click="handleExpandCollapse"
  29. >
  30. <ibps-icon :name="expandCollapseIcon" />
  31. </a>
  32. </el-tooltip>
  33. </div>
  34. </div>
  35. <div
  36. v-if="toolbars"
  37. :class="isExpand ? 'ibps-show-expand' : 'ibps-show-collapse'"
  38. >
  39. <div
  40. v-if="toolbars"
  41. v-show="isExpand"
  42. ref="toolbar"
  43. class="ibps-tree-toolbar"
  44. >
  45. <ibps-toolbar
  46. :actions="toolbars"
  47. type="icon"
  48. @action-event="handleActionEvent"
  49. />
  50. </div>
  51. </div>
  52. <div
  53. v-show="!isExpand"
  54. class="ibps-show"
  55. :style="{ height: (isExpand ? treeHeight : height) - 45 + 'px' }"
  56. >
  57. &nbsp;
  58. </div>
  59. <div
  60. v-if="$slots.searchForm"
  61. v-show="isExpand"
  62. ref="searchForm"
  63. class="ibps-tree-search-form"
  64. >
  65. <slot name="searchForm" />
  66. </div>
  67. <div
  68. v-show="isExpand"
  69. :style="{ height: (isExpand ? treeHeight : height) - 45 + 'px' }"
  70. class="ibps-tree-main"
  71. >
  72. <el-scrollbar
  73. style="height: 100%; width: 100%"
  74. wrap-class="ibps-tree-wrapper"
  75. >
  76. <el-tree
  77. v-if="!lazy ? (treeData && treeData.length>0) : lazy "
  78. ref="elTree"
  79. v-loading="loading"
  80. :data="!lazy ? treeData : null"
  81. :lazy="lazy"
  82. :load="load"
  83. v-bind="treeOptions"
  84. :default-expand-all="judgeTitle()"
  85. node-key="id"
  86. :default-expanded-keys="treeExpandData"
  87. @node-click="handleNodeClick"
  88. @node-contextmenu="handleNodeContextmenu"
  89. >
  90. <span slot-scope="scope" class="ibps-custom-tree-node">
  91. <ibps-icon
  92. v-if="showIcon"
  93. :name="getIcon(scope.data)"
  94. />
  95. <span>{{ scope.node.label }}</span>
  96. </span>
  97. </el-tree>
  98. </el-scrollbar>
  99. </div>
  100. <!--右键菜单-->
  101. <ibps-contextmenu
  102. v-if="contextmenus"
  103. ref="contextmenu"
  104. :visible.sync="contextmenuFlag"
  105. :menulist="contextmenuList"
  106. :x="contentmenuX"
  107. :y="contentmenuY"
  108. :z-index="zIndex"
  109. >
  110. <ibps-contextmenu-list
  111. :menulist="contextmenuList"
  112. @row-click="handleContextmenuClick"
  113. />
  114. </ibps-contextmenu>
  115. </div>
  116. </template>
  117. <script>
  118. import TreeUtils from '@/utils/tree'
  119. import PopupManager from '@/utils/popup'
  120. import IbpsContextmenu from '@/components/ibps-contextmenu'
  121. import IbpsContextmenuList from '@/components/ibps-contextmenu/components/contentmenu-list'
  122. export default {
  123. components: {
  124. IbpsContextmenu,
  125. IbpsContextmenuList
  126. },
  127. props: {
  128. title: String,
  129. angleDouble: {
  130. type: Boolean,
  131. default: true
  132. },
  133. width: {
  134. type: Number,
  135. default: 200
  136. },
  137. height: {
  138. type: Number,
  139. default: 400
  140. },
  141. position: {
  142. type: String,
  143. default: 'west'
  144. },
  145. location: {
  146. type: String,
  147. default: 'initial'
  148. },
  149. loading: {
  150. type: Boolean,
  151. default: false
  152. },
  153. data: {
  154. type: Array
  155. },
  156. dataType: {
  157. // 数据类型,tree:树形,list:列表
  158. type: String,
  159. default: 'list'
  160. },
  161. customZindex: {
  162. type: Number,
  163. default: 2003
  164. },
  165. options: {
  166. type: Object,
  167. default: () => {
  168. return {}
  169. }
  170. },
  171. toolbars: {
  172. type: [Array, Boolean],
  173. default: () => {
  174. return [
  175. {
  176. key: 'refresh'
  177. },
  178. {
  179. key: 'expand'
  180. },
  181. {
  182. key: 'compress'
  183. }
  184. ]
  185. }
  186. },
  187. contextmenus: {
  188. type: Array,
  189. default: () => {
  190. return []
  191. }
  192. },
  193. load: Function,
  194. lazy: {
  195. type: Boolean,
  196. default: false
  197. }
  198. },
  199. data () {
  200. return {
  201. treeHeight: this.height,
  202. isExpand: true,
  203. autoPlacement: true,
  204. contextmenuFlag: false,
  205. contentmenuX: 0,
  206. contentmenuY: 0,
  207. contextmenuData: {},
  208. contextmenuList: [],
  209. zIndex: 2003,
  210. treeExpandData: []
  211. }
  212. },
  213. computed: {
  214. treeData () {
  215. if (!this.data || this.data.length === 0) return []
  216. this.data.map((item) => {
  217. if (item.name === '任务分类') {
  218. item.name = '记录分类'
  219. }
  220. })
  221. if (this.dataType === 'list') {
  222. return TreeUtils.transformToTreeFormat(
  223. JSON.parse(JSON.stringify(this.data)),
  224. {
  225. idKey: this.treeOptions['node-key'],
  226. pIdKey: this.pidKey,
  227. childrenKey: this.treeOptions['props']['children']
  228. }
  229. )
  230. } else {
  231. return JSON.parse(JSON.stringify(this.data))
  232. }
  233. },
  234. treeOptions () {
  235. const defaultOptions = {
  236. rootPId: null,
  237. 'node-key': 'id',
  238. 'pid-key': 'parentId',
  239. 'default-expand-all': true,
  240. 'expand-on-click-node': false,
  241. 'highlight-current': true,
  242. props: {
  243. children: 'children',
  244. label: 'name'
  245. }
  246. }
  247. return Object.assign(defaultOptions, this.options)
  248. },
  249. pidKey () {
  250. return this.treeOptions['pid-key']
  251. },
  252. rootPId () {
  253. return this.treeOptions['rootPId'] || null
  254. },
  255. showIcon () {
  256. return this.treeOptions['showIcon'] || false
  257. },
  258. expandCollapseIcon () {
  259. if (this.position === 'west') {
  260. return this.isExpand
  261. ? 'angle-double-left'
  262. : 'angle-double-right'
  263. } else {
  264. return this.isExpand
  265. ? 'angle-double-right'
  266. : 'angle-double-left'
  267. }
  268. }
  269. },
  270. watch: {
  271. height () {
  272. this.handleTreeHeight()
  273. },
  274. customZindex (val) {
  275. this.zIndex = val
  276. },
  277. treeData (val) {
  278. // if (val && val[0] && val[0].children) {
  279. // this.treeExpandData.push(val[0].children[0].id)
  280. // }
  281. // 如果是部门管理里只显示一个医院的,那么就展开所有的节点
  282. if (val[0] && val[0].children.length === 1 &&
  283. this.title === '部门管理') {
  284. return
  285. }
  286. if (val[0] && val[0].children.length && val[0].children.length !== 1) {
  287. // 默认打开第三层
  288. for (const i of val[0].children) {
  289. this.treeExpandData.push(
  290. i.children.length ? i.children[0].id : i.id
  291. )
  292. }
  293. }
  294. }
  295. },
  296. mounted () {
  297. this.handleTreeHeight()
  298. if (this.contextmenus && this.contextmenus.length > 0) {
  299. this.fixZIndex()
  300. }
  301. },
  302. methods: {
  303. /**
  304. * 判斷tree是否展开
  305. */
  306. judgeTitle () {
  307. if (this.title === '业务对象管理' ||
  308. this.title === undefined ||
  309. (
  310. this.treeData[0] && this.treeData[0].children.length === 1 &&
  311. this.title === '部门管理'
  312. )
  313. ) {
  314. return true
  315. }
  316. return this.lazy
  317. },
  318. /**
  319. * zxh 修复zindex 不是最高的被遮住
  320. */
  321. fixZIndex () {
  322. this.zIndex = PopupManager.getZIndex()
  323. },
  324. getIcon (data) {
  325. let icon = data
  326. ? data[this.treeOptions['iconKey'] || 'icon']
  327. : 'list-alt'
  328. if (icon) {
  329. return icon
  330. }
  331. if (data[this.pidKey] === this.rootPId) {
  332. icon = this.treeOptions['rootIcon'] || 'home'
  333. } else {
  334. icon = this.treeOptions['nodeIcon'] || 'list-alt'
  335. }
  336. },
  337. handleTreeHeight () {
  338. this.treeHeight = this.height
  339. if (this.$refs.header) {
  340. this.treeHeight -= this.$refs.header.clientHeight
  341. }
  342. if (this.$refs.toolbar) {
  343. this.treeHeight -= this.$refs.toolbar.clientHeight
  344. }
  345. if (this.$refs.searchForm) {
  346. this.treeHeight -= this.$refs.searchForm.clientHeight
  347. }
  348. },
  349. handleActionEvent (action, position, data) {
  350. const command = action.key
  351. if (command === 'expand') {
  352. this.expandCompressTree(true)
  353. } else if (command === 'compress') {
  354. this.expandCompressTree(false)
  355. }
  356. this.$emit('action-event', command, position, data)
  357. },
  358. expandCompressTree (expanded) {
  359. for (
  360. var i = 0;
  361. i < this.$refs.elTree.store._getAllNodes().length;
  362. i++
  363. ) {
  364. this.$refs.elTree.store._getAllNodes()[i].expanded = expanded
  365. }
  366. },
  367. handleNodeClick (data) {
  368. this.$emit('node-click', data)
  369. },
  370. refreshNode (id) {
  371. const node = this.$refs.elTree.getNode(id) // 通过节点id找到对应树节点对象
  372. node.loaded = false
  373. node.expand() // 主动调用展开节点方法,重新查询该节点下的所有子节点
  374. },
  375. /**
  376. * 处理节点右键菜单
  377. */
  378. handleNodeContextmenu (event, data) {
  379. if (!this.contextmenus || this.contextmenus.length === 0) return
  380. let target = event.target
  381. let flag = false
  382. if (
  383. (target &&
  384. target.className.indexOf('el-tree-node__content') > -1) ||
  385. (target &&
  386. target.className.indexOf('ibps-custom-tree-node') > -1)
  387. ) {
  388. flag = true
  389. } else if (
  390. (target &&
  391. target.parentNode.className.indexOf(
  392. 'el-tree-node__content'
  393. ) > -1) ||
  394. (target &&
  395. target.parentNode.className.indexOf(
  396. 'ibps-custom-tree-node'
  397. ) > -1)
  398. ) {
  399. target = target.parentNode
  400. flag = true
  401. }
  402. if (flag) {
  403. event.preventDefault()
  404. event.stopPropagation()
  405. this.handleContextmenuList(data)
  406. this.contextmenuData = data
  407. this.contextmenuFlag = true
  408. this.handleReferenceContextmenu(event)
  409. }
  410. },
  411. handleContextmenuList (data) {
  412. this.contextmenuList = []
  413. const isRoot = data[this.pidKey] === this.rootPId
  414. this.contextmenus.forEach((menu) => {
  415. if (!menu.rights) {
  416. this.contextmenuList.push(menu)
  417. } else {
  418. if (Array.isArray(menu.rights)) {
  419. if (menu.rights.indexOf('node') > -1 && !isRoot) {
  420. this.contextmenuList.push(menu)
  421. }
  422. } else if (typeof menu.rights === 'function') {
  423. if (menu.rights.call(this, menu, data, isRoot)) {
  424. this.contextmenuList.push(menu)
  425. }
  426. }
  427. }
  428. })
  429. },
  430. // 处理菜单位置
  431. handleReferenceContextmenu (event) {
  432. const eventX = event.pageX
  433. const eventY = event.pageY
  434. this.$nextTick(() => {
  435. const contextmenuPosition = {
  436. top: eventY,
  437. left: eventX
  438. }
  439. if (this.autoPlacement) {
  440. const el = this.$refs.contextmenu.$el
  441. const contextmenuWidth = el.clientWidth
  442. const contextmenuHeight = el.clientHeight
  443. if (contextmenuHeight + eventY >= window.innerHeight) {
  444. contextmenuPosition.top -= contextmenuHeight
  445. }
  446. if (contextmenuWidth + eventX >= window.innerWidth) {
  447. contextmenuPosition.left -= contextmenuWidth
  448. }
  449. }
  450. this.contentmenuX = contextmenuPosition.left
  451. this.contentmenuY = contextmenuPosition.top
  452. })
  453. },
  454. handleContextmenuClick (command) {
  455. this.contextmenuFlag = false
  456. this.$emit(
  457. 'action-event',
  458. command,
  459. 'contextmenu',
  460. this.contextmenuData[this.pkKey],
  461. this.contextmenuData
  462. )
  463. },
  464. handleExpandCollapse () {
  465. this.isExpand = !this.isExpand
  466. this.$emit('expand-collapse', this.isExpand)
  467. },
  468. remove (data) {
  469. this.$refs.elTree.remove(data)
  470. },
  471. getCurrentKey () {
  472. return this.$refs.elTree.getCurrentKey()
  473. }
  474. }
  475. }
  476. </script>
  477. <style lang="scss" >
  478. $border-color: #e5e6e7;
  479. .ibps-tree {
  480. .layout-header {
  481. background: #ffffff;
  482. height: 35px;
  483. border-left: 1px solid $border-color;
  484. border-bottom: 1px solid $border-color;
  485. font-weight: bold;
  486. text-align: center;
  487. padding: 5px;
  488. position: relative;
  489. overflow: hidden;
  490. overflow-y: none;
  491. &--medium {
  492. height: 40px;
  493. }
  494. &--small {
  495. height: 35px;
  496. }
  497. &--mini {
  498. height: 30px;
  499. }
  500. .layout-header-title {
  501. float: left;
  502. text-align: left;
  503. font-size: 14px;
  504. margin: 10px 5px 5px;
  505. padding: 0;
  506. }
  507. .layout-header-tools {
  508. float: right;
  509. margin-top: 5px;
  510. position: relative;
  511. padding: 0;
  512. .pinBtn {
  513. cursor: pointer;
  514. margin-left: 5px;
  515. color: #c4c4c4;
  516. font-size: 25px;
  517. }
  518. }
  519. }
  520. .ibps-show {
  521. background: #ffffff;
  522. }
  523. .ibps-show-expand {
  524. height: 42px;
  525. background: #ffffff;
  526. border-bottom: 1px solid $border-color;
  527. }
  528. .ibps-show-collapse {
  529. background: #ffffff;
  530. }
  531. .ibps-tree-toolbar {
  532. border: 1px solid $border-color;
  533. height: 35px;
  534. padding: 5px;
  535. }
  536. .ibps-tree-search-form {
  537. padding: 5px;
  538. border-right: 1px solid $border-color;
  539. background: #ffffff;
  540. }
  541. .ibps-tree-main {
  542. border: 1px solid $border-color;
  543. background: #ffffff;
  544. }
  545. .ibps-tree-wrapper {
  546. background: #ffffff;
  547. .el-tree > .el-tree-node {
  548. display: inline-block;
  549. }
  550. }
  551. .ibps-custom-tree-node {
  552. font-size: 13px;
  553. padding-right: 8px;
  554. }
  555. }
  556. </style>