【界面设计器】左树右表

阅读之前

你应该:

名词解释

  • 主体:在视图中提供数据源的主要组件,并且所有动作都围绕着该主体展开。
  • 一级搜索:在表格视图中,上方直观可见的搜索区,为表格提供筛选功能。
  • 二级搜索:与一级搜索不同的是,其搜索条件是通过某些组件的行为追加到一级搜索条件之上的筛选功能。

概述

平台中对于左树右表提供了两种类型的展示形式。

表格视图中的左树右表,是以表格为主体,树组件为表格提供了二级搜索功能。选中树节点时将对表格追加节点的搜索条件,并重新执行查询。

树视图中,是以为主体,其展开的视图可以是表格表单详情等其他视图。

PS:不论是树、级联这些视图组件,还是树选择、级联这些字段组件,其配置数据结构的方式是不尽相同的。唯一的区别在于最终到达的目标模型来源不同。

场景1

为了方便接下来的描述,我们需要先构建一个基本的业务场景,这个场景中包含【商品】和【商品类目】两个模型。

在【商品】的表格左侧添加【商品类目】树,选择某个商品类目后,可以根据商品类目进行筛选,查询所属类目下的全部商品。

其中【商品类目】使用【卡片级联】的展示方式进行管理。

其模型定义如下:

商品(Item)
名称 API名称 业务类型 是否多值 长度(单值长度) 关联模型 关联字段
ID id 整数
编码 code 文本 128
名称 name 文本 128
所属类目 category 多对一 商品类目(ItemCategory) categoryId – id
所属类目ID categoryId 整数
商品类目(ItemCategory)
名称 API名称 业务类型 是否多值 长度(单值长度) 关联模型 关联字段
ID id 整数 128
编码 code 文本 128
名称 name 文本 128
上级类目 parent 多对一 商品类目(ItemCategory) parentId – id
上级类目ID parentId 整数

PS:实际业务场景中,【商品类目】通常使用编码进行关联,即parentCode - code;不仅如此,通常还会添加treeCode字段,以此来实现高效查询当前节点的所有子节点的能力。在演示模型中,我们不必关注这些内容。

创建【商品】视图

image.png

设置联动关系

这里我们需要配置的是【商品类目】的树结构,因此,在【第1级关联】中的模型选择【商品类目】。

在【商品类目】中是通过【上级类目】进行的自关联,因此,在【第1级关联】中的【自关联关系字段】选择【上级类目】。

在选中【商品类目】节点后,需要对右侧表格发起查询。其筛选条件是通过【商品】中的【所属类目】进行筛选的。因此,在【第1级关联】中的【表格关联关系字段】选择【所属类目】。那么,在表格发起查询前,会根据【所属类目】字段的关联关系配置自动添加筛选条件。

配置如下图所示:

image.png

创建【商品类目】视图

image.png

设置联动关系

这里我们需要配置的是【商品类目】的树结构(级联只是树结构的另一种表现形式)

细心的同学可能发现这里没有【表格关联关系字段】,因此,我们仅需配置【自关联关系字段】即可。

配置如下图所示:

image.png

为【商品类目】添加增删改查基础功能

与表格视图不同的是,行内动作区被放在了第一个卡片中的动作区,其他配置方式完全一致。

在这里需要理解的是,一个树节点对应的是表格中的一行。

image.png

【商品类目】使用展开视图进行编辑(可选)

打开【支持展开视图】开关,并设置展开视图。这里我们用表单视图进行编辑操作。

image.png

和其他表单一样,我们将必要的字段和动作拖入对应区域即可。

由于展开视图只会在选中节点时出现,因此我们仅需提供更新功能即可。

image.png

这里需要注意:

  • 提交动作默认打开了【返回上一页】的功能,在当前场景中,更新动作提交数据后,没有上一页需要返回,因此需要关闭【返回上一页】的开关。
  • 提交动作默认打开了【刷新当前视图】的功能,在当前场景中,更新按钮处于【展开视图】中,仅刷新当前视图是不够的,当数据发生变更时,我们需要将级联组件一并刷新,因此需要打开【刷新主视图】的开关。

image.png

PS:【编辑】动作和【展开视图编辑】功能是重复的,在使用时应该只选择其中一种。

