新闻中心

创新引领,展望未来公司新闻一
发布时间:2025-03-08 浏览数:1288

非常好的问题!这次你具体到 L33 这一行的菜单项,要从"这一行 HTML 怎么粘到框架上"反推回去,再讲清楚程序员控制菜单显示的 5 个维度。先去查 `$navCur` 变量怎么来的。

        

            

toolName: no_need_execute

            

status: no_need_execute

          

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:\phpstudy_pro\WWW\test.io\addons\wwh\view\2025\header.html

          


        

          


        

            

toolName: search_by_regex

            

status: success

          

query: navCur

          


        

            

toolName: search_by_regex

            

status: success

          

query: is_search

          


        

            

toolName: view_folder

            

status: success

          

c:/phpstudy_pro/WWW/test.io/addons/wwh/controller

          


        

            

toolName: file_search

            

status: success

          

file_pattern: c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/*.php

          


        

            

toolName: search_by_regex

            

status: success

          

query: assign\(.*navCur

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Index.php

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Search.php

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Column.php

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Archives.php

          

### Relevant Code Snippets

1. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Index.php:L108-L115

  — 在 Index 控制器的 index 方法中,通过 Db 查询获取了 navCur 数据,并赋值给模板变量,其中包含 classify 和 is_search 字段。

2. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Search.php:L83-L97

  — 在 Search 控制器的 index 方法中,显式定义了 navCur 数组并设置了 is_search 为 true,然后赋值给模板。

3. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Column.php:L20-L25

  — 在 Column 控制器的 index 方法中,调用 getCurrentColumn 获取 navCur 数据,并通过 view->assign 方法传递给模板。

4. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Archives.php:L21-L25

  — 在 Archives 控制器的 index 方法中,查询 navCur 数据并赋值给模板变量。

5. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Column.php:L10-L15

  — 在 Column 控制器中,定义了 getCurrentColumn 方法来获取 navCur 数据。

6. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Index.php:L114-L115

  — 使用 array_merge 和 $this->assign 方法将 navCur 变量批量分配到模板。

7. c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Search.php:L89-L97

  — 使用 assign 方法将 navCur 变量分配给模板,同时设置了 is_search 属性。


这些代码片段揭示了 navCur 变量是如何在不同控制器中通过数据库查询或直接赋值,并最终传给模板的。

        

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Index.php

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Search.php

          


        

            

toolName: view_files

            

status: success

          

            

filePath: c:\phpstudy_pro\WWW\test.io\addons\wwh\controller\Column.php

          

非常好的问题!现在拿 [header.html#L34](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/view/2025/header.html#L34) 这一行**真实代码**做解剖(注意 L33 是 `{volist name="nav" id="v"}`,L34 才是 `<li>` 行,用户输入说 L33 应是按 IDE 选区算的):


```html

<li {if !isset($navCur.is_search) && $navCur.classify == $v.classify}class="active menu-open"{/if}>

```


我要讲 3 件事:① 这一行的每个符号都跟框架哪个粘合点连着 ② 这一行从被程序员写下到被浏览器渲染出来经过哪些阶段 ③ 程序员可以从哪几个维度控制菜单显示。


---


## 一、解剖 L34 这一行:每个符号的粘合关系


把这行拆成最小单元,每个单元都对应一个框架粘合点:


| 代码片段 | 是什么 | 跟框架哪个粘合点连着 |

|---|---|---|

| `<li>` | HTML 标签 | 不跟框架粘,纯 HTML |

| `{if ...}class="active menu-open"{/if}` | ThinkPHP 模板标签 | **粘合点 5**:模板编译时 `tagIf()` 翻译成 `<?php if(...): ?>class="..."<?php endif; ?>` |

| `!isset($navCur.is_search)` | PHP 表达式判断 | `$navCur` 这个变量来自 extract 拆包 → **粘合点 6**:变量注入 |

| `$navCur.is_search` | 模板点号语法糖 | 编译时 `autoBuildVar` 把 `.` 翻译成 `[' ']` → `isset($navCur['is_search'])` |

| `$navCur.classify == $v.classify` | 比较两个数组下标 | `$navCur` 和 `$v` 都是关联数组,来自 [Base.php#L125](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L125) `assign('nav', ...)` 和 [Index.php#L108](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Index.php#L108) `assign('navCur', ...)` → **粘合点 6** + **控制器继承链(粘合点 4)** |

| `$v` | volist 循环变量 | 来自 L33 `{volist name="nav" id="v"}` → `foreach($__LIST__ as $v)` → **粘合点 5** |

| `$navCur` | 当前页面所在栏目 | 来自控制器 [Index.php#L108-L111](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Index.php#L108-L111) 数据库查询 → **粘合点 4 + 6** |


### 整行翻译成 PHP 后是这样的(编译阶段做的事)


```php

<li <?php if(!isset($navCur['is_search']) && $navCur['classify'] == $v['classify']): ?>class="active menu-open"<?php endif; ?>>

```


执行时的判定逻辑:**"当前不是搜索页"** 且 **"当前栏目的分类编号等于正在循环到的这个菜单项的分类编号"** → 给这个 `<li>` 加上 `class="active menu-open"` 高亮样式。


---


## 二、L34 从"程序员写下"到"浏览器渲染"的完整 5 阶段


### 阶段 A:程序员在编辑器写 header.html


程序员写下这行 HTML + ThinkPHP 模板标签混合代码,存到 `addons/wwh/view/2025/header.html`。


### 阶段 B:框架启动 + 数据准备(运行时每次请求都做)


| 时间点 | 干了什么 | 涉及粘合点 |

|---|---|---|

| T1 | `App::run()` 启动 | 框架本身 |

| T2 | 触发 `app_init` 钩子 → FastAdmin 注册插件路由 + 调用 `Wwh::appInit()` 加载 `wwh/common.php`(含 `__lang` 函数) | **粘合点 2** |

| T3 | 路由匹配 `wwh/index/index` → 实例化 `\addons\wwh\controller\Index` | **粘合点 3** |

| T4 | `Index::__construct` → 一路调到 `\think\addons\Controller::__construct` → 设置 `view_path = addons/wwh/view/` | **粘合点 4** |

| T5 | `Base::_initialize()` 执行 → 调 `initNavigation()` 查 `fa_wwh_column` 表 + `assign('nav', $navTree)` | **粘合点 6** |

| T6 | `Index::index()` 执行 → 查 `fa_wwh_config`、`fa_wwh_column`(当前栏目) → `assign('navCur', $navCur)` | **粘合点 6** |


到这一步,`View::$data` 里已经有了 `'nav' => [...3个一级栏目树...]` 和 `'navCur' => ['classify'=>'home', ...]`。


### 阶段 C:模板编译(include 之前,第一次访问或模板更新时做)


```

Index::index() 调 $this->view->fetch('/index')

    ↓

View::fetch() → Template::fetch()

    ↓

parseTemplateFile('/index') → addons/wwh/view/2025/index.html

    ↓

compiler() 编译流水线:

  ├─ file_get_contents(index.html)  ← 读到 index.html 内容

  ├─ parse() 流水线:

  │   ├─ parseInclude()  ← {include file="header"} 被替换成 header.html 内容

  │   │                   此刻 header.html#L34 也被拼进来

  │   └─ parseTagLib()   ← 把 {volist}{if} 翻译成 PHP

  │       ├─ L33 {volist name="nav" id="v"}  → foreach($__LIST__ as $v)

  │       └─ L34 {if ...}                    → <?php if(...): ?>...<?php endif; ?>

  └─ storage->write(cacheFile)  ← 写到 runtime/temp/xxxxx.php

```


**这一步就是你之前问的"include 之前"——header.html 在这一刻被融进编译缓存,所有 `{}` 标签被翻译成 PHP 代码**。


### 阶段 D:执行编译后的 PHP(include 时)


[File::read()](file:///c:/phpstudy_pro/WWW/test.io/thinkphp/library/think/template/driver/File.php#L45-L54):


```php

extract($vars, EXTR_OVERWRITE);   // $vars['nav'] → $nav, $vars['navCur'] → $navCur

include $this->cacheFile;          // 执行 runtime/temp/xxxxx.php

```


执行时循环里发生了什么(假设当前访问首页,`$navCur['classify']='home'`,循环到第 1 个一级栏目"关于我们" `classify='about'`):


```php

foreach($nav as $v) {

    // 第 1 轮:$v = ['name'=>'关于我们', 'classify'=>'about', 'childlist'=>[...]]

    // $navCur = ['classify'=>'home', ...]

    ?>

    <li <?php if(!isset($navCur['is_search']) && $navCur['classify'] == $v['classify']): ?>class="active menu-open"<?php endif; ?>>

    <?php

    // 判定:!isset(false) → true,'home' == 'about' → false

    // 结果:if 不成立 → 不加 class

    // 输出:<li>

}

```


### 阶段 E:浏览器收到 HTML + CSS 渲染


```html

<li>关于我们</li>           ← 没有 active class

<li class="active menu-open">首页</li>  ← 命中 active,CSS 给它高亮

<li>产品中心</li>

```


CSS 文件 [style.css](file:///c:/phpstudy_pro/WWW/test.io/public/assets/addons/wwh/2025/css/style.css) 里有 `.nav-menu > li.active > a {color: #0863c4;}` 这种规则,浏览器一看 `class="active"` 就把这条菜单的字涂蓝、加下划线,用户就看到"当前页"高亮了。


---


## 三、程序员控制菜单显示的 5 个维度(从下到上掌控)


### 维度 1:数据库层 —— 增删改栏目(最根本的控制)


`fa_wwh_column` 表的字段就是程序员控制菜单的全部"开关":


| 字段 | 作用 | 改这个字段能控制什么 |

|---|---|---|

| `id` | 栏目 ID | 删一条记录 → 菜单少一项 |

| `name` | 栏目名称 | 改这个 → 菜单文字变(L35 `{$v.name|htmlentities}` 显示的就是它) |

| `diyname` | URL 别名 | 改这个 → L35 `addon_url('wwh/column/index', [':diyname'=>$v.diyname])` 生成的链接变 |

| `outlink` | 外链 | 不为空 → L35 走外链分支,等于空走内链 |

| `parent_id` | 父栏目 ID | 改这个 → 决定是几级菜单([Tree 类](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L122-L124) 按 parent_id 构建树形) |

| `classify` | 分类标识 | 改这个 → L34 `$navCur.classify == $v.classify` 判定哪个菜单高亮 |

| `isnav` | 是否显示在主导航 | `1` 显示 / `0` 不显示([Base.php#L118-L120](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L118-L120) `array_filter` 过滤) |

| `status` | 状态 | `normal` 显示 / `hidden` 隐藏([Base.php#L112](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L112) `where('status', 'normal')` 过滤) |

| `weigh` | 排序权重 | 改这个 → 菜单顺序变([Base.php#L114](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L114) `order('weigh desc')`) |

| `lang` | 语言 | `1` 中文 / `2` 英文([Base.php#L113](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L113) `where('lang', $this->lang)` 过滤) |


**举例**:程序员想让"关于我们"菜单从导航消失 → 后台改 `fa_wwh_column SET isnav=0 WHERE id=1` → 下次请求 `array_filter` 把它过滤掉 → `$navTree` 少一项 → volist 循环少一轮 → 浏览器渲染时少一个 `<li>`。


### 维度 2:控制器层 —— 控制传什么数据给模板


#### 2.1 Base.php 控制循环的数据源([Base.php#L108-L125](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L108-L125))


```php

protected function initNavigation()

{

    $columns = Db::name('wwh_column')

        ->where('status', 'normal')   // ← 维度:可以改成 'normal','hidden' 多状态

        ->where('lang', $this->lang)  // ← 维度:语言过滤

        ->order('weigh desc')         // ← 维度:排序方式

        ->select();


    $navList = array_filter($columns, function($item) {

        return $item['isnav'] == '1';  // ← 维度:主导航过滤条件

    });


    $navTree = Tree::instance()

        ->init(collection($navList)->toArray(), 'parent_id')

        ->getTreeArray(0);            // ← 维度:从哪个 parent_id 开始构建树


    $this->assign('nav', $navTree);   // ← 最终决定 $nav 里有什么

}

```


**程序员改这里能控制**:① 哪些 status 进菜单 ② 哪些 lang 进菜单 ③ 排序规则 ④ 哪些 isnav 进主导航 ⑤ 从哪一级开始构建树。


#### 2.2 Index.php 控制高亮变量 `$navCur`([Index.php#L108-L111](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Index.php#L108-L111))


```php

'navCur' => Db::name('wwh_column')

    ->where('diyname', $diyname)

    ->find() ?: ['classify' => 'none']

```


**程序员改这里能控制**:当前页面在哪个菜单项高亮。比如首页 `$diyname='home'`,查到 `classify='home'`,传给模板后 L34 判定 `$navCur.classify == $v.classify` 时只有 `classify='home'` 的那一项命中高亮。


#### 2.3 Search.php 控制特殊场景([Search.php#L83-L97](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Search.php#L83-L97))


```php

$navCur = [

    'classify' => $currentType ?: 'none',

    'is_search' => true   // ← 关键!搜索页才有的标志位

];

$this->assign(['navCur' => $navCur]);

```


**为什么 L34 要写 `!isset($navCur.is_search)`**?因为搜索页所有菜单都不该高亮(用户搜的不是某个栏目)。Search 控制器特意塞了 `is_search=true`,模板里 `!isset($navCur.is_search)` 判定为 false → 整个 if 短路 → 所有菜单都不加 active class。**这就是程序员用"控制器加字段 + 模板加判断"控制显示的典型套路**。


### 维度 3:模板层 —— 控制循环结构和条件([header.html#L32-L66](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/view/2025/header.html#L32-L66))


#### 3.1 L33 `{volist name="nav" id="v"}` —— 控制循环次数


改 `name` 可以换数据源,改 `id` 可以换循环变量名,加 `offset="1" length="5"` 可以截取前 5 项跳过第 1 项。


#### 3.2 L34 `{if ...}` —— 控制高亮条件


```html

{if !isset($navCur.is_search) && $navCur.classify == $v.classify}

  class="active menu-open"

{/if}

```


改这里能控制:① 加 active 的条件 ② 加什么 class(如改成 `class="current"` 换样式名)③ 加多个条件(如再加 `|| $v.id == $topId`)。


#### 3.3 L36 `{if $v.childlist}` —— 控制是否渲染子菜单


```html

{if $v.childlist}

  <button class="submenu-toggle"></button>

  <ul>...</ul>

{/if}

```


改这里能控制:有子菜单时显示什么、没子菜单时是否完全跳过 `<ul>`。


#### 3.4 L35 `{if empty($v.diyname)}` —— 控制 URL 生成方式


```html

<a href="{if empty($v.diyname)}

  {$v.outlink|htmlentities}                          ← 外链分支

{else}

  {:addon_url('wwh/column/index', [':diyname'=>$v.diyname])}  ← 内链分支

{/if}">

```


改这里能控制:什么栏目走外链、什么栏目走插件路由。


### 维度 4:CSS 层 —— 控制菜单外观


[style.css](file:///c:/phpstudy_pro/WWW/test.io/public/assets/addons/wwh/2025/css/style.css) 控制 `.active` 这个 class 的视觉表现:


```css

.header .nav-menu > li > a:after {

    content:"";display:block;width:0;height:2px;

    transition:.5s;background:#FFFFFF;

    position:absolute;right:0;bottom:20%;border-radius:3px

}

.header .nav-menu > li.active > a:after {

    width: 100%;   /* ← active 时下划线变成 100% 宽 */

}

.header .nav-menu > li.active > a {

    color: #0863c4;   /* ← active 时文字变蓝 */

}

```


**程序员改 CSS 能控制**:① 高亮颜色 ② 高亮方式(下划线/背景色/边框)③ 动画过渡时间。


### 维度 5:JS 层 —— 控制交互行为


[header.html](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/view/2025/header.html) 里有 `.nav-toggle`、`.submenu-toggle` 这些按钮的点击事件(在某个 .js 文件里),点击后给 `<li>` 加 `.menu-open` class 触发 CSS 展开动画。


**程序员改 JS 能控制**:① 子菜单展开/收起的触发方式 ② 移动端汉堡菜单点击逻辑 ③ hover 延迟等。


---


## 四、5 个维度总结表 + 控制能力分级


| 维度 | 改动位置 | 控制粒度 | 改动影响范围 | 难度 |

|---|---|---|---|---|

| 1. 数据库 | `fa_wwh_column` 表字段 | 增删改栏目、改顺序、改显隐 | 全站立即生效(每次请求都查库) | ⭐ |

| 2. 控制器 | `Base.php#L108-L125`、`Index.php#L108-L111`、`Search.php#L83-L97` | 数据过滤、查询条件、`$navCur` 内容 | 影响所有页面(Base)或单页(Index) | ⭐⭐ |

| 3. 模板 | `header.html#L33-L66` | 循环结构、高亮条件、HTML 结构 | 影响所有调用 header 的页面 | ⭐⭐⭐ |

| 4. CSS | `style.css` / `media.css` | 视觉表现(颜色/位置/动画) | 全站视觉风格 | ⭐⭐ |

| 5. JS | 公共 .js 文件 | 交互行为(点击/hover/动画) | 全站交互 | ⭐⭐⭐ |


---


## 五、用一张图把 L34 跟框架的粘合关系钉死


```

┌─ 程序员控制菜单的 5 个维度 ──────────────────────────────────────┐

│                                                                  │

│  ①数据库层                ②控制器层              ③模板层         │

│  fa_wwh_column            Base.php               header.html#L34│

│  ├ isnav=1          →     array_filter      →    {volist} 循环   │

│  ├ status=normal    →     where('status')   →    ↑               │

│  ├ weigh=99         →     order('weigh')    →    决定循环顺序     │

│  ├ parent_id=0      →     Tree::getTreeArray→    决定嵌套层级    │

│  ├ classify='home'  →     assign('nav')     →    $v.classify     │

│  └ diyname='home'   →     assign('navCur')  →    $navCur.classify│

│                                                                  │

│  ④CSS层                  ⑤JS层                                 │

│  style.css                main.js                                │

│  .active {color:blue}    点击 .nav-toggle 加 .active class      │

│  ↑                        ↑                                      │

│  控制视觉表现             控制交互行为                            │

│                                                                  │

└──────────────────────────────────────────────────────────────────┘

                              ↓

                    L34 编译后变成 PHP:

        <li <?php if(!isset($navCur['is_search'])

                    && $navCur['classify']==$v['classify']):?>

                class="active menu-open"

              <?php endif;?>>

                              ↓

                    运行时执行(extract 注入变量 + include)

                              ↓

                    浏览器收到:<li class="active menu-open">首页</li>

                              ↓

                    CSS 把 active 项涂蓝高亮

```


