'a'
mh-two-thousand-and-two
2024-04-12 44d2c92345cd156a59fc327b3060292a282d2893
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import {
  callPageHook
} from '../util'
 
function addKeepAliveInclude (componentName) {
  if (this.keepAliveInclude.indexOf(componentName) === -1) { // 目标页面,自动 include
    this.keepAliveInclude.push(componentName)
  }
}
 
let deltaIds = []
 
function removeKeepAliveInclude (componentNameOrDelta) {
  if (typeof componentNameOrDelta === 'number') {
    deltaIds = this.keepAliveInclude.splice(-(componentNameOrDelta - 1)).map(name => {
      return parseInt(name.split('-').pop())
    })
  } else {
    const index = this.keepAliveInclude.indexOf(componentNameOrDelta)
    if (index !== -1) {
      this.keepAliveInclude.splice(index, 1)
    }
  }
}
 
let positionStore = Object.create(null)
 
export function getTabBarScrollPosition (id) {
  return positionStore[id]
}
 
function saveTabBarScrollPosition (id) {
  positionStore[id] = {
    x: window.pageXOffset,
    y: window.pageYOffset
  }
}
 
function switchTab (routes, to, from) {
  if (
    to &&
    from &&
    to.meta.isTabBar &&
    from.meta.isTabBar
  ) { // tabbar 跳 tabbar
    saveTabBarScrollPosition(from.params.__id__)
  }
  // 关闭非 tabBar 页面
  const pages = getCurrentPages()
  for (let i = pages.length - 1; i >= 0; i--) {
    const pageVm = pages[i]
    const meta = pageVm.$page.meta
    if (!meta.isTabBar) {
      removeKeepAliveInclude.call(this, meta.name + '-' + pageVm.$page.id)
      callPageHook(pageVm, 'onUnload')
    }
  }
}
 
function reLaunch (toName) {
  __uniConfig.reLaunch = (__uniConfig.reLaunch || 1) + 1
  // 关闭所有页面
  const pages = getCurrentPages(true)
  for (let i = pages.length - 1; i >= 0; i--) {
    callPageHook(pages[i], 'onUnload')
    // 重新reLaunch至首页可能会被keepAlive,先手动强制destroy
    pages[i].$destroy()
  }
  this.keepAliveInclude = []
  // 清空 positionStore
  positionStore = Object.create(null)
}
 
let currentPages = []
 
function beforeEach (to, from, next, routes) {
  currentPages = getCurrentPages(true) // 每次 beforeEach 时获取当前currentPages,因为 afterEach 之后,获取不到上一个 page 了,导致无法调用 onUnload
  const fromId = from.params.__id__
  const toId = to.params.__id__
  const toName = to.meta.name + '-' + toId
  if (toId === fromId && to.type !== 'reLaunch') { // 相同页面阻止
    // 处理外部修改 history 导致卡在当前页面的问题
    if (to.fullPath !== from.fullPath) {
      addKeepAliveInclude.call(this, toName)
      next()
    } else {
      next(false)
    }
  } else if (to.meta.id && to.meta.id !== toId) { // id 不妥,replace跳转
    next({
      path: to.path,
      replace: true
    })
  } else {
    const fromName = from.meta.name + '-' + fromId
 
    switch (to.type) {
      case 'navigateTo':
        break
      case 'redirectTo':
        // 关闭前一个页面
        removeKeepAliveInclude.call(this, fromName)
        if (from.meta) {
          if (from.meta.isQuit) { // 如果 redirectTo 的前一个页面是 quit 类型,则新打开的页面也是 quit
            to.meta.isQuit = true
            to.meta.isEntry = !!from.meta.isEntry
          }
          // 小程序没有这个逻辑,当时为何加了保留并更新 tabBar 的逻辑?
          // if (from.meta.isTabBar) { // 如果是 tabBar,需要更新系统组件 tabBar 内的 list 数据
          //   to.meta.isTabBar = true
          //   to.meta.tabBarIndex = from.meta.tabBarIndex
          //   const appVm = getApp().$children[0]
          //   appVm.$set(appVm.tabBar.list[to.meta.tabBarIndex], 'pagePath', to.meta.pagePath)
          // }
        }
 
        break
      case 'switchTab':
        switchTab.call(this, routes, to, from)
        break
      case 'reLaunch':
        reLaunch.call(this, toName)
        to.meta.isQuit = true // reLaunch后,该页面为 quit 类型
        break
      default:
        // 后退或非 API 访问
        if (fromId && fromId > toId) { // back
          removeKeepAliveInclude.call(this, fromName)
          if (this.$router._$delta > 1) {
            removeKeepAliveInclude.call(this, this.$router._$delta)
          }
        }
        break
    }
 
    if (to.type !== 'reLaunch' && to.type !== 'redirectTo' && from.meta.id) { // 如果不是 reLaunch、redirectTo,且 meta 指定了 id
      addKeepAliveInclude.call(this, fromName)
    }
    // if (to.type !== 'reLaunch') { // TODO 如果 reLaunch,1.keepAlive的话,无法触发页面生命周期,并刷新页面,2.不 keepAlive 的话,页面状态无法再次保留,且 routeView 的 cache 有问题
    addKeepAliveInclude.call(this, toName)
    // }
    if (process.env.NODE_ENV !== 'production') {
      console.debug(`Core:keepAliveInclude=${JSON.stringify(this.keepAliveInclude)}`)
    }
    /* eslint-disable no-undef */
    if (__PLATFORM__ === 'h5') {
      if (to.meta && to.meta.name) {
        document.body.className = 'uni-body ' + to.meta.name
        const nvueDirKey = 'nvue-dir-' + __uniConfig.nvue['flex-direction']
        if (to.meta.isNVue) {
          document.body.setAttribute('nvue', '')
          document.body.setAttribute(nvueDirKey, '')
        } else {
          document.body.removeAttribute('nvue')
          document.body.removeAttribute(nvueDirKey)
        }
      }
    }
 
    next()
  }
}
 
