wx-open-launch-weapp封装跳转小程序弹出组件以及样式问题

1,777 阅读3分钟

背景

公司的广告位功能,目前有两种广告位方式

  • 点击广告位,弹出微信小程序弹窗
  • 点击广告位,直接跳转到接口返回的url地址

在封装过程中,wx-open-launch-weapp 这个标签的样式不好调整,所以需要封装一个公共的广告位组件,满足上面两种情况,并且保证 wx-open-launch-weapp 的样式能盖住子元素来触发小程序弹窗

思路

采用子绝父相,即:

container.div 为点击唤起小程序的区域(相对定位),而 content 则是该区域的展示内容,wx-open-launch-weapp 则是占满唤起区域(绝对定位)

首先是唤起微信小程序跳转弹窗的组件 ToWxMpV2

// ToWxMpV2.tsx
import React, { FC, useEffect, useState } from 'react';

const _ = require('loadsh');

export type WxMpConfig = {
  appid: string;
  path: string;
  username?: string;
  envVersion?: string;
};

type Props = {
  config?: WxMpConfig;
  onLaunch?: () => void;
};

// 跳转小程序配置,默认跳转为书链作业小程序首页
// 使用跳转组件时,请先配置configWxData(),获取openTagList
const defaultConfig = {
  appid: 'wx8a969537f52cd9fa',
  path: 'pages/home/home',
  username: 'gh_2fd9ea16574d',
  envVersion: 'release',
};

export const ToWxMpV2: FC<Props> = (props) => {
  const [id] = useState(_.uniqueId('launch-btn'));
  const { children, config = defaultConfig, onLaunch } = props;

  useEffect(() => {
    const btn = document.getElementById(id);
    if (btn) {
      btn.addEventListener('launch', () => {
        onLaunch?.();
      });
    }
  }, []);

  return (
    <div className="relative">
      {/* 内容区域 */}
      {children}
      {/* wx标签子绝父相覆盖children区域 */}
      <wx-open-launch-weapp
        id={id}
        path={config.path}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
        }}
        appid={config.appid}
        username={config.username}
        env-version={config.envVersion}
      >
        <script type="text/wxtag-template">
          {/* 点击唤醒小程序区域 */}
          <div
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: '100%',
              opacity: 0,
            }}
          />
        </script>
      </wx-open-launch-weapp>
    </div>
  );
};

wx-open-launch-weapp里面都给整上绝对定位,然后宽高100%,保证能盖住 container

然后,再根据业务去封装一个 AdvertiseSpace 广告位组件,该组件逻辑是

  • 调接口,记录数据
  • 根据数据中的 actionType 字段,判断是跳转微信小程序,还是直接跳转href
  • 条件渲染不同的子组件
import {
  ActionType,
  BannerDTO,
  IBannerListParamsDTO,
  useBannerList,
} from '@common/hooks/useBannerList';
import {
  isInBookLnApp,
  isMiniProgram,
  isMobile,
  ytLogger,
} from '@common/utils/util';
import { useMount } from 'ahooks';
import React, { ReactNode, memo, useCallback, useMemo, useState } from 'react';
import { WxMpConfig } from '../toWxMp';
import { ToWxMpV2 } from '../toWxMpV2';

export enum AdvertiseSpaceType {
  /**
   * 图片广告位
   */
  Image = 'image',
  /**
   * 按钮广告位
   */
  Button = 'button',
}

export type RenderChildrenParams = {
  bannerData: BannerDTO;
  cb?: () => void;
};

export type AdvertiseSpaceProps = {
  /**
   * 广告位接口参数
   */
  fetchBannerParams: IBannerListParamsDTO;
  /**
   * 渲染子元素
   * @bannerData 广告位数据
   */
  renderChildren: (bannerData: BannerDTO) => ReactNode;
};

const AdvertiseSpace = memo(
  ({ fetchBannerParams, renderChildren }: AdvertiseSpaceProps) => {
    const { fetchBannerList } = useBannerList();
    //广告位数据
    const [bannerData, setBannerData] = useState<BannerDTO>({
      banners: [],
      hide: [],
    });
    //微信配置项
    const [wxMpConfig, setWxMpConfig] = useState<WxMpConfig | undefined>(
      undefined,
    );

    // 跳转方式
    const actionType: ActionType = useMemo(() => {
      return bannerData?.banners?.[0]?.actionType;
    }, [bannerData]);

    // 点击href跳转
    const handleClickBanner = useCallback(() => {
        //...业务逻辑
    }, [bannerData]);

    // 渲染dom
    const renderContent = useMemo(() => {
      switch (actionType) {
        // 跳转微信小程序时,渲染 toWxMpV2
        case ActionType.ToWx:
          return (
            <ToWxMpV2
              config={wxMpConfig}
            >
              {renderChildren(bannerData)}
            </ToWxMpV2>
          );
        // 直接跳转 href 时,直接渲染即可
        case ActionType.Href:
          return (
            <div onClick={handleClickBanner}>{renderChildren(bannerData)}</div>
          );
        default:
          return null;
      }
    }, [
      actionType,
      bannerData,
      handleClickBanner,
      hideAdvertiseSpace,
      renderChildren,
      wxMpConfig,
    ]);

    // 获取 banner 广告位数据
    const queryBanner = async () => {
      const res = await fetchBannerList(fetchBannerParams);
      const { success, data } = res;
      if (success && !!data) {
        // 业务逻辑,记录数据
        setWxMpConfig({
           appid,
           path,
        });
        setBannerData(data);
      } else {
        setWxMpConfig(undefined);
      }
    };

    useMount(() => {
      queryBanner();
    });

    return renderContent;
  },
);

export default AdvertiseSpace;

然后去使用该组件:

// 比如,这里是渲染一个图片形式的广告位
const renderChildren = (bannerData: BannerDTO) => {
  const imgUrl = bannerData.banners[0].imgUrl;
  return (
    <div className="my-[12px]">
      <Img
        src={imgUrl}
        style={{
          width: '100%',
          height: '44px',
          objectFit: 'contain',
          borderRadius: '40px',
        }}
        alt=""
      />
    </div>
  );
};

<AdvertiseSpace
  fetchBannerParams={params}
  renderChildren={renderChildren}
/>

效果

image.png

可以看出,wx-open-launch-weapp 已经盖住了 img,并且大小其实和 img 一样的