olap分析平台的设计与实现(二十一)- 表单模式定义

构建与解析OLAP分析表单:维度布局与数据查询
本文详细介绍了如何构建和解析在线分析处理(OLAP)分析表单,包括布局概念、查询层次、布局类型的查询、成员获取以及表单数据的处理。在表单设计中,涉及行、列、页面和视点布局,通过不同的维度成员组合形成复杂的数据结构。同时,文章提到了前端和后端交互,如数据校验、维度成员的增删以及表单状态的管理。此外,还涵盖了缓存机制、数据验证以及表单创建和编辑的相关操作。

界面效果 :

 

分析表单布局的概念:

第一小节,我们已经介绍过布局的概念,这里以图再说明如下:

                                                                  (假如对维度不进行分组) 

                                                (上图中,分组的情况有7个布局 (不含页面、视点相关布局))

 页面、视点可以看成另外隐藏在表单背后的维度的行、列(不是准确描述)。

其余参考我前面写的:非税olap分析平台的设计与实现(一)--数据仓库模型中,相关内容。

整体而言,查询分为三个层次,1)布局类型上的查询、2)特定布局类型、特定组上的查询  3)特定布局查询

获取特定表单 特别布局类型上的List<List<List<DimMember>>> getFormMbrsAsLayoutGroup(formId,layoutType...);

获取特定表单 特别布局类型 特定组     List<List<DimMember>> tempResult = getFormMbrsAsLayout(formId,layoutType,groupId......);

特定布局上的成员的查询:

     这样分2种情况:

    List<DimMember> layoutMbrs =getMemberWithQueryType(...)  这种情况不展开查询函数;

   

     List<DimMember> layoutMbrs =getMembersByQueryType(...)  这种情况展开查询函数;

实现(由顶层到细节):

1、取得行/列上的集合:

List<List<List<DimMember>>> dimMembers_col = getFormMbrsAsLayoutGroup(form, MConstants.LAYOUT_COL, true);

2、取得一个布局类型的组上的集合,这里布局类型指的是行、列、页面、视点,关键参数为 form、维度类型、组号:

List<List<DimMember>> tempResult = getFormMbrsAsLayout(form,
                    layoutType, groupPosition, isOpenQueryType);

3、取得一个具体布局类型上 特定组的的布局,由具体布局,获得dimession,.另外一个方法是:考虑到所有组的维度dimesssion 是否也可以根据行列类型,获取所有组的dimession,关键参数为 form、维度类型、组号

List<CFormLayout> getAxisFormLayoutList(int formId,
												   int groupPosition, int layoutType)

4、上一步获取到的是 :一个特定表单、特定布局类型、特定组上布局信息,考虑到在一个表单 的特定布局上,所有组的类 布局信息是一样的,应该也有其他办法获取相关数据。现在遍历上一步获取到的布局信息,获取一个组内的所有维度成员信息。

List<List<DimMember>> mbrs = new ArrayList<List<DimMember>>();
......
List<DimMember> layoutMbrs = new ArrayList<DimMember>();       
 for (int i = 0; i < layouts.size(); i++) {
            //取得一个特定布局(特定组、特定组内特定的布局)的成员集合List<DimMember>
            layoutMbrs = getLayoutMbrs(form.getCubeId(), layouts.get(i),
                    isOpenQueryType);
            mbrs.add(layoutMbrs);
        }

5、获取一个布局上,具体维度成员集合:

layoutMbrs = getLayoutMbrs(form.getCubeId(), layouts.get(i),
                    isOpenQueryType);

这一步,逻辑略复杂。

首先是要获取特定布局上一个维度成员的集合,然后对这个结果进行相关处理::

List<CFormMbr> formMbrs = getFormLayoutMembersByLayoutId(layout.getId());

6、getFormLayoutMembersByLayoutId方法中,通过调用getFormLayoutMembersByLayoutId获取维度成员集合,getFormLayoutMembersByLayoutId方法的实现:

	/**
	 * 通过布局ID获取布局成员列表
	 * 
	 * @param layoutId
	 * @return
	 */

	@Override
	public List<CFormMbr> getFormLayoutMembersByLayoutId(int layoutId) {
		List<CFormMbr>  cFormMbrs=new ArrayList<CFormMbr>();
//方法一 :从数据库当中获取数据
		//CFormMbr fomMmeber = new CFormMbr();
//		fomMmeber.setLayoutId(layoutId);
//		Example<CFormMbr> example = Example.of(fomMmeber);
//		List<CFormMbr> formMembers=formMbrMapper.findAll(example);
//		return formMembers;

		//方法二:从缓存当中获取这个数据
		try {
			//cFormMbrs=(List<CFormMbr>)cacheHelp.getObjectInCache(Constants.C_FORMMbr,new C_FormMbr_layoutId_PK(false),layoutId);
			cFormMbrs=(List<CFormMbr>)cacheHelp.getObjectInCache(CFormMbr.class,new C_FormMbr_layoutId_PK(false),layoutId);
		} catch (CacheException e) {
			e.printStackTrace();
		}
		return cFormMbrs;
	}

