横向树列表

运行效果

基于树形动态数据集,使用 antdpro 原生 table 组件实现数据在表格中横向展示

图 0

案例需求

将平台中树形结构动态数据集数据,使用表格进行横向展示,支持节点单步加载、全部数据加载及数据级联选择

知识点

  1. 使用平台树形动态数据集进行数据保存及“父id、全路径id、层级、是否叶子节点”字段平台自动维护
  2. 使用 antdpro 下原生 table 组件实现树形数据在表中横向展示

开发过程

新增树形动态数据集

在应用中“数据”新增动态数据集,其中需新增字段“父id、全路径id、层级、是否叶子节点”,用于后续设置数据为树形结果数据,并对这些字段进行自动维护

图 1

点击数据集“高级”,勾选树形数据,并设置父列为“父id”

图 2

点击数据集高级页面上配置按钮,设置“父id、全路径id、层级、是否叶子节点”字段,之后这4个字段不需要人工设置,平台会自动进行维护

图 3

点击数据集上“数据”或是 IDE 右上角保存按钮,保存当前数据集设置

图 4

至此,树形结构的动态数据集新建完成。点击“数据”新增数据即可。(父id、全路径id、层级、是否叶子节点这4个字段平台会自动维护,不需要人工进行设置)

图 5

使用 antdpro 原生 table 进行开发

新增空白模板页面,新增间距组件(页面使用间距组件分隔工具栏及表格),间距组件中新增工具栏组件,以新增相关操作按钮。

图 6

新增上面“树形动态数据集”功能菜单数据及其相关数据集

注意:

  1. 功能菜单数据:用于表格数据展示,取消树形数据设置及设置分页为-1,全部加载
  2. 用户功能菜单:用于保存选择的菜单数据(用户根据需要自行新建,当前案例中表仅包含用户id及菜单id)
  3. 树形功能菜单数据:与功能菜单数据为同一个动态数据集,用于绑定树形选择组件,需要设置加载所有数据

图 7

图 8

图 9

切换到页面 js 进行相关开发,先新增表格渲染方法,并将该方法加载页面源码中(在页面中引用 js 方法或是变量,可以使用 $page

图 10

图 11

相关代码如下:

{% 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 组件能够使用的格式数据

图 12

相关代码如下:

//菜单数据加载前过滤设置,用于控制数据是全部加载还是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做文件选择

1727687192039

运行效果如下:

1727687216813

results matching ""

    No results matching ""