大家是如何实现当前位置的??

发布于 2017-05-06 02:10:31 字数 6995 浏览 1347 评论 4

先看一下实际效果:

我的实现方式是:

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;
    }

上面的实现方式有几个缺陷:

  1. 由于不是使用 数字型,而是使用 文字型,导致在功能点命名上要求绝对不能有重复名称的。

  2. 如果API链接上要求带上动态参数,则无法实现。

  3. 功能点多的情况下,显得好庞杂的样子(是采用存入数据库表的方式,还是直接像我一样另起一个文件保存??)

不知道大家是怎样实现这个功能的呢??

因为这实际上跟架构有很大的关系,劳烦有经验的大牛,亮出你的 style3q


感谢: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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

浮生未歇 2017-05-06 02:10:34

我承认最终还是没有耐心看完 [微信捂脸]
这种需求整体的解决方案其实很简单, 可以前端做, 也可以后端渲染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
)

)

*/

夜无邪 2017-05-06 02:10:33

可能因为我是前端,这部分由前端做的比较多。

如果是SPA(当然问题中并不是),跟前端路由匹配。
如果不同的controller对应不同的layout的话,根据action区分显示就行。
如果没有layout这个概念,就根据url地址做正则匹配,或是根据controller和action来显示

浮生未歇 2017-05-06 02:10:31

你的实现方法很有趣,但并不实际。
首先你写的代码实在很难读,问问题又不写清楚,并不是所有人都有时间慢慢读你的代码来理解你的问题。或许可以先试着说明一下 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 的原生函数或许还不熟,加油!

针对追问

你们在定义 功能API(系统功能层级) 时,是采用存入数据库表的方式(增删功能点不是很方便),还是用一个文件存起来(方便快捷,就是庞杂,看起来心累)??

通常是存数据库的,这样才能在后台修改。改文件的话会有很多问题。

跑很多次回圈??指的是很多次循环吗??我看过好几遍了...没有多余的步骤啊....估计我眼睛蒙圈了,没发现....大写的 SOS。。。。。

意思是说,在 getCurPos 里有太多的 foreach 了,只是判断个链接,还需要把整个 $manu 遍历好多次,这样很没效率。

由于这些功能API定义的链接都是固定的,如果有些模块需要带上动态参数的,例如:活动管理喵喵抢购活动列表产品列表 中,产品列表 这个功能的链接中是需要带上参数的(活动ID),查看的是某个活动的产品列表,这个问题怎么破??

我想这个问题大部分都是交给 Framework 在处理的,依照你目前的架构也很难修改。建议你还是找点轻量级的 Framework 来研究一下。

泛泛之交 2017-05-06 02:10:31

太长了 你可以用url参数控制

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文