浏览代码

教师研修组件封装、页面开发

liurongli 1 月之前
父节点
当前提交
0c9328cb1e
共有 36 个文件被更改,包括 1307 次插入1849 次删除
  1. 1 0
      .hbuilderx/launch.json
  2. 56 64
      components/nav-bar.vue
  3. 36 0
      components/page-tabbar.vue
  4. 2 2
      config.js
  5. 8 1
      pages.json
  6. 3 3
      pages/login/agreement.vue
  7. 3 3
      pages/my/updatePassword.vue
  8. 25 0
      pages/teacherStudy/courseCenter/index.vue
  9. 39 0
      pages/teacherStudy/index.vue
  10. 15 11
      pages/workspace/index.vue
  11. 二进制
      static/image/tabbar/courseCenter.png
  12. 二进制
      static/image/tabbar/courseCenter_cur.png
  13. 二进制
      static/image/tabbar/learningMonitor.png
  14. 二进制
      static/image/tabbar/learningMonitor_cur.png
  15. 二进制
      static/image/tabbar/myLearning.png
  16. 二进制
      static/image/tabbar/myLearning_cur.png
  17. 60 1745
      style/common.scss
  18. 1 1
      uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue
  19. 7 0
      uni_modules/uv-badge/changelog.md
  20. 73 0
      uni_modules/uv-badge/components/uv-badge/props.js
  21. 176 0
      uni_modules/uv-badge/components/uv-badge/uv-badge.vue
  22. 87 0
      uni_modules/uv-badge/package.json
  23. 11 0
      uni_modules/uv-badge/readme.md
  24. 11 0
      uni_modules/uv-safe-bottom/changelog.md
  25. 67 0
      uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue
  26. 87 0
      uni_modules/uv-safe-bottom/package.json
  27. 11 0
      uni_modules/uv-safe-bottom/readme.md
  28. 14 0
      uni_modules/uv-tabbar/changelog.md
  29. 40 0
      uni_modules/uv-tabbar/components/uv-tabbar-item/props.js
  30. 148 0
      uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue
  31. 50 0
      uni_modules/uv-tabbar/components/uv-tabbar/props.js
  32. 146 0
      uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue
  33. 90 0
      uni_modules/uv-tabbar/package.json
  34. 21 0
      uni_modules/uv-tabbar/readme.md
  35. 4 4
      unpackage/dist/cache/.vite/deps/_metadata.json
  36. 15 15
      unpackage/dist/cache/.vite/deps/jsencrypt.js

+ 1 - 0
.hbuilderx/launch.json

@@ -2,6 +2,7 @@
     "version" : "1.0",
     "configurations" : [
         {
+            "customPlaygroundType" : "device",
             "playground" : "standard",
             "type" : "uni-app:app-ios"
         },

+ 56 - 64
components/nav-bar.vue

@@ -1,65 +1,91 @@
 <template>
-	<uni-nav-bar :height="`${height}rpx`" :backgroundColor="backgroundColor" :fixed="true" :border="false" :statusBar="true" :leftWidth="showLeftIcon?'48rpx':'0rpx'" :rightWidth="showRightIcon?'48rpx':'0rpx'">
-		<template v-slot:left v-if="showLeftIcon">
-			<uni-icons type="left" :color="leftIconColor" style="font-size: 40rpx;" @click="GoBack()" />
-		</template>
-		<view class="center_view">
-			<view v-if="showTitle" class="title">
-				<text class="nav_title" :style="{'color':navTitleColor}">{{title}}</text>
+	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle}" :height="`${height}rpx`" :backgroundColor="backgroundColor" :title="title" color="#333333" :fixed="true" :border="showHeaderborder" :statusBar="true" :leftWidth="leftWidth" :rightWidth="rightWidth" left-icon="left" @clickLeft="GoBack()">
+		<view class="nav_head" v-if="!onlyTitle">
+			<text class="nav_title">{{title}}</text>
+			<view class="search_box">
+				<image src="/static/image/overview/search.png" class="search_icon" ></image>	
+				<input type="text" class="search_input" placeholder="请输入关键字搜索" placeholder-class="search_placeholder" v-model="searchWord" @input="handleSearchInput" />
 			</view>
 		</view>
+		<!-- 条件选择器 -->
+		<view class="filter_picker">
+			<!-- 通知类型筛选 -->
+			<picker mode="selector" v-for="(item,index) in filterPickerData" :key="index" :range="item" :value="state.pickerIndex" @change="handleTypeChange" style="width: calc((100% - 32rpx * 2) / 3);">
+				<view class="filter_item">
+					<text class="filter_text">{{ item[state.pickerIndex] }}</text>
+					<uni-icons type="down" size="16" color="#999999"></uni-icons>
+				</view>
+			</picker>
+		</view>
+		<!-- tabs -->
+		<scroll-view class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
+			<view :class="['tab_item',{'selected':item.sortType==state.tabsSelected}]" v-for="item in tabList" :key="item.sortType" @click="SelectMenu(item.sortType)">
+				{{item.name}}
+			</view>
+		</scroll-view>
 	</uni-nav-bar>
 </template>
 
 <script setup>
 	import uniNavBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-
+	import { reactive } from 'vue';
 	const emit = defineEmits(['GoBack']);
 	const props = defineProps({
-		height: {
+		height: {//高度
 			type: String,
 			default: () => ''
 		},
-		showLeftIcon: {
-			type: Boolean,
-			default: () => false
-		},
-		showRightIcon: {
-			type: Boolean,
-			default: () => false
+		leftWidth: {//导航栏左侧插槽宽度
+			type: String,
+			default: () => '96rpx'
 		},
-		showTitle: {
-			type: Boolean,
-			default: () => false
+		rightWidth: {//导航栏右侧插槽宽度
+			type: String,
+			default: () => '96rpx'
 		},
-		title: {
+		title: {//标题
 			type: String,
 			default: () => ''
 		},
-		backgroundColor:{
+		showHeaderborder:{//是否显示边框 默认不显示
+			type:Boolean,
+			default:false
+		},
+		backgroundColor: {//背景色
 			type: String,
 			default: () => '#FFFFFF'
 		},
-		leftIconColor:{
+		backPath: {//返回路径
 			type: String,
-			default: () => '#333333'
+			default: () => ''
 		},
-		navTitleColor:{
-			type: String,
-			default: () => '#333333'
+		onlyTitle:{//是否只显示标题
+			type:Boolean,
+			default:true
 		},
-		backPath: {
-			type: String,
-			default: () => ''
+		filterPickerData:{//条件选择器
+			type:Array,
+			default:()=>[]
+		},
+		tabList:{//tabs
+			type:Array,
+			default:()=>[]
 		}
 	});
-
+	const state = reactive({
+		pickerIndex:0,
+		tabsSelected:'1'
+	})
 	const GoBack = () => {
 		if (props.backPath == 'myIndex') {
 			uni.switchTab({
 				url: '/pages/my/index'
 			});
+		}else if(props.backPath == 'workspace') {//跳转工作台
+			uni.switchTab({
+				url: '/pages/workspace/index'
+			});
 		} else {
 			emit('GoBack');
 		}
@@ -72,41 +98,7 @@
 		position: relative;
 		z-index: 999;
 	}
-
 	:deep(.uni-navbar__header-container) {
 		padding: 0;
 	}
-
-	:deep(.uni-navbar__header-btns-left) {
-		position: absolute;
-		left: 24rpx;
-		top: 0;
-		height: 88rpx;
-		z-index: 2;
-	}
-
-	.center_view {
-		width: 100%;
-
-		.title {
-			height: 88rpx;
-			padding: 0 72rpx;
-			display: flex;
-			align-items: center;
-			justify-content: center;
-
-			.nav_title {
-				flex: 1;
-				font-size: 32rpx;
-				color: #333333;
-				font-weight: 500;
-				height: 32rpx;
-				line-height: 32rpx;
-				text-align: center;
-				overflow: hidden;
-				text-overflow: ellipsis;
-				white-space: nowrap;
-			}
-		}
-	}
 </style>

+ 36 - 0
components/page-tabbar.vue

@@ -0,0 +1,36 @@
+<template>
+	<uv-tabbar :value="tabBarValue" activeColor="#2E64FA" inactiveColor="#C7CBD6" @change="changeTabBarValue">
+		<uv-tabbar-item v-for="item in tabBarList" :key="item.pathName" :name="item.pathName" :text="item.text">
+			<template v-slot:active-icon>
+				<image class="icon" :src="item.selectedIconPath" style="width:48rpx;height:48rpx"></image>
+			</template>
+			<template v-slot:inactive-icon>
+				<image class="icon" :src="item.iconPath" style="width:48rpx;height:48rpx"></image>
+			</template>
+		</uv-tabbar-item>
+	</uv-tabbar>
+</template>
+
+<script setup>
+	import uvTabbar from '@/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue';
+	import uvTabbarItem from '@/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue';
+	import { reactive } from 'vue';
+	const emit = defineEmits(['ChangeTabBarValue']);
+	const props = defineProps({
+		tabBarValue: {//默认选中的tab
+			type: String,
+			default: () => ''
+		},
+		tabBarList: {
+			type: Array,
+			default: () => []
+		}
+	});
+	const state = reactive({
+	})
+	//切换tabBar
+	const changeTabBarValue = (name) => {
+		emit('ChangeTabBarValue',name)
+	}
+	
+</script>

+ 2 - 2
config.js

@@ -1,7 +1,7 @@
 
 export default {
-  // apiBaseUrl: 'https://dev3.k12100.net/teaching',
-  apiBaseUrl: 'https://www.k12100.com/teaching',
+  apiBaseUrl: 'https://dev3.k12100.net/teaching',
+  // apiBaseUrl: 'https://www.k12100.com/teaching',
   proxyBaseUrl:'',//h5 代理接口地址
   // apiBaseUrl:'http://192.168.0.42:47001'
  

+ 8 - 1
pages.json

@@ -90,6 +90,13 @@
 				"navigationBarTitleText": "我的通知",
 				"navigationStyle": "custom"
 			}
+		},
+		{
+			"path": "pages/teacherStudy/index",
+			"style": {
+				"navigationBarTitleText": "教师研修",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"easycom": {
@@ -111,7 +118,7 @@
 		"borderStyle": "black",
 		"fontSize": "12px",
 		"backgroundColor": "#FFFFFF",
-		"height": "84px",
+		"height": "136rpx",
 		"list": [{
 				"pagePath": "pages/workspace/index",
 				"iconPath": "static/image/tabbar/workspace.png",

+ 3 - 3
pages/login/agreement.vue

@@ -1,7 +1,7 @@
 <template>
-	<view class="page_report">
+	<view class="page_body">
 		<!-- 导航 -->
-		<rp-nav-bar height="88" backPath="" :showLeftIcon="true" :showTitle="true" title="用户协议和隐私政策" @GoBack="GoBack"></rp-nav-bar>
+		<rp-nav-bar height="88" backPath="" :showHeaderborder="false" title="用户协议和隐私政策" @GoBack="GoBack"></rp-nav-bar>
 		<view class="page_module">
 			<view class="p">隐私政策</view>
 			<view class="p_m">
@@ -62,7 +62,7 @@
 </script>
 
 <style lang="scss">
-	.page_report {
+	.page_body {
 		height: 100%;
 		background-color: #FFFFFF;
 

+ 3 - 3
pages/my/updatePassword.vue

@@ -1,7 +1,7 @@
 <template>
-	<view class="page_report">
+	<view class="page_body">
 		<!-- 导航 -->
-		<rp-nav-bar height="88" backPath="" :showLeftIcon="true" :showTitle="true" @GoBack="GoBack"></rp-nav-bar>
+		<rp-nav-bar height="88" :showHeaderborder="false" @GoBack="GoBack"></rp-nav-bar>
 		<view class="page_module">
 			<view class="page_title">修改密码</view>
 			<view class="tip">请输入两次相同的密码进行修改</view>
@@ -139,7 +139,7 @@
 </script>
 
 <style scoped lang="scss">
-	.page_report {
+	.page_body {
 		height: 100%;
 		background-color: #FFFFFF;
 		.page_module {

+ 25 - 0
pages/teacherStudy/courseCenter/index.vue

@@ -0,0 +1,25 @@
+<template>
+	<rp-nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" rightWidth="0rpx" @GoBack="GoBack"></rp-nav-bar>
+	<view class=""></view>
+</template>
+
+<script setup>
+	import rpNavBar from '@/components/nav-bar.vue';
+	import { reactive, ref } from 'vue';
+	const state = reactive({
+		filterPickerData:[['全部类型','全部类型1'],['全部类型','全部类型2'],['全部类型','全部类型2']],
+		tabList:[{
+			name:'最新发布',
+			sortType:'1'
+		},{
+			name:'播放最多',
+			sortType:'2'
+		},{
+			name:'关注最多',
+			sortType:'3'
+		}]
+	})
+</script>
+
+<style>
+</style>

+ 39 - 0
pages/teacherStudy/index.vue

@@ -0,0 +1,39 @@
+<template>
+	<view class="page_body">
+		<!-- 课程中心 -->
+		<course-center></course-center>
+		<page-tabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></page-tabbar>
+	</view>
+</template>
+
+<script setup>
+	import pageTabbar from '@/components/page-tabbar.vue';//头部
+	import courseCenter from './courseCenter/index.vue';
+	import { reactive, ref } from 'vue';
+	const state = reactive({
+		tabBarValue:'courseCenter',
+		tabBarList:[{
+			"pathName":"courseCenter",
+			"iconPath": "/static/image/tabbar/courseCenter.png",
+			"selectedIconPath": "/static/image/tabbar/courseCenter_cur.png",
+			"text": "课程中心"
+		},{
+			"pathName":"myLearning",
+			"iconPath": "/static/image/tabbar/myLearning.png",
+			"selectedIconPath": "/static/image/tabbar/myLearning_cur.png",
+			"text": "我的研修"
+		},{
+			"pathName":"learningMonitor",
+			"iconPath": "/static/image/tabbar/learningMonitor.png",
+			"selectedIconPath": "/static/image/tabbar/learningMonitor_cur.png",
+			"text": "研修监控"
+		}]
+	})
+	//切换tabBar
+	const ChangeTabBarValue = (name) => {
+		state.tabBarValue = name;
+	}
+</script>
+
+<style lang="scss">
+</style>

+ 15 - 11
pages/workspace/index.vue

@@ -136,16 +136,16 @@ const store = useStore();
 const schoolName = ref('');
 
 const systemItems = ref([
-	{ name: '精准教学', image: '/static/image/workspace/icon1.png' },
-	{ name: '教师研修', image: '/static/image/workspace/icon2.png' },
-	{ name: '协同备课', image: '/static/image/workspace/icon3.png' },
-	{ name: '材料采集', image: '/static/image/workspace/icon4.png' },
-	{ name: '教师发展', image: '/static/image/workspace/icon5.png' },
-	{ name: '校内通知', image: '/static/image/workspace/icon6.png' },
-	{ name: '选择题判分', image: '/static/image/workspace/icon7.png' },
-	{ name: '智能选课', image: '/static/image/workspace/icon8.png' },
-	{ name: '学生学习', image: '/static/image/workspace/icon9.png' },
-	{ name: '基础数据', image: '/static/image/workspace/icon10.png' }
+	{ name: '精准教学', key:'', image: '/static/image/workspace/icon1.png' },
+	{ name: '教师研修', key:'teacherStudy', image: '/static/image/workspace/icon2.png' },
+	{ name: '协同备课', key:'', image: '/static/image/workspace/icon3.png' },
+	{ name: '材料采集', key:'', image: '/static/image/workspace/icon4.png' },
+	{ name: '教师发展', key:'', image: '/static/image/workspace/icon5.png' },
+	{ name: '校内通知', key:'notice', image: '/static/image/workspace/icon6.png' },
+	{ name: '选择题判分', key:'', image: '/static/image/workspace/icon7.png' },
+	{ name: '智能选课', key:'', image: '/static/image/workspace/icon8.png' },
+	{ name: '学生学习', key:'', image: '/static/image/workspace/icon9.png' },
+	{ name: '基础数据', key:'', image: '/static/image/workspace/icon10.png' }
 ]);
 
 const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
@@ -291,10 +291,14 @@ onShow(() => {
 });
 
 const handleSystemClick = (item) => {
-	if (item.name === '校内通知') {
+	if (item.key === 'notice') {
 		uni.navigateTo({
 			url: '/pages/notice/overview/index'
 		});
+	}else if(item.key == 'teacherStudy'){
+		uni.navigateTo({
+			url: '/pages/teacherStudy/index'
+		});
 	} else {
 		uni.showToast({ title: `即将打开:${item.name}`, icon: 'none' });
 	}

二进制
static/image/tabbar/courseCenter.png


二进制
static/image/tabbar/courseCenter_cur.png


二进制
static/image/tabbar/learningMonitor.png


二进制
static/image/tabbar/learningMonitor_cur.png


二进制
static/image/tabbar/myLearning.png


二进制
static/image/tabbar/myLearning_cur.png


文件差异内容过多而无法显示
+ 60 - 1745
style/common.scss


+ 1 - 1
uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue

@@ -274,7 +274,7 @@
 		display: flex;
 		/* #endif */
 		width: 120rpx;
-		justify-content: flex-start;
+		justify-content: center;
 		align-items: center;
 	}
 

+ 7 - 0
uni_modules/uv-badge/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-04)
+1. 修复type等属性为null的时候不显示徽标的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-badge 徽标数,数字角标

+ 73 - 0
uni_modules/uv-badge/components/uv-badge/props.js

@@ -0,0 +1,73 @@
+export default {
+	props: {
+		// 是否显示圆点
+		isDot: {
+			type: Boolean,
+			default: false
+		},
+		// 显示的内容
+		value: {
+			type: [Number, String],
+			default: ''
+		},
+		// 是否显示
+		show: {
+			type: Boolean,
+			default: true
+		},
+		// 最大值,超过最大值会显示 '{max}+'
+		max: {
+			type: [Number, String],
+			default: 999
+		},
+		// 主题类型,error|warning|success|primary
+		type: {
+			type: [String,undefined,null],
+			default: 'error'
+		},
+		// 当数值为 0 时,是否展示 Badge
+		showZero: {
+			type: Boolean,
+			default: false
+		},
+		// 背景颜色,优先级比type高,如设置,type参数会失效
+		bgColor: {
+			type: [String, null],
+			default: null
+		},
+		// 字体颜色
+		color: {
+			type: [String, null],
+			default: null
+		},
+		// 徽标形状,circle-四角均为圆角,horn-左下角为直角
+		shape: {
+			type: [String,undefined,null],
+			default: 'circle'
+		},
+		// 设置数字的显示方式,overflow|ellipsis|limit
+		// overflow会根据max字段判断,超出显示`${max}+`
+		// ellipsis会根据max判断,超出显示`${max}...`
+		// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+		numberType: {
+			type: [String,undefined,null],
+			default: 'overflow'
+		},
+		// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+		offset: {
+			type: Array,
+			default: () => []
+		},
+		// 是否反转背景和字体颜色
+		inverted: {
+			type: Boolean,
+			default: false
+		},
+		// 是否绝对定位
+		absolute: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.badge
+	}
+}

+ 176 - 0
uni_modules/uv-badge/components/uv-badge/uv-badge.vue

@@ -0,0 +1,176 @@
+<template>
+	<text
+		v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+		:class="[isDot ? 'uv-badge--dot' : 'uv-badge--not-dot', inverted && 'uv-badge--inverted', shape === 'horn' && 'uv-badge--horn', `uv-badge--${propsType}${inverted ? '--inverted' : ''}`]"
+		:style="[$uv.addStyle(customStyle), badgeStyle]"
+		class="uv-badge"
+	>{{ isDot ? '' :showValue }}</text>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * badge 徽标数
+	 * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+	 * @tutorial https://www.uvui.cn/components/badge.html
+	 * 
+	 * @property {Boolean} 			isDot 		是否显示圆点 (默认 false )
+	 * @property {String | Number} 	value 		显示的内容
+	 * @property {Boolean} 			show 		是否显示 (默认 true )
+	 * @property {String | Number} 	max 		最大值,超过最大值会显示 '{max}+'  (默认999)
+	 * @property {String} 			type 		主题类型,error|warning|success|primary (默认 'error' )
+	 * @property {Boolean} 			showZero	当数值为 0 时,是否展示 Badge (默认 false )
+	 * @property {String} 			bgColor 	背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {String} 			color 		字体颜色 (默认 '#ffffff' )
+	 * @property {String} 			shape 		徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+	 * @property {String} 			numberType	设置数字的显示方式,overflow|ellipsis|limit  (默认 'overflow' )
+	 * @property {Array}} 			offset		设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+	 * @property {Boolean} 			inverted	是否反转背景和字体颜色(默认 false )
+	 * @property {Boolean} 			absolute	是否绝对定位(默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <uv-badge :type="type" :count="count"></uv-badge>
+	 */
+	export default {
+		name: 'uv-badge',
+		mixins: [mpMixin, mixin, props],
+		computed: {
+			// 是否将badge中心与父组件右上角重合
+			boxStyle() {
+				let style = {};
+				return style;
+			},
+			// 整个组件的样式
+			badgeStyle() {
+				const style = {}
+				if(this.color) {
+					style.color = this.color
+				}
+				if (this.bgColor && !this.inverted) {
+					style.backgroundColor = this.bgColor
+				}
+				if (this.absolute) {
+					style.position = 'absolute'
+					// 如果有设置offset参数
+					if(this.offset.length) {
+						// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+						const top = this.offset[0]
+						const right = this.offset[1] || top
+						style.top = this.$uv.addUnit(top)
+						style.right = this.$uv.addUnit(right)
+					}
+				}
+				return style
+			},
+			showValue() {
+				switch (this.numberType) {
+					case "overflow":
+						return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+						break;
+					case "ellipsis":
+						return Number(this.value) > Number(this.max) ? "..." : this.value
+						break;
+					case "limit":
+						return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+							Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+								1e3 * 100) / 100 + "k" : this.value
+						break;
+					default:
+						return Number(this.value)
+				}
+			},
+			propsType(){
+				return this.type || 'error'
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-badge-primary: $uv-primary !default;
+	$uv-badge-error: $uv-error !default;
+	$uv-badge-success: $uv-success !default;
+	$uv-badge-info: $uv-info !default;
+	$uv-badge-warning: $uv-warning !default;
+	$uv-badge-dot-radius: 100px !default;
+	$uv-badge-dot-size: 8px !default;
+	$uv-badge-dot-right: 4px !default;
+	$uv-badge-dot-top: 0 !default;
+	$uv-badge-text-font-size: 11px !default;
+	$uv-badge-text-right: 10px !default;
+	$uv-badge-text-padding: 2px 5px !default;
+	$uv-badge-text-align: center !default;
+	$uv-badge-text-color: #FFFFFF !default;
+
+	.uv-badge {
+		border-top-right-radius: $uv-badge-dot-radius;
+		border-top-left-radius: $uv-badge-dot-radius;
+		border-bottom-left-radius: $uv-badge-dot-radius;
+		border-bottom-right-radius: $uv-badge-dot-radius;
+		@include flex;
+		line-height: $uv-badge-text-font-size;
+		text-align: $uv-badge-text-align;
+		font-size: $uv-badge-text-font-size;
+		color: $uv-badge-text-color;
+
+		&--dot {
+			height: $uv-badge-dot-size;
+			width: $uv-badge-dot-size;
+		}
+		
+		&--inverted {
+			font-size: 13px;
+		}
+		
+		&--not-dot {
+			padding: $uv-badge-text-padding;
+		}
+
+		&--horn {
+			border-bottom-left-radius: 0;
+		}
+
+		&--primary {
+			background-color: $uv-badge-primary;
+		}
+		
+		&--primary--inverted {
+			color: $uv-badge-primary;
+		}
+
+		&--error {
+			background-color: $uv-badge-error;
+		}
+		
+		&--error--inverted {
+			color: $uv-badge-error;
+		}
+
+		&--success {
+			background-color: $uv-badge-success;
+		}
+		
+		&--success--inverted {
+			color: $uv-badge-success;
+		}
+
+		&--info {
+			background-color: $uv-badge-info;
+		}
+		
+		&--info--inverted {
+			color: $uv-badge-info;
+		}
+
+		&--warning {
+			background-color: $uv-badge-warning;
+		}
+		
+		&--warning--inverted {
+			color: $uv-badge-warning;
+		}
+	}
+</style>

+ 87 - 0
uni_modules/uv-badge/package.json

@@ -0,0 +1,87 @@
+{
+	"id": "uv-badge",
+	"displayName": "uv-badge 徽标数,数字角标 全面兼容小程序、nvue、vue2、vue3等多端",
+	"version": "1.0.2",
+	"description": "徽标数一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。",
+	"keywords": [
+        "uv-badge",
+        "uvui",
+        "uv-ui",
+        "徽标数",
+        "数字角标"
+    ],
+	"repository": "",
+	"engines": {
+		"HBuilderX": "^3.1.0"
+	},
+	"dcloudext": {
+		"type": "component-vue",
+		"sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "插件不采集任何数据",
+			"permissions": "无"
+		},
+		"npmurl": ""
+	},
+	"uni_modules": {
+		"dependencies": [
+			"uv-ui-tools"
+		],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

+ 11 - 0
uni_modules/uv-badge/readme.md

@@ -0,0 +1,11 @@
+## Badge 徽标数
+
+> **组件名:uv-badge**
+
+该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+
+### <a href="https://www.uvui.cn/components/badge.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 11 - 0
uni_modules/uv-safe-bottom/changelog.md

@@ -0,0 +1,11 @@
+## 1.0.4(2023-09-14)
+1. 飞书小程序支持
+## 1.0.3(2023-08-14)
+1. 修复百度报错的BUG
+## 1.0.2(2023-07-02)
+uv-safe-bottom 修复,在百度程序,抖音小程序不生效的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-safe-bottom 底部安全区组件

+ 67 - 0
uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue

@@ -0,0 +1,67 @@
+<template>
+	<view
+		class="uv-safe-bottom"
+		:style="[style]"
+		:class="[!isNvue && 'uv-safe-area-inset-bottom']"
+	>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	/**
+	 * SafeBottom 底部安全区
+	 * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+	 * @tutorial https://www.uvui.cn/components/safeAreaInset.html
+	 * @property {type}		prop_name
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 *
+	 * @event {Function()}
+	 * @example <uv-status-bar></uv-status-bar>
+	 */
+	export default {
+		name: "uv-safe-bottom",
+		mixins: [mpMixin, mixin],
+		data() {
+			return {
+				safeAreaBottomHeight: 0,
+				isNvue: false,
+			};
+		},
+		computed: {
+			style() {
+				const style = {};
+				// #ifdef APP-NVUE || MP-TOUTIAO || MP-LARK
+				// nvue下,高度使用js计算填充
+				style.height = this.$uv.addUnit(this.$uv.sys()?.safeAreaInsets?.bottom, 'px');
+				// #endif
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+			},
+		},
+		mounted() {
+			// #ifdef APP-NVUE
+			// 标识为是否nvue
+			this.isNvue = true;
+			// #endif
+		},
+	};
+</script>
+
+<style lang="scss" scoped>
+	.uv-safe-bottom {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		/* #endif */
+	}
+	/* #ifndef APP-NVUE */
+	// 历遍生成4个方向的底部安全区
+	@each $d in top, right, bottom, left {
+		.uv-safe-area-inset-#{$d} {
+			padding-#{$d}: 0;
+			padding-#{$d}: constant(safe-area-inset-#{$d});  
+			padding-#{$d}: env(safe-area-inset-#{$d});  
+		}
+	}
+	/* #endif */
+</style>

+ 87 - 0
uni_modules/uv-safe-bottom/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-safe-bottom",
+  "displayName": "uv-safe-bottom 底部安全区  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.4",
+  "description": "这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。",
+  "keywords": [
+    "uv-safe-bottom",
+    "uvui",
+    "uv-ui",
+    "bottom",
+    "底部安全区"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-safe-bottom/readme.md

@@ -0,0 +1,11 @@
+## SafeBottom 底部安全区 
+
+> **组件名:uv-safe-bottom**
+
+这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+
+### <a href="https://www.uvui.cn/guide/safeAreaInset.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 14 - 0
uni_modules/uv-tabbar/changelog.md

@@ -0,0 +1,14 @@
+## 1.0.5(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.4(2023-06-13)
+1. 底部安全距离依赖添加
+## 1.0.3(2023-06-09)
+1. 增加iconSize参数
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG 
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-tabbar 底部导航栏 

+ 40 - 0
uni_modules/uv-tabbar/components/uv-tabbar-item/props.js

@@ -0,0 +1,40 @@
+export default {
+	props: {
+		// item标签的名称,作为与uv-tabbar的value参数匹配的标识符
+		name: {
+			type: [String, Number, null],
+			default: null
+		},
+		// uv-ui内置图标或者绝对路径的图片
+		icon: {
+			icon: String,
+			default: ''
+		},
+		// 图标大小,默认uv-tabbar的iconSize=20
+		iconSize: {
+			type: [String, Number],
+			default: ''
+		},
+		// 右上角的角标提示信息
+		badge: {
+			type: [String, Number, null],
+			default: null
+		},
+		// 是否显示圆点,将会覆盖badge参数
+		dot: {
+			type: Boolean,
+			default: false
+		},
+		// 描述文本
+		text: {
+			type: String,
+			default: ''
+		},
+		// 控制徽标的位置,对象或者字符串形式,可以设置top和right属性
+		badgeStyle: {
+			type: [Object, String],
+			default: 'top: 6px;right:2px;'
+		},
+		...uni.$uv?.props?.tabbarItem
+	}
+}

+ 148 - 0
uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue

@@ -0,0 +1,148 @@
+<template>
+	<view
+	    class="uv-tabbar-item"
+	    :style="[$uv.addStyle(customStyle)]"
+	    @tap="clickHandler"
+	>
+		<view class="uv-tabbar-item__icon">
+			<uv-icon
+			    v-if="icon"
+			    :name="icon"
+			    :color="isActive? parentData.activeColor : parentData.inactiveColor"
+			    :size="iconSize? iconSize: parentData.iconSize"
+			></uv-icon>
+			<template v-else>
+				<slot
+				    v-if="isActive"
+				    name="active-icon"
+				/>
+				<slot
+				    v-else
+				    name="inactive-icon"
+				/>
+			</template>
+			<uv-badge
+				absolute
+				:offset="[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']"
+			    :customStyle="badgeStyle"
+			    :isDot="dot"
+			    :value="badge || (dot ? 1 : null)"
+			    :show="dot || badge > 0"
+			></uv-badge>
+		</view>
+		
+		<slot name="text">
+			<text
+			    class="uv-tabbar-item__text"
+			    :style="{
+					color: isActive? parentData.activeColor : parentData.inactiveColor
+				}"
+			>{{ text }}</text>
+		</slot>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * TabbarItem 底部导航栏子组件
+	 * @description 此组件提供了自定义tabbar的能力。
+	 * @tutorial https://www.uvui.cn/components/tabbar.html
+	 * @property {String | Number}	name		item标签的名称,作为与uv-tabbar的value参数匹配的标识符
+	 * @property {String}			icon		uvui内置图标或者绝对路径的图片
+	 * @property {String | Number}	badge		右上角的角标提示信息
+	 * @property {Boolean}			dot			是否显示圆点,将会覆盖badge参数(默认 false )
+	 * @property {String}			text		描述文本
+	 * @property {Object | String}	badgeStyle	控制徽标的位置,对象或者字符串形式,可以设置top和right属性(默认 'top: 6px;right:2px;' )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * 
+	 * @example <uv-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><uv-tabbar-item text="首页" icon="home" dot ></uv-tabbar-item></uv-tabbar>
+	 */
+	export default {
+		name: 'uv-tabbar-item',
+		mixins: [mpMixin, mixin, props],
+		emits: ['click','change'],
+		data() {
+			return {
+				isActive: false, // 是否处于激活状态
+				parentData: {
+					value: null,
+					activeColor: '',
+					inactiveColor: '',
+					iconSize: 20
+				}
+			}
+		},
+		created() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+				this.updateParentData()
+				if (!this.parent) {
+					this.$uv.error('uv-tabbar-item必须搭配uv-tabbar组件使用')
+				}
+				// 本子组件在uv-tabbar的children数组中的索引
+				const index = this.parent.children.indexOf(this)
+				// 判断本组件的name(如果没有定义name,就用index索引)是否等于父组件的value参数
+				this.isActive = (this.name || index) === this.parentData.value
+			},
+			updateParentData() {
+				// 此方法在mixin中
+				this.getParentData('uv-tabbar')
+			},
+			// 此方法将会被父组件uv-tabbar调用
+			updateFromParent() {
+				// 重新初始化
+				this.init()
+			},
+			clickHandler() {
+				this.$nextTick(() => {
+					const index = this.parent.children.indexOf(this)
+					const name = this.name || index
+					// 点击的item为非激活的item才发出change事件
+					if (name !== this.parent.value) {
+						this.parent.$emit('change', name)
+					}
+					this.$emit('click', name)
+				})
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-tabbar-item {
+		@include flex(column);
+		align-items: center;
+		justify-content: center;
+		flex: 1;
+		
+		&__icon {
+			@include flex;
+			position: relative;
+			width: 150rpx;
+			justify-content: center;
+		}
+
+		&__text {
+			margin-top: 8rpx;
+			font-size: 22rpx;
+			line-height: 32rpx;
+			font-weight: 400;
+			color: $uv-content-color;
+		}
+	}
+
+	/* #ifdef MP */
+	// 由于小程序都使用shadow DOM形式实现,需要给影子宿主设置flex: 1才能让其撑开
+	:host {
+		flex: 1
+	}
+	/* #endif */
+</style>

+ 50 - 0
uni_modules/uv-tabbar/components/uv-tabbar/props.js

@@ -0,0 +1,50 @@
+export default {
+	props: {
+		// 当前匹配项的name
+		value: {
+			type: [String, Number, null],
+			default: null
+		},
+		// 是否为iPhoneX留出底部安全距离
+		safeAreaInsetBottom: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示上方边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		// 元素层级z-index
+		zIndex: {
+			type: [String, Number],
+			default: 9
+		},
+		// 选中标签的颜色
+		activeColor: {
+			type: String,
+			default: '#1989fa'
+		},
+		// 未选中标签的颜色
+		inactiveColor: {
+			type: String,
+			default: '#7d7e80'
+		},
+		// 是否固定在底部
+		fixed: {
+			type: Boolean,
+			default: true
+		},
+		// fixed定位固定在底部时,是否生成一个等高元素防止塌陷
+		placeholder: {
+			type: Boolean,
+			default: true
+		},
+		// 图标大小
+		iconSize: {
+			type: [String, Number],
+			default: 20
+		},
+		...uni.$uv?.props?.tabbar
+	}
+}

+ 146 - 0
uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue

@@ -0,0 +1,146 @@
+<template>
+	<view class="uv-tabbar">
+		<view
+		    class="uv-tabbar__content"
+		    ref="uv-tabbar__content"
+		    @touchmove.stop.prevent="noop"
+		    :class="[border && 'uv-border-top', fixed && 'uv-tabbar--fixed']"
+		    :style="[tabbarStyle]"
+		>
+			<view class="uv-tabbar__content__item-wrapper">
+				<slot />
+			</view>
+			<uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+		</view>
+		<view
+		    class="uv-tabbar__placeholder"
+			v-if="placeholder"
+		    :style="{
+				height: placeholderHeight + 'px',
+			}"
+		></view>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	/**
+	 * Tabbar 底部导航栏
+	 * @description 此组件提供了自定义tabbar的能力。
+	 * @tutorial https://www.uvui.cn/components/tabbar.html
+	 * @property {String | Number}	value				当前匹配项的name
+	 * @property {Boolean}			safeAreaInsetBottom	是否为iPhoneX留出底部安全距离(默认 true )
+	 * @property {Boolean}			border				是否显示上方边框(默认 true )
+	 * @property {String | Number}	zIndex				元素层级z-index(默认 1 )
+	 * @property {String}			activeColor			选中标签的颜色(默认 '#1989fa' )
+	 * @property {String}			inactiveColor		未选中标签的颜色(默认 '#7d7e80' )
+	 * @property {Boolean}			fixed				是否固定在底部(默认 true )
+	 * @property {Boolean}			placeholder			fixed定位固定在底部时,是否生成一个等高元素防止塌陷(默认 true )
+	 * @property {Object}			customStyle			定义需要用到的外部样式
+	 * 
+	 * @example <uv-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><uv-tabbar-item text="首页" icon="home" dot ></uv-tabbar-item></uv-tabbar>
+	 */
+	export default {
+		name: 'uv-tabbar',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				placeholderHeight: 0
+			}
+		},
+		computed: {
+			tabbarStyle() {
+				const style = {
+					zIndex: this.zIndex
+				}
+				// 合并来自父组件的customStyle样式
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			},
+			// 监听多个参数的变化,通过在computed执行对应的操作
+			updateChild() {
+				return [this.value, this.activeColor, this.inactiveColor]
+			},
+			updatePlaceholder() {
+				return [this.fixed, this.placeholder]
+			}
+		},
+		watch: {
+			updateChild() {
+				// 如果updateChildren中的元素发生了变化,则执行子元素初始化操作
+				this.updateChildren()
+			},
+			updatePlaceholder() {
+				// 如果fixed,placeholder等参数发生变化,重新计算占位元素的高度
+				this.setPlaceholderHeight()
+			}
+		},
+		created() {
+			this.children = []
+		},
+		mounted() {
+			this.setPlaceholderHeight()
+		},
+		methods: {
+			updateChildren() {
+				// 如果存在子元素,则执行子元素的updateFromParent进行更新数据
+				this.children.length && this.children.map(child => child.updateFromParent())
+			},
+			// 设置用于防止塌陷元素的高度
+			async setPlaceholderHeight() {
+				if (!this.fixed || !this.placeholder) return
+				// 延时一定时间
+				await this.$uv.sleep(20)
+				// #ifndef APP-NVUE
+				this.$uvGetRect('.uv-tabbar__content').then(({height = 50}) => {
+					// 修复IOS safearea bottom 未填充高度
+					this.placeholderHeight = height
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['uv-tabbar__content'], (res) => {
+					const {
+						size
+					} = res
+					this.placeholderHeight = size.height
+				})
+				// #endif
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$show-border: 1;
+	$show-border-top: 1;
+	@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-tabbar {
+		@include flex(column);
+		flex: 1;
+		justify-content: center;
+		
+		&__content {
+			@include flex(column);
+			background-color: #fff;
+			
+			&__item-wrapper {
+				height: 136rpx;
+				@include flex(row);
+			}
+		}
+
+		&--fixed {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
+		}
+	}
+</style>

+ 90 - 0
uni_modules/uv-tabbar/package.json

@@ -0,0 +1,90 @@
+{
+  "id": "uv-tabbar",
+  "displayName": "uv-tabbar 底部导航栏 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.5",
+  "description": "底部导航栏组件提供了自定义tabbar的能力,可以满足某些需要鉴权跳转等场景。",
+  "keywords": [
+    "uv-tabbar",
+    "uvui",
+    "uv-ui",
+    "tabbar",
+    "底部导航栏"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-icon",
+			"uv-badge",
+			"uv-safe-bottom"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 21 - 0
uni_modules/uv-tabbar/readme.md

@@ -0,0 +1,21 @@
+## Tabbar 底部导航栏
+
+> **组件名:uv-tabbar**
+
+此组件提供了自定义`tabbar`的能力。
+
+一般固定在底部,可以满足某些需要鉴权跳转等场景。一旦自定义底部导航栏后,为了达到不闪烁的效果,建议在`tabbar`页面进行组件切换。
+
+# <a href="https://www.uvui.cn/components/tabbar.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <span style="font-size:14px;font-weight:700;">(请不要 下载插件ZIP)</span>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 4 - 4
unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,13 +1,13 @@
 {
-  "hash": "d3f7ed84",
-  "configHash": "bc35f491",
+  "hash": "75325abd",
+  "configHash": "1b48dfbe",
   "lockfileHash": "4eefd62d",
-  "browserHash": "ec0c769e",
+  "browserHash": "2ae6d303",
   "optimized": {
     "jsencrypt": {
       "src": "../../../../../node_modules/jsencrypt/lib/index.js",
       "file": "jsencrypt.js",
-      "fileHash": "803ea3d1",
+      "fileHash": "5db203d0",
       "needsInterop": false
     }
   },

+ 15 - 15
unpackage/dist/cache/.vite/deps/jsencrypt.js

@@ -1,4 +1,4 @@
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/util.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/util.js
 var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
 function int2char(n) {
   return BI_RM.charAt(n);
@@ -50,7 +50,7 @@ function cbit(x) {
   return r;
 }
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/base64.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/base64.js
 var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 var b64pad = "=";
 function hex2b64(h) {
@@ -111,7 +111,7 @@ function b64tohex(s) {
   return ret;
 }
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/asn1js/hex.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/hex.js
 var decoder;
 var Hex = {
   decode: function(a) {
@@ -162,7 +162,7 @@ var Hex = {
   }
 };
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/asn1js/base64.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/base64.js
 var decoder2;
 var Base64 = {
   decode: function(a) {
@@ -235,7 +235,7 @@ var Base64 = {
   }
 };
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/asn1js/int10.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/int10.js
 var max = 1e13;
 var Int10 = (
   /** @class */
@@ -308,7 +308,7 @@ var Int10 = (
   }()
 );
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/asn1js/asn1.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/asn1.js
 var ellipsis = "…";
 var reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
 var reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
@@ -825,7 +825,7 @@ var ASN1Tag = (
   }()
 );
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/jsbn.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/jsbn.js
 var dbits;
 var canary = 244837814094590;
 var j_lm = (canary & 16777215) == 15715070;
@@ -2304,7 +2304,7 @@ function nbits(x) {
 BigInteger.ZERO = nbv(0);
 BigInteger.ONE = nbv(1);
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/prng4.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/prng4.js
 var Arcfour = (
   /** @class */
   function() {
@@ -2347,7 +2347,7 @@ function prng_newstate() {
 }
 var rng_psize = 256;
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/rng.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/rng.js
 var rng_state;
 var rng_pool = null;
 var rng_pptr;
@@ -2421,7 +2421,7 @@ var SecureRandom = (
   }()
 );
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
 function pkcs1pad1(s, n) {
   if (n < s.length + 22) {
     console.error("Message too long for RSA");
@@ -2732,7 +2732,7 @@ function removeDigestHeader(str) {
   return str;
 }
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsrsasign/yahoo.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/yahoo.js
 var YAHOO = {};
 YAHOO.lang = {
   /**
@@ -2788,7 +2788,7 @@ YAHOO.lang = {
   }
 };
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/lib/jsrsasign/asn1-1.0.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/asn1-1.0.js
 var KJUR = {};
 if (typeof KJUR.asn1 == "undefined" || !KJUR.asn1)
   KJUR.asn1 = {};
@@ -3526,7 +3526,7 @@ KJUR.asn1.DERTaggedObject = function(params) {
 };
 YAHOO.lang.extend(KJUR.asn1.DERTaggedObject, KJUR.asn1.ASN1Object);
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/JSEncryptRSAKey.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/JSEncryptRSAKey.js
 var __extends = /* @__PURE__ */ function() {
   var extendStatics = function(d, b) {
     extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
@@ -3700,7 +3700,7 @@ var JSEncryptRSAKey = (
   }(RSAKey)
 );
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/JSEncrypt.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/JSEncrypt.js
 var _a;
 var version = typeof process !== "undefined" ? (_a = process.env) === null || _a === void 0 ? void 0 : _a.npm_package_version : void 0;
 var JSEncrypt = (
@@ -3784,7 +3784,7 @@ var JSEncrypt = (
   }()
 );
 
-// ../../../../wpl/hjy/researchApp/node_modules/jsencrypt/lib/index.js
+// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/index.js
 var lib_default = JSEncrypt;
 export {
   JSEncrypt,

部分文件因为文件数量过多而无法显示