swagger嵌入
This commit is contained in:
parent
a3c423d5d9
commit
d59f99da21
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"liveServer.settings.port": 5501
|
||||||
|
}
|
||||||
|
|
@ -80,6 +80,12 @@ public class SecurityConfig {
|
||||||
// 其他请求需要认证
|
// 其他请求需要认证
|
||||||
.anyRequest().authenticated())
|
.anyRequest().authenticated())
|
||||||
|
|
||||||
|
// 允许iframe嵌入
|
||||||
|
.headers(headers -> headers
|
||||||
|
.frameOptions(frame -> frame.disable())
|
||||||
|
.contentTypeOptions(contentType -> contentType.disable())
|
||||||
|
.httpStrictTransportSecurity(hsts -> hsts.disable()))
|
||||||
|
|
||||||
// 认证提供者
|
// 认证提供者
|
||||||
.authenticationProvider(authenticationProvider())
|
.authenticationProvider(authenticationProvider())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
# 通用环境变量
|
# 通用环境变量
|
||||||
VITE_APP_TITLE=Admin Management System
|
VITE_APP_TITLE=AI后台管理
|
||||||
|
|
@ -19,11 +19,15 @@ declare module 'vue' {
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
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']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
|
|
@ -41,6 +45,7 @@ declare module 'vue' {
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
|
|
|
||||||
|
|
@ -115,27 +115,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TabsView from '@/components/TabsView.vue'
|
|
||||||
import DynamicMenu from '@/components/DynamicMenu.vue'
|
import DynamicMenu from '@/components/DynamicMenu.vue'
|
||||||
|
import TabsView from '@/components/TabsView.vue'
|
||||||
|
import { useMenuStore } from '@/stores/menu'
|
||||||
import { useTabsStore } from '@/stores/tabs'
|
import { useTabsStore } from '@/stores/tabs'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useMenuStore } from '@/stores/menu'
|
|
||||||
import {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
House,
|
House,
|
||||||
|
Loading,
|
||||||
Lock,
|
Lock,
|
||||||
Menu,
|
|
||||||
Setting,
|
|
||||||
SwitchButton,
|
SwitchButton,
|
||||||
User,
|
User
|
||||||
UserFilled,
|
|
||||||
Expand,
|
|
||||||
Fold,
|
|
||||||
Loading
|
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { computed, ref, watch, onMounted } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useTabsStore } from '@/stores/tabs'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
|
@ -94,7 +95,6 @@ router.beforeEach(async (to, from, next) => {
|
||||||
|
|
||||||
// 路由后置守卫 - 添加标签页
|
// 路由后置守卫 - 添加标签页
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
const { useTabsStore } = require('@/stores/tabs')
|
|
||||||
const tabsStore = useTabsStore()
|
const tabsStore = useTabsStore()
|
||||||
|
|
||||||
// 只有需要认证且不是隐藏页面的路由才添加标签页
|
// 只有需要认证且不是隐藏页面的路由才添加标签页
|
||||||
|
|
|
||||||
|
|
@ -5,48 +5,293 @@
|
||||||
<span>接口文档</span>
|
<span>接口文档</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="content">
|
<div class="swagger-content">
|
||||||
<p>API接口文档页面</p>
|
<div v-if="loading" class="loading-container">
|
||||||
<el-alert
|
<el-skeleton :rows="10" animated />
|
||||||
title="API文档"
|
</div>
|
||||||
description="您可以访问后端的Swagger文档查看完整的API接口信息"
|
|
||||||
type="success"
|
|
||||||
show-icon
|
|
||||||
:closable="false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="swagger-link" style="margin-top: 20px;">
|
<div v-else-if="error" class="error-container">
|
||||||
|
<el-alert
|
||||||
|
title="加载失败"
|
||||||
|
:description="error"
|
||||||
|
type="error"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
style="margin-top: 20px;"
|
||||||
@click="openSwagger"
|
@click="loadSwagger"
|
||||||
>
|
>
|
||||||
打开Swagger文档
|
重新加载
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="swagger-embedded-container">
|
||||||
|
<div class="swagger-header">
|
||||||
|
<h3>API接口文档</h3>
|
||||||
|
<div class="swagger-controls">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="refreshSwagger"
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
@click="openInNewTab"
|
||||||
|
>
|
||||||
|
新窗口打开
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="swagger-body">
|
||||||
|
<div
|
||||||
|
ref="swaggerContainer"
|
||||||
|
class="swagger-content-container"
|
||||||
|
v-html="swaggerContent"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 打开Swagger文档
|
import { nextTick, onMounted, ref } from 'vue'
|
||||||
const openSwagger = () => {
|
|
||||||
window.open('http://localhost:8080/swagger-ui.html', '_blank')
|
// 类型定义
|
||||||
|
interface SwaggerUIBundle {
|
||||||
|
(config: SwaggerConfig): unknown
|
||||||
|
presets: {
|
||||||
|
apis: unknown
|
||||||
|
}
|
||||||
|
plugins: {
|
||||||
|
DownloadUrl: unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SwaggerConfig {
|
||||||
|
url: string
|
||||||
|
dom_id: string
|
||||||
|
deepLinking: boolean
|
||||||
|
presets: unknown[]
|
||||||
|
plugins: unknown[]
|
||||||
|
layout: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref('')
|
||||||
|
const swaggerContent = ref('')
|
||||||
|
const swaggerContainer = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 加载Swagger UI
|
||||||
|
const loadSwagger = async () => {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 直接获取Swagger UI的HTML内容
|
||||||
|
const response = await fetch('http://localhost:8080/api/swagger-ui/index.html')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = await response.text()
|
||||||
|
|
||||||
|
// 移除head标签,因为我们不需要完整的HTML结构
|
||||||
|
html = html.replace(/<head>[\s\S]*?<\/head>/gi, '')
|
||||||
|
|
||||||
|
// 移除body标签,只保留内容
|
||||||
|
html = html.replace(/<body[^>]*>/, '').replace(/<\/body>/, '')
|
||||||
|
|
||||||
|
// 移除html标签
|
||||||
|
html = html.replace(/<html[^>]*>/, '').replace(/<\/html>/, '')
|
||||||
|
|
||||||
|
// 修改CSS和JS路径为绝对路径
|
||||||
|
html = html.replace(/href="\.\//g, 'href="http://localhost:8080/api/swagger-ui/')
|
||||||
|
html = html.replace(/src="\.\//g, 'src="http://localhost:8080/api/swagger-ui/')
|
||||||
|
|
||||||
|
swaggerContent.value = html
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
// 等待DOM更新后初始化Swagger UI
|
||||||
|
await nextTick()
|
||||||
|
initializeSwaggerUI()
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载Swagger UI失败:', err)
|
||||||
|
error.value = '无法连接到后端服务,请确保后端服务正在运行在 http://localhost:8080'
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Swagger UI
|
||||||
|
const initializeSwaggerUI = () => {
|
||||||
|
if (!swaggerContainer.value) return
|
||||||
|
|
||||||
|
// 动态加载Swagger UI的CSS
|
||||||
|
const link = document.createElement('link')
|
||||||
|
link.rel = 'stylesheet'
|
||||||
|
link.type = 'text/css'
|
||||||
|
link.href = 'http://localhost:8080/api/swagger-ui/swagger-ui.css'
|
||||||
|
document.head.appendChild(link)
|
||||||
|
|
||||||
|
// 动态加载Swagger UI的JS
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = 'http://localhost:8080/api/swagger-ui/swagger-ui-bundle.js'
|
||||||
|
script.onload = () => {
|
||||||
|
const standaloneScript = document.createElement('script')
|
||||||
|
standaloneScript.src = 'http://localhost:8080/api/swagger-ui/swagger-ui-standalone-preset.js'
|
||||||
|
standaloneScript.onload = () => {
|
||||||
|
// 初始化Swagger UI
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const SwaggerUIBundle = (window as any).SwaggerUIBundle as SwaggerUIBundle
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const SwaggerUIStandalonePreset = (window as any).SwaggerUIStandalonePreset
|
||||||
|
|
||||||
|
if (SwaggerUIBundle) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(window as any).ui = SwaggerUIBundle({
|
||||||
|
url: 'http://localhost:8080/api/v3/api-docs',
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.head.appendChild(standaloneScript)
|
||||||
|
}
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新Swagger UI
|
||||||
|
const refreshSwagger = () => {
|
||||||
|
loadSwagger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在新窗口中打开完整Swagger UI
|
||||||
|
const openInNewTab = () => {
|
||||||
|
window.open('http://localhost:8080/api/swagger-ui/index.html', '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载
|
||||||
|
onMounted(() => {
|
||||||
|
loadSwagger()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.swagger-container {
|
.swagger-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-container :deep(.el-card) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-container :deep(.el-card__body) {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-content {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
padding: 40px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-embedded-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fafafa;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-content-container {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.swagger-content-container :deep(#swagger-ui) {
|
||||||
text-align: center;
|
height: 100%;
|
||||||
padding: 50px 0;
|
min-height: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swagger-link {
|
/* 自定义滚动条 */
|
||||||
margin-top: 30px;
|
.swagger-content-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-content-container::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-content-container::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-content-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -7,17 +7,18 @@ import Components from 'unplugin-vue-components/vite'
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => ({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
vueDevTools(),
|
// 仅在开发环境启用 DevTools,避免对生产构建的注入影响打包产物
|
||||||
|
mode === 'development' && vueDevTools(),
|
||||||
AutoImport({
|
AutoImport({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [ElementPlusResolver()],
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [ElementPlusResolver()],
|
||||||
}),
|
}),
|
||||||
],
|
].filter(Boolean),
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|
@ -28,28 +29,12 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
// 确保构建时使用相对路径
|
// 使用相对路径,便于在静态服务器(如 Live Server)直接打开 dist
|
||||||
assetsDir: 'assets',
|
assetsDir: 'assets',
|
||||||
rollupOptions: {
|
// 交给 Vite/Rollup 自己做最佳分包,避免对 Vue 这类存在循环依赖的包强制分块
|
||||||
output: {
|
// 若线上仍遇到压缩器相关问题,可将 minify 切为 'terser'
|
||||||
// 修复循环引用问题的分包策略
|
// minify: 'terser',
|
||||||
manualChunks(id) {
|
sourcemap: false,
|
||||||
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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// 设置基础路径为相对路径,适用于静态文件服务器
|
// 设置基础路径为相对路径,适用于静态文件服务器
|
||||||
base: './',
|
base: './',
|
||||||
|
|
@ -58,4 +43,4 @@ export default defineConfig({
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue