Outline.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <template>
  2. <div class="fm-outline-wrapper">
  3. <div class="fm-outline-header">
  4. <el-input v-model="filterText" placeholder="Filter node" clearable size="small" />
  5. </div>
  6. <div class="fm-outline-content">
  7. <el-scrollbar style="height: 100%;" wrap-class="fm-outline-content-wrapper" view-class="fm-outline-content-view">
  8. <el-tree
  9. ref="treeRef"
  10. class="filter-tree"
  11. :data="treeData"
  12. :props="defaultProps"
  13. default-expand-all
  14. highlight-current
  15. :expand-on-click-node="false"
  16. :filter-node-method="filterNode"
  17. :indent="16"
  18. node-key="id"
  19. @node-click="onNodeClick"
  20. check-on-click-node
  21. >
  22. <template #default="{ node }">
  23. <div class="custom-tree-node" :class="{'is-bind': node.data && node.data.dataBind}">
  24. <span v-html="node.label"></span>
  25. </div>
  26. </template>
  27. </el-tree>
  28. </el-scrollbar>
  29. </div>
  30. </div>
  31. </template>
  32. <script>
  33. export default {
  34. props: ['data', 'show'],
  35. data () {
  36. return {
  37. filterText: '',
  38. defaultProps: {
  39. children: 'children',
  40. label: 'label',
  41. disabled: 'disabled'
  42. },
  43. treeData: [],
  44. currentKey: ''
  45. }
  46. },
  47. emits: ['select'],
  48. mounted () {
  49. this.treeData = this.loadNode(this.data)
  50. this.loadTreeWidthStyle()
  51. },
  52. methods: {
  53. filterNode (value, data) {
  54. if (!value) return true
  55. return data.label.includes(value)
  56. },
  57. loadNode (list) {
  58. let currentNode = []
  59. for (let i = 0; i < list.length; i++) {
  60. if (!list[i].type) continue
  61. currentNode.push({
  62. id: list[i].key,
  63. label: (list[i].icon ? `<i class=" fm-iconfont ${list[i].icon}"></i>&nbsp;` : '')
  64. +`<span class="custom-tree-node-type">${list[i].name ? list[i].name : this.$t('fm.components.fields.' + list[i].type)}</span>`
  65. + (list[i].model ? `&nbsp;{ <span class="custom-tree-node-model">${list[i].model}</span> }` : ''),
  66. children: [],
  67. dataBind: list[i].options && list[i].options.dataBind
  68. })
  69. if (list[i].type == 'grid') {
  70. currentNode[i].children = this.loadNode(list[i].columns)
  71. }
  72. if (list[i].type == 'col') {
  73. currentNode[i].children = this.loadNode(list[i].list)
  74. }
  75. if (list[i].type == 'report') {
  76. // 处理表格布局结构,便于大纲树展示
  77. let reportList = []
  78. for (let r = 0; r < list[i].rows.length; r++) {
  79. for (let c = 0; c < list[i].rows[r].columns.length; c++) {
  80. let td = list[i].rows[r].columns[c]
  81. if (!td.options.invisible) {
  82. reportList.push(td)
  83. }
  84. }
  85. }
  86. currentNode[i].children = this.loadNode(reportList)
  87. }
  88. if (list[i].type == 'td') {
  89. currentNode[i].children = this.loadNode(list[i].list)
  90. }
  91. if (list[i].type == 'tabs') {
  92. for (let t = 0; t < list[i].tabs.length; t++) {
  93. currentNode[i].children.push({
  94. label: list[i].tabs[t].label,
  95. children: [],
  96. disabled: true
  97. })
  98. currentNode[i].children[t].children = this.loadNode(list[i].tabs[t].list)
  99. }
  100. }
  101. if (list[i].type == 'collapse') {
  102. for (let t = 0; t < list[i].tabs.length; t++) {
  103. currentNode[i].children.push({
  104. label: list[i].tabs[t].title,
  105. children: [],
  106. disabled: true
  107. })
  108. currentNode[i].children[t].children = this.loadNode(list[i].tabs[t].list)
  109. }
  110. }
  111. if (list[i].type == 'inline') {
  112. currentNode[i].children = this.loadNode(list[i].list)
  113. }
  114. if (list[i].type == 'table') {
  115. currentNode[i].children = this.loadNode(list[i].tableColumns)
  116. }
  117. if (list[i].type == 'subform') {
  118. currentNode[i].children = this.loadNode(list[i].list)
  119. }
  120. if (list[i].type == 'dialog') {
  121. currentNode[i].children = this.loadNode(list[i].list)
  122. }
  123. if (list[i].type == 'card') {
  124. currentNode[i].children = this.loadNode(list[i].list)
  125. }
  126. if (list[i].type == 'group') {
  127. currentNode[i].children = this.loadNode(list[i].list)
  128. }
  129. }
  130. return currentNode
  131. },
  132. loadTreeWidthStyle () {
  133. if (!this.show) return
  134. document.querySelector('.fm-outline-content .fm-outline-content-view').removeAttribute('style')
  135. let realWidth = document.querySelector('.fm-outline-content .fm-outline-content-wrapper').scrollWidth
  136. document.querySelector('.fm-outline-content .fm-outline-content-view').setAttribute('style', `width: ${realWidth <= 250 ? realWidth : (realWidth + 5)}px`)
  137. },
  138. onNodeClick (data, node) {
  139. if (data.id) {
  140. this.$emit('select', data.id)
  141. this.currentKey = data.id
  142. } else {
  143. this.$refs.treeRef?.setCurrentKey(this.currentKey)
  144. }
  145. },
  146. setCurrentKey (key, isScrollTo) {
  147. this.currentKey = key
  148. this.$refs.treeRef?.setCurrentKey(key)
  149. setTimeout(() => {
  150. isScrollTo && this.scrollTo()
  151. }, 200)
  152. },
  153. scrollTo () {
  154. // 计算当前选中元素距离容器顶部距离
  155. let y = document.querySelector('.fm-outline-content .is-current')?.offsetTop
  156. let containerHeight = document.querySelector('.fm-outline-content').offsetHeight
  157. let containerTop = document.querySelector('.fm-outline-content').getBoundingClientRect().top
  158. let scorllTop = document.querySelector('.fm-outline-content-view').getBoundingClientRect().top
  159. let top = scorllTop - containerTop
  160. if (y > -top + containerHeight || y < -top) {
  161. document.querySelector('.fm-outline-content-wrapper').scrollTop = y
  162. }
  163. }
  164. },
  165. watch: {
  166. filterText (val) {
  167. this.$refs.treeRef.filter(val)
  168. },
  169. data: {
  170. deep: true,
  171. handler (val) {
  172. this.treeData = this.loadNode(val)
  173. this.loadTreeWidthStyle()
  174. this.$nextTick(() => {
  175. this.$refs.treeRef?.setCurrentKey(this.currentKey)
  176. this.$refs.treeRef.filter(this.filterText)
  177. })
  178. }
  179. }
  180. }
  181. }
  182. </script>
  183. <style lang="scss">
  184. .fm-outline-wrapper{
  185. height: 100%;
  186. }
  187. .fm-outline-header{
  188. padding: 12px;
  189. height: 56px;
  190. }
  191. .fm-outline-content{
  192. height: calc(100% - 56px);
  193. .fm-outline-content-wrapper{
  194. height: 100%;
  195. overflow: auto;
  196. }
  197. .custom-tree-node{
  198. display: inline-block;
  199. font-size: 12px;
  200. line-height: 12px;
  201. .fm-iconfont{
  202. vertical-align: top;
  203. font-size: 14px;
  204. }
  205. &.is-bind{
  206. >span>.custom-tree-node-model{
  207. color: #67C23A;
  208. }
  209. }
  210. }
  211. .el-tree-node>.el-tree-node__children{
  212. overflow: unset;
  213. }
  214. .el-tree-node.is-current{
  215. >.el-tree-node__content{
  216. background: #c6e2ff;
  217. >.custom-tree-node{
  218. // background: #c6e2ff;
  219. }
  220. }
  221. }
  222. .filter-tree{
  223. margin-bottom: 10px;
  224. }
  225. }
  226. </style>