前端视图的元数据与数据的传递、交互

在阅读本篇文章之前,您需要学习以下知识点:

1: 元数据

视图的元数据

在日常开发中,我们会经常遇到自定义的字段、动作、视图需要界面设计器配置的数据,这些数据可能是当前页面的字段,也有可能动作,那么如何获取呢?

视图元数据分为两种:
1: 当前视图(metadataRuntimeContext)
2: 根视图(rootRuntimeContext)

那么这两种类型怎么区分呢?

举个例子:
1: 如果当前字段是在表单中,那么当前视图就是表单,根视图就表单的父级视图,如果只有一个表单视图,那么当前视图就是根视图。
2: 如果当前视图是表单,但是表单里面有个表格,对于表格字段而言,当前视图就是表格,根视图就是表单。

当前视图的元数据(metadataRuntimeContext)

在前端,我们通过 metadataRuntimeContext 来获取视图的元数据,例如:

export class CustomFormStringFieldSingleWidget extends FormStringFieldSingleWidget {
  protected mounted(): void {
    console.log(this.metadataRuntimeContext);
  }

    /**
  * 界面设计器配置的动作
  */
  @Widget.Reactive()
  protected get modelActions() {
    return this.metadataRuntimeContext.model.modelActions
  }

   /**
  * 界面设计器配置的字段
  */
  @Widget.Reactive()
  protected get modelFields() {
    return this.metadataRuntimeContext.model.modelFields
  }
}
属性名 类型 可选性 描述
viewAction RuntimeViewAction 是 运行时跳转动作(通过跳转动作创建的运行时上下文具备该属性)
module RuntimeModule 否 运行时模块
model RuntimeModel 否 运行时模型
view RuntimeView 否 运行时视图
viewLayout DslDefinition \| undefined 否 视图布局 DSL,从运行时视图解析获得
viewDsl DslDefinition \| undefined 否 视图模板 DSL,从运行时视图解析获得
viewTemplate DslDefinition 否 视图最终执行 DSL,从运行时视图解析获得或根据布局 DSL 和模板 DSL 合并生成
getModel (model: string, isBelong?: boolean) => GetModelResult \| undefined 否 获取模型,返回获取的模型和所在的运行时上下文
getModelField (data: string, isBelong?: boolean) => GetModelFieldResult \| undefined 否 获取模型字段,返回获取的模型字段和所在的运行时上下文
getRequestModelFields (options?: GetRequestModelFieldsOptions) => RequestModelField[] 否 获取请求字段
getDefaultValue () => Promise<Record<string, unknown>> 否 获取默认值
getInitialValue () => Promise<Record<string, unknown>> 否 获取初始值

运行时模型(model)

属性名 类型 可选性 描述
id string 是 模型 id
model string 否 模型编码
name string 否 技术名称
modelFields RuntimeModelField[] 否 模型字段
modelActions RuntimeAction[] 否 模型动作
type ModelType 是 模型类型
module string 是 模块编码
moduleName string 否 模块名称
moduleDefinition RuntimeModule 是 模块定义
pks string[] 是 主键
uniques string[][] 是 唯一键
indexes string[][] 是 索引
sorting string 是 排序
label string 是 显示标题
labelFields string[] 是 标题字段

可通过modelFields、modelActions获取界面设计器配置的模型字段、模型动作。

当前视图的 dsl(viewDsl)

视图的 DSL,从运行时视图解析获得,如果模型中的modelFields、modelActions获取不到的对应的字段、动作,那么可从该属性中获取

默认值(getDefaultValue)

用来获字段的默认值(界面配置器配置的)

根视图的元数据(rootRuntimeContext)

根视图的元数据结构跟当前视图的元数据类似。

视图数据

视图数据是贯穿于应用程序前端和视图层的关键数据,它承载了应用逻辑到用户界面之间的桥梁作用。

在 oinone 前端中,常见的视图数据分为如下几种:

1: 表单数据(formData)
2: 列表数据(dataSource)
3: 根视图数据(rootData)
4: 当前字段、动作所在的区域数据(activeRecords)
5: 按钮打开弹窗、抽屉的时候,按钮所在的区域数据(openerActiveRecords)

formData

formData 是一个对象,对象中的每个属性对应一个表单项,表单项的值就是该属性的值。所有的表单字段都可以通过this.formData来访问,也可以通过this.formData.xxx来获取某个字段的值。

dataSource

dataSource 是一个数组,数组中的每个元素对应一个列表项,列表项的值就是该元素。所有的列表字段都可以通过this.dataSource来访问,也可以通过this.dataSource[0].xxx来获取某个列表字段的值。

rootData

rootData 是一个数组,如果当前视图是列表,那么 rootData 是列表的数据源。如果当前视图是表单,那么 rootData 是表单的数据源,其中的第 0 项是当前表单的数据。

activeRecords

activeRecords 是一个数组

1: 表单视图的动作
    1.1: activeRecords 是表单的数据源,其中的第 0 项是当前表单的数据。
2: 表格视图上方的动作
    2.1: activeRecords 是当前选中的行数据,其中的第 0 项是对应的数据。
3: 表格的行内动作
    3.1: activeRecords 是当前行数据,其中的第 0 项是对应的数据。

openerActiveRecords

openerActiveRecords 是一个数组,用于保存当前页面的 opener 页面选中的数据。

1: 比如 行内动作 点击打开弹窗,弹窗里面的字段或者动作可以通过 openerActiveRecords 获取 行内 数据.
2: 比如 表单中的动作 点击打开弹窗,弹窗里面的字段或者动作可以通过 openerActiveRecords 表单 数据.

特殊数据

activeTreeContext 树组件的选中数据

activeTreeContext 树组件的选中数据,可以获取到选中的数据,以及选中的数据的父级数据,用于左树右表的视图.

常见场景:

场景 1: 左树右表的视图,表格上方的创建按钮点击打开弹窗,弹窗里面要获取左树选中的数据,用于创建数据.
方案:点击弹窗里面的创建按钮时,通过 this.metadataRuntimeContext.view?.activeTreeContext 获取到左树选中的数据

class CustomActon extends ServerActionWidget {
  protected async executeAction(action: RuntimeServerAction, submitValue: SubmitValue){
    const treeContext = this.metadataRuntimeContext.view?.activeTreeContext || {}
    submitValue.records = {
      ...submitValue.records,
      ...treeContext
    }
    super.executeAction(action, submitValue)
  }
}

场景 2: 左树右表的视图,表格上方的创建按钮点击打开新页面,新页面里面要获取左树选中的数据,用于创建数据.
方案:点击新页面里面的创建按钮时,可通过URL参数获取到左树选中的数据

class CustomActon extends ServerActionWidget {
  protected async executeAction(action: RuntimeServerAction, submitValue: SubmitValue){
    const treeContext = JSON.parse(this.getUrlParameters().context).activeTreeContext || {}
    submitValue.records = {
      ...submitValue.records,
      ...treeContext
    }
    super.executeAction(action, submitValue)
  }
}

searchBody 搜索条件、searchConditions 固定查询条件

参考文章

Oinone社区 作者:汤乾华原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/18209.html

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

Like (0)
汤乾华's avatar汤乾华数式员工
Previous 2024年10月8日 am10:37
Next 2024年10月8日 pm5:55

相关推荐

  • 「前端」关闭源码依赖

    问题背景 在 5.x 版本的 oinone 前端架构中,我们开放了部分源码,但是导致以下性能问题: 1.构建耗时增加​​:Webpack 需要编译源码文件 2.​​加载性能下降​​:页面需加载全部编译产物 3.​​冷启动缓慢​​:开发服务器启动时间延长 以下方案通过关闭源码依赖。 操作步骤 1. 添加路径修正脚本 1: 在当前项目工程根目录中的scripts目录添加patch-package-entry.js脚本,内容如下: const fs = require('fs'); const path = require('path'); const targetPackages = [ '@kunlun+vue-admin-base', '@kunlun+vue-admin-layout', '@kunlun+vue-router', '@kunlun+vue-ui', '@kunlun+vue-ui-antd', '@kunlun+vue-ui-common', '@kunlun+vue-ui-el', '@kunlun+vue-widget' ]; // 递归查找目标包的 package.json function findPackageJson(rootDir, pkgName) { const entries = fs.readdirSync(rootDir); for (const entry of entries) { const entryPath = path.join(rootDir, entry); const stat = fs.statSync(entryPath); if (stat.isDirectory()) { if (entry.startsWith(pkgName)) { const [pkGroupName, name] = pkgName.split('+'); const pkgDir = path.join(entryPath, 'node_modules', pkGroupName, name); const pkgJsonPath = path.join(pkgDir, 'package.json'); if (fs.existsSync(pkgJsonPath)) { return pkgJsonPath; } } // 递归查找子目录 const found = findPackageJson(entryPath, pkgName); if (found) return found; } } return null; } // 从 node_modules/.pnpm 开始查找 const pnpmDir = path.join(__dirname, '../', 'node_modules', '.pnpm'); for (const pkgName of targetPackages) { const packageJsonPath = findPackageJson(pnpmDir, pkgName); if (packageJsonPath) { try { const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJSON.main === 'index.ts') { const libName = packageJSON.name.replace('@', '').replace('/', '-'); packageJSON.main = `dist/${libName}.esm.js`; packageJSON.module = `dist/${libName}.esm.js`; const typings = 'dist/types/index.d.ts'; packageJSON.typings = typings; const [pwd] = packageJsonPath.split('package.json'); const typingsUrl = path.resolve(pwd, typings); const dir = fs.existsSync(typingsUrl); if (!dir)…

    2025年4月17日
    82300
  • 表格新增空行或者按照数据如何复制行

    场景 描述 新增按钮点击后表格出现空行,不带业务数据,需要有行内编辑 如何实现 第一步 在layout目录下新增copyTable组件,组件代码如下 import { BaseElementWidget, SPI, TableWidget, Widget } from '@kunlun/dependencies'; import { OioNotification } from '@kunlun/vue-ui-antd'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'copy-table-row' })) export class CopyTableWidget extends TableWidget { @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB"), {}) private tableCopySub; @Widget.BehaviorSubContext(Symbol("$$TABLE_DELETE_CB")) private tableDeleteSub; @Widget.Reactive() @Widget.Provide() protected get editorMode(): any { return 'manual' } public async copyRowData(row,currentRow) { // 获取vxetable 实例 const tableRef = this.getTableInstance()!.getOrigin(); if (tableRef) { // 有复制未保存数据,如何处理? const insertData = tableRef.getInsertRecords(); if(insertData.length > 0){ OioNotification.warning("警告","请检查未保存数据!") return; } const { row: newRow } = await tableRef.insertAt(row,currentRow) // 插入一条数据并触发校验, 其中字段名称可以替换 await tableRef.setEditCell(newRow, 'city') } } public async deleteRowData(row) { // 获取vxetable 实例 const tableRef = this.getTableInstance()!.getOrigin(); if (tableRef) { // 有复制未保存数据,如何处理? console.log(row, 'remove row') tableRef.remove(row) // 插入一条数据并触发校验 } } async mounted() { super.mounted(); this.tableCopySub.subject.next({copyCb: (row,currentRow) => this.copyRowData(row,currentRow)}) this.tableDeleteSub.subject.next({deleteCb: (row) => this.deleteRowData(row)}) } } 第二步 在action目录下覆盖新增按钮或者复制行按钮;代码如下 import {ActionWidget, ClickResult, ReturnPromise, SPI, Widget} from "@kunlun/dependencies"; @SPI.ClassFactory( ActionWidget.Token({ model: 'resource.k2.Model0000001211', // 替换对应模型 name: 'uiView57c25f66fac9439089d590a4ac47f027' // 替换对应action的name }) ) export class CopyRow extends ActionWidget{ @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB")) private tableCopySub; private tableCopyCb; @Widget.Method() public clickAction(): ReturnPromise<ClickResult> { // 按照某一条数据复制行, 按钮在行内 // let data = JSON.parse(JSON.stringify(this.activeRecords?.[0])); // 复制行删除id // if(data) { // delete…

    2024年7月15日
    3.5K00
  • 如何自定义表格字段?

    目录 一、表单字段注册vue组件实现机制 二、表格字段注册vue组件实现机制 三、机制对比分析 四、表格字段完整案例 表单字段注册vue组件实现机制 核心代码 public initialize(props) { super.initialize(props); this.setComponent(VueComponent); return this; } 表格字段注册vue组件实现机制 核心代码 @Widget.Method() public renderDefaultSlot(context: RowContext) { const value = this.compute(context); return [createVNode(CustomTableString, { value })]; } 因为表格有行跟列,每一列都是一个单独的字段(对应的是TS文件),但是每列里面的单元格承载的是Vue组件,所以通过这种方式可以实现表格每个字段对应的TS文件是同一份,而单元格的组件入口是通过renderDefaultSlot函数动态渲染的vue组件,只需要通过createVNode创建对应的vue组件,然后将props传递进去就行 上下文接口 interface RowContext<T = unknown> { /** * 当前唯一键, 默认使用__draftId, 若不存在时,使用第三方组件内置唯一键(如VxeTable使用{@link VXE_TABLE_X_ID}) */ key: string; /** * 当前行数据 */ data: Record<string, unknown>; /** * 当前行索引 */ index: number; /** * 第三方组件原始上下文 */ origin: T; } 机制对比分析 表单字段 vs 表格字段渲染机制对比表 对比维度 表单字段实现方案 表格字段实现方案 绑定时机 initialize 阶段静态绑定 renderDefaultSlot 阶段动态创建 组件声明方式 this.setComponent(Component) createVNode(Component, props) 上下文传递 通过类成员变量访问 显式接收 RowContext 参数 渲染控制粒度 字段级(表单控件) 单元格级 表格字段完整案例 import { SPI, ViewType, BaseFieldWidget, Widget, TableNumberWidget, ModelFieldType, RowContext } from '@kunlun/dependencies'; import CustomTableString from './CustomTableString.vue'; import { createVNode } from 'vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ ttype: ModelFieldType.String, viewType: [ViewType.Table], widget: 'CustomTableStringWidget' }) ) export class CustomTableStringWidget extends BaseTableFieldWidget { @Widget.Method() public renderDefaultSlot(context:RowContext) { const value = this.compute(context); // 当前字段的值 const rowData = context.data // 当前行的数据 const dataSource = this.dataSource // 表格数据 if (value) { // 自定义组件入口在此处 return [createVNode(CustomTableString, { value })]; } return []; } } <template> <div>当前值: {{value}}</div> </template> <script lang="ts"> import { defineComponent } from 'vue'…

    2023年11月6日
    2.9K00
  • 如何在自有前端工程中使用gql请求?

    场景 客户在个性化程度比较高的h5工程中想使用平台的服务端能力,这个时候需要调用后端的gql请求,此时需要引入平台的请求库依赖 npm的package.json中加入依赖 此文档以4.x举例,使用其他版本的请自行修改版本号 "dependencies": { "@kunlun/request": "~4.3.0", "@kunlun/state": "~4.3.0" } 使用示例 import { HttpClient } from ‘@kunlun/request’ const http = HttpClient.getInstance() http.setBaseURL(”) export const login = (data) => { const gqlBody = `mutation { pamirsUserTransientMutation { login(user: {login: “${data.username}”, password: “${data.password}”}) { broken errorMsg errorCode errorField } } }` return http.mutate(‘user’, gqlBody) } 注意事项 开发环境记得配置oinone请求的路由转发规则,这里以vite.config.js举例 import { defineConfig } from ‘vite’ export default defineConfig({ server: { port: 8088, open: true, proxy: { ‘/pamirs’: { changeOrigin: true, // 服务端请求地址 target: ‘http://127.0.0.1:8091’ } } } })

    2023年11月1日
    1.7K00
  • PC端、移动端默认Mask模板

    PC端 系统默认母版布局 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </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> 系统默认把多tabs放入视图内母版布局 <mask> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <block height="100%" flex="1 0 0" flexDirection="column" alignContent="flex-start" flexWrap="nowrap" overflow="hidden"> <multi-tabs inline="true" /> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </block> </container> </mask> 移动端 <mask> <widget widget="user" /> <widget widget="nav-menu" app-switcher="true" menu="true" /> <widget widget="main-view" height="100%" /> </mask>

    2024年12月11日
    2.2K00

Leave a Reply

Please Login to Comment