增加了详情页缓存的功能

master
toesbieya 6 years ago
parent 9b0428468b
commit f754005959
  1. 1
      vue/src/components/Charts/PieChart.vue
  2. 99
      vue/src/layout/components/KeepAlive.js
  3. 7
      vue/src/layout/components/Main.vue
  4. 11
      vue/src/layout/components/Sidebar/index.vue
  5. 35
      vue/src/layout/components/TagsView/index.vue
  6. 12
      vue/src/layout/components/TagsView/mixin/decideRouterTransition.js
  7. 10
      vue/src/layout/components/TagsView/mixin/shortcuts.js
  8. 6
      vue/src/main.js
  9. 2
      vue/src/mixins/bizDocumentTableMixin.js
  10. 4
      vue/src/mixins/chart/logic.js
  11. 2
      vue/src/router/constant/modules/app.js
  12. 12
      vue/src/router/constant/modules/example.js
  13. 13
      vue/src/router/index.js
  14. 4
      vue/src/router/util.js
  15. 48
      vue/src/store/modules/tagsView.js
  16. 3
      vue/src/views/app/redirect.vue
  17. 17
      vue/src/views/example/detailPage.vue
  18. 15
      vue/src/views/system/role/index.vue
  19. 49
      vue/vue.config.js

@ -17,6 +17,7 @@
data: { data: {
deep: true, deep: true,
handler(val) { handler(val) {
if (!window.echarts) return
this.init(val) this.init(val)
} }
} }

@ -0,0 +1,99 @@
import {isEmpty} from "@/utils"
const KEY = '_routerViewKey'
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (const c of children) {
if (!isEmpty(c) && (!isEmpty(c.componentOptions) || c.isComment && c.asyncFactory)) {
return c
}
}
}
}
function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
function pruneCacheEntry(cache, key, current) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance && cached.componentInstance.$destroy()
}
delete cache[key]
}
function getCacheKey(route, componentOptions) {
if (KEY in componentOptions) return componentOptions[KEY]
const {path, meta} = route
const key = meta && meta.isDetailPage ? path : getComponentName(componentOptions)
componentOptions[KEY] = key
return key
}
export default {
name: 'keep-router-view-alive',
abstract: true,
props: {include: Array},
watch: {
include: {
deep: true,
handler(val) {
const {cache, $route, _vnode} = this
for (const key of Object.keys(cache)) {
const cachedNode = cache[key]
const name = getCacheKey($route, cachedNode.componentOptions)
if (name && !val.includes(name)) {
pruneCacheEntry(cache, key, _vnode)
}
}
}
}
},
created() {
this.cache = Object.create(null)
},
beforeDestroy() {
for (const key of Object.keys(this.cache)) {
pruneCacheEntry(this.cache, key)
}
},
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
let componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
//忽略<transition/>组件
const child = componentOptions.tag === 'transition' ? componentOptions.children[0] : vnode
componentOptions = child && child.componentOptions
if (componentOptions) {
const key = getCacheKey(this.$route, componentOptions)
const {include, cache} = this
if (include && !include.includes(key)) {
return vnode
}
if (cache[key]) {
child.componentInstance = cache[key].componentInstance
}
else cache[key] = child
child.data.keepAlive = true
}
}
return vnode || (slot && slot[0])
}
}

@ -2,11 +2,11 @@
<main class="app-main"> <main class="app-main">
<el-scrollbar ref="scrollbar" v-show="!showIframe" class="scroll-container"> <el-scrollbar ref="scrollbar" v-show="!showIframe" class="scroll-container">
<div class="page-view"> <div class="page-view">
<keep-router-view-alive :include="cachedViews">
<transition :name="transitionName" mode="out-in"> <transition :name="transitionName" mode="out-in">
<keep-alive :include="cachedViews">
<router-view/> <router-view/>
</keep-alive>
</transition> </transition>
</keep-router-view-alive>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -29,11 +29,14 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import {mapState} from 'vuex' import {mapState} from 'vuex'
import KeepRouterViewAlive from "./KeepAlive"
import Footer from "./Footer" import Footer from "./Footer"
export default { export default {
name: 'AppMain', name: 'AppMain',
components: {KeepRouterViewAlive},
computed: { computed: {
...mapState('app', { ...mapState('app', {
scrollTop: state => state.scrollTop, scrollTop: state => state.scrollTop,

@ -32,6 +32,15 @@
sidebarAutoHidden: state => state.sidebarAutoHidden sidebarAutoHidden: state => state.sidebarAutoHidden
}), }),
activeMenu() {
const route = this.$route
const {meta, path} = route
if (path.startsWith('/redirect')) {
return path.replace('/redirect', '')
}
return meta && meta.activeMenu ? meta.activeMenu : path
},
//pc //pc
collapse() { collapse() {
return this.sidebarCollapse && this.device === 'pc' return this.sidebarCollapse && this.device === 'pc'
@ -116,7 +125,7 @@
background-color={variables['menu-background']} background-color={variables['menu-background']}
collapse={this.collapse} collapse={this.collapse}
collapse-transition={false} collapse-transition={false}
default-active={this.$route.path} default-active={this.activeMenu}
unique-opened={this.sidebarUniqueOpen} unique-opened={this.sidebarUniqueOpen}
mode="vertical" mode="vertical"
on-select={this.select} on-select={this.select}

@ -9,17 +9,17 @@
class="tags-view-item" class="tags-view-item"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="div" tag="div"
@contextmenu.prevent.native="openMenu(tag,$event)" @contextmenu.prevent.native="e=>openMenu(tag,e)"
@dblclick.prevent.native="closeSelectedTag(tag)" @dblclick.prevent.native="()=>closeSelectedTag(tag)"
> >
{{ tag.title }} {{ tag.title }}
<i v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/> <i v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="()=>closeSelectedTag(tag)"/>
</router-link> </router-link>
</scroll-pane> </scroll-pane>
<context-menu v-model="contextmenu.show" :left="contextmenu.left" :top="contextmenu.top"> <context-menu v-model="contextmenu.show" :left="contextmenu.left" :top="contextmenu.top">
<context-menu-item @click="refreshSelectedTag(selectedTag)">刷新</context-menu-item> <context-menu-item @click="refreshSelectedTag(selectedTag)">刷新</context-menu-item>
<context-menu-item v-show="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"> <context-menu-item v-show="!isAffix(selectedTag)" @click="()=>closeSelectedTag(selectedTag)">
关闭 关闭
</context-menu-item> </context-menu-item>
<context-menu-item @click="closeOthersTags">关闭其他</context-menu-item> <context-menu-item @click="closeOthersTags">关闭其他</context-menu-item>
@ -32,11 +32,11 @@
import ScrollPane from './ScrollPane' import ScrollPane from './ScrollPane'
import ContextMenu from "@/components/ContextMenu" import ContextMenu from "@/components/ContextMenu"
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem" import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
import shortcutsMixin from './mixin/shortcutsMixin' import shortcuts from './mixin/shortcuts'
import decideRouterTransitionMixin from './mixin/decideRouterTransition' import decideRouterTransition from './mixin/decideRouterTransition'
export default { export default {
mixins: [shortcutsMixin, decideRouterTransitionMixin], mixins: [shortcuts, decideRouterTransition],
components: {ContextMenu, ContextMenuItem, ScrollPane}, components: {ContextMenu, ContextMenuItem, ScrollPane},
@ -66,8 +66,7 @@
watch: { watch: {
$route(to, from) { $route(to, from) {
this.decideRouteTransition && this.decideRouteTransition(to, from) this.decideRouteTransition && this.decideRouteTransition(to, from)
this.addTags(to) this.addTags(to).then(this.moveToCurrentTag)
this.moveToCurrentTag()
}, },
'contextmenu.show'(v) { 'contextmenu.show'(v) {
@ -84,7 +83,7 @@
}, },
filterAffixTags(routes) { filterAffixTags(routes) {
let tags = [] const tags = []
routes.forEach(route => { routes.forEach(route => {
if (route.name && route.meta && route.meta.affix) { if (route.name && route.meta && route.meta.affix) {
tags.push({ tags.push({
@ -110,20 +109,16 @@
//nametab //nametab
addTags(to = this.$route) { addTags(to = this.$route) {
if (!to.name) return return to.name ? this.$store.dispatch('tagsView/addView', to) : Promise.resolve()
return this.$store.dispatch('tagsView/addView', this.$route)
}, },
//tab //tab
moveToCurrentTag() { moveToCurrentTag() {
this.$nextTick(() => { this.$nextTick(() => {
let tag = this.$refs.tag.find(i => i.to.path === this.$route.path) const tag = this.$refs.tag.find(i => i.to.path === this.$route.path)
if (!tag) return if (!tag) return
this.$refs.scrollPane.moveToTarget(tag) this.$refs.scrollPane.moveToTarget(tag)
//
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.commit('tagsView/updateVisitedViews', this.$route)
}
}) })
}, },
@ -138,7 +133,7 @@
closeSelectedTag(view) { closeSelectedTag(view) {
if (this.isAffix(view)) return if (this.isAffix(view)) return
this.$store.dispatch('tagsView/delView', view) this.$store.dispatch('tagsView/delView', view)
.then(() => this.isActive(view) && this.gotoViewLast()) .then(() => this.isActive(view) && this.gotoLastView())
}, },
closeOthersTags() { closeOthersTags() {
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag) this.$store.dispatch('tagsView/delOthersViews', this.selectedTag)
@ -150,10 +145,10 @@
}, },
closeAllTags() { closeAllTags() {
this.$store.dispatch('tagsView/delAllViews') this.$store.dispatch('tagsView/delAllViews')
.then(() => this.gotoViewLast()) .then(() => this.gotoLastView())
}, },
gotoViewLast() { gotoLastView() {
if (this.visitedViews.length === 0) return this.$router.push('/') if (this.visitedViews.length === 0) return this.$router.push('/')
const latest = this.visitedViews[this.visitedViews.length - 1] const latest = this.visitedViews[this.visitedViews.length - 1]
if (this.$route.path !== latest.path) this.$router.push(latest.path) if (this.$route.path !== latest.path) this.$router.push(latest.path)

@ -1,16 +1,16 @@
import {leftSideRouteTransition, rightSideRouteTransition} from '@/config' import {leftSideRouteTransition as left, rightSideRouteTransition as right} from '@/config'
export default { export default {
methods: { methods: {
//根据访问的tab页的左右顺序来确定路由动画 //根据访问的tab页的左右顺序来确定路由动画
decideRouteTransition(to, from) { decideRouteTransition(to, from) {
let fromIndex = this.visitedViews.findIndex(i => i.path === from.path) let transitionName = right
if (fromIndex < 0) return this.$store.commit('tagsView/transitionName', rightSideRouteTransition)
let toIndex = this.visitedViews.findIndex(i => i.path === to.path) const fromIndex = this.visitedViews.findIndex(i => i.path === from.path)
if (toIndex < 0) return this.$store.commit('tagsView/transitionName', rightSideRouteTransition) const toIndex = this.visitedViews.findIndex(i => i.path === to.path)
if (fromIndex < toIndex) transitionName = left
this.$store.commit('tagsView/transitionName', toIndex > fromIndex ? rightSideRouteTransition : leftSideRouteTransition) this.$store.commit('tagsView/transitionName', transitionName)
}, },
} }
} }

@ -1,5 +1,11 @@
/**
* 多页签快捷键混入
* ctrl + 下一个页签
* ctrl + 上一个页签
*/
export default { export default {
methods: { methods: {
//上一个页签
gotoViewFront() { gotoViewFront() {
if (this.visitedViews.length <= 1) return if (this.visitedViews.length <= 1) return
let index = this.visitedViews.findIndex(view => view.path === this.$route.path) let index = this.visitedViews.findIndex(view => view.path === this.$route.path)
@ -7,6 +13,8 @@ export default {
if (index === 0) index = this.visitedViews.length if (index === 0) index = this.visitedViews.length
return this.$router.push({path: this.visitedViews[index - 1].path}) return this.$router.push({path: this.visitedViews[index - 1].path})
}, },
//下一个页签
gotoViewBehind() { gotoViewBehind() {
if (this.visitedViews.length <= 1) return if (this.visitedViews.length <= 1) return
let index = this.visitedViews.findIndex(view => view.path === this.$route.path) let index = this.visitedViews.findIndex(view => view.path === this.$route.path)
@ -31,9 +39,11 @@ export default {
} }
} }
}, },
mounted() { mounted() {
window.addEventListener('keydown', this.shortcutsListen) window.addEventListener('keydown', this.shortcutsListen)
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.shortcutsListen) window.removeEventListener('keydown', this.shortcutsListen)
} }

@ -42,3 +42,9 @@ new Vue({
//页面刷新时socket重连 //页面刷新时socket重连
store.dispatch('socket/init', store.state.user).catch() store.dispatch('socket/init', store.state.user).catch()
window.addEventListener('unhandledrejection', event => {
if (event.reason.stack.startsWith('Error: Redirected when going from')) {
event.preventDefault()
}
})

@ -136,7 +136,7 @@ export default {
} }
}, },
activated() { activated() {
let {type, id} = this.$route.params const {type, id} = this.$route.params
if (!['see', 'edit'].includes(type) || isEmpty(id)) return if (!['see', 'edit'].includes(type) || isEmpty(id)) return
this.row = null this.row = null
this.$nextTick(() => { this.$nextTick(() => {

@ -11,12 +11,14 @@ export default {
default: '350px' default: '350px'
} }
}, },
data() { data() {
return { return {
loading: false, loading: false,
chart: null chart: null
} }
}, },
methods: { methods: {
$_getChartInstance() { $_getChartInstance() {
return waitUntilSuccess( return waitUntilSuccess(
@ -28,9 +30,11 @@ export default {
return this.$el return this.$el
} }
}, },
mounted() { mounted() {
this.$_getChartInstance().then(() => this.init()) this.$_getChartInstance().then(() => this.init())
}, },
beforeDestroy() { beforeDestroy() {
if (!this.chart) return if (!this.chart) return
this.chart.dispose() this.chart.dispose()

@ -11,7 +11,7 @@ const router = [
component: Layout, component: Layout,
children: [ children: [
{ {
path: '/redirect/:path(.*)', path: ':path(.*)',
component: Redirect component: Redirect
} }
] ]

@ -91,6 +91,18 @@ const router = {
name: 'developingTest', name: 'developingTest',
component: () => lazyLoadView(import('@/views/example/developingTest')), component: () => lazyLoadView(import('@/views/example/developingTest')),
meta: {title: '开发测试'} meta: {title: '开发测试'}
},
{
path: 'detailPage1',
name: 'detailPage1',
component: () => lazyLoadView(import('@/views/example/detailPage')),
meta: {title: '详情页缓存1', noCache: false, isDetailPage: true}
},
{
path: 'detailPage2',
name: 'detailPage2',
component: () => lazyLoadView(import('@/views/example/detailPage')),
meta: {title: '详情页缓存2', noCache: false, isDetailPage: true}
} }
] ]
} }

@ -1,12 +1,13 @@
/* /*
* 路由配置 * 路由配置
* *
* 需要鉴权的路由meta && !meta.noAuth * 需要鉴权的路由!meta.noAuth
* 左侧菜单显示name && !hidden && meta.title * 左侧菜单显示name && !hidden && meta.title
* 左侧菜单排序能显示 && sort升序排列 * 左侧菜单排序能显示 && sort升序排列
* 左侧菜单不折叠只有一个children的路由alwaysShow * 左侧菜单不折叠只有一个children的路由alwaysShow
* 面包屑显示meta.title * 面包屑显示meta.title
* 搜索选项显示name && meta.title * 搜索选项显示name && meta.title
* tab栏显示name && meta.title
* tab栏固定显示meta.affix * tab栏固定显示meta.affix
* 页面不缓存!name || meta.noCache * 页面不缓存!name || meta.noCache
* 打开iframemeta.iframe不会重复打开相同src的iframe * 打开iframemeta.iframe不会重复打开相同src的iframe
@ -44,7 +45,15 @@ router.beforeEach(async (to, from, next) => {
//使用redirect进行跳转时不显示进度条 //使用redirect进行跳转时不显示进度条
!to.path.startsWith('/redirect') && NProgress.start() !to.path.startsWith('/redirect') && NProgress.start()
document.title = getPageTitle(to.meta.title) //若是详情页之间的跳转,借助redirect避免组件复用
const isToDetailPage = to.meta && to.meta.isDetailPage,
isFromDetailPage = from.meta && from.meta.isDetailPage
if (isToDetailPage !== undefined && isToDetailPage === isFromDetailPage) {
//这里vue-router会报redirect错误
return next({path: `/redirect${to.path}`, query: to.query})
}
document.title = getPageTitle(to)
//白名单内不需要进行权限控制 //白名单内不需要进行权限控制
if (whiteList.some(reg => reg.test(to.path))) return next() if (whiteList.some(reg => reg.test(to.path))) return next()

@ -4,7 +4,9 @@ import {title} from "@/config"
import PageSkeleton from "@/components/Skeleton/PageSkeleton" import PageSkeleton from "@/components/Skeleton/PageSkeleton"
//拼接页面标题 //拼接页面标题
export function getPageTitle(pageTitle) { export function getPageTitle(route) {
let pageTitle = route.meta && route.meta.title
if (typeof pageTitle === 'function') pageTitle = pageTitle(route)
return pageTitle ? `${pageTitle} - ${title}` : title return pageTitle ? `${pageTitle} - ${title}` : title
} }

@ -1,24 +1,36 @@
import {rightSideRouteTransition} from '@/config'
import {createMutations} from "@/utils" import {createMutations} from "@/utils"
const state = { const state = {
//显示的页签,{...route,title:route.meta.title}对象数组
visitedViews: [], visitedViews: [],
//缓存的页签,用于<keep-router-view-alive/>:include
cachedViews: [], cachedViews: [],
transitionName: rightSideRouteTransition
//路由过渡动画名称
transitionName: ''
} }
const mutations = { const mutations = {
...createMutations(state), ...createMutations(state),
addVisitedView(state, view) { addVisitedView(state, view) {
const {title = '暂无标题'} = view.meta || {}
if (state.visitedViews.some(v => v.path === view.path)) return if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push({...view, title: view.meta.title || 'no-name'})
state.visitedViews.push({...view, title})
}, },
addCachedView(state, view) { addCachedView(state, view) {
if (state.cachedViews.includes(view.name)) return const {noCache, iframe} = view.meta || {}
if (!view.meta.noCache && !view.meta.iframe) {
state.cachedViews.push(view.name) if (noCache || iframe) return
}
const key = getCachedViewKey(view)
if (state.cachedViews.includes(key)) return
state.cachedViews.push(key)
}, },
delVisitedView(state, view) { delVisitedView(state, view) {
@ -26,17 +38,9 @@ const mutations = {
index > -1 && state.visitedViews.splice(index, 1) index > -1 && state.visitedViews.splice(index, 1)
}, },
delCachedView(state, view) { delCachedView(state, view) {
const index = state.cachedViews.indexOf(view.name) const key = getCachedViewKey(view)
const index = state.cachedViews.indexOf(key)
index > -1 && state.cachedViews.splice(index, 1) index > -1 && state.cachedViews.splice(index, 1)
},
updateVisitedViews(state, view) {
for (let v of state.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
} }
} }
@ -53,16 +57,22 @@ const actions = {
delOthersViews({state, commit}, view) { delOthersViews({state, commit}, view) {
const visitedViews = state.visitedViews.filter(v => v.meta.affix || v.path === view.path) const visitedViews = state.visitedViews.filter(v => v.meta.affix || v.path === view.path)
const name = state.cachedViews.find(name => name === view.name) const name = state.cachedViews.find(name => name === getCachedViewKey(view))
commit('visitedViews', visitedViews) commit('visitedViews', visitedViews)
commit('cachedViews', name ? [name] : []) commit('cachedViews', name ? [name] : [])
}, },
delAllViews({state, commit}) { delAllViews({state, commit}) {
commit('visitedViews', state.visitedViews.filter(tag => tag.meta.affix)) commit('visitedViews', state.visitedViews.filter(tag => tag.meta && tag.meta.affix))
commit('cachedViews', []) commit('cachedViews', [])
} }
} }
function getCachedViewKey(view) {
const isDetailPage = view.meta && view.meta.isDetailPage
return isDetailPage ? view.path : view.name
}
export default { export default {
namespaced: true, namespaced: true,
state, state,

@ -2,8 +2,9 @@
export default { export default {
created() { created() {
const {params, query} = this.$route const {params, query} = this.$route
//使name+params
if ('params' in query) { if ('params' in query) {
let {name, params} = JSON.parse(query.params) const {name, params} = JSON.parse(query.params)
this.$router.replace({name, params}) this.$router.replace({name, params})
} }
else this.$router.replace({path: '/' + params.path, query}) else this.$router.replace({path: '/' + params.path, query})

@ -0,0 +1,17 @@
<template>
<div>
<div class="tip-row">详情页缓存示例</div>
<el-input v-model="data"/>
</div>
</template>
<script>
export default {
name: "detailPage",
data() {
return {
data: ''
}
}
}
</script>

@ -36,9 +36,6 @@
:data="tableData" :data="tableData"
@row-click="row=$event" @row-click="row=$event"
> >
<el-table-column align="center" type="expand">
<role-resource slot-scope="{row}" :ids="row.resource_id" :map="resourceMap"/>
</el-table-column>
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="角色名" prop="name" show-overflow-tooltip/> <el-table-column align="center" label="角色名" prop="name" show-overflow-tooltip/>
<el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/>
@ -71,7 +68,6 @@
import SearchForm from "@/components/SearchForm" import SearchForm from "@/components/SearchForm"
import SearchFormItem from "@/components/SearchForm/SearchFormItem" import SearchFormItem from "@/components/SearchForm/SearchFormItem"
import EditDialog from './components/EditDialog' import EditDialog from './components/EditDialog'
import RoleResource from "./components/RoleResource"
import {delRole, searchRoles} from "@/api/system/role" import {delRole, searchRoles} from "@/api/system/role"
import {isEmpty} from '@/utils' import {isEmpty} from '@/utils'
import {elConfirm, elError, elSuccess} from "@/utils/message" import {elConfirm, elError, elSuccess} from "@/utils/message"
@ -85,7 +81,7 @@
mixins: [tableMixin], mixins: [tableMixin],
components: {SearchForm, SearchFormItem, EditDialog, RoleResource}, components: {SearchForm, SearchFormItem, EditDialog},
data() { data() {
return { return {
@ -112,15 +108,6 @@
canDel() { canDel() {
return auth(baseUrl + '/del') return auth(baseUrl + '/del')
},
resourceMap() {
const resource = this.$store.state.resource
if (resource.data.length <= 0) return {}
return resource.data.reduce((map, resource) => {
map[resource.id] = resource
return map
}, {})
} }
}, },

@ -47,7 +47,7 @@ module.exports = {
resolve: { resolve: {
alias: { alias: {
'@': resolve('src'), '@': resolve('src'),
'@ele': resolve('element-ui-personal'), '@ele': resolve('element-ui-personal')
} }
}, },
plugins: [ plugins: [
@ -80,53 +80,6 @@ module.exports = {
.loader('svg-sprite-loader') .loader('svg-sprite-loader')
.options({symbolId: 'icon-[name]'}) .options({symbolId: 'icon-[name]'})
.end() .end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
}
)
}, },
css: { css: {
loaderOptions: { loaderOptions: {

Loading…
Cancel
Save