[关闭]
@Wahson 2017-12-15T15:18:53.000000Z 字数 21119 阅读 1288

MetaUI

前端技术


MetaUI介绍 ( Introduce )

  • 是什么?—— MetaUI是一个基于元数据的前端框架,它是在Polymer2.0之上开发, 能够通过配置元数据,快速开发前端页面。
  • 为什么?—— 曾经无数次给公司的运营系统开发不同的数据页面。但是这些界面大部分无外乎一个查询条件栏,接着是查询和重置条件的按钮,接下来是查询结果(以表格显示),然后每一行数据都会定义有不同的操作按钮,对应不同的操作,点击按钮,要么是弹窗对数据进行编辑保存操作,要么直接提交表单。来来回回都是做这些的重复性的工作,更重要是耗时。那么为什么不能设计出一个框架,通过配置元数据的形式,自动就把这种类型的页面自动完成呢?然后把剩下的时间放在差异点的实现上。甚至如果使用足够简单的话,即使是非开发人员也能通过培训,迅速搭建出界面原型,岂不很有意思?
  • 怎么样?—— 且继续往下看。

MetaUI安装 ( Install )

  1. bower install metaui

或者

  1. bower install git://github.com/isuwang/metaui.git

开始使用MetaUI (Get Started)

MetaUI是一个基于Polymer的框架,所以你读到这里的时候,我已经默许你了解过Polymer,并且也知道polymer-cli如何使用。

那么接下来,我们通过polymer-cli脚手架快速搭建一个polymer工程,并开始使用MetaUI。

1. 首先创建一个目录,目录名叫metaui-demo

  1. mkdir metaui-demo && cd metaui-demo

2. 初始化Polymer工程

  1. polymer init

选择polymer-2-application选项,然后按照提示输入。
如图:
IMAGE

3. 安装metaui

  1. bower install --save git://github.com/isuwang/metaui.git

4. 启动应用

  1. polymer serve

然后复制输出的url到浏览器打开,我这里的url是http://127.0.0.1:8081/components/metaui-demo/

如图:
x

你的浏览器中能看到Hello metaui-demo!吗?看不到的话,别问我为什么。

5. 接下来划重点(敲黑板)

  1. vi src/metaui-demo/metaui-demo.html

引入metaui中的m2-crud组件

  1. <link rel="import" href="../../bower_components/metaui/m2-crud.html">

<template>中声明<m2-crud>

  1. <m2-crud metadata="{{metadata}}"></m2-crud>

初始化metadata属性

  1. static get properties() {
  2. return {
  3. metadata: {
  4. type: Object,
  5. value: {
  6. query: {
  7. formUrl: "/query.do",
  8. fields: [ { name: "username", label: "用户名", editable: true, visible: true } ]
  9. },
  10. result: {
  11. grids: [
  12. { name: "username", label: "用户名" },
  13. { name: "phone", label: "联系电话" },
  14. { name: "address", label: "联系地址" },
  15. { metaType: {type: "ACTION"}, name: "actions", label: "操作" }
  16. ]
  17. }
  18. }
  19. }
  20. };
  21. }

刷新一下网页,duang! duang! duang!
xxx

Opps!看到了错误警告(我开始慌了~,是不是我又~做错了什么?)--不要慌,弹出层的错误提示暂时无需搭理,因为组件加载后会自动根据metadata中配置的query.formUrl进行查询,但是你懂的,我们还没有提供接口服务支持

6. 到此,一个简单的元数据组件已经跑起来了。

元数据到界面的来龙去脉 (How it works)

先来两张图来窥探一下MetaUI的结构:

主页:
xx

弹窗:
bb

MetaUI的结构还是比较简单的,看蓝字上可以找到每个元数据配置对应的界面元素。

元数据的详细定义 (Metadata Definitions)

MetaUI中提供了 <m2-crud><m2-grid><m2-crud-query><m2-action><m2-form><m2-field> 6个基础元数据组件。

<m2-crud> => DomainMeta/CrudMeta

Demo & API Doc
Simple demo:

  1. <m2-crud metadata="{{metadata}}"></m2-crud>
  2. metadata = {
  3. query: {
  4. formUrl: "/query.do",
  5. fields: [
  6. {
  7. name: "username",
  8. label: "用户名",
  9. editable: true,
  10. visible: true
  11. }
  12. ]
  13. },
  14. actions: {
  15. "record-edit": {
  16. actionId: "record-edit",
  17. actionName: "Edit",
  18. operType: 1,
  19. formMeta: {
  20. formUrl: "/url-to-send-form",
  21. confirm: "Sure to submit?",
  22. fields: [
  23. {
  24. name: "name",
  25. label: "Name"
  26. },
  27. {
  28. name: "phone",
  29. label: "Phone"
  30. },
  31. ],
  32. }
  33. }
  34. },
  35. result: {
  36. grids: [
  37. {
  38. name: "username",
  39. label: "用户名"
  40. },
  41. {
  42. name: "phone",
  43. label: "联系电话"
  44. }
  45. ]
  46. }
  47. }
字段名 类型 Required 描述
query FormMeta 表格数据请求表单的元数据
actions array.<ActionMeta> 针对domain上的操作元数据
result GridMeta 表格的元数据

<m2-grid> => GridMeta

Demo & API Doc

  1. <m2-grid metadata="{{metadata}}" value="{{value}}"></m2-crud>
  2. metadata = {
  3. title: "I am the title!",
  4. grids: [
  5. { name: "name", label: "Name" },
  6. { name: "phone", label: "Phone" },
  7. { metaType: {type: "ACTION"}, name: "actions", label: "Actions", }
  8. ],
  9. actions: {
  10. "action-1": {
  11. actionId: "action-1",
  12. actionName: "Action1",
  13. group:"GROUP",
  14. operType: 1,
  15. formMeta: {
  16. formUrl: "/url-to-send-form",
  17. confirm: "Sure to submit?",
  18. fields: [
  19. { name: "name", label: "Name" },
  20. { name: "phone", label: "Phone" },
  21. ]
  22. }
  23. },
  24. "action-2": {
  25. actionId: "action-2",
  26. actionName: "Action2",
  27. group:"GROUP",
  28. operType: 2,
  29. formMeta: {
  30. title: "Dialog Title",
  31. formUrl: "/url-to-send-form",
  32. confirm: "Sure to submit?",
  33. fields: [
  34. { name: "name", label: "Name", visible: true, editable: true },
  35. { name: "phone", label: "Phone", visible: true, editable: true },
  36. ]
  37. }
  38. }
  39. },
  40. style: `
  41. .grid-column {font-size: 16px;}
  42. `
  43. };
  44. value = [
  45. {
  46. name: "foo",
  47. phone: "10086",
  48. actions: ["action-1", "action-2"]
  49. },
  50. {
  51. name: "foo1",
  52. phone: "10086",
  53. actions: ["action-1"]
  54. }
  55. ];
字段名 类型 Required 描述 描述 描述 描述
grids array.<FieldMeta> 表格sdfsdfsdfsdfsfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsfsdfsdfsdfsdfsdfsdfsfdsfssdf单元格元数据 :-- :--
actions map.<string, ActionMeta> 操作集 :-- :--
title string 表格标题 :-- :--
editable boolean 是否可编辑 :-- :--

<m2-action> => ActionMeta

Demo & API Doc
Simple demo:

  1. <m2-action metadata="{{metadata}}" context="{{context}}"></m2-action>
  2. metadata = {
  3. actionId: "record-add",
  4. actionName: "添加记录",
  5. operType: 1,
  6. formMeta: {
  7. formUrl: "/url-to-send-form",
  8. confirm: "Sure to submit?",
  9. fields: [
  10. {
  11. name: "name",
  12. label:"Name"
  13. },
  14. {
  15. name: "phone",
  16. label:"Phone"
  17. }
  18. ]
  19. }
  20. };
  21. context = {
  22. name: "foo",
  23. phone: "10086"
  24. }
字段名 类型 Required 描述
actionId string 操作的唯一标识
actionName string 操作名,如 “收货”
group string 操作组,如 “更多”
operType number 操作类型,1:表单,2.弹窗
formMeta FormMeta 表单meta
modelSrc string 弹窗的model请求url
modelQueryParams array.<string> 弹窗的model请求参数字段名,如 ["id", "name"]
customAction Function 自定义按钮动作,声明了该字段后,会忽略默认的按钮动作

<m2-form> => FormMeta

Demo & API Doc
Simple demo:

  1. <m2-form metadata="{{metadata}}" context="{{context}}"></m2-form>
  2. metadata = {
  3. formUrl: "/url-to-send-form",
  4. title: "我是标题",
  5. confirm: "Sure to submit?",
  6. fields: [
  7. {
  8. name: "name",
  9. label: "姓名",
  10. required: true,
  11. editable: true,
  12. visible: true
  13. },
  14. {
  15. name: "phone",
  16. label: "电话",
  17. required: true,
  18. editable: true,
  19. visible: true
  20. }
  21. ],
  22. style: `
  23. .container {
  24. grid-column-gap: 4%;
  25. grid-template-columns: 48% 48%;
  26. }
  27. .container__item {
  28. border: 1px dashed
  29. }
  30. `,
  31. };
  32. context = {
  33. name: "foo",
  34. phone: "10086"
  35. }
字段名 类型 Required 描述
title string 表单头
formUrl string 表单提交的url
fields array. 字段元数据
confirm string 表单提交前确认提示
hideSubmitBtn boolean 是否禁用提交按钮
responseHandleAs string 请求返回实体处理:text,json,blob,arrayBuffer, formData
sendAsSearchParams boolean 请求是否以 URLSearchParams 格式发送
style string 组件自定义样式

<m2-crud-query> => 阉割的FormMeta

Demo & API Doc

  1. <m2-crud-query metadata="{{metadata}}" query-after-reset></m2-crud-query>
  2. metadata = {
  3. formUrl: "/url-to-send-form",
  4. fields: [
  5. {
  6. name: "name",
  7. label: "姓名",
  8. required: true,
  9. editable: true,
  10. visible: true
  11. },
  12. {
  13. name: "phone",
  14. label: "电话",
  15. required: true,
  16. editable: true,
  17. visible: true
  18. }
  19. ],
  20. noPaging: true
  21. }
字段名 类型 Required 描述
fields array. 字段元数据
noPaging boolean 是否取消分页

<m2-field> => FieldMeta

Demo & API Doc

  1. <m2-field metadata="{{metadata}}" context="{{context}}"></m2-field>
  2. metadata = {
  3. name: "sex",
  4. label: "Please select your sex: ",
  5. element: "m2-select-list",
  6. required: true,
  7. candidates: [
  8. { label: "Male", value: "1" },
  9. { label: "Female", value: "2" }
  10. ],
  11. candidateLabel: "label",
  12. candidateValue: "value",
  13. render: (model) => `<div style="color:blue">${model.name}</div>`
  14. };
  15. context = {
  16. name: "foo",
  17. phone: "10086",
  18. sex: 1
  19. }
字段名 类型 Required 描述
name string 字段名,如“orderNo”
label string 字段名,如“订单号”,缺省值:name字段的值
require boolean 是否必填,缺省值:false
mutil boolean 是否多选,缺省值:false
editable boolean 是否可编辑,缺省值:false
validate boolean 是否需要校验,缺省值:false
visible boolean 界面是否可见,仅对表单中的字段有效,缺省值:false
disabled boolean 是否禁用组件,缺省值:false
element string ui元素,缺省时,提供默认ui:m2-text
format string 显示格式,用于日期显示,如“yyyy-MM-dd”
length number 字符长度
maxLength number string允许的最大长度
minLength number string允许的最小长度
regexp string 正则匹配
prompt string 校验失败时的提示语,如“请输入备注”
min number 数字最小值
max number 数字最大值
placeholder string placeholder
candidates string 候选值,下拉选项中的值,json串,如 "[{value: 1,label: '男'}, {value: 2,label: '女'}]"
candidateLabel string 组件显示字段名,针对下拉组件、radio-group组件等,“label”
candidateValue string 组件值字段名,针对下拉组件、radio-group组件等,“value”
srcKey string 当一个字段的值是来源于另外一个字段,则通过srcKey配置值来源字段的字段名,如 "id"
metaType MetaType或string 元数据类型,如果缺少metaType,默认渲染的组件是;在表格元数据中定义操作列时,可以省略操作的元数据,如 metaType:"ACTION" See h2-grid
render Function(Closure) 只读模式下会执行此闭包生成显示内容

MetaType定义

字段名 类型 Required 描述
type string 数据类型:BOOLEAN、BYTE、SHORT、INTEGER、LONG、DOUBLE、STRING、LIST、MAP、SET、STRUCT、ACTION
metadata object 如果type是list,map,set中的一种,则metadata是这些嵌套类型的元数据

扩展点 (Extensions)

1. 样式扩展

<m2-form> 以及<m2-crud-query>中提供了样式扩展能力,你可以通过配置metadata.style属性了传入自定义的样式。

例如:

  1. metadata.style = `
  2. .container {
  3. grid-column-gap: 4%;
  4. grid-template-columns: 48% 48%;
  5. }
  6. .container__item {
  7. border: 1px dashed
  8. }
  9. `;

这些组件默认使用CSS Grid Layout布局,你可以根据需要在.container类选择器中修改。你可以通过选择器,对<m2-form><m2-crud-query>的shadow dom中的任何元素进行样式修改。

2. m2-field显示扩展

