mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 15:35:55 +08:00
feat: Nav support relative/absolute url
This commit is contained in:
parent
d18f9ece06
commit
d0f961f2ac
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit 743aa9c6fb21bbf3f88a9eec93b34c0ee8a4c4a6
|
||||
Subproject commit e75127ce05c1ad0b93ffff42d545bd21bc7971b7
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.configuration.portals;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.ServerListener;
|
||||
import com.rebuild.server.configuration.ConfigEntry;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 导航渲染
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2020/6/16
|
||||
*/
|
||||
public class NavBuilder extends NavManager {
|
||||
|
||||
public static final NavBuilder instance = new NavBuilder();
|
||||
private NavBuilder() { }
|
||||
|
||||
/**
|
||||
* 默认导航
|
||||
*/
|
||||
private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray(
|
||||
new String[] { "icon", "text", "type", "value" },
|
||||
new Object[][] {
|
||||
new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS },
|
||||
new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG }
|
||||
});
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public JSONArray getNavPortal(HttpServletRequest request) {
|
||||
return getNavPortal(AppUtils.getRequestUser(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public JSONArray getNavPortal(ID user) {
|
||||
ConfigEntry config = getLayoutOfNav(user);
|
||||
if (config == null) {
|
||||
return NAVS_DEFAULT;
|
||||
}
|
||||
|
||||
// 过滤
|
||||
JSONArray navs = (JSONArray) config.getJSON("config");
|
||||
for (Iterator<Object> iter = navs.iterator(); iter.hasNext(); ) {
|
||||
JSONObject nav = (JSONObject) iter.next();
|
||||
JSONArray subNavs = nav.getJSONArray("sub");
|
||||
|
||||
if (subNavs != null && !subNavs.isEmpty()) {
|
||||
for (Iterator<Object> subIter = subNavs.iterator(); subIter.hasNext(); ) {
|
||||
JSONObject subNav = (JSONObject) subIter.next();
|
||||
if (isFilterNav(subNav, user)) {
|
||||
subIter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 无子级,移除主菜单
|
||||
if (subNavs.isEmpty()) {
|
||||
iter.remove();
|
||||
}
|
||||
} else if (isFilterNav(nav, user)) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
return navs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要过滤掉
|
||||
*
|
||||
* @param nav
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
private boolean isFilterNav(JSONObject nav, ID user) {
|
||||
String type = nav.getString("type");
|
||||
if ("ENTITY".equalsIgnoreCase(type)) {
|
||||
String entity = nav.getString("value");
|
||||
if (NAV_PARENT.equals(entity)) {
|
||||
return true;
|
||||
} else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) {
|
||||
return false;
|
||||
} else if (!MetadataHelper.containsEntity(entity)) {
|
||||
LOG.warn("Unknow entity in nav : " + entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染导航菜單
|
||||
*
|
||||
* @param item
|
||||
* @param activeNav
|
||||
* @return
|
||||
*/
|
||||
public String renderNavItem(JSONObject item, String activeNav) {
|
||||
final boolean isUrlType = "URL".equals(item.getString("type"));
|
||||
String navName = item.getString("value");
|
||||
String navUrl = item.getString("value");
|
||||
|
||||
boolean isOutUrl = isUrlType && navUrl.startsWith("http");
|
||||
if (isUrlType) {
|
||||
navName = "nav_url-" + navName.hashCode();
|
||||
if (isOutUrl) {
|
||||
navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl);
|
||||
} else {
|
||||
navUrl = ServerListener.getContextPath() + navUrl;
|
||||
}
|
||||
} else if (NAV_FEEDS.equals(navName)) {
|
||||
navName = "nav_entity-Feeds";
|
||||
navUrl = ServerListener.getContextPath() + "/feeds/home";
|
||||
} else if (NAV_FILEMRG.equals(navName)) {
|
||||
navName = "nav_entity-Attachment";
|
||||
navUrl = ServerListener.getContextPath() + "/files/home";
|
||||
} else {
|
||||
navName = "nav_entity-" + navName;
|
||||
navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list";
|
||||
}
|
||||
|
||||
String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
|
||||
String navText = item.getString("text");
|
||||
|
||||
JSONArray subNavs = null;
|
||||
if (activeNav != null) {
|
||||
subNavs = item.getJSONArray("sub");
|
||||
if (subNavs == null || subNavs.isEmpty()) {
|
||||
subNavs = null;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder navHtml = new StringBuilder()
|
||||
.append(String.format("<li class=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon zmdi zmdi-%s\"></i><span>%s</span></a>",
|
||||
navName + (subNavs == null ? StringUtils.EMPTY : " parent"),
|
||||
subNavs == null ? navUrl : "###",
|
||||
isOutUrl ? "_blank" : "_self",
|
||||
navIcon,
|
||||
navText));
|
||||
if (subNavs != null) {
|
||||
StringBuilder subHtml = new StringBuilder()
|
||||
.append("<ul class=\"sub-menu\"><li class=\"title\">")
|
||||
.append(navText)
|
||||
.append("</li><li class=\"nav-items\"><div class=\"content\"><ul class=\"sub-menu-ul\">");
|
||||
|
||||
for (Object o : subNavs) {
|
||||
JSONObject subNav = (JSONObject) o;
|
||||
subHtml.append(renderNavItem(subNav, null));
|
||||
}
|
||||
subHtml.append("</ul></div></li></ul>");
|
||||
navHtml.append(subHtml);
|
||||
}
|
||||
navHtml.append("</li>");
|
||||
|
||||
if (activeNav != null) {
|
||||
Document navBody = Jsoup.parseBodyFragment(navHtml.toString());
|
||||
for (Element nav : navBody.select("." + activeNav)) {
|
||||
nav.addClass("active");
|
||||
if (activeNav.startsWith("nav_entity-")) {
|
||||
Element navParent = nav.parent();
|
||||
if (navParent != null && navParent.hasClass("sub-menu-ul")) {
|
||||
navParent.parent().parent().parent().parent().addClass("open active");
|
||||
}
|
||||
}
|
||||
}
|
||||
return navBody.selectFirst("li").outerHtml();
|
||||
}
|
||||
return navHtml.toString();
|
||||
}
|
||||
}
|
|
@ -7,26 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.server.configuration.portals;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.ServerListener;
|
||||
import com.rebuild.server.configuration.ConfigEntry;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -43,9 +28,11 @@ public class NavManager extends BaseLayoutManager {
|
|||
public static final String NAV_FEEDS = "$FEEDS$";
|
||||
// 文件
|
||||
public static final String NAV_FILEMRG = "$FILEMRG$";
|
||||
// 项目看板
|
||||
public static final String NAV_KANBANMRG = "$KANBANMRG$";
|
||||
|
||||
public static final NavManager instance = new NavManager();
|
||||
private NavManager() { }
|
||||
protected NavManager() { }
|
||||
|
||||
/**
|
||||
* @param user
|
||||
|
@ -79,160 +66,4 @@ public class NavManager extends BaseLayoutManager {
|
|||
}
|
||||
return array.toArray(new ID[0]);
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
/**
|
||||
* 默认导航
|
||||
*/
|
||||
private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray(
|
||||
new String[] { "icon", "text", "type", "value" },
|
||||
new Object[][] {
|
||||
new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS },
|
||||
new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG }
|
||||
});
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public JSONArray getNavForPortal(HttpServletRequest request) {
|
||||
return getNavForPortal(AppUtils.getRequestUser(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public JSONArray getNavForPortal(ID user) {
|
||||
ConfigEntry config = getLayoutOfNav(user);
|
||||
if (config == null) {
|
||||
return NAVS_DEFAULT;
|
||||
}
|
||||
|
||||
// 过滤
|
||||
JSONArray navs = (JSONArray) config.getJSON("config");
|
||||
for (Iterator<Object> iter = navs.iterator(); iter.hasNext(); ) {
|
||||
JSONObject nav = (JSONObject) iter.next();
|
||||
JSONArray subNavs = nav.getJSONArray("sub");
|
||||
|
||||
if (subNavs != null && !subNavs.isEmpty()) {
|
||||
for (Iterator<Object> subIter = subNavs.iterator(); subIter.hasNext(); ) {
|
||||
JSONObject subNav = (JSONObject) subIter.next();
|
||||
if (isFilterNav(subNav, user)) {
|
||||
subIter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 无子级,移除主菜单
|
||||
if (subNavs.isEmpty()) {
|
||||
iter.remove();
|
||||
}
|
||||
} else if (isFilterNav(nav, user)) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
return navs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要过滤掉
|
||||
*
|
||||
* @param nav
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
private boolean isFilterNav(JSONObject nav, ID user) {
|
||||
String type = nav.getString("type");
|
||||
if ("ENTITY".equalsIgnoreCase(type)) {
|
||||
String entity = nav.getString("value");
|
||||
if (NAV_PARENT.equals(entity)) {
|
||||
return true;
|
||||
} else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) {
|
||||
return false;
|
||||
} else if (!MetadataHelper.containsEntity(entity)) {
|
||||
LOG.warn("Unknow entity in nav : " + entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染导航菜單
|
||||
*
|
||||
* @param item
|
||||
* @param activeNav
|
||||
* @return
|
||||
*/
|
||||
public String renderNavItem(JSONObject item, String activeNav) {
|
||||
final boolean isUrlType = "URL".equals(item.getString("type"));
|
||||
String navName = item.getString("value");
|
||||
String navUrl = item.getString("value");
|
||||
|
||||
if (isUrlType) {
|
||||
navName = "nav_url-" + navName.hashCode();
|
||||
navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl);
|
||||
} else if (NAV_FEEDS.equals(navName)) {
|
||||
navName = "nav_entity-Feeds";
|
||||
navUrl = ServerListener.getContextPath() + "/feeds/home";
|
||||
} else if (NAV_FILEMRG.equals(navName)) {
|
||||
navName = "nav_entity-Attachment";
|
||||
navUrl = ServerListener.getContextPath() + "/files/home";
|
||||
} else {
|
||||
navName = "nav_entity-" + navName;
|
||||
navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list";
|
||||
}
|
||||
|
||||
String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
|
||||
String navText = item.getString("text");
|
||||
|
||||
JSONArray subNavs = null;
|
||||
if (activeNav != null) {
|
||||
subNavs = item.getJSONArray("sub");
|
||||
if (subNavs == null || subNavs.isEmpty()) {
|
||||
subNavs = null;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder navHtml = new StringBuilder()
|
||||
.append(String.format("<li class=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon zmdi zmdi-%s\"></i><span>%s</span></a>",
|
||||
navName + (subNavs == null ? StringUtils.EMPTY : " parent"),
|
||||
subNavs == null ? navUrl : "###",
|
||||
isUrlType ? "_blank" : "_self",
|
||||
navIcon,
|
||||
navText));
|
||||
if (subNavs != null) {
|
||||
StringBuilder subHtml = new StringBuilder()
|
||||
.append("<ul class=\"sub-menu\"><li class=\"title\">")
|
||||
.append(navText)
|
||||
.append("</li><li class=\"nav-items\"><div class=\"content\"><ul class=\"sub-menu-ul\">");
|
||||
|
||||
for (Object o : subNavs) {
|
||||
JSONObject subNav = (JSONObject) o;
|
||||
subHtml.append(renderNavItem(subNav, null));
|
||||
}
|
||||
subHtml.append("</ul></div></li></ul>");
|
||||
navHtml.append(subHtml);
|
||||
}
|
||||
navHtml.append("</li>");
|
||||
|
||||
if (activeNav != null) {
|
||||
Document navBody = Jsoup.parseBodyFragment(navHtml.toString());
|
||||
for (Element nav : navBody.select("." + activeNav)) {
|
||||
nav.addClass("active");
|
||||
if (activeNav.startsWith("nav_entity-")) {
|
||||
Element navParent = nav.parent();
|
||||
if (navParent != null && navParent.hasClass("sub-menu-ul")) {
|
||||
navParent.parent().parent().parent().parent().addClass("open active");
|
||||
}
|
||||
}
|
||||
}
|
||||
return navBody.selectFirst("li").outerHtml();
|
||||
}
|
||||
return navHtml.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="com.alibaba.fastjson.JSONObject"%>
|
||||
<%@ page import="com.alibaba.fastjson.JSONArray"%>
|
||||
<%@ page import="com.rebuild.server.configuration.portals.NavManager"%>
|
||||
<%@ page import="com.rebuild.utils.AppUtils" %>
|
||||
<%@ page import="com.rebuild.server.service.bizz.privileges.ZeroEntry" %>
|
||||
<%@ page import="com.rebuild.server.configuration.portals.NavBuilder" %>
|
||||
<%
|
||||
final String activeNav = request.getParameter("activeNav");
|
||||
final JSONArray navArray = NavManager.instance.getNavForPortal(request);
|
||||
final JSONArray navArray = NavBuilder.instance.getNavPortal(request);
|
||||
%>
|
||||
<div class="rb-left-sidebar">
|
||||
<div class="left-sidebar-wrapper">
|
||||
|
@ -16,14 +16,14 @@ final JSONArray navArray = NavManager.instance.getNavForPortal(request);
|
|||
<div class="left-sidebar-content no-divider">
|
||||
<ul class="sidebar-elements">
|
||||
<li class="<%="dashboard-home".equals(activeNav) ? "active" : ""%>"><a href="${baseUrl}/dashboard/home"><i class="icon zmdi zmdi-home"></i><span>首页</span></a></li>
|
||||
<% for (Object o : navArray) { out.print(NavManager.instance.renderNavItem((JSONObject) o, activeNav)); } %>
|
||||
<% for (Object o : navArray) out.print(NavBuilder.instance.renderNavItem((JSONObject) o, activeNav)); %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
|
||||
<div class="bottom-widget">
|
||||
<a class="nav-settings" href="javascript:;" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
|
||||
<a class="nav-settings" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
|
|
@ -45,7 +45,7 @@ $(document).ready(function () {
|
|||
value = $val('.J_menuUrl')
|
||||
if (!value) {
|
||||
RbHighbar.create('请输入 URL'); return
|
||||
} else if (!!value && !$regex.isUrl(value)) {
|
||||
} else if (!($regex.isUrl(value) || $regex.isUrl(`https://getrebuild.com${value}`))) {
|
||||
RbHighbar.create('请输入有效的 URL')
|
||||
return
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<select class="form-control form-control-sm J_menuEntity">
|
||||
<option value="">请选择关联项</option>
|
||||
<optgroup label="业务实体"></optgroup>
|
||||
<optgroup label="其他">
|
||||
<optgroup label="系统内建">
|
||||
<option value="$FEEDS$" data-icon="chart-donut">动态</option>
|
||||
<option value="$FILEMRG$" data-icon="folder">文件</option>
|
||||
<option value="$PARENT$" data-icon="menu">父级菜单</option>
|
||||
|
@ -72,6 +72,7 @@
|
|||
</div>
|
||||
<div class="tab-pane" id="URL">
|
||||
<input type="text" class="form-control form-control-sm J_menuUrl" placeholder="输入 URL">
|
||||
<div class="form-text">支持外部地址或相对地址</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,14 +19,14 @@
|
|||
<script src="${baseUrl}/assets/js/zmdi-icons.js"></script>
|
||||
<script type="text/babel">
|
||||
$(document).ready(function(){
|
||||
let call = parent.clickIcon || function(icon){ console.log(icon) }
|
||||
const call = parent && parent.clickIcon ? parent.clickIcon : function(icon){ console.log(icon) }
|
||||
$(ZMDI_ICONS).each(function(){
|
||||
if (ZMDI_ICONS_IGNORE.contains(this + '') == false) {
|
||||
let a = $('<a data-icon="' + this + '" title="' + this.toUpperCase() + '"><i class="zmdi zmdi-' + this + '"></a>').appendTo('#icons')
|
||||
a.click(function(){ call($(this).data('icon')) })
|
||||
const $a = $('<a data-icon="' + this + '" title="' + this.toUpperCase() + '"><i class="zmdi zmdi-' + this + '"></a>').appendTo('#icons')
|
||||
$a.click(function(){ call($(this).data('icon')) })
|
||||
}
|
||||
})
|
||||
parent.RbModal.resize()
|
||||
parent && parent.RbModal && parent.RbModal.resize()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -46,18 +46,18 @@ public class NavManagerTest extends TestSupport {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testPortalNav() throws Exception {
|
||||
public void testNavBuilder() throws Exception {
|
||||
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
|
||||
.get("/rebuild")
|
||||
.sessionAttr(WebUtils.CURRENT_USER, UserService.ADMIN_USER);
|
||||
HttpServletRequest request = builder.buildRequest(new MockServletContext());
|
||||
|
||||
JSON navForPortal = NavManager.instance.getNavForPortal(request);
|
||||
JSONArray navForPortal = NavBuilder.instance.getNavPortal(request);
|
||||
System.out.println("testPortalNav .......... \n" + navForPortal.toJSONString());
|
||||
|
||||
if (!((JSONArray) navForPortal).isEmpty()) {
|
||||
JSONObject firstNav = (JSONObject) ((JSONArray) navForPortal).get(0);
|
||||
String navHtml = NavManager.instance.renderNavItem(firstNav, "home");
|
||||
if (!navForPortal.isEmpty()) {
|
||||
JSONObject firstNav = (JSONObject) navForPortal.get(0);
|
||||
String navHtml = NavBuilder.instance.renderNavItem(firstNav, "home");
|
||||
System.out.println(navHtml);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue