横向树列表
运行效果
基于树形动态数据集,使用 antdpro 原生 table 组件实现数据在表格中横向展示
案例需求
将平台中树形结构动态数据集数据,使用表格进行横向展示,支持节点单步加载、全部数据加载及数据级联选择
知识点
- 使用平台树形动态数据集进行数据保存及“父id、全路径id、层级、是否叶子节点”字段平台自动维护
- 使用 antdpro 下原生 table 组件实现树形数据在表中横向展示
开发过程
新增树形动态数据集
在应用中“数据”新增动态数据集,其中需新增字段“父id、全路径id、层级、是否叶子节点”,用于后续设置数据为树形结果数据,并对这些字段进行自动维护
点击数据集“高级”,勾选树形数据,并设置父列为“父id”
点击数据集高级页面上配置按钮,设置“父id、全路径id、层级、是否叶子节点”字段,之后这4个字段不需要人工设置,平台会自动进行维护
点击数据集上“数据”或是 IDE 右上角保存按钮,保存当前数据集设置
至此,树形结构的动态数据集新建完成。点击“数据”新增数据即可。(父id、全路径id、层级、是否叶子节点这4个字段平台会自动维护,不需要人工进行设置)
使用 antdpro 原生 table 进行开发
新增空白模板页面,新增间距组件(页面使用间距组件分隔工具栏及表格),间距组件中新增工具栏组件,以新增相关操作按钮。
新增上面“树形动态数据集”功能菜单数据及其相关数据集
注意:
- 功能菜单数据:用于表格数据展示,取消树形数据设置及设置分页为-1,全部加载
- 用户功能菜单:用于保存选择的菜单数据(用户根据需要自行新建,当前案例中表仅包含用户id及菜单id)
- 树形功能菜单数据:与功能菜单数据为同一个动态数据集,用于绑定树形选择组件,需要设置加载所有数据
切换到页面 js 进行相关开发,先新增表格渲染方法,并将该方法加载页面源码中(在页面中引用 js 方法或是变量,可以使用 $page)
相关代码如下:
{% raw %}
//选择
onChange = (event, column, text, record, index) => {
let cascade = this.comp("pageData").getValue("cascade");
let { treeData } = this.state;
treeData.filter(item => cascade ? (item.fullId.includes(record.fullId)) : (item.fid == record.fid)).forEach(item => item.checked = event.target.checked);
this.setState({ treeData });
}
//展开子节点数据
onExpand = (record) => {
this.comp("pageData").setValue("parentId", record.fid);
this.comp("mainData").refreshData();
}
//编辑菜单数据
onEdit = async (record, isNew = false) => {
let mainData = this.comp("mainData");
if (isNew) {
await mainData.newData();
mainData.setValue("parentId", record.fid);
} else {
mainData.to(record.fid);
}
this.comp("menuModal").show();
}
confirm = (content, ok) => {
Modal.confirm({
title: '提示',
content,
getContainer: () => document.querySelector('#pcx-page-root'),
okText: "确定",
cancelText: "取消",
onOk() {
if (ok) {
ok();
}
}
});
}
//删除功能菜单
onDelMenu = (record) => {
this.confirm((record.isLeaf ? "" : "当前数据下还有子菜单数据,") + "确定删除?", async () => {
this.setState({ loading: true });
try {
let { data: { result, message: msg } } = await this.request({ url: `/react/main/dbrest/menus?fullId=like.${record.fullId}*`, method: "DELETE" });
if (result > 0) {
message.info("删除成功");
//删除当前数据及子数据
let { treeData } = this.state;
treeData = treeData.filter(item => !item.fullId.includes(record.fid));
this.setState({ treeData });
//重新加载父数据
this.onExpand({ fid: record.parentId });
} else {
message.info("删除失败:" + msg);
}
} catch (error) {
message.info("删除失败");
} finally {
this.setState({ loading: false });
}
})
}
//获取表格列
getColumns = () => {
let columns = [];
for (let { key, value, rowSpan } of this.columnsData) {
let column = {
title: value,
dataIndex: key,
ellipsis: true,
render: (text, record, index) => this.onRender(text, record, index, key),
onCell: (record, index) => ({
rowSpan: record[`${key}_rowSpan`] || 0
})
}
columns.push(column)
}
return columns;
}
//多选框渲染
onRender = (text, record, index, column) => {
if (!text) return;
let divBox = [];
divBox.push(<Checkbox checked={record.checked || false} onChange={(e) => { return this.onChange(e, column, text, record, index); }}>{text || '空'}</Checkbox>);
let optDiv = [];
if (!record.isLeaf) {
optDiv.push(<FolderOutlined title="展开" onClick={() => this.onExpand(record)} />);
}
optDiv.push(<PlusOutlined onClick={() => this.onEdit(record, true)} />);
optDiv.push(<DeleteOutlined onClick={() => this.onDelMenu(record)} />);
optDiv.push(<EditOutlined onClick={() => this.onEdit(record, false)} />);
divBox.push(<Space className={"optDiv"} style={{ color: "#1677FF", display: "none" }}>{optDiv}</Space>);
return <Space>{divBox}</Space>;
};
//渲染表格
tableRender = () => {
let columns = this.getColumns();
//渲染字段
return (
<>
<Table
key={new Date().getTime()}
className={"tableBox"}
columns={columns}
dataSource={this.state.treeData}
rowKey="fid"
pagination={false}
bordered={true}
rowHoverable={false}
/>
</>
);
}
{% endraw %}
设置表功能菜单数据数据刷新前后事件,主要用于获取数据及计算 table 组件能够使用的格式数据
相关代码如下:
//菜单数据加载前过滤设置,用于控制数据是全部加载还是parent过滤加载
onMainDataBeforeRefresh = (event) => {
let mainData = event.source;
//设置数据全部加载
if (this.loadAll) {
mainData.setFilter("parentFilter", []);
this.loadAll = false;
return;
}
//设置数据单步加载
let parentId = this.comp("pageData").getValue("parentId");
let filter = [];
if (parentId) {
filter.push({ name: "parentId", value: parentId, op: "eq" });
} else {
filter.push({ name: "parentId", value: null, op: "isNull" });
}
mainData.setFilter("parentFilter", filter);
}
//功能菜单数据刷新后事件
onMainDataAfterRefresh = (event) => {
let rows = event.source.toJson({ ui: true });
//加载为空数据,不任何处理
if (rows.length == 0) return;
this.setState({ loading: true });
//是否重置
if (this.resetData) {
this.columnsData = [];
this.setState({ treeData: [] });
this.comp("pageData").setValue("parentId", null);
this.resetData = false;
}
let parentId = this.comp("pageData").getValue("parentId");
let { treeData = [] } = this.state;
//删除原数据
treeData = treeData.filter(item => item.parentId != parentId);
//动态获取表列名
for (let item of rows) {
let key = `column${item.level}`;
let column = this.columnsData.filter(cData => cData.key == key);
if (!column || column.length == 0) {
this.columnsData.push({ key, value: `菜单${item.level}` });
}
item[key] = item.name;
}
//新增查询数据到表格中
treeData.push(...rows);
//获取用户菜单信息
let userInfo = this.comp("userData").getCurrentRow()?.toJson({ ui: true });
//计算数据合并
for (let i = 0; i < treeData.length; i++) {
let item = treeData[i];
//设置当前菜单是否选择
if (userInfo && userInfo.menuIds.includes(item.fid)) {
item.checked = true;
}
//获取合并数据
let column = treeData.filter(data => data.fullId.includes(item.fullId));
if (column.length > 0) {
item[`column${item.level}` + "_rowSpan"] = column.length;
}
}
//按照全路径id排序,防止数据不在一起导致合并展示异常
treeData.sort((a, b) => a.fullId.localeCompare(b.fullId));
this.setState({ treeData });
this.setState({ loading: false });
}
工具栏中数据加载及保存等方法,代码如下:
//保存用户选择数据
onSaveBtnClick = async (event) => {
this.setState({ loading: true });
try {
//获取选择数据
let menuIds = [];
this.state.treeData.filter(item => item.checked).forEach(item => menuIds.push(item.fid));
let userData = this.comp("userData");
//获取用户已保存的数据
let userInfo = userData.getCurrentRow();
if (!userInfo) {
await userData.newData();
}
userData.setValue("menuIds", menuIds.join());
userData.saveData();
message.info("保存成功")
} catch (error) {
message.error("保存失败");
} finally {
this.setState({ loading: false });
}
}
//刷新
onButton2Click = async (event) => {
this.resetData = true;
this.loadAll = false;
await this.comp("userData").refreshData();
await this.comp("mainData").refreshData();
}
//加载全部数据
onLoadAllBtnClick = async (event) => {
this.resetData = true;
this.loadAll = true;
await this.comp("userData").refreshData();
await this.comp("mainData").refreshData();
}
//保存菜单数据
onMenuModalOk = async (event) => {
this.setState({ loading: true });
try {
let mainData = this.comp("mainData");
//校验数据集必填
mainData.enabledCheck.set(true);//没有接管保存时需要设置
let checkInfo = mainData.check();
if (!checkInfo.valid) {
message.error(checkInfo.msg.join());
return;
}
let currentRow = mainData.getCurrentRow().toJson({ ui: true });
await mainData.saveData();
message.info("保存成功")
//设置当前节点父数据叶子节点为否
let { treeData } = this.state;
treeData.forEach(item => {
if (item.fid == currentRow.parentId) {
item.isLeaf = 0;
}
})
this.setState({ treeData });
//加载子节点数据
this.onExpand({ fid: currentRow.parentId });
this.comp("restData1").refreshData();
} catch (error) {
message.error("保存失败");
} finally {
this.setState({ loading: false });
}
}
上述使用原生代码前需要使用 import 进行引用
import React from 'react';
import { Table, Checkbox, message, Space, Modal } from 'antd';
import {FolderOutlined,PlusOutlined,DeleteOutlined,EditOutlined} from '@ant-design/icons';
案例位置
桌面-页面-列表表单组件-横向树列表.w
使用html的input(file)
使用html的input做文件选择具体实现如下:
js定义渲染方法返回input
inputRender = () => {
return<inputid="input0"type="file"/>
}
需要注意js中还需要添加引用
import React from 'react';
在页面源码中需要显示input的地方通过 {$page.inputRender()}
引入,即可使用html的input做文件选择
运行效果如下: