在電商或 CMS 系統中,常需展示「分類 → 商品列表」或「問題分類 → 問題列表」這類兩層甚至多層巢狀結構。ThinkPHP 的 <volist> 標籤原生支援多層嵌套,但需注意 key 屬性與變量作用域,否則易出現「內層循環變量覆蓋外層」或「無法判斷最後一項」等問題。
<?php
namespace Home\Controller;
use Think\Controller;
class ServiceController extends Controller
{
public function index()
{
$prod = I('get.prod_en');
$id = I('get.id', 0, 'int');
if (empty($prod)) {
// 多重循環所需的二維陣列
$serviceProduct = [];
$service = M('product_class')->order('oid ASC')->select();
foreach ($service as $item) {
$products = M('product')
->where(['prod_class_id' => $item['pcid']])
->order('oid ASC')
->select();
$serviceProduct[] = [
'srvName' => $item['pc_cn'],
'product' => $products,
];
}
$this->assign('serviceProduct', $serviceProduct);
$this->display();
} else {
// 其他分支略...
}
}
}
<volist name="serviceProduct" id="sp" key="i">
<dl class="dlist <?php echo ($i % 2) ? 'odd' : 'even'; ?>">
<dt>{$sp.srvName}</dt>
<volist name="sp.product" id="pd" key="j">
<dd>{$pd.prod_cn}</dd>
<!-- 判斷是否為最後一項,避免多餘分隔符 -->
<if condition="$j lt count($sp['product'])">
<dd>|</dd>
</if>
</volist>
<!-- 空分類顯示佔位 -->
<if condition="count($sp['product']) eq 0">
<dd>暫無產品</dd>
</if>
</dl>
</volist>
| 技巧 | 說明 |
|---|---|
| 指定 key 屬性 | 每一層 volist 必須設定唯一 key(如 i、j),否則內層循環會覆蓋外層的 $key 變量,導致條件判斷失效。 |
| 引用外層陣列長度 | 內層判斷最後一項時,使用 count($sp['product']) 而非 count($pd);$sp 為外層當前項對象,可直接存取其 product 子陣列。 |
| 空陣列防禦 | 若某分類無商品,volist 不會執行,需額外 <if condition="count($sp['product']) eq 0"> 給予友好提示。 |
| CSS 斑馬紋 | 利用外層 key="i" 判斷奇偶($i % 2),為 dl 動態加 class,實現隔行換色。 |
若需「大分類 → 小分類 → 商品」,只需再套一層 volist,並依序命名 key="i"、key="j"、key="k"。原理相同,注意變量作用域即可:
<volist name="bigCats" id="big" key="i">
<volist name="big.smallCats" id="small" key="j">
<volist name="small.products" id="prod" key="k">
<!-- 使用 $i, $j, $k 分別控制三層邏輯 -->
</volist>
</volist>
</volist>
key → 外層 $key 被覆蓋,導致 $key lt count(...) 判斷錯誤。key 且名稱唯一。condition="$j lt count($pd)" → $pd 是當前項對象而非陣列。count($sp['product']) 取得外層當前分類的商品總數。$products 是否為空陣列。select() 回傳空陣列而非 false,模板端 count() 即可安全判斷。掌握 key 隔離、外層變量引用、空陣列處理三大要點,即可輕鬆駕馭 ThinkPHP 多層 volist 嵌套,構建清晰、可維護的列表頁面。