cwg-page-wrapper.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <template>
  2. <view :class="['page-wrapper', { dark: isDark }]">
  3. <cwg-match-media :max-width="991" v-if="!isLoginPage">
  4. <cwg-pc-header @open-right-drawer="openRightDrawer" @open-left-drawer="openLeftDrawer" class="header-box"
  5. :sidebarVisible="sidebarVisible" />
  6. <view class="sidebar-mask mask-visible" v-if="sidebarVisible" @click="openLeftDrawer">
  7. </view>
  8. <view class="fixed"></view>
  9. <cwg-header v-if=pageTitle class="custom-header" :title="pageTitle" />
  10. </cwg-match-media>
  11. <cwg-language style="width: 0;display: none;" />
  12. <cwg-progress />
  13. <cwg-confirm-popup />
  14. <view class="page-content" :style="{ backgroundColor: bgColor }">
  15. <cwg-match-media :max-width="991" v-if="!isLoginPage">
  16. <view class="left-sidebar" :class="{ 'sidebar-visible': sidebarVisible }">
  17. <cwg-sidebar />
  18. </view>
  19. <view class="sidebar-mask" v-if="sidebarVisible" @click="openLeftDrawer" :style="{
  20. opacity: maskVisible ? 1 : 0,
  21. transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
  22. }">
  23. </view>
  24. </cwg-match-media>
  25. <view class="content-info">
  26. <!-- 完善信息提示,-->
  27. <PrefectInfo v-if="!isLoginPage" />
  28. <!-- 申请成为ib,-->
  29. <IbInfo ref="ibRef" v-if="!isLoginPage" />
  30. <view class="content-wrapper" :class="{ 'content-wrapper-padding': isContentPadding }">
  31. <!-- <cwg-header /> -->
  32. <transition name="fade" mode="out-in">
  33. <view :key="$route.path">
  34. <slot />
  35. </view>
  36. </transition>
  37. <cwg-custom-footer />
  38. </view>
  39. </view>
  40. <view :style="{ height: isTabBarPage ? '60px' : '0px' }" />
  41. </view>
  42. </view>
  43. </template>
  44. <script setup lang="ts">
  45. import { ref, computed, onMounted, onUnmounted } from 'vue'
  46. import { onLoad, onShow } from '@dcloudio/uni-app'
  47. import useRoute, { updateRoute } from '@/hooks/useRoute'
  48. import useRouter from '@/hooks/useRouter'
  49. import useUserStore from '@/stores/use-user-store'
  50. import { userApi } from '@/api/user'
  51. import useGlobalStore from '@/stores/use-global-store'
  52. import PrefectInfo from '@/components/PrefectInfo.vue'
  53. import IbInfo from '@/components/IbInfo.vue'
  54. const globalStore = useGlobalStore()
  55. const router = useRouter()
  56. const userStore = useUserStore()
  57. const props = defineProps({
  58. // 是否固定顶部导航栏
  59. isHeaderFixed: {
  60. type: Boolean,
  61. default: false,
  62. },
  63. // 是否登录页,登录页不显示侧边栏和顶部导航,注册忘记密码等页面
  64. isLoginPage: {
  65. type: Boolean,
  66. default: false,
  67. },
  68. // 是否含有padding 默认有
  69. isContentPadding: {
  70. type: Boolean,
  71. default: true,
  72. },
  73. // 主内容背景颜色
  74. bgColor: {
  75. type: String,
  76. default: '',
  77. },
  78. // 顶部导航栏标题
  79. pageTitle: {
  80. type: String,
  81. default: '',
  82. },
  83. })
  84. const isDark = computed(() => globalStore.theme === 'dark')
  85. const isTabBarPage = ref(false)
  86. const rightDrawerRef = ref<any>(null)
  87. const noticeDrawerRef = ref<any>(null)
  88. const ibRef = ref<any>(null)
  89. // 事件处理函数
  90. const handleOpenIb = () => {
  91. if (ibRef.value) ibRef.value?.getInfo()
  92. }
  93. const handleOpenNoticeDrawer = () => {
  94. noticeDrawerRef.value?.open()
  95. }
  96. const handleOpenRightDrawer = () => {
  97. openRightDrawer()
  98. }
  99. onMounted(() => {
  100. // 只在组件挂载时注册事件监听器
  101. uni.$once('open-ib', handleOpenIb)
  102. uni.$on('open-right-drawer', handleOpenRightDrawer)
  103. })
  104. onUnmounted(() => {
  105. // 在组件销毁时移除事件监听器
  106. uni.$off('open-ib', handleOpenIb)
  107. uni.$off('open-right-drawer', handleOpenRightDrawer)
  108. })
  109. function openRightDrawer() {
  110. rightDrawerRef.value?.open()
  111. }
  112. const sidebarVisible = ref(false)
  113. const maskVisible = ref(false)
  114. function openLeftDrawer() {
  115. if (sidebarVisible.value) {
  116. // 关闭时先隐藏遮罩层
  117. maskVisible.value = false
  118. // 等待遮罩层动画结束后再隐藏侧边栏
  119. setTimeout(() => {
  120. sidebarVisible.value = false
  121. }, 200)
  122. } else {
  123. // 打开时先显示侧边栏
  124. sidebarVisible.value = true
  125. // 等待侧边栏动画结束后再显示遮罩层
  126. setTimeout(() => {
  127. maskVisible.value = true
  128. }, 200)
  129. }
  130. }
  131. function handleDrawerNavigate(path: string) {
  132. router.push(path)
  133. }
  134. async function handleDrawerLogout() {
  135. try {
  136. const res = await userApi.logout()
  137. if (res.code === 200) {
  138. userStore.clearUserInfo()
  139. router.push('/pages/login/index')
  140. }
  141. } catch (error) {
  142. userStore.clearUserInfo()
  143. router.push('/pages/login/index')
  144. }
  145. }
  146. onLoad(() => {
  147. updateRoute()
  148. })
  149. onShow(() => {
  150. updateRoute()
  151. })
  152. </script>
  153. <style lang="scss" scoped>
  154. @import "@/uni.scss";
  155. .page-wrapper {
  156. height: calc(100vh - 56px);
  157. }
  158. .header-box {
  159. width: 100%;
  160. height: 56px;
  161. }
  162. .page-content {
  163. width: 100%;
  164. height: calc(100vh - 56px);
  165. overflow: hidden;
  166. display: flex;
  167. flex-direction: column;
  168. position: relative;
  169. box-sizing: border-box;
  170. .content-info {
  171. height: calc(100vh - 56px);
  172. overflow: auto;
  173. }
  174. }
  175. .left-sidebar {
  176. width: 0;
  177. overflow: hidden;
  178. position: absolute;
  179. left: 0;
  180. top: 0;
  181. z-index: 1000;
  182. background-color: #fff;
  183. transition: width 81ms cubic-bezier(0.4, 0, 0.2, 1);
  184. }
  185. .sidebar-mask {
  186. position: absolute;
  187. left: 0;
  188. top: 0;
  189. width: 100vw;
  190. height: calc(100vh - 56px);
  191. background-color: rgba(0, 0, 0, 0.2);
  192. z-index: 101;
  193. }
  194. .mask-visible {
  195. background-color: rgba(0, 0, 0, 0);
  196. width: 100vw;
  197. height: 56px;
  198. }
  199. .sidebar-visible {
  200. width: px2rpx(280);
  201. }
  202. .content-wrapper {
  203. max-width: px2rpx(1224);
  204. width: 100%;
  205. margin: 0 auto;
  206. padding-top: px2rpx(20);
  207. box-sizing: border-box;
  208. display: flex;
  209. flex-direction: column;
  210. justify-content: space-between;
  211. min-height: calc(100vh - 56px);
  212. }
  213. .fixed {
  214. // position: fixed;
  215. // top: 0;
  216. // left: 0;
  217. width: 100%;
  218. height: 56px;
  219. background-color: var(--color-white);
  220. z-index: 9;
  221. }
  222. .custom-header {
  223. background-color: var(--color-white);
  224. padding: 0 px2rpx(15);
  225. position: fixed;
  226. top: 56px;
  227. left: 0;
  228. }
  229. @media screen and (max-width: 1504px) {
  230. .content-wrapper-padding {
  231. padding: px2rpx(16) px2rpx(16) 0 px2rpx(16);
  232. }
  233. }
  234. @media screen and (max-width: 991px) {
  235. .content-wrapper-padding {
  236. padding: px2rpx(16) px2rpx(16) 0 px2rpx(16);
  237. }
  238. .page-wrapper {
  239. height: 100vh;
  240. }
  241. }
  242. .mobile-menu-btn {
  243. width: px2rpx(64);
  244. height: px2rpx(64);
  245. display: flex;
  246. align-items: center;
  247. justify-content: center;
  248. }
  249. /* 页面切换动画 */
  250. .fade-enter-active,
  251. .fade-leave-active {
  252. transition: opacity 0.2s ease;
  253. }
  254. .fade-enter-from,
  255. .fade-leave-to {
  256. opacity: 0;
  257. }
  258. /* 侧边栏动画优化 */
  259. .left-sidebar {
  260. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  261. }
  262. .sidebar-mask {
  263. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  264. }
  265. /* 内容区域动画 */
  266. .content-wrapper {
  267. animation: slideIn 0.2s ease-out;
  268. }
  269. @keyframes slideIn {
  270. from {
  271. transform: translateY(20px);
  272. opacity: 0;
  273. }
  274. to {
  275. transform: translateY(0);
  276. opacity: 1;
  277. }
  278. }
  279. </style>