index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <template>
  2. <view class="main-container">
  3. <!-- 主内容区域 - 使用 swiper 实现滑动切换 -->
  4. <swiper class="main-swiper" :current="currentIndex" @change="onSwiperChange" :circular="false" :duration="300">
  5. <!-- 动态渲染页面 -->
  6. <swiper-item v-for="(page, index) in pages" :key="index">
  7. <view class="page-container">
  8. <BjdmStatisticsPage v-if="page.key === 'mp_njdmHomep' && page.activated"
  9. v-show="currentIndex === index" :ref="el => setPageRef(el, index)" />
  10. <BzrdmPage v-else-if="page.key === 'bzrDm' && page.activated" v-show="currentIndex === index"
  11. :ref="el => setPageRef(el, index)" />
  12. <TodoPage v-else-if="page.key === 'todo' && page.activated" v-show="currentIndex === index"
  13. :ref="el => setPageRef(el, index)" />
  14. <MyPage v-else-if="page.key === 'my' && page.activated" v-show="currentIndex === index"
  15. :ref="el => setPageRef(el, index)" />
  16. </view>
  17. </swiper-item>
  18. </swiper>
  19. </view>
  20. </template>
  21. <script setup>
  22. import { ref, nextTick } from 'vue'
  23. import { onShow, onHide, onLoad, onUnload } from '@dcloudio/uni-app'
  24. import Icon from '@/components/icon/index.vue'
  25. // 引入各个页面组件
  26. import MyPage from '@/pages/my/index.vue'
  27. import TodoPage from '@/pages/todo/todo_list.vue'
  28. import BjdmStatisticsPage from '@/pages/statistics/bjdm_statistics.vue'
  29. import BzrdmPage from '@/pages/bjdm/bjdm_bzrDmHomep.vue'
  30. // 当前选中的页面索引
  31. const currentIndex = ref(0)
  32. // 上一个页面索引
  33. const prevIndex = ref(0)
  34. // 页面组件引用数组
  35. const pageRefs = ref([])
  36. // 页面配置(根据登录态与 syList 动态构建)
  37. const pages = ref([])
  38. /**
  39. * 设置页面组件引用
  40. */
  41. const setPageRef = (el, index) => {
  42. if (el) {
  43. pageRefs.value[index] = el
  44. }
  45. }
  46. /**
  47. * 触发子组件的生命周期
  48. */
  49. const triggerPageLifecycle = (pageIndex, lifecycle) => {
  50. const pageRef = pageRefs.value[pageIndex]
  51. if (pageRef && typeof pageRef[lifecycle] === 'function') {
  52. // console.log(`[Main] 触发页面 ${pages.value[pageIndex]?.title} 的 ${lifecycle}`)
  53. pageRef[lifecycle]()
  54. } else {
  55. try {
  56. console.warn('[Main] 生命周期调用失败: 未找到方法', {
  57. pageIndex,
  58. lifecycle,
  59. hasRef: !!pageRef,
  60. refKeys: pageRef ? Object.keys(pageRef) : []
  61. })
  62. } catch (e) { }
  63. }
  64. }
  65. // 在小程序环境中,组件渲染与 ref 赋值可能比 nextTick 更晚
  66. // 增加一个轻量重试机制,确保拿到子组件并调用其暴露的生命周期
  67. function ensureRefReady(pageIndex, lifecycle, tries = 0) {
  68. const pageRef = pageRefs.value[pageIndex]
  69. if (pageRef && typeof pageRef[lifecycle] === 'function') {
  70. // console.log(`[Main] ensureRefReady 命中 -> 调用 ${pages.value[pageIndex]?.title}.${lifecycle}`)
  71. pageRef[lifecycle]()
  72. return
  73. }
  74. if (tries >= 8) {
  75. console.warn('[Main] ensureRefReady 超过重试次数,放弃调用', {
  76. pageIndex,
  77. lifecycle,
  78. hasRef: !!pageRef,
  79. refKeys: pageRef ? Object.keys(pageRef) : []
  80. })
  81. return
  82. }
  83. setTimeout(() => ensureRefReady(pageIndex, lifecycle, tries + 1), 30)
  84. }
  85. /**
  86. * swiper 切换事件
  87. */
  88. const onSwiperChange = (e) => {
  89. const newIndex = e.detail.current
  90. const oldIndex = currentIndex.value
  91. // 触发前一个页面的 onHide
  92. if (oldIndex !== newIndex) {
  93. console.log(`[Main] 触发页面 ${oldIndex} 的 onHide`)
  94. triggerPageLifecycle(oldIndex, 'onHide')
  95. }
  96. // 更新当前页面索引
  97. prevIndex.value = oldIndex
  98. currentIndex.value = newIndex
  99. // 懒加载激活
  100. activateByIndex(currentIndex.value)
  101. // 触发新页面的 onShow(可能需要等待 ref 就绪)
  102. nextTick(() => {
  103. console.log(`[Main] 触发页面 ${newIndex} 的 onShow`)
  104. ensureRefReady(currentIndex.value, 'onShow')
  105. })
  106. console.log('切换到页面:', pages.value[currentIndex.value].title)
  107. }
  108. /**
  109. * 点击指示器切换页面
  110. */
  111. function switchToPage(index) {
  112. if (currentIndex.value !== index) {
  113. const oldIndex = currentIndex.value
  114. // 触发前一个页面的 onHide
  115. // console.log(`[Main] switchToPage 触发页面 ${oldIndex} 的 onHide`)
  116. triggerPageLifecycle(oldIndex, 'onHide')
  117. // 更新页面索引
  118. prevIndex.value = oldIndex
  119. currentIndex.value = index
  120. // 懒加载激活
  121. activateByIndex(currentIndex.value)
  122. // 触发新页面的 onShow(可能需要等待 ref 就绪)
  123. nextTick(() => {
  124. // console.log(`[Main] switchToPage 触发页面 ${index} 的 onShow`)
  125. ensureRefReady(currentIndex.value, 'onShow')
  126. })
  127. }
  128. }
  129. /**
  130. * 激活页面
  131. */
  132. function activateByIndex(pageIndex) {
  133. const page = pages.value[pageIndex]
  134. if (!page) return
  135. if (!page.activated) {
  136. // 标记为已激活,触发首次挂载
  137. page.activated = true
  138. // 首次激活后,等组件挂载完成再触发子组件 onLoad(自定义)
  139. nextTick(() => {
  140. ensureRefReady(pageIndex, 'onLoad')
  141. })
  142. }
  143. }
  144. /**
  145. * 是否已登录
  146. */
  147. function isLoggedIn() {
  148. try {
  149. const u = uni.getStorageSync('userInfo')
  150. if (!u) return false
  151. const info = typeof u === 'string' ? JSON.parse(u) : u
  152. return !!info?.yhsbToken
  153. } catch (e) {
  154. return false
  155. }
  156. }
  157. /**
  158. * 获取 syList(数组)
  159. */
  160. function getSyList() {
  161. try {
  162. const u = uni.getStorageSync('userInfo')
  163. const info = typeof u === 'string' ? JSON.parse(u) : u
  164. const raw = info?.syList || info?.sylist || '[]'
  165. // return ['statistics','bzrDmHomep']
  166. if (Array.isArray(raw)) return raw
  167. return JSON.parse(raw)
  168. } catch (e) {
  169. return []
  170. }
  171. }
  172. /**
  173. * 按登录态构建一级页
  174. * - 未登录:仅“我的”
  175. * - 已登录且 syList 含 bzrDmHomep:加入“班主任点名”在“我的”前
  176. */
  177. function buildPagesFromAuth() {
  178. const list = []
  179. const loggedIn = isLoggedIn()
  180. const sy = loggedIn ? getSyList() : []
  181. // 能力 → 页面配置映射(按需扩展)
  182. const capabilityMap = {
  183. mp_njdmHomep: {
  184. key: 'mp_njdmHomep',
  185. title: '点名统计',
  186. icon: 'icon-tongji',
  187. path: 'pages/statistics/bjdm_statistics',
  188. component: BjdmStatisticsPage,
  189. },
  190. mp_bzrdmHomep: {
  191. key: 'bzrDm',
  192. title: '班主任点名',
  193. icon: 'icon-dianming',
  194. path: 'pages/bjdm/bjdm_bzrDm',
  195. component: BzrdmPage,
  196. },
  197. todo: {
  198. key: 'todo',
  199. title: '待办',
  200. icon: 'icon-daiban',
  201. path: 'pages/todo/todo_list',
  202. component: TodoPage,
  203. },
  204. }
  205. if (loggedIn) {
  206. const added = new Set()
  207. sy.forEach(cap => {
  208. const conf = capabilityMap[cap]
  209. if (conf && !added.has(conf.key)) {
  210. list.push({ ...conf, activated: false })
  211. added.add(conf.key)
  212. }
  213. })
  214. }
  215. // 末尾永远追加“我的”
  216. list.push({
  217. key: 'my',
  218. title: '我的',
  219. icon: 'icon-wode',
  220. path: 'pages/my/index',
  221. component: MyPage,
  222. activated: false,
  223. })
  224. pages.value = list
  225. console.log('✅ 获取权限列表成功:', list)
  226. }
  227. // 主容器的生命周期
  228. onLoad((options) => {
  229. // 测试写死
  230. // options.sn = 'A100006B6256E6'
  231. // options.cardNo = 'E00401532101245F'
  232. if(options.cardNo == 'E004015316BE6182'){
  233. options.cardNo = 'E004015316BE61821'
  234. }
  235. // if (true) {
  236. if (typeof wmpf !== 'undefined') {
  237. console.log('WMPF环境')
  238. uni.reLaunch({
  239. url: `/pages/device/index?sn=${options.sn}&cardNo=${options.cardNo}`
  240. })
  241. } else {
  242. console.log('非WMPF环境')
  243. // console.log('主容器页面加载', options)
  244. // 先按登录态构建一级页
  245. buildPagesFromAuth()
  246. // 默认打开第一个页面;若提供 ?tab=<key> 则按 tab 定位,找不到则回退 0
  247. currentIndex.value = 0
  248. const tabKey = options?.tab
  249. if (tabKey) {
  250. const foundIndex = pages.value.findIndex(p => p.key === tabKey)
  251. currentIndex.value = foundIndex >= 0 ? foundIndex : 0
  252. }
  253. // 激活当前页(首次时会触发 onLoad)
  254. activateByIndex(currentIndex.value)
  255. // 监听登录事件,登录后重建一级页
  256. uni.$on('login', () => {
  257. const currentKey = pages.value[currentIndex.value]?.key
  258. buildPagesFromAuth()
  259. // 登录后默认定位到第一个;若需要可由 ?tab 决定
  260. currentIndex.value = 0
  261. activateByIndex(currentIndex.value)
  262. nextTick(() => {
  263. ensureRefReady(currentIndex.value, 'onShow')
  264. })
  265. })
  266. }
  267. })
  268. onShow(() => {
  269. // console.log('主容器页面显示')
  270. // 触发当前页面的 onShow(可能需要等待 ref 就绪)
  271. nextTick(() => {
  272. ensureRefReady(currentIndex.value, 'onShow')
  273. })
  274. })
  275. onHide(() => {
  276. // console.log('主容器页面隐藏')
  277. // 触发当前页面的 onHide
  278. triggerPageLifecycle(currentIndex.value, 'onHide')
  279. })
  280. onUnload(() => {
  281. // console.log('主容器页面卸载')
  282. // 触发所有页面的 onUnload
  283. pages.value.forEach((_, index) => {
  284. triggerPageLifecycle(index, 'onUnload')
  285. })
  286. })
  287. </script>
  288. <style lang="scss" scoped>
  289. .main-container {
  290. width: 100%;
  291. height: 100vh;
  292. display: flex;
  293. flex-direction: column;
  294. }
  295. .top-indicator {
  296. height: 100rpx;
  297. background: #ffffff;
  298. border-bottom: 1rpx solid #e6e6e6;
  299. display: flex;
  300. align-items: center;
  301. justify-content: space-around;
  302. padding: 0 20rpx;
  303. box-sizing: border-box;
  304. flex-shrink: 0;
  305. }
  306. .indicator-item {
  307. display: flex;
  308. flex-direction: column;
  309. align-items: center;
  310. justify-content: center;
  311. flex: 1;
  312. height: 100%;
  313. gap: 8rpx;
  314. text {
  315. font-size: 24rpx;
  316. transition: color 0.3s;
  317. }
  318. &.active {
  319. text {
  320. font-weight: bold;
  321. }
  322. }
  323. &:active {
  324. background-color: rgba(83, 156, 248, 0.1);
  325. }
  326. }
  327. .main-swiper {
  328. flex: 1;
  329. width: 100%;
  330. height: calc(100vh - 100rpx);
  331. }
  332. .page-container {
  333. width: 100%;
  334. height: 100%;
  335. overflow: hidden;
  336. }
  337. </style>