- 第一部分:基础准备 - 理解科汛自定义SQL标签的工作模式,并准备好数据库和页面。
- 第二部分:核心教程 - 编写并配置三级菜单的自定义SQL标签,从单表查询到多表关联。
- 第三部分:前端渲染 - 在模板页面中使用循环标签,将查询出的数据渲染成完整的三级菜单。
第一部分:基础准备
在开始之前,我们需要明确几个关键点并做好准备工作。

1 理解科汛自定义SQL标签
科汛的自定义SQL标签允许你直接在模板页面中执行SQL查询,并将查询结果以变量形式传递给模板引擎进行渲染,其基本语法结构如下:
<ks:sql sql="你的SQL语句" var="自定义变量名">
sql: 你要执行的SQL查询语句。var: 一个变量名,用于存储查询结果,查询结果通常是一个二维数组(列表),即使只查询到一条数据,也会被包装成一个包含一个元素的数组。
2 准备数据库表结构
为了演示,我们假设你的数据库中有两个表:ks_class (栏目表) 和 ks_content (内容表),一个典型的三级菜单结构是:一级栏目 -> 二级栏目 -> 三级栏目。
这里我们简化模型,只用一个 ks_class 表来存储所有层级的栏目,表中需要有一个关键的字段来表示层级关系。
ks_class 表示例:

| classid (ID) | classname (名称) | parentid (父级ID) | classurl (链接) |
|---|---|---|---|
| 1 | 首页 | 0 | / |
| 2 | 关于我们 | 0 | /about/ |
| 3 | 产品中心 | 0 | /products/ |
| 4 | 公司简介 | 2 | /about/company/ |
| 5 | 发展历程 | 2 | /about/history/ |
| 6 | 手机系列 | 3 | /products/mobile/ |
| 7 | 电脑系列 | 3 | /products/computer/ |
| 8 | 旗舰手机 | 6 | /products/mobile/flagship/ |
| 9 | 入门手机 | 6 | /products/mobile/entry/ |
关系说明:
parentid = 0的栏目是一级菜单(如“关于我们”、“产品中心”)。parentid等于某个一级栏目classid的,是二级菜单(如“公司简介”的parentid是 2)。parentid等于某个二级栏目classid的,是三级菜单(如“旗舰手机”的parentid是 6)。
3 准备模板页面
在你的模板文件中(index.htm),找到你希望显示菜单的位置,我们将在这里编写后续的代码。
第二部分:核心教程 - 编写三级菜单SQL
实现三级菜单的关键在于如何一次性查询出所有需要的数据,并在前端进行逻辑分组,我们提供两种SQL方案:单表查询 和 多表关联查询,对于三级菜单,单表查询通常更简单高效。
单表查询(推荐)
这种方法只查询 ks_class 表,逻辑清晰,性能较好。