7.getFormLayoutMembersByLayoutId方法中,对特定布局的成员信息(CFormMbr),进行遍历处理:

        for (int i = 0; i < formMbrs.size(); i++) {
            boolean isCompare = false;  //?
            DimMember member = null;
            memberId = formMbrs.get(i).getMemberId();
            queryType = formMbrs.get(i).getQueryType();
            if (isOpenQueryType) {   //展开的情况
                if (formMbrs.get(i).getType() == MConstants.FORM_MBR_TYPE_VAR) {
                    //todo
                      .....
                } else {
                    tempMembers = memberEval.getMembersByQueryType(queryType, memberId, cubeId, layout.getDimId());
                    if (tempMembers != null && tempMembers.size() > 0) {
                        Iterator<DimMember> memberIterator = tempMembers.iterator();
                        while (memberIterator.hasNext()) {
                            DimMember beAddM = memberIterator.next();
                            boolean isHas = false;
                            for (DimMember temp : members) {
                                if (temp.getMemberId() == beAddM.getMemberId()) {
                                    isHas = true;
                                    break;
                                }
                            }
                            if (isHas) {
                                memberIterator.remove();
                            }
                        }
                    }
                    members.addAll(tempMembers);
                }
            } else {  //closed node

                .....
            }
......
        }

8.下面,我们只讨论节点是全展开,且是普通节点的情况,关键是下面的的方法,根据memberId 和 query Type,返回相应节点集合,如这个节点quey type是“子代包含”则把相应节点全部返回。 

tempMembers = memberEval.getMembersByQueryType(queryType, memberId, cubeId, layout.getDimId());

9.memberEval.getMembersByQueryType方法中,首先要把CFormMbr转为DimMember对象:

DimMember curMember=getMemberInDimByMemberId(memberId, dimId);

具体实现(方法一:从数据库中查的方式):

	/**
	 * 获取维度成员
	 * @param memberId
	 *  维度成员ID
	 * @param dimId
	 *            维度ID
	 * @return
	 */
	@Override
	public DimMember getMemberInDimByMemberId(int memberId, int dimId) {
			DimMember object=null;
			CDimension dimension = getDimInstanceById(dimId);
			int obj_type=dimension.getObjType();
		switch (obj_type) {
			case MConstants.DIM_ACCOUNT:
				object =accountMapper.getOne(memberId);
				break;
			case MConstants.DIM_ENTITY:
				object =entityMapper.getOne(memberId);
				break;
			case MConstants.DIM_YEAR:
				object =yearMapper.getOne(memberId);
				break;
			case MConstants.DIM_PERIOD:
				object =periodMapper.getOne(memberId);
				break;
			case MConstants.DIM_VERSION:
				object =versionMapper.getOne(memberId);
				break;
			case MConstants.DIM_SCENARIO:
				object=scenarioMapper.getOne(memberId);
				break;
			case MConstants.DIM_CURRENCY:
				object =curencyMapper.getOne(memberId);
				break;
		}
			return object;
	}

从缓存中查的方式:略

构建formGrid,最后重要形成三个数组,一个描述行标表头部分,一个描述列表头部分,一个描述表格数据区部分。行标题背后是行维,列表头后面是列维,表格区背后是事实数据。

