当前位置: 首页 > news >正文

打造地基: App拉起基础小程序容器

本节概要

从这一小节开始我们将基于上篇文章中搭建的开发环境,尝试实现 点击小程序 -> 拉起一个基础的小程序容器 的过程;

现在我们先来梳理一下拉起小程序的过程:

在这里插入图片描述

这里我们将创建三个类实现相应的功能:

AppManager

用于创建一个小程序App和关闭小程序的入口管理类

MiniApp

小程序的实例类,这个类将完成小程序的初始化和调度管理工作

Application

基础应用类,这个类主要是用来模拟实现客户端挂载小程序,管理小程序的推入推出;

在开始实现之前,我们先进行一些简单的改造: 在应用页面上创建一个根节点用于挂载小程序的页面

// src/App.vue<script lang="ts" setup>
+ import { ref, onMounted } from "vue";+ const miniWindow = ref<HTMLElement>();
</script><template>
+ <Teleport to="body">
+   <div class="mini-body absolute top-0 left-0 z-10" ref="miniWindow"></div>
+ </Teleport>
</template>

AppManager 小程序应用管理类

在实现小程序管理类 AppManager 之前,我们先定义下打开一个小程序的所需要的参数类型定义:

export interface AppInfo {appId: string; /* 小程序AppId */path: string;  /* 小程序页面path */name?: string; /* 小程序名称 */logo?: string; /* 小程序logo */scene?: number; /* 小程序打开的场景值: 这个是模拟的现在小程序打开的参数,我们这里实际没有用到 */[key: string]: any;
}

打开小程序,实际必须的参数主要是 appIdpath,在打开过程中,客户端将会根据小程序 appId 去接口服务获取小程序的详细信息,如 appNamelogo 等参数;当然还有拉起小程序的参数,一般我们从的 path 参数上去解析出上面带的 query 参数信息;我们简单整理下这个流程如下:

在这里插入图片描述

现在我们来实现小程序 AppManager 类,创建 openApp 方法

class AppManager {// 管理所有创建的小程序static appStack: MiniApp[] = [];/*** 打开小程序App* @param opts 小程序参数* @param wx 客户端应用管理类,主要用来实现小程序页面的最终挂载*/static async openApp(opts: AppInfo, wx: Application) {const { appId, path, scene } = opts;// 1. 解析 path 上的query 参数const { pagePath, query } = queryPath(path);// 2. 通过接口获取小程序的详细信息const { appName, logo } = await getMiniAppInfo(appId);// 3. 创建小程序App实例const miniApp = new MiniApp({appId,scene,logo,query,path: pagePath,name: appName,});this.appStack.push(miniApp);wx.presentView(miniApp);}/*** 关闭小程序App*/static closeApp(miniApp: MiniApp) {miniApp.parent?.dismissView({destroy: false,});}
} 

这里我们需要创建一个工具函数 queryPath 来解析 path 参数,拆分出真实的路径信息和参数数据

例如一个 path 参数的形式如下: pages/index/index?name=喵游

// src/utils/util.ts
export function queryPath(path: string) {const [pagePath, paramsStr] = path.split('?')[1];const result = {query: {},pagePath,};if (!paramsStr) {return result;}let paramList = paramsStr.split('&');paramList.forEach((param) => {let key = param.split('=')[0];let value = param.split('=')[1];result.query[key] = value;});return result;
}

关于通过服务接口获取小程序详细信息的逻辑,这里我们先简单实现,直接通过静态数据返回

// src/service/index.ts
const appInfo = {douyin: {appName: '抖音',logo: 'https://img.zcool.cn/community/0173a75b29b349a80121bbec24c9fd.jpg@1280w_1l_2o_100sh.jpg'},meituan: {appName: '美团',logo: 'https://s3plus.meituan.net/v1/mss_e2821d7f0cfe4ac1bf9202ecf9590e67/cdn-prod/file:9528bfdf/20201023%E7%94%A8%E6%88%B7%E6%9C%8D%E5%8A%A1logo/%E7%BE%8E%E5%9B%A2app.png'},jingdong: {appName: '京东',logo: 'https://ts1.cn.mm.bing.net/th/id/R-C.8e130498abf4685d15ecb977869a5a39?rik=%2f%2bLRdQM48y8y0A&riu=http%3a%2f%2fwww.xiue.cc%2fwp-content%2fuploads%2f2017%2f09%2fjd.jpg&ehk=hUzDTV9xjw%2flaGD5eZcKGl%2fN7UkzBSHRjo73I%2bMeVvo%3d&risl=&pid=ImgRaw&r=0'}
};export function getMiniAppInfo(appId: string) {return new Promise<MiniAppInfo>((resolve) => {resolve(appInfo[appId]);});
}

MiniApp 小程序实例类

在实现小程序类之前,我们先来创建下小程序页面的HTML模板,主要包括:

  • 小程序页面右上方药丸按钮
  • webview 挂载节点
  • 小程序页面启动的 loading 态模版
// src/miniApp/tpl.ts
export const miniAppTpl = `<div class="wx-mini-app"><!-- 右上方药丸按钮 --><ul class="wx-mini-app-navigation__actions"><li class="wx-mini-app-navigation__actions-variable"></li><li class="wx-mini-app-navigation__actions-close"></li></ul><!-- webview挂载节点 --><div class="wx-mini-app__webviews"></div><!-- 启动loading页面 --><div class="wx-mini-app__launch-screen"><div class="wx-mini-app__launch-screen-content"><div class="wx-mini-app__logo"><div class="wx-mini-app__logo-img"><img class="wx-mini-app__logo-img-url"></div><div class="wx-mini-app__logo-circle"></div><span class="wx-mini-app__green-point"></span></div><h1 class="wx-mini-app__name"></h1></div></div>
</div>`;

目前我们先简单实现小程序的实例类,主要包括:

  • 初始化小程序参数
  • 创建小程序页面初始化函数
class MiniApp {/* 小程序appId */appId: string;/* 小程序App信息 */app: OpenMiniAppOpts;/* application实例 */parent: Application | null = null;/* 小程序页面根节点 */el: HTMLElement;/* 小程序webview的挂载节点 */webviewContainer: HTMLElement | null = null;constructor(opts: OpenMiniAppOpts) {this.app = opts;this.appId = opts.appId;// 创建小程序页面的根节点this.el = document.createElement('div');this.el.classList.add('wx-native-view');}/* 初始化小程序页面 */viewDidLoad() {// 初始化小程序页面模版this.initMiniAppFrame();this.webviewContainer = this.el.querySelector('.wx-mini-app__webviews');// 显示小程序加载状态信息this.showLaunchScreen();// 绑定小程序关闭事件this.bindCloseEvent();}initMiniAppFrame() {this.el.innerHTML = miniAppTpl;}/*** 显示小程序加载状态*/showLaunchScreen() {const launchScreen = this.el.querySelector('.wx-mini-app__launch-screen') as HTMLElement;const name = this.el.querySelector('.wx-mini-app__name') as HTMLElement;const logo = this.el.querySelector('.wx-mini-app__logo-img-url') as HTMLImageElement;name.innerHTML = this.app.name;logo.src = this.app.logo;launchScreen.style.display = 'block';}bindCloseEvent() {const closeBtn = this.el.querySelector('.wx-mini-app-navigation__actions-close') as HTMLElement;closeBtn.onclick = () => {AppManager.closeApp(this);};}
}

Application 客户端管理类

Application 的实现比较简单,主要实现小程序的推入和推出方法,并添加一些推入推出的动画效果

class Application {/* 应用的根容器 */el: HTMLElement;/* 应用页面挂载节点 */window: HTMLElement | null = null;/* 存储应用的视图列表 */views: MiniApp[] = [];/* 页面加载状态: 用于避免在一个小程序加载阶段再加载别的 */done: boolean = true;constructor(el: HTMLElement) {this.el = el;this.init();}init() {// 创建应用页面的挂载节点,并添加到根容器中this.window = document.createElement('div');this.window.classList.add('wx-native-window');this.el.appendChild(this.window);}/*** 拉起小程序页面*/async presentView(view: MiniApp) {if (!this.done) return;this.done = false;view.parent = this;view.el.style.zIndex = `${this.views.length + 1}`;// 初始化小程序为止: 将小程序为止调整到屏幕-1屏,再添加划入动画view.el.classList.add('wx-native-view--before-present');view.el.classList.add('wx-native-view--enter-anima');this.window?.appendChild(view.el);this.views.push(view);view.viewDidLoad();await sleep(20);// 小程序入场: 调整小程序为止view.el.classList.add('wx-native-view--instage');await sleep(540);this.done = true;// 移除初始化样式类view.el.classList.remove('wx-native-view--before-present');view.el.classList.remove('wx-native-view--enter-anima');}/*** 退出小程序*/async dismissView(opts: any = {}) {if (!this.done) return;this.done = false;// 推出小程序主要是将当前小程序页面推出// 将前一个小程序页面显示出来// 这里推出小程序可能是直接从页面上把节点直接卸载掉;// 或者是直接添加特定的样式,将小程序移入负一屏,这样在下次拉起的时候可以直接复用;const preView = this.views[this.views.length - 2];const currentView = this.views[this.views.length - 1];const { destroy = true } = opts;// 将当前的小程序推出, 添加推出动画currentView.el.classList.add('wx-native-view--enter-anima');preView?.el.classList.add('wx-native-view--enter-anima');preView?.el.classList.add('wx-native-view--before-presenting');await sleep(0);// 添加推出样式类,及最终推出实际是将小程序页面先移到-1屏currentView.el.classList.add('wx-native-view--before-present');currentView.el.classList.remove('wx-native-view--instage');preView?.el.classList.remove('wx-native-view--presenting');await sleep(540);this.done = true;// 卸载: 从页面上移除掉页面节点destroy && this.el!.removeChild(currentView.el);this.views.pop();preView?.el.classList.remove('wx-native-view--enter-anima');preView?.el.classList.remove('wx-native-view--before-presenting');}
}

小程序列表点击拉起小程序页面

完成上面三个类的创建后,我们在App.vue 文件中尝试点击小程序列表的时候拉起页面:

+ let application: Application;+ onMounted(() => {
+   application = new Application(miniWindow.value!);
+ });+ function openApp(app: any) {
+   AppManager.openApp(app, application);
+ }

至此我们从列表点击打开一个小程序容器的过程就基本实现了,目前应用效果如下:

项目中关于css样式动画部分文章内省略,大家可前往本小节项目仓库代码查看~~

本小节代码已同步至github: mini-wx-app

相关文章:

  • 大事件项目记录12-文章管理接口开发-总
  • 现代 JavaScript (ES6+) 入门到实战(一):告别 var!拥抱 let 与 const,彻底搞懂作用域
  • Spark Web UI从0到1详解
  • SpringSecurity6-授权-动态权限
  • (NIPS-2024)CogVLM:预训练语言模型的视觉专家
  • 大事件项目记录13-接口开发-补充
  • 深入剖析 Linux 内核网络核心:sock.c 源码解析
  • 现代 JavaScript (ES6+) 入门到实战(四):数组的革命 map/filter/reduce - 告别 for 循环
  • 数据挖掘、机器学习与人工智能:概念辨析与应用边界
  • 设计模式精讲 Day 18:备忘录模式(Memento Pattern)
  • FastAPI路由管理APIRouter实战指南
  • 广度优先搜索BFS(广搜)复习(c++)
  • 【智能协同云图库】智能协同云图库第三弹:基于腾讯云 COS 对象存储—开发图片模块
  • 电子计数跳绳原型
  • 如何撰写有价值的项目复盘报告
  • 深入剖析Nacos服务发现与注册,及如何基于LoadBalancer实现负载均衡
  • Tomcat 安装使用教程
  • 第10篇 图像语义分割和目标检测介绍
  • OpenCV 4.10.0 移植
  • Kafka与RabbitMQ相比有什么优势?