uni-vue3-oadmin:基于uniapp+vite5+pinia2跨端手机后台OA系统

5,322 阅读6分钟

历经了二十多天爆肝开发,我的跨端原创新项目uniapp-vue3-os手机后台OA管理系统正式完结了。

未标题-6.png

未标题-5.png

全新原创vue3+uniapp+uni-ui多端仿ios手机后台OA系统管理实例Vue3-Uni-WeOS

app2.gif

技术栈

  • 开发工具:hbuilderX4.15
  • 框架技术:vite5+uniapp+vue3+pinia2
  • UI组件库:uni-ui+uv-ui(uniapp vue3组件库)
  • 弹框组件:uv3-popup(基于uniapp+vue3自定义弹框组件)
  • 表格组件:uv3-table(基于uniapp+vue3增强版表格组件)
  • 模拟数据:mockjs(用于自定义表格模拟数据)
  • 缓存技术:pinia-plugin-unistorage
  • 支持编译:h5+小程序端+app端

mp3.gif

mp5.gif

项目结构目录

使用hbuilderx4.15创建vue3项目模板,采用vue3 setup语法编码开发。

360截图20240520210143658.png

360截图20240520210727994.png

360截图20240520182253867.png

项目中使用到的表格组件uv3-table。采用全新自研vue3+uniapp多功能综合增强版表格组件。

87715df3334b6b7f29bdffddcaac7da7_1289798-20240518095958103-844372811.png

支持固定表头/列、边框、斑马纹、单选/多选,自定义表头/表体插槽、左右固定列阴影高亮显示。支持编译兼容H5+小程序端+App端

0e7bc9485d3b6d3a4ce8c0f1206141bf_1289798-20240518101338492-315393540.png

  • 基础用法
<uv3-table :columns="columns" :dataSource="data.list" />
  • 自定义条纹
<uv3-table
    :columns="columns"
    :dataSource="data.list"
    stripe
    stripeColor="#eee"
    padding="5px"
    height="450rpx"
/>
  • 综合示例
<uv3-table
    :dataSource="data.list"
    :columns="columns"
    :headerBold="true"
    headerBackground="#ecf5ff"
    stripe
    border
    padding="5px"
    maxHeight="650rpx"
    @rowClick="handleRowClick"
    @select="handleSelect"
>
    <!-- 自定义header插槽内容 -->
    <template #headerCell="{ key, col, index }">
        <template v-if="key == 'title'">
            <view class="flex-c">{{col.label}} <input placeholder="搜索" size="small" /></view>
        </template>
        <template v-else-if="key == 'date'">
            <uni-icons type="calendar"></uni-icons> {{col.label}}
        </template>
        <template v-else>{{col.label}}</template>
    </template>
    
    <!-- 自定义body插槽内容(由于小程序不支持动态:name插槽,通过key标识来自定义表格内容) -->
    <template #default="{ key, value, row, col, index }">
        <template v-if="key == 'image'">
            <uv-image :src="value" lazyLoad observeLazyLoad @click="previewImage(value)" />
        </template>
        <template v-else-if="key == 'switch'">
            <switch :checked="value" style="transform:scale(0.6);" />
        </template>
        <template v-else-if="key == 'tags'">
            <uv-tags :text="value" :color="row.color" :borderColor="row.color" plain size="mini" />
        </template>
        <template v-else-if="key == 'rate'">
            <uni-rate :value="value" size="14" readonly />
        </template>
        <template v-else-if="key == 'action'">
            <uni-icons type="compose" color="#00aa7f" @click="handleEdit(row)" />
            <uni-icons type="trash" color="#ff007f" style="margin-left: 5px;" @click="handleDel(row)" />
        </template>
        <template v-else>{{value}}</template>
    </template>
</uv3-table>

点击表格行,会返回该行数据。

925c37d292ecf2d0005bd1aa0bd91d2e_1289798-20240518102259116-2067904030.png

点击多选框,会返回选中数据。

cf4a0c8b7b918417ee4ad894fb765fcd_1289798-20240518102513671-1199067581.png

支持如下参数配置

