uniapp所有页面使用某个组件

2,383 阅读2分钟

一、背景

uniapp项目中每个页面顶部都要加上公告滚动条,如下图:

image.png

实现方案:

  • H5:如果路由使用VueRouter可以直接在APP.vue组件里直接添加公告滚动条组件;如果没有使用VueRouter则在页面挂载后使用insetBefore()方法插入组件。
  • 小程序:由于小程序没有开放根标签,没有办法在根标签下追加全局标签,所以需要在编译时加入。

二、实现

1、创建公告滚动条组件

/components/NoticeBar.vue: (以下为示例,具体的滚动条组件看文末)

<template>
  <view>
    NoticeBar
  </view>
</template>

2、将设置为全局组件

main.ts中将组件挂载为全局组件,这样使用的时候就不用引入啦~:

import NoticeBar from '@/components/NoticeBar.vue'
Vue.component('NoticeBar', NoticeBar)

3、实现H5的全局公告滚动条

(1)有使用VueRouter的方法:

原本是使用在App.vue中使用NoticeBar组件并引入页面组件<router-view>的方法,虽然组件和页面都能正常显示,但是我们的uniapp项目中没有使用VueRouter,所以页面回退的时候会有问题,所有回退都会跳转到首页。

App.vue中:

<template>
  <view v-if="isH5">
    <NoticeBar />
    <router-view></router-view>
  </view>
</template>

(2)没有使用VueRouter的方法:

后面改成了最原始的方法:在页面挂载后使用insetBefore()方法插入组件。

main.ts中:

// h5所有页面挂上noticeBar组件
const initNoticeBar = () => {
  const app = document.getElementsByTagName('uni-app')['0']
  const page = document.getElementsByTagName('uni-page')['0']
  console.log('获取app和page', document, app, page, location)
  app.insertBefore(new noticeBar().$mount().$el, page)
}

...
mountApp()
// #ifdef H5
initNoticeBar()
// #endif

4、实现小程序的全局公告滚动条

小程序的相对麻烦一些,因为小程序没有根组件,所以需要每个页面都去插入公告滚动条组件,由于我们小程序有几十个页面,一个个页面去加累死人,可以在编译时加入遍历每个页面template,将组件插入第一个元素里。具体做法如下:

vue.config.js中加入如下代码:

module.exports = {
  chainWebpack(config) {
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap((options) => {
        const compile = options.compiler.compile
        options.compiler.compile = (template, info) => {
          // 获取所有页面(排除所有组件),插入<NoticeBar />组件
          if (
            info.resourcePath?.match(/^pages/) &&
            !info.resourcePath?.match(/\/components\//)
          ) {
            template = template.trim()
            template = template.replace(
              /^<[\d\D]+?>/g,
              (_) => `${_}<NoticeBar />
              `
            )
          }
          return compile(template, info)
        }
        return options
      })
  }
}

三、公告滚动条组件

<template>
  <view class="wrapper" v-if="showNoticeBar">
    <view class="notice-bar">
      <view class="notice-title">
        <image
          class="notice-icon"
          src="https://pubfile.bluemoon.com.cn/group1/M00/60/76/wKgwcGPyz-OAETXkAAAD3-2zYCI641.png"
        />
        <text class="notice-title__txt">公告</text>
      </view>
      <view class="notice-content">
        <view
          class="notice-content__txt-wrapper"
          :style="{
            'animation-duration':
              (animationDuration ? animationDuration : 10) + 's'
          }"
        >
          <view
            class="notice-content__txt"
            v-for="(item, index) in list"
            :key="index"
            @click="toDetail(item)"
            v-html="getTxt(item)"
          >
          </view>
        </view>
      </view>
      <view class="notice-close" @click="closeNoticeBar">
        <image
          class="notice-icon"
          src="https://pubfile.bluemoon.com.cn/group1/M00/60/78/wKgwb2Py0N-ACC2nAAABwyrvzsg318.png"
        />
      </view>
    </view>
  </view>
</template>
<script lang="ts" setup>
import {
  ref,
  computed,
  onMounted,
  getCurrentInstance,
  watchEffect,
  nextTick
} from '@vue/composition-api'
import { getMessageRemind } from '@/apis/message'
import { isMP } from '@/utils/index'
import { useNoticeBar } from '@/stores/noticeBar'
import { storeToRefs } from 'pinia'
const noticeBarStore = useNoticeBar()
const { showNoticeBar } = storeToRefs(noticeBarStore)
interface Notice {
  content: string
  currentGiveThumbFlag: boolean
  giveThumbTotal: number
  noticeState: number
  noticeType: number
  noticeTypeDesc: string
  releaseChannel: string
  releaseCycle: number
  releaseTime: string
  summary: string
  sysNoticeId: string
  title: string
  triggerMode: number
  updateTime: string
  msgId: string
}

const instance = getCurrentInstance()?.proxy
const noticeBannerList = ref<Notice[]>([])
const list = computed(() => {
  return noticeBannerList.value.concat(noticeBannerList.value)
})

// 数据变化时,重新计算滚动时间
watchEffect(async () => {
  if (noticeBannerList.value.length > 0) {
    await nextTick()
    setAnimationDuration()
  }
})

// 关闭滚动条
const closeNoticeBar = () => {
  noticeBarStore.setShowNoticeBar(false)
}

// 获取公告数据
const queryMessage = async () => {
  const clientType = isMP ? 2 : 3
  const { data, isSuccess } = await getMessageRemind({ clientType })
  if (!isSuccess) return
  // 公告横幅
  noticeBannerList.value =
    data.unreadSysNoticeDetailList?.filter((item: Notice) => {
      return item.releaseChannel.split(',').includes('4')
    }) || []
  if (noticeBannerList.value.length > 0) {
    noticeBarStore.setShowNoticeBar(true)
  } else {
    noticeBarStore.setShowNoticeBar(false)
  }
}
// 处理公告数据
const getTxt = (item: Notice) => {
  const colon = item.title && item.summary ? ':' : ''
  const txt = item.title + colon + item.summary
  var reg = /<.*?>/g
  const res = txt.replace(reg, '')
  return res
}

// 根据文案宽度 动态设置滚动时间
const animationDuration = ref(10)
const setAnimationDuration = () => {
  uni
    .createSelectorQuery()
    .in(instance)
    .select('.notice-content__txt-wrapper')
    .boundingClientRect((data: any) => {
      const speed = 100
      console.log('data', data, data.width / speed)
      if (data.width) animationDuration.value = data.width / speed
    })
    .exec()
}

// 跳转详情页
const toDetail = (item: Notice) => {
  uni.navigateTo({
    url: `/pages/notice/index?sysNoticeId=${item.sysNoticeId}&msgId=${item.msgId}`
  })
}

onMounted(async () => {
  queryMessage()
})

// defineExpose({ open })
</script>
<style lang="scss" scoped>
.wrapper {
  height: 80rpx;
}
.notice {
  &-bar {
    @include flex();
    @include jc-between();
    @include ai-center();
    width: 100vw;
    height: 80rpx;
    padding: 22rpx 16rpx 22rpx 32rpx;
    background: #fef0ee;
    box-sizing: border-box;
    font-size: 28rpx;
    color: #d14a38;
    position: fixed;
    z-index: 9;
    top: 0;
    left: 0;
    /* #ifdef H5 */
    top: 88rpx;
    /* #endif */
  }
  &-title {
    @include flex();
    @include ai-center();
    &__txt {
      margin-left: 8rpx;
      font-weight: bold;
    }
  }
  &-icon {
    width: 36rpx;
    height: 36rpx;
  }
  &-content {
    @include flex-1();
    margin: 0 16rpx;
    overflow: hidden;
    white-space: nowrap;
    word-break: keep-all;
    &__txt {
      padding-right: 200rpx;
      display: inline-block;
      &-wrapper {
        display: inline-block;
        animation-name: u-loop-animation;
        animation-duration: 10s;
        animation-timing-function: linear;
        animation-iteration-count: infinite;
      }
    }
  }
}
@keyframes u-loop-animation {
  0% {
    transform: translate3d(0, 0, 0);
  }

  100% {
    transform: translate3d(-50%, 0, 0);
  }
}
</style>