Vite-Admin后台管理系统|vite4+vue3+pinia前端后台框架实例

Posted web前端项目案例实战

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vite-Admin后台管理系统|vite4+vue3+pinia前端后台框架实例相关的知识,希望对你有一定的参考价值。

基于vite4.x+vue3+pinia前端后台管理系统解决方案ViteAdmin

前段时间分享了一篇vue3自研pc端UI组件库VEPlus。这次带来最新开发的基于vite4+vue3+pinia技术栈搭配ve-plus组件库构建的中后台权限管理系统框架。支持vue-i18n国际化多语言、动态路由鉴权、4种布局模板及tab页面缓存等功能。

技术栈

  • 编码工具:Cursor+Sublime
  • 框架技术:vite4+vue3+pinia+vue-router
  • UI组件库:ve-plus (基于vue3自研ui组件库)
  • 样式处理:sass^1.58.3
  • 图表组件:echarts^5.4.2
  • 国际化方案:vue-i18n^9.2.2
  • 富文本编辑器组件:wangeditor^4.7.15
  • markdown编辑器:md-editor-v3^2.11.0
  • 数据模拟:mockjs^1.1.0

功能点

  1. 最新前端技术栈vite4、vue3、pinia、vue-router、vue-i18n、ve-plus。
  2. 支持中文/英文/繁体多语言模式切换。
  3. 支持表格单选/多选、边框/隔行换色、横向/纵向虚拟滚动条等功能。
  4. 搭配高颜值的vue3-plus组件库,风格更加统一。
  5. 内置多个模板布局样式
  6. 支持动态路由权限控制
  7. 支持tabs动态路由缓存
  8. 高效率开发,整个框架已经搭建完毕,只需定制化相应模块即可。

项目页面结构

整体采用vue3 setup语法糖模式开发,搭配ve-plus轻量级组件库,使得界面清新且运行极速。

效果图

Vue3 UI  VEPlus组件

ve-plus:基于vue3开发的pc端组件库,包含了40+常用的功能组件,易于上手。

veplus整合了vue3.js开发的两个独立插件vue3-layer弹窗vue3-scrollbar虚拟滚动条组件。

快速安装

npm install ve-plus -S
cnpm install ve-plus -S
yarn add ve-plus

具体的使用方法,大家可以去看看这篇分享文章。

https://www.cnblogs.com/xiaoyan2017/p/17170454.html

Vite-Admin布局模块

公共布局文件在layouts目录,提供了4种经典的布局模板。

<script setup>
    import  computed  from \'vue\'
    import  appStore  from \'@/store/modules/app\'

    // 引入布局模板
    import Classic from \'./layout/classic/index.vue\'
    import Columns from \'./layout/columns/index.vue\'
    import Vertical from \'./layout/vertical/index.vue\'
    import Transverse from \'./layout/transverse/index.vue\'

    const store = appStore()
    const config = computed(() => store.config)

    const LayoutConfig = 
        classic: Classic,
        columns: Columns,
        vertical: Vertical,
        transverse: Transverse
    
</script>

<template>
    <div class="veadmin__container" :style="\'--themeSkin\': store.config.skin">
        <component :is="LayoutConfig[config.layout]" />
    </div>
</template>

主模板main.vue提供了Permission权限控制及KeepAlive路由缓存

<!-- 主缓存模板 -->
<script setup>
    import  ref  from \'vue\'
    import  useRoutes  from \'@/hooks/useRoutes\'
    import  tabsStore  from \'@/store/modules/tabs\'

    import Permission from \'@/components/Permission.vue\'
    import Forbidden from \'@/views/error/forbidden.vue\'

    const  route  = useRoutes()
    const store = tabsStore()
</script>

<template>
    <Scrollbar autohide gap="2">
        <div class="ve__layout-main__wrapper">
            <!-- 路由鉴权 -->
            <Permission :roles="route?.meta?.roles">
                <template #tips>
                    <Forbidden />
                </template>
                <!-- 路由缓存 -->
                <router-view v-slot=" Component ">
                    <transition name="ve-slide-right" mode="out-in" appear>
                        <KeepAlive :include="store.cacheViews">
                            <component v-if="store.reload" :is="Component" :key="route.path" />
                        </KeepAlive>
                    </transition>
                </router-view>
            </Permission>
        </div>
    </Scrollbar>
</template>

自定义路由菜单RouteMenu

  

如上图:路由菜单组件只需传入配置参数,即可切换不同的模式。

<RouteMenu :rootRouteEnable="false" />
<RouteMenu
    rootRouteEnable
    collapsed
    background="#292d3e"
    backgroundHover="#353b54"
    color="rgba(235,235,235,.7)"
/>
<RouteMenu
    mode="horizontal"
    background="#292d3e"
    backgroundHover="#353b54"
    color="rgba(235,235,235,.7)"
/>

RouteMenu.vue模板

<!-- 路由菜单 -->
<script setup>
  import  ref, computed, h, watch, nextTick  from \'vue\'
  import  useI18n  from \'vue-i18n\'
  import  Icon, useLink  from \'ve-plus\'
  import  useRoutes  from \'@/hooks/useRoutes\'
  import  appStore  from \'@/store/modules/app\'

  // 引入路由集合
  import mainRoutes from \'@/router/modules/main.js\'

  const props = defineProps(
    // 菜单模式(vertical|horizontal)
    mode:  type: String, default: \'vertical\' ,
    // 是否开启一级路由菜单
    rootRouteEnable:  type: Boolean, default: true ,
    // 是否要收缩
    collapsed:  type: Boolean, default: false ,

    // 菜单背景色
    background: String,
    // 滑过背景色
    backgroundHover: String,
    // 菜单文字颜色
    color: String,
    // 菜单激活颜色
    activeColor: String
  )

  const  t  = useI18n()
  const  jumpTo  = useLink()
  const  route, getActiveRoute, getCurrentRootRoute, getTreeRoutes  = useRoutes()
  const store = appStore()

  const rootRoute = computed(() => getCurrentRootRoute(route))
  const activeKey = ref(getActiveRoute(route))
  const menuOptions = ref(getTreeRoutes(mainRoutes))
  const menuFilterOptions = computed(() => 
    if(props.rootRouteEnable) 
      return menuOptions.value
    
    // 过滤掉一级菜单
    return menuOptions.value.find(item => item.path == rootRoute.value && item.children)?.children
  )
  console.log(\'根路由地址::>>\', rootRoute.value)
  console.log(\'过滤后路由地址::>>\', menuFilterOptions.value)

  watch(() => route.path, () => 
    nextTick(() => 
      activeKey.value = getActiveRoute(route)
    )
  )

  // 批量渲染图标
  const batchRenderIcon = (option) => 
    return h(Icon, name: option?.meta?.icon)
  

  // 批量渲染标题
  const batchRenderLabel = (option) => 
    return t(option?.meta?.title)
  

  // 路由菜单更新
  const handleUpdate = (key) => 
    jumpTo(key)
  
</script>

<template>
  <Menu
    class="veadmin__menus"
    v-model="activeKey"
    :options="menuFilterOptions"
    :mode="mode"
    :collapsed="collapsed && store.config.collapse"
    iconSize="18"
    key-field="path"
    :renderIcon="batchRenderIcon"
    :renderLabel="batchRenderLabel"
    :background="background"
    :backgroundHover="backgroundHover"
    :color="color"
    :activeColor="activeColor"
    @change="handleUpdate"
    style="border: 0;"
  />
</template>

vue-i18n国际化解决方案

vite-admin支持中英文/繁体三种语言模式,使用 "vue-i18n": "^9.2.2" 组件。

 

/**
 * 国际化配置
 * @author YXY
 */

import  createI18n  from \'vue-i18n\'
import  appStore  from \'@/store/modules/app\'

// 引入语言配置
import enUS from \'./en-US\'
import zhCN from \'./zh-CN\'
import zhTW from \'./zh-TW\'

// 默认语言
export const langVal = \'zh-CN\'

export default async (app) => 
    const store = appStore()
    const lang = store.lang || langVal

    const i18n = createI18n(
        legacy: false,
        locale: lang,
        messages: 
            \'en\': enUS,
            \'zh-CN\': zhCN,
            \'zh-TW\': zhTW
        
    )
    
    app.use(i18n)

Lang.vue模板

<script setup>
  import  ref  from \'vue\'
  import  useI18n  from \'vue-i18n\'
  import  appStore  from \'@/store/modules/app\'
  
  const  locale  = useI18n()
  const store = appStore()

  const langVal = ref(locale.value)
  const langOptions = ref([
    key: "zh-CN", label: "简体中文",
    key: "zh-TW", label: "繁体字",
    key: "en", label: "英文",
  ])

  const changeLang = () => 
    // 设置locale语言
    locale.value = langVal.value
    store.lang = locale.value
    // store.setLang(locale.value)
  
</script>

<template>
  <Dropdown v-model="langVal" :options="langOptions" placement="bottom" @change="changeLang">
    <div class="toolbar__item"><Icon name="ve-icon-lang" size="20" cursor /></div>
    <template #label="item">
      <div>
        item.label <span style="color: #999; font-size: 12px;">item.key</span>
      </div>
    </template>
  </Dropdown>
</template>

Vue3动态图表Hooks

vite-admin支持动态图表,使用 "echarts": "^5.4.2" 组件。

/**
 * 动态图表Hooks
 * @author YXY
 */

import  onMounted, onBeforeUnmount, ref  from \'vue\'
import * as echarts from \'echarts\'
import  useResizeObserver  from \'ve-plus\'

export function useEcharts(node, options) 
    let chartNode
    let chartRef = ref(null)

    const resizeHandle = () => 
        chartNode && chartNode.resize()
    

    onMounted(() => 
        if(node.value) 
            chartNode = echarts.init(node.value)
            chartNode.setOption(options)
            chartRef.value = chartNode
        
    )

    onBeforeUnmount(() => 
        chartNode.dispose()
    )
    // 自适应图表
    useResizeObserver(node, resizeHandle)

    return chartRef

通过useResizeObserver函数,支持图表自适应大小。

网站动态标题title

通过监听路由route更改,动态设置网站标题。

/**
 * 设置网站标题
 * @author YXY
 */

import  watch, unref  from \'vue\'
import  useRouter  from \'vue-router\'
import  useI18n  from \'vue-i18n\'

export function useTitle() 
    const  VITE_APP_TITLE  = import.meta.env
    const  currentRoute  = useRouter()
    const  t, locale  = useI18n()
    
    watch(
        () => [currentRoute.value.path, locale.value],
        () => 
            console.log(\'开始监听标题变化........\')
            
            const route = unref(currentRoute)
            const title = route?.meta?.title ? `$t(route?.meta?.title) - $VITE_APP_TITLE` : VITE_APP_TITLE
            console.log(\'监听标题\', title)
            document.title = title
        ,
        immediate: true
    )

动态路由缓存

ve-admin支持keepalive路由缓存功能。使用 pinia 替代 vuex 状态管理,使用 pinia-plugin-persistedstate 持久化存储。

https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

TabsView.vue模板

<script setup>
  import  ref, computed, watch, nextTick, h  from \'vue\'
  import  useRouter, useRoute  from \'vue-router\'
  import  useI18n  from \'vue-i18n\'
  import  appStore  from \'@/store/modules/app\'
  import  tabsStore  from \'@/store/modules/tabs\'

  const  t  = useI18n()
  const router = useRouter()
  const route = useRoute()

  const app = appStore()
  const store = tabsStore()

  const tabKey = ref(route.path)
  const tabOptions = computed(() => store.tabViews)

  // 滚动到当前路由
  const scrollToActiveRoute = () => 
    nextTick(() => 
      const activeRef = scrollbarRef.value.scrollbarWrap.querySelector(\'.actived\').offsetLeft
      scrollbarRef.value.scrollTo(left: activeRef, top: 0, behavior: \'smooth\')
    )
  

  // 监听路由(增加标签/缓存)
  watch(() => route.path, () => 
    tabKey.value = route.path

    const params = 
      path: route.path,
      name: route.name,
      meta: 
        ...route.meta
      
    
    store.addTabs(params)
    scrollToActiveRoute()
  , 
    immediate: true
  )

  // 右键菜单
  const scrollbarRef = ref()
  const selectedTab = ref()
  const contextmenuRef = ref()
  const contextmenuOptions = ref([
     key: \'refresh\', icon: \'ve-icon-reload\', label: \'tabview__contextmenu-refresh\' ,
     key: \'close\', icon: \'ve-icon-close\', label: \'tabview__contextmenu-close\' ,
     key: \'closeLeft\', icon: \'ve-icon-logout\', label: \'tabview__contextmenu-closeleft\' ,
     key: \'closeRight\', icon: \'ve-icon-logout1\', label: \'tabview__contextmenu-closeright\' ,
     key: \'closeOther\', icon: \'ve-icon-retweet\', label: \'tabview__contextmenu-closeother\' ,
     key: \'closeAll\', icon: \'ve-icon-close-square\', label: \'tabview__contextmenu-closeall\' ,
  ])
  const handleRenderLabel = (option) => 
    return t(option?.label)
  

  // 是否第一个标签
  const isFirstTab = () => 
    return selectedTab.value.path === store.tabViews[0].path || selectedTab.value.path === \'/home/index\'
  
  // 是否最后一个标签
  const isLastTab = () => 
    return selectedTab.value.path === store.tabViews[store.tabViews.length - 1].path
  

  const openContextMenu = (tab, e) => 
    selectedTab.value = tab
    contextmenuOptions.value[1].disabled = tab.meta?.isAffix
    contextmenuOptions.value[2].disabled = isFirstTab()
    contextmenuOptions.value[3].disabled = isLastTab()

    // 设置坐标
    contextmenuRef.value.setPos(e.clientX, e.clientY)
    contextmenuRef.value.show()
  

  const changeContextMenu = (v) => 
    if(v.key == \'refresh\') 
      if(tabKey.value !== selectedTab.value.path) 
        router.push(selectedTab.value.path)
      
      store.reloadTabs()
      return
    else if(v.key == \'close\') 
      store.removeTabs(selectedTab.value)
    else if(v.key == \'closeLeft\') 
      store.removeLeftTabs(selectedTab.value)
    else if(v.key == \'closeRight\') 
      store.removeRightTabs(selectedTab.value)
    else if(v.key == \'closeOther\') 
      store.removeOtherTabs(selectedTab.value)
    else if(v.key == \'closeAll\') 
      store.clearTabs()
    
    updateTabRoute()
  

  // 跳转更新路由
  const updateTabRoute = () => 
    const lastTab = store.tabViews.slice(-1)[0]
    if(lastTab && lastTab.path) 
      router.push(lastTab.path)
    else 
      router.push(\'/\')
    
  
  // 切换tab
  const changeTab = (tab) => 
    router.push(tab.path)
  
  // 关闭tab
  const closeTab = (tab) => 
    store.removeTabs(tab)
    updateTabRoute()
  
</script>

<template>
  <div v-if="app.config.tabsview" class="veadmin__tabsview">
    <Scrollbar ref="scrollbarRef" mousewheel>
      <ul class="tabview__wrap">
        <li
          v-for="(tab,index) in tabOptions" :key="index"
          :class="\'actived\': tabKey == tab.path"
          @click="changeTab(tab)"
          @contextmenu.prevent="openContextMenu(tab, $event)"
        >
          <Icon class="tab-icon" :name="tab.meta?.icon" />
          <span class="tab-title">$t(tab.meta?.title)</span>
          <Icon v-if="!tab.meta?.isAffix" class="tab-close" name="ve-icon-close" @click.prevent.stop="closeTab(tab)" />
        </li>
      </ul>
    </Scrollbar>
  </div>
  <!-- 右键菜单 -->
  <Dropdown
    ref="contextmenuRef"
    trigger="manual"
    :options="contextmenuOptions"
    fixed="true"
    :render-label="handleRenderLabel"
    @change="changeContextMenu"
    style="height: 0;"
  />
</template>

/**
 * 状态管理 Pinia
 */

import  createPinia  from \'pinia\'
// 引入pinia本地持久化存储
import piniaPluginPersistedstate from \'pinia-plugin-persistedstate\'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export default pinia
/**
 * 标签栏缓存状态管理
 * 在setup store中
 * ref() 就是 state 属性
 * computed() 就是 getters
 * function() 就是 actions
 * @author YXY
 * Q:282310962 WX:xy190310
 */

import  ref, nextTick  from \'vue\'
import  useRoute  from \'vue-router\'
import  defineStore  from \'pinia\'
import  appStore  from \'@/store/modules/app\'

export const tabsStore = defineStore(\'tabs\', () => 
        const currentRoute = useRoute()
        const store = appStore()

        /*state*/
        const tabViews = ref([]) // 标签栏列表
        const cacheViews = ref([]) // 缓存列表
        const reload = ref(true) // 刷新标识

        // 判断tabViews某个路由是否存在
        const tabIndex = (route) => 
            return tabViews.value.findIndex(item => item?.path === route?.path)
        

        /*actions*/
        // 新增标签
        const addTabs = (route) => 
            const index = tabIndex(route)
            if(index > -1) 
                tabViews.value.map(item => 
                    if(item.path == route.path) 
                        // 当前路由缓存
                        return Object.assign(item, route)
                    
                )
            else 
                tabViews.value.push(route)
            

            // 更新keep-alive缓存
            updateCacheViews()
        

        // 移除标签
        const removeTabs = (route) => 
            const index = tabIndex(route)
            if(index > -1) 
                tabViews.value.splice(index, 1)
            
            updateCacheViews()
        

        // 移除左侧标签
        const removeLeftTabs = (route) => 
            const index = tabIndex(route)
            if(index > -1) 
                tabViews.value = tabViews.value.filter((item, i) => item?.meta?.isAffix || i >= index)
            
            updateCacheViews()
        

        // 移除右侧标签
        const removeRightTabs = (route) => 
            const index = tabIndex(route)
            if(index > -1) 
                tabViews.value = tabViews.value.filter((item, i) => item?.meta?.isAffix || i <= index)
            
            updateCacheViews()
        

        // 移除其它标签
        const removeOtherTabs = (route) => 
            tabViews.value = tabViews.value.filter(item => item?.meta?.isAffix || item?.path === route?.path)
            updateCacheViews()
        

        // 移除所有标签
        const clearTabs = () => 
            tabViews.value = tabViews.value.filter(item => item?.meta?.isAffix)
            updateCacheViews()
        

        // 更新keep-alive缓存
        const updateCacheViews = () => 
            cacheViews.value = tabViews.value.filter(item => store.config.keepAlive || item?.meta?.isKeepAlive).map(item => item.name)
            console.log(\'cacheViews缓存路由>>:\', cacheViews.value)
        

        // 移除keep-alive缓存
        const removeCacheViews = (route) => 
            cacheViews.value = cacheViews.value.filter(item => item !== route?.name)
        

        // 刷新路由
        const reloadTabs = () => 
            removeCacheViews(currentRoute)
            reload.value = false
            nextTick(() => 
                updateCacheViews()
                reload.value = true
                document.documentElement.scrollTo( left: 0, top: 0 )
            )
        

        // 清空缓存
        const clear = () => 
            tabViews.value = []
            cacheViews.value = []
        

        return 
            tabViews,
            cacheViews,
            reload,
            addTabs,
            removeTabs,
            removeLeftTabs,
            removeRightTabs,
            removeOtherTabs,
            clearTabs,
            reloadTabs,
            clear
        
    ,
    // 本地持久化存储(默认存储localStorage)
    
        // persist: true
        persist: 
            storage: localStorage,
            paths: [\'tabViews\', \'cacheViews\']
        
    
)

vite.config.js配置文件

import  defineConfig, loadEnv  from \'vite\'
import vue from \'@vitejs/plugin-vue\'
import  resolve  from \'path\'
import  wrapEnv  from \'./src/utils/env\'

// https://vitejs.dev/config/
export default defineConfig(( mode ) => 
    const viteEnv = loadEnv(mode, process.cwd())
    const env = wrapEnv(viteEnv)

    return 
        plugins: [vue()],

        // base: \'/\',
        // mode: \'development\', // development|production

        /*构建选项*/
        build: 
            // minify: \'esbuild\', // 打包方式 esbuild(打包快)|terser
            // chunkSizeWarningLimit: 2000, // 打包大小警告
            // rollupOptions: 
            //     output: 
            //         chunkFileNames: \'assets/js/[name]-[hash].js\',
            //         entryFileNames: \'assets/js/[name]-[hash].js\',
            //         assetFileNames: \'assets/[ext]/[name]-[hash].[ext]\',
            //     
            // 
        ,
        esbuild: 
            // 打包去除 console.log 和 debugger
            drop: env.VITE_DROP_CONSOLE ? [\'console\', \'debugger\'] : []
        ,

        /*开发服务器选项*/
        server: 
            // 端口
            port: env.VITE_PORT,
            // 是否浏览器自动打开
            open: env.VITE_OPEN,
            // 开启https
            https: env.VITE_HTTPS,
            // 代理配置
            proxy: 
                // ...
            
        ,

        resolve: 
            // 设置别名
            alias: 
                \'@\': resolve(__dirname, \'src\'),
                \'@assets\': resolve(__dirname, \'src/assets\'),
                \'@components\': resolve(__dirname, \'src/components\'),
                \'@views\': resolve(__dirname, \'src/views\'),
                // 解决vue-i18n警告提示:You are running the esm-bundler build of vue-i18n.
                \'vue-i18n\': \'vue-i18n/dist/vue-i18n.cjs.js\'
            
        
    
)

Okra,基于 vite4.x+pinia+vePlus 开发后台管理系统模板就分享到这里,希望对大家有些帮助哈~~

Electron-Vite2-MacUI桌面管理框架|electron13+vue3.x仿mac桌面UI

基于vue3.0.11+electron13仿制macOS桌面UI管理系统ElectronVue3MacUI

前段时间有分享一个vue3结合electron12开发后台管理系统项目。今天要分享的是最新研发的跨平台仿macOS桌面UI管理框架。使用了最新前端技术electron13+vite2.3+vue3搭建开发。支持多窗口、动态壁纸、程序坞DOCK菜单、可拖拽等功能。

一、实现技术

  • 编辑器:Vscode
  • 框架技术:Vite2.3.4+Vue3.0.11+Vuex4+VueRouter@4
  • 跨端框架:Electron13.0.1
  • 打包工具:vue-cli-plugin-electron-builder
  • UI组件库:Element-Plus^1.0.2 (饿了么vue3组件库)
  • 弹窗组件:MacLayer (vue3弹窗v3layer改进版)
  • 图表组件:Echarts^5.1.1
  • 模拟请求:Mockjs1.1.0

二、功能特性

✅经典的图标+dock菜单模式
✅流畅的操作体验
✅可拖拽桌面+程序坞dock菜单
✅符合macOS big sur操作窗口管理
✅丰富的视觉效果,自定义桌面个性壁纸
✅可视化创建多窗口,支持拖拽/缩放/最大化,可传入自定义组件页面。

三、项目分层结构

项目中的弹窗分为vue3自定义弹窗组件和electron新开弹窗窗口。

◆ Electron桌面公共布局模板

菜单栏位于屏幕顶部。程序坞Dock菜单位于屏幕底部。位于二者之间的称为桌面。

<!-- //Main主模块模板 -->
<template>
    <div class="macui__wrapper" :style="{\'--themeSkin\': store.state.skin}">
        <div v-if="!route.meta.isNewin" class="macui__layouts-main flexbox flex-col">
            <!-- //顶部导航 -->
            <div class="layout__topbar">
                <TopNav />
            </div>
            
            <div class="layout__workpanel flex1 flexbox" @contextmenu="handleCtxMenu">
                <div class="panel__mainlayer flex1 flexbox" style="margin-bottom: 70px;">
                    <DeskMenu />
                </div>
            </div>

            <!-- //底部Dock菜单 -->
            <Dock />
        </div>
        <router-view v-else class="macui__layouts-main flexbox flex-col macui__filter"></router-view>
    </div>
</template>

◆ Vue3+Electron实现无边框导航条

如上图:顶部导航条均是自定义组件实现功能。同时支持自定义标题、背景及文字颜色等功能。

下拉菜单则是使用的element-plus中的Dropdown组件实现功能。

<template>
    <WinBar bgcolor="rgba(29,29,32,.7)" color="#fff" zIndex="1000" title="false">
        <template #menu>
            ...
            <el-dropdown placement="bottom-start" @command="handleMenuClicked">
                <a class="menu menu-label">首页</a>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item command="2-1">首页</el-dropdown-item>
                        <el-dropdown-item command="2-2">控制台</el-dropdown-item>
                        <el-dropdown-item command="2-3">自定义面包屑导航</el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
            ...
        </template>
        <template #wbtn>
            <MsgMenu />
            <a class="menu" title="换肤" @click="handleSkinWin"><i class="iconfont el-icon-magic-stick"></i></a>
            <a class="menu" :class="{\'on\': isAlwaysOnTop}" :title="isAlwaysOnTop ? \'取消置顶\' : \'置顶\'" @click="handleAlwaysTop"><i class="iconfont icon-ding"></i></a>
            <a class="menu" title="搜索"><i class="iconfont el-icon-search"></i></a>
            <Setting />
            <a class="menu menu-label">{{currentDate}}</a>
            <el-divider direction="vertical" />
            <Avatar @logout="handleLogout" />
            <el-divider direction="vertical" />
        </template>
    </WinBar>
</template>

对于导航条组件的一些实现方式,大家如果感兴趣可以去看看之前的分享文章哈~

https://www.cnblogs.com/xiaoyan2017/p/14449570.html

◆ Vite2+Electron实现dock动效菜单

底部dock可自适应布局,支持拖拽排序,采用了毛玻璃模糊背景效果。

<template>
    <div class="macui__dock">
        <div class="macui__dock-wrap macui__filter" ref="dockRef">
            <a class="macui__dock-item"><span class="tooltips">appstore</span><img src="/static/mac/appstore.png" /></a>
            <a class="macui__dock-item active"><span class="tooltips">launchpad</span><img src="/static/mac/launchpad.png" /></a>
            ...
        </div>
    </div>
</template>
// 拖拽Dock菜单
const dragDockMenu = () => {
    Sortable.create(dockRef.value, {
        handle: \'.macui__dock-item\',
        filter: \'.macui__dock-filter\',
        animation: 200,
        delay: 0,
        onEnd({ newIndex, oldIndex }) {
            console.log(\'新索引:\', newIndex)
            console.log(\'旧索引:\', oldIndex)
        }
    })
}

// 打开地图
const openMaps = () => {
    createWin({
        title: \'地图\',
        route: \'/map\',
        width: 1000,
        height: 500,
    })
}

// 打开日历
const openCalendar = () => {
    createWin({
        title: \'日历\',
        route: \'/calendar\',
        width: 500,
        height: 500,
        resize: false,
    })
}
.macui__dock {display: flex; justify-content: center; height: 60px; transform: translateX(-50%); position: fixed; left: 50%; bottom: 5px; z-index: 10010;}
.macui__dock-wrap {background: rgba(255,255,255,.3); box-shadow: 0 1px 1px rgba(29,29,32,.15); border-radius: 15px; display: flex; align-items: center; height: 60px; padding: 0 10px;}
.macui__dock-item {color: #fff; cursor: pointer; display: inline-block; position: relative;}
.macui__dock-item .tooltips {display: none; background: rgba(0,0,0,.3); border-radius: 5px; padding: 4px 8px; text-align: center; width: 100%; position: absolute;}
.macui__dock-item img {height: 50px; width: 50px; object-fit: cover; transition: all .2s;}
.macui__dock-item.active:after {content: \'\'; background: rgba(29,29,32,.9); border-radius: 50%; margin-left: -2px; height: 4px; width: 4px; position: absolute; left: 50%; bottom: -2px;}
.macui__dock-item:hover .tooltips {display: block; opacity: 1; top: -70px; animation: animTooltips .3s;}
.macui__dock-item:hover img {margin: 0 2em; transform: scale(2) translateY(-10px);}

◆ Vue3仿mac弹窗组件

项目中如上图的弹窗组件,都是使用vue3自定义弹窗组件v3layer改进版实现功能。

v3layer支持超过30+种参数自定义配置,支持拖拽、四角缩放、全屏等功能,并且新增了支持动态传入组件页面功能。

import Home from \'@/views/home/index.vue\'
import ControlPanel from \'@/views/home/dashboard.vue\'
import CustomTpl from \'@/views/home/customTpl.vue\'
import Table from \'@/views/component/table/custom.vue\'
import Form from \'@/views/component/form/all.vue\'
import UserSetting from \'@/views/setting/manage/user/index.vue\'
import Ucenter from \'@/views/setting/ucenter.vue\'

const deskmenu = [
    {
        type: \'component\',
        icon: \'el-icon-monitor\',
        title: \'首页\',
        component: Home,
    },
    {
        type: \'component\',
        icon: \'icon-gonggao\',
        title: \'控制面板\',
        component: ControlPanel,
    },
    {
        type: \'component\',
        img: \'/static/mac/reminders.png\',
        title: \'自定义组件模板\',
        component: CustomTpl,
        area: [\'600px\', \'360px\'],
    },
    {
        type: \'iframe\',
        img: \'/static/vite.png\',
        title: \'vite.js官方文档\',
        component: \'https://cn.vitejs.dev/\',
    },
    {
        type: \'component\',
        icon: \'el-icon-s-grid\',
        title: \'表格\',
        component: Table,
    },
    // ...
]
// 点击菜单
const handleMenuClicked = (menu) => {
    let icon = menu.icon ? `<i class="iconfont ${menu.icon}"></i>` : menu.img ? `<img src="${menu.img}" />` : \'\'
    let title = menu.title ? `<div class="macui__customTitle">${icon}${menu.title}</div>` : \'标题\'
    v3layer({
        type: menu.type || null,
        layerStyle: menu.style || \'\',
        customClass: \'macui__deskLayer\',
        title: title,
        content: menu.component || \'<div style="color:red;margin-top:50px;">嗷嗷!您似乎忘记了填充内容。</div>\',
        area: menu.area || [\'1000px\', \'550px\'],
        shade: false,
        xclose: true,
        maximize: menu.maximize != false ? true : false,
        resize: menu.resize != false ? true : false,
        fullscreen: menu.fullscreen || false,
        zIndex: 500,
        topmost: true,
    })
}

大家如果对v3layer的实现感兴趣的话,可以去看看之前的这篇分享文章。

https://www.cnblogs.com/xiaoyan2017/p/14221729.html

◆ Vite2+Electron项目打包配置

基于vite2和electron搭建的项目,如果需要打包成.exe文件,需要新建一个 electron-builder.json 的配置文件。

{
    "productName": "electron-macui",
    "appId": "cc.xiaoyan.electron-macui",
    "copyright": "Copyright © 2021-present",
    "author": "Power By XiaoYan | Q:282310962  WX:xy190310"
    "compression": "maximum",
    "asar": false,
    "extraResources": [
        {
            "from": "./resource",
            "to": "resource"
        }
    ],
    "nsis": {
        "oneClick": false,
        "allowToChangeInstallationDirectory": true,
        "perMachine": true,
        "deleteAppDataOnUninstall": true,
        "createDesktopShortcut": true,
        "createStartMenuShortcut": true,
        "shortcutName": "ElectronMacUI"
    },
    "win": {
        "icon": "./resource/shortcut.ico",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}",
        "target": [
            {
                "target": "nsis",
                "arch": ["ia32"]
            }
        ]
    },
    "mac": {
        "icon": "./resource/shortcut.icns",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
    },
    "linux": {
        "icon": "./resource",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
    }
}

好了,以上就是基于vite2.x+electron13开发跨端仿制macOS桌面UI后台管理系统,希望对大家有所帮助哈!

最后附上一个vue3+electron跨平台桌面端仿QQ聊天实例

https://www.cnblogs.com/xiaoyan2017/p/14454624.html

 

以上是关于Vite-Admin后台管理系统|vite4+vue3+pinia前端后台框架实例的主要内容,如果未能解决你的问题,请参考以下文章

绿色的银行类cms管理系统模板——后台

Vite 4.0 正式发布!

k6:增加VU数量的每个阶段如何管理rps-limit

性能指标公式平均每个用户发出的请求数量R=u*C*T/VU公式中u是啥意思

VU|DBA校友风采——沈宇锋:快递智能领域中的黑马

各城市web相关职业招聘数据共享