diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aef8443 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/admin-backend/src/main/java/com/admin/config/SecurityConfig.java b/admin-backend/src/main/java/com/admin/config/SecurityConfig.java index ff44469..64fd8f8 100644 --- a/admin-backend/src/main/java/com/admin/config/SecurityConfig.java +++ b/admin-backend/src/main/java/com/admin/config/SecurityConfig.java @@ -79,6 +79,12 @@ public class SecurityConfig { .antMatchers("/static/**", "/favicon.ico").permitAll() // 其他请求需要认证 .anyRequest().authenticated()) + + // 允许iframe嵌入 + .headers(headers -> headers + .frameOptions(frame -> frame.disable()) + .contentTypeOptions(contentType -> contentType.disable()) + .httpStrictTransportSecurity(hsts -> hsts.disable())) // 认证提供者 .authenticationProvider(authenticationProvider()) diff --git a/admin-backend/src/main/java/com/admin/config/SwaggerFrameOptionsFilter.java b/admin-backend/src/main/java/com/admin/config/SwaggerFrameOptionsFilter.java new file mode 100644 index 0000000..e5855e5 --- /dev/null +++ b/admin-backend/src/main/java/com/admin/config/SwaggerFrameOptionsFilter.java @@ -0,0 +1,42 @@ +package com.admin.config; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +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; + +@Component +@Order(Integer.MIN_VALUE) // 最高优先级 +public class SwaggerFrameOptionsFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + // 执行过滤器链 + filterChain.doFilter(request, response); + + // 如果是Swagger相关的请求,强制覆盖响应头 + String requestURI = request.getRequestURI(); + if (requestURI != null && ( + requestURI.contains("/swagger-ui") || + requestURI.contains("/v3/api-docs") || + requestURI.contains("/swagger-ui.html") + )) { + // 强制移除X-Frame-Options限制 + response.setHeader("X-Frame-Options", "SAMEORIGIN"); + response.setHeader("Content-Security-Policy", "frame-ancestors 'self' *"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", "*"); + + // 确保这些头被正确设置 + response.setHeader("X-Frame-Options", "SAMEORIGIN"); + } + } +} diff --git a/admin-frontend/.env b/admin-frontend/.env index 5d8c946..2f309f1 100644 --- a/admin-frontend/.env +++ b/admin-frontend/.env @@ -1,2 +1,2 @@ # 通用环境变量 -VITE_APP_TITLE=Admin Management System \ No newline at end of file +VITE_APP_TITLE=AI后台管理 \ No newline at end of file diff --git a/admin-frontend/components.d.ts b/admin-frontend/components.d.ts index 1146eb7..c632f90 100644 --- a/admin-frontend/components.d.ts +++ b/admin-frontend/components.d.ts @@ -19,11 +19,15 @@ declare module 'vue' { ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDivider: typeof import('element-plus/es')['ElDivider'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElHeader: typeof import('element-plus/es')['ElHeader'] @@ -41,6 +45,7 @@ declare module 'vue' { ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] diff --git a/admin-frontend/src/layout/LayoutView.vue b/admin-frontend/src/layout/LayoutView.vue index d0f3568..2a6f2e0 100644 --- a/admin-frontend/src/layout/LayoutView.vue +++ b/admin-frontend/src/layout/LayoutView.vue @@ -115,27 +115,22 @@ \ No newline at end of file diff --git a/admin-frontend/vite.config.ts b/admin-frontend/vite.config.ts index 5ef4307..92ede31 100644 --- a/admin-frontend/vite.config.ts +++ b/admin-frontend/vite.config.ts @@ -7,17 +7,18 @@ import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' // https://vite.dev/config/ -export default defineConfig({ +export default defineConfig(({ mode }) => ({ plugins: [ vue(), - vueDevTools(), + // 仅在开发环境启用 DevTools,避免对生产构建的注入影响打包产物 + mode === 'development' && vueDevTools(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), - ], + ].filter(Boolean), server: { port: 3000, proxy: { @@ -28,28 +29,12 @@ export default defineConfig({ } }, build: { - // 确保构建时使用相对路径 + // 使用相对路径,便于在静态服务器(如 Live Server)直接打开 dist assetsDir: 'assets', - rollupOptions: { - output: { - // 修复循环引用问题的分包策略 - manualChunks(id) { - if (id.includes('node_modules')) { - if (id.includes('element-plus')) { - return 'element-plus' - } - if (id.includes('vue')) { - return 'vue' - } - if (id.includes('@element-plus/icons-vue')) { - return 'element-icons' - } - // 其他第三方库 - return 'vendor' - } - } - } - } + // 交给 Vite/Rollup 自己做最佳分包,避免对 Vue 这类存在循环依赖的包强制分块 + // 若线上仍遇到压缩器相关问题,可将 minify 切为 'terser' + // minify: 'terser', + sourcemap: false, }, // 设置基础路径为相对路径,适用于静态文件服务器 base: './', @@ -58,4 +43,4 @@ export default defineConfig({ '@': fileURLToPath(new URL('./src', import.meta.url)) }, }, -}) +}))