sula带你十分钟完成crud

起步

本文将从umi搭建项目开始,帮助你在项目中轻松运用sula。

1、先找个文件夹建个空目录

mkdir sula-use && cd sula-use

2、通过umi官方工具创建项目

yarn create @umijs/umi-app && npm i && npm start

在浏览器里打开 http://localhost:8000/

avatar

3、安装 sula 和 umi-plugin-sula

// 推荐cnpm
npm i sula umi-plugin-sula --save

注意:umi-plugin-sula 完成了sula插件的注册(否则要开发者手动注册),以及 history、语言类型的设置。

4、在 .umirc.ts 中启用 umi-plugin-sula 插件

avatar

5、sula中未引入antd样式,在src目录下新建global.ts并导入antd主题

// global.ts
import 'antd/dist/antd.less';

6、最后可以在项目中尽情的使用sula了

avatar

最终呈现


avatar

CreateForm使用

本示例是CreateForm一种简单的实现,你会看到最常用的属性配置

  • fields:表单控件配置
    • field:表单控件插件
  • submit:表单提交配置
    • url:提交地址
// pages/index.tsx
import React from 'react';
import { CreateForm } from 'sula';

export default () => {
  const config = {
    fields: [
      {
        name: 'name',
        label: 'Name',
        field: 'input',
      },
    ],
    submit: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/submit.json',
      method: 'POST',
    }
  }

  return (
    <div style={{ padding: '30px' }}>
      <CreateForm {...config} />
    </div>
  )
}

初始呈现

初始呈现

添加表单项

  • field:使用对象形式,可配置插件属性
    • type:插件类型
    • props:插件属性
  • initialSource:表单项初始数据源(select下拉框)
  • valuePropName: 子节点的值的属性,如 Switch 的是 'checked' 详情参考antd
import React from 'react';
import { CreateForm } from 'sula';

export default () => {
+ const initialSource = [{
+   text: 'Web',
+   value: 'web'
+ }, {
+   text: 'Pd',
+   value: 'pd'
+ }];

  const config = {
    fields: [
      {
        name: 'name',
        label: 'Name',
        field: 'input',
+      }, {
+        name: 'profession',
+        label: 'Profession',
+        initialSource,
+        field: {
+          type: 'select',
+          props: {
+            placeholder: 'please select profession'
+          }
+        },
+      }, {
+        name: 'rooms',
+        label: 'Rooms',
+        field: 'switch',
+        valuePropName: 'checked'
      }
    ],
    submit: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/submit.json',
      method: 'POST',
    }
  }

  return (
    <div style={{ padding: '30px' }}>
      <CreateForm {...config} />
    </div>
  )
}

添加表单项

设置表单布局和容器

  • container:容器插件
    • type:插件类型
    • props:插件属性
  • actionsPosition:按钮位置 bottom | right | center | default(默认)
  • itemLayout: 表单布局,支持labelCol和wrapperCol属性,同antd
import React from 'react';
import { CreateForm } from 'sula';

export default () => {
  const initialSource = [{
    text: 'Web',
    value: 'web'
  }, {
    text: 'pd',
    value: 'pd'
  }];

  const config = {
    fields: [
      {
        name: 'name',
        label: 'Name',
        field: 'input',
      }, {
        name: 'profession',
        label: 'Profession',
        initialSource,
        field: {
          type: 'select',
          props: {
            placeholder: 'please please'
          }
        },
      }, {
        name: 'rooms',
        label: 'Rooms',
        field: 'switch',
        valuePropName: 'checked'
      }
    ],
+    actionsPosition: 'right',
+    container: {
+      type: 'card',
+      props: {
+        title: 'Card'
+      }
+    },
+    itemLayout: {
+      span: 6,    // span表示每项所占的栅格数
+    },
    submit: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/submit.json',
      method: 'POST',
    },
  }

  return (
    <div style={{ padding: '30px' }}>
      <CreateForm {...config} />
    </div>
  )
}

设置表单布局和容器

远程表单值

  • remoteValues: 表单初始值远程请求
    • url: 接口地址
  • mode: 表单模式 create: 创建模式,view: 查看模式,edit: 编辑模式

mode为view或edit时才会执行remoteValues请求

import React from 'react';
import { CreateForm } from 'sula';

export default () => {
  const initialSource = [{
    text: 'Web',
    value: 'web'
  }, {
    text: 'pd',
    value: 'pd'
  }];

  const config = {
    fields: [
      {
        name: 'name',
        label: 'Name',
        field: 'input',
      }, {
        name: 'profession',
        label: 'Profession',
        initialSource,
        field: {
          type: 'select',
          props: {
            placeholder: 'please please'
          }
        },
      }, {
        name: 'rooms',
        label: 'Rooms',
        field: 'switch',
        valuePropName: 'checked'
      }
    ],
    actionsPosition: 'right',
    container: {
      type: 'card',
      props: {
        title: 'Card'
      }
    },
    itemLayout: {
      span: 6,    // span表示每项所占的栅格数
    },
    submit: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/submit.json',
      method: 'POST',
    },
+    remoteValues: {
+      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/formList',
+      method: 'get',
+    },
+    mode: 'edit'
  }
  return (
    <div style={{ padding: '30px' }}>
      <CreateForm {...config} />
    </div>
  )
}

最终代码

import React from 'react';
import { CreateForm } from 'sula';

export default () => {
  const initialSource = [{
    text: 'Web',
    value: 'web'
  }, {
    text: 'pd',
    value: 'pd'
  }];

  const config = {
    fields: [
      {
        name: 'name',
        label: 'Name',
        field: 'input',
      }, {
        name: 'profession',
        label: 'Profession',
        initialSource,
        field: {
          type: 'select',
          props: {
            placeholder: 'please please'
          }
        },
      }, {
        name: 'rooms',
        label: 'Rooms',
        field: 'switch',
        valuePropName: 'checked'
      }
    ],
    actionsPosition: 'right',
    container: {
      type: 'card',
      props: {
        title: 'Card'
      }
    },
    itemLayout: {
      span: 6,    // span表示每项所占的栅格数
    },
    submit: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/submit.json',
      method: 'POST',
    },
    remoteValues: {
      url: 'http://rap2.taobao.org:38080/app/mock/256045/form/formList',
      method: 'get',
    },
    mode: 'edit'
  }
  return (
    <div style={{ padding: '30px' }}>
      <CreateForm {...config} />
    </div>
  )
}

最终呈现


最终呈现

QueryTable使用

  • layout: 查询表单布局
  • columns:表格列配置
interface ColumnsProps = {
  title: string | ({ sortOrder, sortColumn, filters }) => ReactNode;
  key: string;
  render: RenderPlugin | RenderPlugin[];
  filterRender: { type: string } | string;
  [key: string]: AntdColumnsProps; // antd table columns透传属性
}

初始代码

// pages/index.tsx
import React from 'react';
import { QueryTable } from 'sula';
export default () => {
  const fields = [{
    name: 'name',
    label: 'Name',
    field: {
      type: 'input',
      props: {
        placeholder: 'please input'
      }
    }
  }]
  const columns = [{
    title: 'Sequence',
    key: 'index'
  }, {
    title: 'Country',
    key: 'nat'
  }, {
    title: 'Age',
    key: 'age',
  }, {
    title: 'Name',
    key: 'name'
  }, {
    title: 'Email',
    key: 'email',
  }]
  return (
    <div style={{ padding: '30px' }}>
      <QueryTable
        layout="vertical"
        columns={columns}
        fields={fields}
        rowKey="id"
      />
    </div>
  )
}

初始呈现

初始呈现

添加初始化数据

  • remoteDataSource:表格值接口请求
    • url: 请求地址
    • convertParams:处理请求参数
    • converter:处理返回数据

查询表格请求参数默认为以下格式

{
   pagination: {
     pageSize: 10,
     current: 1, 
   },
   filters: {  // 过滤项
     age: 1,
     name: 'Bob'
   },
   sorter: {
     columnKey: 'index', // 排序项
     order: 'ascend'   // descend 降序; ascend:升序
   }
}

sula-table支持的期望数据格式

{
  data: {
    list: [],   // 表格数据列表
    current: 1,
    pageSize: 10,  // 每页条数
    total: 100,     // 数据总数
  }
}
import React from 'react';
import { QueryTable } from 'sula';