前端表单模型定义:

    constructor(props) {
        super(props)
        this.state = {
            formFolders: [],   //表单文件夹 左边表单数的数据
            selectedFolderId: -1,
            selectedFoldName: "",    //文件夹名称  新增加的时候 设置为""
            modifyFoldName: "",  //传给编辑框--文件夹名称
            modalTitle: '',
            isModalVisible: false,
            selectedFormId: -1,  //选中的表单id 弃用?
            selectedFormIds: [],  //选中的表单id 数组 存放的是key
            isModalVisibleForFold: false,  //文件夹

            status: -1,  //
            forms: [],  //表单
            isModalVisibleForForm: false, //表单编辑
            isModalVisibleForSelDimMbr: false,//选择维度成员

            //下面是表单详细
            //formId:-1,  用selectedFormId代替
            formId: 0,   //传递到Modal当中
            formName: "",  //报表名称
            reportDesc: "",  //报表描述
            remark: "",       //说明
            scale: 2,
            // dims:[],
            colDims: [],
            rowDims: [],
            pageDims: [],
            viewDims: [],

            dims_option: [],
            dims_all: [],

            rowLayouts: [],
            colLayouts: [],
            pageLayouts: [],
            viewLayouts: [],
            // colMembers:[],
            // rowMembers:[]

            treeData_forSelDimMbr: [],   //供选择的维度成员

            targetFormLayout_Mbrs: [],   //target dimession member

            selectedMbr: {},  //选中的维度成员
            selectedFunId: 0,   //选中的方法
            funList: [
                '成员',......
            ],

            layout_type: 0,  //表单tab
            group_no: 0,//布局的组编号
            index_layoutMbr: 0,  //index_dims  一个具体布局的index    这个名字没起好

            formDefStatus: -1 //表单状态呢 0 是编辑  1是新增
        }
    }


创建一个表单要完成的工作:

根据前端数据,  设置List<List<List>>

搞个modal   创建弹出

保存成功后,是从后端刷还是从前端直接增加数据?我是从后端重新查询一次。

涉及的实体:

  1. c_form
  2. 布局:CFormLayout
  3. CFormMbr
  4. C_Description

为简化问题 暂时不管别名

表单的类别管理:

 用antd 树实现:

{
  "code": "200",
  "data": {
    "id": "-1",
    "text": "全部",
    "state": {
      "opened": true
    },
    "checked": true,
    "children": [
      {
        "key": 40441,
        "id": "40441",
        "text": "数据分析",
        "title": "数据分析",
        "checked": false,
        "children": [
          {
            "key": 40442,
            "id": "40442",
            "text": "湖北非税",
            "title": "湖北非税",
            "checked": false,
            "parentId": "40441",
            "hasParent": true,
            "hasChildren": false,
            "btn": false,
            "levelnum": 0
          }
        ],
        "parentId": "0",
        "hasParent": false,
        "hasChildren": true,
        "btn": false,
        "levelnum": 0
      }
    ],
    "parentId": "",
    "hasParent": false,
    "hasChildren": true,
    "btn": false,
    "levelnum": 0
  }
}

校验:校验所有维度 是否在表单中存在

Description 表中,就id及string,表的描述及编制说明都用这个表,但id不同,form对象需要记住这2个id

getForms(formfolderId)

前端传给后端的是dimId,不是dimession. 

/**
 * 以下常量对应com_FORMLAYOUT.LAYOUT_TYPE
 */
static public final int LAYOUT_VP = 0;    //视点
static public final int LAYOUT_PAGE = 1;  //页面
static public final int LAYOUT_ROW = 2;   //行
static public final int LAYOUT_COL = 3;   //列

表单设计相关动作:

行维度删除:

1、 删除state当中的值

2、添加到可选择的维度集合

dims_option:[] 这个当中包含可选择的维度

删除维度用以下方法:

       const  delDim=(dimId)=>{
           console.log("delete dimession id:"+dimId);
       }
                                                        <List.Item
                                                            actions={[<Button key="list-dimession-delete"
                                                                              icon={<DeleteRowOutlined/>}
                                                                              shape="circle"
                                                                              onClick={delDim(item.id)}
                                                            />]}>{item.name}
                                                         </List.Item>

查询表单返回(getForm(formId))

