- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
基于 tauri+vite4+pinia2 跨端后台管理系统应用实例 TauriAdmin .
tauri-admin 基于最新跨端技术 Tauri Rust webview2 整合 Vite4 构建桌面端通用后台管理解决方案。 搭载轻量级ve-plus组件库 、支持 多窗口切换管理、vue-i18n多语言包、动态路由权限、常用业务功能模块、3种布局模板及动态路由缓存 等功能.
目前tauri已经迭代到1.4,如果大家对tauri+vue3创建多窗口项目感兴趣,可以去看看之前的这篇分享文章.
https://www.cnblogs.com/xiaoyan2017/p/16812092.html 。
使用tauri脚手架搭配vite4构建项目,整体采用vue3 setup语法编码开发.
import { createApp } from "vue" import "./styles.scss" import App from "./App.vue" // 引入路由及状态管理 import Router from './router' import Pinia from './pinia' // 引入插件配置 import Libs from './libs' const app = createApp(App) app .use(Router) .use(Pinia) .use(Libs) .mount( "#app")
提供了三种常见的布局模板,大家也可以定制喜欢的模板样式.
< script setup > import { computed } from ' vue ' import { appStore } from ' @/pinia/modules/app ' // 引入布局模板 import Columns from ' ./template/columns/index.vue ' import Vertical from ' ./template/vertical/index.vue ' import Transverse from ' ./template/transverse/index.vue ' const store = appStore() const config = computed(() => store.config) const LayoutConfig = { columns: Columns, vertical: Vertical, transverse: Transverse } </ script > < template > < div class ="veadmin__container" :style ="{'--themeSkin': store.config.skin}" > < component :is ="LayoutConfig[config.layout]" /> </ div > </ template >
如上图:配置router路由信息.
/* * * 路由配置 * @author YXY Q:282310962 */ import { appWindow } from '@tauri-apps/api/window' import { createRouter, createWebHistory } from 'vue-router' import { appStore } from '@/pinia/modules/app' import { hasPermission } from '@/hooks/usePermission' import { loginWin } from '@/multiwins/actions' // 批量导入modules路由 const modules = import.meta.glob('./modules/*.js', { eager: true }) const patchRoutes = Object.keys(modules).map(key => modules[key]. default ).flat() /* * * @description 动态路由参数配置 * @param path ==> 菜单路径 * @param redirect ==> 重定向地址 * @param component ==> 视图文件路径 * 菜单信息(meta) * @param meta.icon ==> 菜单图标 * @param meta.title ==> 菜单标题 * @param meta.activeRoute ==> 路由选中(默认空 route.path) * @param meta.rootRoute ==> 所属根路由选中(默认空) * @param meta.roles ==> 页面权限 ['admin', 'dev', 'test'] * @param meta.breadcrumb ==> 自定义面包屑导航 [{meta:{...}, path: '...'}] * @param meta.isAuth ==> 是否需要验证 * @param meta.isHidden ==> 是否隐藏页面 * @param meta.isFull ==> 是否全屏页面 * @param meta.isKeepAlive ==> 是否缓存页面 * @param meta.isAffix ==> 是否固定标签(tabs标签栏不能关闭) * */ const routes = [ // 首页 { path: '/' , redirect: '/home' }, // 错误模块 { path: '/:pathMatch(.*)*' , component: () => import('@views/error/404.vue' ), meta: { title: 'page__error-notfound' } }, ...patchRoutes ] const router = createRouter({ history: createWebHistory(), routes }) // 全局钩子拦截 router.beforeEach((to, from, next) => { // 开启加载提示 loading({ text: 'Loading...' , background: 'rgba(70, 255, 170, .1)' , onOpen: () => { console.log( '开启loading' ) }, onClose: () => { console.log( '关闭loading' ) } }) const store = appStore() if (to?.meta?.isAuth && ! store.isLogged) { loginWin() loading.close() } else if (!hasPermission(store.roles, to?.meta? .roles)) { // 路由鉴权 appWindow? .show() next( '/error/forbidden' ) loading.close() Notify({ title: '访问限制!' , description: ` <span style="color: #999;">当前登录角色 ${store.roles} 没有操作权限,请联系管理员授权后再操作。</div>`, type: 'danger' , icon: 've-icon-unlock' , time: 10 }) } else { appWindow ? .show() next() } }) router.afterEach(() => { loading.close() }) router.onError(error => { loading.close() console.warn( 'Router Error》》' , error.message); }) export default router
如上图:vue3项目搭配pinia进行状态管理.
/* * * 状态管理 Pinia util * @author YXY */ import { createPinia } from 'pinia' // 引入pinia本地持久化存储 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' const pinia = createPinia() pinia.use(piniaPluginPersistedstate) export default pinia
项目中的三种模板提供了不同的路由菜单。均是基于Menu组件封装的RouteMenu菜单.
< 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 ' @/pinia/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: { type: String, default : ' transparent ' }, // 滑过背景色 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 }) watch(() => route.path, () => { nextTick(() => { activeKey.value = getActiveRoute(route) }) }) // 批量渲染图标 const batchRenderIcon = (option) => { return h(Icon, {name: option ? .meta ? .icon ?? ' ve-icon-verticleleft ' }) } // 批量渲染标题 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 >
。
Menu组件支持横向/竖向排列,调用非常简单.
< RouteMenu :rootRouteEnable ="false" backgroundHover ="#f1f8fb" activeColor ="#24c8db" /> < RouteMenu rootRouteEnable collapsed background ="#193c47" backgroundHover ="#1a5162" color ="rgba(235,235,235,.7)" activeColor ="#24c8db" :collapsedIconSize ="20" /> < RouteMenu mode ="horizontal" background ="#193c47" backgroundHover ="#1a5162" color ="rgba(235,235,235,.7)" activeColor ="#24c8db" arrowIcon ="ve-icon-caretright" />
tauri-vue3-admin项目使用vue-i18n进行多语言处理.
import { createI18n } from 'vue-i18n' import { appStore } from '@/pinia/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) }
项目支持配置路由页面缓存功能。可以在全局pinia/modules/app.js中配置,也可以在router配置项meta中配置isKeepAlive: true.
< 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" size ="12" @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 >
import { ref, nextTick } from 'vue' import { useRoute } from 'vue-router' import { defineStore } from 'pinia' import { appStore } from '@/pinia/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 ) } // 更新keep-alive缓存 updateCacheViews() } // 移除左侧标签 const removeLeftTabs = (route) => { const index = tabIndex(route) if (index > -1 ) { tabViews.value = tabViews.value.filter((item, i) => item?.meta?.isAffix || i >= index) } // 更新keep-alive缓存 updateCacheViews() } // 移除右侧标签 const removeRightTabs = (route) => { const index = tabIndex(route) if (index > -1 ) { tabViews.value = tabViews.value.filter((item, i) => item?.meta?.isAffix || i <= index) } // 更新keep-alive缓存 updateCacheViews() } // 移除其它标签 const removeOtherTabs = (route) => { tabViews.value = tabViews.value.filter(item => item?.meta?.isAffix || item?.path === route? .path) // 更新keep-alive缓存 updateCacheViews() } // 移除所有标签 const clearTabs = () => { tabViews.value = tabViews.value.filter(item => item?.meta? .isAffix) // 更新keep-alive缓存 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: { // key: 'tabsState', storage: localStorage, paths: [ 'tabViews', 'cacheViews' ] } } )
{ "build" : { "beforeDevCommand": "yarn dev" , "beforeBuildCommand": "yarn build" , "devPath": "http://localhost:1420" , "distDir": "../dist" , "withGlobalTauri": false }, "package" : { "productName": "tauri-admin" , "version": "0.0.0" }, "tauri" : { "allowlist" : { "all": true , "shell" : { "all": false , "open": true } }, "bundle" : { "active": true , "targets": "all" , "identifier": "com.tauri.admin" , "icon" : [ "icons/32x32.png" , "icons/128x128.png" , "icons/128x128@2x.png" , "icons/icon.icns" , "icons/icon.ico" ] }, "security" : { "csp": null }, "windows" : [ { "fullscreen": false , "resizable": true , "title": "tauri-admin" , "width": 1000 , "height": 640 , "center": true , "decorations": false , "fileDropEnabled": false , "visible": false } ], "systemTray" : { "iconPath": "icons/icon.ico" , "iconAsTemplate": true , "menuOnLeftClick": false } } }
[package] name = "tauri-admin" version = "0.0.0" description = "基于tauri+vue3+vite4+pinia轻量级桌面端后台管理Tauri-Admin" authors = "andy <282310962@qq.com>" license = "" repository = "" edition = "2023" # See more keys and their definitions at https: // doc.rust-lang.org/cargo/reference/manifest.html [build - dependencies] tauri -build = { version = "1.4", features = [] } [dependencies] tauri = { version = "1.4", features = ["api-all", "icon-ico", "icon-png", "system-tray" ] } serde = { version = "1.0", features = ["derive" ] } serde_json = "1.0" [features] # this feature is used for production builds or when `devPath` points to the filesystem # DO NOT REMOVE !! custom -protocol = ["tauri/custom-protocol"]
OK,基于tauri+vue3跨端后台管理系统就分享到这里。希望对大家有所帮助哈~~ 。
最后附上两个最新开发的Electron和uniapp跨端项目实例 。
https://www.cnblogs.com/xiaoyan2017/p/17468074.html 。
https://www.cnblogs.com/xiaoyan2017/p/17507581.html 。
。
最后此篇关于Tauri-Admin通用后台管理系统|tauri+vue3+pinia桌面端后台EXE的文章就讲到这里了,如果你想了解更多关于Tauri-Admin通用后台管理系统|tauri+vue3+pinia桌面端后台EXE的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个使用 css 列的下拉菜单,当我使用 jquery slide() 时,它会调整下拉框的大小,并重排内容直到达到完整高度。 这是一个工作示例 https://codepen.io/peterg
我有一个带有嵌套 ScrollViewer 的 Expander,如下所示: 代码(简化版)
我想在所有 ajax 调用之后调用一些 javascript 函数。我知道如何调用每个单独的 ajax 调用中的函数,如下所示: function xyz() { if (window.XMLHttp
我想将值从应用程序端传递到 api。在此 api 调用中传递图像、名字、电子邮件、电话和位置。在 Debug模式下,检查值不会被传递。 代码下方 File file = null;
我正在尝试在使用reportlab生成的pdf中的表格后插入分页符,我正在使用以下函数生成pdf: def render_to_pdf(template_src, context_dict): t
CBPeripheralManager 是否有推荐的方法来终止连接。到目前为止我发现的最好的方法就是干脆不响应动态值,然后 BLE 堆栈似乎关闭了连接,但这似乎很粗糙。 一定有更好的方法吗? 最佳答案
我的 API 的 REST 端位于以下地址:http://test.jll.aplikacje-dedykowane.pl/rest/warehouse/all 。现在,我尝试返回在此页面准备的 JS
我有以下 CPP 代码。我想做的是,当我的 native 端发生错误时,我会通知 Java 该错误。我用了How can I catch SIGSEGV (segmentation fault) an
限制对象的方法之一是像这样给函数添加限制 def ten_objects(): obj = Model.objects.all()[0:10] # limit to 10 retur
我目前在电信公司实习,这是一个专业,也是本科生。我有很多选择。据我所知,我知道独立应用程序端的 c、c++、c#、java 语言,在移动端我尝试进入 android 世界,也知道 php、mysql、
我想让我的边框底部看起来像这样: 有一个 flex 的末端。目前它看起来像这样: 我尝试使用的 CSS 是 border-bottom-right-radius: 10px;。代码如下所示:
我有一个 Flutter 项目,突然间,据我所知,我没有做任何特别的事情..Android 端开始显示错误,我完全迷路了,我完全不知道哪里出了问题,也不知道为什么会这样。 这就是我打开 android
我有一个自定义对象列表 (List) 。我需要将此数据发送到 React Native 端以显示在平面列表中。我该怎么做?这个列表出现在 类 NativeToReact(reactContext:Re
我有这个代码: #if defined(NOT_STANDALONE) JNIEXPORT void JNICALL sumTraces (JNIEnv* env, jclass caller,
我有一个定义一对多模型关系的 Django 应用程序。模型如下所示: from django.db import models # Create your models here. class Str
我有以下代码,它根据 IFrame 内容的大小调整 IFrame 的大小: function setIframeHeight(id) {
如何创建自定义过滤器 angularjs javascript Controller 端?我想通过 SegmentId 在名为段的数组中搜索,以创建过滤器,该过滤器通过 SegmentId 在段数组搜
我的代码在 netbeans 8.0.2 中我几乎尝试了所有方法,但没有结果。请帮助我。如何在 netbeans 中显示它? 最佳答案 您只需单击源包(源文件),它就会显示您的项目文件。 关于java
我想这是纯 C++ 问题和 OpenGL 问题之间的一种交叉。我有一个统一的缓冲区,并在其中分配 sizeof(ShaderData) 字节的空间。我在着色器的 GPU 端使用 std140 布局。
我对 Hadoop 中 reduce 端的文件合并过程的理解有问题,因为它在“Hadoop:权威指南”(Tom White)中有所描述。引用它: When all the map outputs ha
我是一名优秀的程序员,十分优秀!