如何用Jinja和Flask实现Scope数据库的树形展示?
解决方案:分两步实现树形权限展示
这问题我太熟了!树形结构的权限层级展示,核心就是先把数据库里的扁平数据转成嵌套树形,再用Jinja的递归宏来渲染就行,分两步给你捋清楚:
第一步:后端把扁平数据转成嵌套树形结构
你现在数据库里的条目是扁平的,每个节点只存父ID和子ID,得先把这些数据整成嵌套的树形结构,方便模板渲染。写个Python函数就能搞定:
def build_scope_tree(root_id, all_scopes): # 先把所有scope用ID做键存成字典,方便快速查找 scope_map = {scope.id: scope for scope in all_scopes} # 递归构建单个节点的子树 def build_node(node_id): node = scope_map.get(node_id) if not node: return None # 组装当前节点的结构,包含ID和子节点列表 tree_node = { 'id': node.id, 'children': [] } # 如果有子节点,递归构建每个子节点的树 if node.children_id: # 注意过滤掉None的情况,避免报错 for child_id in filter(None, node.children_id): child_tree = build_node(child_id) if child_tree: tree_node['children'].append(child_tree) return tree_node # 从指定的根ID开始构建整棵树 return build_node(root_id)
调用的时候也很简单,比如要展示admin的全量权限树:
# 先把数据库里的所有scope取出来 all_scopes = list(UserScope.scan()) # 去重!注意你数据库里有重复的user-1条目,记得先处理 unique_scopes = {s.id: s for s in all_scopes}.values() # 构建树形结构 root_tree = build_scope_tree('admin', unique_scopes) # 把root_tree传给Jinja模板
如果传的是user-1,返回的树形结构就只有它自己,完美符合你的需求。
第二步:用Jinja递归宏渲染树形结构
Jinja支持递归宏,刚好适合处理这种不确定层级的树形结构。直接写个宏就能搞定:
先在模板里定义递归宏(比如单独写个macros/scope_tree.html,或者直接写在主模板里):
{% macro render_tree(node, indent_level=0) %} <div class="scope-item" style="margin-left: {{ indent_level * 25 }}px;"> {# 加粗当前节点ID,突出层级 #} <strong>{{ node.id }}</strong> {% if node.children %} {# 如果有子节点,递归渲染每个子节点,层级+1 #} {% for child in node.children %} {{ render_tree(child, indent_level + 1) }} {% endfor %} {% endif %} </div> {% endmacro %}
然后在主模板里调用这个宏:
{% from "macros/scope_tree.html" import render_tree %} {% if root_tree %} <h3>权限层级展示</h3> {{ render_tree(root_tree) }} {% else %} <p>未找到对应的权限节点</p> {% endif %}
优化展示效果(可选)
如果想要更美观的交互,比如折叠/展开子节点,可以加一点CSS和JS:
改进后的宏(带折叠按钮)
{% macro render_tree(node, indent_level=0) %} <div class="scope-node" style="margin-left: {{ indent_level * 25 }}px; margin: 6px 0;"> <span class="node-title"> <strong>{{ node.id }}</strong> {% if node.children %} <span class="toggle-btn" style="cursor: pointer; margin-left: 8px;">[+]</span> {% endif %} </span> {% if node.children %} <div class="child-nodes" style="display: none;"> {% for child in node.children %} {{ render_tree(child, indent_level + 1) }} {% endfor %} </div> {% endif %} </div> {% endmacro %}
加一点CSS
.scope-node { padding: 2px 0; } .child-nodes { margin-top: 4px; } .toggle-btn { color: #007bff; }
加简单的JS实现折叠
document.querySelectorAll('.toggle-btn').forEach(btn => { btn.addEventListener('click', () => { const childNodes = btn.parentElement.nextElementSibling; const isOpen = childNodes.style.display !== 'none'; childNodes.style.display = isOpen ? 'none' : 'block'; btn.textContent = isOpen ? '[+]' : '[-]'; }); });
这样用户就能点击[+]/[-]来展开或折叠子节点,体验会好很多。
额外注意事项
- 去重处理:你提供的数据库输出里有重复的
user-1条目,记得在构建树形前先去重,避免重复渲染; - 空值处理:如果
children_id是None,记得在函数里过滤掉,避免遍历None报错; - 扩展字段:如果需要展示更多节点信息(比如权限详情),可以在
build_node函数里把额外字段(比如权限列表)加入tree_node,然后在模板里渲染。
这样一套流程下来,不管你传入的是根节点admin还是叶子节点user-1,都能清晰展示对应的权限层级结构啦!
内容的提问来源于stack exchange,提问作者juleslasne
相关产品推荐
相关产品推荐