export default () => {
  const fields = [{
    name: 'name',
    label: 'Name',
    field: {
      type: 'input',
      props: {
        placeholder: 'please input'
      }
    }
  }]

+  const remoteDataSource = {
+    url: 'http://rap2.taobao.org:38080/app/mock/256045/table/list',
+    method: 'GET',
+    convertParams({ params }) {
+      return {
+        results: 3,
+        ...params,
+      };
+    },
+    converter({ data }) {
+      return {
+        list: data.results.map((item, index) => {
+          return {
+            ...item,
+            id: index,
+            index,
+          };
+        }),
+        total: 10,
+      };
+    },
+  };

  const columns = [{
    title: 'Sequence',
    key: 'index'
  }, {
    title: 'Country',
    key: 'nat'
  }, {
    title: 'Age',
    key: 'age',
  }, {
    title: 'Name',
    key: 'name'
  }, {
    title: 'Email',
    key: 'email',
  }]
  return (
    <div style={{ padding: '30px' }}>
      <QueryTable
        layout="vertical"
        columns={columns}
+        remoteDataSource={remoteDataSource}
        fields={fields}
        rowKey="id"
      />
    </div>
  )
}

添加初始化数据

添加搜索项

注意:根据搜索项个数,查询表单会呈现不同的形态

  • 搜索项个数小于等于2个时;搜索项和搜索按钮在同一行展示


    小于等于2
  • 搜索项个数大于2个不超过5个时,搜索按钮会展示在第二行最右侧


    2到6
  • 搜索项个数超过5个时,超出的搜索项会被折叠隐藏


    大于等于6
import React from 'react';
import { QueryTable } from 'sula';
export default () => {
  const fields = [{
    name: 'name',
    label: 'Name',
    field: {
      type: 'input',
      props: {
        placeholder: 'please input'
      }
    }
+  }, {
+    name: 'nat',
+    label: 'Country',
+    field: 'input'
+  }, {
+    name: 'email',
+    label: 'Email',
+    field: 'input'
+  }, {
+    name: 'index',
+    label: 'Sequence',
+    field: 'input'
+  }, {
+    name: 'age',
+    label: 'Age',
+    field: 'input'
+  }, {
+    name: 'others',
+    label: 'Others',
+    field: 'input'
  }]

  const remoteDataSource = {
    url: 'http://rap2.taobao.org:38080/app/mock/256045/table/list',
    method: 'GET',
    convertParams({ params }) {
      return {
        results: 3,
        ...params,
      };
    },
    converter({ data }) {
      return {
        list: data.results.map((item, index) => {
          return {
            ...item,
            id: index,
            index,
          };
        }),
        total: 10,
      };
    },
  };

  const columns = [{
    title: 'Sequence',
    key: 'index'
  }, {
    title: 'Country',
    key: 'nat'
  }, {
    title: 'Age',
    key: 'age',
  }, {
    title: 'Name',
    key: 'name'
  }, {
    title: 'Email',
    key: 'email',
  }]

  return (
    <div style={{ padding: '30px' }}>
      <QueryTable
        layout="vertical"
        columns={columns}
        remoteDataSource={remoteDataSource}
        fields={fields}
        rowKey="id"
      />
    </div>
  )
}

添加操作列

添加操作列

说明:AntD 4.x 的Icon采用按需引入的方式,使用前需要注册Icon插件。

// global.ts
import { Icon } from 'sula';
import { DeleteOutlined } from '@ant-design/icons';

// 注册所需的icon
Icon.iconRegister({
  delete: DeleteOutlined,
});
  • render: 操作列配置
    • type:渲染插件类型 文本 图标 按钮
    • props:属性
    • action: 行为插件,点击触发行为,支持多种格式
      • string:行为插件类型,如refreshtable刷新表格
      • function:回调函数
      • object:可配置 type final error 等属性
      • array:支持以上几种类型,promise链式调用

action类型为request时,url、params等可配置在对象中;sula会帮你处理请求

  const columns = [{
    title: 'Sequence',
    key: 'index'
  }, {
    title: 'Country',
    key: 'nat'
  }, {
    title: 'Age',
    key: 'age',
  }, {
    title: 'Name',
    key: 'name'
  }, {
    title: 'Email',
    key: 'email',
+  }, {
+    title: 'Operator',
+    key: 'operator',
+    render: [
+      {
+        confirm: 'delete or not',
+        type: 'icon',
+        props: {
+          type: 'delete'
+        },
+        action: [
+          {
+            type: 'request',
+            url: 'http://rap2.taobao.org:38080/app/mock/256045/table/detele',
+            method: 'POST',
+            params: {
+              id: '#{record.id}',
+            },
+            successMessage: 'successfully deleted',
+          },
+          'refreshTable'
+        ]
+      }
+    ]
  }]

添加操作列

添加排序、筛选

  • filters: 表头的筛选菜单项
  • filterRender:过滤插件
  • sorter:服务端排序 可设为ture

本例中filterRender设置的search为sula内置过滤插件,效果如下图

avatar
const columns = [{
    title: 'Sequence',
    key: 'index',
+    sorter: true
  }, {
    title: 'Country',
    key: 'nat',
+    filterRender: 'search'
  }, {
    title: 'Age',
    key: 'age',
+    filters: [{
+      text: 3,
+      value: 3
+    }, {
+      text: 12,
+      value: 12
+    }, {
+      text: 18,
+      value: 18
+    }],
  }, {
    title: 'Name',
    key: 'name'
  }

添加排序筛选

在表头添加操作按钮

  • rowSelection: 表格行是否可选择
  • actionsRender: 表头操作按钮配置,同 操作列render配置

disabled: 根据是否勾选选择框来判断批量操作的状态,参数ctx为 table实例、table数据源、history

+  const actionsRender = [{
+    type: 'button',
+    disabled: ctx => {
+      const selectedRowKeys = ctx.table.getSelectedRowKeys() || [];
+      return !selectedRowKeys.length;
+    },
+    props: {
+      children: 'Bulk Export',
+      type: 'primary'
+    },
+    action: [
+      () => {
+        console.log('Bulk Export')
+      },
+      'refreshTable' // 刷新表格
+    ]
+  }]

  return (
    <div style={{ padding: '30px' }}>
      <QueryTable
        layout="vertical"
        columns={columns}
        remoteDataSource={remoteDataSource}
+        actionsRender={actionsRender}
+        rowSelection={{}}
        fields={fields}
        rowKey="id"
      />
    </div>
  )

最终代码

import React from 'react';
import { QueryTable } from 'sula';

export default () => {
  const fields = [{
    name: 'name',
    label: 'Name',
    field: {
      type: 'input',
      props: {
        placeholder: 'please input'
      }
    }
  }, {
    name: 'nat',
    label: 'Country',
    field: 'input'
  }, {
    name: 'email',
    label: 'Email',
    field: 'input'
  }, {
    name: 'index',
    label: 'Sequence',
    field: 'input'
  }, {
    name: 'age',
    label: 'Age',
    field: 'input'
  }, {
    name: 'others',
    label: 'Others',
    field: 'input'
  }]

  const remoteDataSource = {
    url: 'http://rap2.taobao.org:38080/app/mock/256045/table/list',
    method: 'GET',
    convertParams({ params }) {
      return {
        results: 3,
        ...params,
      };
    },
    converter({ data }) {
      return {
        list: data.results.map((item, index) => {
          return {
            ...item,
            id: index,
            index,
          };
        }),
        total: 10,
      };
    },
  };
  
  const columns = [{
    title: 'Sequence',
    key: 'index',
    sorter: true
  }, {
    title: 'Country',
    key: 'nat',
    filterRender: 'search'
  }, {
    title: 'Age',
    key: 'age',
    filters: [{
      text: 3,
      value: 3
    }, {
      text: 12,
      value: 12
    }, {
      text: 18,
      value: 18
    }],
  }, {
    title: 'Name',
    key: 'name'
  }, {
    title: 'Email',
    key: 'email',
  }, {
    title: 'Operator',
    key: 'operator',
    render: [
      {
        confirm: 'delete or not',
        type: 'icon',
        props: {
          type: 'delete'
        },
        action: [
          {
            type: 'request',
            url: 'http://rap2.taobao.org:38080/app/mock/256045/table/detele',
            method: 'POST',
            params: {
              id: '#{record.id}',
            },
            successMessage: 'successfully deleted',
          },
          'refreshTable'
        ]
      }
    ]
  }]

  const actionsRender = [{
    type: 'button',
    disable: ctx => {
      const selectedRowKeys = ctx.table.getSelectedRowKeys() || [];
      return !selectedRowKeys.length;
    },
    props: {
      children: 'Bulk Export',
      type: 'primary'
    },
    action: [
      () => {
        console.log('Bulk Export')
      },
      'refreshTable'
    ]
  }]

  return (
    <QueryTable
      layout="vertical"
      columns={columns}
      remoteDataSource={remoteDataSource}
      actionsRender={actionsRender}
      rowSelection={{}}
      fields={fields}
      rowKey="id"
    />
  )
}

最终呈现


最终呈现

体验sula

sula群
  1. 源码地址:github ,期待star
  2. demo和文档:sula
  3. sula配置化体验工具:sula-cooker

欢迎试用sulajs,有不符合业务场景的地方,期待issue和pr。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352