问题背景
市面上关于pc桌面端的后台管理系统,已经有很多成熟的应用方案。然而手机端OA系统管理却没有一些比较令人满意的产品。原因可能是手机设备尺寸有限、没有像pc端操作方便。还有就是很难颠覆传统的左侧菜单列表+右侧操作区的思想。
于是历经一个月潜心研发,我的原创新作uniapp-vue3-os手机os桌面系统项目正式见面了。
带来一种全新os桌面式系统后台管理解决方案!
只需要简单配置桌面JSON菜单,就能轻松跳转到相应的页面。
技术栈
- 编辑器:HbuilderX 4.15
- 技术框架:uniapp+vite5.x+vue3+pinia2
- UI组件库:uni-ui+uv-ui(uniapp vue3组件库)
- 弹框组件:uv3-popup(基于uniapp+vue3自定义弹框组件)
- 表格组件:uv3-table(基于uniapp+vue3增强版表格)
- 模拟数据:mockjs(用于自定义表格模拟数据)
- 缓存技术:pinia-plugin-unistorage
- 支持编译:h5+小程序端+app端
支持运行编译到h5端+小程序端+APP端。另外运行到pc端效果依然棒棒哒~
为了达到整体效果一致性,摒弃传统的输入框登录验证,采用全新的数字密码解锁模式。
<template> <uv3-layout> <view class="uv3__launch"> <view v-if="splashScreen" class="uv3__launch-splash" @touchstart="handleTouchStart" @touchmove="handleTouchUpdate"> <view class="body flex1"> <Today /> </view> <view class="foot"> <view class="btn flex-c"><image src="/static/svg/flashlight.svg" mode="widthFix" /></view> <view class="text flex1 flexbox flex-col"> <uni-icons type="up" color="#fff" /> 上滑解锁 </view> <view class="btn flex-c"><image src="/static/svg/camera.svg" mode="widthFix" /></view> </view> </view> <view v-else class="uv3__launch-keyboard" :class="{'closed': authPassed}"> <view class="uv3__launch-pwdwrap"> <view class="text">密码解锁</view> <view class="circle flexbox"> <view v-for="(num, index) in passwordArr" :key="index" class="dot" :class="{'active': num <= pwdValue.length}"></view> </view> </view> <view class="uv3__launch-numwrap"> <view v-for="(item, index) in keyNumbers" :key="index" class="numbox" @click="handleClickNum(item.num)"> <view class="num">{{item.num}}</view><view class="letter">{{item.letter}}</view> </view> </view> <view class="foot flexbox"> <view class="c-fff">紧急呼叫</view> <view v-if="pwdValue" class="c-fff" @click="handleDel">删除</view> <view v-else class="c-fff" @click="handleBack">返回</view> </view> </view> </view> </uv3-layout> </template>
<script setup> import { ref, computed, inject, nextTick } from 'vue' import { authStore } from '@/pinia/modules/auth' import { uuid, guid } from '@/utils' // 数字键盘输入值 const pwdValue = ref('') const keyNumbers = ref([ {num: 1, letter: ''}, {num: 2, letter: 'ABC'}, {num: 3, letter: 'DEF'}, {num: 4, letter: 'GHI'}, {num: 5, letter: 'JKL'}, {num: 6, letter: 'MNO'}, {num: 7, letter: 'PQRS'}, {num: 8, letter: 'TUV'}, {num: 9, letter: 'WXYZ'}, {num: 0, letter: '+'}, ]) // 点击数字键盘 const handleClickNum = (num) => { let pwdLen = passwordArr.value.length if(pwdValue.value.length >= pwdLen) return pwdValue.value += num // 校验密码 if(pwdValue.value.length == pwdLen) { // 验证通过 if(pwdValue.value == password.value) { authPassed.value = true authState.setAuthorization(guid(32)) authState.setUserInfo({ avatar: '', username: 'Andy', token: uuid(64) }) setTimeout(() => { uni.redirectTo({ url: '/pages/desk/desk' }) }, 200) }else { setTimeout(() => { pwdValue.value = '' }, 200) } } } // 删除 const handleDel = () => { let num = Array.from(pwdValue.value) num.splice(-1, 1) pwdValue.value = num.join('') } </script>
最终效果如上图:效果非常丝滑,验证解锁更加方便快捷。
公共模板文件
定义公共布局模板,初始化一些桌面图标变量。
<script setup> import { ref } from 'vue' import { appStore } from '@/pinia/modules/app' const appState = appStore() // #ifdef MP-WEIXIN defineOptions({ /** * 解决小程序class、id透传问题(vue3写法) * manifest.json中配置mergeVirtualHostAttributes: true, 在微信小程序平台不生效,组件外部传入的class没有挂到组件根节点上 * https://github.com/dcloudio/uni-ui/issues/753 */ options: { virtualHost: true } }) // #endif const props = defineProps({ showBackground: { type: [Boolean, String], default: true }, }) // 自定义变量(桌面图标) const deskVariable = ref({ '--icon-radius': '15px', // 圆角 '--icon-size': '118rpx', // 图标尺寸 '--icon-gap-col': '25px', // 水平间距 '--icon-gap-row': '45px', // 垂直间距 '--icon-labelSize': '12px', // 标签文字大小 '--icon-labelColor': '#fff', // 标签颜色 '--icon-fit': 'contain', // 图标自适应模式 }) </script> <template> <view class="uv3__container flexbox flex-col flex1" :style="deskVariable"> <!-- 顶部插槽 --> <slot name="header" /> <!-- 内容区 --> <view class="uv3__scrollview flex1"> <slot /> </view> <!-- 底部插槽 --> <slot name="footer" /> <!-- 背景图(修复小程序不支持background背景图) --> <image v-if="showBackground" class="fixwxbg" :src="appState.config.skin || '/static/skin/theme.png'" mode="scaleToFill" /> </view> </template>
uniapp桌面栅格菜单
桌面菜单采用json配置
/** * label 图标标题 * imgico 图标(本地或网络图片) 当type: 'icon'则为uni-icons图标名,当type: 'widget'则为自定义小部件标识名 * type 图标类型(icon | widget) icon为uni-icons图标、widget为自定义小部件 * path 跳转路由页面 * link 跳转外部链接 * hideLabel 是否隐藏图标标题 * background 自定义图标背景色 * size 栅格磁贴布局(16种) 1x1 1x2 1x3 1x4、2x1 2x2 2x3 2x4、3x1 3x2 3x3 3x4、4x1 4x2 4x3 4x4 * onClick 点击图标回调函数 * children 二级菜单 */
<template> <swiper class="uv3__deskmenu" :indicator-dots="true" indicator-color="rgba(255,255,255,.5)" indicator-active-color="#fff" > <swiper-item v-for="(mitem, mindex) in deskMenu" :key="mindex"> <view class="uv3__gridwrap"> <view v-for="(item, index) in mitem.list" :key="index" class="uv3__gridwrap-item" @click="handleClickDeskMenu(item)"> <!-- 图标 --> <view class="ico" :style="{'background': item.background}"> <!-- 二级菜单 --> <template v-if="Array.isArray(item.children)"> <view class="uv3__gridwrap-thumb"> </view> </template> <template v-else> <template v-if="item.type == 'widget'"> <!-- 自定义部件 --> <component :is="item.imgico" /> </template> <template v-else> <!-- 自定义图标 --> </template> </template> </view> <!-- 标题 --> <view v-if="!item.hideLabel" class="label clamp2">{{item.label}}</view> </view> </view> </swiper-item> </swiper> <!-- 二级弹窗式菜单 --> <Popup v-model="deskPopupVisible"> <view class="uv3__deskpopup"> </view> </Popup> </template>
点击桌面菜单,处理不同的事件逻辑。
const handleClickDeskMenu = (item) => { if(item.link) { // 链接 openURL(item.link) }else if(item.path) { // 页面路由地址 uni.navigateTo({ url: item.path.substr(0, 1) == '/' ? item.path : '/' + item.path }) }else if(Array.isArray(item.children)) { // 二级菜单 deskPopupMenu.value = item deskPopupVisible.value = true } // 绑定点击事件 typeof item.onClick === 'function' && item.onClick() }
桌面菜单json配置片段
const deskMenu = ref([ { pid: 20240507001, list: [ {label: '今日', imgico: 'today', type: 'widget', hideLabel: true, size: '2x1'}, {label: '天气', imgico: 'weather', type: 'widget', hideLabel: true, size: '2x1'}, {label: '日历', imgico: 'fullcalendar', type: 'widget', path: 'pages/calendar/index', size: '4x2'}, // {label: '日历', imgico: 'date', type: 'widget', size: '2x2'}, // {label: '备忘录', imgico: 'note', type: 'widget', size: '2x2'}, {label: 'audio', imgico: 'audio', type: 'widget', size: '2x1'}, { label: '相册', imgico: '/static/svg/huaban.svg', background: '#00aa7f', onClick: () => { // ... } }, ] }, { pid: 20240510001, list: [ {label: 'Github', imgico: '/static/svg/github.svg', background: '#607d8b', size: '3x1'}, {label: '码云Gitee', imgico: '/static/svg/gitee.svg', background: '#bb2124',}, {label: '抖音', imgico: '/static/svg/douyin.svg', background: '#1c0b1a', size: '1x2'}, {label: 'ChatGPT', imgico: '/static/svg/chatgpt.svg', hideLabel: true, background: '#11b6a7', size: '3x2'}, ] }, { pid: 20240511003, list: [ {label: 'uni-app', imgico: '/static/uni.png', link: 'https://uniapp.dcloud.net.cn/'}, {label: 'vitejs官方文档', imgico: '/static/vite.png', link: 'https://vitejs.dev/'}, { label: '主题壁纸', imgico: 'color-filled', type: 'icon', onClick: () => { // ... } }, {label: '日历', imgico: 'calendar', type: 'widget', path: 'pages/calendar/index', background: '#fff',}, {label: '首页', imgico: 'home', type: 'icon', path: 'pages/index/index'}, {label: '工作台', imgico: 'shop-filled', type: 'icon', path: 'pages/index/dashboard'}, { label: '组件', 'children': [ {label: '组件', imgico: '/static/svg/component.svg', path: 'pages/component/index'}, {label: '表格', imgico: '/static/svg/table.svg', path: 'pages/component/table'}, ] }, { label: '关于', imgico: 'info-filled', type: 'icon', onClick: () => { // ... } }, { label: '公众号', imgico: 'weixin', type: 'icon', onClick: () => { // ... } }, ] } ])
通过开发这个uniapp-vue3-os项目,旨在探索一种全新的手机后台OA管理系统新模式。
目前这个项目已经得到了很多小伙伴们的喜欢支持!
后面会考虑用flutter技术再开发一版类似的跨平台OA系统。