模型驱动 - (2) 模型及数据适配

244 阅读4分钟
原文链接: zhuanlan.zhihu.com
无所谓使用什么样的瓶子,我就喝 evian

模型的核心是内容,所以这里的模型指的就是组件的模型,而模型的组织形式可以多种多样


模型?有没有个让我简单看懂的例子?

目的是手工配置方便,turing内定义了一种JSON结构,如:

Dept 模型的定义如下:

let views = {
    //default这一段是基础属性会与以下其他的视图定义做合并输出
    "default": {
        id: { caption: "编号" },
        name: { caption: "名称" },
        pId: { caption: "父级部门编号" },
    },
    //以下视图定义根据业务需要,进行追加,每个属性即对于default的扩展
    "默认列表:table": {
        id: { },
        name: { minWidth:150 },
        pId: {},
    },
    "默认表单:form": {
        id: {},
        name: {},
        pId: { xtype:"tree", url:"/api/dept" },
    },
    "查询": {
    }
}
他们之间的关系是:继承。当然JSON里面比较通俗,就直接叫合并了

这样方便少写重复的配置属性,比如caption不论在表单、表格、搜索中的名字应该是统一的,一般就定义在default中。

而表格中可能会要显示几个额外的字段,就单独配置在表格模型里面。

那么大致使用方式应该如下:

let inst = new Dept();
let columns = inst.view("默认列表:table")

console.log(columns)

[
    {"caption":"编号","title":"编号","key":"id","minWidth":120},
    {"caption":"名称","title":"名称","key":"name","minWidth":150},
    {"caption":"父级部门编号","title":"父级部门编号","key":"pId","minWidth":120}
]

那么把这个模型直接赋值给组件即可

<Table :columns="columns" :data="rowData"></Table>


模型能转成组件要的属性吗?

你可能会注意到“默认列表:table”,这里还有一个规则:

主要在于表格、表单所需要得到的实际组件属性不同,需要使用类型来指明具体是对应上一章节中组件的种类。

:table属性如果独立出来,可以更高的复用模型,但是对于长期维护来看,每种模型区分开来可以更好的降低相互的耦合,降低维护时多余的担心。

这个是来自组件库提供出来的 adapter 映射来实现的

此处略过180字,完整示例在最后附上


该怎么用它呢?

模型仅仅是JSON定义,那么要将它运行起来,需要与后台数据对接才行

能看图的尽量不bb

用起来很简单的样子

let inst = new Dept();
let rowData = async inst.findAll({ parentId:"00001" })


后台该返回怎么样的数据结构才能接的上?

如:查列表的时候:
后台系统都是返回 [{},{},{}]
有的是返回{rows:[{}], total:1}

turing库在初始化时,可以重载请求干预的5个方法,实现后台结构返回数据可以对接上

这里还包括了请求字典数据、树形字典数据的干预方法。组件库中使用以上api,如:

import {ConnectItem,defaults} from'tg-turing'

defaults.afterFindAll[0] = function(data, action){
        if (data.data instanceof Array === true) {
            return data.data;
        } else {
            if (data.data.datas === undefined || data.data.datas[action.name] === undefined) {
                console.error("请求返回数据异常", action.url, action, data);
            }
            data.data.datas[action.name].count = data.data.datas[action.name].totalSize;
            data.data.datas[action.name].index = data.data.datas[action.name].pageNumber;
            data.data.datas[action.name].size = data.data.datas[action.name].pageSize;
            return data.data.datas[action.name];
        }
    }
很有意思的秘籍,exports.defaults.afterFindAll[0],被覆盖的函数使用的是数组,原因是我们遇到了这样一个问题

index.js 初始化后,对函数进行覆盖
exports.defaults.getDictData = function(){}

而组件库中调用 afterFindAll 时,仍然为原始值,用不上初始化后覆盖的函数实现。
参考 axios 的defaults实现,发现将函数套用至数组内再覆盖即可。

!!!但是说来奇怪,现在已无法再重现这样的问题了,也就是说不用数组,直接覆盖函数也可用!!!


完整的模型定义示例 - Dept.js

import {DataAdapter} from 'tg-turing'
import TgAntd from 'tg-turing-antd'   //基于antd风格的组件实现库
export default class extends DataAdapter {
    constructor() {
        super()
        //视图定义
        let views = {
            //default这一段是基础属性会与以下其他的视图定义做合并输出
            "default": {
                id: { caption: "编号" },
                name: { caption: "名称" },
                pId: { caption: "父级部门编号" },
            },
            //以下视图定义根据业务需要,进行追加,每个属性即对于default的扩展
            "默认列表:table": {
                id: { },
                name: { minWidth:150 },
                pId: {},
            },
            "默认表单:form": {
                id: {},
                name: {},
                pId: { xtype:"tree", url:"/api/dept" },
            },
            "查询": {
            }
        }
        this.actions.findAll.url = "/api/dept";
        this.actions.findAll.method = "get"
        this.actions.save.url = "/api/dept/save";
        this.actions.delete.url = "/api/dept/{id}";
        this.actions.delete.method = "delete"

        this.initView(views);
    }
    view(name, params) {
        let props = name.split(":")
        let antdtype = props[1];
        return TgTgAntd.Adapter(antdtype, this.getView(props), params);
    }
}

完整API:

wisedu/turinggithub.com图标


争论:这些模型的配置究竟应该存储在哪里?

两种方案我们同时在不同的项目中实践

对此我只能说:各有利弊

  • 存储在JS中:适合一个人负责完整项目。因为胜在简单
  • 存储在Controller:适合多人协作,专业分工的场景,培训后效率更高。因为复杂在了边界上

感受一下异步调用:

let turing = window['tg-turing']
let inst = new turing.DataAdapterFactory.create()
export default {
    data() {
        return {
            columns:[],
            datasourceRules:[]
        }
    },
    async mounted() {
        await inst.load("/dsManager.do", "cxggsjy")  //存储在后台controller中的cxggsjy模型
        let columns = inst.view('grid:table');
        columns.unshift({
            key: 'opt',
            title: "操作",
            width: 120,
            align: 'center'
        });
        this.columns = columns;

        //自定义的校验也需要异步补充上
        this.$nextTick(()=>{
            //校验数据源名称是否被占用,编辑的情况下,不用校验名称
            this.datasourceRules['group:[数据源信息]'].name.push({
                validator: (rule, value, callback, source) => {
                    ...
                },
                trigger: 'blur'
            })
        })
        this.$refs.grid.reload()
    },
};


为啥之前都没提到 load 函数呢?

这是异步加载模型的方法,但后台如何存储模型、获取模型、后台用什么技术实现,又出现了一堆新问题。

所以异步加载的适配器,实际上是针对后端存储解决方案的特殊 数据适配器。

这里给出我们继承的代码🌰:

https://github.com/wisedu/turing/blob/master/DataBind/EMAPDataAdapter.jsgithub.com


总结:

  • 前端 接 组件
  • 后端 接 数据