<m2-field>组件在只读模式下,会查找metadata.render,如果该字段有值且是一个函数,组件内部会执行这个函数并传入组件的context属性值作为参数,并把函数返回值作为显示内容,渲染到shadow dom中。render函数除了返回字符串,也可以返回一个HTMLElement。
例如:

  1. // return string
  2. metadata.render = ({name}) => `<div style="color:blue">${name}</div>`;
  3. // return HTMLElement
  4. metadata.render = ({id, taskNo}) => {
  5. const aTag = document.createElement("a");
  6. aTag.href = "javascript:void(0)";
  7. aTag.onclick = (e) => {
  8. // do something here
  9. };
  10. aTag.innerHTML = taskNo;
  11. return aTag;
  12. }

3. m2-action 自定义点击操作扩展

<m2-action>默认的点击操作是弹窗或者提交表单,但是你可以metadata.customAction字段传入函数来覆盖默认的行为。customAction函数执行时会传入当前m2-action中的context属性值作为参数。

  1. metadata.customAction = (context) => {
  2. console.log(context.name);
  3. }

4. 扩展查询条件

m2-crud-query组件中定义了一个slot插槽,允许你通过light dom的形式定义额外的查询条件。

  1. <m2-crud-query metadata="{{metadata}}">
  2. <div slot="ext-condition">
  3. <o-status-select label="状态" on-status-selected="onStatusSelect"></o-status-select>
  4. </div>
  5. </m2-crud-query>
  6. onStatusSelect(e) {
  7. e.target.dispatchEvent(
  8. new CustomEvent('query-domain',
  9. {
  10. // e.detail的值是组件中返回的状态值,比如 {status: 1}
  11. detail: e.detail,
  12. composed: true,
  13. bubbles: true
  14. }
  15. )
  16. );
  17. }

<m2-crud-query>内部监听了query-domain事件,并且会把通过e.detail传递过来的字段作为请求参数的一部分,发起查询请求。另外需要注意dispatch事件的元素必须是绑定到slot中的元素(上例中可以是div[slot=ext-condition]o-status-select)。

5. 组件扩展

m2-field组件只是一个壳,内部会首先判断 metadata.element是否指定了组件名,如果有,创建改组件,并添加到shadow dom 中,否则判断metadata.metaType的值:

  1. switch (metaType) {
  2. case "ACTION":
  3. return "m2-action";
  4. case "FORM":
  5. return "m2-form";
  6. case "GRID":
  7. return "m2-grid";
  8. default:
  9. return "m2-text";
  10. }

匹配不上时默认渲染出<m2-text>

你可以定义你的组件,并在metadata.element中指定该组件名。
自定义组件时,你需要遵循以下规定:
1. 组件需要提供value属性,来指定组件的值。而且value属性需要声明notify: true
2. 组件需要提供validate()方法,返回一个布尔值,用来检测组件中的值是否合法。
3. 你可以直接继承 M2WidgetBase父类(/bower_components/metaui/widgets/m2-widget-base.html, M2WidgetBase提供了value的定义以及validate()的默认实现。

参考例子:

  1. <link rel="import" href="../bower_components/metaui/widgets/m2-widget-base.html">
  2. <link rel="import" href="../bower_components/paper-input/paper-input.html">
  3. <dom-module id="your-widget-name">
  4. <template>
  5. <paper-input
  6. id="input"
  7. always-float-label
  8. value="{{ value }}"
  9. label="[[ metadata.label ]]"
  10. auto-validate
  11. required="[[ metadata.required]]"
  12. error-message="[[ metadata.prompt ]]"
  13. min="[[ metadata.min ]]"
  14. minlength="[[ metadata.minLength ]]"
  15. max="[[ metadata.max ]]"
  16. maxlength="[[ metadata.maxLength ]]"
  17. pattern="[[ metadata.regexp ]]"
  18. readonly="[[ !metadata.editable ]]">
  19. </paper-input>
  20. </template>
  21. <script>
  22. class YourWidget extends M2WidgetBase {
  23. static get is() {
  24. return "your-widget-name";
  25. }
  26. /**
  27. * Before a form being submitted, validate() will be called automatically to check if the value is legal.
  28. * You can override it if needed.
  29. */
  30. validate() {
  31. // return this.$.input.validate();
  32. return true;
  33. }
  34. // more methods here
  35. }
  36. window.customElements.define(YourWidget.is, YourWidget);
  37. </script>
  38. </dom-module>

m2-field中的 context 属性值也会自动绑定到自定义组件内部。

6. 数据相互感知能力

m2-form组件像是一个m2-field容器,所有的m2-field会共享m2-form中context属性值,任何一个m2-field中的value发生变化,会同步到context属性上,而且在任何一个m2-field中你可以监听另外一个m2-field值的变化。看下面例子:

  1. metadata = {
  2. formUrl: "/url-to-send-form",
  3. title: "Form Title",
  4. confirm: "Sure to submit?",
  5. fields: [
  6. {
  7. name: "bar",
  8. label: "bar",
  9. required: true,
  10. editable: true,
  11. visible: true
  12. },
  13. {
  14. name: "foo",
  15. label: "foo",
  16. required: true,
  17. editable: true,
  18. visible: true,
  19. onAttached: (fieldEle, formEle) => {
  20. formEle.watch(['bar'], ({bar}, whoChange) => {
  21. console.log(whoChange);
  22. fieldEle.style = bar.length > 4 ?
  23. 'background:red;' : 'background:green;'
  24. });
  25. }
  26. }
  27. ]
  28. }

重点看看metadata.fields[1].onAttached

  1. onAttached: (fieldEle, formEle) => {
  2. /**
  3. * ['bar'] 订阅bar字段的变化
  4. * ({bar}, whoChange) => {...} 回调函数,入参是m2-field对应的context, 以及值发生变化的字段名。
  5. */
  6. formEle.watch(['bar'], ({bar}, whoChange) => {
  7. console.log(whoChange); // "bar"
  8. fieldEle.style = bar.length > 4 ?
  9. 'background:red;' : 'background:green;'
  10. });
  11. }

这里在 foo 的字段上,订阅了bar字段值的变化。当foo字段对应的 m2-field 被依附到m2-form的shadow dom时,onAttached 函数会被调用来初始化订阅,通过m2-form内部的 watch() 方法来完成订阅。bar 字段的值一旦变化,传入 watch() 方法的回调就会执行。因此在此回调函数里面,你可以定义实现一些功能,比如根据bar值来调整foo字段所在的 m2-field 的样式,等等。

工具 (Tools)

结合isuwang-soa

各位看官已经见识到单独使用MetaUI的威力,但是随着开发的界面越来越多,很多重复性的配置难免会带来一些枯燥感。接下来,我们结合 isuwang-soa,江湖人称dapeng框架,以及meta-constructor,带来更快速的开发体验。
 
isuwang-soa是一个SOA框架,具体的使用不在这里介绍,请移步 https://github.com/isuwang/isuwang-soa

接下来,我们开始编写IDL(接口定义语言),这个过程我们通过编写Thrift文件来完成。
先看一个简单的例子:

  1. TaskAdminService {
  2. /**
  3. * 根据条件查询任务
  4. */
  5. task_domain.TTaskResponse findTasks(task_domain.TTaskRequest request)
  6. (m.form.url="/task/taskadmin/findTasks.do")
  7. /**
  8. * 新建任务
  9. */
  10. void createTask(task_domain.TCreateTaskRequest request)
  11. (m.form.url="/task/taskadmin/createTask.do",m.type="domain-action",m.id="task_add",
  12. m.name="新增任务", m.operType="2", m.form.title="新增任务")
  13. /**
  14. * 修改任务
  15. */
  16. void updateTask(task_domain.TUpdateTaskRequest request)
  17. (m.form.url="/task/taskadmin/updateTask.do",m.type="entity-action",m.id="task_update",
  18. m.name="编辑任务", m.operType="2", m.form.title="编辑任务")
  19. /**
  20. * 删除任务
  21. */
  22. void deleteTask(i32 id)
  23. (m.form.url="/task/taskadmin/deleteTask.do",m.type="entity-action",m.id="task_delete",
  24. m.name="删除", m.operType="1", m.form.title="删除",m.form.confirm="确定删除?",m.form.sendAsSearchParams = "true")
  25. }

dapeng内嵌了对thrift文件的解析,通过dapeng-code-generator插件可以基于thrift文件生成接口以及实体的代码,另外还会生成一组描述每个服务以及服务相关实体元信息的xml文件,诸如(m.form.url="/task/taskadmin/findTasks.do")这类的注释也会解析到xml文件中。这个xml文件,将会是MetaUI的数据来源,我们稍后会介绍。

现在先来介绍一下对thrift注释所做的扩展:

注释key 对应元数据字段 取值 描述
m.type domain-action, entity-action domain-action:domain作用域的操作,比如添加记录,entity-action:实体作用域的操作,比如删除或修改一个记录
m.id ActionMeta.actionId 默认值:方法名称 操作id,建议跟权限名相同
m.name ActionMeta.actionName 操作名称
m.operType ActionMeta.operType "1" 或 "2" "1":直接表单提交,"2":弹窗
m.modelSrc ActionMeta.modelSrc
m.group ActionMeta.group 操作分组
m.form.confirm FormMeta.confirm 表单提交前的确认提示
m.form.title FormMeta.title 表单页面的标题
m.form.url FormMeta.url 表单提交的url,与前端controller对应
m.form.hideSubmitBtn Form.hideSubmitBtn "true" or "false",缺省值:"false" 表单中是否隐藏默认的保存按钮
m.form.sendAsSearchParams FormMeta.sendAsSearchParams "true" or "false",缺省值:"false" 请求是否以 URLSearchParams 格式发送

看一个Struct上添加注释的例子:

  1. struct TTaskRequest {
  2. 1: base_model.TPageRequest pageRequest
  3. 2: optional string taskNo (m.label="任务编号", ... ),
  4. 3: optional i32 taskId (m.srcKey="id", m.visible="false",...)
  5. 4: ...
  6. }

在实体字段上配置的注释,key跟FieleMeta上的字段是完全一致的。

你也可以直接在接口入参后面添加注释,下面是一个简单的例子,但是这个例子有点为了举例而举例的嫌疑,因为直接把参数名改为 id 就可以避免 m.srcKey="id" 的配置了。

  1. TaskAdminService {
  2. // ...
  3. void deleteTask(i32 taskId (m.srcKey="id"))
  4. (m.form.url="...", ...)
  5. // ...
  6. }

刚才我们讲到服务的元数据会解析到一个xml文件中,该文件以 服务的namespace+名字 命名,如 com.isuwang.soa.task.service.TaskAdminService.xml

我们依稀记得MetaUI元数据的配置字段可远不止这些,但是先不着急,通过下图,我希望能够让你对MetaUI跟Thrift配置的关系有一个更清晰的认识。
!图在这

实体(Struct)的每个字段对应的是一个<m2-field/>,FieldMeta的信息会在数据库中配置。

  1. DROP DATABASE IF EXISTS metadb;
  2. CREATE DATABASE IF NOT EXISTS metadb DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
  3. DROP TABLE IF EXISTS `metadb`.`fields`;
  4. CREATE TABLE `fields` (
  5. `id` int(11) NOT NULL AUTO_INCREMENT,
  6. `struct_name` varchar(200) NOT NULL COMMENT '实体名',
  7. `name` varchar(50) NOT NULL COMMENT '字段名,如"orderNo"',
  8. `element` varchar(50) DEFAULT NULL COMMENT '替换元素,如 "paper-input" ',
  9. `label` varchar(50) NOT NULL COMMENT '字段名,如"订单号"',
  10. `required` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否必填 1: 是 0:否',
  11. `multi` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否多选 1: 是 0:否',
  12. `format` varchar(50) DEFAULT NULL COMMENT '显示格式,用于日期显示,如“yyyy-MM-dd”',
  13. `editable` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否可编辑 1: 是 0:否',
  14. `validate` smallint(1) NOT NULL DEFAULT '0' COMMENT '前是否需要校验, 1: 是 0:否',
  15. `length` int(11) DEFAULT NULL COMMENT '字符允许长度',
  16. `max_length` int(11) DEFAULT NULL COMMENT '允许最大长度',
  17. `min_length` int(11) DEFAULT NULL COMMENT '允许最小长度',
  18. `regexp` varchar(50) DEFAULT NULL COMMENT '正则匹配',
  19. `prompt` varchar(50) DEFAULT NULL COMMENT '校验失败时的提示语,如“请输入备注”',
  20. `min` int(11) DEFAULT NULL COMMENT '数字最小值',
  21. `max` int(11) DEFAULT NULL COMMENT '数字最大值',
  22. `candidates` varchar(1000) DEFAULT NULL COMMENT '下拉选项中的值,json串,如 [{value:1,label:"男"},{value:2,label:"女"}]',
  23. `candidate_label` varchar(50) DEFAULT NULL COMMENT '组件显示字段名,针对下拉组件、radio-group组件等,“label”',
  24. `candidate_value` varchar(50) DEFAULT NULL COMMENT '组件值字段名,针对下拉组件、radio-group组件等,“value”',
  25. `placeholder` varchar(50) DEFAULT NULL COMMENT 'placeholder',
  26. `src_key` varchar(50) DEFAULT NULL COMMENT '源字段名',
  27. `visible` smallint(1) NOT NULL DEFAULT '1' COMMENT '界面是否可见 1: 是 0:否',
  28. `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  29. `created_by` int(11) DEFAULT NULL,
  30. `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  31. `updated_by` int(11) DEFAULT NULL,
  32. `disabled` int(11) DEFAULT NULL,
  33. PRIMARY KEY (`id`),
  34. UNIQUE KEY `field_unique` (`struct_name`,`name`)
  35. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='字段元数据';

看一个插入语句的例子:

  1. INSERT INTO metadb.fields (struct_name, name, element, label, required, multi, format, editable, validate, length, max_length, min_length, `regexp`, prompt, min, max, candidates, candidate_label, candidate_value, placeholder, src_key, visible, created_at, created_by, updated_at, updated_by, disabled) VALUES ('com.isuwang.soa.task.domain.TTaskRequest', 'taskNo', 'm2-task-picker', '任务编号', 0, 0, null, 1, 0, null, null, null, null, null, null, null, null, null, null, null, null, 1, '2017-11-09 17:51:03', null, '2017-11-23 18:08:04', null, null);

你应该注意到了,thrift文件和数据库中都可以配置FieldMeta字段的值,比如在上面例子中taskId配置了 m.visible="false" ,同时 metadb.fields表 中的 name="taskId" 的记录中 visible 字段的值是1 (会转换成true),那么谁的优先级更高呢?这取决于你如何解析他们。MetaUI提供了一个工具,在utils目录下的 <meta-constructor />,它把thrift跟数据库中定义的元数据,组装成MetaUI组件认识的结构。<meta-constructor />处理元数据时,thrift上定义的FieldMeta字段值优先级更高。待会会详细介绍 <meta-constructor />

依赖dapeng-metaui-servlet 来部署元数据查询服务

dapeng-metaui-servlet 是一个简单的servlet,servlet会从请求参数中读取 serviceNameversion ,然后通过反射的方式查找到服务元数据的xml文件,解析xml文件,从中获取到跟改service相关的实体信息,到 metadb.fields表 中查询相关的元数据记录,xml会装换成json,跟元数据记录一并返回。

1. 在前端工程中添加maven依赖(有点理所当然,我默认了你的是maven工程)

  1. <dependency>
  2. <groupId>com.isuwang</groupId>
  3. <artifactId>dapeng-metaui-servlet</artifactId>
  4. <version>1.0.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. <version>5.1.25</version>
  10. </dependency>

2. 前端服务 web.xml 中配置servlet

  1. <servlet>
  2. <servlet-name>metadataServlet</servlet-name>
  3. <servlet-class>com.isuwang.dapeng.metadata.servlet.MetadataServlet</servlet-class>
  4. <init-param>
  5. <param-name>driverClassName</param-name>
  6. <param-value>com.mysql.jdbc.Driver</param-value>
  7. </init-param>
  8. </servlet>
  9. <servlet-mapping>
  10. <servlet-name>metadataServlet</servlet-name>
  11. <url-pattern>/dapeng/metadata</url-pattern>
  12. </servlet-mapping>

3. 在resource目录下添加 init.properties 配置文件,写入以下内容并修改数据库配置

  1. DB_METADB_URL=jdbc:mysql://127.0.0.1:3306/metadb?useUnicode=true&characterEncoding=utf8
  2. DB_METADB_USER=your-db-username
  3. DB_METADB_PASSWD=your-db-password

也可以设置系统的环境变量,如:
此处输入图片的描述
设置环境变量后,会优先读取环境变量的值。

这里是一个请求的例子:

  1. fetch("/dapeng/metadata?serviceName=com.isuwang.soa.task.service.TaskAdminService&version=1.0.0", {method:"POST",credentials:"include"})

返回的结果:

  1. {
  2. fieldsInStruct: {
  3. com.isuwang.soa.task.domain.TTaskRequest: [...],
  4. ...
  5. },
  6. serviceMeta: {
  7. enumDefinitions: [],
  8. meta: {version: "1.0.0", timeout: 30000},
  9. methods: [...],
  10. name: "TaskAdminService",
  11. namespace: "com.isuwang.soa.task.service",
  12. structDefinitions: [...]
  13. }
  14. }

<meta-constructor />

<meta-constructor> 是一个工具组件,它会自动请求 dapeng/metadata 获取元数据,并把返回的数据进行重新组装成 <m2-* /> 组件能读懂的结构,然后通过 metadata 字段返回。看下面的例子:

  1. <link rel="import" href="../bower_components/polymer/polymer-element.html">
  2. <link rel="import" href="../bower_components/metaui/utils/meta-constructor.html">
  3. <link rel="import" href="../bower_components/metaui/m2-crud.html">
  4. <dom-module id="task-admin-index">
  5. <template>
  6. <meta-constructor src="/dapeng/metadata"
  7. metadata="{{ metadata }}"
  8. option="[[ option ]]"
  9. on-return="[[ onReturn() ]]"></meta-constructor>
  10. <m2-crud class="main"
  11. id="crud"
  12. metadata="[[ metadata ]]"
  13. key-for-page-result="pageResponse.results"
  14. result-decorator="[[ _getResultChangeHandler() ]]"></m2-crud>
  15. </template>
  16. <script>
  17. class TaskAdminIndex extends Polymer.Element {
  18. static get is() {
  19. return "task-admin-index";
  20. }
  21. static get properties() {
  22. return {
  23. metadata: {
  24. type: Object
  25. },
  26. option: {
  27. type: Object,
  28. value: function () {
  29. return {
  30. serviceName: "com.isuwang.soa.task.service.TaskAdminService",
  31. version: "1.0.0",
  32. methodName: "findTasks"
  33. };
  34. }
  35. }
  36. };
  37. }
  38. _getResultChangeHandler() {
  39. return (result) => {
  40. // do something with result
  41. return result;
  42. }
  43. }
  44. onReturn() {
  45. return (metadata) => {
  46. // do something with metadata
  47. return metadata;
  48. }
  49. }
  50. }
  51. window.customElements.define(TaskAdminIndex.is, TaskAdminIndex);
  52. </script>
  53. </dom-module>

元数据增删改查工具

使用mock来模拟数据

首先强调一点,我这里讲的mock不是大家所熟知的 mockjs(当然未来可能会集成进来),请大家注意不要混淆。mock使用一种对代码尽可能小侵入的方式(两处侵入:1. head标签中的代码库引入,2. URI侵入),实现请求的模拟。

接下来看看mock如何使用:

1. 引入 mock_setup.js

  1. <head>
  2. ...
  3. <script type="text/javascript" src="../bower_components/h2-elements/utils/mock_setup.js"></script>
  4. ...
  5. </head>

2. 创建 mockData.js 模拟数据准备文件

For Example:

  1. import {data} from "./data.js";
  2. MockDataPool.when("POST", "/path/to/index.do")
  3. .withExpectedHeader("Content-Type", "application/json;charset=utf-8")
  4. .withExpectedHeader("Cache-Control", "no-cache")
  5. .responseWith({status: 200, body: JSON.stringify(data)});

3. 打开数据模拟模式:在页面的URI后添加mock参数(mock=xxx.js)。

For example:
http://127.0.0.1:8000/components/h2-elements/demo/h2-fetch/index.html?mock=/your/path/to/mockData.js,
/your/path/to/ index.html 到 mockData.js 的相对路径。比如,如果index.html和mockData.js在一个相同的目录下,则mock的值可以是 index.html?mock=mockData.js 或者 index.html?mock=./mockData.js

4. 使用

  1. MockDataPool.match( request: Request|Requestable-object ): Response|Responsable-object
  2. // demo
  3. const response = MockDataPool.match({
  4. url: "/path/to/index.do",
  5. method: "POST",
  6. headers: {
  7. "Content-Type": "application/json;charset=utf-8",
  8. "Cache-Control", "no-cache"
  9. }
  10. });

“如果一个动物看起来像鸭子,叫起来也像鸭子,那么它就是一个鸭子。” -- 鲁迅

Requestable-object就是一个看起来像Request的对象,它跟Request有相似的字段跟行为。
Responsable-object同理。

建议你直接使用 h2-elements/h2-fetch.html 组件,里面封装了MockDataPool的使用。
用法1:

  1. <h2-fetch request="{{request}}" response="{{response}}" error="{{error}}"></h2-fetch>
  2. request = {
  3. url: "/path/to/index.do",
  4. method: "POST",
  5. headers: {
  6. "Cache-Control": "no-cache"
  7. }
  8. }

用法2:

  1. const request = {
  2. url: "/path/to/index.do",
  3. method: "POST",
  4. headers: {
  5. "Cache-Control": "no-cache"
  6. }
  7. };
  8. new H2Fetch().fetchIt(request)
  9. .then(res => res.json())
  10. .then(console.log)
  11. .catch(console.log);

你仍然需要执行 步骤1步骤2,因为它们是使用mock的前提。下面是 <h2-fetch/>中使用MockDataPool的代码:

  1. if (window.__mockEnabled) { // mock开关标志,mock_setup.js 初始化时会设置该值
  2. const matchedRes = MockDataPool.match(requset);
  3. if(matchedRes) {
  4. return Promise.resolve(new Response(matchedRes.body, matchedRes));
  5. }
  6. }
  7. return window.fetch(collectedReq);

<h2-fetch />会首先使用match方法去匹配,匹配上了则返回封装的Response,否则发起真实的fetch请求。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注