MultiTab.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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. },
  68. selectedLastPath () {
  69. this.activeKey = this.tabsList[this.tabsList.length - 1]
  70. },
  71. replaceTab (newtab, oldtab) {
  72. const index = this.pages.findIndex(item => item.name == oldtab.name)
  73. this.pages.splice(index, 1, newtab)
  74. this.tabsList.splice(index, 1, newtab.name)
  75. },
  76. // content menu
  77. refashThat (e) {
  78. this.reloadView()
  79. },
  80. closeThat (e) {
  81. // 判断是否为最后一个标签页,如果是最后一个,则无法被关闭
  82. if (this.tabsList.length > 1) {
  83. this.remove(e)
  84. } else {
  85. this.$message.info('这是最后一个标签了, 无法被关闭')
  86. }
  87. },
  88. closeLeft (e) {
  89. const currentIndex = this.tabsList.indexOf(e)
  90. if (currentIndex > 0) {
  91. this.tabsList.forEach((item, index) => {
  92. if (index < currentIndex) {
  93. this.remove(item)
  94. }
  95. })
  96. } else {
  97. this.$message.info('左侧没有标签')
  98. }
  99. },
  100. closeRight (e) {
  101. const currentIndex = this.tabsList.indexOf(e)
  102. if (currentIndex < (this.tabsList.length - 1)) {
  103. this.tabsList.forEach((item, index) => {
  104. if (index > currentIndex) {
  105. this.remove(item)
  106. }
  107. })
  108. } else {
  109. this.$message.info('右侧没有标签')
  110. }
  111. },
  112. closeAll (e) {
  113. const currentIndex = this.tabsList.indexOf(e)
  114. this.tabsList.forEach((item, index) => {
  115. if (index !== currentIndex) {
  116. this.remove(item)
  117. }
  118. })
  119. },
  120. closeMenuClick (key, route) {
  121. this[key](route)
  122. },
  123. renderTabPaneMenu (e) {
  124. // return (
  125. // <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}>
  126. // {this.activeKey == e ? (<a-menu-item key="refashThat">刷新页面</a-menu-item>) : ''}
  127. // <a-menu-item key="closeRight">关闭右侧</a-menu-item>
  128. // <a-menu-item key="closeLeft">关闭左侧</a-menu-item>
  129. // <a-menu-item key="closeAll">关闭全部</a-menu-item>
  130. // </a-menu>
  131. // )
  132. return (
  133. <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}>
  134. <a-menu-item key="closeRight">关闭右侧</a-menu-item>
  135. <a-menu-item key="closeLeft">关闭左侧</a-menu-item>
  136. <a-menu-item key="closeAll">关闭全部</a-menu-item>
  137. </a-menu>
  138. )
  139. },
  140. // render
  141. renderTabPane (title, keyPath) {
  142. const menu = this.renderTabPaneMenu(keyPath)
  143. return (
  144. <a-dropdown overlay={menu} trigger={['contextmenu']}>
  145. <span style={{ userSelect: 'none' }}>{ title }</span>
  146. </a-dropdown>
  147. )
  148. }
  149. },
  150. watch: {
  151. '$route': function (newVal, oldVal) {
  152. if (newVal.name == oldVal.name) { return }
  153. console.log(newVal, oldVal, this.pages)
  154. // 解决页面地址相同参数不同时打开多个页签问题(例 详情页id或sn不同时)
  155. const index = this.tabsList.indexOf(newVal.name)
  156. // 是否时同一个父级菜单下的页面
  157. const mnlen = newVal.matched.length < 3
  158. const newMatched = newVal.matched[mnlen ? 1 : 2]
  159. // 是否已存在同级菜单
  160. const hasEqChild = this.pages.find(item => {
  161. const alen = item.matched.length < 3
  162. return item.matched[alen ? 1 : 2].name == newMatched.name
  163. })
  164. // console.log(hasEqChild, 'hasEqChild')
  165. const oldMenu = hasEqChild || oldVal
  166. const molen = oldMenu.matched.length < 3
  167. const oldMatched = oldMenu.matched[molen ? 1 : 2]
  168. const hasChildren = newMatched.name == oldMatched.name
  169. // 判断是否是新增,编辑,详情页
  170. const na = newVal.meta.title.indexOf('详情') >= 0
  171. const nb = newVal.meta.title.indexOf('新增') >= 0
  172. const nc = newVal.meta.title.indexOf('编辑') >= 0
  173. const nxt = newVal.meta.replaceTab
  174. const oa = oldMenu.meta.title.indexOf('详情') >= 0
  175. const ob = oldMenu.meta.title.indexOf('新增') >= 0
  176. const oc = oldMenu.meta.title.indexOf('编辑') >= 0
  177. const oxt = oldMenu.meta.replaceTab
  178. if (index < 0) { // 不存在
  179. console.log(1, hasChildren, '不存在')
  180. if ((na || nb || nc || nxt || oa || ob || oc || oxt) && hasChildren) {
  181. // 替换当前页签
  182. this.replaceTab(newVal, oldMenu)
  183. // 从列表打开新增,编辑
  184. this.$store.state.app.isNewTab = na || nb || nc || nxt
  185. // 从编辑,新增返回列表,刷新列表
  186. this.$store.state.app.updateList = ob || oc || oxt
  187. this.activeKey = newVal.name
  188. } else {
  189. // 新打开的页签
  190. if (this.pages.length < this.maxTabNums) {
  191. this.tabsList.push(newVal.name)
  192. this.pages.push(newVal)
  193. this.pagesRecordList.push(newVal.name)
  194. this.$store.state.app.isNewTab = true
  195. this.$store.state.app.updateList = false
  196. this.activeKey = newVal.name
  197. } else {
  198. // this.$message.info('您当前打开窗口太多,请关闭一些,窗口不能超过' + this.maxTabNums + '个')
  199. const _this = this
  200. this.$warning({
  201. title: '提示',
  202. content: '您当前打开窗口太多,请关闭一些,窗口不能超过' + this.maxTabNums + '个',
  203. onOk () {}
  204. })
  205. _this.$router.go(-1)
  206. }
  207. }
  208. } else { // 已存在
  209. console.log(1, nxt, '已存在')
  210. this.pages.splice(index, 1, newVal)
  211. this.tabsList.splice(index, 1, newVal.name)
  212. this.$store.state.app.isNewTab = !!nxt
  213. if (hasEqChild) {
  214. this.$store.state.app.updateList = hasEqChild.name == oldVal.name
  215. }
  216. this.activeKey = newVal.name
  217. }
  218. },
  219. activeKey: function (newPathKey) {
  220. const pages = this.pages.find(item => item.name == newPathKey)
  221. this.$router.push({ name: newPathKey, params: pages.params, query: pages.query })
  222. }
  223. },
  224. render () {
  225. const { onEdit, onChange, refashThat, $data: { pages, maxTabNums } } = this
  226. const tabPanel = pages.length > maxTabNums ? pages.slice(0, maxTabNums - 1) : pages
  227. const panes = tabPanel.map(page => {
  228. return (
  229. <a-tab-pane
  230. style={{ height: 0 }}
  231. key={page.name} closable={tabPanel.length > 1}
  232. >
  233. <span slot="tab">
  234. {this.renderTabPane(page.meta.customTitle || page.meta.title, page.name)}
  235. {this.activeKey == 'page.name' ? (<a-icon
  236. {...{ on: { click: refashThat } }}
  237. style={{ marginLeft: '8px', fontSize: '12px' }}
  238. type="reload" />) : ''}
  239. </span>
  240. </a-tab-pane>)
  241. })
  242. return (
  243. <div class="ant-pro-mu lti-tab">
  244. <div class="ant-pro-multi-tab-wrapper multi-tab-con">
  245. <a-tabs
  246. hideAdd
  247. type={'editable-card'}
  248. v-model={this.activeKey}
  249. tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }}
  250. {...{ on: { edit: onEdit, 'change': onChange } }}>
  251. {panes}
  252. </a-tabs>
  253. </div>
  254. </div>
  255. )
  256. }
  257. }
  258. </script>