Outline.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <template>
  2. <div class="fm-outline-header">
  3. <el-input v-model="filterText" placeholder="Filter node" clearable />
  4. </div>
  5. <div class="fm-outline-content">
  6. <el-scrollbar ref="scrollRef">
  7. <el-tree
  8. ref="treeRef"
  9. class="filter-tree"
  10. :data="treeData"
  11. :props="defaultProps"
  12. default-expand-all
  13. highlight-current
  14. :expand-on-click-node="false"
  15. :filter-node-method="filterNode"
  16. :indent="16"
  17. node-key="id"
  18. @node-click="onNodeClick"
  19. check-on-click-node
  20. >
  21. <template #default="{ node }">
  22. <div class="custom-tree-node" :class="{'is-bind': node.data?.dataBind}">
  23. <span v-html="node.label"></span>
  24. </div>
  25. </template>
  26. </el-tree>
  27. </el-scrollbar>
  28. </div>
  29. </template>
  30. <script>
  31. export default {
  32. props: ['data', 'show'],
  33. data () {
  34. return {
  35. filterText: '',
  36. defaultProps: {
  37. children: 'children',
  38. label: 'label',
  39. disabled: 'disabled'
  40. },
  41. treeData: [],
  42. currentKey: ''
  43. }
  44. },
  45. emits: ['select'],
  46. mounted () {
  47. this.treeData = this.loadNode(this.data)
  48. this.loadTreeWidthStyle()
  49. },
  50. methods: {
  51. filterNode (value, data) {
  52. if (!value) return true
  53. return data.label.includes(value)
  54. },
  55. loadNode (list) {
  56. let currentNode = []
  57. for (let i = 0; i < list.length; i++) {
  58. if (!list[i].type) continue
  59. currentNode.push({
  60. id: list[i].key,
  61. label: (
  62. list[i].type == 'custom' ? ('<i class="fm-iconfont icon-extend"></i>&nbsp;') : (
  63. list[i].icon ? `<i class=" fm-iconfont ${list[i].icon}"></i>&nbsp;` : '<i class="fm-iconfont"></i>'))
  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?.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 .el-scrollbar__view').removeAttribute('style')
  135. let realWidth = document.querySelector('.fm-outline-content .el-scrollbar__wrap').scrollWidth
  136. document.querySelector('.fm-outline-content .el-scrollbar__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 .el-scrollbar__view').getBoundingClientRect().top
  159. let top = scorllTop - containerTop
  160. if (y > -top + containerHeight || y < -top) {
  161. this.$refs.scrollRef.setScrollTop(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-header{
  185. padding: 12px;
  186. height: 56px;
  187. }
  188. .fm-outline-content{
  189. height: calc(100% - 56px);
  190. .custom-tree-node{
  191. display: inline-block;
  192. font-size: 12px;
  193. line-height: 12px;
  194. .fm-iconfont{
  195. vertical-align: top;
  196. font-size: 14px;
  197. }
  198. &.is-bind{
  199. >span>.custom-tree-node-model{
  200. color: #67C23A;
  201. }
  202. }
  203. }
  204. .el-tree-node>.el-tree-node__children{
  205. overflow: unset;
  206. }
  207. .el-tree-node.is-current{
  208. >.el-tree-node__content{
  209. background: #c6e2ff;
  210. >.custom-tree-node{
  211. // background: #c6e2ff;
  212. }
  213. }
  214. }
  215. .filter-tree{
  216. margin-bottom: 10px;
  217. }
  218. }
  219. html.dark{
  220. .fm-outline-content{
  221. .el-tree-node.is-current{
  222. >.el-tree-node__content{
  223. background: #213d5b;
  224. }
  225. }
  226. }
  227. }
  228. </style>