大家是如何实现当前位置的??
先看一下实际效果:
我的实现方式是:
1. 功能点API
2. 具体实现
3. 相关实现代码
/*
* 解析 URL 成菜单表中能够识别的 模块 + 子模块(或动作名称)名称
*/
public function parseUrlToEazyName(){
$controller = $GLOBALS['controller'];
$action = $GLOBALS['action'];
$result = [];
$names = [];
$part = function($str = '' , $s_idx = 0) use (&$result , &$part , &$count){
// 首字母小写:避免第一个大写,被单独取出
$search = preg_match('/[A-Z]/' , $str , $rel , PREG_OFFSET_CAPTURE , $s_idx + 1);
$str_len = mb_strlen($str);
if ($search === 0) {
$e_idx = $str_len;
} else {
$e_idx = $rel[0][1];
}
$result[] = mb_substring($str , $s_idx , $e_idx);
if ($str_len !== $e_idx) {
$part($str , $e_idx);
}
return $result;
};
// 控制器
$part($controller , 0);
$names['controller'] = strtolower(implode('_' , $result));
// 动作
$result = [];
$part($action , 0);
$names['action'] = strtolower(implode('_' , $result));
return $names;
}
// 当前位置
public function getCurPos(){
$names = $this->parseUrlToEazyName();
$action = $names['action'];
$data = [];
$parent_list = [];
// 找到当前元素所在 单元
$find = function($name = '' , Array $list = []){
$result = false;
$do = function(Array $list = []) use(&$name , &$result , &$do){
foreach ($list as $v)
{
if (!empty($v)) {
if ($v['name'] === $name) {
$result = $v;
return ;
} else {
if ($v['has_child']) {
$do($v['child']);
}
}
}
}
};
$do($list);
return $result;
};
// 通过 底层项 找到整个链条
$findParentTree = function(Array $item = [] , Array $list = []) use(&$data , &$find , &$parent_list){
$data = $item;
$parent = $item['parent'];
$parent_list[] = $item['name'];
if (!empty($parent)) {
$parent_list[] = $parent;
}
// 由于是最底层的项,所以即使其有子项,也必须清空,但是 has_child 不修改。
$data['child'] = [];
$result = [];
while ($parent)
{
$result = $find($parent , $list);
if (!empty($result)) {
$result['has_child'] = true;
$result['child'] = $data;
$data = $result;
$item = $data;
$parent = $data['parent'];
$parent_list[] = $parent;
}
}
return $data;
};
// 通过给定的菜单名称,找到其所有的子元素,如果没有的话,返回空数组
$findTopModuleNextTree = function($name = '' , Array $list = []){
foreach ($list as $v)
{
if ($v['name'] === $name) {
return $v['child'];
}
}
return false;
};
// 生成 HTML
$genHTML = function(Array $list = []){
$html = '';
$gen = function(Array $item = []) use(&$html , &$gen){
$html .= "<a href='" . ($item['is_link'] ? $item['link'] : 'javascript:void(0);') . "' class='" . ($item['is_link'] ? 'pos_link' : 'not_link') . "'>" . $item['cn_explain'] . "</a>/";
if (!empty($item['child'])) {
$gen($item['child']);
}
};
$gen($list);
return $html;
};
$result = $find($action , $GLOBALS['menu']);
if ($result === false) {
throw new Exception('菜单中找不到当前动作路径');
}
$result = $findParentTree($result , $GLOBALS['menu']);
// 将当前选中的顶级模块赋值进模板
$this->assign('top_module' , $result);
$top_module_next_tree = $findTopModuleNextTree($result['name'] , $GLOBALS['menu']);
// 当前模块的对应的二级菜单模块
foreach ($parent_list as $v)
{
foreach ($top_module_next_tree as $v1)
{
if (!empty($v1)) {
if (isset($v1['name']) && $v === $v1['name']) {
// 将顶级模块的二级模块赋值进模板
$this->assign('c_menu' , $v);
break;
}
}
}
}
$html = $genHTML($result);
$lc = mb_substr($html , mb_strlen($html) - 1);
if ($lc === '/') {
$html = mb_substr($html , 0 , -1);
}
return $html;
}
上面的实现方式有几个缺陷:
由于不是使用 数字型,而是使用 文字型,导致在功能点命名上要求绝对不能有重复名称的。
如果
API
链接上要求带上动态参数,则无法实现。功能点多的情况下,显得好庞杂的样子(是采用存入数据库表的方式,还是直接像我一样另起一个文件保存??)
不知道大家是怎样实现这个功能的呢??
因为这实际上跟架构有很大的关系,劳烦有经验的大牛,亮出你的 style
,3q
感谢:BinotaLIU 的回答,补充说明下函数的相关作用...
1. parseUrlToEazyName 函数的作用:
由于我的 URL模式
是 PATHINFO
的,所以,他的表现形式是:index.php/Module/Controller/Action
,具体例子是:index.php/ControlPannel/rootIndex
,表示执行的是 ControlPannel类的rootIndex方法
,但需要实现 当前位置 的这个功能的时候,大家也看到了我的 menu.php
中的内容,他里面的命名方法是 control_pannel 或 root_index
这种的,所以,为了在 menu.php
中找到对应的项,需要对路径做一个解析。这就是 parseUrlToEazyName
的作用了。
2. getCurPos 函数的作用:
,他有多个细化的功能点:
1. $find 函数,从 menu.php 中查找到当前提供功能对应的项。
2. $findParentTree 函数,有两个作用:返回当前功能所在链条的顶级项($top_module) + 生成当前功能的所有父级项($parent_list)
3. $findTopModuleNextTree 函数,实际上就是获取当前项的所有子项
4. $genHTML ,生成的是当前位置的HTML:`活动管理喵喵抢购活动列表产品列表`
$findParentTree 他的具体功效是,其一,是无论你深入到一个模块的哪一个层级,顶级模块始终会展开(返回$top_module):
$findParentTree 他的具体功效是,其二,由于左边的菜单属于二级菜单(不能无限极分类),所以需要找到顶级模块的下一级对应模块,我想到办法是,找到底层项的所有父级项($parent_list),找到顶级项的所有子项,在在子项看存在哪个父项(绝对只有一个存在),那个就是对应的二级菜单项了,选中:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我承认最终还是没有耐心看完 [微信捂脸]
这种需求整体的解决方案其实很简单, 可以前端做, 也可以后端渲染html输出, 提供一个后端的思路(不见得是最方便的, 但比你罗列的这堆应该简单多了):
设计一个良好的URL规范
维护一个数组用来映射URL和名称的映射, URL作为Key, 值中包含中文菜单名称
根据URL, 然后解析这个数组来得到URL和名称的对应关系值
利用框架的middleware / event等功能注入页面 (具体实现取决于用什么框架)
以下代码展示2,3的点中提到的部分
<?php
$urlNameMap = [
'hello' => [
'name' => '你好',
'subItems' => [
'cat' => [
'name' => '喵星人',
'subItems' => [
'create' => [
'name' => '创建喵星人'
],
'find' => [
'name' => '查找喵星人'
]
]
],
'dog' => [
'name' => '汪星人',
'subItems' => [
'find' => [
'name' => '查找汪星人'
]
]
]
]
]
];
function parseUrl($inputUrl)
{
global $urlNameMap; // 示例代码, 所以图省事这里用了全局变量, 实际生产强烈不建议采用全局变量, 建议优化成读取配置文件等形式
$parsedResult = parse_url($inputUrl);
$path = isset($parsedResult['path']) ? trim($parsedResult['path'], '/') : '';
$query = isset($parsedResult['query']) ? '?' . $parsedResult['query'] : '';
$pathInArray = explode('/', $path);
$partCount = count($pathInArray);
$result = [];
$urlPrefix = '/';
foreach ($pathInArray as $key => $item) {
if (isset($urlNameMap[$item])) {
$urlNameMapNode = $urlNameMap[$item];
if ($key == $partCount - 1) {
// 最后一个项目, 所以要考虑queryString
$url = $urlPrefix . $item . $query;
} else {
// 根据具体情况修改这部分的逻辑
$url = $urlPrefix . $item . '/index';
}
$result[] = [
'name' => $urlNameMapNode['name'],
'url' => $url
];
$urlPrefix .= $item . '/';
if (isset($urlNameMapNode['subItems'])) {
$urlNameMap = $urlNameMapNode['subItems'];
}
}
}
return $result;
}
print_r(parseUrl('/hello/cat/find?query=1234'));
/**
这里输出
Array
(
[0] => Array
(
[name] => 你好
[url] => /hello/index
)
[1] => Array
(
[name] => 喵星人
[url] => /hello/cat/index
)
[2] => Array
(
[name] => 查找喵星人
[url] => /hello/cat/find?query=1234
)
)
*/
可能因为我是前端,这部分由前端做的比较多。
如果是SPA(当然问题中并不是),跟前端路由匹配。
如果不同的controller对应不同的layout的话,根据action区分显示就行。
如果没有layout这个概念,就根据url地址做正则匹配,或是根据controller和action来显示
你的实现方法很有趣,但并不实际。
首先你写的代码实在很难读,问问题又不写清楚,并不是所有人都有时间慢慢读你的代码来理解你的问题。或许可以先试着说明一下 1. 自己的代码做了哪些事情,2. 目前实现了哪些功能,3. 期望改进或增加什么功能
首先先来针对你提出的两个函数提出一些个人的想法,接着再说点整体的点评吧!
parseUrlToEazyName
想了想,
$GLOBALS['controller']
应该是一组小驼峰法的字符串。既然都用正则来处理了,是否该用更有效率的方法呢?public function parseUrlToEazyName()
{
$converCamelStr = function ($str) {
//若首字母大写会被转成 _ 开头的字符串,这时候 ltrim 可以清除开头的 _
return ltrim(strtolower(preg_replace('/([A-Z])/', '_$1', $str)), '_');
};
$names = [];
$names['controller'] = $convertCamelStr($GLOBALS['controller']);
$names['action'] = $convertCamelStr($GLOBALS['action']);
return $names;
}
getCurPos
你写的实在太复杂了,注解又不够多,看了好多遍还是不懂你在做什么。你自己解释一下你的代码实现了什么。
我想这里出现了几个问题:
你是通过
parent
这项的字符串来找出父级元素的,这样其实很危险,因为若要修改父级元素的name
,就必须把所有子项的parent
都改掉,既然都使用了巢状结构的 array,其实并不需要再特地表示出parent
,因为这并不是一个 RMDB 的数据库。$findParentTree()
当中,如果while($parent)
中的$result
为空会怎么样呢?=> 没做例外处理你不觉得你的代码会跑很多次回圈吗?
整体点评
分工做的并不好,我的意思是说,你或许需要了解一下 SOLID 原则。各个方法间的耦合度太高了。
类似下列的代码:
foreach ($list as $i) {
if (!empty($i)) {
// 做点复杂的事情
}
}
其实是可以改成这样的:
foreach ($list as $i) {
if (empty($i)) continue;
// 做点复杂的事情
}
如此一来就可以避免一个 if 的 block 太过肥大,也可以避免最后出现太多层的花括号。
对 PHP 的原生函数或许还不熟,加油!
针对追问
通常是存数据库的,这样才能在后台修改。改文件的话会有很多问题。
意思是说,在 getCurPos 里有太多的 foreach 了,只是判断个链接,还需要把整个 $manu 遍历好多次,这样很没效率。
我想这个问题大部分都是交给 Framework 在处理的,依照你目前的架构也很难修改。建议你还是找点轻量级的 Framework 来研究一下。
太长了 你可以用url参数控制