目标: 查询出所有一级菜单,并同时关联出每个一级菜单下的二级和三级菜单。
SQL语句:
SELECT
c1.classid AS id1,
c1.classname AS name1,
c1.classurl AS url1,
c2.classid AS id2,
c2.classname AS name2,
c2.classurl AS url2,
c3.classid AS id3,
c3.classname AS name3,
c3.classurl AS url3
FROM
ks_class c1
LEFT JOIN
ks_class c2 ON c1.classid = c2.parentid
LEFT JOIN
ks_class c3 ON c2.classid = c3.parentid
WHERE
c1.parentid = 0 -- 条件:只查询顶级菜单
ORDER BY
c1.orderby, c1.classid, c2.orderby, c2.classid, c3.orderby, c3.classid;
SQL语句解析:
FROM ks_class c1: 主表是ks_class,并给它一个别名c1,代表所有的一级菜单。LEFT JOIN ks_class c2 ON c1.classid = c2.parentid: 将表自身作为第二张表c2(二级菜单)进行左连接,连接条件是“一级菜单的ID等于二级菜单的父ID”,这样,每个一级菜单就会关联出它下属的所有二级菜单。LEFT JOIN ks_class c3 ON c2.classid = c3.parentid: 同理,再将表自身作为第三张表c3(三级菜单)进行左连接,连接条件是“二级菜单的ID等于三级菜单的父ID”,这样,每个二级菜单就会关联出它下属的所有三级菜单。WHERE c1.parentid = 0: 这是关键的过滤条件,确保我们只从顶级菜单开始查询,避免数据混乱。ORDER BY ...: 排序,确保菜单的顺序符合预期,先按一级菜单排序,再按二级,最后按三级。
在科汛标签中使用SQL
我们将上面的SQL语句嵌入到科汛标签中。
<ks:sql sql="SELECT c1.classid AS id1, c1.classname AS name1, c1.classurl AS url1, c2.classid AS id2, c2.classname AS name2, c2.classurl AS url2, c3.classid AS id3, c3.classname AS name3, c3.classurl AS url3 FROM ks_class c1 LEFT JOIN ks_class c2 ON c1.classid = c2.parentid LEFT JOIN ks_class c3 ON c2.classid = c3.parentid WHERE c1.parentid = 0 ORDER BY c1.orderby, c1.classid, c2.orderby, c2.classid, c3.orderby, c3.classid" var="menu_list">
执行后,变量 menu_list 中将存储一个二维数组,数组中的每个元素都代表一个“一级菜单及其下属所有二、三级菜单”的组合,如果一个一级菜单没有二级菜单,id2, name2 等字段将为空。
第三部分:前端渲染 - 循环与嵌套
现在最关键的一步来了:如何将 menu_list 这个扁平化的数组,渲染成我们想要的树形(嵌套)结构。
我们将使用三层嵌套的 <ks:foreach> 循环标签来实现。
完整模板代码示例:
<!-- 在你的模板文件中,<div class="nav">...</div> 内 -->
<style>
/* 简单的样式,让菜单更清晰 */
.nav { font-family: Arial, sans-serif; }
.nav ul { list-style: none; padding: 0; margin: 0; }
.nav li { position: relative; }
.nav a { display: block; padding: 8px 15px; text-decoration: none; color: #333; border-bottom: 1px solid #eee; }
.nav a:hover { background-color: #f0f0f0; }
.nav .sub-menu { display: none; } /* 默认隐藏二级菜单 */
.nav .sub-menu .sub-menu { display: none; } /* 默认隐藏三级菜单 */
/* 鼠标悬停显示子菜单 (可选) */
.nav > li:hover > .sub-menu { display: block; }
.nav .sub-menu > li:hover > .sub-menu { display: block; }
</style>
<ul class="nav">
<!--
第一层循环:遍历所有一级菜单
item 是循环变量,代表数组中的每一个元素,即一个一级菜单及其关联数据
key 是索引,这里用不到
-->
<ks:foreach name="menu_list" item="item1" key="key1">
<li>
<!--
输出一级菜单的名称和链接
注意:这里要判断 name1 是否存在,因为 LEFT JOIN 可能为空
-->
<a href="{if:$item1['url1']}{fun:geturl($item1['url1'])}{else:/}{/if}">
{$item1['name1']}
</a>
<!--
第二层循环:遍历当前一级菜单下的所有二级菜单
item2 代表每一个二级菜单项
注意:这里我们只处理 name2 不为空的情况,即确实存在二级菜单
-->
<ks:foreach name="item1['name2']" item="item2" key="key2">
<ul class="sub-menu">
<li>
<!-- 输出二级菜单的名称和链接 -->
<a href="{if:$item1['url2']}{fun:geturl($item1['url2'])}{else:/}{/if}">
{$item1['name2']}
</a>
<!--
第三层循环:遍历当前二级菜单下的所有三级菜单
item3 代表每一个三级菜单项
注意:这里我们只处理 name3 不为空的情况
-->
<ks:foreach name="item1['name3']" item="item3" key="key3">
<ul class="sub-menu">
<li>
<!-- 输出三级菜单的名称和链接 -->
<a href="{if:$item1['url3']}{fun:geturl($item1['url3'])}{else:/}{/if}">
{$item1['name3']}
</a>
</li>
</ul>
</ks:foreach>
</li>
</ul>
</ks:foreach>
</li>
</ks:foreach>
</ul>
代码解析与关键点
-
三层嵌套
<ks:foreach>:- 第一层:
ks:foreach name="menu_list",这是我们的主循环,变量item1包含了一级菜单及其所有关联的二、三级菜单数据。 - 第二层:
ks:foreach name="item1['name2']",这是最巧妙的地方,科汛的foreach标签可以直接对一个数组的某个(可能是重复的)字段进行循环。item1['name2']会收集当前一级菜单下所有二级菜单的name2值,形成一个临时数组,foreach就会遍历这个数组,每次循环时,item2会是当前这个二级菜单的所有字段(id2,name2,url2)。 - 第三层:
ks:foreach name="item1['name3']",同理,item1['name3']会收集当前一级菜单下所有三级菜单的name3值,item3会是当前这个三级菜单的所有字段。
- 第一层:
-
数据访问:
- 一级菜单数据通过
item1['name1'],item1['url1']访问。 - 二级菜单数据通过
item1['name2'],item1['url2']访问。 - 三级菜单数据通过
item1['name3'],item1['url3']访问。
- 一级菜单数据通过
-
链接处理:
{fun:geturl($item1['url1'])}是科汛的内置函数,用于处理URL,确保链接正确,如果URL为空,则默认跳到首页 。- 注意:在
foreach内部,循环变量(如item1)在整个循环过程中是同一个引用,所以内部的foreach访问的item1['url2']实际上是它父级循环中item1的对应字段,这在科汛模板引擎中是可行的。
-
CSS样式:
- 我添加了一些简单的CSS,用于隐藏子菜单(
.sub-menu { display: none; })。 - 你可以根据需要修改,例如使用
hover来触发子菜单的显示,或者使用JavaScript实现更复杂的交互(如下拉、滑动等)。
- 我添加了一些简单的CSS,用于隐藏子菜单(
总结与进阶
通过以上三个步骤,你就成功地使用科汛自定义SQL标签实现了一个动态、可配置的三级菜单。
-
优点:
- 数据与表现分离:菜单数据完全由数据库控制,修改菜单只需在后台管理栏目,无需改动代码。
- 高效:一次数据库查询获取所有数据,减少了页面加载时的请求次数。
- 灵活:SQL和模板都可以根据你的实际需求进行修改,例如增加菜单图标、描述等字段。
-
进阶:
- 高亮当前栏目:可以通过
{ks:get}标签获取当前页面的栏目ID,然后在循环中判断item1['id1'],item2['id2'],item3['id3']是否与之相等,如果相等则给<a>标签添加一个class="active",并定义其高亮样式。 - 多表关联:如果你的菜单项需要关联内容表(点击“产品中心”显示最新的产品列表),你可以在SQL中再
JOINks_content表,并获取相关内容信息。 - 缓存:对于不经常变化的菜单,可以开启科汛的缓存功能,进一步提升网站性能。
- 高亮当前栏目:可以通过
希望这份详细的教程能帮助你顺利掌握科汛自定义SQL标签的使用!