const props = defineProps({
    // 表格数据
    dataSource: {
        type: Array,
        default() {
            return []
        }
    },
    /**
     * @params {string} background 对应列背景色
     * @params {string} type 对应列类型(多选selection 索引index)
     * @params {string} label 显示的列标题
     * @params {string} prop 对应的列字段名
     * @params {string} align 列水平对齐方式(left center right)
     * @params {number|string} width 对应列宽度
     * @params {boolean|string} fixed 该列固定到左侧(fixed:true|'left')或右侧(fixed:'right')
     * @params {string} columnStyle 对应列自定义样式
     * @params {string} className/class 表格列的类名className
     */
    columns: {
        type: Array,
        default() {
            return []
        }
    },
    // 表格宽度
    width: { type: [Number, String] },
    // 表格高度
    height: { type: [Number, String] },
    // 表格最大高度
    maxHeight: { type: [Number, String] },
    // 是否为斑马纹
    stripe: { type: [Boolean, String] },
    // 斑马纹背景
    stripeColor: { type: String, default: '#fafafa' },
    // 是否带有边框
    border: { type: [Boolean, String] },
    // 列宽度(推荐默认rpx)
    columnWidth: { type: [Number, String], default: 200 },
    // 单元格间距
    padding: { type: String, default: '5rpx 10rpx' },
    // 是否显示表头
    showHeader: { type: [Boolean, String], default: true },
    // 表头背景色
    headerBackground: { type: String, default: '#ebeef5' },
    // 表头颜色
    headerColor: { type: String, default: '#333' },
    // 表头字体加粗
    headerBold: { type: [Boolean, String], default: true },
    // 表格背景色
    background: { type: String, default: '#fff' },
    // 表格颜色
    color: { type: String, default: '#606266' },
    // 空数据时显示的文本内容,也可以通过 #empty 设置
    emptyText: { type: String, default: '暂无数据' }
})

p3.gif

vue3+uniapp实现滑动解锁

001360截图20240520170944142.png

002360截图20240520171111362.png

这个项目登录验证一改以往的输入用户名、密码方式。采用动画效果上滑后输入数字来解锁登录。

image.png

<template>
	<uv3-layout>
		<view class="uv3__launch" :style="{'padding-top': fixPadTop+'px'}">
			<view v-if="splashScreen" class="uv3__launch-splash flexbox flex-col" @touchstart="handleTouchStart" @touchmove="handleTouchUpdate">
				<view class="body flex1">
					<Today />
				</view>
				<view class="foot flexbox flex-alignc">
					<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 flexbox flex-col" :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>
// 点击数字键盘
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) {
			// 登录逻辑处理
		}else {
			pwdValue.value = ''
		}
	}
}
// 删除
const handleDel = () => {
	let num = Array.from(pwdValue.value)
	num.splice(-1, 1)
	pwdValue.value = num.join('')
}

未标题-3.png

公共Layout模板

在公共模板里定义一些桌面图标变量。

<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>

未标题-8.png

桌面Desk布局

409bf1662c366711b9eede184605ccd1_1289798-20240521175128281-1416206536.png

<!-- 桌面模板 -->
<script setup>
    import { ref } from 'vue'
    
    import Desk from './components/desk.vue'
    import Dock from './components/dock.vue'
    import Touch from './components/touch.vue'
</script>

<template>
    <uv3-layout>
        <!-- 桌面菜单 -->
        <Desk />
        
        <template #footer>
            <!-- 底部导航 -->
            <Dock />
        </template>
        <!-- 悬浮球(辅助触控) -->
        <Touch />
    </uv3-layout>
</template>

003360截图20240520183440513.png

006360截图20240520184518687.png

007360截图20240520184607104.png

008360截图20240520185303932.png

009360截图20240520185606957.png

010360截图20240520185644045.png

011360截图20240520190122398.png

013360截图20240520190401254.png

014360截图20240520190610402.png

015360截图20240520190801802.png

017360截图20240520191011128.png

022360截图20240520191709365.png

018360截图20240520191314079.png

019360截图20240520191440516.png

目前该项目已经发布到我的原创作品集,如果有需要,可以去看看哈~

uni-vue3-weos手机后台OA管理系统

025360截图20240520214418872.png

在pc端以750px像素展示,效果依然beautiful~~

桌面磁贴化栅格布局

桌面图标采用全新自研卡片式栅格布局。

82e8dc507bdbb2f32893405d5acfe89b_1289798-20240521181036651-2076891044.png

image.png

e0cf5376230c1d2613dfc1e8204ce06b_1289798-20240521181336890-18459133.png

桌面图标菜单支持如下参数

/**
 * 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 二级菜单
 */

c7aa46b214682d4571e1a647799d1063_1289798-20240521181928269-1937039606.png

<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>

桌面菜单自动化JSON配置示例

// 桌面图标json配置 by Andy
[
    {
        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'},
            ...
        ]
    },
    {
        pid: 20240511003,
        list: [
            {label: 'uni-app', imgico: '/static/uni.png', link: 'https://uniapp.dcloud.net.cn/'},
            {
                label: '主题壁纸', imgico: 'color-filled', type: 'icon',
                onClick: () => {
                    // ...
                }
            },
            {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/table.svg', path: 'pages/component/table'},
                    ...
                ]
            },
            ...
            {
                label: '关于', imgico: 'info-filled', type: 'icon',
                onClick: () => {
                    // ...
                }
            },
        ]
    }
]

uni-vue3-os项目旨在探索vue3+uniapp开发手机OA系统解决方案。当然如果有一些其它有创意的想法,也可以在这个框架上面来开发实现,比如类似iTabs标签页手机版功能。

OKay,限于篇幅以上只是做了部分知识点的一些分享,希望对大家有所帮助!

juejin.cn/post/731918…

juejin.cn/post/736312…

a8a5dc63jw1falkc05snfg206q046gli.gif