function afterEach (to, from) {
  const fromId = from.params.__id__
  const toId = to.params.__id__
  let fromVm
  // 使用 beforeEach 时的 pages
  if (from.meta.isSet) {
    fromVm = currentPages.find(pageVm => pageVm.$page.meta.pagePath === from.meta.pagePath)
  } else {
    fromVm = currentPages.find(pageVm => pageVm.$page.id === fromId)
  }
 
  function unloadPage (vm) {
    if (vm) {
      callPageHook(vm, 'onUnload')
      const index = currentPages.indexOf(vm)
      if (index >= 0) {
        currentPages.splice(index, 1)
      }
    }
  }
 
  switch (to.type) {
    case 'navigateTo': // 前一个页面触发 onHide
      fromVm && callPageHook(fromVm, 'onHide')
      break
    case 'redirectTo': // 前一个页面触发 onUnload
      unloadPage(fromVm)
      break
    case 'switchTab':
      if (from.meta.isTabBar) { // 前一个页面是 tabBar 触发 onHide,非 tabBar 页面在 beforeEach 中已触发 onUnload
        fromVm && callPageHook(fromVm, 'onHide')
      }
      break
    case 'reLaunch':
      break
    default:
      if (fromId && fromId > toId) { // history back
        unloadPage(fromVm)
        if (this.$router._$delta > 1) {
          deltaIds.reverse().forEach(deltaId => {
            const pageVm = currentPages.find(pageVm => pageVm.$page.id === deltaId)
            unloadPage(pageVm)
          })
        }
      }
      break
  }
 
  delete this.$router._$delta
  deltaIds.length = 0
 
  if (to.type !== 'reLaunch') { // 因为 reLaunch 会重置 id,故不触发 onShow,switchTab 在 beforeRouteEnter 中触发
    // 直接获取所有 pages,getCurrentPages 正常情况下仅返回页面栈内,传 true 则返回所有已存在(主要是 tabBar 页面)
    const pages = getCurrentPages(true)
    let toVm
    // 使用最新的 pages
    if (to.meta.isSet) {
      toVm = pages.find(pageVm => pageVm.$page.meta.pagePath === to.meta.pagePath)
    } else {
      toVm = pages.find(pageVm => pageVm.$page.id === toId)
    }
    if (toVm) { // 目标页面若已存在,则触发 onShow
      // 延迟执行 onShow,防止与 UniServiceJSBridge.emit('onHidePopup') 冲突。
      setTimeout(function () {
        if (__PLATFORM__ === 'h5') {
          UniServiceJSBridge.emit('onNavigationBarChange', toVm.$parent.$parent.navigationBar)
        }
        callPageHook(toVm, 'onShow')
      }, 0)
      if (__PLATFORM__ === 'h5') {
        document.title = toVm.$parent.$parent.navigationBar.titleText
      }
    }
  }
}
export default function initRouterGuard (appVm, routes) {
  // 处理keepAliveInclude
  appVm.$router.beforeEach(function (to, from, next) {
    beforeEach.call(appVm, to, from, next, routes)
  })
  // 处理前进时的 onUnload,onHide 和后退时的 onShow
  appVm.$router.afterEach(function (to, from) {
    afterEach.call(appVm, to, from)
  })
}