diff --git a/docs/zh-CN/components/crud.md b/docs/zh-CN/components/crud.md index 817a8c6e7..5b4b927d7 100755 --- a/docs/zh-CN/components/crud.md +++ b/docs/zh-CN/components/crud.md @@ -519,6 +519,78 @@ Cards 模式支持 [Cards](./cards) 中的所有功能。 如果想前端实现过滤功能,请看[前端一次性加载](#前端一次性加载)部分。 +### 自动生成查询区域 + +通过设置`"autoGeneratedFilter": true`开启查询区域,会根据列元素的 `searchable` 属性值,自动生成查询条件表单,只有 `searchable` 属性值为合法的组件 Schema 时才会生成查询条件。注意这个属性和 `filter` 冲突,开启 `filter` 后 `autoGeneratedFilter` 将会失效。 + +```schema: scope="body" +{ + "type": "crud", + "api": "/api/sample", + "syncLocation": false, + "autoGenerateFilter": true, + "columns": [ + { + "name": "id", + "label": "ID", + "searchable": { + "type": "input-text", + "name": "id", + "label": "主键", + "placeholder": "输入id" + } + }, + { + "name": "engine", + "label": "Rendering engine" + }, + { + "name": "browser", + "label": "Browser", + "searchable": { + "type": "select", + "name": "browser", + "label": "浏览器", + "placeholder": "选择浏览器", + "options": [ + { + "label": "Internet Explorer ", + "value": "ie" + }, + { + "label": "AOL browser", + "value": "aol" + }, + { + "label": "Firefox", + "value": "firefox" + } + ] + } + }, + { + "name": "platform", + "label": "Platform(s)" + }, + { + "name": "version", + "label": "Engine version", + "searchable": { + "type": "input-number", + "name": "version", + "label": "版本号", + "placeholder": "输入版本号", + "mode": "horizontal" + } + }, + { + "name": "grade", + "label": "CSS grade" + } + ] +} +``` + ## 配置默认请求参数 可以配置`defaultParams`,来指定拉取接口时的默认参数: @@ -1960,6 +2032,51 @@ crud 组件支持通过配置`headerToolbar`和`footerToolbar`属性,实现在 这样就只会发送 ids 了。 +### 列排序 + +通过配置`headerToolbar` 中 `columns-toggler` 的 `"draggable": true`可以实现设置显示列和列排序功能。 + +```schema: scope="body" +{ + "type": "crud", + "api": "/api/sample", + "syncLocation": false, + "headerToolbar": [ + { + "type": "columns-toggler", + "align": "right", + "draggable": true + } + ], + "columns": [ + { + "name": "id", + "label": "ID" + }, + { + "name": "engine", + "label": "Rendering engine" + }, + { + "name": "browser", + "label": "Browser" + }, + { + "name": "platform", + "label": "Platform(s)" + }, + { + "name": "version", + "label": "Engine version" + }, + { + "name": "grade", + "label": "CSS grade" + } + ] +} +``` + ## 单条操作 当操作对象是单条数据时这类操作叫单条操作,比如:编辑、删除、通过、拒绝等等。CRUD 的 table 模式可以在 column 通过放置按钮来完成(其他模式参考 table 模式)。比如编辑就是添加个按钮行为是弹框类型的按钮或者添加一个页面跳转类型的按钮把当前行数据的 id 放在 query 中传过去、删除操作就是配置一个按钮行为是 AJAX 类型的按钮,将数据通过 api 发送给后端完成。 @@ -2290,6 +2407,7 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据, | footerToolbar | Array | `['statistics', 'pagination']` | 底部工具栏配置 | | alwaysShowPagination | `boolean` | `false` | 是否总是显示分页 | | affixHeader | `boolean` | `true` | 是否固定表头(table 下) | +| autoGeneratedFilter | `boolean` | `false` | 是否开启查询区域,开启后会根据列元素的 `searchable` 属性值,自动生成查询条件表单 | 注意除了上面这些属性,CRUD 在不同模式下的属性需要参考各自的文档,比如 diff --git a/docs/zh-CN/components/table.md b/docs/zh-CN/components/table.md index dcc8dd330..ead681df9 100755 --- a/docs/zh-CN/components/table.md +++ b/docs/zh-CN/components/table.md @@ -1613,6 +1613,140 @@ order: 67 注意这个属性和 `checkOnItemClick` 冲突,因为都是定义行的点击行为,开启 `itemAction` 后 `checkOnItemClick` 将会失效。 +## 行角标 + +> 1.4.0 及以上版本 + +通过属性`itemBadge`,可以为表格行配置[角标](./badge),可以使用[数据映射](../../../docs/concepts/data-mapping)为每一行添加特定的 Badge 属性。[`visibleOn`](../../../docs/concepts/expression)属性控制显示的条件,表达式中`this`可以取到行所在上下文的数据,比如行数据中有`badgeText`字段才显示角标,可以设置`"visibleOn": "this.badgeText"` + +```schema: scope="body" +{ + "type": "service", + "body": { + "type": "table", + "source": "${table}", + "syncLocation": false, + "itemBadge": { + "text": "${badgeText}", + "mode": "ribbon", + "position": "top-left", + "level": "${badgeLevel}", + "visibleOn": "this.badgeText" + }, + "columns": [ + { + "name": "id", + "label": "ID", + "searchable": { + "type": "input-text", + "name": "id", + "label": "主键", + "placeholder": "输入id", + "size": "sm", + } + }, + { + "name": "engine", + "label": "Rendering engine" + }, + { + "name": "browser", + "label": "Browser", + "searchable": { + "type": "select", + "name": "browser", + "label": "浏览器", + "placeholder": "选择浏览器", + "size": "sm", + "options": [ + { + "label": "Internet Explorer ", + "value": "ie" + }, + { + "label": "AOL browser", + "value": "aol" + }, + { + "label": "Firefox", + "value": "firefox" + } + ] + } + }, + { + "name": "platform", + "label": "Platform(s)" + }, + { + "name": "version", + "label": "Engine version", + "searchable": { + "type": "input-number", + "name": "version", + "label": "版本号", + "placeholder": "输入版本号", + "size": "sm", + "mode": "horizontal" + } + }, + { + "name": "grade", + "label": "CSS grade" + } + ] + }, + data: { + table: [ + { + "id": 1, + "engine": "Trident", + "browser": "Internet Explorer 4.0", + "platform": "Win 95+", + "version": "4", + "grade": "X", + "badgeText": "默认", + "badgeLevel": "info" + }, + { + "id": 2, + "engine": "Trident", + "browser": "Internet Explorer 5.0", + "platform": "Win 95+", + "version": "5", + "grade": "C", + "badgeText": "危险", + "badgeLevel": "danger" + }, + { + "id": 3, + "engine": "Trident", + "browser": "Internet Explorer 5.5", + "platform": "Win 95+", + "version": "5.5", + "grade": "A" + }, + { + "id": 4, + "engine": "Trident", + "browser": "Internet Explorer 6", + "platform": "Win 98+", + "version": "6", + "grade": "A" + }, + { + "id": 5, + "engine": "Trident", + "browser": "Internet Explorer 7", + "platform": "Win XP SP2+", + "version": "7", + "grade": "A" + } + ] + } +} +``` + ## 属性表 | 属性名 | 类型 | 默认值 | 说明 | @@ -1638,6 +1772,7 @@ order: 67 | rowClassNameExpr | [模板](../../docs/concepts/template) | | 通过模板给行添加 CSS 类名 | | prefixRow | `Array` | | 顶部总结行 | | affixRow | `Array` | | 底部总结行 | +| itemBadge | [`BadgeSchema`](./badge) | | 行角标配置 | ## 列配置属性表 @@ -1645,11 +1780,11 @@ order: 67 | ---------- | --------------------------------------------- | ------- | ---------------- | | label | [模板](../../docs/concepts/template) | | 表头文本内容 | | name | `string` | | 通过名称关联数据 | -| fixed | `left`/`right`/`none` | | 是否固定当前列 | +| fixed | `left` \| `right` \| `none` | | 是否固定当前列 | | popOver | | | 弹出框 | | quickEdit | | | 快速编辑 | | copyable | `boolean` 或 `{icon: string, content:string}` | | 是否可复制 | | sortable | `boolean` | `false` | 是否可排序 | -| searchable | `boolean` | `false` | 是否可快速搜索 | -| width | `number`/`string` | 列宽 | +| searchable | `boolean` \| `Schema` | `false` | 是否可快速搜索 | +| width | `number` \| `string` | 列宽 | | remark | | | 提示信息 | diff --git a/mock/cfc/mock/sample.db.js b/mock/cfc/mock/sample.db.js index 9d15698a2..c68ae042e 100755 --- a/mock/cfc/mock/sample.db.js +++ b/mock/cfc/mock/sample.db.js @@ -1,405 +1,407 @@ module.exports = [ - { - "engine": "Trident", - "browser": "Internet Explorer 4.0", - "platform": "Win 95+", - "version": "4", - "grade": "X" - }, - { - "engine": "Trident", - "browser": "Internet Explorer 5.0", - "platform": "Win 95+", - "version": "5", - "grade": "C" - }, - { - "engine": "Trident", - "browser": "Internet Explorer 5.5", - "platform": "Win 95+", - "version": "5.5", - "grade": "A" - }, - { - "engine": "Trident", - "browser": "Internet Explorer 6", - "platform": "Win 98+", - "version": "6", - "grade": "A" - }, - { - "engine": "Trident", - "browser": "Internet Explorer 7", - "platform": "Win XP SP2+", - "version": "7", - "grade": "A" - }, - { - "engine": "Trident", - "browser": "AOL browser (AOL desktop)", - "platform": "Win XP", - "version": "6", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Firefox 1.0", - "platform": "Win 98+ / OSX.2+", - "version": "1.7", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Firefox 1.5", - "platform": "Win 98+ / OSX.2+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Firefox 2.0", - "platform": "Win 98+ / OSX.2+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Firefox 3.0", - "platform": "Win 2k+ / OSX.3+", - "version": "1.9", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Camino 1.0", - "platform": "OSX.2+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Camino 1.5", - "platform": "OSX.3+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Netscape 7.2", - "platform": "Win 95+ / Mac OS 8.6-9.2", - "version": "1.7", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Netscape Browser 8", - "platform": "Win 98SE+", - "version": "1.7", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Netscape Navigator 9", - "platform": "Win 98+ / OSX.2+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.0", - "platform": "Win 95+ / OSX.1+", - "version": "1", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.1", - "platform": "Win 95+ / OSX.1+", - "version": "1.1", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.2", - "platform": "Win 95+ / OSX.1+", - "version": "1.2", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.3", - "platform": "Win 95+ / OSX.1+", - "version": "1.3", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.4", - "platform": "Win 95+ / OSX.1+", - "version": "1.4", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.5", - "platform": "Win 95+ / OSX.1+", - "version": "1.5", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.6", - "platform": "Win 95+ / OSX.1+", - "version": "1.6", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.7", - "platform": "Win 98+ / OSX.1+", - "version": "1.7", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Mozilla 1.8", - "platform": "Win 98+ / OSX.1+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Seamonkey 1.1", - "platform": "Win 98+ / OSX.2+", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Gecko", - "browser": "Epiphany 2.20", - "platform": "Gnome", - "version": "1.8", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "Safari 1.2", - "platform": "OSX.3", - "version": "125.5", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "Safari 1.3", - "platform": "OSX.3", - "version": "312.8", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "Safari 2.0", - "platform": "OSX.4+", - "version": "419.3", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "Safari 3.0", - "platform": "OSX.4+", - "version": "522.1", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "OmniWeb 5.5", - "platform": "OSX.4+", - "version": "420", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "iPod Touch / iPhone", - "platform": "iPod", - "version": "420.1", - "grade": "A" - }, - { - "engine": "Webkit", - "browser": "S60", - "platform": "S60", - "version": "413", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 7.0", - "platform": "Win 95+ / OSX.1+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 7.5", - "platform": "Win 95+ / OSX.2+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 8.0", - "platform": "Win 95+ / OSX.2+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 8.5", - "platform": "Win 95+ / OSX.2+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 9.0", - "platform": "Win 95+ / OSX.3+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 9.2", - "platform": "Win 88+ / OSX.3+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera 9.5", - "platform": "Win 88+ / OSX.3+", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Opera for Wii", - "platform": "Wii", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Nokia N800", - "platform": "N800", - "version": "-", - "grade": "A" - }, - { - "engine": "Presto", - "browser": "Nintendo DS browser", - "platform": "Nintendo DS", - "version": "8.5", - "grade": "C" - }, - { - "engine": "KHTML", - "browser": "Konqureror 3.1", - "platform": "KDE 3.1", - "version": "3.1", - "grade": "C" - }, - { - "engine": "KHTML", - "browser": "Konqureror 3.3", - "platform": "KDE 3.3", - "version": "3.3", - "grade": "A" - }, - { - "engine": "KHTML", - "browser": "Konqureror 3.5", - "platform": "KDE 3.5", - "version": "3.5", - "grade": "A" - }, - { - "engine": "Tasman", - "browser": "Internet Explorer 4.5", - "platform": "Mac OS 8-9", - "version": "-", - "grade": "X" - }, - { - "engine": "Tasman", - "browser": "Internet Explorer 5.1", - "platform": "Mac OS 7.6-9", - "version": "1", - "grade": "C" - }, - { - "engine": "Tasman", - "browser": "Internet Explorer 5.2", - "platform": "Mac OS 8-X", - "version": "1", - "grade": "C" - }, - { - "engine": "Misc", - "browser": "NetFront 3.1", - "platform": "Embedded devices", - "version": "-", - "grade": "C" - }, - { - "engine": "Misc", - "browser": "NetFront 3.4", - "platform": "Embedded devices", - "version": "-", - "grade": "A" - }, - { - "engine": "Misc", - "browser": "Dillo 0.8", - "platform": "Embedded devices", - "version": "-", - "grade": "X" - }, - { - "engine": "Misc", - "browser": "Links", - "platform": "Text only", - "version": "-", - "grade": "X" - }, - { - "engine": "Misc", - "browser": "Lynx", - "platform": "Text only", - "version": "-", - "grade": "X" - }, - { - "engine": "Misc", - "browser": "IE Mobile", - "platform": "Windows Mobile 6", - "version": "-", - "grade": "C" - }, - { - "engine": "Misc", - "browser": "PSP browser", - "platform": "PSP", - "version": "-", - "grade": "C" - }, - { - "engine": "Other browsers", - "browser": "All others", - "platform": "-", - "version": "-", - "grade": "U" - } - ].map(function (item, index) { - return Object.assign({}, item, { - id: index + 1 - }) - }); + { + "engine": "Trident", + "browser": "Internet Explorer 4.0", + "platform": "Win 95+", + "version": "4", + "grade": "X", + "badgeText": "默认" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 5.0", + "platform": "Win 95+", + "version": "5", + "grade": "C", + "badgeText": "危险" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 5.5", + "platform": "Win 95+", + "version": "5.5", + "grade": "A" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 6", + "platform": "Win 98+", + "version": "6", + "grade": "A" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 7", + "platform": "Win XP SP2+", + "version": "7", + "grade": "A" + }, + { + "engine": "Trident", + "browser": "AOL browser (AOL desktop)", + "platform": "Win XP", + "version": "6", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Firefox 1.0", + "platform": "Win 98+ / OSX.2+", + "version": "1.7", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Firefox 1.5", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Firefox 2.0", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Firefox 3.0", + "platform": "Win 2k+ / OSX.3+", + "version": "1.9", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Camino 1.0", + "platform": "OSX.2+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Camino 1.5", + "platform": "OSX.3+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Netscape 7.2", + "platform": "Win 95+ / Mac OS 8.6-9.2", + "version": "1.7", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Netscape Browser 8", + "platform": "Win 98SE+", + "version": "1.7", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Netscape Navigator 9", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.0", + "platform": "Win 95+ / OSX.1+", + "version": "1", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.1", + "platform": "Win 95+ / OSX.1+", + "version": "1.1", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.2", + "platform": "Win 95+ / OSX.1+", + "version": "1.2", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.3", + "platform": "Win 95+ / OSX.1+", + "version": "1.3", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.4", + "platform": "Win 95+ / OSX.1+", + "version": "1.4", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.5", + "platform": "Win 95+ / OSX.1+", + "version": "1.5", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.6", + "platform": "Win 95+ / OSX.1+", + "version": "1.6", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.7", + "platform": "Win 98+ / OSX.1+", + "version": "1.7", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.8", + "platform": "Win 98+ / OSX.1+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Seamonkey 1.1", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Gecko", + "browser": "Epiphany 2.20", + "platform": "Gnome", + "version": "1.8", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "Safari 1.2", + "platform": "OSX.3", + "version": "125.5", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "Safari 1.3", + "platform": "OSX.3", + "version": "312.8", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "Safari 2.0", + "platform": "OSX.4+", + "version": "419.3", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "Safari 3.0", + "platform": "OSX.4+", + "version": "522.1", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "OmniWeb 5.5", + "platform": "OSX.4+", + "version": "420", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "iPod Touch / iPhone", + "platform": "iPod", + "version": "420.1", + "grade": "A" + }, + { + "engine": "Webkit", + "browser": "S60", + "platform": "S60", + "version": "413", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 7.0", + "platform": "Win 95+ / OSX.1+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 7.5", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 8.0", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 8.5", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 9.0", + "platform": "Win 95+ / OSX.3+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 9.2", + "platform": "Win 88+ / OSX.3+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera 9.5", + "platform": "Win 88+ / OSX.3+", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Opera for Wii", + "platform": "Wii", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Nokia N800", + "platform": "N800", + "version": "-", + "grade": "A" + }, + { + "engine": "Presto", + "browser": "Nintendo DS browser", + "platform": "Nintendo DS", + "version": "8.5", + "grade": "C" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.1", + "platform": "KDE 3.1", + "version": "3.1", + "grade": "C" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.3", + "platform": "KDE 3.3", + "version": "3.3", + "grade": "A" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.5", + "platform": "KDE 3.5", + "version": "3.5", + "grade": "A" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 4.5", + "platform": "Mac OS 8-9", + "version": "-", + "grade": "X" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 5.1", + "platform": "Mac OS 7.6-9", + "version": "1", + "grade": "C" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 5.2", + "platform": "Mac OS 8-X", + "version": "1", + "grade": "C" + }, + { + "engine": "Misc", + "browser": "NetFront 3.1", + "platform": "Embedded devices", + "version": "-", + "grade": "C" + }, + { + "engine": "Misc", + "browser": "NetFront 3.4", + "platform": "Embedded devices", + "version": "-", + "grade": "A" + }, + { + "engine": "Misc", + "browser": "Dillo 0.8", + "platform": "Embedded devices", + "version": "-", + "grade": "X" + }, + { + "engine": "Misc", + "browser": "Links", + "platform": "Text only", + "version": "-", + "grade": "X" + }, + { + "engine": "Misc", + "browser": "Lynx", + "platform": "Text only", + "version": "-", + "grade": "X" + }, + { + "engine": "Misc", + "browser": "IE Mobile", + "platform": "Windows Mobile 6", + "version": "-", + "grade": "C" + }, + { + "engine": "Misc", + "browser": "PSP browser", + "platform": "PSP", + "version": "-", + "grade": "C" + }, + { + "engine": "Other browsers", + "browser": "All others", + "platform": "-", + "version": "-", + "grade": "U" + } +].map(function (item, index) { + return Object.assign({}, item, { + id: index + 1 + }) +}); diff --git a/scss/_properties.scss b/scss/_properties.scss index 046614759..e34be64e6 100644 --- a/scss/_properties.scss +++ b/scss/_properties.scss @@ -1178,6 +1178,8 @@ --Table-toolbar-marginX: 0; --Table-toolbar-marginY: var(--gap-base); --Table-tree-borderColor: var(--Table-borderColor); + --Table-searchableForm-backgroundColor: #f6f7f8; + --Table-searchableForm-borderRadius: #{px2rem(4px)}; --TableCell--edge-paddingX: var(--gap-md); --TableCell-filterBtn--onActive-color: var(--primary); @@ -1378,4 +1380,11 @@ --Steps-line-success-bg: var(--Steps-status-success); --Progress-borderRadius: var(--borderRadius); + --ColumnToggler-backgroundColor: var(--white); + --ColumnToggler-borderRadius: #{px2rem(4px)}; + --ColumnToggler-lineHeight: #{px2rem(24px)}; + --ColumnToggler-title-fontColor: #080e1a; + --ColumnToggler-fontColor: #151a26; + --ColumnToggler-item-backgroundColor: #f6f7f8; + --ColumnToggler-item-backgroundColor-onHover: rgba(36, 104, 242, 0.1); } diff --git a/scss/components/_column-toggler.scss b/scss/components/_column-toggler.scss new file mode 100644 index 000000000..1b401c788 --- /dev/null +++ b/scss/components/_column-toggler.scss @@ -0,0 +1,234 @@ +.#{$ns}ColumnToggler { + position: relative; + display: inline-block; + + &-caret { + margin-left: var(--DropDown-caret-marginLeft); + display: inline-block; + vertical-align: top; + transition: transform var(--animation-duration) ease; + + > svg { + width: px2rem(10px); + height: px2rem(10px); + top: 0.125em; + } + } + + &.is-opened &-caret { + transform: rotate(180deg); + } + + &.is-actived > .#{$ns}Button { + color: var(--link-color); + } + + &--block { + display: block; + + .#{$ns}Button { + display: block; + } + } + + &-menu { + position: absolute; + z-index: $zindex-dropdown; + top: 100%; + left: 0; + margin: px2rem(1px) 0 0; + background: var(--DropDown-menu-bg); + list-style: none; + padding: var(--DropDown-menu-paddingY) var(--DropDown-menu-paddingX); + border: var(--DropDown-menu-borderWidth) solid + var(--DropDown-menu-borderColor); + border-radius: var(--DropDown-menu-borderRadius); + box-shadow: var(--DropDown-menu-boxShadow); + min-width: var(--DropDown-menu-minWidth); + text-align: left; + } + + &--alignRight &-menu { + left: auto; + right: 0; + } + + &-menuItem, + &-menu > li { + padding: var(--DropDown-menuItem-paddingY) var(--DropDown-menuItem-paddingX); + white-space: nowrap; + box-sizing: border-box; + height: var(--DropDown-menu-height); + vertical-align: middle; + user-select: none; + color: var(--link-color); + text-decoration: var(--link-decoration); + + &:hover { + background: var(--DropDown-menuItem-onHover-bg); + color: var(--DropDown-menuItem-onHover-color); + } + + &.is-active { + color: var(--DropDown-menuItem-onActive-color); + } + + &:not(.is-disabled), + &:not(.disabled) { + cursor: pointer; + } + + &.is-disabled { + cursor: not-allowed; + // pointer-events: none; + color: var(--DropDown-menuItem-onDisabled-color); + filter: grayscale(80%); + } + + &.#{$ns}DropDown-divider { + height: px2rem(1px); + margin: px2rem(9px) 0; + overflow: hidden; + background: var(--DropDown-menu-borderColor); + padding: 0; + } + } + + &-menu > li a { + color: inherit; + display: block; + text-decoration: none; + } + + &-popover { + border: none; + box-shadow: none; + } + + > .#{$ns}Button { + min-width: unset; + } + + @mixin flex-layout($direction: row) { + display: flex; + flex-flow: $direction nowrap; + justify-content: space-between; + align-items: center; + } + + &-modal { + @include flex-layout(column); + width: px2rem(400px); + padding: var(--ColumnToggler-lineHeight); + margin-top: calc(50vh - 100px); + border-radius: var(--ColumnToggler-borderRadius); + background: var(--ColumnToggler-backgroundColor); + box-shadow: 0 4px 5px 0 rgba(21, 26, 38, 0.06), + 0 1px 10px 0 rgba(21, 26, 38, 0.05), 0 2px 4px -1px rgba(21, 26, 38, 0.04); + + &-header { + width: 100%; + @include flex-layout(); + + a, + span { + display: inline-block; + } + + .#{$ns}ColumnToggler-modal-title { + opacity: 0.95; + font-size: var(--fontSizeMd); + color: var(--ColumnToggler-title-fontColor); + line-height: var(--ColumnToggler-lineHeight); + font-weight: bold; + } + } + + &-content { + padding: 0; + width: 100%; + list-style: none; + margin: px2rem(8px) 0; + + .#{$ns}ColumnToggler-menuItem { + color: var(--ColumnToggler-title-fontColor); + background: var(--ColumnToggler-item-backgroundColor); + border-radius: px2rem(2px); + font-size: var(--fontSizeSm); + padding: px2rem(4px) px2rem(8px); + height: var(--ColumnToggler-lineHeight); + width: calc((100% - 24px) / 3); + float: left; + margin: px2rem(4px); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &:hover { + background: var(--ColumnToggler-item-backgroundColor-onHover); + } + + &--dragging { + border: 1px solid var(--link-color); + } + + label { + > i { + height: var(--fontSizeLg); + width: var(--fontSizeLg); + vertical-align: top; + } + } + + &-dragBar { + margin-left: 0; + margin-right: var(--gap-xs); + cursor: move; + color: #d8d8d8; + } + } + } + + &-footer { + width: 100%; + @include flex-layout(); + + .#{$ns}ColumnToggler-modeSelect { + color: var(--ColumnToggler-fontColor); + font-size: var(--fontSizeSm); + + &.is-actived { + color: var(--link-color); + } + } + + & > div { + @include flex-layout(); + + &:first-child { + justify-content: flex-start; + + a { + display: inline-block; + } + } + + &:last-child { + justify-content: flex-end; + + button { + width: 72px; + } + } + } + } + } +} + +.#{$ns}ColumnToggler-tooltip { + border: none; + + .#{$ns}Tooltip-arrow::before { + border-top-color: transparent; + } +} diff --git a/scss/components/_table.scss b/scss/components/_table.scss index 2022a2c57..e37d236ac 100644 --- a/scss/components/_table.scss +++ b/scss/components/_table.scss @@ -136,6 +136,22 @@ } } + &-searchableForm { + background: var(--Table-searchableForm-backgroundColor); + border-radius: var(--Table-searchableForm-borderRadius); + + &-footer { + padding: var(--Panel-footerPadding); + clear: both; + } + + &-checkbox { + & > div > .#{$ns}CheckboxControl { + padding-top: 0; + } + } + } + &-header { padding: var(--Table-toolbar-marginY) var(--Table-toolbar-marginX); @@ -317,6 +333,8 @@ } > tbody > tr { + position: relative; + & + tr { border-top: var(--Table-borderWidth) solid var(--Table-borderColor); > th { @@ -867,6 +885,12 @@ visibility: hidden; position: absolute; } + + &-badge { + position: absolute; + top: 0; + left: 0; + } } .#{$ns}InputTable-toolbar { diff --git a/scss/themes/_common.scss b/scss/themes/_common.scss index 727bfbc18..f3bf8cb3f 100644 --- a/scss/themes/_common.scss +++ b/scss/themes/_common.scss @@ -42,6 +42,7 @@ @import '../components/wizard'; @import '../components/crud'; @import '../components/table'; +@import '../components/column-toggler'; @import '../components/list'; @import '../components/cards'; @import '../components/card'; diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index abe5be09c..df4dde86e 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -13,7 +13,7 @@ import {ClassNamesFn} from '../theme'; * Badge 角标。 * 文档:https://baidu.gitee.io/amis/docs/components/badge */ -export interface BadgeSchema extends BaseSchema { +export interface BadgeSchema extends Omit { /** * 文本内容 */ @@ -64,7 +64,7 @@ export interface BadgeSchema extends BaseSchema { /** * 提示类型 */ - level?: 'info' | 'warning' | 'success' | 'danger'; + level?: 'info' | 'warning' | 'success' | 'danger' | SchemaExpression; } export interface BadgeProps { @@ -89,17 +89,13 @@ export class Badge extends React.Component { animationElement: any ) { const {classnames: cx, badge, data} = this.props; - let { - mode = 'dot', - level = 'danger', - style - } = badge as BadgeSchema; + let {mode = 'dot', level = 'danger', style} = badge as BadgeSchema; if (typeof level === 'string' && level[0] === '$') { level = resolveVariable(level, data); } - switch(mode) { + switch (mode) { case 'dot': return ( { case 'text': return ( {text} @@ -127,14 +127,17 @@ export class Badge extends React.Component { style={{width: outSize, height: outSize}} > {text} {animationElement} - ); default: return null; @@ -155,6 +158,7 @@ export class Badge extends React.Component { let { mode = 'dot', text, + level, size, style, offset, @@ -177,7 +181,7 @@ export class Badge extends React.Component { if (typeof size === 'undefined') { if (mode === 'dot') { size = 6; - } else if (mode === 'ribbon'){ + } else if (mode === 'ribbon') { size = 12; } else { size = 16; @@ -194,7 +198,9 @@ export class Badge extends React.Component { // 当text、overflowCount都为number类型时,进行封顶值处理 if (typeof text === 'number' && typeof overflowCount === 'number') { text = ( - (text as number) > (overflowCount as number) ? `${overflowCount}+` : text + (text as number) > (overflowCount as number) + ? `${overflowCount}+` + : text ) as string | number; } @@ -222,7 +228,7 @@ export class Badge extends React.Component { const left = `calc(50% + ${parseInt(offset[0] as string, 10)}px)`; const right = `calc(-50% + ${parseInt(offset[1] as string, 10)}px)`; offsetStyle = { - transform: `translate(${left}, ${right})`, + transform: `translate(${left}, ${right})` }; } @@ -250,15 +256,16 @@ export class Badge extends React.Component { return (
{children} - {isDisplay ? - this.renderBadge( - text, - size, - position, - offsetStyle, - sizeStyle, - animationElement - ) : null} + {isDisplay + ? this.renderBadge( + text, + size, + position, + offsetStyle, + sizeStyle, + animationElement + ) + : null}
); } diff --git a/src/locale/de-DE.ts b/src/locale/de-DE.ts index 61792d1e4..f6a4c05b7 100644 --- a/src/locale/de-DE.ts +++ b/src/locale/de-DE.ts @@ -172,6 +172,8 @@ register('de-DE', { 'Table.startSort': 'Klicken, um Sortierung zu starten', 'Table.valueField': 'valueField muss vorhanden sein', 'Table.index': 'Index', + 'Table.toggleColumn': 'Spalten anzeigen', + 'Table.searchFields': 'Abfragefelder setzen', 'Tag.placeholder': 'Noch kein Tag', 'Tag.tip': 'Kürzlich verwendetes Tag', 'Text.add': 'Neu {{label}}', diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index 6aeb1e6c9..d11b3b594 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -173,6 +173,8 @@ register('en-US', { 'Table.startSort': 'Click to start sorting', 'Table.valueField': 'Must have valueField', 'Table.index': 'Index', + 'Table.toggleColumn': 'Display columns', + 'Table.searchFields': 'Set query fields', 'Tag.placeholder': 'No tag yet', 'Tag.tip': 'Recently used tag', 'Text.add': 'New {{label}}', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index 2df03471a..499bcd2e2 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -177,6 +177,8 @@ register('zh-CN', { 'Table.startSort': '点击开始排序', 'Table.valueField': '请配置 valueField', 'Table.index': '序号', + 'Table.toggleColumn': '显示列', + 'Table.searchFields': '设置查询字段', 'Tag.placeholder': '暂无标签', 'Tag.tip': '最近使用的标签', 'Text.add': '新增:{{label}}', diff --git a/src/renderers/CRUD.tsx b/src/renderers/CRUD.tsx index b77d4e19a..1f88ac8ac 100644 --- a/src/renderers/CRUD.tsx +++ b/src/renderers/CRUD.tsx @@ -345,6 +345,7 @@ export default class CRUD extends React.Component { 'footerToolbar', 'filterTogglable', 'filterDefaultVisible', + 'autoGenerateFilter', 'syncResponse2Query', 'keepItemSelectionOnPageChange', 'labelTpl', @@ -1985,6 +1986,7 @@ export default class CRUD extends React.Component { popOverContainer, translate: __, onQuery, + autoGenerateFilter, ...rest } = this.props; @@ -2035,6 +2037,7 @@ export default class CRUD extends React.Component { key: 'body', className: cx('Crud-body', bodyClassName), ref: this.controlRef, + autoGenerateFilter: !filter && autoGenerateFilter, selectable: !!( (this.hasBulkActionsToolbar() && this.hasBulkActions()) || pickerMode @@ -2067,6 +2070,9 @@ export default class CRUD extends React.Component { onSelect: this.handleSelect, onPopOverOpened: this.handleChildPopOverOpen, onPopOverClosed: this.handleChildPopOverClose, + onSearchableFromReset: this.handleFilterReset, + onSearchableFromSubmit: this.handleFilterSubmit, + onSearchableFromInit: this.handleFilterInit, headerToolbarRender: this.renderHeaderToolbar, footerToolbarRender: this.renderFooterToolbar, data: store.mergedData diff --git a/src/renderers/Form/Checkbox.tsx b/src/renderers/Form/Checkbox.tsx index fbe5b0a09..8007d1ed0 100644 --- a/src/renderers/Form/Checkbox.tsx +++ b/src/renderers/Form/Checkbox.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {FormItem, FormControlProps, FormBaseControl} from './Item'; import cx from 'classnames'; import Checkbox from '../../components/Checkbox'; +import {withBadge, BadgeSchema} from '../../components/Badge'; /** * Checkbox 勾选框。 @@ -27,6 +28,11 @@ export interface CheckboxControlSchema extends FormBaseControl { * 选项说明 */ option?: string; + + /** + * 角标 + */ + badge?: BadgeSchema; } export interface CheckboxProps @@ -78,4 +84,6 @@ export default class CheckboxControl extends React.Component< type: 'checkbox', sizeMutable: false }) +// @ts-ignore +@withBadge export class CheckboxControlRenderer extends CheckboxControl {} diff --git a/src/renderers/Form/Combo.tsx b/src/renderers/Form/Combo.tsx index 70cab70b1..430e0ef21 100644 --- a/src/renderers/Form/Combo.tsx +++ b/src/renderers/Form/Combo.tsx @@ -251,7 +251,6 @@ export interface ComboControlSchema extends FormBaseControl { /** * 最大值验证错误提示 */ - maxLengthValidateFailed?: string; }; } diff --git a/src/renderers/Table/ColumnToggler.tsx b/src/renderers/Table/ColumnToggler.tsx new file mode 100644 index 000000000..c71ce1a45 --- /dev/null +++ b/src/renderers/Table/ColumnToggler.tsx @@ -0,0 +1,544 @@ +import React from 'react'; +import {findDOMNode} from 'react-dom'; +import Sortable from 'sortablejs'; +import cloneDeep from 'lodash/cloneDeep'; +import {RendererProps} from '../../factory'; +import Overlay from '../../components/Overlay'; +import PopOver from '../../components/PopOver'; +import Modal from '../../components/Modal'; +import Button from '../../components/Button'; +import Checkbox from '../../components/Checkbox'; +import TooltipWrapper from '../../components/TooltipWrapper'; +import type {TooltipObject} from '../../components/TooltipWrapper'; +import {noop, autobind} from '../../utils/helper'; +import {filter} from '../../utils/tpl'; +import {Icon} from '../../components/icons'; +import {RootClose} from '../../utils/RootClose'; +import {IColumn} from '../../store/table'; + +export interface ColumnTogglerProps extends RendererProps { + /** + * 按钮文字 + */ + label?: string | React.ReactNode; + + /** + * 按钮提示文字,hover focus 时显示 + */ + tooltip?: string | TooltipObject; + + /** + * 禁用状态下的提示 + */ + disabledTip?: string | TooltipObject; + + /** + * 点击外部是否关闭 + */ + closeOnOutside?: boolean; + + /** + * 点击内容是否关闭 + */ + closeOnClick?: boolean; + + /** + * 下拉菜单对齐方式 + */ + align?: 'left' | 'right'; + + /** + * ColumnToggler的CSS类名 + */ + className?: string; + + /** + * 按钮的CSS类名 + */ + btnClassName?: string; + + /** + * 按钮大小 + */ + size?: 'xs' | 'sm' | 'md' | 'lg'; + + /** + * 按钮级别,样式 + */ + level?: 'info' | 'success' | 'danger' | 'warning' | 'primary' | 'link'; + + /** + * 是否独占一行 `display: block` + */ + block?: boolean; + + /** + * 是否可通过拖拽排序 + */ + draggable?: boolean; + + /** + * 默认是否展开 + */ + defaultIsOpened?: boolean; + + /** + * 激活状态 + */ + isActived?: boolean; + + /** + * ICON名称 + */ + icon?: string | React.ReactNode; + + /** + * 是否只显示图标。 + */ + iconOnly?: boolean; + + /** + * 是否隐藏展开的Icon + */ + hideExpandIcon?: boolean; + + /** + * 是否显示遮罩层 + */ + overlay?: boolean; + + /** + * 列数据 + */ + columns: Array; + + onColumnToggle: (columns: Array) => void; + modalContainer?: () => HTMLElement; +} + +export interface ColumnTogglerState { + isOpened: boolean; + enableSorting: boolean; + tempColumns: any[]; +} + +export default class ColumnToggler extends React.Component< + ColumnTogglerProps, + ColumnTogglerState +> { + state: ColumnTogglerState = { + isOpened: false, + enableSorting: false, + tempColumns: cloneDeep(this.props.columns) + }; + + static defaultProps: Pick< + ColumnTogglerProps, + 'placement' | 'tooltipTrigger' | 'tooltipRootClose' | 'draggable' + > = { + placement: 'top', + tooltipTrigger: ['hover', 'focus'], + tooltipRootClose: false, + draggable: false + }; + + target: any; + sortable?: Sortable; + dragRefDOM: HTMLElement; + + constructor(props: ColumnTogglerProps) { + super(props); + + this.open = this.open.bind(this); + this.close = this.close.bind(this); + this.toggle = this.toggle.bind(this); + this.domRef = this.domRef.bind(this); + this.dragRef = this.dragRef.bind(this); + } + + componentDidMount() { + if (this.props.defaultIsOpened) { + this.setState({ + isOpened: true + }); + } + } + + componentWillUnmount() { + this.destroyDragging(); + } + + domRef(ref: any) { + this.target = ref; + } + + toggle(e: React.MouseEvent) { + e.preventDefault(); + + this.setState({ + isOpened: !this.state.isOpened + }); + } + + open() { + this.setState({ + isOpened: true + }); + } + + close() { + this.setState({ + isOpened: false, + enableSorting: false, + tempColumns: cloneDeep(this.props.columns) + }); + } + + swapColumnPosition(oldIndex: number, newIndex: number) { + const columns = this.state.tempColumns; + + columns[oldIndex] = columns.splice(newIndex, 1, columns[oldIndex])[0]; + this.setState({tempColumns: columns}); + } + + updateToggledColumn( + column: IColumn, + index: number, + value: any, + shift?: boolean + ) { + const tempColumns = this.state.tempColumns.concat(); + + tempColumns.splice(index, 1, { + ...column, + toggled: value + }); + this.setState({tempColumns}); + } + + @autobind + dragRef(ref: any) { + const {enableSorting} = this.state; + const {draggable} = this.props; + + if (enableSorting && draggable && ref) { + this.initDragging(); + } + } + + initDragging() { + const dom = findDOMNode(this) as HTMLElement; + const ns = this.props.classPrefix; + + this.sortable = new Sortable( + dom.querySelector(`.${ns}ColumnToggler-modal-content`) as HTMLElement, + { + group: `ColumnToggler-modal-content`, + animation: 150, + handle: `.${ns}ColumnToggler-menuItem-dragBar`, + ghostClass: `${ns}ColumnToggler-menuItem--dragging`, + onEnd: (e: any) => { + if (e.newIndex === e.oldIndex) { + return; + } + + const parent = e.to as HTMLElement; + if (e.oldIndex < parent.childNodes.length - 1) { + parent.insertBefore(e.item, parent.childNodes[e.oldIndex]); + } else { + parent.appendChild(e.item); + } + + this.swapColumnPosition(e.oldIndex, e.newIndex); + } + } + ); + } + + destroyDragging() { + this.sortable && this.sortable.destroy(); + } + + @autobind + onConfirm() { + const {tempColumns} = this.state; + const {onColumnToggle} = this.props; + + onColumnToggle && onColumnToggle([...tempColumns]); + this.setState({ + isOpened: false, + enableSorting: false + }); + } + + renderOuter() { + const { + popOverContainer, + classnames: cx, + classPrefix: ns, + children, + closeOnClick, + closeOnOutside + } = this.props; + const body = ( + + {(ref: any) => { + return ( +
    + {children} +
+ ); + }} +
+ ); + + if (popOverContainer) { + return ( + this.target} show> + + {body} + + + ); + } + + return body; + } + + renderModal() { + const { + render, + classnames: cx, + classPrefix: ns, + modalContainer, + draggable, + overlay, + translate: __ + } = this.props; + + const {enableSorting, tempColumns} = this.state; + + return ( + <> + +
+ 列设置 + + + +
+ +
    + {Array.isArray(tempColumns) + ? tempColumns.map((column, index) => ( + +
  • + {enableSorting && draggable && tempColumns.length > 1 ? ( + <> + + + + + {column.label ? render('tpl', column.label) : null} + + + ) : ( + + + {column.label ? render('tpl', column.label) : null} + + + )} +
  • +
    + )) + : null} +
+ +
+
+ + +
+
+ + +
+
+
+ + ); + } + + render() { + const { + tooltip, + placement, + tooltipContainer, + tooltipTrigger, + tooltipRootClose, + disabledTip, + block, + disabled, + btnDisabled, + btnClassName, + size, + label, + level, + primary, + className, + classnames: cx, + align, + iconOnly, + icon, + isActived, + data, + draggable, + hideExpandIcon + } = this.props; + + const button = ( + + ); + + return ( +
+ {draggable ? ( + button + ) : ( + + {button} + + )} + {this.state.isOpened + ? draggable + ? this.renderModal() + : this.renderOuter() + : null} +
+ ); + } +} diff --git a/src/renderers/Table/TableCell.tsx b/src/renderers/Table/TableCell.tsx index 1433c476a..dba1c1878 100644 --- a/src/renderers/Table/TableCell.tsx +++ b/src/renderers/Table/TableCell.tsx @@ -6,11 +6,13 @@ import PopOverable from '../PopOver'; import {observer} from 'mobx-react'; import omit = require('lodash/omit'); import {filter} from '../../utils/tpl'; +import {Badge} from '../../components/Badge'; export interface TableCellProps extends RendererProps { wrapperComponent?: React.ReactType; column: object; } + export class TableCell extends React.Component { static defaultProps = { wrapperComponent: 'td' @@ -51,9 +53,12 @@ export class TableCell extends React.Component { prefix, affix, isHead, + colIndex, + row, + showBadge, + itemBadge, ...rest } = this.props; - const schema = { ...column, className: innerClassName, @@ -120,6 +125,16 @@ export class TableCell extends React.Component { tabIndex={tabIndex} onKeyUp={onKeyUp} > + {showBadge ? ( + + ) : null} {prefix} {body} {affix} diff --git a/src/renderers/Table/index.tsx b/src/renderers/Table/index.tsx index 2650f6321..2fbb1d2c7 100644 --- a/src/renderers/Table/index.tsx +++ b/src/renderers/Table/index.tsx @@ -5,6 +5,7 @@ import {SchemaNode, Action, Schema} from '../../types'; import forEach from 'lodash/forEach'; import {filter} from '../../utils/tpl'; import DropDownButton from '../DropDownButton'; +import './ColumnToggler'; import Checkbox from '../../components/Checkbox'; import Button from '../../components/Button'; import {TableStore, ITableStore, IColumn, IRow} from '../../store/table'; @@ -17,7 +18,8 @@ import { isArrayChildrenModified, getVariable, removeHTMLTag, - eachTree + eachTree, + isObject } from '../../utils/helper'; import { isPureVariable, @@ -52,6 +54,8 @@ import {TplSchema} from '../Tpl'; import {MappingSchema} from '../Mapping'; import {isAlive, getSnapshot} from 'mobx-state-tree'; import ItemActionsWrapper from './ItemActionsWrapper'; +import ColumnToggler from './ColumnToggler'; +import {BadgeSchema} from '../../components/Badge'; /** * 表格列,不指定类型时默认为文本类型。 @@ -100,7 +104,7 @@ export type TableColumnObject = { /** * 是否可快速搜索 */ - searchable?: boolean; + searchable?: boolean | SchemaObject; /** * 配置是否默认展示 @@ -280,6 +284,16 @@ export interface TableSchema extends BaseSchema { * 行样式表表达式 */ rowClassNameExpr?: string; + + /** + * 行角标 + */ + itemBadge?: BadgeSchema; + + /** + * 开启查询区域,会根据列元素的searchable属性值,自动生成查询条件表单 + */ + autoGenerateFilter?: boolean; } export interface TableProps extends RendererProps { @@ -347,6 +361,7 @@ export interface TableProps extends RendererProps { popOverContainer?: any; canAccessSuperData?: boolean; reUseRow?: boolean; + itemBadge?: BadgeSchema; } type ExportExcelToolbar = SchemaNode & { @@ -406,7 +421,8 @@ export default class Table extends React.Component { 'popOverContainer', 'headerToolbarClassName', 'toolbarClassName', - 'footerToolbarClassName' + 'footerToolbarClassName', + 'itemBadge' ]; static defaultProps: Partial = { className: '', @@ -479,6 +495,8 @@ export default class Table extends React.Component { this.handleMouseMove = this.handleMouseMove.bind(this); this.handleMouseLeave = this.handleMouseLeave.bind(this); this.subFormRef = this.subFormRef.bind(this); + this.handleColumnToggle = this.handleColumnToggle.bind(this); + this.renderAutoFilterForm = this.renderAutoFilterForm.bind(this); const { store, @@ -1348,6 +1366,111 @@ export default class Table extends React.Component { document.removeEventListener('mouseup', this.handleColResizeMouseUp); } + handleColumnToggle(columns: Array) { + const {store} = this.props; + + store.update({columns}); + } + + renderAutoFilterForm(): React.ReactNode { + const { + render, + store, + onSearchableFromReset, + onSearchableFromSubmit, + onSearchableFromInit, + classnames: cx, + translate: __ + } = this.props; + const searchableColumns = store.searchableColumns; + const activedSearchableColumns = store.activedSearchableColumns; + + if (!searchableColumns.length) { + return null; + } + + const groupedSearchableColumns: Array> = [ + {body: [], md: 4}, + {body: [], md: 4}, + {body: [], md: 4} + ]; + + activedSearchableColumns.forEach((column, index) => { + groupedSearchableColumns[index % 3].body.push({ + ...column.searchable, + name: column.searchable?.name ?? column.name, + label: column.searchable?.label ?? column.label, + mode: 'horizontal' + }); + }); + + return render( + 'searchable-form', + { + type: 'form', + api: null, + title: '', + mode: 'normal', + submitText: __('search'), + body: [ + { + type: 'grid', + columns: groupedSearchableColumns + } + ], + actions: [ + { + type: 'dropdown-button', + label: __('Table.searchFields'), + className: cx('Table-searchableForm-dropdown', 'mr-2'), + level: 'link', + trigger: 'click', + size: 'sm', + align: 'right', + buttons: searchableColumns.map(column => { + return { + type: 'checkbox', + className: cx('Table-searchableForm-checkbox'), + name: `__search_${column.searchable?.name ?? column.name}`, + option: column.searchable?.label ?? column.label, + value: column.enableSearch, + badge: { + offset: [-10, 5], + visibleOn: `${ + column.toggable && !column.toggled && column.enableSearch + }` + }, + onChange: (value: boolean) => { + column.setEnableSearch(value); + } + }; + }) + }, + { + type: 'submit', + label: __('search'), + level: 'primary', + className: 'w-18' + }, + { + type: 'reset', + label: __('reset'), + className: 'w-18' + } + ] + }, + { + key: 'searchable-form', + panelClassName: cx('Table-searchableForm'), + actionsClassName: cx('Table-searchableForm-footer'), + onReset: onSearchableFromReset, + onSubmit: onSearchableFromSubmit, + onInit: onSearchableFromInit, + formStore: undefined + } + ); + } + renderHeading() { let { title, @@ -1437,7 +1560,8 @@ export default class Table extends React.Component { render, classPrefix: ns, resizable, - classnames: cx + classnames: cx, + autoGenerateFilter } = this.props; if (column.type === '__checkme') { @@ -1484,7 +1608,7 @@ export default class Table extends React.Component { let affix = null; - if (column.searchable && column.name) { + if (column.searchable && column.name && !autoGenerateFilter) { affix = ( { classnames: cx, checkOnItemClick, popOverContainer, - canAccessSuperData + canAccessSuperData, + itemBadge } = this.props; if (column.name && item.rowSpans[column.name] === 0) { @@ -1725,7 +1850,13 @@ export default class Table extends React.Component { quickEditFormRef: this.subFormRef, prefix, onImageEnlarge: this.handleImageEnlarge, - canAccessSuperData + canAccessSuperData, + row: item, + itemBadge, + showBadge: + !props.isHead && + itemBadge && + store.firstToggledColumnIndex === props.colIndex }; delete subProps.label; @@ -1960,23 +2091,29 @@ export default class Table extends React.Component { } return ( - } + size={config?.size || 'sm'} + label={ + config?.label || + } + draggable={config?.draggable} + columns={store.columnsData} + onColumnToggle={this.handleColumnToggle} > {store.toggableColumns.map(column => (
  • @@ -1985,7 +2122,7 @@ export default class Table extends React.Component {
  • ))} -
    + ); } @@ -2535,7 +2672,13 @@ export default class Table extends React.Component { } render() { - const {className, store, classnames: cx, affixColumns} = this.props; + const { + className, + store, + classnames: cx, + affixColumns, + autoGenerateFilter + } = this.props; this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。 const heading = this.renderHeading(); @@ -2553,6 +2696,7 @@ export default class Table extends React.Component { 'Table--unsaved': !!store.modified || !!store.moved })} > + {autoGenerateFilter ? this.renderAutoFilterForm() : null} {header} {heading}
    { + function getColumnsExceptBuiltinTypes() { + return self.columns.filter(item => !/^__/.test(item.type)); + } + function getForms() { return self.formsRef.map(item => ({ store: getStoreById(item.id) as IFormStore, @@ -499,11 +509,46 @@ export const TableStore = iRendererStore }); } + function getFirstToggledColumnIndex() { + const column = self.columns.find( + column => !/^__/.test(column.type) && column.toggled + ); + + return column == null ? null : column.index; + } + + function getSearchableColumns() { + return self.columns.filter( + column => column.searchable && isObject(column.searchable) + ); + } + + function getActivedSearchableColumns() { + return self.columns.filter( + column => + column.searchable && + isObject(column.searchable) && + column.enableSearch + ); + } + return { + get columnsData() { + return getColumnsExceptBuiltinTypes(); + }, + get forms() { return getForms(); }, + get searchableColumns() { + return getSearchableColumns(); + }, + + get activedSearchableColumns() { + return getSearchableColumns().filter(column => column.enableSearch); + }, + get filteredColumns() { return getFilteredColumns(); }, @@ -598,6 +643,10 @@ export const TableStore = iRendererStore return maxLength === selectedLength; }, + get firstToggledColumnIndex() { + return getFirstToggledColumnIndex(); + }, + getData, get columnGroup() {