【商品类目】限制仅支持四级,并为每一个卡片添加标题(可选)

移除【第1级关联】中的【自关联关系字段】,依次添加2、3、4级关联,选择【商品类目】模型,将自动选中【层级关联关系字段】为【上级类目】,并输入每一级关联的标题即可。

这里需要注意的是,在【第1级关联】中需要添加筛选条件,使其只能查询到根节点。

image.png

image.png

image.png

PS:这里的限制仅为交互上的限制,在创建/更新时,如果可以在服务端限制上级类目的选择,以及数据提交时的校验,效果更佳。

【商品类目】的【创建/编辑】使用弹窗打开(可选)

有时我们希望用户在页面中的操作尽可能的流畅,在表单规模较小的情况下,我们也可以使用【弹窗/抽屉】这类交互来优化用户体验。

image.png

小贴士:

  • 弹窗打开的方式提供三种页面设计模式,绑定已有页面、使用新页面、复制已有页面。
  • 使用新页面和复制已有页面的方式只能进行一次性的视图设计,无法进行复用。
  • 使用绑定已有页面的方式可以使得视图进行复用,但复用的视图也只能同步弹窗的内容部分,弹窗底部的动作仅会在创建动作时复制一次。
  • 跳转动作的打开方式以及页面设计模式等属于元数据信息,无法通过属性面板进行修改,因此只能通过重新创建新动作的方式进行修改。
  • 鉴于业务的复杂和多变,通常情况下我们只采用【绑定已有页面】的方式为弹窗设计内容部分。这样在交互发生变更时,可以更好的适应变化。

场景2

为了方便接下来的描述,我们需要再构建一个基本的业务场景,这个场景中包含【公司】和【部门】两个模型。

【公司】模型的管理能力使用标准的【增删改查】视图。(此处不进行演示)

在【部门】的表格左侧添加【公司】-【部门】树,在选择某个公司后,可以根据所属公司进行筛选,查询所属公司下的全部部门。在选择某个部门后,可以根据上级部门进行筛选,查询该部门下的子部门(不包含子部门的子部门)。

PS:如这里要求查询该部门下的全部子部门,需要服务端配合。

其模型定义如下:

公司(Company)
名称 API名称 业务类型 是否多值 长度(单值长度) 关联模型 关联字段
ID id 整数
编码 code 文本 128
名称 name 文本 128
部门(Department)
名称 API名称 业务类型 是否多值 长度(单值长度) 关联模型 关联字段
ID id 整数 128
编码 code 文本 128
所属公司 company 多对一 公司(Company) companyId – id
所属公司ID companyId 整数
上级部门 parent 多对一 部门(Department) parentId – id
上级部门ID parentId 整数

创建【部门】视图

image.png

设置联动关系

这里我们需要配置的是【公司】-【部门】的树结构,因此,在【第1级关联】中的模型选择【公司】,在【第2级关联】中的模型选择【部门】。

在【第2级关联】中的【层级关联关系字段】默认会选择一个可用字段,这里我们是通过【部门】中的【所属公司】进行关联的。

在【部门】中是通过【上级部门】进行的自关联,因此,在【第2级关联】中的【自关联关系字段】选择【上级部门】。

在选中【公司】节点后,需要对右侧表格发起查询。其筛选条件是通过【部门】中的【所属公司】进行筛选的。因此,在【第1级关联】中的【表格关联关系字段】选择【所属公司】。那么,在表格发起查询前,会根据【所属公司】字段的关联关系配置自动添加筛选条件。

在选中【部门】节点后,同样需要对右侧表格发起查询。其筛选条件是通过【部门】中的【上级部门】进行筛选的。因此,在【第2级关联】中的【表格关联关系字段】选择【上级部门】。那么,在表格发起查询前,会根据【上级部门】字段的关联关系配置自动添加筛选条件。

配置如下图所示:

image.png

树/级联配置详解

在体验过树/级联配置之后,我们需要具体的介绍每个配置在树构建时的作用,以便于理解一系列类似组件的配置。

配置解释
  • 模型:当前层级的模型。
  • 数据标题:树节点展示标题。
  • 筛选条件:当设置【自关联关系字段】时,会根据【自关联关系字段】的关联关系自动添加筛选条件,此时如果配置了筛选条件,将进行追加。当未设置【自关联关系字段】时,为查询单层级树节点的筛选条件。
  • 自关联关系字段:通过字段配置以及所在层级自动添加筛选条件用于查询下级节点的一种方式。
  • 层级关联关系字段:通过字段配置将当前层级与上一层级进行关联,自动添加筛选条件用于查询下级节点的一种方式。
  • 表格关联关系字段:在树节点选中时,通过字段配置自动添加筛选条件用于表格查询。
名词解释
  • 树(Tree):一种数据结构,用于描述一组具备父子关系的数据。
  • 节点(TreeNode):对于树中的每一个节点存储必要属性的对象。主要包括唯一标识(key)数据(value)父(parent)子(children)以及层级(level)等属性。
  • 根节点:没有父节点的节点。
  • 父节点:相对于子节点而言的节点。
  • 子节点:相对于父节点而言的节点。
  • 当前节点:用户行为触发时所操作的对于节点。
  • 选中节点:通过当前节点中的某些配置为数据提供者追加筛选条件,并让数据提供者执行刷新操作。
  • 展开节点:通过当前节点中的某些配置查询该节点的子节点。

树的层级配置

在配置面板,我们可以看到有第x级关联这样的分组。每一个分组中的全部配置都对应了树的一个层级所需的配置。

但需要理解的是,这里的层级并不是指运行时看到的树的真实层级,而是指配置时所需的层级。

比如:通过设置【自关联关系字段】,我们可以在这配置的一层级中,在运行时展开多个层级,直到无法查询到自关联的子节点为止。

自关联关系字段影响查询下级节点

在未配置【自关联关系字段】时,我们在运行时查询第一层树节点(通常也称为根节点)时,其查询条件可以表示为:

rsql: [${筛选条件}]

当配置了【自关联关系字段】时,我们在运行时查询第一层树节点时,其查询条件可以表示为:

rsql: parentId =isnull= true [and ${筛选条件}]

展开查询第二层树节点时,其查询条件可以表示为:

rsql: parentId == ${parent.id} [and ${筛选条件}]

PS:这里的parentId泛指【自关联关系字段】的relationFields属性,而不是固定的某个字段。当存在多个时,将通过and连接。这里的parent.id泛指上级节点中的对应的【自关联关系字段】的referenceFields属性。

字段的关联关系属性

类型定义:(这里仅展示必要的元数据类型定义)

export interface RuntimeRelationField {
    /**
     * 是否关系存储
     */
    relationStore: boolean;
    /**
     * 关联模型编码
     */
    references: string;
    /**
     * 关系字段(在当前模型中的字段)
     */
    relationFields: string[];
    /**
     * 关联字段(在关联模型中的字段)
     */
    referenceFields: string[];
}

export type RuntimeO2OField = RuntimeRelationField;

export interface RuntimeO2MField extends RuntimeRelationField {
    /**
     * 限制数量
     */
    limit?: number | string;
}

export type RuntimeM2OField = RuntimeRelationField;

export interface RuntimeM2MField extends RuntimeRelationField {
    /**
     * 多对多中间模型编码
     */
    through: string;
    /**
     * 多对多中间模型关系字段(与关系字段对应)
     */
    throughRelationFields: string[];
    /**
     * 多对多中间模型关联字段(与关联字段对应)
     */
    throughReferenceFields: string[];
    /**
     * 限制数量
     */
    limit?: string | number;
}

一对一(O2O)多对一(M2O)以及一对多(O2M)这三种类型的关联关系字段中,relationFieldsreferenceFields总是长度相等,且相同索引位置的字段总是配对的。

多对多(M2M)这种类型的关联关系字段中,relationFieldsthroughRelationFields总是长度相等,且相同索引位置的字段总是配对的,,referenceFieldsthroughReferenceFields总是长度相等,且相同索引位置的字段总是配对的。

其中relationFields表示当前模型中的字段,referenceFields表示关联模型中的字段。throughRelationFields表示在中间模型中与当前模型字段对应的字段,throughReferenceFields表示在中间模型中与关联模型字段对应的字段。

层级关联关系字段影响查询下级节点

从【第2级关联】开始,需要配置【层级关联关系字段】,将其与上一层级进行关联。

这里需要注意的是,如果【第1级关联】使用了【自关联关系字段】进行自关联,那么只有当无法通过自关联条件查询到子节点时,会自动转换为使用【层级关联关系字段】查询当前节点的子节点。

其查询条件可以表示为:

rsql: parentId == ${parent.id} [and ${筛选条件}]

PS:这里的parentId泛指【层级关联关系字段】的relationFields属性,而不是固定的某个字段。当存在多个时,将通过and连接。这里的parent.id泛指上级节点中的对应的【层级关联关系字段】的referenceFields属性。

构成完整关联关系*

在配置【联动关系】时,我们必然需要一些条件来规定一种怎样的配置才能在运行时正常运行。下面将介绍构成完整关联关系的一些基本条件和限制。

最终到达的目标模型

为了构建完整的关联关系,并且可以在业务使用中可以正常运行,我们对【联动关系】的配置进行了基础的限制。最终到达的目标模型就是保障配置有效性的基础。

在不同的视图组件中,最终到达的目标模型也有所区别。下面列举了目前现有的几种情况。

  • 表格视图中的左树右表,其最终到达的模型为表格对应的模型,也就是当前视图模型。其要求最后一级关联必须通过【表格关联关系字段】与表格建立关联关系。

  • 树视图中,其最终到达的模型为对应的模型,也就是当前视图模型。其要求最后一级关联中的【模型】必须是该模型。

  • 字段树下拉级联组件中,其最终到达的模型为字段对应的关联模型,也就是字段的references对应的模型。其要求最后一级关联中的【模型】必须是该模型。

PS:一般而言,未构成完整关联关系的提示都是由于最终到达的目标模型限制引起的。

层级关联关系的模型限制

平台中的模型元数据定义了模型的拓扑结构,模型与模型之间通过关联关系字段产生联系,然后形成模型的拓扑结构。在这个基础上,每一层级的模型也就有了可选范围。

在【第1级关联】中的【模型】可以选择任意模型。

从【第2级关联】开始,可选的【模型】就有了限制。其只能选择与上一级关联的模型可以通过关联关系字段关联的模型。由于元数据定义的模型的拓扑结构过于复杂,我们无法通过适当的条件进行筛选。因此,在用户交互上我们并没有限制这一点,这就要求用户明确需要构建一个怎样的树结构,并根据树结构进行合理的配置。

PS:当模型选择错误时,【层级关联关系字段】未选择的情况,也是未构成完整关联关系的情况之一。

层级关联关系的字段限制

由于字段的关联关系具有方向性,在四种关联关系类型中,具备明确方向性的类型为一对多(O2M)多对一(M2O),在子节点的查询时,我们假定节点展开一定是向的一方进行展开的。因此,【层级关联关系字段】也只能使用这两种关联关系类型的字段。

在其他两种关联关系类型中,我们无法确定其展开的方向,也就无法提供自动化的查询机制。

元数据模型的拓扑结构复杂性(扩展内容,仅作了解即可)
  • 模型继承:模型存在扩展继承、代理继承等继承方式,那么当前模型就不仅仅是这一个模型,包括它的全部父模型和子模型。
  • 单向关联:当模型是通过单向关联到当前模型时,我们不仅需要从当前模型出发查找一些对应的模型外,还需要从其他模型出发查找一些与当前模型关联的模型。

由于这些复杂性共同导致最终筛选的结果集和所有模型的结果集几乎一致,其在便于用户选择的需求下表现的并不好,因此我们放弃了筛选。

选择和配置的一般步骤
  • 明确业务需求,需要构建一个怎样的树结构
  • 确定最终到达的目标模型
  • 定义模型元数据,以符合业务需求。
  • 确定展示主体是表格还是,分别选择不同的视图。(字段组件可跳过)
  • 确定从什么模型开始选择,最终可以通过层级关联关系字段到达最终目标模型的。

Oinone社区 作者:nation原创文章,如若转载,请注明出处:https://doc.oinone.top/designer/uidesigner/63.html

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

(2)
nationnation
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

  • 左树右表的配置,无代码方式实现

    场景 以公司的部门与员工为示例(需要建一个自关联字段) 第一步先在模型设计器界面创建一个员工模型 第二步在模式设计器界面创建一个部门模型,并建自关联字段与他表模型关联字段 1.创建部门模型 2.创建自关联字段 3.建关联其他模型字段 第三步在界面设计器页面设计树表 1.创建树表页面 2.设计树表 3.设计创建表单 第四步查看效果 1.先创建部门 2.再创建员工 3.最终树表效果

    2023年11月20日
    00
  • 弹窗或抽屉表单视图rootRecord获取不到对应的数据

    在平台默认的实现中,rootRecord 代表的是根视图的数据。比如,在表格页面点击按钮打开了弹窗,弹窗里面包含一个表单视图,但是该视图获取 rootRecord 却是最外层的视图数据。 如果期望 rootRecord 数据是弹窗的视图数据,需要手动修改表单的 rootRecord。下面的代码演示了如何重写 rootData 以确保其数据是弹窗的数据: @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Form, widget: 'MyCustomFormWidgetFormWidget' }) ) export class MyCustomFormWidgetFormWidget extends FormWidget { @Widget.Reactive() @Widget.Provide() public get rootData(): any[] | undefined { return this.activeRecords; } } 上述代码重写了 rootData,这样就可以确保 rootData 的数据是弹窗的数据。 接下来就是注册: registerLayout( ` <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="MyCustomFormWidgetFormWidget" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> `, { viewType: ViewType.Form, model: '弹窗模型', viewName: '弹窗视图名称' } )

    2023年11月13日
    00
  • 自定义字段的数据联动

    某种情况下,开发人员期望自定以的字段发生变化后,需要修改其他的字段,这篇文章从两个维度来讲解如果处理数据的联动 界面设计器配置 1: 在界面设计器页面中的的组件区域找到自定义的字段,设计元件 2: 在模型区域,搜索提交方式,如果找到了,就把该字段拖拽进来, 如果找不到,就在元件中的组件区域,拖拽一个文本字段,按照下面的配置进行配置,然后保存 图一是找到了对应的字段图二是找不到对应的字段 【图一】 【图二】 图二的字段编码必须是constructDataTrigger 3: 从模型区搜索联动函数,将其拖拽进来 3: 从模型区搜索提交数据,将其拖拽进来4: 从模型区搜索提交字段,将其拖拽进来 5: 发布 (记得刷新页面哦) 最后再到对应的设计器页面,选中该字段,进行配置 提交方式为blur或者change , 需要开发者手动调用该方法 this.blur()或者this.change(value) // 字段对应的ts文件 class MyField extends FormFieldWidget { onChangeValue(val) { // this.change(val) // this.blur() } } 联动函数就是要调用的后端函数 提交数据分为:变更字段 -> 发生变化后的字段当前视图字段 -> 当前视图所有的字段指定字段 -> 指定字段,如果配置的指定字段,那么提交字段的配置就要输入对应的字段 代码配置 平台也支持通过代码的方式修改字段 // 字段对应的ts文件 class MyField extends FormFieldWidget { onChangeValue(val) { // 修改字段本身的值 this.change(val) // 修改其他字段的值 this.formData.otherField = 'value' this.reloadFormData$.subject.next(true); } }

    2023年11月9日
    00
  • 创建与编辑一体化

    在业务操作中,用户通常期望能够在创建页面后立即进行编辑,以减少频繁切换页面的步骤。我们可以充分利用Oinone平台提供的创建与编辑一体化功能,使操作更加高效便捷。 通过拖拽实现表单页面设计 在界面设计器中,我们首先需要设计出对应的页面。完成页面设计后,将需要的动作拖入设计好的页面。这个动作的关键在于支持一个功能,即根据前端传入的数据是否包含id来判断是创建操作还是编辑操作。 动作的属性配置如下: 前端自定义动作 一旦页面配置完成,前端需要对这个动作进行自定义。以下是一个示例的代码: @SPI.ClassFactory( ActionWidget.Token({ actionType: [ActionType.Server], model: '模型', name: '动作的名称' }) ) export class CreateOrUpdateServerActionWidget extends ServerActionWidget { @Widget.Reactive() protected get updateData(): boolean { return true; } } 通过以上步骤,我们实现了一个更智能的操作流程,使用户能够在创建页面的同时进行即时的编辑,从而提高了整体操作效率。这种创建与编辑一体化的功能不仅使操作更加顺畅,同时也为用户提供了更灵活的工作流程。

    2023年11月21日
    00
  • 前端低无一体使用教程

    介绍 客户在使用oinone平台的时候,有一些个性化的前端展示或交互需求,oinone作为开发平台,不可能提前预置好一个跟客户需求一模一样的组件,这个时候我们提供了一个“低无一体”模块,可以反向生成API代码,生成对应的扩展工程和API依赖包,再由专业前端研发人员基于扩展工程(kunlun-sdk),利用API包进行开发并上传至平台,这样就可以在界面设计器的组件里切换为我们通过低无一体自定义的新组件。 低无一体的具体体现 “低”— 指低代码,在sdk扩展工程内编写的组件代码 “无”— 指无代码,在页面设计器的组件功能处新建的组件定义 低无一体的组件跟直接在自有工程内写组件的区别? 低无一体的组件复用性更高,可以在本工程其他页面和其他业务工程中再次使用。 组件、元件介绍 元件 — 指定视图类型(viewType) + 指定业务类型(ttype)字段的个性化交互展示。组件 — 同一类个性化交互展示的元件的集合。组件是一个大一点的概念,比如常用的 Input 组件,他的元件在表单视图有字符串输入元件、密码输入元件,在详情和表格展示的时候就是只读的,页面最终使用的其实是元件。通过组件+ttype+视图类型+是否多值+组件名可以找到符合条件的元件,组件下有多个元件会根据最优匹配规则找到最合适的具体元件。 如何使用低无一体 界面设计器组件管理页面添加组件 进入组件的元件管理页面 点击“添加元件” 设计元件的属性 这里以是否“显示清除按钮”作为自定义属性从左侧拖入到中间设计区域,然后发布 点击“返回组件” 鼠标悬浮到卡片的更多按钮的图标,弹出下拉弹出“低无一体”的按钮 在弹窗内点击“生成SDK”的按钮 生成完成后,点击“下载模板工程” 解压模板工程kunlun-sdk.zip 解压后先查看README.MD,了解一下工程运行要点,可以先运行 npm i 安装依赖 再看kunlun-plugin目录下已经有生成好的组件对应的ts和vue文件 下面在vue文件内增加自定义代码,可以运行 npm run dev 在开发模式下调试看效果 <template> <div class="my-form-string-input"> <oio-input :value="realValue" @update:value="change" > <template #prepend>MyPrepend</template> </oio-input> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import { OioInput } from '@kunlun/vue-ui-antd'; export default defineComponent({ name: 'customField1', components: { OioInput }, props: { value: { type: String }, change: { type: Function }, }, setup(props) { const realValue = ref<string | null | undefined>(props.value); return { realValue }; } }); </script> <style lang="scss"> .my-form-string-input { border: 1px solid red; } </style> 确定改好代码后运行 npm run build,生成上传所需的js和css文件 可以看到 kunlun-plugin目录下多出了dist目录,我们需要的是 kunlun-plugin.umd.js 和 kunlun-plugin.css 这2个文件 再次回到组件的“低无一体”管理弹窗页面,上传上面生成的js和css文件,并点击“确定”保存,到这里我们的组件就新增完成了。 下面我们再到页面设计器的页面中使用上面设计的组件(这里的表单页面是提前准备好的,这里就不介绍如何新建表单页面了) 将左侧组件库拉直最底部,可以看到刚刚新建的组件,将其拖至中间设计区域,我们可以看到自定义组件的展示结果跟刚刚的代码是对应上的(ps: 如果样式未生效,请刷新页面查看,因为刚刚上传的js和css文件在页面初始加载的时候才会导入进来,刚刚上传的动作未导入新上传的代码文件),再次点击设计区域中的自定义组件,可以看到右侧属性设置面板也出现了元件设计时拖入的属性。 最后再去运行时的页面查看效果,与代码逻辑一致! 注意事项 为什么我上传了组件后页面未生效? 检查前端启动工程的低无一体是否配置正确

    2023年11月6日
    00

Leave a Reply

登录后才能评论