应用中心

在App Finder 中点击应用中心可以进入Oinone的应用中心,可以看到Oinone平台所有应用列表、应用大屏、以及技术可视化。

1. App Finder

平台提供App Finder搜索查找已安装的应用、点击进入应用;

我收藏的应用:在应用中心收藏后会呈现在“我收藏的应用”;

业务应用:与业务相关、用户可操作的应用;

设计器:平台提供五大设计器设计应用,即平台的无代码能力,包括:模型设计器、界面设计器、流程设计器、数据可视化、集成设计器。

image.png

2. 应用列表

应用列表管理平台中所有应用,管理应用的生命周期,如安装、升级、卸载,提供搜索、创建、编辑、卸载、收藏、设置首页等功能。

在介绍应用具体操作前,我们先来了解以下概念:

应用类型:分为应用与模块两种类型,两者区别在于在于应用有前台页面,可以在前台页面操作数据,模块没有前台页面、服务于其他应用或模块,大家在创建应用时可根据业务需求创建应用或模块。

依赖:创建新应用时,可依赖已有应用或模块,依赖后使用依赖应用/模块的能力,比如依赖文件应用可使用导入、导出能力,依赖资源应用可使用地址、语言等能力。

image.png

图3-2-35 Oinone的应用列表

2.1 创建

  • 创建应用时,需要选择类型、定义应用名称、技术名称,选择依赖模块、所属分类、客户端类型。
  • 每个应用大多数都需要依赖一些基础模块:文件、资源、
  • 应用分类是按照应用所属业务域进行的分类管理,目前是平台提供的分类,后续会开放给用户自行管理。
  • 客户端类型是指应用适用于PC端、移动端,如果只选择PC端,则应用不可在移动端使用。

image.png

2.2 编辑

编辑时,不允许编辑类型,技术名称,需要在创建时定义正确。

image.png

2.3 安装与卸载

卸载后,应用就不会呈现在App Finder中,不可进入应用、使用应用,可重新安装,安装后继续使用。

image.png

2.4 收藏应用

点击应用卡片右上角的星标可收藏、取消收藏应用,收藏的应用在App Finder和工作台中展示在收藏位置,可快捷进入。

image.png

image.png

2.5 设置首页

定义每个应用的首页,有两种方式:

a. 通过绑定菜单,进入绑定菜单的页面;

b. 直接绑定视图,选择模型、找到模型下的视图,如果可作为首页的视图不存在,也可以进入设计器创建。

image.png

image.png

2.6 应用详情

点击了解更多,可进入应用详情,查看应用基础信息。

image.png

2.7 设计器快捷入口

设计页面:进入界面设计器;

设计模型:进入模型设计器;

设计流程:进入流程设计器;

3. 应用大屏

应用大屏按照分类展示应用,未设置应用分类的应用,无法在应用大屏中呈现。

image.png

图3-2-37 未设置应用类目则无法在应用大屏中呈现。

4. 技术可视化

在技术可视化页面,出展示已经安装模块的元数据,并进行分类呈现。

image.png

图3-2-38 云数据分类呈现

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9386.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
史, 昂's avatar史, 昂数式管理员
Previous 2024年5月23日
Next 2024年5月23日

