这是一个非常经典的需求,但 DedeCMS 本身并没有内置如此精细化的多级筛选功能,我们需要通过自定义模型 + 开发的方式来实现。

核心思路是:
- 创建数据模型:为商品建立一个支持多级分类和自定义属性(用于筛选)的数据模型。
- 设计数据结构:将筛选条件(如品牌、材质、风格等)作为商品的自定义字段来存储。
- 修改模板:在前台模板中,用下拉菜单或复选框的形式展示这些筛选条件。
- 修改程序:修改列表页的
arc.listview.class.php文件,使其能够根据筛选条件动态生成 SQL 查询语句。
下面是详细的步骤分解。
第一步:创建自定义数据模型
这是实现筛选功能的基础,我们需要创建一个专门用于商城商品的数据模型,并为其添加筛选所需的字段。
-
进入模型管理:
(图片来源网络,侵删)- 登录 DedeCMS 后台。
- 核心 -> 内容模型管理 -> 添加新模型。
- 模型名称:填写“商城商品”或“产品”。
- 表前缀:可以使用默认的
dede_,也可以自定义,如shop_,为了清晰,建议使用shop_。 - 填写其他信息,然后提交,系统会自动创建数据表
shop_archives(文章主表)和shop_addonxx(文章附加表,xx是数字)。
-
添加筛选字段:
- 在刚刚创建的模型管理页面,点击“字段管理”。
- 我们将为每个筛选条件添加一个字段,我们要实现“品牌 > 系列 > 型号”的三级筛选,就需要添加三个字段。
示例:添加“品牌”字段
- 字段名:
brand(必须为英文小写,无空格) - 字段别名:
品牌 - 字段类型:
下拉菜单 - 字段值:
品牌A,品牌B,品牌C(用英文逗号分隔,这些是后台发布商品时可以选择的品牌) - 是否为必填项:根据需要选择
- 保存。
示例:添加“系列”字段
- 字段名:
series - 字段别名:
系列 - 字段类型:
下拉菜单 - 字段值:
系列1,系列2,系列3 - 保存。
示例:添加“型号”字段
(图片来源网络,侵删)- 字段名:
model - 字段别名:
型号 - 字段类型:
下拉菜单或文本(如果型号很多且不固定,文本让用户输入更灵活) - 保存。
你可以添加更多筛选字段,如:
price(价格,类型为数字)color(颜色,类型为下拉菜单)material(材质,类型为下拉菜单)guarantee(保修,类型为单选)
-
发布测试商品:
- 核心 -> 内容发布 -> 选择你刚创建的“商城商品”模型。
- 发布几条测试商品,并为它们填写不同的筛选字段值。
- 商品A:品牌=品牌A, 系列=系列1, 型号=X1
- 商品B:品牌=品牌A, 系列=系列1, 型号=X2
- 商品C:品牌=品牌B, 系列=系列2, 型号=Y1
第二步:修改列表页模板 (list_*.htm)
现在我们要在前台展示筛选控件。
-
找到你的列表页模板,通常位于
templets/你的默认模板/目录下,文件名如list_shop.htm。 -
开始前,添加筛选表单: 在
{dede:list}标签之前,添加以下 HTML 代码,这里以品牌、系列、型号为例。<div class="filter-area"> <form name="filterForm" action="{dede:global.cfg_cmspath/}/plus/list.php" method="get"> <!-- 隐藏字段,指定模型ID,这个ID在模型管理里可以看到 --> <input type="hidden" name="tid" value="{dede:field.tid/}" /> <!-- 隐藏字段,指定模型ID,这个ID在模型管理里可以看到 --> <input type="hidden" name="modelid" value="你的模型ID" /> <!-- 品牌筛选 --> <div class="filter-item"> <label>品牌:</label> <select name="brand" onchange="document.filterForm.submit()"> <option value="">全部品牌</option> {dede:channel type='self' row='100'} <!-- 这里需要自定义SQL来获取所有品牌,下面会提供SQL示例 --> <!-- <option value="[field:typename/]">[field:typename/]</option> --> {/dede:channel} </select> </div> <!-- 系列筛选 (根据品牌联动) --> <div class="filter-item"> <label>系列:</label> <select name="series" onchange="document.filterForm.submit()"> <option value="">全部系列</option> <!-- 系列选项将通过JS动态加载,这里先留空 --> </select> </div> <!-- 型号筛选 (根据系列联动) --> <div class="filter-item"> <label>型号:</label> <select name="model" onchange="document.filterForm.submit()"> <option value="">全部型号</option> <!-- 型号选项将通过JS动态加载,这里先留空 --> </select> </div> <!-- 其他筛选字段,如价格 --> <div class="filter-item"> <label>价格:</label> <input type="text" name="minprice" placeholder="最低价" size="5" /> <span>-</span> <input type="text" name="maxprice" placeholder="最高价" size="5" /> <button type="submit">筛选</button> </div> </form> </div>
重要说明:
modelid:你必须在这里填入你创建的“商城商品”模型的ID。{dede:channel}标签默认用于获取栏目,不适用于获取自定义字段的值,要获取所有品牌列表,你需要自定义一个函数或使用SQL标签,一个简单的方法是在后台手动获取所有品牌值,然后硬编码到option中,或者使用更复杂的dede:sql标签(不推荐,有性能和安全风险)。
第三步:修改程序文件 (arc.listview.class.php)
这是实现筛选功能的核心逻辑,当用户选择筛选条件并提交表单后,PHP程序需要根据这些条件来修改查询商品的SQL语句。
-
找到文件:
include/arc.listview.class.php -
修改
Query方法:在这个文件中,找到Query()方法,这个方法负责构建最终的SQL查询,我们需要在它里面添加对筛选参数的处理。 -
添加筛选逻辑:在
Query()方法内部,找到构建where条件的部分(通常是一个$this->addSql = '';相关的逻辑),然后在后面添加你的筛选代码。// 在 arc.listview.class.php 的 Query() 方法中添加 // 1. 获取筛选参数 $brand = isset($this->refObj->Fields['brand']) ? trim($this->refObj->Fields['brand']) : ''; $series = isset($this->refObj->Fields['series']) ? trim($this->refObj->Fields['series']) : ''; $model = isset($this->refObj->Fields['model']) ? trim($this->refObj->Fields['model']) : ''; $minprice = isset($this->refObj->Fields['minprice']) ? trim($this->refObj->Fields['minprice']) : ''; $maxprice = isset($this->refObj->Fields['maxprice']) ? trim($this->refObj->Fields['maxprice']) : ''; // 2. 构建 WHERE 条件 $addwhere = array(); if ($brand != '') { // 注意:字段名要和你自定义的字段名一致 // 表名前缀要和你创建模型时的一致,这里是 shop_ $addwhere[] = " (arc.addonfield LIKE '%\"{$brand}\"%') "; } if ($series != '') { $addwhere[] = " (arc.addonfield LIKE '%\"{$series}\"%') "; } if ($model != '') { $addwhere[] = " (arc.addonfield LIKE '%\"{$model}\"%') "; } if ($minprice != '') { $addwhere[] = " arc.price >= {$minprice} "; } if ($maxprice != '') { $addwhere[] = " arc.price <= {$maxprice} "; } // 3. 将条件附加到主查询 if (count($addwhere) > 0) { $this->addSql = " AND " . implode(' AND ', $addwhere); }
代码解释:
$this->refObj->Fields:用于获取通过URL传递过来的GET参数。arc.addonfield:这是 DedeCMS 存储所有自定义字段值的地方,它是一个序列化的JSON字符串,所以我们需要用LIKE来查询。- 注意:这种
LIKE '%...%'的查询方式在数据量很大时性能会很差,对于大型商城,推荐将筛选字段单独存为数据库列,并进行精确匹配 (),上面的代码是通用但低效的方案。 - 表名:
arc是默认的别名,如果你的附加表前缀是shop_,那么附加表的实际表名是shop_addonxx,在查询时,你可能需要直接操作this->dsql->SetQuery()来构建更复杂的SQL,而不是仅仅依赖this->addSql,但为了简单,我们先使用addSql。
第四步:实现三级联动(可选但强烈推荐)
上面的筛选是静态的,用户选择品牌后,系列和型号不会自动更新,要实现联动,需要用到 JavaScript (AJAX)。
-
准备数据源:你需要一个PHP文件来根据父级ID返回子级数据。
get_filter_options.php。 这个文件需要连接数据库,查询shop_addonxx表,根据brand字段来获取唯一的series列表,再根据series来获取model列表。get_filter_options.php示例:<?php require_once(dirname(__FILE__)."/../include/common.inc.php"); require_once(DEDEINC."/dedetpl.class.php"); $type = isset($_GET['type']) ? trim($_GET['type']) : ''; $value = isset($_GET['value']) ? trim($_GET['value']) : ''; $options = array(); $table = '你的附加表名'; // shop_addon10 $dsql = new DedeSql(false); if ($type == 'series' && $value != '') { // 根据品牌获取系列 $query = "SELECT DISTINCT dede_field_serie_value FROM `{$table}` WHERE dede_field_brand_value = '{$value}'"; // 注意:字段名 dede_field_xxx_value 是DedeCMS存储自定义字段的默认格式,需要确认你的实际字段名 // 更稳妥的写法是查询 addonfield 字段并用JSON解析 $query = "SELECT addonfield FROM `{$table}` WHERE addonfield LIKE '%\"brand\":\"{$value}\"%'"; $dsql->Execute('me', $query); while ($row = $dsql->GetArray('me')) { $data = json_decode($row['addonfield'], true); if (isset($data['serie']) && !in_array($data['serie'], $options)) { $options[] = $data['serie']; } } } elseif ($type == 'model' && $value != '') { // 根据系列获取型号 $query = "SELECT addonfield FROM `{$table}` WHERE addonfield LIKE '%\"serie\":\"{$value}\"%'"; $dsql->Execute('me', $query); while ($row = $dsql->GetArray('me')) { $data = json_decode($row['addonfield'], true); if (isset($data['model']) && !in_array($data['model'], $options)) { $options[] = $data['model']; } } } echo json_encode($options); exit(); ?> -
修改模板,添加JS: 在你的
list_*.htm模板中,添加以下JS代码。<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ // 品牌改变时,加载系列 $('select[name="brand"]').change(function(){ var brand = $(this).val(); var seriesSelect = $('select[name="series"]'); seriesSelect.empty().append('<option value="">加载中...</option>'); if(brand){ $.ajax({ url: '/get_filter_options.php', type: 'GET', data: {type: 'series', value: brand}, dataType: 'json', success: function(series){ seriesSelect.empty().append('<option value="">全部系列</option>'); $.each(series, function(i, item){ seriesSelect.append('<option value="' + item + '">' + item + '</option>'); }); } }); } else { seriesSelect.empty().append('<option value="">全部系列</option>'); } }); // 系列改变时,加载型号 $('select[name="series"]').change(function(){ var series = $(this).val(); var modelSelect = $('select[name="model"]'); modelSelect.empty().append('<option value="">加载中...</option>'); if(series){ $.ajax({ url: '/get_filter_options.php', type: 'GET', data: {type: 'model', value: series}, dataType: 'json', success: function(models){ modelSelect.empty().append('<option value="">全部型号</option>'); $.each(models, function(i, item){ modelSelect.append('<option value="' + item + '">' + item + '</option>'); }); } }); } else { modelSelect.empty().append('<option value="">全部型号</option>'); } }); }); </script>
总结与注意事项
- 性能问题:
LIKE '%...%'查询在数据量大时是性能杀手,对于正式运行的商城,强烈建议修改数据表结构,为每个筛选字段(如brand,series,model)建立独立的数据库列,并进行索引,这样查询效率会高几个数量级。 - URL美化:默认的筛选URL会很长,如
.../list.php?tid=1&brand=品牌A&series=系列1,可以使用 DedeCMS 的URL重写规则或伪静态技术来美化URL,如.../list/goods/brand/品牌A/series/系列1.html,这需要修改arc.listview.class.php中的MakeHtml()和GetMakeFileRule()方法,实现较为复杂。 - 调试:在修改
arc.listview.class.php时,如果遇到问题,可以在Query()方法里echo $this->addSql; exit;来打印最终生成的SQL语句,检查是否符合预期。 - 版本兼容性:以上代码基于较新版本的 DedeCMS 思路,如果你使用的是非常老的版本(如 5.6),文件路径和类名可能略有不同,但核心逻辑是一致的。
通过以上四个步骤,你就可以在 DedeCMS 中实现一个功能完善的三级筛选商城了,这是一个典型的二次开发案例,考验的是对 DedeCMS 数据结构和流程的理解。
