Vue3

vue3.3.4 + vite4.4.9

1.路由

1.1 安装和配置

npm install vue-router@4
  • router/index.js
import {
  createRouter,
  createWebHashHistory,
} from 'vue-router'

import ItProjectIndex from '../views/itproject/Index.vue';

const routes = [
  // 路由守卫 vue3动态路由问题导致刷新完页面会爆出No match found for location with path
    {
        path: "/:pathMatch(.*)*", // 必备
        component: () => import("@/views/404.vue"),
    },
     {
        path: '/login',
        component: Login,
        name: 'Login'
    },
    {
        path: '/',
        component: Frame,
        redirect: '/homePage',
        name: '首页',
        children: [
            {
                path: 'homePage',
                component: HomePage,
                name: '欢迎页',
            },
            // 菜谱
            {
                path: '/menu',
                name: '菜谱管理',
                children: [
                    {
                        path: '/menu/index',
                        component: MenuIndex,
                        name: '菜谱列表'
                    },
                ]
            },
        ]
    },
   moduleRouter
]

// 创建路由对象
const router = createRouter({
  history: createWebHashHistory(), //url有#
  // history: createWebHistory(), //url没有#
  routes
})

export default router
  • 模块化路由 router/module/index.js
const moduleRouter = {
        path: '/module',
        component: PageFrame,
        redirect: '/module/index',
        children: [
            {
                path: '/module/index',
                component: moduleIndex,
                name: 'moduleIndex'
            },
        ]
}

export default moduleRouter;
  • main.js
import router from "./router/index";
const app = createApp(App);
app.use(router).mount('#app');

1.2 路由变化监听

  • onBeforeRouteUpdate钩子
import { onBeforeRouteUpdate } from "vue-router";

onBeforeRouteUpdate((val, oldVal) => {
      const query = val.query
      this.isHeader = !query.hasOwnProperty('isHeader') ? '1' : query.isHeader

  });

1.3 路由传参

query和params传参的时候,每一个参数应该以字符串形式传入
如果传一个对象必须JSON.stringify()序列化
传map需要先转化为object再转化为json传入

  • index.js
{
  path: '/Consumer/shopDetail',
  component: ShopDetail,
  name: 'ShopDetail',
  query: {
    shopInfo: {}
  }
},
  • 父组件
import { useRouter, useRoute } from 'vue-router'
const router = useRouter();
router.push({
  // map -> json 需要先转object
  let mapObj = Object.create(null);
  for (let[k,v] of data.order) {
      mapObj[k] = v;
  }
  const mapJson = JSON.stringify(mapObj)
  path: '/Consumer/shopItemDetail',
  query: {
      shopItemInfo: JSON.stringify(data.shopItemList[key]),
      order: mapJson,
  }
})
  • 子组件
    接受参数用route
import { useRouter, useRoute } from 'vue-router'
const route = useRoute();
data.shopItemInfo = JSON.parse(route.query.shopItemInfo)
// json -> map
let mapJson = route.query.order;
const obj = JSON.parse(<string>mapJson)
let map = new Map();
for (let k of Object.keys(obj)) {
    map.set(k,obj[k]);
}
data.order = map

2.ElementPlus

2.1 安装和配置

npm install element-plus --save
  • main.js
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App);
app.use(ElementPlus).mount('#app');

2.2 表单验证

const data = reactive({
  rules: {
          name: [
              {required: true, message: '分类名不能为空', trigger: 'blur'}
          ],
          parentId: [
              {required: true, message: '大类ID不能为空', trigger: 'blur'}
          ]
      },
})

// 表单ref
const itemForm = ref();
const submitForm = (formName) => {
    // 表单验证
    itemForm.value.validate((valid, fields) => {
        if (valid) {
            saveOrUpdate();
        } else {
            ElMessage({
                message: '请校验',
                type: 'warning',
            })
        }
    })
}

3. Axios

3.1 安装和配置

 npm install axios
  • main.js
import axios from 'axios'

const app = createApp(App);
app.config.globalProperties.$axios = axios;

3.2 封装

  • 封装为request.js
/**
 * ajax请求配置
 */
import axios from 'axios'
import {ElMessage} from 'element-plus'
import qs from "qs";
import {getToken, removeToken} from '@/utils/auth/auth'

// axios默认配置
axios.defaults.timeout = 10000 // 超时时间

// 网关地址
axios.defaults.baseURL = "http://localhost:8921";

// 整理数据
axios.defaults.transformRequest = function(data) {
    return data
}

// 路由请求拦截
axios.interceptors.request.use(
    config => {
       // 权限系统适配网关
        if (config.url.indexOf('/manager') !== -1) {
            config.baseURL = 'http://localhost:8921';
        }
        if (getToken()) {
            config.headers['Authorization'] = getToken()
        }
        console.log(axios.defaults.baseURL)
        if (config.type === 'form'){
            // 后端@RequestParams注解接收
            config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
            config.data = qs.stringify(config.data)
        } else if (config.type === 'file') {
            // 后端@RequestParams注解接收
            config.headers['Content-Type'] = 'multipart/form-data'
            config.data = qs.stringify(config.data)
        } else {
            // 后端@RequestBody注解接收
            config.headers['Content-Type'] = 'application/json;charset=UTF-8'
            config.data = JSON.stringify(config.data)
        }
        return config
    },
    error => {
        return Promise.reject(error.response)
    }
)

// 路由响应拦截
axios.interceptors.response.use(
    response => {
        if (response.headers && (response.headers['content-type'] === 'application/x-msdownload' ||
                response.headers['content-type'].indexOf('application/vnd.ms-excel') !== -1 ||
                response.headers['content-type'].indexOf('application/octet-stream') !== -1)) {
            return response;
        } else {
            // if (res.code !== '20000') {
            //     Message({
            //         message: res.message || '操作失败,请联系管理员',
            //         type: 'error',
            //         duration: 5 * 1000
            //     })
            //     return Promise.reject(new Error(JSON.stringify(res) || '操作失败,请联系管理员'))
            // } else {
            //     return res
            // }
            return response.data
        }
    },
    error => {
        console.log('err' + error) // for debug
        if (error && error.response) {
            const {status} = error.response;
            if (status === 401) {
                ElMessage.error('Token值无效,请重新登录');
                removeToken();
                router.replace('/login');
            } else {
                // Message({
                //     message: error.message,
                //     type: 'error',
                //     duration: 5 * 1000
                // });
            }
        } else {
            ElMessage.error('网络出现问题,请稍后再试');
        }
        return Promise.reject(error)
    }

    // response => {
    //     if (response.data.success === false) {
    //         return ElMessage.error(response.data.errDesc)
    //     } else {
    //         return response.data
    //     }
    // },
    // error => {
    //     return Promise.reject(error.response) // 返回接口返回的错误信息
    // }


)
export default axios

3.3 Api使用

  • api.js
import request from '@/utils/request'

export default {
    testLogin(data) {
        return request({
            url: 'shop/test/testLogin',
            method: 'post',
            type: 'form',
            data: data
        })
    },

    testLogin1(data) {
        return request({
            url: 'shop/test/testLogin1',
            method: 'post',
            data: data
        })
    },
}
  • vue界面使用
mounted() {
    // 原生
    this.$axios.get("http://localhost:9000/order/order/1").then(res => {
        this.obj= res.data[0];
        console.log(res.data);
    })
    // 封装
    Api.test().then(res => {
        console.log(res);
    })
}

4. Vuex/Store

store是用来存储组件状态的,而不是用来做本地数据存储的。所以,对于不希望页面刷新之后被重置的数据,使用本地存储来进行存储。

4.1 安装和配置

npm install vuex@next --save
  • store/index.js
import {createStore } from 'vuex'

export default createStore({
    // state是用来定义数据,这里可以设置数据的默认数据,但是为了数据的安全性,通常不直接修改state数据,而是通过mutations修改
    state: {
        count: 0,
    },
    // getters用来处理数据,对state中的数据进行处理,得到自己想要的效果,当需要在多处组件使用这种数据时,gettters是你最好的选择
    getters:{
        getCount(state){
            return state.count;
        },
    },
    // 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation
    // mutations通常为修改state数据而使用,这用就可以避免直接修改state的数据
    mutations: {
        increment (state) {
            state.count++
        },
    },
    // actions当你的数据是需要发送请求获取时,这是非常完美的选择
    actions: {
        async setGoods(content,id){
            let res = await api.getGoods(id);
            content.commit('setGoods',res.data);
        }
        
        // 各部分使用
        // console.log(store.state.goods); //state
        // console.log(store.getters['goodsData']); //getters
        // store.commit('changeGoods',123); //mutations
        // actions
        // async function abc() {
        //     await store.dispatch("setGoods", {
        //         id: 123,
        //     });
        // }
    },
    // 模块化引入
    modules: {
        // tagsView,
        // side,
        // app,
        // permission
    },
})
  • main.js
import store from './store/index'

const app = createApp(App);
app.use(store).mount('#app');

5.全局变量

设置一个JSON格式的配置文件

  • public/config.json
{
  "BASE_URL": "/api",
  "obj":{
      "NAME":"",
    },
}
  • main.js
app.config.globalProperties = {
    BASE_URL: dataJson.BASE_URL,
    obj : dataJson.obj,
};

5.1 getCurrentInstance

不推荐使用

  • script 使用
    直接用this.***的方式使用全局属性
  • script lang="ts" setup 使用
import { getCurrentInstance } from 'vue'
// 使用getCurrentInstanceAPI获取全局对象方法一,从globalProperties中可以获取到所有的全局变量
const currentInstance = getCurrentInstance();
const globalProperties = currentInstance?.appContext.config.globalProperties
console.log(globalProperties);

5.2 使用 Provide / Inject

  • main.js
// 变量
app.provide('$test', '666')
// 函数
import dayjs from 'dayjs'
const formatTime = (time, format) => (time ? dayjs(time).format(format || 'YYYY-MM-DD') : '-')
app.provide('dayjs', formatTime)
  • 组件
// 变量
import { inject } from 'vue'
const test = inject('$test')
console.log('inject的$test', test)
// 函数
const dayjs = inject('dayjs')
const formatResult = dayjs(1639014286)
console.log('dayFormat', formatResult)

5.3 俩个script标签

  • main.js
app.config.globalProperties.myOption = 'myOption'
  • 组件
<script>
import HelloWorld from './components/HelloWorld.vue'
import { onBeforeMount, defineComponent } from 'vue'
let that = this
export default defineComponent({
  beforeCreate() {
    that = this
  },
})
</script>
<script setup>
onBeforeMount(() => {
  console.log('that', that.myOption)
})
</script>

5.4 单js文件

import dataJson from '../../public/config.json'
const key= dataJson.key;

6.SCSS

6.1 安装和配置

npm install --save-dev sass-loader
  • style目录
    放入.scss文件
  • vite.config.js
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    // css预处理器
    preprocessorOptions: {
      scss: {
        // 引入 mixin.scss 这样就可以在全局中使用 mixin.scss中预定义的变量了
        // 给导入的路径最后加上 ;
        additionalData: '@import "@/style/name1.scss";' +
            '@import "@/style/name2.scss";' +
            '@import "@/style/name3.scss";'
      }
    }
  }
})

6.2 组件使用

<style lang="scss" scoped>
</style>
  • 深度选择器
    /deep/ 改为 v-deep

7.生命周期

  • 使用
<script lang="ts" setup>
// 引入生命周期钩子
import { onMounted } from "vue";
onMounted(()=>{
  console.log(' DOM被挂载完成时执行');
});
</script>

7.1 组件比数据更早完成渲染

有时候子组件需要的数据还没请求完成,但界面已经渲染完成

  • 加个判断即可
<template v-if="canteenList.length!=0">
   <canteen-card :list='canteenList' @goTo='goInCanteen'></canteen-card>
</template>

8.data,method,watch,computed 使用

  • data
// 挂载使用
<template>
  v-model="data.activeName"
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs} from 'vue'

// Data
const data = reactive({
    // formList:搜索条件对象 分页控制对象
    formList: {
        name: '',
        parentId: ''
    },
    // 分页配置
    pageConfig: {
        currentPage: 1,
        pageSize: 10,
        total: 1000
    },
    // dialog
    type: '',
})
// 解构抛出 直接使用
// const { type} = toRefs(data)

// 函数使用
const getData = () => {
    // 查询方法
    // 查询参数
    const params = {
        name : data.formList.name,
        parentId : data.formList.parentId,
        pageIndex : data.pageConfig.currentPage,
        pageSize : data.pageConfig.pageSize
    }
}
</script>
  • method
<script lang="ts" setup>
// 函数使用
const getData = () => {
    // 查询方法
    // 查询参数
    const params = {
        name : data.formList.name,
        parentId : data.formList.parentId,
        pageIndex : data.pageConfig.currentPage,
        pageSize : data.pageConfig.pageSize
    }
}
</script>

- computed
```js
<script lang="ts" setup>
import { ref, computed} from 'vue'

// computed计算属性传入一个回调函数
const areYouSureYouAreABaby = computed(() => {
  return `I'm sure,I'm a 300 month baby, ${baby.value}`
})
</script>

- watch
```js
<script lang="ts" setup>
import { ref, watch, watchEffect } from 'vue'

watch(() => baby.value, () => {
  return `I'm sure,I'm a 300 month baby, ${baby.value}`
})
</script>

9.父子组件传值

9.1 父传子第一种

  • 父组件
<ItemDialog ref="itemDialog"></ItemDialog>


const itemDialog = ref();
const addData = () => {
    data.type = "add";
    itemDialog.value.init(null,data.type);
}
  • 子组件
const init = (id, type) => {
    // 界面初始化接收参数
    data.type = type;
}

//暴露方法
defineExpose({
    init,
});

9.2 父传子第二种

  • 父组件
<ShopCardList :shop-list="data.shopList"></ShopCardList>
import ShopCardList from "./Shop/components/shopCardList.vue";
  • 子组件
import { reactive, onMounted,defineProps } from 'vue'
// Props 只读不能修改
const props = defineProps({
    shopList: [],
})

9.3 子传父

  • 父组件
<ScreeningList @get-screening-index="getScreeningIndex"></ScreeningList>
import ScreeningList from "./Shop/components/screeningList.vue";

const getScreeningIndex = (index) => {
    data.params.screening = index;
    getShopList();
}
  • 子组件
const emit = defineEmits(['getScreeningIndex'])
const getShopListByScreening = (index) => {
    emit('getScreeningIndex',index)
}

## 10.项目结构
- index.html:界面入口
删除页面白边
```html
  <body style="margin: 0">
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>

11.本地存储

  • 不适合存储大量的数据。4k左右
  • 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效
  • 通信时,每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

2.localStorage

  • 是永久存储,浏览器关闭后数据不会丢失,除非主动删除数据。当关闭页面后重新打开,会读取上一次打开的页面数据。
  • 除非被清除,否则永久保存,可变相设置失效时间。5MB
  • 仅在客户端(即浏览器)中保存,不参与和服务器的通信

3.sessionStorage

  • 在当前浏览器窗口关闭后自动删除

4.操作

sessionStorage.setItem("key", "value");    
localStorage.setItem("aaa", "111");

var value = sessionStorage.getItem("key");    
var site = localStorage.getItem("asd");

// 删除
sessionStorage.removeItem("key");    
localStorage.removeItem("asd");

// 清除
sessionStorage.clear();    
localStorage.clear();

12.动态路由和导航栏

12.1 框架界面

  • frame/index
<template>
    <el-container>
        <el-aside width="200px">
            <Sidebar class="sidebar-container"></Sidebar>
        </el-aside>
        <el-container >
            <el-header>
                <Navbar></Navbar>
            </el-header>
            <el-main>
                <AppMain></AppMain>
            </el-main>
        </el-container>
    </el-container>
</template>

<script lang="ts" setup>
    import Navbar from './Navbar.vue';
    import Sidebar from './Sidebar/index.vue';
    import AppMain from "./AppMain.vue";
</script>

<style>
.sidebar-container {
    transition: width 0.28s;
    width: 200px;
    background-color: #545c64;
    height: 100%;
    position: fixed;
    font-size: 0px;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
}
</style>
  • frame/AppMain
A<template>
    <router-view v-slot="{ Component }" :key="key">
        <keep-alive>
            <component :is="Component" />
        </keep-alive>
    </router-view>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { useStore } from "vuex";
import { useRouter } from 'vue-router'

const store = useStore();
const router = useRouter();

const key = computed(() => {
    return router
})
</script>

<style lang="scss" scoped>
.app-main {
  min-height: calc(100vh - 50px);
  width: 100%;
  position: relative;
  overflow: hidden;
}

.fixed-header+.app-main {
  padding-top: 50px;
}

.hasTagsView {
  .app-main {
    min-height: calc(100vh - 84px);
  }

  .fixed-header+.app-main {
    padding-top: 84px;
  }
}
</style>

<style lang="scss">
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 15px;
  }
}
</style>
  • frame/Navbar
<template>
    <div class="navbar">
        <el-row>
            <el-col :span="14" >
                <div class="l-content">
                    <el-breadcrumb separator="/">
                        <el-breadcrumb-item
                            v-for="(item,index) in data.breadList"
                            :key="index"
                            :to="{ path: item.path }"
                        >{{item.name}}</el-breadcrumb-item>
                    </el-breadcrumb>
                </div>
            </el-col>
            <el-col :span="10">
                <div class="right-menu">
                    <el-dropdown class="avatar-container right-menu-item hover-effect"
                                 trigger="click">
                        <div class="avatar-wrapper">
                            <img class="user-avatar" src="../assets/images/login.jpg" alt="">
                            <span class="user-name">{{ data.name }}</span>
                            <i class="el-icon-caret-bottom"/>
                        </div>
                        <template #dropdown>
                            <el-dropdown-menu>
                                <el-dropdown-item>
                                        <span style="display:block;"
                                              @click="handleModifyPass()">
                                            修改密码
                                        </span>
                                </el-dropdown-item>
                                <el-dropdown-item divided>
                                        <span style="display:block;"
                                              @click="logout()">
                                            退出系统
                                        </span>
                                </el-dropdown-item>
                            </el-dropdown-menu>
                        </template>
                    </el-dropdown>
                </div>
            </el-col>
        </el-row>

        <el-dialog title="修改密码"
                   :modal="true"
                   :append-to-body="false"
                   :close-on-click-modal="false"
                   v-model="data.dialogVisible"
                   width="400px">
            <template #header>
                <i class="el-icon-key"/> 修改密码
            </template>
            <el-form ref="form" :model="data.form" label-width="100px" :rules="data.rules">
                <el-form-item label="账户名">
                    <el-input v-model="data.form.name" style="width:250px" :disabled="true"/>
                </el-form-item>
                <el-form-item label="旧密码" prop="oldPass">
                    <el-input v-model="data.form.oldPass" style="width:250px" show-password/>
                </el-form-item>
                <el-form-item label="新密码" prop="newPass">
                    <el-input v-model="data.form.newPass" style="width:250px" show-password/>
                </el-form-item>
                <el-form-item label="确认密码" prop="confirmPass">
                    <el-input v-model="data.form.confirmPass" style="width:250px" show-password/>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="data.dialogVisible = false">取 消</el-button>
                <el-button type="danger" @click="handleSavePass()">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>
<script lang="js" setup>
import { useStore } from 'vuex'
import { onMounted, reactive, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import Api from '@/api/auth'
import {ElMessage} from "element-plus";
import { removeToken } from '@/utils/auth/auth.js'
import {getEncryptPassword} from "@/utils/passwordEncrypt";
import { onBeforeRouteUpdate } from "vue-router";

const store = useStore();
const router = useRouter()
const route = useRoute();

const data = reactive({
    // 路由集合
    breadList: [],
    sidebarOpened: false,
    dialogVisible: false,
    form: {
        oldPass: '',
        newPass: '',
        confirmPass: '',
        accountId: localStorage.getItem("userId"),
    },
    rules: {
        oldPass: [
            {required: true, message: '请输入旧密码', trigger: 'blur'}
        ],
        newPass: [
            {
                required: true,
                // pattern: /(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}/,
                message: '请输入新密码',
                trigger: 'blur'
            }
        ],
        confirmPass: [
            // {validator: validatePass, trigger: 'blur'}
        ]
    }
})


// Mounted
onMounted(() => {
    data.name = localStorage.getItem("userName")
    data.form.name = data.name;

    getBreadcrumb();
})

/**
 * 路由变化
 */
onBeforeRouteUpdate((val, oldVal) => {
    getBreadcrumb(val.matched);
});

// Methods
const isHome = (route) => {
    return route.name === "首页";
}

const getBreadcrumb = (matched) => {
    if (matched === undefined) {
        matched = route.matched;
    }
    //如果不是首页
    if (!isHome(matched[0])) {
        matched = [{ path: "/home", meta: { title: "首页" } }].concat(matched);
    }
    data.breadList = matched;
}

const validatePass = (rule, value, callback) => {
    if (value === '') {
        callback(new Error('请输入确认密码'));
    } else if (value !== this.form.newPass) {
        callback(new Error('两次输入密码不一致!'));
    } else {
        callback();
    }
};

/**
 * 修改密码
 */
const handleModifyPass = () => {
    data.dialogVisible = true;
}

/**
 * 确认修改密码
 */
const form = ref();
const handleSavePass = () => {
    form.value.validate(valid => {
        if (valid) {
            data.form.newPass = getEncryptPassword(data.form.newPass, 'aes');
            data.form.oldPass = getEncryptPassword(data.form.oldPass, 'aes');
            Api.modifyPass(data.form).then(res => {
                console.log(res)
                if (res.code === '20000'){
                    ElMessage.success('修改成功');
                    logout();
                } else {
                    ElMessage.error(res.message)
                }
            })
        } else {
            return false;
        }
    });
}

/**
 * 退出系统
 */
const logout = () => {
    Api.logout().then(res => {
        removeToken();
        router.push({
            path: '/login',
        })
    })
}
</script>

<style lang="scss" scoped>
.l-content{
    margin-top: 20px;
}
.navbar {
    margin-left: 10px;
  width: 99%;
  height: 60px;
  overflow: hidden;
  position: relative;
  background: #fafafa;
  box-shadow: 0 1px 4px rgba(0, 21, 41, .08);


  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background .3s;

        &:hover {
          background: rgba(0, 0, 0, .025)
        }
      }
    }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        margin-top: 5px;
        position: relative;

        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 20px;
          vertical-align: middle;
          margin-bottom: 8px;
        }

        .user-name {
          font-size: 16px;
          color: #000;
        }

        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
}

</style>
  • frame/Siderbar/index
    通过后端获取配置
<template>
    <div>
        <el-scrollbar >
            <el-menu style="margin-right: -1px;"
                class="myMenu"
                default-active="1"
                :collapse="data.isCollapse"
                :unique-opened="true"
                :default-openeds="data.openeds"
                :collapse-transition="false"
                mode="vertical"
                active-text-color="#ffd04b"
                background-color="#545c64"
                text-color="#fff"
            >
            <sidebar-item v-for="route in data.indexDate" :key="route.funId.toString()" :item="route" :base-path="route.url"/>
            <sidebar-item v-for="route in data.menuData"
                          :key="route.funId.toString()"
                          :item="route"
                          :base-path="route.url"/>
            </el-menu>
        </el-scrollbar>
    </div>
</template>

<script lang="js" setup>
    import { useStore } from "vuex";
    import { useRouter } from 'vue-router'
    import SidebarItem from './SidebarItem.vue'
    import { reactive } from "vue";

    const store = useStore();
    const router = useRouter()

    const data = reactive({
        showLogo: true,
        isCollapse: false,
        openeds: [],
        menuData: [],
        indexDate: [
            {
                'funId': '1',
                'funName': '首页',
                'url': '/homePage',
            },
            {
                'funId': '2',
                'funName': '菜谱管理',
                'icon':'KnifeFork',
                'children':[{
                    'funId': '21',
                    'funName': '菜谱评论管理',
                    'url': '/menucomment/index',
                    'icon': 'KnifeFork'
                },{
                    'funId': '22',
                    'funName': '菜谱列表',
                    'url': '/menu/index',
                    'icon': 'KnifeFork'
                }]
            },
        ]
    })
</script>
  • frame/Siderbar/SiderbarItem
<template>
    <div>
        <!-- 有二级分类的一级分类 -->
        <el-sub-menu
            class="myMenu"
            v-if="item.children && item.children.length > 0"
            :key="item.funId"
            :index="item.funId.toString()"
            teleported>
            <template #title>
                <component :is="item.icon" style="width: 16px;height: 16px;"></component>
                <span>{{item.funName}}</span>
            </template>
            <!--二级分类-->
            <router-link v-for="(citem,cindex) in item.children"
                         :key="cindex" :to="resolvePath(citem.url)">
                <el-menu-item :index="citem.url">
                    <template #title>
                        <component :is="citem.icon" style="width: 16px;height: 16px;"></component>
                        <span>{{citem.funName}}</span>
                    </template>
                </el-menu-item>
            </router-link>
        </el-sub-menu>
        <!--只有一级分类-->
        <router-link class="myMenu"
                     v-else-if="item.children == null || item.children.length == 0"
                    :key="item.funId.toString()"
                    :to="resolvePath(item.url)"
                    :index="item.funId.toString()">
            <el-menu-item :index="item.funId.toString()">
                <template #title>
                    <component :is="item.icon" style="width: 16px;height: 16px;"></component>
                    <span>{{item.funName}}</span>
                </template>
            </el-menu-item>
        </router-link>
    </div>
</template>

<script lang="js" setup>
import {onMounted, reactive} from "vue";
import {
    HelpFilled,
    Flag,
} from '@element-plus/icons-vue'


const data = reactive({

})

// Props
const props = defineProps({
    item: {
        type: Object,
        required: true
    },
    isNest: {
        type: Boolean,
        default: false
    },
    basePath: {
        type: String,
        default: ''
    }
})

// Mounted
onMounted(() => {

})

// Methods
const resolvePath = (routePath) => {
    return routePath;
}
</script>

<style scoped lang="scss">
    a {
        text-decoration: none;
    }

</style>
posted @   lwx_R  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示