相关推荐

  • 3.3.2 模型的类型

    本文会介绍不同类型模型以及其简单的应用场景,方便大家理解不同类型模型的用途 模型分为元模型和业务模型。元数据是指描述应用程序运行所必需的数据、规则和逻辑的数据集;元模型是指用于描述内核元数据的一套模式集合;业务模型是指用于描述业务应用元数据的一套模式集合。 元模型分为模块域、模型域和函数域三个域。域的划分规则是根据元模型定义数据关联关系的离散性来判断,离散程度越小越聚集到一个域。在4.1.4【模块元数据详解】一文中介绍的ModuleDefinition就是元模型。而我们在开发中涉及的就是业务模型 一、模型类型 抽象模型:往往是提供公共能力和字段的模型,它本身不会直接用于构建协议和基础设施(如表结构等)。 传输模型:用于表现层和应用层之间的数据交互,本身不会存储,没有默认的数据管理器,只有数据构造器。 存储模型:存储模型用于定义数据表结构和数据的增删改查(数据管理器)功能,是直接与连接器进行交互的数据容器。 代理模型:用于代理存储模型的数据管理器能力的同时,扩展出非存储数据信息的交互功能的模型。 二、模型定义种类 模型定义就是模型描述,不同定义类型,代表计算描述模型的元数据的规则不同 静态模型定义:模型元数据不持久化、不进行模型定义的计算(默认值、主键、继承、关联关系) 静态计算模型定义:模型元数据不持久化但初始化时进行模型定义计算获得最终的模型定义 动态模型定义:模型元数据持久化且初始化时进行模型定义计算获得最终的模型定义 静态模型定义需要使用@Model.Static进行注解;静态计算模型定义使用@Model.Static(compute=true)进行注解;动态模型定义不注解@Model.Static注解。 三、安装与更新 使用@Model.model来配置模型的不可变更编码。模型一旦安装,无法在对该模型编码值进行修改,之后的模型配置更新会依据该编码进行查找并更新;如果仍然修改该注解的配置值,则系统会将该模型识别为新模型,存储模型会创建新的数据库表,而原表将会rename为废弃表。 如果模型配置了@Base注解,表明在【oinone的设计器】中该模型配置不可变更;如果字段配置了@Base注解,表明在【oinone的设计器】中该字段配置不可变更。 四、基础配置 模型基类 所有的模型都需要继承以下模型中的一种,来表明模型的类型,同时继承以下模型的默认数据管理器(详见3.3.3模型的数据管理器一节)。 继承BaseModel,构建存储模型,默认无id属性。 继承BaseRelation,构建多对多关系模型,默认无id属性。 继承TransientModel,构建临时模型(传输模型),临时模型没有数据管理器,也没有id属性。 继承EnhanceModel,构建数据源为ElasticSearch的增强模型。 快捷继承 继承IdModel,构建主键为id的模型。继承IdModel的模型会数据管理器会增加queryById方法(根据id查询单条记录) 继承CodeModel,构建带有唯一编码code的主键为id的模型。可以使用@Model.Code注解配置编码生成规则。也可以直接覆盖CodeModel的generateCode方法或者自定义新增的前置扩展点自定义编码生成逻辑。继承CodeModel的模型会数据管理器会增加queryByCode方法(根据唯一编码查询单条记录) 继承VersionModel,构建带有乐观锁,唯一编码code且主键为id的模型。 继承IdRelation,构建主键为id的多对多关系模型。 模型继承关系图 图3-3-2-1 模型继承关系图 AbstractModel抽象基类是包含createDate创建时间、writeDate更新时间、createUid创建用户ID、writeUid更新用户ID、aggs聚合结果和activePks批量主键列表等基础字段的抽象模型。 TransientModel传输模型抽象基类是所有传输模型的基类,传输模型不存储,没有数据管理器。 TransientRelation传输关系模型是所有传输关系模型的基类,传输关系模型不存储,用于承载多对多关系,没有数据管理器。 BaseModel存储模型基类提供数据管理器功能,数据模型主键可以不是ID。 IdModel带id模型抽象基类,在BaseModel数据管理器基础之上提供根据ID查询、更新、删除数据的功能。 BaseRelation关系模型抽象基类用于承载多对多关系,是多对多关系的中间模型,数据模型主键可以不是ID。 IdRelation带id关系模型抽象基类,在BaseRelation数据管理器基础之上提供根据ID查询、更新、删除数据的功能。 CodeModel带code模型抽象基类,提供按配置生成业务唯一编码功能,根据code查询、更新、删除数据的功能。 EnhanceModel增强模型,提供全文检索能力。此模型会在4.1.25【框架之搜索引擎】一文中展开介绍。 五、抽象模型(举例) 抽象模型本身不会直接用于构建协议和基础设施(如表结构等),而是通过继承的机制供子模型复用其字段和函数。子模型可以是所有类型的模型。 比如demo模块要管理的一些公共模型字段,我们可以建一个AbstractDemoIdModel和AbstractDemoCodeModel,demo模块中的实体模型就可以继承它们。我们来为demo模块的模型统一增加一个数据状态这么一个字段,用做数据的生效与失效管理。 Step1 引入DataStatusEnum类 pamirs-demo-api的pom.xml包增加依赖,便于引入DataStatusEnum类,当然也可以自己建,这里只是oinone提供了统一的数据记录状态的枚举,以及相应的通用方法,这边就直接引入 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-core-common</artifactId> </dependency> 图3-3-2-2 引入通用类库 Step2 修改DemoModule DataStatusEnum枚举类本身也会做为数据字典,以元数据的方式被管理起来,当一个模块依赖另一个模块的元数据相关对象,则需要改模块的模块依赖定义。为DemoModule增加CommonModule的依赖注解 package pro.shushi.pamirs.demo.api; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute; import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxHomepage; import pro.shushi.pamirs.core.common.CommonModule; import pro.shushi.pamirs.demo.api.model.PetShop; import pro.shushi.pamirs.meta.annotation.Module; import pro.shushi.pamirs.meta.base.PamirsModule; import pro.shushi.pamirs.meta.common.constants.ModuleConstants; @Component @Module( name = DemoModule.MODULE_NAME, displayName = "oinoneDemo工程", version = "1.0.0", dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE} ) @Module.module(DemoModule.MODULE_MODULE) @Module.Advanced(selfBuilt = true, application = true) @UxHomepage(@UxRoute(PetShop.MODEL_MODEL)) public class DemoModule implements PamirsModule { public static final String MODULE_MODULE = "demo_core"; public static final String MODULE_NAME = "DemoCore"; @Override public String[] packagePrefix() { return new String[]{ "pro.shushi.pamirs.demo"}; } } 图3-3-2-3 定义模块依赖 Step3 新建AbstractDemoCodeModel和AbstractDemoIdModel 并新增AbstractDemoIdModel和AbstractDemoCodeModel分别继承IdModel和CodeModel,实现IDataStatus接口不是必须的,刚好DataStatus有配套的通用逻辑,暂时也先加进去,具体使用会在本文的【代理模型】这段介绍 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.core.common.behavior.IDataStatus; import pro.shushi.pamirs.core.common.enmu.DataStatusEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.annotation.sys.Base; import pro.shushi.pamirs.meta.base.common.CodeModel; import pro.shushi.pamirs.meta.enmu.ModelTypeEnum; @Base @Model.model(AbstractDemoCodeModel.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.ABSTRACT) @Model(displayName = "AbstractDemoCodeModel") public abstract class AbstractDemoCodeModel extends CodeModel implements IDataStatus { public static final String MODEL_MODEL="demo.AbstractDemoCodeModel"; @Base @Field.Enum @Field(displayName = "数据状态",defaultValue = "DISABLED",required =…

    2024年5月23日
    1.8K00
  • 2.4.2 Oinone独特性之每一个需求都可以是一个模块

    我们的Oinone平台采用模型驱动的方式,并符合面向对象设计原则,每个需求都可以是一个独立模块,可以独立安装、升级和卸载。这让系统真正像乐高积木一样搭建,具有高度的灵活性和可维护性。 与大部分低代码或无代码平台不同的是,它们的应用市场上的应用往往是模板式的,也就是说,这是一个拷贝,个性化只能在应用上直接修改,而且一旦修改就不能升级。这对于软件公司和客户来说都非常痛苦。客户无法享受到软件公司产品的升级功能,而软件公司在服务大量客户时,也会面临不同版本的维护问题,成本也非常高。而我们的Oinone平台完全避免了这些问题,让客户和软件公司都可以从中受益(如下图2-9、2-10所示)。 图2-9软件公司与客户项目的关系-让标准与个性化共存 图2-10 软件公司与客户项目的关系-让升级无忧 实现原理 在满足客户个性化定制需求时,传统的方法通常是直接修改标准产品源码,但这样做会带来一个问题:标准产品无法持续升级。相反,无论是在OP模式还是SaaS模式下,Oinone都采用全新的模块为客户进行个性化开发,保持标准产品和个性化模块的独立维护和升级。这是因为在元数据设计时,Oinone采用了面向对象的设计原则,实现了元数据设计与面向对象设计思想的完美融合。 面向对象设计的核心特征包括封装、继承、多态,而Oinone的元数据设计完全融入了这些思想。下面是几个例子,说明Oinone的元数据设计如何体现面向对象设计的核心特征,并带来了什么好处: 继承:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来扩展模型的字段、逻辑、展示。 多态:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来覆盖模型的原有字段、逻辑、展示。 封装:外部无需关心模型内部如何实现,只需按照不同场景调用模型对应开放级别的字段、逻辑、展示。 这些特征和优势使得Oinone在满足客户个性化需求时更加灵活和可持续,同时使得标准产品的维护和升级变得更加容易和高效。 在Java语言设计中,万物皆对象,一切都以对象为基础。而Oinone的元数据设计则是以模型为出发点,作为数据和行为的承载体。如下图2-11清晰地描述了Java面向对象编程中封装、继承、多态在Oinone元数据中的对应关系。Oinone元数据描述了B对象继承A对象并拥有其所有属性和方法,并覆盖了A对象的属性1和方法1,同时新增了属性3和方法3。 此外,Oinone的面向对象特性是用元数据来描述的。一方面,我们基于Java编码规范收集相关元数据,以保持不改变Java编程习惯。另一方面,方法和对象的挂载是松耦合的,只要按照元数据规范进行挂载,就能轻松地将其附加到模型上。在不改变原有A对象的情况下,我们可以直接增加方法和属性(如下图2-12所示)。 图2-11 java面向对象在Oinone元数据中对应 图2-12 java对象的修改 VS Oinone元数据模型的修改 Oinone函数不仅支持面向对象的继承和多态特性,还提供了面向切面的拦截器和SPI机制的扩展点,以应对方法逻辑的覆盖和扩展,以及系统层面的逻辑扩展(如下图2-13所示)。这些扩展功能可以独立地在模块中维护。 其中,拦截器可以在不侵入函数逻辑的情况下,根据优先级为满足条件的函数添加执行前和执行后的逻辑。 扩展点是一种类似于SPI机制的逻辑扩展机制,用于扩展函数的逻辑。通过这一机制,可以对函数逻辑进行灵活的扩展,以满足不同的业务需求。 图2-13 Oinone函数拦截与扩展机制 不管是对象、属性还是方法,都可以以独立的模块方式来扩展,这就使得每一个需求都可以成为一个独立的模块,方便我们在研发标准产品时进行模块化的划分,同时也让我们在以低代码模式为客户进行二次开发时,能够更好地支持“标准产品迭代与个性化保持独立”的需求。在2.4.3【oinone独特性之低无一体】一文中,我们也提到了这个特性,但那是在低无一体的情况下,通过元数据融合来实现的。让我们看看基于低代码开发模式下,典型的Oinone二次开发工程结构(如下图2-14所示),就可以更好地理解这个特性啦! 图2-14 Oinone典型的二开工程结构

    2024年5月23日
    1.5K00
  • 6.5 权限体系(改)

    做好企业级软件,首先得过权限这一关 在企业的IT部门沟通中,权限是避免不了。自嘲下在我们刚出来创业时,为了收获客户对我们技术能力的信任,每每与跟客户沟通时都会说我们是阿里出来的,但在权限设计这个环节不那么灵验,反而被打上了不懂B端权限设计的标签,会问很多问题。我就很奇怪难道大厂就没有内部管理系统了?大厂只有C端交易,没有B端交易?但从侧面说明权限不简单还特别重要。做好企业级软件,首先得过权限这一关。 整体介绍 对于平台运行来说,权限是必须,但我们的auth模块不是必须的,auth模块只是我们提供的一种默认实现,客户可以根据平台的spi机制进行替换。auth模块利用了平台的Hook特性做到与业务无关,在我们开发上层应用的时,是不用感知它的存在的。 auth模块涉及:功能权限、数据权限 数据权限:行权限和列权限。备注:数据权限的控制只能用于【存储模型】 表级权限:表达的语义是:是否该表可读/写(修改和新增) 列权限:表达的语义是:是否该列可读/写(修改和新增) 行权限:表达的语义是:是否对该行可读/写(修改)/删除 功能权限:表达的语义是ServerAction/Function是否可执行/展示,viewAction是否可展示,菜单是否可以显示 范围说明: 配置多个权限项的时候,取并集 配置多个角色的时候,取并集 模型设计 产品体验 Step1 创建角色 通过App Finder 切换至【权限】应用,点击新增按钮创建一个名为oinone的角色 Step2 创建数据权限项 在【权限项列表】菜单,点击【创建】按钮新增一个名为【宠物达人数据权限项】,同时宠物达人的数据权限设置为只能查看性别为男的记录 配置说明: 名称: 代表该项配置的权限的名字(必填)(必须是全系统唯一) 权限模型: 选择需要拦截的数据所在的表,即为模型,可以搜索使用 描述:对该权限项的描述 权限条件的配置: 满足全部:对条件一和条件二要同时满足的数据才能被看见 满足任一:对条件一和条件二要任意满足的数据都能被看见 读权限:对该数据是否有读取的权限 写权限:对该数据是否有修改的权限 删除权限:对该条件内的数据是否有删除的权限 Step3 为角色配置权限 编辑oinone角色,只开通oinoneDemo工程应用 选中【数据权限】选项卡点击【添加】按钮,勾选宠物达人数据权限项点击【确定】按钮 整体点击保存,回到列表页记得点击【权限生效】按钮 Step4 新建用户绑定角色 切换到用户中心模块,点击【创建】按钮填写必要信息,并在角色选中oinone权限组。 退出admin用户,用oinone登陆,权限效果: 只能看见demo模块 oinone登陆只能看到性别为男的宠物达人记录 admin用户登陆 oinone用户登陆 因为宠物达人的页面没有把性别字段放出来,我们看下数据库数据 auth模块扩展 在日常项目开发中,难免会碰到一些针对权限管理的特殊需求,或是为提升性能做的特殊逻辑。接下来我给大家介绍auth模块扩展性。 权限全局配置 对所有权限角色都做限制,而且不想让用户感知,可以实现PermissionFunApi接口,API接口实现的配置方式【只能用于支持全局的数据权限配置】 实现接口PermissionFunApi 将实现托管给SpringAOP 接口的具体实现看下图的代码 package pro.shushi.pamirs.demo.core.service; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.service.PermissionFunApi; import pro.shushi.pamirs.demo.api.model.PetTalent; @Component public class PetTalentPermissionFunApi implements PermissionFunApi { @Override public String permissionDomain(Object… args) { //获取当前组织中 return "name == '张学友'"; } @Override public String nameSpace() { return PetTalent.MODEL_MODEL; } } 不参与权限控制 如果某一接口不想做权限控制,则可以在启动工程的application-dev.yml文件中配置不需要权限过滤的接口 pamirs: auth: fun-filter: – namespace: demo.PetTalent fun: queryPage 换一个没有配置宠物达人权限的用户(除管理员以外)进入系统,则也可以看到数据。注意【权限全局控制】还是生效的 API 1. 获取当前用户对该模型的行权限 Result<String> result = CommonApiFactory.getApi(AuthApi.class).canReadAccessData("Model"); 返回值为 { 'data':"name=in=('hahaha')" 'success':true … } 用法 : 场景:前端发起的请求都会经过权限拦截,后端代码逻辑发起的数据请求都是不经过任何权限的过滤,但是某些特殊情况需要在后端代码逻辑发起的数据请求也带上权限过滤 入参:请求的模型 出参:Result 数据结构中data会存储一段字符串,该字符串为Rsql 将该Rsql追加到wrapper中 Result<String> result = CommonApiFactory.getApi(AuthApi.class).canReadAccessData("base.UeModule"); String data=result.getData(); QueryWrapper<UeModule> wrapper = Pops.query(); wrapper.setEntity(ueModule); if (!StringUtils.isBlank(data)) { wrapper.apply(data); }

    2024年5月23日
    1.6K00
  • 3.5.7.2 自定义母版

    母版是什么 在业务系统中,母版是一种全局通用的组件,用于包裹每个页面的元素。这个概念类似于 PPT 中的母版,它定义了页面的整体结构、样式和布局,使得系统具有一致的外观和风格。 特点: 全局通用:母版是全局性的,适用于系统中的每个页面,确保一致性的用户体验。 包裹元素:母版包裹每个页面的元素,定义了整体的结构和布局。 外观一致性:通过统一的母版设计,系统达到外观和风格的一致性。 与 PPT 母版的类比: 在母版的概念上,与 PPT 中的母版类似,都是用于定义整体结构和样式,确保每个页面都具有一致的外观。 默认母版范围: 图3-5-7-20 默认母版范围 作用场景 在系统中,我们提供了多个纬度的母版,包括全局、应用和页面纬度。这样的设计允许根据不同的业务场景选择合适的母版纬度,以满足不同层次的定制需求。 母版纬度: 全局母版: 覆盖系统中所有页面,确保全局的一致性和统一的用户体验。 定义全局性的结构、样式和布局。 应用母版: 适用于特定应用或模块,定制化程度介于全局和页面之间。 允许在不同应用间实现一定的差异化。 页面母版: 面向具体页面,提供最大的定制化空间。 允许在不同页面中定义不同的结构和样式。 选择纬度: 业务场景决定:根据具体的业务场景和需求选择合适的母版纬度。 定制化需求:如果需要全局一致性,选择全局母版;如果在应用层面有特定需求,选择应用母版;如果需要最大的灵活性和定制化,选择页面母版。 参数说明: 在系统中,使用 registerMask 注册母版时,需要提供两个参数。第一个参数是母版的 XML,第二个参数用于控制母版的注册行为,包括 module、moduleName、model 和 actionName。 母版 XML(第一个参数): 提供母版的 XML 描述,定义了母版的结构和样式。 控制参数(第二个参数): module:指定模块的名称。 moduleName:指定模块的显示名称。 model:指定母版所属的模型。 actionName:指定母版的操作名称,可以是字符串或字符串数组。 全局母版 可以使用 registerMask 注册母版。当第二个参数为 {} 时,即代表注册全局母版。 示例工程目录 以下是需关注的工程目录示例,并确保导入导出依赖正确: 图3-5-7-21 全局纬度注册母版工程目录示例 示例代码 以下是一个示例代码: import { registerMask } from '@kunlun/dependencies'; /** * mask: 在做系统时,我们通常会把外层的布局(菜单、顶部导航)等抽出公共组件,公共组件会抽离全局通用。类似ppt母版的概念即为Mask * registerMask 第二个入参为{}即注册了全局得母版 * * 和默认母版相比移除右上角消息、语言切换、用户 * `<block> * <widget widget="notification" /> * <widget widget="divider" /> * <widget widget="language" /> * <widget widget="divider" /> * <widget widget="user" /> * </block>` */ const registerGlobalMask = () => { registerMask(` <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask>`, {}); } registerGlobalMask(); 图3-5-7-22 全局纬度注册母版代码示例 复制这段代码执行下registerGlobalMask()试试看吧 效果 右上角消息、语言切换、用户被移除。 图3-5-7-23 全局纬度注册母版效果示例 应用母版 在系统中,可以使用 registerMask 注册母版。当第二个参数为 {moduleName: ‘resource’} 时,即注册了 resource(资源)应用的母版。 示例工程目录 以下是需关注的工程目录示例,更新导出./moduleMask: 图3-5-7-24 应用纬度注册母版工程目录示例 示例代码 以下是一个示例代码: /** * registerMask 第二个入参为{moduleName: 'resource'}即注册了resource(资源)应用的母版 * * resource来源: 通常是浏览器url上的module * * 和默认模版相比拿掉了菜单 * `<sidebar> * <widget widget="nav-menu" height="100%"…

    2024年5月23日
    1.6K00

Leave a Reply

Please Login to Comment