技术改造 - v27
TIP
由于直接上 3 需要太多的工作,先迁移到 2.7, 后续在考虑升级到 3
Vue 2.7 是 Vue 2 最新的次级版本。其提供了内置的组合式 API 支持。
在 Vue 2.7 中,从 Vue 3 移植回了最重要的一些特性,使得 Vue 2 用户也可以享有这些便利。
- 组合式 API
- setup
- v-bind
- 更多
以下列举了一些需要改造的点:
Vue CLI / webpack
- 将本地的 @vue/cli-xxx 依赖升级至所在主版本范围内的最新版本 (如有):
- v4 升级至
~4.5.18
- v4 升级至
- 移除
vue-template-compiler
json
{
"name": "@mgcloud/demo-ui",
"version": "3.2.0",
"description": "模板项目",
"author": "wudi",
"scripts": {
"dev": "vue-cli-service serve",
"test": "vue-cli-service build --mode test",
"build:prod": "vue-cli-service build",
"lint": "eslint --ext .js,.vue src",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
"repository": {
"type": "git",
"url": "http://39.108.189.115:81/general/frontend/demo-ui.git"
},
"dependencies": {
"axios": "0.18.1",
"core-js": "3.25.2",
"element-ui": "2.15.14",
"moment": "^2.29.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"vue": "2.6.14",
"vue-router": "3.0.2",
"vuex": "3.1.0",
"vue": "2.7.16",
"vue-router": "3.6.5",
"vuex": "3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/cli-plugin-babel": "~4.5.18",
"@vue/cli-plugin-eslint": "~4.5.18",
"@vue/cli-plugin-unit-jest": "~4.5.18",
"@vue/cli-service": "~4.5.18",
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "^26.6.1",
"babel-plugin-dynamic-import-node": "2.3.3",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"husky": "1.3.1",
"lint-staged": "8.1.5",
"sass": "1.27.0",
"sass-loader": "^10.1.1",
"script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2",
"svg-sprite-loader": "4.1.3",
"vue-template-compiler": "2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}项目内改造
vue
<style scoped lang="scss">
::v-deep.app-btn { // [!code --]
:deep(.app-btn) { // [!code ++]
width: 33%;
padding: 20px 0;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 16px;
color: #333;
&:hover {
:deep(.txt) {
color: #1890ff;
}
:deep(.icon-box) {
box-shadow: 0 5px 25px 0 rgba(0, 0, 0, 0.45);
}
}
&:nth-child(3n) {
margin-right: 0;
}
&:last-child {
margin-right: 0;
}
:deep(.el-card__body) {
padding: 0;
}
}
</style>遵循 vue3 新规范
移除 mixin
vue
<script>
import AppLink from './Link.vue'
import FixiOSBug from './FixiOSBug'
import { isExternal } from '@/utils/validate'
import { getNormalPath } from '@/utils/ruoyi'
export default {
name: 'SidebarItem',
components: {
AppLink,
},
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
basePath: {
type: String,
default: '',
},
},
data() {
this.onlyOneChild = null
return {}
},
computed: {
device() {
return this.$store.state.uiSetting.device
},
},
mounted() {
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
this.fixBugIniOS()
},
methods: {
fixBugIniOS() {
const $subMenu = this.$refs.subMenu
if ($subMenu) {
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = (e) => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
},
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
// return path.resolve(this.basePath, routePath)
return getNormalPath(`${this.basePath}/${routePath}`)
},
},
}
</script>
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<AppLink
v-if="onlyOneChild.meta"
:to="resolvePath(onlyOneChild.path)"
>
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<svg-icon
v-if="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
/>
<span v-if="onlyOneChild.meta.title" slot="title">
{{ onlyOneChild.meta.title }}
</span>
</el-menu-item>
</AppLink>
</template>
<el-submenu
v-else
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title">
<template v-if="item.meta">
<svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta && item.meta.icon" />
<span v-if="item.meta.title" slot="title">
{{ item.meta.title }}
</span>
</template>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>vue
<script>
import { mapState } from 'vuex'
import { AppMain, Navbar, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import store from '@/store'
const { body } = document
const WIDTH = 992 // refer to Bootstrap's responsive design
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
Sidebar,
TagsView,
},
mixins: [ResizeMixin],
computed: {
...mapState({
sidebar: state => state.uiSetting.sidebar,
device: state => state.uiSetting.device,
needTagsView: state => state.uiSetting.tagsView,
}),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile',
}
},
},
watch: {
$route(_route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('uiSetting/closeSideBar', { withoutAnimation: false })
}
},
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler)
},
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler)
},
methods: {
$_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
store.dispatch('uiSetting/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('uiSetting/closeSideBar', { withoutAnimation: true })
}
}
},
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', {
withoutAnimation: false,
})
},
},
}
</script>
<template>
<div
:class="classObj"
class="app-wrapper"
>
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
></div>
<Sidebar class="sidebar-container" />
<div
:class="{ hasTagsView: needTagsView }"
class="main-container"
>
<div>
<Navbar />
<TagsView v-if="needTagsView" />
</div>
<AppMain />
</div>
</div>
</template>