MultiTab.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <script>
  2. import events from './events'
  3. export default {
  4. name: 'MultiTab',
  5. data () {
  6. return {
  7. pagesRecordList: [], // 记录打开过的页面
  8. tabsList: [], // 已打开页签
  9. pages: [], // 已打开页面
  10. activeKey: '', // 当前页面
  11. newTabIndex: 0,
  12. maxTabNums: 10
  13. }
  14. },
  15. inject: ['reloadView'],
  16. created () {
  17. // bind event
  18. events.$on('open', val => {
  19. if (!val) {
  20. throw new Error(`multi-tab: open tab ${val} err`)
  21. }
  22. this.activeKey = val
  23. }).$on('close', val => {
  24. if (!val) {
  25. this.closeThat(this.activeKey)
  26. return
  27. }
  28. this.closeThat(val)
  29. }).$on('rename', ({ key, name }) => {
  30. // console.log('rename', key, name)
  31. try {
  32. const item = this.pages.find(item => item.path === key)
  33. item.meta.customTitle = name
  34. this.$forceUpdate()
  35. } catch (e) {
  36. }
  37. })
  38. if (this.pages.length <= this.maxTabNums) {
  39. this.pages.push(this.$route)
  40. this.tabsList.push(this.$route.name)
  41. this.pagesRecordList.push(this.$route.name)
  42. this.selectedLastPath()
  43. }
  44. },
  45. methods: {
  46. onEdit (targetKey, action) {
  47. this[action](targetKey)
  48. },
  49. onChange (targetKey) {
  50. const index = this.tabsList.indexOf(targetKey)
  51. if (index >= 0) {
  52. const row = this.pages[index]
  53. if (row && row.fullPath && row.fullPath.indexOf('/list') == -1) { // 非列表页,根据当前路由路径判断是子页还是列表页
  54. this.$store.state.app.isNewSubTab = true
  55. } else {
  56. this.$store.state.app.isNewSubTab = false
  57. }
  58. }
  59. },
  60. remove (targetKey) {
  61. this.pages = this.pages.filter(page => page.name !== targetKey)
  62. this.tabsList = this.tabsList.filter(name => name !== targetKey)
  63. // 判断当前标签是否关闭,若关闭则跳转到最后一个还存在的标签页
  64. if (!this.tabsList.includes(this.activeKey)) {
  65. this.selectedLastPath()
  66. }
  67. this.$store.state.app.closeTabPages.push(targetKey)
  68. },
  69. selectedLastPath () {
  70. this.activeKey = this.tabsList[this.tabsList.length - 1]
  71. },
  72. replaceTab (newtab, oldtab) {
  73. const index = this.pages.findIndex(item => item.name == oldtab.name)
  74. this.pages.splice(index, 1, newtab)
  75. this.tabsList.splice(index, 1, newtab.name)
  76. },
  77. // content menu
  78. refashThat (e) {
  79. this.reloadView()
  80. },
  81. closeThat (e) {
  82. // 判断是否为最后一个标签页,如果是最后一个,则无法被关闭
  83. if (this.tabsList.length > 1) {
  84. this.remove(e)
  85. } else {
  86. this.$message.info('这是最后一个标签了, 无法被关闭')
  87. }
  88. },
  89. closeLeft (e) {
  90. const currentIndex = this.tabsList.indexOf(e)
  91. if (currentIndex > 0) {
  92. this.tabsList.forEach((item, index) => {
  93. if (index < currentIndex) {
  94. this.remove(item)
  95. }
  96. })
  97. } else {
  98. this.$message.info('左侧没有标签')
  99. }
  100. },
  101. closeRight (e) {
  102. const currentIndex = this.tabsList.indexOf(e)
  103. if (currentIndex < (this.tabsList.length - 1)) {
  104. this.tabsList.forEach((item, index) => {
  105. if (index > currentIndex) {
  106. this.remove(item)
  107. }
  108. })
  109. } else {
  110. this.$message.info('右侧没有标签')
  111. }
  112. },
  113. closeAll (e) {
  114. const currentIndex = this.tabsList.indexOf(e)
  115. this.tabsList.forEach((item, index) => {
  116. if (index !== currentIndex) {
  117. this.remove(item)
  118. }
  119. })
  120. },
  121. closeMenuClick (key, route) {
  122. this[key](route)
  123. },
  124. renderTabPaneMenu (e) {
  125. // return (
  126. // <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}>
  127. // {this.activeKey == e ? (<a-menu-item key="refashThat">刷新页面</a-menu-item>) : ''}
  128. // <a-menu-item key="closeRight">关闭右侧</a-menu-item>
  129. // <a-menu-item key="closeLeft">关闭左侧</a-menu-item>
  130. // <a-menu-item key="closeAll">关闭全部</a-menu-item>
  131. // </a-menu>
  132. // )
  133. return (
  134. <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}>
  135. <a-menu-item key="closeRight">关闭右侧</a-menu-item>
  136. <a-menu-item key="closeLeft">关闭左侧</a-menu-item>
  137. <a-menu-item key="closeAll">关闭全部</a-menu-item>
  138. </a-menu>
  139. )
  140. },
  141. // render
  142. renderTabPane (title, keyPath) {
  143. const menu = this.renderTabPaneMenu(keyPath)
  144. return (
  145. <a-dropdown overlay={menu} trigger={['contextmenu']}>
  146. <span style={{ userSelect: 'none' }}>{ title }</span>
  147. </a-dropdown>
  148. )
  149. }
  150. },
  151. watch: {
  152. '$route': function (newVal, oldVal) {
  153. if (newVal.name == oldVal.name) { return }
  154. // console.log(newVal, oldVal, this.pages)
  155. // 解决页面地址相同参数不同时打开多个页签问题(例 详情页id或sn不同时)
  156. const index = this.tabsList.indexOf(newVal.name)
  157. // 是否时同一个父级菜单下的页面
  158. const mnlen = newVal.matched.length < 3
  159. const newMatched = newVal.matched[mnlen ? 1 : 2]
  160. // 是否已存在同级菜单
  161. const hasEqChild = this.pages.find(item => {
  162. const alen = item.matched.length < 3
  163. return item.matched[alen ? 1 : 2].name == newMatched.name
  164. })
  165. // console.log(hasEqChild, 'hasEqChild')
  166. const oldMenu = hasEqChild || oldVal
  167. const molen = oldMenu.matched.length < 3
  168. const oldMatched = oldMenu.matched[molen ? 1 : 2]
  169. const hasChildren = newMatched.name == oldMatched.name
  170. // 判断是否是新增,编辑,详情页
  171. const na = newVal.meta.title.indexOf('详情') >= 0
  172. const nb = newVal.meta.title.indexOf('新增') >= 0
  173. const nc = newVal.meta.title.indexOf('编辑') >= 0
  174. const nxt = newVal.meta.replaceTab
  175. const oa = oldMenu.meta.title.indexOf('详情') >= 0
  176. const ob = oldMenu.meta.title.indexOf('新增') >= 0
  177. const oc = oldMenu.meta.title.indexOf('编辑') >= 0
  178. const oxt = oldMenu.meta.replaceTab
  179. if (index < 0) { // 不存在
  180. // console.log(1, hasChildren, '不存在')
  181. if ((na || nb || nc || nxt || oa || ob || oc || oxt) && hasChildren) {
  182. // 替换当前页签
  183. this.replaceTab(newVal, oldMenu)
  184. // 从列表打开新增,编辑
  185. this.$store.state.app.isNewTab = na || nb || nc || nxt
  186. // 从编辑,新增返回列表,刷新列表
  187. this.$store.state.app.updateList = ob || oc || oxt
  188. this.activeKey = newVal.name
  189. } else {
  190. // 新打开的页签
  191. if (this.pages.length < this.maxTabNums) {
  192. this.tabsList.push(newVal.name)
  193. this.pages.push(newVal)
  194. this.pagesRecordList.push(newVal.name)
  195. this.$store.state.app.isNewTab = true
  196. this.$store.state.app.updateList = false
  197. this.activeKey = newVal.name
  198. } else {
  199. // this.$message.info('您当前打开窗口太多,请关闭一些,窗口不能超过' + this.maxTabNums + '个')
  200. const _this = this
  201. this.$store.state.app.closeTabPages.push(newVal.name)
  202. this.$warning({
  203. title: '提示',
  204. content: '您当前打开窗口太多,请关闭一些,窗口不能超过' + this.maxTabNums + '个',
  205. onOk () {}
  206. })
  207. _this.$router.go(-1)
  208. }
  209. }
  210. } else { // 已存在
  211. // console.log(1, nxt, '已存在')
  212. this.pages.splice(index, 1, newVal)
  213. this.tabsList.splice(index, 1, newVal.name)
  214. this.$store.state.app.isNewTab = !!nxt
  215. if (hasEqChild) {
  216. this.$store.state.app.updateList = hasEqChild.name == oldVal.name
  217. }
  218. this.activeKey = newVal.name
  219. }
  220. },
  221. activeKey: function (newPathKey) {
  222. const pages = this.pages.find(item => item.name == newPathKey)
  223. this.$router.push({ name: newPathKey, params: pages.params, query: pages.query })
  224. }
  225. },
  226. render () {
  227. const { onEdit, onChange, refashThat, $data: { pages, maxTabNums } } = this
  228. const tabPanel = pages.length > maxTabNums ? pages.slice(0, maxTabNums - 1) : pages
  229. const panes = tabPanel.map(page => {
  230. return (
  231. <a-tab-pane
  232. style={{ height: 0 }}
  233. key={page.name} closable={tabPanel.length > 1}
  234. >
  235. <span slot="tab">
  236. {this.renderTabPane(page.meta.customTitle || page.meta.title, page.name)}
  237. {this.activeKey == 'page.name' ? (<a-icon
  238. {...{ on: { click: refashThat } }}
  239. style={{ marginLeft: '8px', fontSize: '12px' }}
  240. type="reload" />) : ''}
  241. </span>
  242. </a-tab-pane>)
  243. })
  244. return (
  245. <div class="ant-pro-mu lti-tab">
  246. <div class="ant-pro-multi-tab-wrapper multi-tab-con">
  247. <a-tabs
  248. hideAdd
  249. type={'editable-card'}
  250. v-model={this.activeKey}
  251. tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }}
  252. {...{ on: { edit: onEdit, 'change': onChange } }}>
  253. {panes}
  254. </a-tabs>
  255. </div>
  256. </div>
  257. )
  258. }
  259. }
  260. </script>