---


## 六、一句话总结


> [header.html#L34](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/view/2025/header.html#L34) 这一行的菜单项跟框架的粘合**完全发生在 include 之前的"编译阶段"**——`{if}` 被 `tagIf()` 翻译成 PHP `if`、`$v.classify` 的点号被 `autoBuildVar()` 翻译成 `$v['classify']`,整个过程在 [Template::parse()](file:///c:/phpstudy_pro/WWW/test.io/thinkphp/library/think/Template.php#L360-L373) 流水线里完成。运行时只剩纯 PHP 代码,靠 `extract()` 拆出 `$nav`、`$navCur` 让循环和高亮判断能取到数据。**程序员控制菜单显示的 5 个维度从底到顶是**:①数据库 `fa_wwh_column` 表字段(最根本:增删改栏目、改 isnav/status/weigh)→ ②控制器 [Base.php#L108-L125](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Base.php#L108-L125) `assign('nav')` 和 [Index.php#L108-L111](file:///c:/phpstudy_pro/WWW/test.io/addons/wwh/controller/Index.php#L108-L111) `assign('navCur')` 决定传什么数据 → ③模板 `{volist}{if}` 决定循环结构和高亮条件 → ④CSS `.active` 决定视觉表现 → ⑤JS 决定交互行为。每一层只控制自己该管的事,互不越界,这就是 MVC 分层架构的好处。

留言咨询

*
*
*
*
*
提交
0.098499s