{
  "code": "200",
  "data": {
    "id": 600148,
    "cubeId": 0,
    "name": "测试表单001",
    "desc": "表单描述测试001",
    "remark": "说明",
    "scale": 0,
    "folderId": 0,
    "rowLayout": {
      "dims": [
        111,
        112
      ],
      "layouts_xy": [
        {
          "position": 0,
          "groupName": "",
          "theLayouts": [
            {
              "position": 0,
              "theLayoutName": "",
              "formLayout_Mbrs": [
                {
                  "id": 600076,
                  "memberId": 0,
                  "queryType": 0,
                  "type": 0,
                  "name": "jj001"
                }
              ]
            },
            {
              "position": 0,
              "theLayoutName": "",
              "formLayout_Mbrs": [
                {
                  "id": 420201,
                  "memberId": 0,
                  "queryType": 0,
                  "type": 0,
                  "name": "黄石市市本级"
                }
              ]
            }
          ]
        }
      ]
    },
    "colLayout": {
      "dims": [
        113,
        114
      ],
      "layouts_xy": [
        {
          "position": 0,
          "groupName": "",
          "theLayouts": [
            {
              "position": 0,
              "theLayoutName": "",
              "formLayout_Mbrs": [
                {
                  "id": 60198,
                  "memberId": 0,
                  "queryType": 0,
                  "type": 0,
                  "name": "2012年"
                }
              ]
            },
            {
              "position": 0,
              "theLayoutName": "",
              "formLayout_Mbrs": [
                {
                  "id": 60004,
                  "memberId": 0,
                  "queryType": 0,
                  "type": 0,
                  "name": "3月"
                }
              ]
            }
          ]
        }
      ]
    },
    "pageLayout": {
      "dims": [
        116,
        117
      ],
      "layouts_pv": [
        {
          "position": 0,
          "theLayoutName": "",
          "formLayout_Mbrs": [
            {
              "id": 60256,
              "memberId": 0,
              "queryType": 0,
              "type": 0,
              "name": "实际"
            }
          ]
        },
        {
          "position": 0,
          "theLayoutName": "",
          "formLayout_Mbrs": [
            {
              "id": 60250,
              "memberId": 0,
              "queryType": 0,
              "type": 0,
              "name": "战略制定"
            },
            {
              "id": 60251,
              "memberId": 0,
              "queryType": 0,
              "type": 0,
              "name": "编制版本"
            }
          ]
        }
      ]
    },
    "viewLayout": {
      "dims": [
        115
      ],
      "layouts_pv": [
        {
          "position": 0,
          "theLayoutName": "",
          "formLayout_Mbrs": [
            {
              "id": 60163,
              "memberId": 0,
              "queryType": 0,
              "type": 0,
              "name": "人民币"
            }
          ]
        }
      ]
    },
    "colDims": [
      {
        "id": 113,
        "name": "年",
        "objType": 3,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 3,
        "lastModifyTime": "2018-06-12T14:19:41.000+0000",
        "usedIn": 7,
        "type": 2
      },
      {
        "id": 114,
        "name": "期间",
        "objType": 4,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 4,
        "lastModifyTime": "2018-06-12T14:19:41.000+0000",
        "usedIn": 7,
        "type": 2
      }
    ],
    "rowDims": [
      {
        "id": 111,
        "name": "科目",
        "objType": 1,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 0,
        "lastModifyTime": "2018-06-12T14:19:41.000+0000",
        "usedIn": 7,
        "type": 1
      },
      {
        "id": 112,
        "name": "组织",
        "objType": 2,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 2,
        "lastModifyTime": "2018-06-12T14:35:09.000+0000",
        "usedIn": 7,
        "type": 1
      }
    ],
    "pageDims": [
      {
        "id": 116,
        "name": "场景",
        "objType": 6,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 6,
        "lastModifyTime": "2018-06-12T14:19:41.000+0000",
        "usedIn": 7,
        "type": 1
      },
      {
        "id": 117,
        "name": "版本",
        "objType": 7,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 7,
        "lastModifyTime": "2018-06-18T14:14:47.000+0000",
        "usedIn": 7,
        "type": 1
      }
    ],
    "viewDims": [
      {
        "id": 115,
        "name": "币别",
        "objType": 5,
        "parent": 0,
        "generation": 0,
        "hasChildren": 0,
        "description": 0,
        "queryType": 0,
        "formulaType": 0,
        "position": 5,
        "lastModifyTime": "2018-06-12T14:19:41.000+0000",
        "usedIn": 7,
        "type": 1
      }
    ],
    "description": 0,
    "setFormShuoMingId": 0,
    "approval": 0,
    "showway": 0
  }
}

去掉rowLayout 下的dims[] 相关元素方法:

遍历layouts_xy下每个组theLayouts 的特定位置上的布局,其实是根据删除dimession的index确认的数据。

位置(position):

up、down 

1、必须有position信息,up、down

 移动位置:

        //移动位置
        const moveUp_dim = (index) => {
            if (index === 0)
            {
                message.info("已经首部,不能再上移。")
                return;
            }
            let _rowDims = this.state.rowDims;
            let _rowLayouts = this.state.rowLayouts;
            [_rowDims[index - 1], _rowDims[index]] = [_rowDims[index], _rowDims[index - 1]];
            _rowLayouts.map(g => {
                [g.theLayouts[index - 1], g.theLayouts[index]] = [g.theLayouts[index], g.theLayouts[index - 1]];
            });
            this.setState({rowDims: _rowDims, rowLayouts: _rowLayouts})
        }

