微前端学习以及分享
微前端学习以及分享
注:本次分享demo的源码github地址:https://github.com/rondout/micro-frontend
什么是微前端
微前端的概念是由ThoughtWorks
在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
它主要解决了两个问题:
- 随着项目迭代应用越来越庞大,难以维护。
- 跨团队或跨部门协作开发项目导致效率低下的问题。
微前端架构有以下特点:
-
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权 -
独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新 -
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
-
独立运行时
每个微应用之间状态隔离,运行时状态不共享
现有的微前端解决方案
现在主流的微前端解决方案有以下几种:
-
qiankun
:qiankun
孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统。 -
MicroApp:
micro-app
是由京东前端团队推出的一款微前端框架,它借鉴了WebComponent
的思想,通过js沙箱
、样式隔离
、元素隔离
、路由隔离
模拟实现了隔离特性,从而实现微前端的组件化渲染,旨在降低上手难度、提升工作效率。micro-app
和技术栈无关,也不和业务绑定,可以用于任何前端框架。 -
Wujie
:无界微前端方案基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。
目前而言,这三种方案都是不错的微前端解决方案,但是目前而言qiankun
对vite的支持仍然不友好,qiankun
本身是不支持vite构建的应用的,还需要使用社区的插件,而且我也有去做demo,然后觉得坑太多了,就选择了 MicroApp
方案来做微前端技术调研学习的方案。
学习目标
本次学习目标有以下几个:
-
设计微前端架构
-
实现基座应用和子应用之间的通信
-
不同子应用之间的通信
-
数据共享以及数据私有
-
部署整个微前端架构以及有关应用
基座和子应用
微前端架构中很重要的一个概念就是基座和子应用
,基座就是整个应用的基础,所有的子应用就是一个个单独的前端应用(工程)。在微前端架构中,子应用是可以单独开发然后适配基座的,最终整个应用运行后,子应用是挂载在基座上的。
我这边设计的整个微前端架构的目录结构如下:
- assets
- base-app
- child-vue3-app
- child-react-app
- child-native-app
- servers
- package.json
- tsconfig.json
这其中 base-app
、 child-vue3-app
、 child-react-app
、 child-native-app
都是一个单独的应用,可独立运行,也可和微前端架构一起整体运行。
在 package.json
中配置启动脚本:
"install:main": "cd base-app && pnpm install","install:vue": "cd child-vue3-app && pnpm install","install:react": "cd child-react-app && pnpm install","install:native": "cd child-native-app && pnpm install","install:server": "cd servers && pnpm install","dev:main": "cd base-app && pnpm dev","dev:vue": "cd child-vue3-app && pnpm dev","dev:react": "cd child-react-app && pnpm dev","dev:native": "cd child-native-app && pnpm dev","dev:server": "cd servers && pnpm dev","install": "pnpm install:main && pnpm install:vue && pnpm install:react && pnpm install:native && pnpm install:server","dev": "concurrently \"pnpm dev:main\" \"pnpm dev:vue\" \"pnpm dev:react\" \"pnpm dev:native\"",
我们在运行项目的时候可以先通过pnpm install
命令安装所有的依赖(包括每一个子应用),然后通过pnpm dev
命令启动项目。
这里大致解释一下 concurrently
这个工具,就是用来同时启动多个命令,比如我们这里启动了基座应用和子应用,因此我们可以通过pnpm dev
命令同时启动基座应用和子应用。
搭建基座和子应用
我们可以用任意的技术栈来搭建基座应用。由于我们现在目前以及后续的技术栈是 vue3 + ts + vite
这套。因此这里我也就以该技术栈搭建基座。
子应用我们可以使用各种技术栈搭建不同的子应用来进行技术实践,我这边准备了一下几种技术栈:
vue3 + ts + vite
react + ts + vite
- 原生
native app (使用nodejs搭建静态资源服务器)
搭建基座应用和子应用的流程这里就无需多讲了。也不是本次分享的重点,因此这里略过。
我们设想的整个应用的结构如下:
我们把整个应用分为3个部分:
- 头部 headers
- 侧边 aside
- 内容区域 Content
我们期望的是头部和侧边区域是基座应用,内容区域是子应用。比如我们的 demo
里面的,侧边栏有首页
、VUE APP
、 REACT APP
以及 NATIVE APP
,首页是基座应用,VUE APP 和 REACT APP 以及 NATIVE APP
是子应用。
初始化基座
首先是安装依赖:
npm i @micro-zoe/micro-app --save
// 或者
pnpm add @micro-zoe/micro-app
// 或者
yarn add @micro-zoe/micro-app
然后根据官方文档操作步骤,初始化基座的 MicroApp
有关的配置代码:
import microApp from "@micro-zoe/micro-app";export function startMicro() {console.log("MicroApp start!");microApp.start();
}
这样子,我们的基座应用就初始化完成了,非常简单。
加载子应用
在基座应用中直接使用 <micro-app>
标签加载子应用:
<template><!-- name:应用名称, url:应用地址 --><micro-app name='my-app' url='http://localhost:3000/'></micro-app>
</template>
这里是以 vue
子应用为例,后续完整代码会有react
版本的。这里注意name
和url
只是这个标签的属性之二,该标签还支持多种属性,后续我们遇到一个说一个。比如我们在加载 vite
构建的子应用就需要加上iframe
属性。因为目前vite
构建的子应用目前只支持 iframe
沙箱。
子应用TS配置
由于子应用无需安装 micro-app
依赖,并且子应用通过window对象实现微前端功能以及和基座应用之间的通信,因此我们最好是给子应用声明一下window对象上面的有关属性和方法:
env.d.ts
:
/** @Author: shufei.han* @Date: 2024-08-02 09:29:40* @LastEditors: shufei.han* @LastEditTime: 2024-08-29 12:05:55* @FilePath: \micro-frontend\child-vue3-app\env.d.ts* @Description: */
/// <reference types="vite/client" />
import type { MicroMessageType } from '@/models/base.model';
import 'ant-design-vue/typings/global'declare global {interface MicroMessage<T = any> {type: MicroMessageType;value?: T;}interface Window {microApp: {addDataListener:(dataListener: (data: MicroMessage) => any, autoTrigger?: boolean) => void;removeDataListener:(dataListener: (data: MicroMessage) => any, autoTrigger?: boolean) => void;removeGlobalDataListener:(dataListener: (data: MicroMessage) => any, autoTrigger?: boolean) => void;addGlobalDataListener:(dataListener: (data: MicroMessage) => any, autoTrigger?: boolean) => void;clearDataListener: () => void;getData: () => MicroMessage;dispatch: <T extends MicroMessage = MicroMessage, C extends Function>(data:T, cb?: C) => void;getGlobalData: () => MicroMessage;setGlobalData: <T extends MicroMessage = MicroMessage, C extends Function>(data:T, cb?: C) => void;};/** 应用名称 */__MICRO_APP_NAME__: string;/** 判断应用是否在微前端环境中 */__MICRO_APP_ENVIRONMENT__: boolean;/** 子应用的静态资源前缀 */__MICRO_APP_PUBLIC_PATH__: string;/** 子应用的基础路径 */__MICRO_APP_BASE_ROUTE__: string;/** 判断当前应用是否是主应用 */__MICRO_APP_BASE_APPLICATION__: string;/** 获取真实window(即主应用window) */rawWindow: Window;/** 获取真实document(即主应用document) */rawDocument: Document;}
}export {}
子应用跨域配置
如果我们直接像上述配置一样直接接入子应用,由于浏览器的同源策略,如果子应用不支持跨域,则会报跨域错误。因此我们需要在子应用所在的 web server
进行跨域配置:
vite
配置:
export default defineConfig({server: {headers: {'Access-Control-Allow-Origin': '*',}}
})
nodejs
配置(我的 native app
是使用express
搭建的静态资源服务,因此这里以该技术栈举例):
private setCors() {this.instance.use((req, res, next) => {res.header("Access-Control-Allow-Origin", "*");res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With,Content-Type, Accept");next();});}
环境变量
Micro App
提供了一下环境变量,用于我们开发过程中的某些环境有关的逻辑判断,这些环境变量大多都通过绑定为 window
对象的属性的方式来使用,因此如果我们使用Typescript
开发子应用的话,就要进行这些全局变量的变量声明,我们在我们子应用中的声明文件(vite
构建的应用的默认声明文件就是根目录下面的env.d.ts
):
/// <reference types="vite/client" />declare global {interface Window {/** 应用名称 */__MICRO_APP_NAME__: string;/** 判断应用是否在微前端环境中 */__MICRO_APP_ENVIRONMENT__: boolean;/** 子应用的静态资源前缀 */__MICRO_APP_PUBLIC_PATH__: string;/** 子应用的基础路径 */__MICRO_APP_BASE_ROUTE__: string;/** 判断当前应用是否是主应用 */__MICRO_APP_BASE_APPLICATION__: string;/** 获取真实window(即主应用window) */rawWindow: Window;/** 获取真实document(即主应用document) */rawDocument: Document;}
}export {}
生命周期
micro-app
通过CustomEvent
定义生命周期,在组件渲染过程中会触发相应的生命周期事件。
生命周期列表
-
created:
<micro-app>
标签初始化后,加载资源前触发。 -
beforemount:
加载资源完成后,开始渲染之前触发。 -
mounted:
子应用渲染结束后触发。 -
unmount:
子应用卸载时触发。 -
error:
子应用加载出错时触发,只有会导致渲染终止的错误才会触发此生命周期。
监听方式
Vue
:
<micro-appname='xx'url='xx'onCreated={() => console.log('micro-app元素被创建')}onBeforemount={() => console.log('即将渲染')}onMounted={() => console.log('已经渲染完成')}onUnmount={() => console.log('已经卸载')}onError={() => console.log('加载出错')}
/>
React
:
因为React不支持自定义事件,所以我们需要引入一个polyfill
。
在标签所在的文件顶部
添加polyfill
,注释也要复制。
/** @jsxRuntime classic */
/** @jsx jsxCustomEvent */
import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event'<micro-appname='xx'url='xx'onCreated={() => console.log('micro-app元素被创建')}onBeforemount={() => console.log('即将渲染')}onMounted={() => console.log('已经渲染完成')}onUnmount={() => console.log('已经卸载')}onError={() => console.log('加载出错')}
/>
数据通信
数据通信这一节的内容在微前端的领域非常重要,这关乎着各个子应用之间,以及子应用与主应用之间如何进行数据通信。
数据通信主要分为以下几种:
- 主应用向子应用发送数据
- 子应用向主应用发送数据
- 各个子应用之间的互相通信
主应用向子应用发送数据
主应用给子应用传递数据有两种方式:
- 通过data属性发送数据
- 通过方法手动发送数据
通过data属性发送数据
data作为 micro-app
标签的属性,用该属性作为主应用向子应用传递数据的中介:
<micro-app :name="SubApps.REACT" @created="created" :data="data" keep-alive url="http://localhost:4003/" iframe @datachange="handleChange"></micro-app>const data = ref<MicroMessage>({type: MicroMessageType.TEXT_MSG,value: 'This is a initial TextMessage'
})onMounted(() => {setInterval(() => {data.value.value = 'new value'}, 1000)
})
这种方式类似于我们平时组件中的父子组件传参,区别就是,在子应用中需要通过getData
方法区手动获取数据,该数据不是全响应式的(基座中绑定的数据如果发生变化会动态更改传给子应用的值,但是子应用需要通过getData
方法手动获取更改后的值。)。
子应用:
const data = window.microApp.getData()
通过microApp.setData
方法发送数据
除了通过data属性传递数据,还可以通过microApp.setData
方法动态发送数据:
microApp.setData(name, message)
这两种方法是有区别的,第一种方法只能通过getData
方法去手动的获取数据,数据更新时子应用是无感知的。而第二种方法类似于发布订阅机制,子应用可以随时监听到数据变化。
子应用通过注册事件监听数据
子应用可以使用window.microApp.addDataListener
方法监听来自主应用的事件消息:
window.microApp.addDataListener((data: MicroMessage) => {handleMessage(data)
})
我们在收到消息后通过handleMessage
方法来统一处理消息。
这里建议:虽然microApp
只要求我们的消息格式是 Object
就可以,但是我这边建议,我们需要对消息进行一个统一的格式管理,方便维护。
比如我这边利用TS
的特性,对基座和子应用之间的消息进行枚举,然后再定义接口来约束消息的格式,这样基座和子应用之间处理消息的时候根据消息类型来去做对应的处理逻辑:
// 对消息类型进行枚举
export enum MicroMessageType {CHANGE_THEME = 'change_theme',SET_COUNT = 'set_count',TEXT_MSG = 'text_msg',
}// 约束来基座和子应用之间通信的消息格式
interface MicroMessage<T = any> {type: MicroMessageType;value?: T;
}
总的来说:通过data属性传递数据和通过发布订阅的模式(消息事件监听)传递数据是有区别的,在实际场景中根据自己需要选取使用。
子应用向主应用发送数据
在微前端种,不仅有主应用向子应用发送消息的场景,通常也可能会有子应用向主应用发送消息的场景,在
micro-app
中,子应用通过 window.microApp.dispatch
方法发送数据,这里同样为了约束子应用向主应用发送的数据的格式,我们可以在声明文件中约定子应用推送消息的格式:
dispatch: <T extends MicroMessage = MicroMessage, C extends Function>(data:T, cb?: C) => void
然后我们就可以定义一个公用的方法用来向主应用发送数据:
export const sendMessageToBase = (message: MicroMessage) => {window.microApp.dispatch(message)
}// 使用
const handleSend = () => {sendMessageToBase({type: MicroMessageType.TEXT_MSG,value: {value},});
};
全局数据通信
全局数据通信会向主应用和所有子应用发送数据,在跨应用通信的场景中适用。
主应用发送数据:
import microApp from "@micro-zoe/micro-app";export const sendGlobalData = (message: MicroMessage) => {microApp.setGlobalData(message)
}
子应用发送数据:
// setGlobalData只接受对象作为参数
window.microApp.setGlobalData({type: '全局数据'})
主应用获取数据:
microApp.addGlobalDataListener((msg) => {Modal.info({title:`BaseApp收到全局数据`, content: JSON.stringify(msg), centered:true})mainStore.setGlobalMessages(msg)
}// 或者使用getGlobalData手动获取数据
const data = microApp.getGlobalData()
子应用获取数据:
window.microApp.addGlobalDataListener((data) => {handleGlobalMessage(data)
})// 或者使用getGlobalData手动获取数据
const data = window.microApp.getGlobalData()
虚拟路由系统
MicroApp
通过拦截浏览器路由事件以及自定义的location
、history
,实现了一套虚拟路由系统,子应用运行在这套虚拟路由系统中,和主应用的路由进行隔离,避免相互影响。建议全局配置路由模式:
microApp.start({'router-mode':'native'
});
MicroApp
提供一下这几种路由模式:
- search模式
- native模式
- native-scope模式
- pure模式
- state模式
这里就不详细说明这些路由模式了,这里只说常用的两种:
search模式路由
search是默认模式,通常不需要特意设置,search模式下子应用的路由信息会作为query参数同步到浏览器地址上。这种模式最简单也不需要去做其他的配置。
但是这种模式的路由,在页面上看起来会很奇怪,不建议使用。
native模式路由
native模式是指放开路由隔离,子应用和主应用共同基于浏览器路由进行渲染,它拥有更加直观和友好的路由体验,但配置方式更加复杂。需要基于vue-router
的base
配置或者react-router
的basename
配置:
Vue3
中的配置方法:
我们使用4.x版本的vue-router
,在生成路由的方法createWebHashHistory
中传入base
配置:
history: createWebHashHistory(import.meta.env.VITE_BASE_URL)
这里需要注意这里配置的base
需要和主应用中的该子应用的routerPath
保持一致,因此推荐使用另一种方法来设置而非通过环境变量:
<micro-app :name="SubApps.VUE" @created="created" url="http://192.168.8.125:4002/" baseroute="/vue/" iframe @datachange="handleChange"></micro-app>// router/index.ts
const router = createRouter({..............history: createWebHashHistory(window.__MICRO_APP_BASE_ROUTE__ || '/'),..............
}
React中使用
可以根据官方文档进行配置。
注意:根据子应用和主应用不同的路由模式,我们可能需要进行不同的配置或者不需要配置,具体查阅官方文档。
native模式的路由看着就比较清晰了:
总结
本次分享是对自己学习MicroApp
的过程的一个分享,本次demo也只是一个非常基础的demo,微前端架构需要考虑的内容还很多,需要再实战中遇到问题再积累总结。
最后:本次demo的源码放在了https://github.com/rondout/micro-frontend,个人认为这个demo还是有一定的参考价值,不光是微前端,还包含了一些前端工程化的内容,大家有兴趣的话可以clone下来参考,后续如果有时间我也会继续维护和补充优化该demo。
相关文章:

微前端学习以及分享
微前端学习以及分享 注:本次分享demo的源码github地址:https://github.com/rondout/micro-frontend 什么是微前端 微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多…...

【Linux-进程间通信】vscode使用通信引入匿名管道引入
一、新系统,新软件 1.新系统 哈喽宝子们,从今以后我们不再使用风靡一时的CentOS系统了,因为CentOS已经不在维护了,各大公司几乎也都从CentOS转入其他操作系统了;我们现在由原来的CentOS系统切换到最新的Ubuntu系统&a…...
nerd bug:VPG多次计算vnetloss的计算图报错的解决
待更 Reference https://www.cnblogs.com/StarZhai/p/15495292.htmlhttps://github.com/huggingface/transformers/issues/12613https://discuss.pytorch.org/t/inplace-operation-errors-when-implementing-a2c-algorithm/145406/6...

BigDecimal类Date类JDK8日期
一、BigDecimal类是什么?它有什么用?先看一段代码,看这个代码有什么问题再说BigDeimal这个类是干什么用的,这样会好理解一些。 public class Test {public static void main(String[] args) {System.out.println(0.1 0.2);Syste…...

MybatisWebApp
如何构建一个有关Mybatis的Web? 在这里给出我自己的一些配置。我的TomCat版本:10.1.28 ,IDEA版本:2024.1.4 Pom.XML文件 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/200…...

第十五章 RabbitMQ延迟消息之延迟插件
目录 一、引言 二、延迟插件安装 2.1. 下载插件 2.2. 安装插件 2.3. 确认插件是否生效 三、核心代码 四、运行效果 五、总结 一、引言 上一章我们讲到通过死信队列组合消息过期时间来实现延迟消息,但相对而言这并不是比较好的方式。它的代码实现相对来说比…...

OpenAI 公布了其新 o1 模型家族的元提示(meta-prompt)
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

Java基础14-网络编程
十四、网络编程 java.net.*包下提供了网络编程的解决方案! 基本的通信架构 基本的通信架构有2种形式: CS架构( Client客户端/Server服务端)、BS架构(Browser浏 览器/Server服务端)。无论是CS架构,还是BS架构的软件都必须依赖网络编程!。 1、网络通信的三要素 网络通…...
sed命令详解
sed命令详解 sed(stream editor,流编辑器)是 Linux 和 Unix 系统中功能强大的文本处理工具,它能够对输入流(如文件、管道输入等)进行逐行处理,从而实现多种多样的文本编辑操作。 基本语法 se…...

Linux高阶——1013—正则表达式练习
1、正则表达式匹配机制 问号放在或者*后面,表示切换成非贪婪模式 [^>]表示非右尖括号的都能匹配,直到找到href"为止 [^"]表示向右匹配,到"为止 因此,三个都能匹配 2、 正则函数 寻找结果 源文件 正则函数运…...
【CMake】为可执行程序或静态库添加 Qt 资源文件,静态库不生效问题
【CMake】添加静态库中的 Qt 资源 文章目录 可执行程序1. 创建资源文件(.qrc)2. 修改 CMakeLists.txt3. 使用资源文件 静态库1. 修改 CMakeLists.txt2. 使用资源2.1 初始化资源文件2.2 可执行程序中调用 这里介绍的不是使用 Qt 创建工程时默认的 CMakeLi…...
服务器、jvm、数据库的CPU飙高怎么处理
服务器 CPU 飙高处理 排查步骤: 监控工具:使用操作系统自带的监控工具,比如 top、htop、sar、vmstat 等,查看哪些进程占用了大量的 CPU 资源。进程排查:通过 top 等工具找到消耗 CPU 最高的进程,确定是哪…...
自适应过滤法—初级
#课本P144例题 """ Python 简单的自适应过滤移动平均预测方法 """ import numpy as np import matplotlib.pyplot as plt#用于迭代的函数 def self_adaptive( seq, N, k, maxsteps ):## 初始化序列seq_ada = np.zeros( len(seq) ) # 设置预测…...
UML图有用吗?真正厉害的软件开发,有用的吗?什么角色用?
UML(Unified Modeling Language,统一建模语言)图在软件开发中是有用的,但其使用取决于项目的规模、复杂度以及开发团队的实践习惯。真正厉害的开发者并非一定要依赖UML图,但在某些情况下,UML图确实能够提升…...

基于Java+Springboot+Vue开发的酒店客房预订管理系统
项目简介 该项目是基于JavaSpringbootVue开发的酒店客房预订管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java…...

OpenCV高级图形用户界面(5)获取指定滑动条(trackbar)的当前位置函数getTrackbarPos()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 返回滑动条的位置。 该函数返回指定滑动条的当前位置。 cv::getTrackbarPos() 函数用于获取指定滑动条(trackbar)的当前…...

拓扑排序在实际开发中的应用
1. 拓扑排序说明 简单解释:针对于有向无环图(DAG),给出一个可行的节点排序,使节点之间的依赖关系不冲突。 复杂解释:自行搜索相关资料。 本次应用中的解释:给出一个可行的计算顺序࿰…...

【CTF-SHOW】Web入门 Web27-身份证日期爆破 【关于bp intruder使用--详记录】
1.点进去 是一个登录系统,有录取名单和学籍信息 发现通过姓名和身份证号可以进行录取查询,推测录取查询可能得到学生对应学号和密码,但是身份证号中的出生日期部分未知,所以可以进行爆破 2.打开bp抓包 这里注意抓的是学院录取查…...
Windows 添加右键以管理员身份运行 PowerShell
在 Windows 系统中添加一个右键菜单选项,以便可以使用管理员权限打开 PowerShell,可以通过编辑注册表来实现。 打开注册表编辑器: 按 Win R 打开运行对话框。输入 regedit 并按回车,这将打开注册表编辑器。 导航到文件夹背景键&…...

数学建模算法与应用 第15章 预测方法
目录 15.1 微分方程模型 Matlab代码示例:求解简单的微分方程 15.2 灰色预测模型(GM) Matlab代码示例:灰色预测模型 15.3 自回归模型(AR) Matlab代码示例:AR模型的预测 15.4 指数平滑法 M…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...