workbench.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. <script setup>
  2. import { computed, ref } from 'vue'
  3. import { onShow } from '@dcloudio/uni-app'
  4. import { useUserStore } from '@/stores/user'
  5. import { WORKBENCH_MODULES } from '@/config/workbench-menus'
  6. import { PAGE_PATHS, WORKBENCH_ROUTE_MAP } from '@/config/api'
  7. import { filterWorkbenchModules } from '@/utils/workbenchMenuFilter'
  8. import { getSafeAreaStyle } from '@/utils/safeArea'
  9. const safeAreaStyle = getSafeAreaStyle()
  10. const userStore = useUserStore()
  11. const modules = computed(() =>
  12. filterWorkbenchModules(WORKBENCH_MODULES, {
  13. menuAll: userStore.menuAll,
  14. menuCodes: userStore.menuCodes
  15. })
  16. )
  17. const loggingOut = ref(false)
  18. const displayName = computed(() => {
  19. const info = userStore.userInfo
  20. if (!info) return '未登录'
  21. return info.realName || info.username || '用户'
  22. })
  23. /** 三列宫格补齐空白格,避免末行空位露出网格底色 */
  24. const menuPadCount = (len) => {
  25. const rem = Number(len) % 3
  26. return rem === 0 ? 0 : 3 - rem
  27. }
  28. onShow(async () => {
  29. userStore.checkLoginStatus()
  30. if (!userStore.isLoggedIn) {
  31. uni.reLaunch({ url: PAGE_PATHS.login })
  32. return
  33. }
  34. await userStore.fetchMenuCodes()
  35. })
  36. /** 含 web-view 的页用 redirectTo,避免真机 navigateTo:fail timeout 闪进又弹回工作台 */
  37. const isMpWebviewRoute = (url) => String(url || '').includes('/pages/common/mp-webview')
  38. const openPage = (url) => {
  39. const failToast = () => uni.showToast({ title: '打开失败,请重试', icon: 'none' })
  40. if (isMpWebviewRoute(url)) {
  41. const ret = uni.redirectTo({
  42. url,
  43. fail: (err) => {
  44. // 栈异常时再退到 reLaunch
  45. const msg = String((err && err.errMsg) || '')
  46. if (/fail/i.test(msg)) {
  47. uni.reLaunch({ url, fail: failToast })
  48. return
  49. }
  50. failToast()
  51. }
  52. })
  53. if (ret && typeof ret.catch === 'function') ret.catch(() => {})
  54. return
  55. }
  56. const fail = (err) => {
  57. const msg = String((err && err.errMsg) || '')
  58. if (/timeout/i.test(msg)) {
  59. uni.redirectTo({ url, fail: failToast })
  60. return
  61. }
  62. failToast()
  63. }
  64. const ret = uni.navigateTo({ url, fail })
  65. if (ret && typeof ret.catch === 'function') {
  66. ret.catch(() => {})
  67. }
  68. }
  69. const openMenu = (module, menu) => {
  70. if (!menu.enabled) {
  71. uni.showToast({ title: '本期暂未开放', icon: 'none' })
  72. return
  73. }
  74. const route = WORKBENCH_ROUTE_MAP[menu.code]
  75. if (route) {
  76. openPage(route)
  77. return
  78. }
  79. const title = encodeURIComponent(menu.name || '')
  80. const moduleTitle = encodeURIComponent(module.title || '')
  81. const code = encodeURIComponent(menu.code || '')
  82. openPage(`${PAGE_PATHS.bizPlaceholder}?title=${title}&module=${moduleTitle}&code=${code}`)
  83. }
  84. const handleLogout = async () => {
  85. if (loggingOut.value) return
  86. loggingOut.value = true
  87. try {
  88. await userStore.logout()
  89. } finally {
  90. loggingOut.value = false
  91. }
  92. }
  93. </script>
  94. <template>
  95. <view class="mp-page mp-page--tab">
  96. <view class="mp-header mp-header--workbench" :style="safeAreaStyle">
  97. <view class="mp-header__body">
  98. <view class="mp-header__titles">
  99. <text class="mp-header__title">山峡化工工作台</text>
  100. </view>
  101. <view class="mp-header__user-stack">
  102. <text class="mp-header__user-line">{{ displayName }},欢迎您</text>
  103. <text
  104. class="mp-header__logout"
  105. :class="{ 'mp-header__logout--disabled': loggingOut }"
  106. @click="handleLogout"
  107. >
  108. {{ loggingOut ? '退出中…' : '退出登录' }}
  109. </text>
  110. </view>
  111. </view>
  112. </view>
  113. <view class="mp-section">
  114. <view
  115. v-for="mod in modules"
  116. :key="mod.key"
  117. class="mp-module-block"
  118. :class="'mp-module-block--' + mod.key"
  119. >
  120. <view class="mp-module-block__head">
  121. <text class="mp-module-block__icon">{{ mod.iconText }}</text>
  122. <view class="mp-module-block__title-wrap">
  123. <text class="mp-module-block__title">{{ mod.title }}</text>
  124. <text class="mp-module-block__desc">{{ mod.desc }}</text>
  125. </view>
  126. </view>
  127. <view class="mp-menu-grid">
  128. <view
  129. v-for="(menu, idx) in mod.menus"
  130. :key="menu.code || menu.name + idx"
  131. class="mp-menu-card"
  132. :class="{ 'mp-menu-card--disabled': !menu.enabled }"
  133. @click="openMenu(mod, menu)"
  134. >
  135. <view class="mp-menu-card__icon">{{ menu.icon || '' }}</view>
  136. <text class="mp-menu-card__name">{{ menu.name }}</text>
  137. </view>
  138. <view
  139. v-for="i in menuPadCount(mod.menus.length)"
  140. :key="'pad-' + mod.key + '-' + i"
  141. class="mp-menu-card mp-menu-card--pad"
  142. />
  143. </view>
  144. </view>
  145. </view>
  146. <app-env-badge />
  147. </view>
  148. </template>