动态添加组的办法:

在xy中直接增加一组数据

  1. 加入一个组
  2. 组下面加布局信息
select_dim_mbr  pareameter  is:

选择的维度成员值添加到右边的集合当中:

 根据选择的节点构造对象ojbect,

把object存放到state。

目标是:

this.state.targetFormLayout_Mbrs
         let _o={
                    id:this.state.selectedMbr.id,
                    name:this.state.selectedMbr.name,
                    queryType:this.state.selectedFunId
            }

右边必须构建对象!!!

            let _o={
                    id:this.state.selectedMbr.id,
                    name:this.state.selectedMbr.name,
                    queryType:this.state.selectedFunId
            }

当你能够点击ok的时候,你应该已经知道:

  1. 布局类型(行、列等)
  2. 组号
  3. layoutMbr 的index 

(维度id 已知道 但是这里没啥用吧)

删除  :就是从数组当中删除 ,然后重新setstate

 点击确定时候,其实是把target 重新赋予给......

点击选择按钮时,时了解gourp_no 等数值的。

创建表单:

//创建表单
    openFormModalForCreating = () => {
        if (this.state.selectedFolderId === undefined || this.state.selectedFolderId === ""
            || this.state.selectedFolderId == "-1" || this.state.selectedFolderId == -1
            || this.state.selectedFolderId == null) {
            message.info("请选择一个节点!")
            return
        }
        getDims().then(
            (res) => {
                this.setState({
                    formId: 0,
                    formName: "",
                    remark: "",
                    reportDesc: "",
                    scale: 2,
                    // formId:res.data.id,
                    colDims: [],
                    rowDims: [],
                    pageDims: [],
                    viewDims: [],
                    dims_option: res.data,   //

                    rowLayouts: [{
                        "position": 0,
                        "groupName": "",
                        "theLayouts": []
                    }],  //组的集合
                    colLayouts: [{
                        "position": 0,
                        "groupName": "",
                        "theLayouts": []
                    }],  //组的集合
                    pageLayouts: [],  //页面
                    viewLayouts: [],  //view

                    isModalVisibleForForm: true,
                    formDefStatus: 1 //new  create status
                });
            },
            (error) => {
                console.log("get response failed!");
            }
        );
    };

dims:

{
  "code": "200",
  "data": [
    {
      "id": 111,
      "name": "科目",
      "objType": 1,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 0,
      "lastModifyTime": "2018-06-12T14:19:41.000+0000",
      "usedIn": 7,
      "type": 1
    },
    {
      "id": 112,
      "name": "组织",
      "objType": 2,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 2,
      "lastModifyTime": "2018-06-12T14:35:09.000+0000",
      "usedIn": 7,
      "type": 1
    },
    {
      "id": 113,
      "name": "年",
      "objType": 3,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 3,
      "lastModifyTime": "2018-06-12T14:19:41.000+0000",
      "usedIn": 7,
      "type": 2
    },
    {
      "id": 114,
      "name": "期间",
      "objType": 4,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 4,
      "lastModifyTime": "2018-06-12T14:19:41.000+0000",
      "usedIn": 7,
      "type": 2
    },
    {
      "id": 115,
      "name": "币别",
      "objType": 5,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 5,
      "lastModifyTime": "2018-06-12T14:19:41.000+0000",
      "usedIn": 7,
      "type": 1
    },
    {
      "id": 116,
      "name": "场景",
      "objType": 6,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 6,
      "lastModifyTime": "2018-06-12T14:19:41.000+0000",
      "usedIn": 7,
      "type": 1
    },
    {
      "id": 117,
      "name": "版本",
      "objType": 7,
      "parent": 0,
      "generation": 0,
      "hasChildren": 0,
      "description": 0,
      "queryType": 0,
      "formulaType": 0,
      "position": 7,
      "lastModifyTime": "2018-06-18T14:14:47.000+0000",
      "usedIn": 7,
      "type": 1
    }
  ]
}

ps:一个维度可以应用于多个cube,cube可以理解为分析模型,数据集市

ps:使用ES6语法通过onClick事件响应传递参数

ps:Ant Design - 可展开表中 Data 必须携带 key

ps:react/ant design 利用函数控制表单(提交表单,重置表单)

ps:JPA EntityManager persist 方法详解(不允许提前设置id)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值