mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
parent
b0f12d7557
commit
cd5cd3f40c
@ -200,11 +200,11 @@ CRUD 组件对数据源接口的数据结构要求如下:
|
||||
| orderDir | 'asc'/'desc' | 排序方式 |
|
||||
| keywords | string | 搜索关键字 |
|
||||
|
||||
### 解析Query原始类型
|
||||
### 解析 Query 原始类型
|
||||
|
||||
> `3.5.0`及以上版本
|
||||
|
||||
`syncLocation`开启后,CRUD在初始化数据域时,将会对url中的Query进行转换,将原始类型的字符串格式的转化为同位类型,目前仅支持**布尔类型**
|
||||
`syncLocation`开启后,CRUD 在初始化数据域时,将会对 url 中的 Query 进行转换,将原始类型的字符串格式的转化为同位类型,目前仅支持**布尔类型**
|
||||
|
||||
```
|
||||
"true" ==> true
|
||||
@ -213,7 +213,6 @@ CRUD 组件对数据源接口的数据结构要求如下:
|
||||
|
||||
如果只想保持字符串格式,可以设置`"parsePrimitiveQuery": false`关闭该特性,具体效果参考[示例](../../../examples/crud/parse-primitive-query)。
|
||||
|
||||
|
||||
## 功能
|
||||
|
||||
既然这个渲染器叫增删改查,那接下来分开介绍这几个功能吧。
|
||||
@ -584,6 +583,61 @@ Cards 模式支持 [Cards](./cards) 中的所有功能。
|
||||
}
|
||||
```
|
||||
|
||||
## 嵌套
|
||||
|
||||
当行数据中存在 `children` 字段时,CRUD 会自动识别为树形数据,并支持展开收起。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "crud",
|
||||
"name": "crud",
|
||||
"syncLocation": false,
|
||||
"api": "/api/mock2/crud/table2",
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 嵌套懒加载
|
||||
|
||||
如果数据量比较大不适合一次性加载,可以配置 `deferApi` 接口,结合行数据中标记 `defer: true` 属性,实现懒加载。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "crud",
|
||||
"name": "crud",
|
||||
"syncLocation": false,
|
||||
"api": "/api/mock2/crud/table6",
|
||||
"deferApi": "/api/mock2/crud/table6?parentId=${id}",
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 查询条件表单
|
||||
|
||||
大部分表格展示有对数据进行检索的需求,CRUD 自身支持通过配置`filter`,实现查询条件过滤表单。`filter` 配置实际上同 [Form](./form/index) 组件,因此支持绝大部分`form`的功能。
|
||||
@ -3173,6 +3227,7 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据,
|
||||
| title | `string` | `""` | 可设置成空,当设置成空时,没有标题栏 |
|
||||
| className | `string` | | 表格外层 Dom 的类名 |
|
||||
| api | [API](../../docs/types/api) | | CRUD 用来获取列表数据的 api。 |
|
||||
| deferApi | [API](../../docs/types/api) | | 当行数据中有 defer 属性时,用此接口进一步加载内容 |
|
||||
| loadDataOnce | `boolean` | | 是否一次性加载所有数据(前端分页) |
|
||||
| loadDataOnceFetchOnFilter | `boolean` | `true` | 在开启 loadDataOnce 时,filter 时是否去重新请求 api |
|
||||
| source | `string` | | 数据映射接口返回某字段的值,不设置会默认使用接口返回的`${items}`或者`${rows}`,也可以设置成上层数据源的内容 |
|
||||
|
@ -1825,6 +1825,7 @@ popOver 的其它配置请参考 [popover](./popover)
|
||||
| type | `string` | | `"type"` 指定为 table 渲染器 | |
|
||||
| title | `string` | | 标题 | |
|
||||
| source | `string` | `${items}` | 数据源, 绑定当前环境变量 | |
|
||||
| deferApi | [API](../../docs/types/api) | | 当行数据中有 defer 属性时,用此接口进一步加载内容 |
|
||||
| affixHeader | `boolean` | `true` | 是否固定表头 | |
|
||||
| columnsTogglable | `auto` 或者 `boolean` | `auto` | 展示列显示开关, 自动即:列数量大于或等于 5 个时自动开启 | |
|
||||
| placeholder | `string` 或者 `SchemaTpl` | `暂无数据` | 当没数据的时候的文字提示 | |
|
||||
|
@ -2,11 +2,9 @@ export default {
|
||||
title: '支持多层嵌套,列数据中有 children 字段即可。(建议不超过10层)',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/crud/table2',
|
||||
api: '/api/mock2/crud/table6',
|
||||
deferApi: '/api/mock2/crud/table6?parentId=${id}',
|
||||
saveOrderApi: '/api/mock2/form/saveData',
|
||||
expandConfig: {
|
||||
expand: 'all'
|
||||
},
|
||||
draggable: true,
|
||||
columns: [
|
||||
{
|
||||
|
527
mock/cfc/mock/crud/table6.js
Normal file
527
mock/cfc/mock/crud/table6.js
Normal file
@ -0,0 +1,527 @@
|
||||
function findInTree(tree, id) {
|
||||
let ret = null;
|
||||
tree.some(function (item) {
|
||||
if (item.id == id) {
|
||||
ret = item;
|
||||
return true;
|
||||
}
|
||||
if (item.children) {
|
||||
ret = findInTree(item.children, id);
|
||||
return !!ret;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
module.exports = function (req, res) {
|
||||
const perPage = 10;
|
||||
const page = req.query.page || 1;
|
||||
let items = data.concat();
|
||||
if (req.query.parentId) {
|
||||
const item = findInTree(items, req.query.parentId);
|
||||
|
||||
if (!item) {
|
||||
res.json({
|
||||
status: 404,
|
||||
msg: 'Not Found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
...item,
|
||||
children: Array.isArray(item.children)
|
||||
? item.children.map(item => ({
|
||||
...item,
|
||||
children: undefined,
|
||||
defer: !!(Array.isArray(item.children) && item.children.length)
|
||||
}))
|
||||
: []
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ret = {
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
count: items.length,
|
||||
rows: items
|
||||
.concat()
|
||||
.splice((page - 1) * perPage, perPage)
|
||||
.map(item => ({
|
||||
...item,
|
||||
children: undefined,
|
||||
defer: !!(Array.isArray(item.children) && item.children.length)
|
||||
}))
|
||||
}
|
||||
};
|
||||
res.json(ret);
|
||||
};
|
||||
|
||||
module.exports.cache = true;
|
||||
const data = [
|
||||
{
|
||||
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,
|
||||
children:
|
||||
Math.random() > 0.5
|
||||
? undefined
|
||||
: [
|
||||
{
|
||||
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: 'Other browsers',
|
||||
browser: 'All others',
|
||||
platform: '-',
|
||||
version: '-',
|
||||
grade: 'U'
|
||||
}
|
||||
].map(function (child, i) {
|
||||
return Object.assign({}, child, {
|
||||
id: (index + 1) * 100 + i + 1,
|
||||
children: [
|
||||
{
|
||||
engine: 'Trident',
|
||||
browser: 'Internet Explorer 4.0',
|
||||
platform: 'Win 95+',
|
||||
version: '4',
|
||||
grade: 'X'
|
||||
},
|
||||
{
|
||||
engine: 'Misc',
|
||||
browser: 'Internet Explorer 5.0',
|
||||
platform: 'Win 95+',
|
||||
version: '5',
|
||||
grade: 'C'
|
||||
},
|
||||
{
|
||||
engine: 'Misc',
|
||||
browser: 'Internet Explorer 5.0',
|
||||
platform: 'Win 95+',
|
||||
version: '1',
|
||||
grade: 'A'
|
||||
}
|
||||
].map(function (child, i) {
|
||||
return Object.assign({}, child, {
|
||||
id: (i + 1) * 100 + (index + 1) * 1000 + i + 1
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
@ -27,15 +27,20 @@ module.exports = function (req, res) {
|
||||
return require(file)(req, res);
|
||||
} else if (exist(jsFile)) {
|
||||
let file = require.resolve(path.join(DIRNAME, jsFile));
|
||||
delete require.cache[file];
|
||||
let mod = require(file);
|
||||
|
||||
if (!mod.cache) {
|
||||
delete require.cache[file];
|
||||
mod = require(file);
|
||||
}
|
||||
|
||||
if (req.query.waitSeconds) {
|
||||
return setTimeout(function () {
|
||||
require(file)(req, res);
|
||||
mod(req, res);
|
||||
}, parseInt(req.query.waitSeconds, 10) * 1000);
|
||||
}
|
||||
|
||||
return require(file)(req, res);
|
||||
return mod(req, res);
|
||||
}
|
||||
if (exist(jsonFile)) {
|
||||
if (req.query.waitSeconds) {
|
||||
|
@ -39,6 +39,45 @@ import {getStoreById} from './manager';
|
||||
*/
|
||||
const PARTITION_INDEX = 3;
|
||||
|
||||
function initChildren(
|
||||
children: Array<any>,
|
||||
depth: number,
|
||||
pindex: number,
|
||||
parentId: string,
|
||||
path: string = ''
|
||||
): any {
|
||||
depth += 1;
|
||||
return children.map((item, index) => {
|
||||
item = isObject(item)
|
||||
? item
|
||||
: {
|
||||
item
|
||||
};
|
||||
const id = item.__id ?? guid();
|
||||
|
||||
return {
|
||||
// id: String(item && (item as any)[self.primaryField] || `${pindex}-${depth}-${key}`),
|
||||
id: String(id),
|
||||
parentId: String(parentId),
|
||||
key: String(`${pindex}-${depth}-${index}`),
|
||||
path: `${path}${index}`,
|
||||
depth: depth,
|
||||
index: index,
|
||||
newIndex: index,
|
||||
pristine: item,
|
||||
data: item,
|
||||
defer: !!item.defer,
|
||||
loaded: false,
|
||||
loading: false,
|
||||
rowSpans: {},
|
||||
children:
|
||||
item && Array.isArray(item.children)
|
||||
? initChildren(item.children, depth, index, id, `${path}${index}.`)
|
||||
: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const Column = types
|
||||
.model('Column', {
|
||||
label: types.optional(types.frozen(), undefined),
|
||||
@ -127,20 +166,33 @@ export const Row = types
|
||||
rowSpans: types.frozen({} as any),
|
||||
index: types.number,
|
||||
newIndex: types.number,
|
||||
nth: 0,
|
||||
path: '', // 行数据的位置
|
||||
expandable: false,
|
||||
checkdisable: false,
|
||||
isHover: false,
|
||||
children: types.optional(
|
||||
types.array(types.late((): IAnyModelType => Row)),
|
||||
[]
|
||||
),
|
||||
defer: false, // 是否为懒数据
|
||||
loaded: false, // 懒数据是否加载完了
|
||||
loading: false, // 懒数据是否正在加载
|
||||
error: '', // 懒数据加载失败的错误信息
|
||||
depth: types.number, // 当前children位于第几层,便于使用getParent获取最顶层TableStore
|
||||
appeared: true,
|
||||
lazyRender: false
|
||||
})
|
||||
.views(self => ({
|
||||
get expandable(): boolean {
|
||||
let table: any;
|
||||
return !!(
|
||||
(self && self.children.length) ||
|
||||
(self && self.defer && !self.loaded) ||
|
||||
((table = getParent(self, self.depth * 2) as any) &&
|
||||
table.footable &&
|
||||
table.footableColumns.length)
|
||||
);
|
||||
},
|
||||
|
||||
get checked(): boolean {
|
||||
return (getParent(self, self.depth * 2) as ITableStore).isSelected(
|
||||
self as IRow
|
||||
@ -320,36 +372,70 @@ export const Row = types
|
||||
});
|
||||
|
||||
if (Array.isArray(data.children)) {
|
||||
const arr = data.children;
|
||||
const pool = arr.concat();
|
||||
this.replaceChildren(data.children);
|
||||
}
|
||||
},
|
||||
|
||||
// 把多的删了先
|
||||
if (self.children.length > arr.length) {
|
||||
self.children.splice(arr.length, self.children.length - arr.length);
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
const len = self.children.length;
|
||||
while (pool.length) {
|
||||
// 因为父级id未更新,所以需要将子级的parentId正确指向父级id
|
||||
const item = {
|
||||
...pool.shift(),
|
||||
parentId: self.id
|
||||
}!;
|
||||
|
||||
if (index < len) {
|
||||
self.children[index].replaceWith(item);
|
||||
} else {
|
||||
const row = Row.create(item);
|
||||
self.children.push(row);
|
||||
}
|
||||
index++;
|
||||
replaceChildren(children: Array<any>) {
|
||||
const arr = children;
|
||||
const pool = arr.concat();
|
||||
|
||||
// 把多的删了先
|
||||
if (self.children.length > arr.length) {
|
||||
self.children.splice(arr.length, self.children.length - arr.length);
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
const len = self.children.length;
|
||||
while (pool.length) {
|
||||
// 因为父级id未更新,所以需要将子级的parentId正确指向父级id
|
||||
const item = {
|
||||
...pool.shift(),
|
||||
parentId: self.id
|
||||
}!;
|
||||
|
||||
if (index < len) {
|
||||
self.children[index].replaceWith(item);
|
||||
} else {
|
||||
const row = Row.create(item);
|
||||
self.children.push(row);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
},
|
||||
|
||||
markAppeared(value: any) {
|
||||
value && (self.appeared = !!value);
|
||||
},
|
||||
|
||||
markLoading(value: any) {
|
||||
self.loading = !!value;
|
||||
},
|
||||
|
||||
markLoaded(value: any) {
|
||||
self.loaded = !!value;
|
||||
},
|
||||
|
||||
setError(value: any) {
|
||||
self.error = String(value);
|
||||
},
|
||||
|
||||
resetDefered() {
|
||||
self.error = '';
|
||||
self.loaded = false;
|
||||
},
|
||||
|
||||
setDeferData({children, ...rest}: any) {
|
||||
self.data = {
|
||||
...self.data,
|
||||
...rest
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
this.replaceChildren(
|
||||
initChildren(children, self.depth, self.index, self.id, self.path)
|
||||
);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@ -1238,55 +1324,6 @@ export const TableStore = iRendererStore
|
||||
return combineCell(arr, keys);
|
||||
}
|
||||
|
||||
function initChildren(
|
||||
children: Array<any>,
|
||||
depth: number,
|
||||
pindex: number,
|
||||
parentId: string,
|
||||
path: string = '',
|
||||
nThRef: {index: number}
|
||||
): any {
|
||||
depth += 1;
|
||||
return children.map((item, index) => {
|
||||
item = isObject(item)
|
||||
? item
|
||||
: {
|
||||
item
|
||||
};
|
||||
const id = item.__id ?? guid();
|
||||
|
||||
return {
|
||||
// id: String(item && (item as any)[self.primaryField] || `${pindex}-${depth}-${key}`),
|
||||
id: String(id),
|
||||
parentId: String(parentId),
|
||||
key: String(`${pindex}-${depth}-${index}`),
|
||||
path: `${path}${index}`,
|
||||
depth: depth,
|
||||
index: index,
|
||||
nth: nThRef.index++,
|
||||
newIndex: index,
|
||||
pristine: item,
|
||||
data: item,
|
||||
rowSpans: {},
|
||||
children:
|
||||
item && Array.isArray(item.children)
|
||||
? initChildren(
|
||||
item.children,
|
||||
depth,
|
||||
index,
|
||||
id,
|
||||
`${path}${index}.`,
|
||||
nThRef
|
||||
)
|
||||
: [],
|
||||
expandable: !!(
|
||||
(item && Array.isArray(item.children) && item.children.length) ||
|
||||
(self.footable && self.footableColumns.length)
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function initRows(
|
||||
rows: Array<any>,
|
||||
getEntryId?: (entry: any, index: number) => string,
|
||||
@ -1298,7 +1335,6 @@ export const TableStore = iRendererStore
|
||||
/* 避免输入内容为非数组挂掉 */
|
||||
rows = !Array.isArray(rows) ? [] : rows;
|
||||
|
||||
const nThRef = {index: 0};
|
||||
let arr: Array<SRow> = rows.map((item, index) => {
|
||||
if (!isObject(item)) {
|
||||
item = {
|
||||
@ -1315,20 +1351,18 @@ export const TableStore = iRendererStore
|
||||
key: String(`${index}-1-${index}`),
|
||||
depth: 1, // 最大父节点默认为第一层,逐层叠加
|
||||
index: index,
|
||||
nth: nThRef.index++,
|
||||
newIndex: index,
|
||||
pristine: item,
|
||||
path: `${index}`,
|
||||
data: item,
|
||||
rowSpans: {},
|
||||
defer: !!item.defer,
|
||||
loaded: false,
|
||||
loading: false,
|
||||
children:
|
||||
item && Array.isArray(item.children)
|
||||
? initChildren(item.children, 1, index, id, `${index}.`, nThRef)
|
||||
: [],
|
||||
expandable: !!(
|
||||
(item && Array.isArray(item.children) && item.children.length) ||
|
||||
(self.footable && self.footableColumns.length)
|
||||
)
|
||||
? initChildren(item.children, 1, index, id, `${index}.`)
|
||||
: []
|
||||
};
|
||||
});
|
||||
|
||||
@ -1342,7 +1376,9 @@ export const TableStore = iRendererStore
|
||||
}
|
||||
|
||||
replaceRow(arr, reUseRow);
|
||||
self.isNested = self.rows.some(item => item.children.length);
|
||||
self.isNested = self.rows.some(
|
||||
item => item.children.length || (item.defer && !item.loaded)
|
||||
);
|
||||
|
||||
// 前 20 个直接渲染,后面的按需渲染
|
||||
if (
|
||||
|
@ -620,6 +620,10 @@
|
||||
> thead > tr > th.#{$ns}Table-primayCell,
|
||||
> tbody > tr > td.#{$ns}Table-primayCell {
|
||||
white-space: nowrap; // 树形表格展示标题栏,不要换行
|
||||
|
||||
> .#{$ns}Spinner {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -864,6 +868,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-retryBtn {
|
||||
color: var(--Form-feedBack-color);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--Form-feedBack-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-expandBtn,
|
||||
&-expandBtn2 {
|
||||
position: relative;
|
||||
|
@ -197,6 +197,8 @@ register('de-DE', {
|
||||
'Options.editLabel': 'Bearbeiten {{label}}',
|
||||
'Options.label': 'Option',
|
||||
'Options.createFailed': 'Erstellen fehlgeschlagen',
|
||||
'Options.retry':
|
||||
"Laden fehlgeschlagen '{{reason}}', klicken Sie auf Wiederholen",
|
||||
'placeholder.empty': '<Empty>',
|
||||
'placeholder.enter': 'Eingabe',
|
||||
'placeholder.noData': 'Keine Daten',
|
||||
|
@ -189,6 +189,7 @@ register('en-US', {
|
||||
'Options.editLabel': 'Edit {{label}}',
|
||||
'Options.label': 'option',
|
||||
'Options.createFailed': 'create failed, please check',
|
||||
'Options.retry': "Loading failed '{{reason}}', click retry",
|
||||
'placeholder.empty': '<Empty>',
|
||||
'placeholder.enter': 'Enter',
|
||||
'placeholder.noData': 'No data',
|
||||
|
@ -194,6 +194,7 @@ register('zh-CN', {
|
||||
'Options.editLabel': '编辑{{label}}',
|
||||
'Options.label': '选项',
|
||||
'Options.createFailed': '新增失败,请仔细检查',
|
||||
'Options.retry': '加载失败「{{reason}}」,点击重试',
|
||||
'placeholder.empty': '<空>',
|
||||
'placeholder.enter': '请输入',
|
||||
'placeholder.noData': '暂无数据',
|
||||
|
@ -132,6 +132,11 @@ export interface CRUDCommonSchema extends BaseSchema, SpinnerExtraProps {
|
||||
*/
|
||||
api?: SchemaApi;
|
||||
|
||||
/**
|
||||
* 懒加载 API,当行数据中用 defer: true 标记了,则其孩子节点将会用这个 API 来拉取数据。
|
||||
*/
|
||||
deferApi?: SchemaApi;
|
||||
|
||||
/**
|
||||
* 批量操作
|
||||
*/
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
buildTrackExpression,
|
||||
evalTrackExpression
|
||||
} from 'amis-core';
|
||||
import {BadgeObject, Checkbox, Icon} from 'amis-ui';
|
||||
import {BadgeObject, Checkbox, Icon, Spinner} from 'amis-ui';
|
||||
import React from 'react';
|
||||
|
||||
export interface CellProps extends ThemeProps {
|
||||
@ -32,6 +32,7 @@ export interface CellProps extends ThemeProps {
|
||||
popOverContainer?: any;
|
||||
quickEditFormRef: any;
|
||||
onImageEnlarge?: any;
|
||||
translate: (key: string, ...args: Array<any>) => string;
|
||||
}
|
||||
|
||||
export default function Cell({
|
||||
@ -51,7 +52,8 @@ export default function Cell({
|
||||
onDragStart,
|
||||
popOverContainer,
|
||||
quickEditFormRef,
|
||||
onImageEnlarge
|
||||
onImageEnlarge,
|
||||
translate: __
|
||||
}: CellProps) {
|
||||
if (column.name && item.rowSpans[column.name] === 0) {
|
||||
return null;
|
||||
@ -134,7 +136,18 @@ export default function Cell({
|
||||
/>
|
||||
);
|
||||
prefix.push(
|
||||
item.expandable ? (
|
||||
item.loading ? (
|
||||
<Spinner key="loading" size="sm" show />
|
||||
) : item.error ? (
|
||||
<a
|
||||
className={cx('Table-retryBtn')}
|
||||
key="retryBtn"
|
||||
onClick={item.resetDefered}
|
||||
data-tooltip={__('Options.retry', {reason: item.error})}
|
||||
>
|
||||
<Icon icon="retry" className="icon" />
|
||||
</a>
|
||||
) : item.expandable ? (
|
||||
<a
|
||||
key="expandBtn2"
|
||||
className={cx('Table-expandBtn2', item.expanded ? 'is-active' : '')}
|
||||
@ -169,7 +182,13 @@ export default function Cell({
|
||||
);
|
||||
}
|
||||
return [prefix, affix, addtionalClassName];
|
||||
}, [item.expandable, item.expanded, column.isPrimary]);
|
||||
}, [
|
||||
item.expandable,
|
||||
item.expanded,
|
||||
item.error,
|
||||
item.loading,
|
||||
column.isPrimary
|
||||
]);
|
||||
|
||||
// 根据条件缓存 data,避免孩子重复渲染
|
||||
const hasCustomTrackExpression =
|
||||
|
@ -60,6 +60,8 @@ export class TableRow extends React.PureComponent<
|
||||
depth: number;
|
||||
expandable: boolean;
|
||||
appeard?: boolean;
|
||||
loading?: boolean;
|
||||
error?: string;
|
||||
checkdisable: boolean;
|
||||
trRef?: React.Ref<any>;
|
||||
isNested?: boolean;
|
||||
@ -362,6 +364,8 @@ export default observer((props: TableRowProps) => {
|
||||
depth={item.depth}
|
||||
expandable={item.expandable}
|
||||
checkdisable={item.checkdisable}
|
||||
loading={item.loading}
|
||||
error={item.error}
|
||||
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
|
||||
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
|
||||
data={canAccessSuperData ? item.locals : item.data}
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
SchemaExpression,
|
||||
position,
|
||||
animation,
|
||||
evalExpressionWithConditionBuilder
|
||||
evalExpressionWithConditionBuilder,
|
||||
isEffectiveApi
|
||||
} from 'amis-core';
|
||||
import {Renderer, RendererProps} from 'amis-core';
|
||||
import {SchemaNode, ActionObject, Schema} from 'amis-core';
|
||||
@ -71,6 +72,7 @@ import ColGroup from './ColGroup';
|
||||
import debounce from 'lodash/debounce';
|
||||
import AutoFilterForm from './AutoFilterForm';
|
||||
import Cell from './Cell';
|
||||
import {reaction} from 'mobx';
|
||||
|
||||
/**
|
||||
* 表格列,不指定类型时默认为文本类型。
|
||||
@ -343,6 +345,11 @@ export interface TableSchema extends BaseSchema {
|
||||
* table layout
|
||||
*/
|
||||
tableLayout?: 'fixed' | 'auto';
|
||||
|
||||
/**
|
||||
* 懒加载 API,当行数据中用 defer: true 标记了,则其孩子节点将会用这个 API 来拉取数据。
|
||||
*/
|
||||
deferApi?: SchemaApi;
|
||||
}
|
||||
|
||||
export interface TableProps extends RendererProps, SpinnerExtraProps {
|
||||
@ -640,6 +647,18 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
|
||||
formItem && isAlive(formItem) && formItem.setSubStore(store);
|
||||
Table.syncRows(store, this.props, undefined) && this.syncSelected();
|
||||
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() =>
|
||||
store
|
||||
.getExpandedRows()
|
||||
.filter(
|
||||
row => row.defer && !row.loaded && !row.loading && !row.error
|
||||
),
|
||||
(rows: Array<IRow>) => rows.forEach(this.loadDeferredRow)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static syncRows(
|
||||
@ -711,6 +730,34 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
async loadDeferredRow(row: IRow) {
|
||||
const {env} = this.props;
|
||||
const deferApi = row.data.deferApi || this.props.deferApi;
|
||||
|
||||
if (!isEffectiveApi(deferApi)) {
|
||||
throw new Error('deferApi is required');
|
||||
}
|
||||
|
||||
try {
|
||||
row.markLoading(true);
|
||||
|
||||
const response = await env.fetcher(deferApi, row.locals);
|
||||
if (!response.ok) {
|
||||
throw new Error(response.msg);
|
||||
}
|
||||
|
||||
row.setDeferData(response.data);
|
||||
row.markLoaded(true);
|
||||
row.setError('');
|
||||
} catch (e) {
|
||||
row.setError(e.message);
|
||||
env.notify('error', e.message);
|
||||
} finally {
|
||||
row.markLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动设置表格高度占满界面剩余区域
|
||||
* 用 css 实现有点麻烦,要改很多结构,所以先用 dom hack 了,避免对之前的功能有影响
|
||||
@ -2024,7 +2071,8 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
checkOnItemClick,
|
||||
popOverContainer,
|
||||
canAccessSuperData,
|
||||
itemBadge
|
||||
itemBadge,
|
||||
translate
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -2047,6 +2095,7 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
popOverContainer={this.getPopOverContainer}
|
||||
quickEditFormRef={this.subFormRef}
|
||||
onImageEnlarge={this.handleImageEnlarge}
|
||||
translate={translate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user