diff --git a/app/client/src/components/Layout.css b/app/client/src/components/Layout.css index 7e1c721..5c7f1e4 100644 --- a/app/client/src/components/Layout.css +++ b/app/client/src/components/Layout.css @@ -75,7 +75,9 @@ @media (max-width: 900px) { .layoutRoot { flex-direction: column; - min-height: 100vh; + height: 100vh; + height: 100dvh; + overflow: hidden; } .primary-nav-container { @@ -91,16 +93,17 @@ .layout-right { width: 100%; - height: auto; + height: 100%; flex: 1; /* Add padding at bottom for fixed nav */ - padding-bottom: 56px; + padding-bottom: calc(50px + env(safe-area-inset-bottom, 0px)); + overflow: hidden; } .content-area { flex-direction: column; - height: auto; - overflow: visible; + height: 100%; + overflow: hidden; } /* Hide secondary sidebar on mobile - will be shown as popup */ @@ -109,7 +112,9 @@ } .main-content { - overflow: visible; + overflow-y: auto; + height: 100%; + -webkit-overflow-scrolling: touch; } /* Old sidebar compatibility */ diff --git a/app/client/src/components/Navigation/PrimaryNavigation.css b/app/client/src/components/Navigation/PrimaryNavigation.css index 1b143bf..9a761ea 100644 --- a/app/client/src/components/Navigation/PrimaryNavigation.css +++ b/app/client/src/components/Navigation/PrimaryNavigation.css @@ -136,6 +136,7 @@ backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px); border-top: 1px solid rgba(60, 60, 67, 0.18); + justify-content: space-evenly; } .primary-nav-logo { @@ -143,20 +144,13 @@ } .primary-nav-items { - flex: 1; - flex-direction: row; - align-items: stretch; - justify-content: space-evenly; - gap: 0; - overflow-x: visible; - padding: 0 4px; - height: 100%; + display: contents; } .primary-nav-item { flex: 0 1 auto; margin: 0; - padding: 5px 8px 4px; + padding: 5px 4px 4px; min-width: 44px; border-radius: 0; position: relative; @@ -229,7 +223,7 @@ display: flex; flex: 0 1 auto; margin: 0; - padding: 5px 8px 4px; + padding: 5px 4px 4px; min-width: 44px; flex-direction: column; align-items: center; diff --git a/app/server/huntly-server/src/main/java/com/huntly/server/config/WebResourceCacheConfig.java b/app/server/huntly-server/src/main/java/com/huntly/server/config/WebResourceCacheConfig.java new file mode 100644 index 0000000..8d0b535 --- /dev/null +++ b/app/server/huntly-server/src/main/java/com/huntly/server/config/WebResourceCacheConfig.java @@ -0,0 +1,58 @@ +package com.huntly.server.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +@Configuration +public class WebResourceCacheConfig { + + @Bean + public FilterRegistrationBean staticResourceCacheFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new OncePerRequestFilter() { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String path = request.getRequestURI(); + String cacheHeader = getCacheControlHeader(path); + if (cacheHeader != null) { + response.setHeader("Cache-Control", cacheHeader); + } + filterChain.doFilter(request, response); + } + }); + registration.addUrlPatterns("/*"); + registration.setOrder(1); + return registration; + } + + private String getCacheControlHeader(String path) { + // index.html 不缓存 + if (path.endsWith("/index.html") || path.equals("/")) { + return CacheControl.noCache().getHeaderValue(); + } + // 带 hash 的 js/css 文件长期缓存 + if (path.startsWith("/static/js/") || path.startsWith("/static/css/")) { + return CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic().getHeaderValue(); + } + // 媒体文件缓存1天 + if (path.startsWith("/static/media/")) { + return CacheControl.maxAge(1, TimeUnit.DAYS).cachePublic().getHeaderValue(); + } + // 根目录图片缓存1天 + if (path.matches("^/[^/]+\\.(png|jpg|jpeg|gif|webp|svg|ico)$")) { + return CacheControl.maxAge(1, TimeUnit.DAYS).cachePublic().getHeaderValue(); + } + return null; + } +} diff --git a/app/server/huntly-server/src/main/java/com/huntly/server/controller/ReactAppController.java b/app/server/huntly-server/src/main/java/com/huntly/server/controller/ReactAppController.java index 184f434..3d75796 100644 --- a/app/server/huntly-server/src/main/java/com/huntly/server/controller/ReactAppController.java +++ b/app/server/huntly-server/src/main/java/com/huntly/server/controller/ReactAppController.java @@ -12,6 +12,6 @@ public class ReactAppController { @RequestMapping(value = {"/", "/{x:[\\w\\-]+}", "/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}"}) public String getIndex() { - return "/index.html"; + return "forward:/index.html"; } } diff --git a/app/server/huntly-server/src/main/resources/application.yml b/app/server/huntly-server/src/main/resources/application.yml index 5d7642a..11c56df 100644 --- a/app/server/huntly-server/src/main/resources/application.yml +++ b/app/server/huntly-server/src/main/resources/application.yml @@ -16,11 +16,6 @@ spring: mvc: pathmatch: matching-strategy: ant_path_matcher # use this to make springfox work - web: - resources: - cache: - cachecontrol: - max-age: 7d huntly: jwtSecret: MTI2ZTc1NzAtMjJlMy00MmVlLTkwYmQtOTVjNGM4ZTRhN2YzMTI2ZTc1NzAtMjJlMy00MmVlLTkwYmQtOTVjNGM4ZTRhN2Yz