Android学习之路(23)组件化框架ARouter的使用
一、功能介绍
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取Fragment
- 完全支持Kotlin以及混编(配置见文末 其他#5)
- 支持第三方 App 加固(使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
- 支持增量编译(开启文档生成后无法增量编译)
- 支持动态注册路由信息
二、典型应用
- 从外部URL映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块API调用,通过控制反转来做组件解耦
三、基础功能
-
添加依赖和配置
android {defaultConfig {...javaCompileOptions {annotationProcessorOptions {arguments = [AROUTER_MODULE_NAME: project.getName()]}}} }dependencies {// 替换成最新版本, 需要注意的是api// 要与compiler匹配使用,均使用最新版可以保证兼容compile 'com.alibaba:arouter-api:x.x.x'annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'... } // 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4' // Kotlin配置参考文末'其他#5'
-
添加注解
// 在支持路由的页面上添加注解(必选) // 这里的路径需要注意的是至少需要有两级,/xx/xx @Route(path = "/test/activity") public class YourActivity extend Activity {... }
-
初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效ARouter.openLog(); // 打印日志ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) } ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
-
发起路由操作
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中) ARouter.getInstance().build("/test/activity").navigation();// 2. 跳转并携带参数 ARouter.getInstance().build("/test/1").withLong("key1", 666L).withString("key3", "888").withObject("key4", new Test("Jack", "Rose")).navigation();
-
添加混淆规则(如果使用了Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*;} -keep public class com.alibaba.android.arouter.facade.**{*;} -keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口 -keep interface * implements com.alibaba.android.arouter.facade.template.IProvider# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现 # -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
-
使用 Gradle 插件实现路由表的自动加载 (可选)
apply plugin: 'com.alibaba.arouter'buildscript {repositories {mavenCentral()}dependencies {classpath "com.alibaba:arouter-register:?"} }
可选使用,通过 ARouter 提供的注册插件进行路由表的自动加载(power by AutoRegister), 默认通过扫描 dex 的方式 进行加载通过 gradle 插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问 dex 文件,初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!
-
使用 IDE 插件导航到目标类 (可选)
在 Android Studio 插件市场中搜索
ARouter Helper
, 或者直接下载文档上方最新版本
中列出的arouter-idea-plugin
zip 安装包手动安装,安装后 插件无任何设置,可以在跳转代码的行首找到一个图标 () 点击该图标,即可跳转到标识了代码中路径的目标类
四、进阶用法
-
通过URL跳转
// 新建一个Activity用于监听Scheme事件,之后直接把url传递给ARouter即可 public class SchemeFilterActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Uri uri = getIntent().getData();ARouter.getInstance().build(uri).navigation();finish();} }
AndroidManifest.xml
<activity android:name=".activity.SchemeFilterActivity"><!-- Scheme --><intent-filter><dataandroid:host="m.aliyun.com"android:scheme="arouter"/><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.DEFAULT"/><category android:name="android.intent.category.BROWSABLE"/></intent-filter> </activity>
-
解析参数
// 为每一个参数声明一个字段,并使用 @Autowired 标注 // URL中不能传递Parcelable类型数据,通过ARouter api可以传递Parcelable对象 @Route(path = "/test/activity") public class Test1Activity extends Activity {@Autowiredpublic String name;@Autowiredint age;// 通过name来映射URL中的不同参数@Autowired(name = "girl") boolean boy;// 支持解析自定义对象,URL中使用json传递@AutowiredTestObj obj; // 使用 withObject 传递 List 和 Map 的实现了// Serializable 接口的实现类(ArrayList/HashMap)// 的时候,接收该对象的地方不能标注具体的实现类类型// 应仅标注为 List 或 Map,否则会影响序列化中类型// 的判断, 其他类似情况需要同样处理 @AutowiredList<TestObj> list;@AutowiredMap<String, List<TestObj>> map;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ARouter.getInstance().inject(this);// ARouter会自动对字段进行赋值,无需主动获取Log.d("param", name + age + boy);} }// 如果需要传递自定义对象,新建一个类(并非自定义对象类),然后实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式),例如: @Route(path = "/yourservicegroupname/json") public class JsonServiceImpl implements SerializationService {@Overridepublic void init(Context context) {}@Overridepublic <T> T json2Object(String text, Class<T> clazz) {return JSON.parseObject(text, clazz);}@Overridepublic String object2Json(Object instance) {return JSON.toJSONString(instance);} }
-
声明拦截器(拦截跳转过程,面向切面编程)
// 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查 // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行 @Interceptor(priority = 8, name = "测试用拦截器") public class TestInterceptor implements IInterceptor {@Overridepublic void process(Postcard postcard, InterceptorCallback callback) {...callback.onContinue(postcard); // 处理完成,交还控制权// callback.onInterrupt(new RuntimeException("我觉得有点异常")); // 觉得有问题,中断路由流程// 以上两种至少需要调用其中一种,否则不会继续路由}@Overridepublic void init(Context context) {// 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次} }
-
处理跳转结果
// 使用两个参数的navigation方法,可以获取单次跳转的结果 ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {@Overridepublic void onFound(Postcard postcard) {...}@Overridepublic void onLost(Postcard postcard) {...} });
-
自定义全局降级策略
// 实现DegradeService接口,并加上一个Path内容任意的注解即可 @Route(path = "/xxx/xxx") public class DegradeServiceImpl implements DegradeService { @Override public void onLost(Context context, Postcard postcard) {// do something. }@Override public void init(Context context) {} }
-
为目标页面声明更多信息
// 我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的 // 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关 // 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断 @Route(path = "/test/activity", extras = Consts.XXXX)
-
通过依赖注入解耦:服务管理(一) 暴露服务
// 声明接口,其他组件通过接口来调用服务 public interface HelloService extends IProvider {String sayHello(String name); }// 实现接口 @Route(path = "/yourservicegroupname/hello", name = "测试服务") public class HelloServiceImpl implements HelloService {@Overridepublic String sayHello(String name) {return "hello, " + name;}@Overridepublic void init(Context context) {} }
-
通过依赖注入解耦:服务管理(二) 发现服务
public class Test {@AutowiredHelloService helloService;@Autowired(name = "/yourservicegroupname/hello")HelloService helloService2;HelloService helloService3;HelloService helloService4;public Test() {ARouter.getInstance().inject(this);}public void testService() {// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取// Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)helloService.sayHello("Vergil");helloService2.sayHello("Vergil");// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byTypehelloService3 = ARouter.getInstance().navigation(HelloService.class);helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();helloService3.sayHello("Vergil");helloService4.sayHello("Vergil");} }
-
预处理服务
// 实现 PretreatmentService 接口,并加上一个Path内容任意的注解即可 @Route(path = "/xxx/xxx") public class PretreatmentServiceImpl implements PretreatmentService {@Overridepublic boolean onPretreatment(Context context, Postcard postcard) {// 跳转前预处理,如果需要自行处理跳转,该方法返回 false 即可}@Overridepublic void init(Context context) {} }
-
动态注册路由信息 适用于部分插件化架构的App以及需要动态注册路由信息的场景,可以通过 ARouter 提供的接口实现动态注册 路由信息,目标页面和服务可以不标注 @Route 注解,注意:同一批次仅允许相同 group 的路由信息注册
ARouter.getInstance().addRouteGroup(new IRouteGroup() {@Overridepublic void loadInto(Map<String, RouteMeta> atlas) {atlas.put("/dynamic/activity", // pathRouteMeta.build(RouteType.ACTIVITY, // 路由信息TestDynamicActivity.class, // 目标的 Class"/dynamic/activity", // Path"dynamic", // Group, 尽量保持和 path 的第一段相同0, // 优先级,暂未使用0 // Extra,用于给页面打标));}});
五、更多功能
-
初始化中的其他设置
ARouter.openLog(); // 开启日志 ARouter.openDebug(); // 使用InstantRun的时候,需要打开该开关,上线之后关闭,否则有安全风险 ARouter.printStackTrace(); // 打印日志的时候打印线程堆栈
-
详细的API说明
// 构建标准的路由请求 ARouter.getInstance().build("/home/main").navigation();// 构建标准的路由请求,并指定分组 ARouter.getInstance().build("/home/main", "ap").navigation();// 构建标准的路由请求,通过Uri直接解析 Uri uri; ARouter.getInstance().build(uri).navigation();// 构建标准的路由请求,startActivityForResult // navigation的第一个参数必须是Activity,第二个参数则是RequestCode ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);// 直接传递Bundle Bundle params = new Bundle(); ARouter.getInstance().build("/home/main").with(params).navigation();// 指定Flag ARouter.getInstance().build("/home/main").withFlags();.navigation();// 获取Fragment Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();// 对象传递 ARouter.getInstance().withObject("key", new TestObj("Jack", "Rose")).navigation();// 觉得接口不够多,可以直接拿出Bundle赋值 ARouter.getInstance().build("/home/main").getExtra();// 转场动画(常规方式) ARouter.getInstance().build("/test/activity2").withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom).navigation(this);// 转场动画(API16+) ActivityOptionsCompat compat = ActivityOptionsCompat.makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);// ps. makeSceneTransitionAnimation 使用共享元素的时候,需要在navigation方法中传入当前ActivityARouter.getInstance().build("/test/activity2").withOptionsCompat(compat).navigation();// 使用绿色通道(跳过所有的拦截器) ARouter.getInstance().build("/home/main").greenChannel().navigation();// 使用自己的日志工具打印日志 ARouter.setLogger();// 使用自己提供的线程池 ARouter.setExecutor();
-
获取原始的URI
String uriStr = getIntent().getStringExtra(ARouter.RAW_URI);
-
重写跳转URL
// 实现PathReplaceService接口,并加上一个Path内容任意的注解即可 @Route(path = "/xxx/xxx") // 必须标明注解 public class PathReplaceServiceImpl implements PathReplaceService {/*** For normal path.** @param path raw path*/String forString(String path) {return path; // 按照一定的规则处理之后返回处理后的结果}/*** For uri type.** @param uri raw uri*/ Uri forUri(Uri uri) {return url; // 按照一定的规则处理之后返回处理后的结果 } }
-
生成路由文档
// 更新 build.gradle, 添加参数 AROUTER_GENERATE_DOC = enable // 生成的文档路径 : build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json android {defaultConfig {...javaCompileOptions {annotationProcessorOptions {arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]}}} }
六、其他
-
路由中的分组概念
- SDK中针对所有的路径(/test/1 /test/2)进行分组,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化
- 可以通过 @Route 注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组
- 注意:一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到
@Route(path = "/test/1", group = "app")
-
拦截器和服务的异同
- 拦截器和服务所需要实现的接口不同,但是结构类似,都存在 init(Context context) 方法,但是两者的调用时机不同
- 拦截器因为其特殊性,会被任何一次路由所触发,拦截器会在ARouter初始化的时候异步初始化,如果第一次路由的时候拦截器还没有初始化结束,路由会等待,直到初始化完成。
- 服务没有该限制,某一服务可能在App整个生命周期中都不会用到,所以服务只有被调用的时候才会触发初始化操作
-
旧版本gradle插件的配置方式
apply plugin: 'com.neenbedankt.android-apt'buildscript {repositories {mavenCentral()}dependencies {classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'} }apt {arguments {AROUTER_MODULE_NAME project.getName();} }dependencies {compile 'com.alibaba:arouter-api:x.x.x'apt 'com.alibaba:arouter-compiler:x.x.x'... }
-
Kotlin项目中的配置方式
// 可以参考 module-kotlin 模块中的写法 apply plugin: 'kotlin-kapt'kapt {arguments {arg("AROUTER_MODULE_NAME", project.getName())} }dependencies {compile 'com.alibaba:arouter-api:x.x.x'kapt 'com.alibaba:arouter-compiler:x.x.x'... }
相关文章:

Android学习之路(23)组件化框架ARouter的使用
一、功能介绍 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中支持多模块工程使用支持添加多个拦截器,自定义拦截顺序支持依赖注入,可单独作为依赖注入框架使用支持InstantRun支持MultiDex(Google方案)映射关系按组分类、多级管理&…...

HCIA vlan练习
目录 实验拓扑 实验要求 实验步骤 1、交换机创建vlan 2、交换机上的各个接口划分到对应vlan中 3、trunk干道 4、路由器单臂路由 5、路由器DHCP设置 实验测试 华为交换机更换端口连接模式报错处理 实验拓扑 实验要求 根据图划分vlan,并通过DHCP给主机下发…...

Ubuntu下安装Gazebo仿真器
Ubuntu下安装Gazebo仿真器 Gazebo仿真平台通常需要配合ROS使用,因此需要先安装ROS。可以参考ROS安装教程 首先安装一些必要的工具 sudo apt-get update sudo apt-get install lsb-release wget gnupg修改源 sudo wget https://packages.osrfoundation.org/gazebo…...

Chatgpt+Comfyui绘图源码说明及本地部署文档
其他文档地址: ChatgptComfyui绘图源码运营文档 ChatgptComfyui绘图源码线上部署文档 一、源码说明 1、源码目录说明 app_home:app官网源码chatgpt-java:管理后台服务端源码、用户端的服务端源码chatgpt-pc:电脑网页前端源码cha…...

ts中 any 和 unknown 有什么区别,分别什么时候使用
any 和 unknown 都是顶级类型 top type,也就是所有类型的父类型 (1)any代表任意类型, 是不做任何检查,相当于不使用 ts,不建议使用,使用 a as any as string 之类的,可以让类型检查…...

C++中命名空间、缺省参数、函数重载
目录 1.命名空间 2.缺省参数 3.函数重载 1.命名空间 在C中定义命名空间我们需要用到namespace关键字,后面跟上命名空间的名字,结构框架有点类似结构体(如图所示) 上面的代码我一一进行讲解: 1.我们先来说第三行和main函…...

【笔记】Helm-3 主题-12 Helm插件指南
Helm插件指南 Helm插件是一个可以通过helm CLI访问的工具,但不是Helm的内置代码。 已有插件可以搜索GitHub。 https://github.com/search?qtopic%3Ahelm-plugin&typeRepositories 该指南描述如何使用和创建插件。 概述 Helm插件是与Helm无缝集成的附加工具…...

2023.1.17 关于 Redis 持久化 AOF 策略详解
目录 引言 AOF 策略 实例演示一 缓冲区 重写机制 手动触发 自动触发 AOF 重写流程 实例演示二 引言 Redis 实现持久化的两大策略 RDB ——> Redis DataBase(定期备份)AOF ——> Append Only File(实时备份) 注意&…...

P2PNet推理和训练
0、环境信息 Package Version ------------------------ ------------ certifi 2023.11.17 charset-normalizer 3.3.2 contourpy 1.2.0 cycler 0.12.1 easydict 1.11 filelock …...

pyexecjs原生js加密算法逆向
查看必要参数,得知sign签名 从堆栈自上到下依次查找源代码 如下图,找到后打上断点,得知e是输入的参数,说明b()是一个加密函数,点击进入查看底层函数 把1117这个函数内的三个方法CV到python中的一个js文件中,…...

数据结构Java版(4)——链表
一、概述 链表是一种常见的数据结构,用于存储一系列具有相同类型的数据元素。它由多个节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。 链表与数组不同,它的节点在内存中不是连续存储的,而是通过每个节点中的指针…...

cfssl简单使用
1、安装 方式1:直接下载 详见:手动生成证书 | Kubernetes # 1、下载cfssl、cfssljson、cfssl-certinfo # cfssl:用于签发证书 # cfssljson:将cfssl签发生成的证书(json格式)变成文件承载式文件 # cfssl-certinfo:验…...

基于Word2vec词聚类的关键词实现
一.基于Word2vec词聚类的关键词步骤 基于Word2Vec的词聚类关键词提取包括以下步骤: 1.准备文本数据:收集或准备文本数据,可以是单一文档或文档集合,涵盖关键词提取的领域。2.文本预处理:清洗文本数据,去除…...

开源项目_大模型应用_Chat2DB
1 基本信息 项目地址:https://github.com/chat2db/Chat2DBStar:10.7K 2 功能 Chat2DB 是一个智能且多功能的 SQL 客户端和报表工具,适用于各种数据库。 对于那些平时会用到数据库,但又不是数据库专家的程序员来说,…...

【线性代数与矩阵论】范数理论
范数理论 2023年11月16日 文章目录 范数理论1. 向量的范数2. 常用向量范数3. 向量范数的等价性4. 矩阵的范数5. 常用的矩阵范数6. 矩阵范数与向量范数的相容性7. 矩阵范数诱导的向量范数8. 由向量范数诱导的矩阵范数9. 矩阵范数的酉不变性10. 矩阵范数的等价性11. 长方阵的范数…...

【C++】priority_queue模拟实现过程中值得注意的点
👀樊梓慕:个人主页 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 🌝每一个不曾起舞的日子,都是对生命的辜负 前言 本篇文章旨在记录博主在模…...

Git提交 ssh: connect to host github.com port 22: Connection timed out解决方案
你们好,我是金金金。 场景 之前都是好好的,不知道今天为什么提交代码就这样了 排查 根据英文可以看出,ssh端口号被拒绝了,22号端口不行,那就换一个端口 造成error的原因 ssh端口被拒绝 解决 找到.ssh文件ÿ…...

Java调用WebService接口,SOAP协议HTTP请求返回XML对象
Java调用Web service接口SOAP协议HTTP请求,解析返回的XML字符串: 1. 使用Java的HTTP库发送SOAP请求,并接收返回的响应。 可以使用Java的HttpURLConnection、Apache HttpClient等库。 2. 将返回的响应转换为字符串。 3. 解析XML字符串&…...

Django框架二
一、模型层及ORM 1.模型层定义 负责跟数据库之间进行通信 2.Django配置mysql 安装mysqlclient,mysqlclient版本最好在13.13以上 pip3 install mysqlclient DATABASES {default: {ENGINE: django.db.backends.mysql,NAME: "mysite1",USER:root,PASSWO…...

工业相机与镜头参数及选型
文章目录 1、相机成像系统模型1.1 视场1.2 成像简化模型 2、工业相机参数2.1 分辨率2.2 靶面尺寸2.3 像元尺寸2.4 帧率/行频2.5 像素深度2.6 动态范围2.7 信噪比2.8 曝光时间2.9 相机接口 3、工业镜头参数3.1 焦距3.2 光圈3.3 景深3.4 镜头分辨率3.5 工作距离(Worki…...

VSCode使用Makefile Tools插件开发C/C++程序
提起Makefile,可能有人会觉得它已经过时了,毕竟现在有比它更好的工具,比如CMake,XMake,Meson等等,但是在Linux下很多C/C源码都是直接或者间接使用Makefile文件来编译项目的,可以说Makefile是基石…...

用C语言验证“三门定理”
#include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <time.h>// 一个源自博弈论的数学游戏问题: // 参赛者会看见三扇门, // 其中一扇门的里面有一辆汽车, // 选中里面是汽车的那扇门࿰…...

计算机网络-分层结构,协议,接口,服务
文章目录 总览为什么要分层怎样分层正式认识分层概念小结 总览 为什么要分层 发送文件前要做的准备工作很多 把这个准备工作分层小问题解决,也就分层解决 怎样分层 每层相互独立,每层做的工作不同 界面自然清晰,层与层之间的接口能够体现…...

前端开发 2: CSS
在前端开发中,CSS(层叠样式表)是一种用于描述网页样式的语言。它控制着网页的布局、颜色、字体等外观效果。在本篇博客中,我将为你介绍 CSS 的基础知识和常用技巧,帮助你更好地掌握前端开发中的样式设计。 CSS 基础知…...

嵌入式-Stm32-江科大基于标准库的GPIO4个小实验
文章目录 一 、硬件介绍二 、实验:LED闪烁、LED流水灯、蜂鸣器提示2.1 需求1:面包板上的LED以1s为周期进行闪烁。亮0.5s,灭0.5s.....2.2 需求2: 8个LED实现流水灯2.3 需求3:蜂鸣器不断地发出滴滴、滴滴.....的提示音。蜂鸣器低电平触发。 三、…...

HackTheBox - Medium - Linux - Noter
Noter Noter 是一种中型 Linux 机器,其特点是利用了 Python Flask 应用程序,该应用程序使用易受远程代码执行影响的“节点”模块。由于“MySQL”守护进程以用户“root”身份运行,因此可以通过利用“MySQL”的用户定义函数来利用它来获得RCE并…...

Uniapp多选Popup(弹出层)
uniapp中多选组件很少,故个人简单开发了一个,可简单使用,也可根据个人需求稍微改进 支持的功能 单选多选(默认)限制选择数量默认选中禁用选项 属性说明 属性默认值说明singlefalsetrue为开启单选,否则为…...

什么是网络安全?网络安全概况
网络安全涉及保护我们的计算机网络、设备和数据免受未经授权的访问或破坏。 这个领域包括多种技术、过程和控制措施,旨在保护网络、设备和数据免受攻击、损害或未授权访问。网络安全涉及多个方面,包括但不限于信息安全、应用程序安全、操作系统安全等 …...

c语言小游戏之扫雷
目录 一:游戏设计理念及思路 二:初步规划的游戏界面 三:开始扫雷游戏的实现 注:1.创建三个文件,test.c用来测试整个游戏的运行,game.c用来实现扫雷游戏的主体,game.h用来函数声明和包含头文…...

如何本地安装Python Flask并结合内网穿透实现远程开发
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...