首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 表格形式展示無限級分類:TreeTable 類實現行列合併

     編輯:PHP 綜合知識

需求背景

傳統遞歸輸出無限級分類多為縮進列表或下拉選單,難以直觀展示層級歸屬關系。TreeTable 通過 HTML 表格的 rowspan 和 colspan 將樹狀結構渲染為類似文件夾樹的表格,清晰展示父子層級。

數據結構要求

輸入數組必須包含 id、parentid、name 三字段:

$data = [
    ['id'=>1, 'parentid'=>0, 'name'=>'一級欄目一'],
    ['id'=>2, 'parentid'=>0, 'name'=>'一級欄目二'],
    ['id'=>3, 'parentid'=>1, 'name'=>'二級欄目一'],
    ['id'=>4, 'parentid'=>1, 'name'=>'二級欄目二'],
    ['id'=>5, 'parentid'=>2, 'name'=>'二級欄目三'],
    ['id'=>6, 'parentid'=>3, 'name'=>'三級欄目一'],
    ['id'=>7, 'parentid'=>3, 'name'=>'三級欄目二'],
];

TreeTable 核心類(簡化版關鍵邏輯)

<?php
class TreeTable {
    public $arr = [];      // 處理後的節點數組
    public $columns = 0;   // 最大層級數(列數)
    public $rows = 0;      // 總行數(葉子節點數)

    public function init(array $data) {
        // 1. 建立 ID 索引
        foreach ($data as $v) $this->arr[$v['id']] = $v;

        // 2. 計算每個節點屬性
        foreach ($this->arr as $k => $v) {
            $this->arr[$k]['column'] = $this->getLevel($v['id']);           // 所在列(層級)
            $this->arr[$k]['arrchildid'] = $this->getArrChildId($v['id']);  // 所有子孫 ID
            $this->arr[$k]['arrparentid'] = $this->getArrParentId($v['id']); // 所有祖先 ID
            $this->arr[$k]['child_bottom_num'] = $this->getChildCount($v['id']); // 葉子節點數
        }

        $this->columns = $this->getColumns();
        $this->rows = $this->getRows();
        $this->sortArr(); // 按祖先路徑排序
        $this->calcRowColSpan(); // 計算 rowspan/colspan
        return $this->arr;
    }

    // 遞歸獲取層級
    private function getLevel($id, $init = true) {
        static $level = 1;
        if ($init) $level = 1;
        if ($this->arr[$id]['parentid']) {
            $level++;
            $this->getLevel($this->arr[$id]['parentid'], false);
        }
        return $level;
    }

    // 遞歸獲取所有葉子節點數(用於 rowspan)
    private function getChildCount($id, $init = true) {
        static $count = 0;
        if ($init) $count = 0;
        $children = $this->getChild($id);
        if ($children) {
            foreach ($children as $c) $this->getChildCount($c['id'], false);
        } else {
            $count++;
        }
        return $count;
    }

    // 獲取直接子節點
    private function getChild($id) {
        $res = [];
        foreach ($this->arr as $v) if ($v['parentid'] == $id) $res[] = $v;
        return $res;
    }

    // 獲取所有子孫 ID 字符串
    private function getArrChildId($id, $init = true) {
        static $ids = '';
        if ($init) $ids = '';
        foreach ($this->arr as $v) {
            if ($v['parentid'] == $id) {
                $ids .= ($ids ? ',' : '') . $v['id'];
                $this->getArrChildId($v['id'], false);
            }
        }
        return $ids;
    }

    // 獲取所有祖先 ID 字符串
    private function getArrParentId($id, $str = '') {
        $pid = $this->arr[$id]['parentid'];
        if ($pid > 0) {
            $str = $str ? $pid . ',' . $str : (string)$pid;
            $str = $this->getArrParentId($pid, $str);
        }
        return $str;
    }

    // 排序:按祖先路徑字符串、ID 排序,保證樹狀順序
    private function sortArr() {
        $pidArr = $idArr = [];
        foreach ($this->arr as $k => $v) {
            $pidArr[$k] = $v['arrparentid'];
            $idArr[$k] = $v['id'];
        }
        array_multisort($pidArr, SORT_ASC, SORT_STRING, $idArr, SORT_ASC, SORT_NUMERIC, $this->arr);
        // 重新索引
        $new = [];
        foreach ($this->arr as $v) $new[$v['id']] = $v;
        $this->arr = $new;
    }

    // 計算行位置、rowspan、colspan
    private function calcRowColSpan() {
        foreach ($this->arr as $k => $v) {
            $this->arr[$k]['row'] = $this->getRowLocation($v['id']);
            $this->arr[$k]['rowspan'] = $v['child_bottom_num'];
            $this->arr[$k]['colspan'] = $v['child_bottom_num'] == 0 ? $this->columns - $v['column'] + 1 : 0;
        }
    }

    // 計算行位置(核心算法)
    private function getRowLocation($id) {
        $node = $this->arr[$id];
        $count = 0;
        // 1. 所有祖先同層中,層級序號小於當前祖先且為葉子的節點
        $ancestors = explode(',', $node['arrparentid']);
        foreach ($ancestors as $pid) {
            foreach ($this->arr as $n) {
                if ($n['column'] == $this->arr[$pid]['column'] &&
                    $this->arr[$pid]['column_level'] > $n['column_level'] &&
                    $n['child_bottom_num'] == 0) $count++;
            }
        }
        // 2. 當前層級中,序號小於當前節點的葉子節點數
        foreach ($this->arr as $n) {
            if ($n['column'] == $node['column'] &&
                $n['column_level'] < $node['column_level']) {
                $count += $n['child_bottom_num'] ?: 1;
            }
        }
        return $count + 1;
    }

    // 生成表格 HTML(不含 <table> 標簽)
    public function getTreeTable() {
        $html = '';
        for ($r = 1; $r <= $this->rows; $r++) {
            $html .= "\n<tr>";
            foreach ($this->arr as $v) {
                if ($v['row'] == $r) {
                    $rs = $v['rowspan'] ? " rowspan='{$v['rowspan']}'" : '';
                    $cs = $v['colspan'] ? " colspan='{$v['colspan']}'" : '';
                    $html .= "\n<td$rs$cs>{$v['name']}</td>";
                }
            }
            $html .= "\n</tr>";
        }
        return $html;
    }
}
?>

使用示例

<?php
require 'TreeTable.class.php';
$data = [ /* 上述數據 */ ];
$tree = new TreeTable();
$tree->init($data);
?>
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;">
    <?php echo $tree->getTreeTable(); ?>
</table>

渲染效果說明

  • 一級欄目佔據第一列,rowspan 跨越其所有葉子後代行。
  • 二級欄目佔據第二列,以此類推。
  • 葉子節點(無子節點)colspan 跨越剩餘列,填滿行尾。
  • 視覺上呈現類似 Windows 資源管理器左側文件夾樹的結構。

優化建議

  1. 支持自定義列渲染(如添加操作按鈕、圖標、狀態)。
  2. 大數據量時緩存計算結果,避免每次請求重新構建。
  3. 前端配合 CSS/JS 實現展開/折疊、拖拽排序等交互。
  4. 數據庫層面使用「閉包表」或「遞歸 CTE」高效查詢子樹。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved