Vue3.x+element-plus+ts踩坑筆記

2022-10-20 21:00:54

閒聊

前段時間小穎在B站找了個學習vue3+TS的視訊,自己嘗試著搭建了一些基礎程式碼,在實現功能的過程中遇到了一些問題,為了防止自己遺忘,寫個隨筆記錄一下嘻嘻

專案程式碼

git地址:vue3.x-ts-element-plus--demo

踩坑集合:

1.根據 element-plus 官網提示 按需引入 元件後,遇到:ElLoadingElMessageElNotificationElMessageBox  樣式丟失

起因是小穎在封裝  axios 時,發現引入的  ElNotification 元件沒有樣式,表單提交時載入  ElLoading 元件有沒有樣式,後來通過面向百度解決了該問題,嘻嘻

解決方案一:

第一步:執行下面程式碼

npm i unplugin-element-plus -D

第二步:在 vue.config.js 改為

const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    plugins: [
      AutoImport({
        resolvers: [ElementPlusResolver()],
      }),
      Components({
        resolvers: [ElementPlusResolver()],
      }),
      require('unplugin-element-plus/webpack')({
        // options
      }),
    ],
  },
})

解決方案二:

直接全域性引入 element-plus

第一步:修改 main.ts

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/es/components/button/style/css'
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')

參考:記錄-解決element-plus自動引入後ElLoading、ElMessage、ElNotification、ElMessageBox樣式丟失的問題

2.動態使用圖示元件時,圖示元件不能正確渲染

起因是小穎在封裝選單元件時,要動態遍歷選單資料根據資料中的  icon 值,通過:

<component :is="menuInfo.icon" class="menu-icon" />

動態渲染各自的選單圖示,但是沒有渲染出來,通過F12發現渲染出來的dom就不是圖示元件的dom,而是這樣的:

當前 menuInfo.icon 值為:setting

左側選單元件

因考慮到選單可能不止兩級可能會是多級的所以小穎將其封裝成以下元件:

<template>
  <div class="logo-box">XXXX管理系統</div>
  <div class="menu-box">
    <el-menu
      active-text-color="#ffd04b"
      background-color="#545c64"
      class="el-menu-vertical"
      :default-active="menuActive"
      :unique-opened="true"
      text-color="#fff"
      @open="handleOpen"
      @close="handleClose"
    >
      <template v-for="menu in menuList" :key="menu.id">
        <subMenu :menuInfo="menu" />
      </template>
    </el-menu>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, computed } from "vue";
import { useStore } from "vuex";
import SubMenu from "./subMenu.vue";

const store = useStore();
const props = defineProps({
  menuList: {
    type: Array,
    default: () => [],
  },
});
const menuActive = computed(() => {
  return store.state.setting.menuActive;
});
const handleOpen = (key: string, keyPath: string[]) => {
  console.log(key, keyPath);
};
const handleClose = (key: string, keyPath: string[]) => {
  console.log(key, keyPath);
};
</script>

<style lang="scss" scoped>
.logo-box {
  height: 80px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 20px;
  cursor: pointer;
  background-color: #545c64;
  color: #fff;
  // background: v-bind(themeBackground);
}
.menu-box {
  height: calc(100vh - 80px);
  background-color: #545c64;
}
.el-menu-vertical {
  border-right: none;
}
.el-menu-vertical:not(.menu--collapse) {
  min-height: 400px;
}
</style>
leftMenu.vue
<template>
  <el-sub-menu v-if="menuInfo.childs.length > 0" :index="menuInfo.id">
    <template #title>
      <el-icon :size="18">
          <component :is="menuInfo.icon" />
      </el-icon>
      <span>{{ menuInfo.m_name }}</span>
    </template>
    <template v-for="item in menuInfo.childs" :key="item.id">
      <sub-menu :menu-info="item" />
    </template>
  </el-sub-menu>
  <el-menu-item v-else :index="menuInfo.id"  @click="menuFun(menuInfo, menuInfo.id)" >
    <el-icon :size="18">
      <component :is="menuInfo.icon" class="menu-icon" />
    </el-icon>
    <span>{{ menuInfo.m_name }}</span>
  </el-menu-item>
</template>
<script lang="ts" name="SubMenu" setup>
import {
  Document,
  Menu as IconMenu,
  Location,
  Setting,
  Menu,
  Grid,
} from "@element-plus/icons-vue";
import { defineProps } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";

//路由
const router = useRouter();
//vuex
const store = useStore();
const props = defineProps({
  menuInfo: {
    type: Object,
    default: () => {
      return {
        id: "",
        parent_id: "",
        m_name: "",
        icon: "",
        childs: [],
      };
    },
  },
});

const menuFun = (event: any, index: string) => {
  setNav(event);
  store.dispatch("setMenuActive", { menuActive: index });
  if (event.url && event.url.length > 0) {
    router.push({
      path: event.url,
      query: {},
    });
  }
};
const setNav = (item: any) => {
  store.dispatch("setNav", { nav: item });
};
</script>
subMenu.vue

 解決方案一:

修改 main.ts 

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/es/components/button/style/css'
import * as Icons from '@element-plus/icons-vue'
const app = createApp(App)

Object.keys(Icons).forEach(key => {
    app.component(key, Icons[key as keyof typeof Icons])
})
app.use(store)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

解決方案二:

將 main.ts 改回原來的

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
//公共css
import './assets/css/index.scss'

將subMenu.vue元件的 js 程式碼改為

import { defineComponent } from "vue";
  import {
    Document,
    Menu as IconMenu,
    Location,
    Setting,
    Menu,
    Grid,
  } from "@element-plus/icons-vue";
  import { useRouter } from "vue-router";
  import { useStore } from "vuex";
  export default defineComponent({
    components: {
      Document,
      Menu,
      Location,
      Setting,
      Grid,
    },
    props: {
      menuInfo: {
        type: Object,
        default: () => {
          return {
            id: "",
            parent_id: "",
            m_name: "",
            icon: "",
            childs: [],
          };
        },
      },
    },
    setup() {
      //路由
      const router = useRouter();
      //vuex
      const store = useStore();
      const menuFun = (event: any, index: string) => {
        setNav(event);
        store.dispatch("setMenuActive", { menuActive: index });
        if (event.url && event.url.length > 0) {
          router.push({
            path: event.url,
            query: {},
          });
        }
      };
      const setNav = (item: any) => {
        store.dispatch("setNav", { nav: item });
      };
      return {
        menuFun,
      };
    },
  });
</script>

參考哪裡忘記了,第一種是面向百度的,第二種是小穎自己試出來的

來來來找到了,參考這裡;vue3 動態載入el-icon圖示

3.vuex頁面重新整理資料丟失問題的四種解決方式

解決方案:

第一步:執行以下程式碼

npm install --save vuex-persist

第二步:在 store 下的 index.ts 中引入並使用

import VuexPersistence from "vuex-persist";//解決頁面重新整理vuex資料丟失

const vuexLocal = new VuexPersistence({
  storage: window.localStorage
})
export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: modules,
  plugins: [vuexLocal.plugin]
})

更多方法參考:vuex頁面重新整理資料丟失問題的四種解決方式

4.如何自動引入  store  下指定目錄下的所有檔案,此方法也適用於引入元件

比如小穎要實現在 store 下的 index.ts 中自動引入 store  下的 modules 中的所有 ts 

解決方案: 

將 index.ts 改為:

import { createStore } from 'vuex'
import VuexPersistence from "vuex-persist";//解決頁面重新整理vuex資料丟失
const modulesFiles = require.context('./modules', false, /\.ts$/)

// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules: any, modulePath) => {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})
const vuexLocal = new VuexPersistence({
  storage: window.localStorage
})
export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: modules,
  plugins: [vuexLocal.plugin]
})

後面的坑等後面寫了再繼續補充,最近小穎在忙著弄接的私活所以也沒繼續看vue3了,等這段時間忙完繼續搞,