index.vue 11 KB

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