xian 1 سال پیش
کامیت
b6311e2dc9
100فایلهای تغییر یافته به همراه9641 افزوده شده و 0 حذف شده
  1. 0 0
      .gitignore
  2. 20 0
      .hbuilderx/launch.json
  3. BIN
      2ddc30013acd412293c16602061027bd_1640848363108_hs_pns_app.jks
  4. 19 0
      App.vue
  5. 21 0
      LICENSE
  6. 28 0
      README.md
  7. 3 0
      common/api.js
  8. 0 0
      common/common.js
  9. 3 0
      common/config.js
  10. 58 0
      common/demo.scss
  11. 7 0
      common/mixin.js
  12. 2 0
      common/props.js
  13. 95 0
      components/banner-1/banner-1.vue
  14. 72 0
      components/classify-1/classify-1.vue
  15. 93 0
      components/falls-list/index.vue
  16. 67 0
      components/homelist/homelist.vue
  17. 108 0
      components/page-nav/page-nav.vue
  18. 1386 0
      components/uqrcode/common/uqrcode.js
  19. 162 0
      components/uqrcode/uqrcode.vue
  20. 36 0
      components/watch-login/css/icon.css
  21. 134 0
      components/watch-login/watch-button.vue
  22. 219 0
      components/watch-login/watch-input.vue
  23. 72 0
      components/zai-lattice/index.css
  24. 117 0
      components/zai-lattice/index.vue
  25. BIN
      hs.keystore
  26. 48 0
      main.js
  27. 171 0
      manifest.json
  28. 11 0
      package.json
  29. 120 0
      pages.json
  30. 107 0
      pages/control/form.vue
  31. 282 0
      pages/control/index.vue
  32. 95 0
      pages/control/stat.vue
  33. 170 0
      pages/control/statDay.vue
  34. 221 0
      pages/control/timing.vue
  35. 86 0
      pages/login/css/main.css
  36. 141 0
      pages/login/forget.vue
  37. 47 0
      pages/login/login.vue
  38. 43 0
      pages/login/register.vue
  39. 37 0
      pages/login/selectOrg.vue
  40. 363 0
      static/common/js/touch-emulator.js
  41. BIN
      static/img/control/close.png
  42. BIN
      static/img/control/open.png
  43. BIN
      static/img/control/plus.png
  44. BIN
      static/img/control/switch-user.png
  45. BIN
      static/img/home/banner1.png
  46. BIN
      static/img/home/homeTop1.png
  47. BIN
      static/img/login/control.png
  48. BIN
      static/img/login/org_logo.png
  49. BIN
      static/img/logo/750x560.png
  50. BIN
      static/img/page/empty_view.png
  51. BIN
      static/img/tab-icon/Vector-active.png
  52. BIN
      static/img/tab-icon/Vector.png
  53. BIN
      static/img/tab-icon/me-active.png
  54. BIN
      static/img/tab-icon/me.png
  55. BIN
      static/img/tab-icon/order-food-active.png
  56. BIN
      static/img/tab-icon/order-food.png
  57. BIN
      static/img/tab-icon/shop-active.png
  58. BIN
      static/img/tab-icon/shop.png
  59. 17 0
      store/index.js
  60. 43 0
      template.h5.html
  61. 7 0
      uni.scss
  62. 21 0
      uni_modules/uview-ui/LICENSE
  63. 105 0
      uni_modules/uview-ui/README.md
  64. 35 0
      uni_modules/uview-ui/changelog.md
  65. 74 0
      uni_modules/uview-ui/components/u--form/u--form.vue
  66. 40 0
      uni_modules/uview-ui/components/u--image/u--image.vue
  67. 63 0
      uni_modules/uview-ui/components/u--input/u--input.vue
  68. 46 0
      uni_modules/uview-ui/components/u--text/u--text.vue
  69. 47 0
      uni_modules/uview-ui/components/u--textarea/u--textarea.vue
  70. 54 0
      uni_modules/uview-ui/components/u-action-sheet/props.js
  71. 275 0
      uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue
  72. 59 0
      uni_modules/uview-ui/components/u-album/props.js
  73. 236 0
      uni_modules/uview-ui/components/u-album/u-album.vue
  74. 44 0
      uni_modules/uview-ui/components/u-alert/props.js
  75. 243 0
      uni_modules/uview-ui/components/u-alert/u-alert.vue
  76. 46 0
      uni_modules/uview-ui/components/u-avatar-group/props.js
  77. 103 0
      uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue
  78. 78 0
      uni_modules/uview-ui/components/u-avatar/props.js
  79. 53 0
      uni_modules/uview-ui/components/u-avatar/u-avatar.vue
  80. 54 0
      uni_modules/uview-ui/components/u-back-top/props.js
  81. 137 0
      uni_modules/uview-ui/components/u-back-top/u-back-top.vue
  82. 72 0
      uni_modules/uview-ui/components/u-badge/props.js
  83. 171 0
      uni_modules/uview-ui/components/u-badge/u-badge.vue
  84. 46 0
      uni_modules/uview-ui/components/u-button/nvue.scss
  85. 156 0
      uni_modules/uview-ui/components/u-button/props.js
  86. 485 0
      uni_modules/uview-ui/components/u-button/u-button.vue
  87. 73 0
      uni_modules/uview-ui/components/u-button/vue.scss
  88. 99 0
      uni_modules/uview-ui/components/u-calendar/header.vue
  89. 570 0
      uni_modules/uview-ui/components/u-calendar/month.vue
  90. 134 0
      uni_modules/uview-ui/components/u-calendar/props.js
  91. 288 0
      uni_modules/uview-ui/components/u-calendar/u-calendar.vue
  92. 85 0
      uni_modules/uview-ui/components/u-calendar/util.js
  93. 14 0
      uni_modules/uview-ui/components/u-car-keyboard/props.js
  94. 311 0
      uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  95. 14 0
      uni_modules/uview-ui/components/u-cell-group/props.js
  96. 61 0
      uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue
  97. 109 0
      uni_modules/uview-ui/components/u-cell/props.js
  98. 224 0
      uni_modules/uview-ui/components/u-cell/u-cell.vue
  99. 82 0
      uni_modules/uview-ui/components/u-checkbox-group/props.js
  100. 103 0
      uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue

+ 0 - 0
.gitignore


+ 20 - 0
.hbuilderx/launch.json

@@ -0,0 +1,20 @@
+{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
+  // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
+    "version": "0.0",
+    "configurations": [{
+     	"app-plus" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"default" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"h5" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

BIN
2ddc30013acd412293c16602061027bd_1640848363108_hs_pns_app.jks


+ 19 - 0
App.vue

@@ -0,0 +1,19 @@
+<script>
+	export default {
+		onLaunch: function() {
+		},
+		onShow: function() {
+			
+		},
+		onHide: function() {
+			
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "@/uni_modules/uview-ui/index.scss";
+	@import "common/demo.scss";
+	
+</style>

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 www.uviewui.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 28 - 0
README.md

@@ -0,0 +1,28 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## [官方文档:https://v2.uviewui.com](https://v2.uviewui.com)
+
+## 特性
+
+- 全面兼容nvue,原生渲染,高性能
+- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
+- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
+- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
+- 众多的常用页面和布局,让您专注逻辑,事半功倍
+- 详尽的文档支持,现代化的演示效果
+- 按需引入,精简打包体积

+ 3 - 0
common/api.js

@@ -0,0 +1,3 @@
+const { http } = uni.$u
+// 获取菜单
+export const fetchMenu = (params, config = {}) => http.post('/ebapi/public_api/index', params, config)

+ 0 - 0
common/common.js


+ 3 - 0
common/config.js

@@ -0,0 +1,3 @@
+module.exports = {
+    baseUrl: 'https://api.youzixy.com'
+}

+ 58 - 0
common/demo.scss

@@ -0,0 +1,58 @@
+.u-view {
+	padding: 40px 20px 0px 20px;
+	&__title {
+		font-size: 14px;
+		color: rgb(143, 156, 162);
+		margin-bottom: 10px;
+	}
+}
+
+.u-block{
+	padding: 14px;
+	&__section{
+		margin-bottom:10px;
+	}
+	&__title {
+		margin-top:10px;
+		font-size: 15px;
+		color: $u-content-color;
+		margin-bottom:10px;
+	}
+	&__flex{
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+	}
+}
+
+// 使用了cell组件的icon图片样式
+.u-cell-icon {
+	width: 36rpx;
+	height: 36rpx;
+	margin-right: 8rpx;
+}
+
+.u-page {
+	padding: 15px 15px 40px 15px;
+}
+
+.u-demo-block {
+	flex: 1;
+	margin-bottom: 23px;
+	
+	&__content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		// flex-direction: row!important;
+		// align-items: center;
+		// flex-wrap: wrap;
+	}
+	
+	&__title {
+		font-size: 14px;
+		color: rgb(143, 156, 162);
+		margin-bottom: 8px;
+	}
+}
+

+ 7 - 0
common/mixin.js

@@ -0,0 +1,7 @@
+export default {
+    data() {
+        return {
+
+        }
+    }
+}

+ 2 - 0
common/props.js

@@ -0,0 +1,2 @@
+uni.$u.props.gap.bgColor = '#f3f4f6'
+uni.$u.props.gap.height = '10'

+ 95 - 0
components/banner-1/banner-1.vue

@@ -0,0 +1,95 @@
+<template>
+	<view style="">
+		<view class="banner">
+			<swiper class="swiper" :current="current" previous-margin="192rpx" next-margin="192rpx"
+				@change="changeCurrent">
+				<swiper-item v-for="(item, index) in list" :key="index" class="swiper-item"
+					@click="downLoad(item.link)">
+					<view class="banner-li" :class="{ 'active-item': current === index }">
+						<image class="banner-li__img" :src="url+item.imgPath" mode="">
+						</image>
+					</view>
+				</swiper-item>
+			</swiper>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'Banner1',
+		props: {
+			list: {
+				type: Array,
+				default: () => []
+			},
+			url: {
+				type: String,
+				default: null
+			}
+		},
+		data() {
+			return {
+				current: 1, // 当前展示滑块下标
+			}
+		},
+		watch: {
+
+		},
+		created() {
+
+		},
+		methods: {
+			// 切换
+			changeCurrent(event) {
+				this.current = event.detail.current
+			},
+			// 打开自定义链接
+			openCustomLink(link = '') {
+				console.log(`link: ${link}`)
+			},
+			downLoad(item) {
+				console.log(this.baseURL)
+				if (item) {
+					let url = encodeURIComponent(item)
+					uni.navigateTo({
+						url: '../../pages/home/myWebView?url=' + url
+					})
+				}
+			},
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.banner {
+		width: 100%;
+		height: 226upx;
+		background-color: #efefef;
+		padding-top: 10px;
+
+		.banner-li {
+			width: 366upx;
+			height: 206upx;
+			border-radius: 8upx;
+			overflow: hidden;
+			transform: scaleY(0.85);
+			transition: all .5s;
+
+			&__img {
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+		.swiper-item {
+			width: 366upx;
+			height: 206upx;
+			padding: 0 10upx;
+		}
+
+		.active-item {
+			transform: scaleY(1);
+		}
+	}
+</style>

+ 72 - 0
components/classify-1/classify-1.vue

@@ -0,0 +1,72 @@
+<template>
+	<view class="classify">
+		<view class="classify-li" v-for="(item, index) in list" :key="index" @click="downLoad(item.link)">
+			<image class="classify-li__img" :src="url+item.imgPath" alt=""></image>
+			<view class="classify-li__text">
+				{{ item.text }}
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'Classify1',
+		props: {
+			list: {
+				type: Array,
+				default: () => []
+			},
+			url: {
+				type: String,
+			}
+		},
+		data() {
+			return {
+				baseURL: uni.$BASE_URL
+			}
+		},
+		methods: {
+			// 打开自定义链接
+			openCustomLink(link = '') {
+				console.log(`link: ${link}`)
+			},
+			downLoad(item) {
+				if (item) {
+					let url = encodeURIComponent(item)
+					uni.navigateTo({
+						url: '../../pages/home/myWebView?url=' + url
+					})
+				}
+			},
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.classify {
+		display: flex;
+		justify-content: flex-start;
+		flex-wrap: wrap;
+		padding: 14upx 24upx 8rpx;
+
+		.classify-li {
+			flex: 0 0 20%;
+			font-size: 24upx;
+
+			&__img {
+				display: block;
+				width: 98upx;
+				height: 98upx;
+				margin: auto;
+			}
+
+			&__text {
+				display: block;
+				color: #5a5a5a;
+				text-align: center;
+				margin: 8upx 0 14upx 0;
+			}
+		}
+	}
+</style>

+ 93 - 0
components/falls-list/index.vue

@@ -0,0 +1,93 @@
+<template>
+	<view>
+		<view class="waterfall_left">
+			<view class="waterfall_list" v-for="(item,index) in goodsLeftList" :key="index">
+				<view class="waterfall_list_img">
+					<image :src="item.goods_main_pic" mode="widthFix" @load="considerPush"></image>
+				</view>
+				<view class="msg-box">
+					<view class="name single-omit">{{item.goods_name}}</view>
+					<view class="price-box flex-align-center">
+						<view class="unit"><text>¥</text>{{item.goods_sku.sku_sale_price}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="waterfall_right">
+			<view class="waterfall_list" v-for="(item,index) in goodsRightList" :key="index">
+				<view class="waterfall_list_img">
+					<image :src="item.goods_main_pic" mode="widthFix" @load="considerPush"></image>
+				</view>
+				<view class="msg-box">
+					<view class="name single-omit">{{item.goods_name}}</view>
+					<view class="price-box flex-align-center">
+						<view class="unit"><text>¥</text>{{item.goods_sku.sku_sale_price}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			list: {
+				type: Array,
+				required: true
+			},
+		},
+		data() {
+			return {
+				// 左侧商品列表
+				goodsLeftList: [],
+				// 右侧商品列表
+				goodsRightList: [],
+				// 组件数据备份
+				newList: [],
+			}
+		},
+		created() {
+			this.touchOff(); // 触发排列
+		},
+		mounted() {},
+		watch: {
+			list(newValue, oldValue) {
+				this.touchOff()
+			},
+		},
+		computed: {},
+		methods: {
+			// 触发重新排列
+			touchOff() {
+				this.newList = [...this.list];
+				this.goodsLeftList = [];
+				this.goodsRightList = [];
+				if (this.newList.length != 0) {
+					this.goodsLeftList.push(this.newList.shift()); //触发排列
+				}
+
+			},
+			// 计算排列
+			considerPush() {
+				if (this.newList.length == 0) return; //没有数据了
+				let leftH = 0,
+					rightH = 0; //左右高度
+				var query = uni.createSelectorQuery().in(this);
+				query.selectAll('.waterfall_left').boundingClientRect()
+				query.selectAll('.waterfall_right').boundingClientRect()
+				query.exec(res => {
+					leftH = res[0].length != 0 ? res[0][0].height : 0; //防止查询不到做个处理
+					rightH = res[1].length != 0 ? res[1][0].height : 0;
+					if (leftH == rightH || leftH < rightH) {
+						// 相等 || 左边小  
+						this.goodsLeftList.push(this.newList.shift());
+					} else {
+						// 右边小
+						this.goodsRightList.push(this.newList.shift());
+					}
+				});
+			},
+		}
+	}
+</script>

+ 67 - 0
components/homelist/homelist.vue

@@ -0,0 +1,67 @@
+<template>
+	<view style="border-top: 1px solid #cfcfcf;">
+		<u-list>
+			<view v-if="!list">
+				<image style="margin: 0 auto; width: 100%;" src="../../static/img/page/empty_view.png"></image>
+			</view>
+			<u-list-item v-for="(item, index) in list" :key="index" @click="downLoad(item.link)">
+				<view @click="downLoad(item.link)">
+					<u-row style="border-bottom: 1px solid #cfcfcf;">
+						<view>
+							<u-image :src="url+item.imgPath" width="120px" height="90px" style="margin: 7px;">
+							</u-image>
+						</view>
+						<view>
+							<u-text :text="item.text" mode="text" :lines="3" size="18">
+							</u-text>
+							<view style="padding: 20px 0 0 0;">
+								<u-text :text="item.updateTime" mode="text" color="#b1b1b1" size="14">
+								</u-text>
+							</view>
+						</view>
+						</u-col>
+					</u-row>
+				</view>
+			</u-list-item>
+		</u-list>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			list: {
+				type: Array,
+				default: () => []
+			},
+			url: {
+				type: String,
+			}
+		},
+		name: "homelist",
+		data() {
+			return {
+				baseURL: uni.$BASE_URL
+			};
+		},
+		methods: {
+			// 打开自定义链接
+			openCustomLink(link = '') {
+				console.log(`link: ${link}`)
+				window.location.href = "https://" + link
+			},
+			downLoad(item) {
+				if (item) {
+					let url = encodeURIComponent(item)
+					uni.navigateTo({
+						url: '../../pages/home/myWebView?url=' + url
+					})
+				}
+			},
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 108 - 0
components/page-nav/page-nav.vue

@@ -0,0 +1,108 @@
+<template>
+	<view class="nav-wrap">
+		<view class="nav-title">
+			<u--image :showLoading="true" src="https://cdn.uviewui.com/uview/common/logo.png" width="70px"
+				height="70px" />
+			<view class="nav-info">
+				<view class="nav-info__title" @tap="jumpToWx">
+					<text class="nav-info__title__text">uView 2.0</text>
+					<!-- #ifdef MP-WEIXIN -->
+					<!-- uni-app不支持text内部的text组件的tap事件,所以放到外部响应tap -->
+					<text class="nav-info__title__jump">查看1.x演示</text>
+					<!-- #endif -->
+				</view>
+				<text class="nav-slogan">多平台快速开发的UI框架</text>
+			</view>
+		</view>
+		<text class="nav-desc">{{desc}}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			desc: String,
+			title: String,
+		},
+		methods: {
+			jumpToWx() {
+				// #ifdef MP-WEIXIN
+				uni.navigateToMiniProgram({
+					appId: 'wx3be833c4a263e0c2'
+				})
+				// #endif
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	.nav-wrap {
+		padding: 15px;
+		position: relative;
+	}
+
+	.lang {
+		position: absolute;
+		top: 15px;
+		right: 15px;
+	}
+
+	.nav-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: flex-start;
+	}
+
+	.nav-info {
+		margin-left: 15px;
+		
+		&__title {
+			/* #ifndef APP-NVUE */
+			display: flex;
+			/* #endif */
+			flex-direction: row;
+			align-items: center;
+			
+			&__text {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				color: $u-main-color;
+				font-size: 25px;
+				font-weight: bold;
+				text-align: left;
+			}
+			
+			&__jump {
+				font-size: 12px;
+				color: $u-primary;
+				font-weight: normal;
+				margin-left: 20px;
+			}
+		}
+	}
+
+	.logo {
+		width: 70px;
+		height: 70px;
+		/* #ifndef APP-NVUE */
+		height: auto;
+		/* #endif */
+	}
+
+	.nav-slogan {
+		color: $u-tips-color;
+		font-size: 14px;
+	}
+
+	.nav-desc {
+		margin-top: 10px;
+		font-size: 14px;
+		color: $u-content-color;
+		line-height: 20px;
+	}
+</style>

+ 1386 - 0
components/uqrcode/common/uqrcode.js

@@ -0,0 +1,1386 @@
+//---------------------------------------------------------------------
+// github https://github.com/Sansnn/uQRCode
+// version 2.0.23
+//---------------------------------------------------------------------
+
+let uQRCode = {};
+
+(function() {
+  //---------------------------------------------------------------------
+  // QRCode for JavaScript
+  //
+  // Copyright (c) 2009 Kazuhiko Arase
+  //
+  // URL: http://www.d-project.com/
+  //
+  // Licensed under the MIT license:
+  //   http://www.opensource.org/licenses/mit-license.php
+  //
+  // The word "QR Code" is registered trademark of 
+  // DENSO WAVE INCORPORATED
+  //   http://www.denso-wave.com/qrcode/faqpatent-e.html
+  //
+  //---------------------------------------------------------------------
+
+  //---------------------------------------------------------------------
+  // QR8bitByte
+  //---------------------------------------------------------------------
+
+  function QR8bitByte(data) {
+    this.mode = QRMode.MODE_8BIT_BYTE;
+    this.data = data;
+  }
+
+  QR8bitByte.prototype = {
+
+    getLength: function(buffer) {
+      return this.data.length;
+    },
+
+    write: function(buffer) {
+      for (var i = 0; i < this.data.length; i++) {
+        // not JIS ...
+        buffer.put(this.data.charCodeAt(i), 8);
+      }
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // QRCode
+  //---------------------------------------------------------------------
+
+  function QRCode(typeNumber, errorCorrectLevel) {
+    this.typeNumber = typeNumber;
+    this.errorCorrectLevel = errorCorrectLevel;
+    this.modules = null;
+    this.moduleCount = 0;
+    this.dataCache = null;
+    this.dataList = new Array();
+  }
+
+  QRCode.prototype = {
+
+    addData: function(data) {
+      var newData = new QR8bitByte(data);
+      this.dataList.push(newData);
+      this.dataCache = null;
+    },
+
+    isDark: function(row, col) {
+      if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) {
+        throw new Error(row + "," + col);
+      }
+      return this.modules[row][col];
+    },
+
+    getModuleCount: function() {
+      return this.moduleCount;
+    },
+
+    make: function() {
+      // Calculate automatically typeNumber if provided is < 1
+      if (this.typeNumber < 1) {
+        var typeNumber = 1;
+        for (typeNumber = 1; typeNumber < 40; typeNumber++) {
+          var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, this.errorCorrectLevel);
+          var buffer = new QRBitBuffer();
+          var totalDataCount = 0;
+          for (var i = 0; i < rsBlocks.length; i++) {
+            totalDataCount += rsBlocks[i].dataCount;
+          }
+
+          for (var i = 0; i < this.dataList.length; i++) {
+            var data = this.dataList[i];
+            buffer.put(data.mode, 4);
+            buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+            data.write(buffer);
+          }
+          if (buffer.getLengthInBits() <= totalDataCount * 8)
+            break;
+        }
+        this.typeNumber = typeNumber;
+      }
+      this.makeImpl(false, this.getBestMaskPattern());
+    },
+
+    makeImpl: function(test, maskPattern) {
+
+      this.moduleCount = this.typeNumber * 4 + 17;
+      this.modules = new Array(this.moduleCount);
+
+      for (var row = 0; row < this.moduleCount; row++) {
+
+        this.modules[row] = new Array(this.moduleCount);
+
+        for (var col = 0; col < this.moduleCount; col++) {
+          this.modules[row][col] = null; //(col + row) % 3;
+        }
+      }
+
+      this.setupPositionProbePattern(0, 0);
+      this.setupPositionProbePattern(this.moduleCount - 7, 0);
+      this.setupPositionProbePattern(0, this.moduleCount - 7);
+      this.setupPositionAdjustPattern();
+      this.setupTimingPattern();
+      this.setupTypeInfo(test, maskPattern);
+
+      if (this.typeNumber >= 7) {
+        this.setupTypeNumber(test);
+      }
+
+      if (this.dataCache == null) {
+        this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
+      }
+
+      this.mapData(this.dataCache, maskPattern);
+    },
+
+    setupPositionProbePattern: function(row, col) {
+
+      for (var r = -1; r <= 7; r++) {
+
+        if (row + r <= -1 || this.moduleCount <= row + r) continue;
+
+        for (var c = -1; c <= 7; c++) {
+
+          if (col + c <= -1 || this.moduleCount <= col + c) continue;
+
+          if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
+            (0 <= c && c <= 6 && (r == 0 || r == 6)) ||
+            (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+            this.modules[row + r][col + c] = true;
+          } else {
+            this.modules[row + r][col + c] = false;
+          }
+        }
+      }
+    },
+
+    getBestMaskPattern: function() {
+
+      var minLostPoint = 0;
+      var pattern = 0;
+
+      for (var i = 0; i < 8; i++) {
+
+        this.makeImpl(true, i);
+
+        var lostPoint = QRUtil.getLostPoint(this);
+
+        if (i == 0 || minLostPoint > lostPoint) {
+          minLostPoint = lostPoint;
+          pattern = i;
+        }
+      }
+
+      return pattern;
+    },
+
+    createMovieClip: function(target_mc, instance_name, depth) {
+
+      var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth);
+      var cs = 1;
+
+      this.make();
+
+      for (var row = 0; row < this.modules.length; row++) {
+
+        var y = row * cs;
+
+        for (var col = 0; col < this.modules[row].length; col++) {
+
+          var x = col * cs;
+          var dark = this.modules[row][col];
+
+          if (dark) {
+            qr_mc.beginFill(0, 100);
+            qr_mc.moveTo(x, y);
+            qr_mc.lineTo(x + cs, y);
+            qr_mc.lineTo(x + cs, y + cs);
+            qr_mc.lineTo(x, y + cs);
+            qr_mc.endFill();
+          }
+        }
+      }
+
+      return qr_mc;
+    },
+
+    setupTimingPattern: function() {
+
+      for (var r = 8; r < this.moduleCount - 8; r++) {
+        if (this.modules[r][6] != null) {
+          continue;
+        }
+        this.modules[r][6] = (r % 2 == 0);
+      }
+
+      for (var c = 8; c < this.moduleCount - 8; c++) {
+        if (this.modules[6][c] != null) {
+          continue;
+        }
+        this.modules[6][c] = (c % 2 == 0);
+      }
+    },
+
+    setupPositionAdjustPattern: function() {
+
+      var pos = QRUtil.getPatternPosition(this.typeNumber);
+
+      for (var i = 0; i < pos.length; i++) {
+
+        for (var j = 0; j < pos.length; j++) {
+
+          var row = pos[i];
+          var col = pos[j];
+
+          if (this.modules[row][col] != null) {
+            continue;
+          }
+
+          for (var r = -2; r <= 2; r++) {
+
+            for (var c = -2; c <= 2; c++) {
+
+              if (r == -2 || r == 2 || c == -2 || c == 2 ||
+                (r == 0 && c == 0)) {
+                this.modules[row + r][col + c] = true;
+              } else {
+                this.modules[row + r][col + c] = false;
+              }
+            }
+          }
+        }
+      }
+    },
+
+    setupTypeNumber: function(test) {
+
+      var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+
+      for (var i = 0; i < 18; i++) {
+        var mod = (!test && ((bits >> i) & 1) == 1);
+        this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+      }
+
+      for (var i = 0; i < 18; i++) {
+        var mod = (!test && ((bits >> i) & 1) == 1);
+        this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+      }
+    },
+
+    setupTypeInfo: function(test, maskPattern) {
+
+      var data = (this.errorCorrectLevel << 3) | maskPattern;
+      var bits = QRUtil.getBCHTypeInfo(data);
+
+      // vertical		
+      for (var i = 0; i < 15; i++) {
+
+        var mod = (!test && ((bits >> i) & 1) == 1);
+
+        if (i < 6) {
+          this.modules[i][8] = mod;
+        } else if (i < 8) {
+          this.modules[i + 1][8] = mod;
+        } else {
+          this.modules[this.moduleCount - 15 + i][8] = mod;
+        }
+      }
+
+      // horizontal
+      for (var i = 0; i < 15; i++) {
+
+        var mod = (!test && ((bits >> i) & 1) == 1);
+
+        if (i < 8) {
+          this.modules[8][this.moduleCount - i - 1] = mod;
+        } else if (i < 9) {
+          this.modules[8][15 - i - 1 + 1] = mod;
+        } else {
+          this.modules[8][15 - i - 1] = mod;
+        }
+      }
+
+      // fixed module
+      this.modules[this.moduleCount - 8][8] = (!test);
+
+    },
+
+    mapData: function(data, maskPattern) {
+
+      var inc = -1;
+      var row = this.moduleCount - 1;
+      var bitIndex = 7;
+      var byteIndex = 0;
+
+      for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+
+        if (col == 6) col--;
+
+        while (true) {
+
+          for (var c = 0; c < 2; c++) {
+
+            if (this.modules[row][col - c] == null) {
+
+              var dark = false;
+
+              if (byteIndex < data.length) {
+                dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+              }
+
+              var mask = QRUtil.getMask(maskPattern, row, col - c);
+
+              if (mask) {
+                dark = !dark;
+              }
+
+              this.modules[row][col - c] = dark;
+              bitIndex--;
+
+              if (bitIndex == -1) {
+                byteIndex++;
+                bitIndex = 7;
+              }
+            }
+          }
+
+          row += inc;
+
+          if (row < 0 || this.moduleCount <= row) {
+            row -= inc;
+            inc = -inc;
+            break;
+          }
+        }
+      }
+
+    }
+
+  };
+
+  QRCode.PAD0 = 0xEC;
+  QRCode.PAD1 = 0x11;
+
+  QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) {
+
+    var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
+
+    var buffer = new QRBitBuffer();
+
+    for (var i = 0; i < dataList.length; i++) {
+      var data = dataList[i];
+      buffer.put(data.mode, 4);
+      buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+      data.write(buffer);
+    }
+
+    // calc num max data.
+    var totalDataCount = 0;
+    for (var i = 0; i < rsBlocks.length; i++) {
+      totalDataCount += rsBlocks[i].dataCount;
+    }
+
+    if (buffer.getLengthInBits() > totalDataCount * 8) {
+      throw new Error("code length overflow. (" +
+        buffer.getLengthInBits() +
+        ">" +
+        totalDataCount * 8 +
+        ")");
+    }
+
+    // end code
+    if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+      buffer.put(0, 4);
+    }
+
+    // padding
+    while (buffer.getLengthInBits() % 8 != 0) {
+      buffer.putBit(false);
+    }
+
+    // padding
+    while (true) {
+
+      if (buffer.getLengthInBits() >= totalDataCount * 8) {
+        break;
+      }
+      buffer.put(QRCode.PAD0, 8);
+
+      if (buffer.getLengthInBits() >= totalDataCount * 8) {
+        break;
+      }
+      buffer.put(QRCode.PAD1, 8);
+    }
+
+    return QRCode.createBytes(buffer, rsBlocks);
+  }
+
+  QRCode.createBytes = function(buffer, rsBlocks) {
+
+    var offset = 0;
+
+    var maxDcCount = 0;
+    var maxEcCount = 0;
+
+    var dcdata = new Array(rsBlocks.length);
+    var ecdata = new Array(rsBlocks.length);
+
+    for (var r = 0; r < rsBlocks.length; r++) {
+
+      var dcCount = rsBlocks[r].dataCount;
+      var ecCount = rsBlocks[r].totalCount - dcCount;
+
+      maxDcCount = Math.max(maxDcCount, dcCount);
+      maxEcCount = Math.max(maxEcCount, ecCount);
+
+      dcdata[r] = new Array(dcCount);
+
+      for (var i = 0; i < dcdata[r].length; i++) {
+        dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+      }
+      offset += dcCount;
+
+      var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+      var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+      var modPoly = rawPoly.mod(rsPoly);
+      ecdata[r] = new Array(rsPoly.getLength() - 1);
+      for (var i = 0; i < ecdata[r].length; i++) {
+        var modIndex = i + modPoly.getLength() - ecdata[r].length;
+        ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+      }
+
+    }
+
+    var totalCodeCount = 0;
+    for (var i = 0; i < rsBlocks.length; i++) {
+      totalCodeCount += rsBlocks[i].totalCount;
+    }
+
+    var data = new Array(totalCodeCount);
+    var index = 0;
+
+    for (var i = 0; i < maxDcCount; i++) {
+      for (var r = 0; r < rsBlocks.length; r++) {
+        if (i < dcdata[r].length) {
+          data[index++] = dcdata[r][i];
+        }
+      }
+    }
+
+    for (var i = 0; i < maxEcCount; i++) {
+      for (var r = 0; r < rsBlocks.length; r++) {
+        if (i < ecdata[r].length) {
+          data[index++] = ecdata[r][i];
+        }
+      }
+    }
+
+    return data;
+
+  }
+
+  //---------------------------------------------------------------------
+  // QRMode
+  //---------------------------------------------------------------------
+
+  var QRMode = {
+    MODE_NUMBER: 1 << 0,
+    MODE_ALPHA_NUM: 1 << 1,
+    MODE_8BIT_BYTE: 1 << 2,
+    MODE_KANJI: 1 << 3
+  };
+
+  //---------------------------------------------------------------------
+  // QRErrorCorrectLevel
+  //---------------------------------------------------------------------
+
+  var QRErrorCorrectLevel = {
+    L: 1,
+    M: 0,
+    Q: 3,
+    H: 2
+  };
+
+  //---------------------------------------------------------------------
+  // QRMaskPattern
+  //---------------------------------------------------------------------
+
+  var QRMaskPattern = {
+    PATTERN000: 0,
+    PATTERN001: 1,
+    PATTERN010: 2,
+    PATTERN011: 3,
+    PATTERN100: 4,
+    PATTERN101: 5,
+    PATTERN110: 6,
+    PATTERN111: 7
+  };
+
+  //---------------------------------------------------------------------
+  // QRUtil
+  //---------------------------------------------------------------------
+
+  var QRUtil = {
+
+    PATTERN_POSITION_TABLE: [
+      [],
+      [6, 18],
+      [6, 22],
+      [6, 26],
+      [6, 30],
+      [6, 34],
+      [6, 22, 38],
+      [6, 24, 42],
+      [6, 26, 46],
+      [6, 28, 50],
+      [6, 30, 54],
+      [6, 32, 58],
+      [6, 34, 62],
+      [6, 26, 46, 66],
+      [6, 26, 48, 70],
+      [6, 26, 50, 74],
+      [6, 30, 54, 78],
+      [6, 30, 56, 82],
+      [6, 30, 58, 86],
+      [6, 34, 62, 90],
+      [6, 28, 50, 72, 94],
+      [6, 26, 50, 74, 98],
+      [6, 30, 54, 78, 102],
+      [6, 28, 54, 80, 106],
+      [6, 32, 58, 84, 110],
+      [6, 30, 58, 86, 114],
+      [6, 34, 62, 90, 118],
+      [6, 26, 50, 74, 98, 122],
+      [6, 30, 54, 78, 102, 126],
+      [6, 26, 52, 78, 104, 130],
+      [6, 30, 56, 82, 108, 134],
+      [6, 34, 60, 86, 112, 138],
+      [6, 30, 58, 86, 114, 142],
+      [6, 34, 62, 90, 118, 146],
+      [6, 30, 54, 78, 102, 126, 150],
+      [6, 24, 50, 76, 102, 128, 154],
+      [6, 28, 54, 80, 106, 132, 158],
+      [6, 32, 58, 84, 110, 136, 162],
+      [6, 26, 54, 82, 110, 138, 166],
+      [6, 30, 58, 86, 114, 142, 170]
+    ],
+
+    G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+    G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+    G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+
+    getBCHTypeInfo: function(data) {
+      var d = data << 10;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+        d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+      }
+      return ((data << 10) | d) ^ QRUtil.G15_MASK;
+    },
+
+    getBCHTypeNumber: function(data) {
+      var d = data << 12;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+        d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+      }
+      return (data << 12) | d;
+    },
+
+    getBCHDigit: function(data) {
+
+      var digit = 0;
+
+      while (data != 0) {
+        digit++;
+        data >>>= 1;
+      }
+
+      return digit;
+    },
+
+    getPatternPosition: function(typeNumber) {
+      return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+    },
+
+    getMask: function(maskPattern, i, j) {
+
+      switch (maskPattern) {
+
+        case QRMaskPattern.PATTERN000:
+          return (i + j) % 2 == 0;
+        case QRMaskPattern.PATTERN001:
+          return i % 2 == 0;
+        case QRMaskPattern.PATTERN010:
+          return j % 3 == 0;
+        case QRMaskPattern.PATTERN011:
+          return (i + j) % 3 == 0;
+        case QRMaskPattern.PATTERN100:
+          return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+        case QRMaskPattern.PATTERN101:
+          return (i * j) % 2 + (i * j) % 3 == 0;
+        case QRMaskPattern.PATTERN110:
+          return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+        case QRMaskPattern.PATTERN111:
+          return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+
+        default:
+          throw new Error("bad maskPattern:" + maskPattern);
+      }
+    },
+
+    getErrorCorrectPolynomial: function(errorCorrectLength) {
+
+      var a = new QRPolynomial([1], 0);
+
+      for (var i = 0; i < errorCorrectLength; i++) {
+        a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+      }
+
+      return a;
+    },
+
+    getLengthInBits: function(mode, type) {
+
+      if (1 <= type && type < 10) {
+
+        // 1 - 9
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 10;
+          case QRMode.MODE_ALPHA_NUM:
+            return 9;
+          case QRMode.MODE_8BIT_BYTE:
+            return 8;
+          case QRMode.MODE_KANJI:
+            return 8;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else if (type < 27) {
+
+        // 10 - 26
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 12;
+          case QRMode.MODE_ALPHA_NUM:
+            return 11;
+          case QRMode.MODE_8BIT_BYTE:
+            return 16;
+          case QRMode.MODE_KANJI:
+            return 10;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else if (type < 41) {
+
+        // 27 - 40
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 14;
+          case QRMode.MODE_ALPHA_NUM:
+            return 13;
+          case QRMode.MODE_8BIT_BYTE:
+            return 16;
+          case QRMode.MODE_KANJI:
+            return 12;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else {
+        throw new Error("type:" + type);
+      }
+    },
+
+    getLostPoint: function(qrCode) {
+
+      var moduleCount = qrCode.getModuleCount();
+
+      var lostPoint = 0;
+
+      // LEVEL1
+
+      for (var row = 0; row < moduleCount; row++) {
+
+        for (var col = 0; col < moduleCount; col++) {
+
+          var sameCount = 0;
+          var dark = qrCode.isDark(row, col);
+
+          for (var r = -1; r <= 1; r++) {
+
+            if (row + r < 0 || moduleCount <= row + r) {
+              continue;
+            }
+
+            for (var c = -1; c <= 1; c++) {
+
+              if (col + c < 0 || moduleCount <= col + c) {
+                continue;
+              }
+
+              if (r == 0 && c == 0) {
+                continue;
+              }
+
+              if (dark == qrCode.isDark(row + r, col + c)) {
+                sameCount++;
+              }
+            }
+          }
+
+          if (sameCount > 5) {
+            lostPoint += (3 + sameCount - 5);
+          }
+        }
+      }
+
+      // LEVEL2
+
+      for (var row = 0; row < moduleCount - 1; row++) {
+        for (var col = 0; col < moduleCount - 1; col++) {
+          var count = 0;
+          if (qrCode.isDark(row, col)) count++;
+          if (qrCode.isDark(row + 1, col)) count++;
+          if (qrCode.isDark(row, col + 1)) count++;
+          if (qrCode.isDark(row + 1, col + 1)) count++;
+          if (count == 0 || count == 4) {
+            lostPoint += 3;
+          }
+        }
+      }
+
+      // LEVEL3
+
+      for (var row = 0; row < moduleCount; row++) {
+        for (var col = 0; col < moduleCount - 6; col++) {
+          if (qrCode.isDark(row, col) &&
+            !qrCode.isDark(row, col + 1) &&
+            qrCode.isDark(row, col + 2) &&
+            qrCode.isDark(row, col + 3) &&
+            qrCode.isDark(row, col + 4) &&
+            !qrCode.isDark(row, col + 5) &&
+            qrCode.isDark(row, col + 6)) {
+            lostPoint += 40;
+          }
+        }
+      }
+
+      for (var col = 0; col < moduleCount; col++) {
+        for (var row = 0; row < moduleCount - 6; row++) {
+          if (qrCode.isDark(row, col) &&
+            !qrCode.isDark(row + 1, col) &&
+            qrCode.isDark(row + 2, col) &&
+            qrCode.isDark(row + 3, col) &&
+            qrCode.isDark(row + 4, col) &&
+            !qrCode.isDark(row + 5, col) &&
+            qrCode.isDark(row + 6, col)) {
+            lostPoint += 40;
+          }
+        }
+      }
+
+      // LEVEL4
+
+      var darkCount = 0;
+
+      for (var col = 0; col < moduleCount; col++) {
+        for (var row = 0; row < moduleCount; row++) {
+          if (qrCode.isDark(row, col)) {
+            darkCount++;
+          }
+        }
+      }
+
+      var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+      lostPoint += ratio * 10;
+
+      return lostPoint;
+    }
+
+  };
+
+
+  //---------------------------------------------------------------------
+  // QRMath
+  //---------------------------------------------------------------------
+
+  var QRMath = {
+
+    glog: function(n) {
+
+      if (n < 1) {
+        throw new Error("glog(" + n + ")");
+      }
+
+      return QRMath.LOG_TABLE[n];
+    },
+
+    gexp: function(n) {
+
+      while (n < 0) {
+        n += 255;
+      }
+
+      while (n >= 256) {
+        n -= 255;
+      }
+
+      return QRMath.EXP_TABLE[n];
+    },
+
+    EXP_TABLE: new Array(256),
+
+    LOG_TABLE: new Array(256)
+
+  };
+
+  for (var i = 0; i < 8; i++) {
+    QRMath.EXP_TABLE[i] = 1 << i;
+  }
+  for (var i = 8; i < 256; i++) {
+    QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^
+      QRMath.EXP_TABLE[i - 5] ^
+      QRMath.EXP_TABLE[i - 6] ^
+      QRMath.EXP_TABLE[i - 8];
+  }
+  for (var i = 0; i < 255; i++) {
+    QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+  }
+
+  //---------------------------------------------------------------------
+  // QRPolynomial
+  //---------------------------------------------------------------------
+
+  function QRPolynomial(num, shift) {
+
+    if (num.length == undefined) {
+      throw new Error(num.length + "/" + shift);
+    }
+
+    var offset = 0;
+
+    while (offset < num.length && num[offset] == 0) {
+      offset++;
+    }
+
+    this.num = new Array(num.length - offset + shift);
+    for (var i = 0; i < num.length - offset; i++) {
+      this.num[i] = num[i + offset];
+    }
+  }
+
+  QRPolynomial.prototype = {
+
+    get: function(index) {
+      return this.num[index];
+    },
+
+    getLength: function() {
+      return this.num.length;
+    },
+
+    multiply: function(e) {
+
+      var num = new Array(this.getLength() + e.getLength() - 1);
+
+      for (var i = 0; i < this.getLength(); i++) {
+        for (var j = 0; j < e.getLength(); j++) {
+          num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+        }
+      }
+
+      return new QRPolynomial(num, 0);
+    },
+
+    mod: function(e) {
+
+      if (this.getLength() - e.getLength() < 0) {
+        return this;
+      }
+
+      var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0));
+
+      var num = new Array(this.getLength());
+
+      for (var i = 0; i < this.getLength(); i++) {
+        num[i] = this.get(i);
+      }
+
+      for (var i = 0; i < e.getLength(); i++) {
+        num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+      }
+
+      // recursive call
+      return new QRPolynomial(num, 0).mod(e);
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // QRRSBlock
+  //---------------------------------------------------------------------
+
+  function QRRSBlock(totalCount, dataCount) {
+    this.totalCount = totalCount;
+    this.dataCount = dataCount;
+  }
+
+  QRRSBlock.RS_BLOCK_TABLE = [
+
+    // L
+    // M
+    // Q
+    // H
+
+    // 1
+    [1, 26, 19],
+    [1, 26, 16],
+    [1, 26, 13],
+    [1, 26, 9],
+
+    // 2
+    [1, 44, 34],
+    [1, 44, 28],
+    [1, 44, 22],
+    [1, 44, 16],
+
+    // 3
+    [1, 70, 55],
+    [1, 70, 44],
+    [2, 35, 17],
+    [2, 35, 13],
+
+    // 4		
+    [1, 100, 80],
+    [2, 50, 32],
+    [2, 50, 24],
+    [4, 25, 9],
+
+    // 5
+    [1, 134, 108],
+    [2, 67, 43],
+    [2, 33, 15, 2, 34, 16],
+    [2, 33, 11, 2, 34, 12],
+
+    // 6
+    [2, 86, 68],
+    [4, 43, 27],
+    [4, 43, 19],
+    [4, 43, 15],
+
+    // 7		
+    [2, 98, 78],
+    [4, 49, 31],
+    [2, 32, 14, 4, 33, 15],
+    [4, 39, 13, 1, 40, 14],
+
+    // 8
+    [2, 121, 97],
+    [2, 60, 38, 2, 61, 39],
+    [4, 40, 18, 2, 41, 19],
+    [4, 40, 14, 2, 41, 15],
+
+    // 9
+    [2, 146, 116],
+    [3, 58, 36, 2, 59, 37],
+    [4, 36, 16, 4, 37, 17],
+    [4, 36, 12, 4, 37, 13],
+
+    // 10		
+    [2, 86, 68, 2, 87, 69],
+    [4, 69, 43, 1, 70, 44],
+    [6, 43, 19, 2, 44, 20],
+    [6, 43, 15, 2, 44, 16],
+
+    // 11
+    [4, 101, 81],
+    [1, 80, 50, 4, 81, 51],
+    [4, 50, 22, 4, 51, 23],
+    [3, 36, 12, 8, 37, 13],
+
+    // 12
+    [2, 116, 92, 2, 117, 93],
+    [6, 58, 36, 2, 59, 37],
+    [4, 46, 20, 6, 47, 21],
+    [7, 42, 14, 4, 43, 15],
+
+    // 13
+    [4, 133, 107],
+    [8, 59, 37, 1, 60, 38],
+    [8, 44, 20, 4, 45, 21],
+    [12, 33, 11, 4, 34, 12],
+
+    // 14
+    [3, 145, 115, 1, 146, 116],
+    [4, 64, 40, 5, 65, 41],
+    [11, 36, 16, 5, 37, 17],
+    [11, 36, 12, 5, 37, 13],
+
+    // 15
+    [5, 109, 87, 1, 110, 88],
+    [5, 65, 41, 5, 66, 42],
+    [5, 54, 24, 7, 55, 25],
+    [11, 36, 12],
+
+    // 16
+    [5, 122, 98, 1, 123, 99],
+    [7, 73, 45, 3, 74, 46],
+    [15, 43, 19, 2, 44, 20],
+    [3, 45, 15, 13, 46, 16],
+
+    // 17
+    [1, 135, 107, 5, 136, 108],
+    [10, 74, 46, 1, 75, 47],
+    [1, 50, 22, 15, 51, 23],
+    [2, 42, 14, 17, 43, 15],
+
+    // 18
+    [5, 150, 120, 1, 151, 121],
+    [9, 69, 43, 4, 70, 44],
+    [17, 50, 22, 1, 51, 23],
+    [2, 42, 14, 19, 43, 15],
+
+    // 19
+    [3, 141, 113, 4, 142, 114],
+    [3, 70, 44, 11, 71, 45],
+    [17, 47, 21, 4, 48, 22],
+    [9, 39, 13, 16, 40, 14],
+
+    // 20
+    [3, 135, 107, 5, 136, 108],
+    [3, 67, 41, 13, 68, 42],
+    [15, 54, 24, 5, 55, 25],
+    [15, 43, 15, 10, 44, 16],
+
+    // 21
+    [4, 144, 116, 4, 145, 117],
+    [17, 68, 42],
+    [17, 50, 22, 6, 51, 23],
+    [19, 46, 16, 6, 47, 17],
+
+    // 22
+    [2, 139, 111, 7, 140, 112],
+    [17, 74, 46],
+    [7, 54, 24, 16, 55, 25],
+    [34, 37, 13],
+
+    // 23
+    [4, 151, 121, 5, 152, 122],
+    [4, 75, 47, 14, 76, 48],
+    [11, 54, 24, 14, 55, 25],
+    [16, 45, 15, 14, 46, 16],
+
+    // 24
+    [6, 147, 117, 4, 148, 118],
+    [6, 73, 45, 14, 74, 46],
+    [11, 54, 24, 16, 55, 25],
+    [30, 46, 16, 2, 47, 17],
+
+    // 25
+    [8, 132, 106, 4, 133, 107],
+    [8, 75, 47, 13, 76, 48],
+    [7, 54, 24, 22, 55, 25],
+    [22, 45, 15, 13, 46, 16],
+
+    // 26
+    [10, 142, 114, 2, 143, 115],
+    [19, 74, 46, 4, 75, 47],
+    [28, 50, 22, 6, 51, 23],
+    [33, 46, 16, 4, 47, 17],
+
+    // 27
+    [8, 152, 122, 4, 153, 123],
+    [22, 73, 45, 3, 74, 46],
+    [8, 53, 23, 26, 54, 24],
+    [12, 45, 15, 28, 46, 16],
+
+    // 28
+    [3, 147, 117, 10, 148, 118],
+    [3, 73, 45, 23, 74, 46],
+    [4, 54, 24, 31, 55, 25],
+    [11, 45, 15, 31, 46, 16],
+
+    // 29
+    [7, 146, 116, 7, 147, 117],
+    [21, 73, 45, 7, 74, 46],
+    [1, 53, 23, 37, 54, 24],
+    [19, 45, 15, 26, 46, 16],
+
+    // 30
+    [5, 145, 115, 10, 146, 116],
+    [19, 75, 47, 10, 76, 48],
+    [15, 54, 24, 25, 55, 25],
+    [23, 45, 15, 25, 46, 16],
+
+    // 31
+    [13, 145, 115, 3, 146, 116],
+    [2, 74, 46, 29, 75, 47],
+    [42, 54, 24, 1, 55, 25],
+    [23, 45, 15, 28, 46, 16],
+
+    // 32
+    [17, 145, 115],
+    [10, 74, 46, 23, 75, 47],
+    [10, 54, 24, 35, 55, 25],
+    [19, 45, 15, 35, 46, 16],
+
+    // 33
+    [17, 145, 115, 1, 146, 116],
+    [14, 74, 46, 21, 75, 47],
+    [29, 54, 24, 19, 55, 25],
+    [11, 45, 15, 46, 46, 16],
+
+    // 34
+    [13, 145, 115, 6, 146, 116],
+    [14, 74, 46, 23, 75, 47],
+    [44, 54, 24, 7, 55, 25],
+    [59, 46, 16, 1, 47, 17],
+
+    // 35
+    [12, 151, 121, 7, 152, 122],
+    [12, 75, 47, 26, 76, 48],
+    [39, 54, 24, 14, 55, 25],
+    [22, 45, 15, 41, 46, 16],
+
+    // 36
+    [6, 151, 121, 14, 152, 122],
+    [6, 75, 47, 34, 76, 48],
+    [46, 54, 24, 10, 55, 25],
+    [2, 45, 15, 64, 46, 16],
+
+    // 37
+    [17, 152, 122, 4, 153, 123],
+    [29, 74, 46, 14, 75, 47],
+    [49, 54, 24, 10, 55, 25],
+    [24, 45, 15, 46, 46, 16],
+
+    // 38
+    [4, 152, 122, 18, 153, 123],
+    [13, 74, 46, 32, 75, 47],
+    [48, 54, 24, 14, 55, 25],
+    [42, 45, 15, 32, 46, 16],
+
+    // 39
+    [20, 147, 117, 4, 148, 118],
+    [40, 75, 47, 7, 76, 48],
+    [43, 54, 24, 22, 55, 25],
+    [10, 45, 15, 67, 46, 16],
+
+    // 40
+    [19, 148, 118, 6, 149, 119],
+    [18, 75, 47, 31, 76, 48],
+    [34, 54, 24, 34, 55, 25],
+    [20, 45, 15, 61, 46, 16]
+  ];
+
+  QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) {
+
+    var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
+
+    if (rsBlock == undefined) {
+      throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" +
+        errorCorrectLevel);
+    }
+
+    var length = rsBlock.length / 3;
+
+    var list = new Array();
+
+    for (var i = 0; i < length; i++) {
+
+      var count = rsBlock[i * 3 + 0];
+      var totalCount = rsBlock[i * 3 + 1];
+      var dataCount = rsBlock[i * 3 + 2];
+
+      for (var j = 0; j < count; j++) {
+        list.push(new QRRSBlock(totalCount, dataCount));
+      }
+    }
+
+    return list;
+  }
+
+  QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) {
+
+    switch (errorCorrectLevel) {
+      case QRErrorCorrectLevel.L:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+      case QRErrorCorrectLevel.M:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+      case QRErrorCorrectLevel.Q:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+      case QRErrorCorrectLevel.H:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+      default:
+        return undefined;
+    }
+  }
+
+  //---------------------------------------------------------------------
+  // QRBitBuffer
+  //---------------------------------------------------------------------
+
+  function QRBitBuffer() {
+    this.buffer = new Array();
+    this.length = 0;
+  }
+
+  QRBitBuffer.prototype = {
+
+    get: function(index) {
+      var bufIndex = Math.floor(index / 8);
+      return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
+    },
+
+    put: function(num, length) {
+      for (var i = 0; i < length; i++) {
+        this.putBit(((num >>> (length - i - 1)) & 1) == 1);
+      }
+    },
+
+    getLengthInBits: function() {
+      return this.length;
+    },
+
+    putBit: function(bit) {
+
+      var bufIndex = Math.floor(this.length / 8);
+      if (this.buffer.length <= bufIndex) {
+        this.buffer.push(0);
+      }
+
+      if (bit) {
+        this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+      }
+
+      this.length++;
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // Support Chinese
+  //---------------------------------------------------------------------
+  function utf16To8(text) {
+    var result = '';
+    var c;
+    for (var i = 0; i < text.length; i++) {
+      c = text.charCodeAt(i);
+      if (c >= 0x0001 && c <= 0x007F) {
+        result += text.charAt(i);
+      } else if (c > 0x07FF) {
+        result += String.fromCharCode(0xE0 | c >> 12 & 0x0F);
+        result += String.fromCharCode(0x80 | c >> 6 & 0x3F);
+        result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+      } else {
+        result += String.fromCharCode(0xC0 | c >> 6 & 0x1F);
+        result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+      }
+    }
+    return result;
+  }
+
+  uQRCode = {
+    errorCorrectLevel: QRErrorCorrectLevel,
+
+    defaults: {
+      size: 354,
+      margin: 0,
+      backgroundColor: '#FFFFFF',
+      foregroundColor: '#000000',
+      fileType: 'png', // 'jpg', 'png'
+      errorCorrectLevel: QRErrorCorrectLevel.H,
+      typeNumber: -1
+    },
+
+    getModules: function(options) {
+      // options = Object.assign(this.defaults, options); // 不能用Object.assign,它会导致this.defaults被污染
+      options = {
+        ...this.defaults,
+        ...options
+      };
+      var qrcode = new QRCode(options.typeNumber, options.errorCorrectLevel);
+      qrcode.addData(utf16To8(options.text));
+      qrcode.make();
+      return qrcode.modules;
+    },
+
+    make: function(options, componentInstance) {
+      return new Promise((resolve, reject) => {
+        options = {
+          ...this.defaults,
+          ...options
+        }
+        if (!options.canvasId) {
+          throw new Error('uQRCode: Please set canvasId!');
+        }
+        var modules = this.getModules(options);
+        var tileW = (options.size - options.margin * 2) / modules.length;
+        var tileH = tileW;
+        var startTime = Date.now();
+
+        var ctx = uni.createCanvasContext(options.canvasId, componentInstance);
+        // ctx.draw(false); // 解决重复绘制,ctx.draw(true);调用过多导致真机卡顿、掉帧等问题
+        ctx.setFillStyle(options.backgroundColor);
+        ctx.fillRect(0, 0, options.size, options.size);
+        for (var row = 0; row < modules.length; row++) {
+          for (var col = 0; col < modules.length; col++) {
+            // 计算每一个小块的位置
+            var x = Math.round(col * tileW) + options.margin;
+            var y = Math.round(row * tileH) + options.margin;
+            var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW);
+            var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW);
+            var style = modules[row][col] ?
+              options.foregroundColor :
+              options.backgroundColor;
+            ctx.setFillStyle(style);
+            ctx.fillRect(x, y, w, h);
+            // ctx.draw(true);
+          }
+        }
+        
+        var drawDelay = options.drawDelay ? options.drawDelay : options.size * 2;
+        setTimeout(function() {
+          ctx.draw(false, function() {
+            // canvasToTempFilePath不加延时在微信小程序安卓端会混乱,所有问题主要就出现在这:canvasToTempFilePath,本来绘制是正确的,调用它的过程中,组件还是绘制状态,所以导致绘制异常,因为没有确保状态正常的回调方法,draw的回调也不行,所以加延时确保组件状态正常,再调用。
+            var drawTime = Date.now() - startTime;
+            var toFileDelay = options.toFileDelay ?
+              options.toFileDelay :
+              drawTime + // draw绘制耗费时间
+              (options.size * 2) + // 绘制大小的宽高,用来作为耗时参数
+              (modules.length * 2); // 矩阵信息,对应宽高,用来作为耗时参数
+            setTimeout(function() {
+              uni.canvasToTempFilePath({
+                canvasId: options.canvasId,
+                fileType: options.fileType,
+                width: options.size,
+                height: options.size,
+                destWidth: options.size,
+                destHeight: options.size,
+                success: function(res) {
+                  resolve(Object.assign(res, {
+                    time: Date.now() - startTime
+                  }));
+                },
+                fail: function(err) {
+                  reject(err);
+                }
+              }, componentInstance);
+            }, toFileDelay);
+          });
+        }, drawDelay);
+
+      });
+    }
+
+  }
+
+})();
+
+export default uQRCode;

+ 162 - 0
components/uqrcode/uqrcode.vue

@@ -0,0 +1,162 @@
+<template>
+  <view class="uqrcode">
+    <view v-if="options.mode === 'view'" class="uqrcode-view" :style="{
+      'width': `${options.size}px`, 
+      'height': `${options.size}px`,
+      'padding': `${options.margin}px`,
+      'background-color': options.backgroundColor
+      }">
+      <view class="uqrcode-view-row" v-for="(row, rowIndex) in modules.length" :key="rowIndex">
+        <view class="uqrcode-view-col" v-for="(col, colIndex) in modules.length" :key="colIndex" :style="{
+        	'width': `${colSize}px`,
+        	'height': `${colSize}px`,
+        	'background-color': modules[rowIndex][colIndex] ? options.foregroundColor : options.backgroundColor
+        	}">
+        </view>
+      </view>
+    </view>
+    <canvas v-else-if="options.mode === 'canvas'" class="uqrcode-canvas" :id="options.canvasId" :canvas-id="options.canvasId" :style="{'width': `${options.size}px`, 'height': `${options.size}px`}" />
+  </view>
+</template>
+
+<script>
+  import uqrcode from './common/uqrcode'
+
+  export default {
+    name: 'uqrcode',
+    // props: {
+    //   mode: {
+    //     type: String,
+    //     default: 'view' // view|canvas
+    //   }
+    // },
+    data() {
+      return {
+        options: uqrcode.defaults,
+        modules: [],
+        result: {}
+      }
+    },
+    computed: {
+      colSize() {
+        return (this.options.size - this.options.margin * 2) / this.modules.length
+      }
+    },
+    methods: {
+      make(options) {
+        options = {
+          ...this.options,
+          ...options
+        }
+        if (!options.mode) {
+          options.mode = 'view'
+        }
+        if (!options.canvasId) {
+          options.canvasId = this.uuid()
+        }
+        this.options = options
+        if (options.mode === 'view') {
+          this.modules = uqrcode.getModules(options)
+        } else if (options.mode === 'canvas') {
+          return new Promise((resolve, reject) => {
+            uqrcode.make(options, this).then(res => {
+              this.result = res
+              resolve({
+                ...res
+              })
+            }).catch(err => {
+              reject(err)
+            })
+          })
+        }
+      },
+      save() {
+        if (this.options.mode === 'view') {
+          uni.showToast({
+            icon: 'none',
+            title: 'view模式不支持保存,请提示用户使用截屏保存'
+          })
+        } else if (this.options.mode === 'canvas') {
+          // #ifdef H5
+          uni.showToast({
+            icon: 'none',
+            title: 'canvas H5不支持保存,请将二维码放置在image组件,再提示用户长按image保存'
+          })
+          // #endif
+          // #ifndef H5
+          uni.saveImageToPhotosAlbum({
+            filePath: this.result.tempFilePath,
+            success: (res) => {
+              uni.showToast({
+                icon: 'success',
+                title: '保存成功'
+              })
+            },
+            fail: (err) => {
+              uni.showToast({
+                icon: 'none',
+                title: JSON.stringify(err)
+              })
+            }
+          })
+          // #endif
+        }
+      },
+      uuid(len = 32, firstU = true, radix = null) {
+        let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+        let uuid = [];
+        radix = radix || chars.length;
+
+        if (len) {
+          // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+          for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
+        } else {
+          let r;
+          // rfc4122标准要求返回的uuid中,某些位为固定的字符
+          uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+          uuid[14] = '4';
+
+          for (let i = 0; i < 36; i++) {
+            if (!uuid[i]) {
+              r = 0 | Math.random() * 16;
+              uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+            }
+          }
+        }
+
+        // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+        if (firstU) {
+          uuid.shift();
+          return 'u' + uuid.join('');
+        } else {
+          return uuid.join('');
+        }
+      }
+    }
+  }
+</script>
+
+<style>
+  .uqrcode-view {
+    /* #ifndef APP-NVUE */
+    display: flex;
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: column;
+  }
+
+  .uqrcode-view-row {
+    /* #ifndef APP-NVUE */
+    display: flex;
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
+  }
+
+  .uqrcode-view-col {
+    /* #ifndef APP-NVUE */
+    display: flex;
+    box-sizing: border-box;
+    /* #endif */
+  }
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 36 - 0
components/watch-login/css/icon.css


+ 134 - 0
components/watch-login/watch-button.vue

@@ -0,0 +1,134 @@
+<template>
+	<view>
+		<!-- 按钮 -->
+		<button 
+			:class="['buttonBorder',!_rotate?'dlbutton':'dlbutton_loading']" 
+			:style="{'background':bgColor, 'color': fontColor}"
+            
+			@click="$emit('click', $event)"
+			@contact="$emit('contact', $event)"
+			@error="$emit('error', $event)"
+			@getphonenumber="$emit('getphonenumber', $event)"
+			@getuserinfo="$emit('getuserinfo', $event)"
+			@launchapp="$emit('launchapp', $event)"
+			@longtap="$emit('longtap', $event)"
+			@opensetting="$emit('opensetting', $event)"
+			@touchcancel="$emit('touchcancel', $event)"
+			@touchend="$emit('touchend', $event)"
+			@touchmove="$emit('touchmove', $event)"
+			@touchstart="$emit('touchstart', $event)"
+		>
+			<view :class="_rotate?'rotate_loop':''">
+				<text v-if="_rotate" class="cuIcon cuIcon-loading1 "></text>
+				<view v-if="!_rotate"><slot name="text">{{ text }}</slot></view>
+			</view>
+		</button>
+	</view>
+</template>
+
+<script>
+	export default{
+		props:{
+			text: String, //显示文本
+			rotate:{
+				//是否启动加载
+				type: [Boolean,String],
+				default: false,
+			}, 
+			bgColor:{
+				//按钮背景颜色
+				type: String,
+				default: "linear-gradient(to right, rgba(0,0,0,0.7), rgba(0,0,0,0.6))",
+			},
+			fontColor:{
+				//按钮字体颜色
+				type: String,
+				default: "#FFFFFF",
+			},
+		},
+		computed:{
+			_rotate() {
+				//处理值
+				return String(this.rotate) !== 'false'
+			},
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+	
+	button{
+		outline: none;  /* 或者 outline: 0 */
+	}
+	button:after {  
+	    border: none;  
+	}
+	button:focus{
+		outline: none;  /* 或者 outline: 0 */
+	}
+	
+	.dlbutton {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #FFFFFF;
+		font-size: 30rpx;
+		white-space:nowrap;
+		overflow: hidden;
+		width:601rpx;
+		height:100rpx;
+		background:linear-gradient(to right, rgba(0,0,0,0.7), rgba(0,0,0,0.6));
+		box-shadow:0rpx 0rpx 13rpx 0rpx rgba(164,217,228,0.4);
+		border-radius:2.5rem;
+		margin-top: 0rpx;
+	}
+	.dlbutton_loading {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #FFFFFF;
+		font-size: 30rpx;
+		width:100rpx;
+		height:100rpx;
+		background:linear-gradient(to right, rgba(0,0,0,0.7), rgba(0,0,0,0.6));
+		box-shadow:0rpx 0rpx 13rpx 0rpx rgba(164,217,228,0.4);
+		border-radius:2.5rem;
+		margin-top: 0rpx;
+	}
+	.buttonBorder{
+	    border: none ;
+	    border-radius: 2.5rem ;
+	    -webkit-box-shadow: 0 0 60rpx 0 rgba(0,0,0,.2) ;
+	    box-shadow: 0 0 60rpx 0 rgba(0,0,0,.2) ;
+	    -webkit-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -moz-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -ms-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -o-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	}
+	
+	/* 旋转动画 */
+	.rotate_loop{
+		-webkit-transition-property: -webkit-transform;
+	    -webkit-transition-duration: 1s;
+	    -moz-transition-property: -moz-transform;
+	    -moz-transition-duration: 1s;
+	    -webkit-animation: rotate 1s linear infinite;
+	    -moz-animation: rotate 1s linear infinite;
+	    -o-animation: rotate 1s linear infinite;
+	    animation: rotate 1s linear infinite;
+	}
+	@-webkit-keyframes rotate{from{-webkit-transform: rotate(0deg)}
+	    to{-webkit-transform: rotate(360deg)}
+	}
+	@-moz-keyframes rotate{from{-moz-transform: rotate(0deg)}
+	    to{-moz-transform: rotate(359deg)}
+	}
+	@-o-keyframes rotate{from{-o-transform: rotate(0deg)}
+	    to{-o-transform: rotate(359deg)}
+	}
+	@keyframes rotate{from{transform: rotate(0deg)}
+	    to{transform: rotate(359deg)}
+	}
+</style>

+ 219 - 0
components/watch-login/watch-input.vue

@@ -0,0 +1,219 @@
+<template>
+	<view class="main-list oBorder">
+		<!-- 文本框 -->
+		<input 
+			class="main-input" 
+			:value="value" 
+			:type="_type"
+			:focus="_focus"
+			:maxlength="maxlength" 
+			:placeholder="placeholder" 
+			:password="type==='password'&&!showPassword" 
+			:disabled="isDisabled"
+			@input="$emit('input', $event.detail.value)"
+			@blur="$emit('blur', $event)"
+			@focus="$emit('focus', $event)"
+			@longpress="$emit('longpress', $event)"
+			@confirm="$emit('confirm', $event)"
+			@click="$emit('click', $event)"
+			@longtap="$emit('longtap', $event)"
+			@touchcancel="$emit('touchcancel', $event)"
+			@touchend="$emit('touchend', $event)"
+			@touchmove="$emit('touchmove', $event)"
+			@touchstart="$emit('touchstart', $event)"
+		/>
+		<!-- 是否可见密码 -->
+		<image 
+			v-if="_isShowPass&&type==='password'&&!_isShowCode"
+			class="img cuIcon" 
+			:class="showPassword?'cuIcon-attention':'cuIcon-attentionforbid'" 
+			@tap="showPass"
+		></image>
+		<!-- 倒计时 -->
+		<view 
+			v-if="_isShowCode&&!_isShowPass"
+			:class="['vercode',{'vercode-run': second>0}]" 
+			@click="setCode"
+		>{{ getVerCodeSecond }}</view>
+		
+	</view>
+</template>
+
+<script>
+	let _this, countDown;
+	export default{
+		data(){
+			return{
+				showPassword: false, //是否显示明文
+				second: 0, //倒计时
+				isRunCode: false, //是否开始倒计时
+			}
+		},
+		props:{
+			type: String, //类型
+			value: String, //值
+			placeholder: String, //框内提示
+			maxlength: {
+				//最大长度
+				type: [Number,String],
+				default: 20,
+			},
+			isDisabled:false,
+			isShowPass:{
+				//是否显示密码图标(二选一)
+				type: [Boolean,String],
+				default: false,
+			},
+			isShowCode:{
+				//是否显示获取验证码(二选一)
+				type: [Boolean,String],
+				default: false,
+			},
+			codeText:{
+				type: String,
+				default: "获取验证码",
+			},
+			setTime:{
+				//倒计时时间设置
+				type: [Number,String],
+				default: 60,
+			},
+			focus:{  
+				//是否聚焦  
+				type: [Boolean,String],  
+				default: false  
+			}  
+		},
+		model: {
+			prop: 'value',
+			event: 'input'
+		},
+		mounted() {
+			_this=this
+			//准备触发
+			this.$on('runCode',(val)=>{
+                this.runCode(val);
+            });
+			clearInterval(countDown);//先清理一次循环,避免缓存
+		},
+		methods:{
+			showPass(){
+				//是否显示密码
+				this.showPassword = !this.showPassword
+			},
+			setCode(){
+				//设置获取验证码的事件
+				if(this.isRunCode){
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.$emit('setCode')
+			},
+			runCode(val){
+				//开始倒计时
+				if(String(val)=="0"){
+					
+					//判断是否需要终止循环
+					this.second = 0; //初始倒计时
+					clearInterval(countDown);//清理循环
+					this.isRunCode= false; //关闭循环状态
+					return false;
+				}
+				if(this.isRunCode){
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.isRunCode= true
+				this.second = this._setTime //倒数秒数
+				
+				let _this=this;
+				countDown = setInterval(function(){
+					_this.second--
+					if(_this.second==0){
+						_this.isRunCode= false
+						clearInterval(countDown)
+					}
+				},1000)
+			}
+		},
+		computed:{
+			_type(){
+				//处理值
+				const type = this.type
+				return type == 'password' ? 'text' : type
+			},
+			_isShowPass() {
+				//处理值
+				return String(this.isShowPass) !== 'false'
+			},
+			_isShowCode() {
+				//处理值
+				return String(this.isShowCode) !== 'false'
+			},
+			_setTime() {
+				//处理值
+				const setTime = Number(this.setTime)
+				return setTime>0 ? setTime : 60
+			},
+			_focus() {  
+				//处理值  
+				return String(this.focus) !== 'false'  
+			},  
+			getVerCodeSecond(){
+				//验证码倒计时计算
+				if(this.second<=0){
+					return this.codeText;
+				}else{
+					if(this.second<10){
+						return '0'+this.second;
+					}else{
+						return this.second;
+					}
+				}
+				
+			}
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+	
+	.main-list{
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		/* height: 36rpx; */   /* Input 高度 */
+		color: #333333;
+		padding: 40rpx 32rpx;
+		margin:32rpx 0;
+	}
+	.img{
+		width: 32rpx;
+		height: 32rpx;
+		font-size: 32rpx;
+	}
+	.main-input{
+		flex: 1;
+		text-align: left;
+		font-size: 28rpx;
+		/* line-height: 100rpx; */
+		padding-right: 10rpx;
+		margin-left: 20rpx;
+	}
+	.vercode {
+		color: rgba(0,0,0,0.7);
+		font-size: 24rpx;
+		/* line-height: 100rpx; */
+	}
+	.vercode-run {
+		color: rgba(0,0,0,0.4) !important;
+	}
+	.oBorder{
+	    border: none;
+	    border-radius: 2.5rem ;
+	    -webkit-box-shadow: 0 0 60rpx 0 rgba(43,86,112,.1) ;
+	    box-shadow: 0 0 60rpx 0 rgba(43,86,112,.1) ;
+	}
+</style>

+ 72 - 0
components/zai-lattice/index.css

@@ -0,0 +1,72 @@
+.zai-lattice-box {
+	width: 90%;
+	margin: 0 auto;
+	position: relative;
+	background: #a59ae2;
+	color: #fff;
+	border-radius: 30upx;
+	transition: background 3s;
+}
+
+.zai-progress {
+	position: relative;
+	top: -10px;
+	transition: background-color 3s;
+}
+
+.zai-lattice-box-shadow {
+	background: #F7FFF9;
+	box-shadow: 0px 7px 14px rgba(98, 160, 153, 0.35);
+	border-radius: 19px;
+}
+
+.zai-lattice-box .zai-lattice-box-p {
+	padding: 40upx;
+	position: relative;
+}
+
+.zai-lattice-box-p .zai-lattice-title {
+	color: #EFEEF2;
+	margin-top: 20upx;
+	font-size: 44upx;
+}
+
+.zai-lattice-box-p .zai-lattice-num {
+	margin-bottom: 0upx;
+}
+
+.zai-lattice-box-p .zai-lattice-num .zai-lattice-num-title {
+	font-weight: 600;
+	font-size: 52upx;
+}
+
+.zai-lattice-now-day{
+	font-size: 18upx;
+}
+
+.zai-lattice-box-p .zai-lattice-num .zai-lattice-num-unit {
+	font-size: 34upx;
+	margin-left: 10upx;
+	color: #A1A0B5;
+}
+
+.zai-lattice-box-p .zai-lattice-icon {
+	color: #A1A0B5;
+	font-size: 66upx;
+	position: absolute;
+	right: 40upx;
+	bottom: 40upx;
+}
+
+.zai-lattice-box-p .zai-lattice-img {
+	position: absolute;
+	right: 40upx;
+	bottom: 40upx;
+	width: 66upx;
+	height: 66upx;
+}
+
+.zai-lattice-box-p .zai-lattice-img .zai-lattice-image {
+	width: 66upx;
+	height: 66upx;
+}

+ 117 - 0
components/zai-lattice/index.vue

@@ -0,0 +1,117 @@
+<template name="zai-lattice">
+	<view class="zai-lattice-box" :class="shadow?'zai-lattice-box-shadow':''" :style="{'background':backgroundColor}">
+		<view class="zai-lattice-box-p">
+			<!-- 			<progress :percent="progressPercent" class="zai-progress" active stroke-width="5"
+				:backgroundColor="backgroundColor" :activeColor="progressColor" /> -->
+			<u-line-progress v-if="showNow" :percentage="progressPercent" class="zai-progress" active stroke-width="5"
+				:showText="false" :activeColor="progressColor" :inactiveColor="backgroundColor" />
+			<view class="zai-lattice-title" :style="{color:titleColor}">{{title}}</view>
+			<view v-if="showNow">
+				<text class="zai-lattice-now-day" :style="{color:numColor}">今日累计</text>
+			</view>
+			<view v-if="showNow" class="zai-lattice-num">
+				<text class="zai-lattice-num-title" :style="{color:numColor}">{{num}}</text>
+				<text class="zai-lattice-num-unit" :style="{color:unitColor}">{{unit}}</text>
+			</view>
+			<view class="zai-lattice-icon" v-if="type=='icon'" :class="icon"
+				:style="{color:iconColor,'font-size':fontSize}"></view>
+			<view class="zai-lattice-img" v-if="type=='img'" :style="{width:fontSize,height:fontSize}"
+				@click="onClick()">
+				<image :src="src" mode="aspectFit" class="zai-lattice-image" :style="{width:fontSize,height:fontSize}">
+				</image>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "zai-lattice",
+		props: {
+			//背景颜色
+			backgroundColor: {
+				type: [String],
+				default: '#f6fff9'
+			},
+			//是否开启阴影
+			shadow: Boolean,
+			//进度条颜色
+			progressColor: {
+				type: [String],
+				default: '#26a495'
+			},
+			//进度条位置
+			progressPercent: {
+				type: [Number],
+				default: 0
+			},
+			//名称
+			title: String,
+			//数量
+			num: {
+				type: [Number, String],
+				default: 0
+			},
+			//单位
+			unit: String,
+			//字体图标
+			icon: String,
+			//图片地址
+			src: {
+				type: [String],
+				default: '../../static/img/control/close.png'
+			},
+			//图标颜色
+			iconColor: {
+				type: [String],
+				default: '#A1A0B5'
+			},
+			//单位颜色
+			unitColor: {
+				type: [String],
+				default: '#26a495'
+			},
+			//数量颜色
+			numColor: {
+				type: [String],
+				default: '#26a495'
+			},
+			//名称颜色
+			titleColor: {
+				type: [String],
+				default: '#EFEEF2'
+			},
+			//图标或图片大小
+			size: {
+				type: [Number, String],
+				default: 33
+			},
+			//图标类型
+			type: {
+				type: [String],
+				default: 'icon'
+			},
+			//显示今日累计
+			showNow: {
+				type: [Boolean],
+				default: true
+			},
+		},
+		computed: {
+			fontSize() {
+				var size = Number(this.size)
+				size = isNaN(size) ? 33 : size
+				return `${size}px`
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style>
+	@import "./index.css";
+</style>

BIN
hs.keystore


+ 48 - 0
main.js

@@ -0,0 +1,48 @@
+import Vue from 'vue'
+import App from './App'
+import store from './store'
+
+// 引入全局uView
+import uView from '@/uni_modules/uview-ui'
+import mixin from './common/mixin'
+import {
+	httpRequest,
+	BASE_URL,
+	goto,
+	goTab,
+	goBack,
+	dateResult
+} from './util/request/api.js'
+
+
+Vue.prototype.$httpRequest = httpRequest
+Vue.prototype.$BASE_URL = BASE_URL
+Vue.prototype.$goto = goto
+Vue.prototype.$goTab = goTab
+Vue.prototype.$goBack = goBack
+Vue.prototype.$dateResult = dateResult
+
+Vue.prototype.$store = store
+
+Vue.config.productionTip = false
+
+App.mpType = 'app'
+Vue.use(uView)
+
+// #ifdef MP
+// 引入uView对小程序分享的mixin封装
+const mpShare = require('@/uni_modules/uview-ui/libs/mixin/mpShare.js')
+Vue.mixin(mpShare)
+// #endif
+
+Vue.mixin(mixin)
+
+const app = new Vue({
+	store,
+	...App
+})
+
+// 引入请求封装
+require('./util/request/index')(app)
+
+app.$mount()

+ 171 - 0
manifest.json

@@ -0,0 +1,171 @@
+{
+    "name" : "会远控",
+    "appid" : "__UNI__CCEB8E1",
+    "description" : "会控App",
+    "versionName" : "1.2.8",
+    "versionCode" : 20,
+    "transformPx" : false,
+    "app-plus" : {
+        //"popGesture" : "none",
+        "optimization" : {
+            "subPackages" : true
+        },
+        "safearea" : {
+            "background" : "#ffffff",
+            "bottom" : {
+                "offset" : "none"
+            }
+        },
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : false,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "modules" : {
+            "Webview-x5" : {}
+        },
+        "distribute" : {
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.BLUETOOTH_PRIVILEGED\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
+            },
+            "ios" : {
+                "idfa" : false,
+                "dSYMs" : false
+            },
+            "sdkConfigs" : {
+                "ad" : {}
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            },
+            "splashscreen" : {
+                "androidStyle" : "default",
+                "android" : {
+                    "hdpi" : "unpackage/resources/__UNI__CCEB8E1/pns_app_StartPage.png",
+                    "xhdpi" : "unpackage/resources/__UNI__CCEB8E1/pns_app_StartPage.png",
+                    "xxhdpi" : "unpackage/resources/__UNI__CCEB8E1/pns_app_StartPage.png"
+                }
+            }
+        }
+    },
+    "quickapp" : {},
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false,
+            "es6" : false,
+            "minified" : false,
+            "postcss" : false
+        },
+        "optimization" : {
+            "subPackages" : true
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true,
+        "component2" : true
+    },
+    "mp-qq" : {
+        "optimization" : {
+            "subPackages" : true
+        },
+        "appid" : "15646153"
+    },
+    "mp-baidu" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "h5" : {
+        "template" : "template.h5.html",
+        "router" : {
+            "mode" : "hash",
+            "base" : ""
+        },
+        "optimization" : {
+            "treeShaking" : {
+                "enable" : false
+            }
+        },
+        "title" : "uView UI",
+        "sdkConfigs" : {
+            "maps" : {}
+        },
+        "domain" : "",
+        "devServer" : {
+            "proxy" : {
+                //配置代理服务器来解决跨域问题,uniapp不适用CORS方案和设置JSONP方案
+                "/api/" : {
+                    //映射域名
+                    "target" : "http://114.115.142.166:8080/",
+                    "pathRewrite" : {
+                        "^/api" : ""
+                    }
+                }
+            }
+        }
+    },
+    "locale" : "zh-Hans",
+    "fallbackLocale" : "zh-Hans"
+}

+ 11 - 0
package.json

@@ -0,0 +1,11 @@
+{
+	"id": "uview-ui",
+	"scripts": {
+		"test": "eslint . --fix"
+	},
+	"dependencies": {},
+	"devDependencies": {
+		"eslint": "^8.2.0",
+		"eslint-config-airbnb": "^19.0.0"
+	}
+}

+ 120 - 0
pages.json

@@ -0,0 +1,120 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/control/index",
+			"style": {
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": true,
+				"backgroundColor": "#ffffff",
+				"backgroundColorBottom": "#ffffff",
+				"backgroundColorTop": "#ffffff",
+				"app-plus": {
+					"titleNView": false
+				}
+			}
+		
+		},{
+			"path": "pages/login/login",
+			"style": {
+				"app-plus": {
+					"navigationBarTextStyle": "black",
+					"titleNView": false
+				}
+			}
+		}, {
+			"path": "pages/login/forget",
+			"style": {
+				"navigationBarTitleText": "找回密码",
+				"enablePullDownRefresh": false,
+				"navigationBarTextStyle": "black",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#ffffff"
+					}
+				}
+			}
+		}, {
+			"path": "pages/login/register",
+			"style": {
+				"navigationBarTitleText": "注册",
+				"enablePullDownRefresh": false,
+				"navigationBarTextStyle": "black",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#ffffff"
+					}
+				}
+			}
+		}
+		, {
+			"path": "pages/control/form",
+			"style": {
+				"titleNView": false,
+				"navigationBarTextStyle": "black",
+				"navigationBarTitleText":"新增设备",
+				"enablePullDownRefresh": false,
+				"backgroundColor": "#26a495",
+				"backgroundColorBottom": "#26a495",
+				"backgroundColorTop": "#26a495",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#26a495"
+					}
+				}
+			}
+
+		}, {
+			"path": "pages/control/timing",
+			"style": {
+				"titleNView": false,
+				"navigationBarTextStyle": "black",
+				"navigationBarTitleText":"定时设置",
+				"enablePullDownRefresh": false,
+				"backgroundColor": "#26a495",
+				"backgroundColorBottom": "#26a495",
+				"backgroundColorTop": "#26a495",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#26a495"
+					}
+				}
+			}
+
+		}, {
+			"path": "pages/control/stat",
+			"style": {
+				"titleNView": false,
+				"navigationBarTextStyle": "black",
+				"navigationBarTitleText":"月度统计",
+				"enablePullDownRefresh": false,
+				"backgroundColor": "#26a495",
+				"backgroundColorBottom": "#26a495",
+				"backgroundColorTop": "#26a495",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#26a495"
+					}
+				}
+			}
+		}, {
+			"path": "pages/control/statDay",
+			"style": {
+				"titleNView": false,
+				"navigationBarTextStyle": "black",		
+				"navigationBarTitleText":"每日用时",
+				"enablePullDownRefresh": false,
+				"backgroundColor": "#26a495",
+				"backgroundColorBottom": "#26a495",
+				"backgroundColorTop": "#26a495",
+				"app-plus": {
+					"titleNView": {
+						"backgroundColor": "#26a495"
+					}
+				}
+			}
+		}
+	],
+	"globalStyle": {
+		"app-plus": {}
+	}
+}

+ 107 - 0
pages/control/form.vue

@@ -0,0 +1,107 @@
+<template>
+	<view class="u-page">
+		<view class="u-demo-block">
+			<text class="u-demo-block__title">新增设备</text>
+			<view class="">
+				<!-- 注意,如果需要兼容微信小程序,最好通过setRules方法设置rules规则 -->
+				<u--form labelPosition="left" ref="form">
+					<u-form-item label="名称" prop="" borderBottom ref="item1">
+						<u--input v-model="form.deviceName" border="none" placeholder="设备名称"></u--input>
+					</u-form-item>
+					<u-form-item label="编号" prop="" borderBottom ref="item1" @click="openUseNo()">
+						<u--input  v-model="form.useNoName" disabledColor="#ffffff" placeholder="请选择编号" border="none" :disabled="true">
+						</u--input>
+						<u-icon slot="right" name="arrow-right"></u-icon>
+					</u-form-item>
+				</u--form>
+				<view>
+					<u-picker keyName="useNoName" :show="show" :columns="columns" @confirm="confirm" @cancel="cancel"
+						confirmColor="#53a591">
+					</u-picker>
+				</view>
+				<view style="margin: 30px;">
+					<u-button color="#26a495" text="提交" @click="submitAdd()"></u-button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				show: false,
+				data: [],
+				columns: [
+					[]
+				],
+				form: {
+					deviceName:'',
+					useNoName :'',
+					deviceUseNo:''
+				}
+			}
+		},
+		onLoad() {
+			this.getList()
+		},
+		methods: {
+			openUseNo() {
+				this.show = true
+			},
+			confirm(e) {
+				console.log('confirm', e)
+				this.form.useNoName = e.value[0].useNoName;
+				this.form.deviceUseNo = e.value[0].useNoId;
+				this.show = false
+			},
+			cancel() {
+				uni.showTabBar({
+					animation: true
+				})
+				this.show = false
+			},
+			async getList() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/useNoList',
+					method: 'get',
+				});
+				if (res.code === 200) {
+					var data = res.data
+					for (var i = 0; i < data.length; i++) {
+						this.columns[0].push(data[i])
+					}
+				} else {
+
+				}
+			},
+			async submitAdd() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/add',
+					method: 'post',
+					data: this.form
+				});
+				if (res.code === 200) {
+					this.form = {}
+					uni.showLoading({
+						icon: 'ok',
+						duration: 2500,
+						title: "保存成功",
+					});
+					this.$goto('index')
+				} else {
+			
+				}
+			},
+		},
+	}
+</script>
+
+<style lang="scss">
+
+</style>

+ 282 - 0
pages/control/index.vue

@@ -0,0 +1,282 @@
+<template>
+	<view style="padding: 30px 10px 10px;;margin-top: 20px;">
+		<view style="text-align: right;margin: 0 20px 10px;">
+			<image src="../../static/img/control/switch-user.png" style="width: 30px;height: 30px;"
+				@click="isOutLoginShow()">
+				</u-image>
+		</view>
+		<view class="controlDiv" v-for="item in data" @longpress="longpress(item)">
+			<zai-lattice v-if="item.runningStatus === 0" class="controlBody" backgroundColor="#dae3dd" shadow
+				progressColor='#cecece' type="img" :progressPercent='item.dosagePct' :title='item.deviceName'
+				:num='item.dosage' :unit='item.dosageUnit' src="../../static/img/control/close.png"
+				@click="latticeClick(item)" titleColor='#969696' unitColor="#969696" numColor="#969696" />
+
+			<zai-lattice v-if="item.runningStatus === 1" class="controlBody" backgroundColor="#f6fff9" shadow
+				progressColor='#26a495' type="img" :progressPercent='item.dosagePct' :title='item.deviceName'
+				:num='item.dosage' :unit='item.dosageUnit' src="../../static/img/control/open.png"
+				@click="latticeClick(item)" titleColor='#26a495' />
+		</view>
+		<view class="controlDiv">
+			<zai-lattice :showNow="false" class="controlBody" backgroundColor="#dae3dd" titleColor='#26a495' shadow
+				progressColor='#ffffff' type="img" title='新增' src="../../static/img/control/plus.png"
+				@click="toForm()" />
+		</view>
+		<view>
+			<u-action-sheet :actions="list" :title="title" :show="show" @close="close" @select="selectClick">
+			</u-action-sheet>
+		</view>
+	</view>
+</template>
+
+<script>
+	import zaiLattice from "../../components/zai-lattice";
+	export default {
+		components: {
+			zaiLattice
+		},
+		data() {
+			return {
+				openOrCloseButton: true,
+				title: '操作',
+				list: [{
+						name: '编辑',
+						type: 1,
+						subname: "修改名称",
+						color: '#000000',
+						fontSize: '20'
+					},
+					{
+						name: '电量统计',
+						type: 5,
+						subname: "月度统计",
+						color: '#000000',
+						fontSize: '20'
+					},
+					{
+						name: '用时统计',
+						type: 2,
+						subname: "月度统计",
+						color: '#000000',
+						fontSize: '20'
+					},
+					{
+						name: '定时设置',
+						type: 3,
+						subname: "智能启停",
+						color: '#000000',
+						fontSize: '20'
+					},
+					{
+						name: '删除',
+						type: 4,
+						subname: "直接删除",
+						color: '#000000',
+						fontSize: '20'
+					}
+				],
+				show: false,
+				data: [],
+				form: {
+					deviceInfoId: null,
+					deviceId: null,
+					useNoId: null,
+					isOpenOrClose: null
+				}
+			}
+		},
+		onLoad() {
+			this.getList()
+			const value1 = uni.getStorageSync('userId');
+			const value2 = uni.getStorageSync('token');
+			if (!value1 && !value2) {
+				this.$goto('../login/login');
+			}
+		},
+		onShow() {
+			this.getList()
+		},
+		onHide() {
+
+		},
+		onPullDownRefresh() {
+			this.getList()
+			uni.stopPullDownRefresh();
+		},
+		methods: {
+			toForm() {
+				this.$goto('form')
+			},
+			selectClick(index) {
+
+				if (index.type === 1) {
+					this.edit()
+				} else if (index.type === 2) {
+					this.$goto('stat?type=sc')
+				} else if (index.type === 3) {
+					this.$goto('timing')
+				} else if (index.type === 4) {
+					this.delete()
+				} else if (index.type === 5) {
+					this.$goto("stat?type=nh")
+				}
+			},
+			async getList() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/list?userId=' + uni.getStorageSync('userId'),
+					method: 'get',
+					isNotErrorMsg: true
+				});
+				if (res.code === 200) {
+					this.data = res.data
+					console.log(this.data)
+				} else {
+
+				}
+			},
+			async openOrClose(item) {
+				if (item.runningStatus === 0) {
+					this.form.isOpenOrClose = 1
+				} else {
+					this.form.isOpenOrClose = 0
+				}
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/openOrClose',
+					method: 'post',
+					data: this.form
+				});
+				setTimeout(() => {
+					if (res.code === 200 && item.runningStatus === 0 || res.code === 500 && item
+						.runningStatus === 1) {
+						item.runningStatus = 1
+						item.dosagePct = 100
+					} else if (res.code === 500 && item.runningStatus === 0 || res.code === 200 && item
+						.runningStatus === 1) {
+						item.runningStatus = 0
+						item.dosagePct = 0
+					}
+					this.openOrCloseButton = true
+				}, 2000)
+			},
+			latticeClick(item) {
+				if (this.openOrCloseButton) {
+					this.openOrCloseButton = false
+					this.form.deviceInfoId = item.id
+					this.form.deviceId = item.deviceId
+					this.form.useNoId = item.deviceUseNo
+					if (item.runningStatus === 0) {
+						item.dosagePct = 100
+					} else {
+						item.dosagePct = 0
+					}
+					this.openOrClose(item)
+				}
+			},
+			longpress(item) {
+				uni.setStorageSync("deviceInfo", item)
+				this.show = true
+			},
+			isOutLoginShow() {
+				uni.showModal({
+					title: '提示',
+					content: '是否退回到登录页面',
+					confirmColor: '#26a495', //删除字体的颜色
+					cancelColor: '#343047', //取消字体的颜色
+					success: function(res) {
+						if (res.confirm) {
+							uni.clearStorageSync()
+							uni.reLaunch({
+								url: '../login/login'
+							})
+						} else if (res.cancel) {}
+					}
+				});
+			},
+			edit() {
+				var that = this
+				uni.showModal({
+					title: '编辑',
+					placeholderText: "修改名称",
+					editable: true,
+					confirmColor: '#26a495', //删除字体的颜色
+					cancelColor: '#000000', //取消字体的颜色
+					success: function(res) {
+						if (res.confirm) {
+							var data = uni.getStorageSync("deviceInfo")
+							data.deviceName = res.content
+							that.editDeviceInfo(data)
+							uni.redirectTo({
+								url: 'index'
+							});
+						} else if (res.cancel) {}
+					}
+				});
+			},
+			delete() {
+				var that = this
+				uni.showModal({
+					title: '提示',
+					content: "确认删除吗?",
+					confirmColor: '#26a495', //删除字体的颜色
+					cancelColor: '#000000', //取消字体的颜色
+					success: function(res) {
+						if (res.confirm) {
+							var id = uni.getStorageSync("deviceInfo").id
+							that.deleteDeviceInfo(id)
+						} else if (res.cancel) {}
+					}
+				});
+			},
+			async deleteDeviceInfo(id) {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/delete?deviceInfoId=' + id,
+					method: 'get',
+				});
+				if (res.code === 200) {
+					this.getList()
+				} else {
+
+				}
+			},
+			async editDeviceInfo(data) {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/editDevice',
+					method: 'put',
+					data: data
+				});
+				if (res.code === 200) {
+
+				} else {
+
+				}
+			},
+			open() {
+				// console.log('open');
+			},
+			close() {
+				this.show = false
+				// console.log('close');
+			}
+		}
+	}
+</script>
+
+<style>
+	page {
+		background: linear-gradient(to bottom right, #ececec, #ececec);
+	}
+
+	.controlDiv {
+		float: left;
+		width: 50%;
+		margin-bottom: 20px;
+		border-radius: 10px;
+	}
+</style>

+ 95 - 0
pages/control/stat.vue

@@ -0,0 +1,95 @@
+<template>
+	<view>
+		<view>
+			<u-row style="padding: 10px;border-bottom: 1px solid #9c9c9c;">
+				<u-col span="4">
+					<view class="viewCent">
+						<u-text text="年-月" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'sc'">
+					<view class="viewCent">
+						<u-text text="运行时间" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'nh'">
+					<view class="viewCent">
+						<u-text text="电量消耗 (预估)" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="2">
+				</u-col>
+			</u-row>
+		</view>
+		<view v-for="item in dataList">
+			<u-row style="padding: 10px;border-bottom: 1px solid #aaaaaa;">
+				<u-col span="4">
+					<view class="viewCent">
+						<u-text :text="item.createTimeVo" size="18"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'sc'">
+					<view class="viewCent">
+						<u-text :text="item.dosage" size="18"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'nh'">
+					<view class="viewCent">
+						<u-text :text="(parseFloat(item.remark) * nhBz).toFixed(2) +' kWh'" size="18"></u-text>
+					</view>
+				</u-col>
+				<u-col span="2" @click="toStatDayPage(item)">
+					<view class="viewCent">
+						<u-text text="详细" size="12" color="#26a495"></u-text>
+					</view>
+				</u-col>
+			</u-row>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				dataList: [],
+				type: '',
+				nhBz: 0
+			}
+		},
+		onLoad(option) {
+			if (option && option.type) {
+				console.log(option.type)
+				this.type = option.type
+			}
+			this.nhBz = parseFloat(uni.getStorageSync('deviceInfo').energyConsumption)
+			this.getList()
+		},
+		methods: {
+			async getList() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/statList?useNoId=' + uni.getStorageSync('deviceInfo').deviceUseNo,
+					method: 'get'
+				});
+				if (res.code === 200) {
+					this.dataList = res.data
+
+				} else {
+
+				}
+			},
+			toStatDayPage(item) {
+				uni.setStorageSync('yearMonth', item.createTimeVo)
+				this.$goto('statDay?type=' + this.type)
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.viewCent {
+		margin: 0 auto;
+	}
+</style>

+ 170 - 0
pages/control/statDay.vue

@@ -0,0 +1,170 @@
+<template>
+	<view>
+		<view>
+			<u-row style="padding: 10px;border-bottom: 1px solid #000000;">
+				<u-col span="4">
+					<view class="viewCent">
+						<u-text text="日期" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'sc'">
+					<view class="viewCent">
+						<u-text text="运行时间" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="type == 'nh'">
+					<view class="viewCent">
+						<u-text text="电量消耗 (预估)" size="14"></u-text>
+					</view>
+				</u-col>
+				<u-col span="2">
+				</u-col>
+			</u-row>
+		</view>
+		<view v-for="item in dataList">
+			<u-row style="padding: 10px;border-bottom: 1px solid #aaaaaa;">
+				<u-col span="4">
+					<view class="viewCent">
+						<u-text :text="item.createTimeVo" size="18"></u-text>
+					</view>
+				</u-col>
+				<u-col span="6" v-if="">
+					<view class="viewCent">
+						<u-text v-if="item.isStat === 0 && type == 'sc'" :text="item.dosage" size="18"></u-text>
+						<u-text v-if="item.isStat === 0 && type == 'nh'"
+							:text="(parseFloat(item.remark) * nhBz).toFixed(2) +' kWh'" size="18"></u-text>
+						<u-text v-if="item.isStat === 1" text="统计中" color="#1c81df" size="18"></u-text>
+						<u-text v-if="item.isStat === 2" text="未使用" color="#ffaa00" size="18"></u-text>
+					</view>
+				</u-col>
+				<u-col span="2" @click="toStatDayPage(item)">
+					<view class="viewCent">
+						<u-text text="日志" size="12" color="#26a495"></u-text>
+					</view>
+				</u-col>
+			</u-row>
+		</view>
+		<u-popup :show="show" @close="close" @open="open">
+			<view style="padding: 20px 20px 0 20px;">
+				<view>
+					<u-row style="padding: 10px;border-bottom: 1px solid #aaaaaa;">
+						<u-col span="9">
+							<view class="viewCent">
+								<u-text text="时间" size="18"></u-text>
+							</view>
+						</u-col>
+						<u-col span="3">
+							<view class="viewCent">
+								<u-text text="操作" size="18"></u-text>
+							</view>
+						</u-col>
+					</u-row>
+					<view v-if="logList.length === 0" style="text-align: center;margin-top: 40px;">
+						暂无数据
+					</view>
+					<view>
+						<u-list style="height:400px;">
+							<u-list-item v-for="item in logList">
+								<view>
+									<u-row style="padding: 10px;border-bottom: 1px solid #aaaaaa;">
+										<u-col span="9">
+											<view class="viewCent">
+												<u-text :text="item.createTime" size="16"></u-text>
+											</view>
+										</u-col>
+										<u-col span="3">
+											<view class="viewCent">
+												<u-text v-if="item.useDescribe === '打开'" :text="item.useDescribe"
+													color="#00cb62" size="18"></u-text>
+												<u-text v-else color="#cf0000" :text="item.useDescribe" size="18">
+												</u-text>
+											</view>
+										</u-col>
+									</u-row>
+								</view>
+							</u-list-item>
+						</u-list>
+					</view>
+				</view>
+				<view style="text-align: center; margin-top: 20px;position: absolute;top: -10px;right: 15px;">
+					<view @click="show = false;">
+						<u-icon name="close" size="18"></u-icon>
+					</view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				show: false,
+				dataList: [],
+				logList: [],
+				type: '',
+				nhBz: 0
+			}
+		},
+		onLoad(option) {
+			if (option && option.type) {
+				console.log(option.type)
+				this.type = option.type
+			}
+			this.nhBz = parseFloat(uni.getStorageSync('deviceInfo').energyConsumption)
+			console.log(this.nhBz)
+			this.getList()
+		},
+		methods: {
+			async getList() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/statDayList?useNoId=' + uni.getStorageSync('deviceInfo').deviceUseNo +
+						'&yearMonth=' + uni.getStorageSync('yearMonth'),
+					method: 'get'
+				});
+				if (res.code === 200) {
+					this.dataList = res.data
+					console.log(this.dataList)
+				} else {
+
+				}
+			},
+			toStatDayPage(item) {
+				this.show = true
+				this.getLogList(item)
+			},
+			async getLogList(item) {
+				this.logList = []
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/useLogList?useNoId=' + item.useNoId +
+						'&date=' + item.createTime.substr(0, 10),
+					method: 'get'
+				});
+				if (res.code === 200) {
+					this.logList = res.data
+					console.log(this.logList)
+				} else {
+
+				}
+			},
+			open() {
+				// console.log('open');
+			},
+			close() {
+				this.show = false
+				// console.log('close');
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.viewCent {
+		margin: 0 auto;
+	}
+</style>

+ 221 - 0
pages/control/timing.vue

@@ -0,0 +1,221 @@
+<template>
+	<view style="padding: 10px;">
+		<u--form>
+			<text class="u-demo-block__title">执行周期</text>
+			<u-form-item>
+				<view style="padding: 10px;">
+					<checkbox-group @change="checkboxChange">
+						<label v-for="item in checkboxList" :key="item.value">
+							<view>
+								<u-row style="width: 90px;margin-bottom: 5px;">
+									<u-col span="4">
+										<checkbox :value="item.value" :checked="item.checked" />
+									</u-col>
+									<u-col span="8">
+										<view>{{item.value}}</view>
+									</u-col>
+								</u-row>
+
+							</view>
+						</label>
+					</checkbox-group>
+				</view>
+			</u-form-item>
+
+			<text class="u-demo-block__title">开启/停止 时间</text>
+			<view style="padding: 10px;">
+				<view style="padding: 15px; border-bottom: 1px solid #d6d6d6;">
+					<picker mode="time" :value="form.startTime" @change="bindTimeChangeStart">
+						<text class="tit" style="font-size: 14px; color: #787878; margin-right:50upx;">开启时间</text>
+						<text class="uni-input" v-if="form.startTime" style="width:320upx;">{{form.startTime}}</text>
+						<text class="uni-input grey" v-else>请选择开启时间</text>
+					</picker>
+				</view>
+				<view style="padding: 15px; border-bottom: 1px solid #d6d6d6;">
+					<picker mode="time" :value="form.endTime" @change="bindTimeChangeEnd">
+						<text style="font-size: 14px; color: #787878; margin-right:50upx;">停止时间</text>
+						<text class="uni-input" v-if="form.endTime" style="width:520upx;">{{form.endTime}}</text>
+						<text class="uni-input grey" v-else>请选择停止时间</text>
+					</picker>
+				</view>
+			</view>
+		</u--form>
+
+		<view class="uni-list">
+		</view>
+		<view style="margin: 30px;">
+			<u-button color="#26a495" text="提交" @click="submit()"></u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				show: false,
+				showBirthday: false,
+				time: '13:00',
+				timeType: 0,
+				timeValue: '',
+				data: [],
+				//横向排列形式数据
+				checkboxList: [{
+						value: '星期日',
+						checked: false
+					}, {
+						value: '星期一',
+						checked: false
+					}, {
+						value: '星期二',
+						checked: false
+					}, {
+						value: '星期三',
+						checked: false
+					}, {
+						value: '星期四',
+						checked: false
+					}, {
+						value: '星期五',
+						checked: false
+					},
+					{
+						value: '星期六',
+						checked: false
+					}
+				],
+				form: {
+					cycle: [],
+					useNoId: 0,
+					startTime: null,
+					endTime: null
+				}
+			}
+		},
+		onLoad() {
+			this.form.useNoId = uni.getStorageSync("deviceInfo").deviceUseNo
+			this.getTimingInfo()
+		},
+		methods: {
+			bindTimeChangeStart: function(e) {
+				this.form.startTime = e.target.value + ":00"
+			},
+			bindTimeChangeEnd: function(e) {
+				this.form.endTime = e.target.value + ":00"
+			},
+			checkboxChange: function(e) {
+				var items = this.checkboxList,
+					values = e.detail.value;
+				for (var i = 0, lenI = items.length; i < lenI; ++i) {
+					const item = items[i]
+					if (values.includes(item.value)) {
+						this.$set(item, 'checked', true)
+					} else {
+						this.$set(item, 'checked', false)
+					}
+				}
+			},
+			openUseNo() {
+				this.show = true
+			},
+			confirm(e) {
+				//console.log('confirm', e)
+				this.orgInfo = e.value[0];
+				this.show = false
+			},
+			cancel() {
+				uni.showTabBar({
+					animation: true
+				})
+				this.show = false
+			},
+			submit() {
+				let st = this.form.startTime
+				let et = this.form.endTime
+				st = st.split(":")
+				et = et.split(":")
+				if (st[0] === et[0]) {
+					var cz = parseInt(et[1]) - parseInt(st[1])
+					if(cz < 5){
+						uni.showToast({
+							icon:"none",
+							duration: 2500,
+							title: "间隔时间必须大于5分钟",
+						});
+						return;
+					}
+				}
+				var cycle = []
+				this.checkboxList.forEach((c, i) => {
+					if (c.checked === true) {
+						cycle.push(i)
+					}
+				})
+				this.form.cycle = cycle + ''
+				this.submitTiming()
+			},
+			async submitTiming() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/timing',
+					method: 'put',
+					data: this.form,
+					isNotErrorMsg: true
+				});
+				if (res.code === 200) {
+					var data = res.data
+					uni.showToast({
+						duration: 1000,
+						title: "保存成功",
+					});
+				} else {
+
+				}
+			},
+			async getTimingInfo() {
+				const {
+					data: res
+				} = await this.$httpRequest({
+					url: '/api/control/timing?' + "useNoId=" + this.form.useNoId,
+					method: 'get',
+					isNotErrorMsg: true
+				});
+				if (res.code === 200) {
+					var data = res.data
+					console.log(data)
+					this.form.startTime = data.startTime
+					this.form.endTime = data.endTime
+					data.cycle.split(",").forEach((c, i) => {
+						this.checkboxList[c].checked = true
+					})
+				} else {
+
+				}
+			},
+			hideKeyboard() {
+				uni.hideKeyboard()
+			},
+			result(time, mode) {
+				const timeFormat = uni.$u.timeFormat,
+					toast = uni.$u.toast
+				switch (mode) {
+					case 'datetime':
+						return toast(timeFormat(time, 'yyyy-mm-dd hh:MM'))
+					case 'date':
+						return toast(timeFormat(time, 'yyyy-mm-dd'))
+					case 'year-month':
+						return toast(timeFormat(time, 'yyyy-mm'))
+					case 'time':
+						return toast(time)
+					default:
+						return ''
+				}
+			},
+
+		}
+	}
+</script>
+
+<style>
+</style>

+ 86 - 0
pages/login/css/main.css

@@ -0,0 +1,86 @@
+.content {
+	display: flex;
+	flex-direction: column;
+	justify-content:center;
+	/* margin-top: 128rpx; */
+}
+
+/* 头部 logo */
+.header {
+	width:161rpx;
+	height:161rpx;
+	box-shadow:0rpx 0rpx 60rpx 0rpx rgba(0,0,0,0.1);
+	border-radius:50%;
+	background-color: #f4f4f4; 
+	margin-top: 328rpx;	
+	margin-bottom: 72rpx;
+	margin-left: auto;
+	margin-right: auto;
+}
+.header image{
+	width:168rpx;
+	height:141rpx;
+}
+
+/* 主体 */
+.main {
+	display: flex;
+	flex-direction: column;
+	padding-left: 70rpx;
+	padding-right: 70rpx;
+}
+.tips {
+	color: #999999;
+	font-size: 28rpx;
+	margin-top: 64rpx;
+	margin-left: 48rpx;
+}
+
+/* 登录按钮 */
+.wbutton{
+	margin-top: 96rpx;
+}
+
+/* 其他登录方式 */
+.other_login{
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	margin-top: 256rpx;
+	text-align: center;
+}
+.login_icon{
+	border: none;
+	font-size: 64rpx;
+	margin: 0 64rpx 0 64rpx;
+	color: rgba(0,0,0,0.7)
+}
+.wechat_color{
+	color: #83DC42;
+}
+.weibo_color{
+	color: #F9221D;
+}
+.github_color{
+	color: #24292E;
+}
+
+/* 底部 */
+.footer{
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	font-size: 28rpx;
+	margin-top: 64rpx;
+	color: rgba(0,0,0,0.7);
+	text-align: center;
+	height: 40rpx;
+	line-height: 40rpx;
+}
+.footer text{
+	font-size: 24rpx;
+	margin-left: 15rpx;
+	margin-right: 15rpx;
+}

+ 141 - 0
pages/login/forget.vue

@@ -0,0 +1,141 @@
+<template>
+	<view class="forget">
+		
+		<view class="content">
+			<!-- 主体 -->
+			<view class="main">
+				<view class="tips">若你忘记了密码,可在此重置新密码。</view>
+				<wInput
+					v-model="phoneData"
+					type="text"
+					maxlength="11"
+					placeholder="请输入手机号码"
+				></wInput>
+				<wInput
+					v-model="passData"
+					type="password"
+					maxlength="11"
+					placeholder="请输入新密码"
+					isShowPass
+				></wInput>
+				
+				<wInput
+					v-model="verCode"
+					type="number"
+					maxlength="4"
+					placeholder="验证码"
+					
+					isShowCode
+					codeText="获取重置码"
+					setTime="30"
+					ref="runCode"
+					@setCode="getVerCode()"
+				></wInput>
+			</view>
+			
+			<wButton 
+				class="wbutton"
+				text="重置密码"
+				:rotate="isRotate" 
+				@click.native="startRePass()"
+			></wButton>
+
+		</view>
+	</view>
+</template>
+
+<script>
+	let _this;
+	import wInput from '../../components/watch-login/watch-input.vue' //input
+	import wButton from '../../components/watch-login/watch-button.vue' //button
+	export default {
+		data() {
+			return {
+				phoneData: "", //电话
+				passData: "", //密码
+				verCode:"", //验证码
+				isRotate: false, //是否加载旋转
+			}
+		},
+		components:{
+			wInput,
+			wButton
+		},
+		mounted() {
+			_this= this;
+		},
+		methods: {
+			getVerCode(){
+				//获取验证码
+				if (_this.phoneData.length != 11) {
+				     uni.showToast({
+				        icon: 'none',
+						position: 'bottom',
+				        title: '手机号不正确'
+				    });
+				    return false;
+				}
+				console.log("获取验证码")
+				this.$refs.runCode.$emit('runCode'); //触发倒计时(一般用于请求成功验证码后调用)
+				uni.showToast({
+				    icon: 'none',
+					position: 'bottom',
+				    title: '模拟倒计时触发'
+				});
+				
+				setTimeout(function(){
+					_this.$refs.runCode.$emit('runCode',0); //假装模拟下需要 终止倒计时
+					uni.showToast({
+					    icon: 'none',
+						position: 'bottom',
+					    title: '模拟倒计时终止'
+					});
+				},3000)
+			},
+			startRePass() {
+				//重置密码
+				if(this.isRotate){
+					//判断是否加载中,避免重复点击请求
+					return false;
+				}
+				if (this.phoneData.length != 11) {
+				    uni.showToast({
+				        icon: 'none',
+						position: 'bottom',
+				        title: '手机号不正确'
+				    });
+				    return false;
+				}
+			    if (this.passData.length < 6) {
+			        uni.showToast({
+			            icon: 'none',
+						position: 'bottom',
+			            title: '密码不正确'
+			        });
+			        return false;
+			    }
+				if (this.verCode.length != 4) {
+				    uni.showToast({
+				        icon: 'none',
+						position: 'bottom',
+				        title: '验证码不正确'
+				    });
+				    return false;
+				}
+				console.log("重置密码成功")
+				_this.isRotate=true
+				setTimeout(function(){
+					_this.isRotate=false
+				},3000)
+				
+				
+			}
+		}
+	}
+</script>
+
+<style>
+	@import url("../../components/watch-login/css/icon.css");
+	@import url("./css/main.css");
+</style>
+

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 47 - 0
pages/login/login.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 43 - 0
pages/login/register.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 37 - 0
pages/login/selectOrg.vue


+ 363 - 0
static/common/js/touch-emulator.js

@@ -0,0 +1,363 @@
+(function (window, document, exportName, undefined) {
+    "use strict";
+
+    var isMultiTouch = false;
+    var multiTouchStartPos;
+    var eventTarget;
+    var touchElements = {};
+
+    // polyfills
+    if (!document.createTouch) {
+        document.createTouch = function (view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
+            // auto set
+            if (clientX == undefined || clientY == undefined) {
+                clientX = pageX - window.pageXOffset;
+                clientY = pageY - window.pageYOffset;
+            }
+
+            return new Touch(target, identifier, {
+                pageX: pageX,
+                pageY: pageY,
+                screenX: screenX,
+                screenY: screenY,
+                clientX: clientX,
+                clientY: clientY
+            });
+        };
+    }
+
+    if (!document.createTouchList) {
+        document.createTouchList = function () {
+            var touchList = new TouchList();
+            for (var i = 0; i < arguments.length; i++) {
+                touchList[i] = arguments[i];
+            }
+            touchList.length = arguments.length;
+            return touchList;
+        };
+    }
+
+    /**
+     * create an touch point
+     * @constructor
+     * @param target
+     * @param identifier
+     * @param pos
+     * @param deltaX
+     * @param deltaY
+     * @returns {Object} touchPoint
+     */
+    function Touch(target, identifier, pos, deltaX, deltaY) {
+        deltaX = deltaX || 0;
+        deltaY = deltaY || 0;
+
+        this.identifier = identifier;
+        this.target = target;
+        this.clientX = pos.clientX + deltaX;
+        this.clientY = pos.clientY + deltaY;
+        this.screenX = pos.screenX + deltaX;
+        this.screenY = pos.screenY + deltaY;
+        this.pageX = pos.pageX + deltaX;
+        this.pageY = pos.pageY + deltaY;
+    }
+
+    /**
+     * create empty touchlist with the methods
+     * @constructor
+     * @returns touchList
+     */
+    function TouchList() {
+        var touchList = [];
+
+        touchList.item = function (index) {
+            return this[index] || null;
+        };
+
+        // specified by Mozilla
+        touchList.identifiedTouch = function (id) {
+            return this[id + 1] || null;
+        };
+
+        return touchList;
+    }
+
+
+    /**
+     * Simple trick to fake touch event support
+     * this is enough for most libraries like Modernizr and Hammer
+     */
+    function fakeTouchSupport() {
+        var objs = [window, document.documentElement];
+        var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];
+
+        for (var o = 0; o < objs.length; o++) {
+            for (var p = 0; p < props.length; p++) {
+                if (objs[o] && objs[o][props[p]] == undefined) {
+                    objs[o][props[p]] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * we don't have to emulate on a touch device
+     * @returns {boolean}
+     */
+    function hasTouchSupport() {
+        return ("ontouchstart" in window) || // touch events
+            (window.Modernizr && window.Modernizr.touch) || // modernizr
+            (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2; // pointer events
+    }
+
+    /**
+     * disable mouseevents on the page
+     * @param ev
+     */
+    function preventMouseEvents(ev) {
+        // 注释启用默认事件
+        // ev.preventDefault();
+        // ev.stopPropagation();
+    }
+
+    /**
+     * only trigger touches when the left mousebutton has been pressed
+     * @param touchType
+     * @returns {Function}
+     */
+    function onMouse(touchType) {
+        return function (ev) {
+            // prevent mouse events
+            preventMouseEvents(ev);
+
+            if (ev.which !== 1) {
+                return;
+            }
+
+            // The EventTarget on which the touch point started when it was first placed on the surface,
+            // even if the touch point has since moved outside the interactive area of that element.
+            // also, when the target doesnt exist anymore, we update it
+            if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
+                eventTarget = ev.target;
+            }
+
+            // shiftKey has been lost, so trigger a touchend
+            if (isMultiTouch && !ev.shiftKey) {
+                triggerTouch('touchend', ev);
+                isMultiTouch = false;
+            }
+
+            triggerTouch(touchType, ev);
+
+            // we're entering the multi-touch mode!
+            if (!isMultiTouch && ev.shiftKey) {
+                isMultiTouch = true;
+                multiTouchStartPos = {
+                    pageX: ev.pageX,
+                    pageY: ev.pageY,
+                    clientX: ev.clientX,
+                    clientY: ev.clientY,
+                    screenX: ev.screenX,
+                    screenY: ev.screenY
+                };
+                triggerTouch('touchstart', ev);
+            }
+
+            // reset
+            if (ev.type == 'mouseup') {
+                multiTouchStartPos = null;
+                isMultiTouch = false;
+                eventTarget = null;
+            }
+        }
+    }
+
+    /**
+     * trigger a touch event
+     * @param eventName
+     * @param mouseEv
+     */
+    function triggerTouch(eventName, mouseEv) {
+        var touchEvent = document.createEvent('Event');
+        touchEvent.initEvent(eventName, true, true);
+
+        touchEvent.altKey = mouseEv.altKey;
+        touchEvent.ctrlKey = mouseEv.ctrlKey;
+        touchEvent.metaKey = mouseEv.metaKey;
+        touchEvent.shiftKey = mouseEv.shiftKey;
+
+        touchEvent.touches = getActiveTouches(mouseEv, eventName);
+        touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
+        touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);
+
+        eventTarget.dispatchEvent(touchEvent);
+    }
+
+    /**
+     * create a touchList based on the mouse event
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function createTouchList(mouseEv) {
+        var touchList = new TouchList();
+
+        if (isMultiTouch) {
+            var f = TouchEmulator.multiTouchOffset;
+            var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
+            var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;
+
+            touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX * -1) - f, (deltaY * -1) + f));
+            touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX + f, deltaY - f));
+        } else {
+            touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
+        }
+
+        return touchList;
+    }
+
+    /**
+     * receive all active touches
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function getActiveTouches(mouseEv, eventName) {
+        // empty list
+        if (mouseEv.type == 'mouseup') {
+            return new TouchList();
+        }
+
+        var touchList = createTouchList(mouseEv);
+        if (isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
+            touchList.splice(1, 1);
+        }
+        return touchList;
+    }
+
+    /**
+     * receive a filtered set of touches with only the changed pointers
+     * @param mouseEv
+     * @param eventName
+     * @returns {TouchList}
+     */
+    function getChangedTouches(mouseEv, eventName) {
+        var touchList = createTouchList(mouseEv);
+
+        // we only want to return the added/removed item on multitouch
+        // which is the second pointer, so remove the first pointer from the touchList
+        //
+        // but when the mouseEv.type is mouseup, we want to send all touches because then
+        // no new input will be possible
+        if (isMultiTouch && mouseEv.type != 'mouseup' &&
+            (eventName == 'touchstart' || eventName == 'touchend')) {
+            touchList.splice(0, 1);
+        }
+
+        return touchList;
+    }
+
+    /**
+     * show the touchpoints on the screen
+     */
+    function showTouches(ev) {
+        var touch, i, el, styles;
+
+        // first all visible touches
+        for (i = 0; i < ev.touches.length; i++) {
+            touch = ev.touches[i];
+            el = touchElements[touch.identifier];
+            if (!el) {
+                el = touchElements[touch.identifier] = document.createElement("div");
+                document.body.appendChild(el);
+            }
+
+            styles = TouchEmulator.template(touch);
+            for (var prop in styles) {
+                el.style[prop] = styles[prop];
+            }
+        }
+
+        // remove all ended touches
+        if (ev.type == 'touchend' || ev.type == 'touchcancel') {
+            for (i = 0; i < ev.changedTouches.length; i++) {
+                touch = ev.changedTouches[i];
+                el = touchElements[touch.identifier];
+                if (el) {
+                    el.parentNode.removeChild(el);
+                    delete touchElements[touch.identifier];
+                }
+            }
+        }
+    }
+
+    /**
+     * TouchEmulator initializer
+     */
+    function TouchEmulator() {
+        if (hasTouchSupport()) {
+            return;
+        }
+
+        fakeTouchSupport();
+
+        window.addEventListener("mousedown", onMouse('touchstart'), true);
+        window.addEventListener("mousemove", onMouse('touchmove'), true);
+        window.addEventListener("mouseup", onMouse('touchend'), true);
+
+        window.addEventListener("mouseenter", preventMouseEvents, true);
+        window.addEventListener("mouseleave", preventMouseEvents, true);
+        window.addEventListener("mouseout", preventMouseEvents, true);
+        window.addEventListener("mouseover", preventMouseEvents, true);
+
+        // it uses itself!
+        window.addEventListener("touchstart", showTouches, true);
+        window.addEventListener("touchmove", showTouches, true);
+        window.addEventListener("touchend", showTouches, true);
+        window.addEventListener("touchcancel", showTouches, true);
+    }
+
+    // start distance when entering the multitouch mode
+    TouchEmulator.multiTouchOffset = 75;
+
+    /**
+     * css template for the touch rendering
+     * @param touch
+     * @returns object
+     */
+    TouchEmulator.template = function (touch) {
+        var size = 0;
+        var transform = 'translate(' + (touch.clientX - (size / 2)) + 'px, ' + (touch.clientY - (size / 2)) + 'px)';
+        return {
+            position: 'fixed',
+            left: 0,
+            top: 0,
+            background: '#fff',
+            border: 'solid 1px #999',
+            opacity: .6,
+            borderRadius: '100%',
+            height: size + 'px',
+            width: size + 'px',
+            padding: 0,
+            margin: 0,
+            display: 'block',
+            overflow: 'hidden',
+            pointerEvents: 'none',
+            webkitUserSelect: 'none',
+            mozUserSelect: 'none',
+            userSelect: 'none',
+            webkitTransform: transform,
+            mozTransform: transform,
+            transform: transform,
+            zIndex: 100
+        }
+    };
+
+    // export
+    if (typeof define == "function" && define.amd) {
+        define(function () {
+            return TouchEmulator;
+        });
+    } else if (typeof module != "undefined" && module.exports) {
+        module.exports = TouchEmulator;
+    } else {
+        window[exportName] = TouchEmulator;
+    }
+})(window, document, "TouchEmulator");

BIN
static/img/control/close.png


BIN
static/img/control/open.png


BIN
static/img/control/plus.png


BIN
static/img/control/switch-user.png


BIN
static/img/home/banner1.png


BIN
static/img/home/homeTop1.png


BIN
static/img/login/control.png


BIN
static/img/login/org_logo.png


BIN
static/img/logo/750x560.png


BIN
static/img/page/empty_view.png


BIN
static/img/tab-icon/Vector-active.png


BIN
static/img/tab-icon/Vector.png


BIN
static/img/tab-icon/me-active.png


BIN
static/img/tab-icon/me.png


BIN
static/img/tab-icon/order-food-active.png


BIN
static/img/tab-icon/order-food.png


BIN
static/img/tab-icon/shop-active.png


BIN
static/img/tab-icon/shop.png


+ 17 - 0
store/index.js

@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex) // vue的插件机制
+
+// Vuex.Store 构造器选项
+const store = new Vuex.Store({
+    // 为了不和页面或组件的data中的造成混淆,state中的变量前面建议加上$符号
+    state: {
+        // 用户信息
+        $userInfo: {
+            id: 1
+        }
+    }
+})
+
+export default store

+ 43 - 0
template.h5.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+	<head>
+		<meta charset="utf-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<link rel="shortcut icon" type="image/x-icon" href="https://cdn.uviewui.com/uview/common/favicon.ico">
+		<meta name="viewport"
+			content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+		<title>
+			<%= htmlWebpackPlugin.options.title %>
+		</title>
+		<!-- 正式发布的时候使用,开发期间不启用。↓ -->
+		<script src="/static/common/js/touch-emulator.js"></script>
+		<script>
+			TouchEmulator();
+		</script>
+		<style>
+			::-webkit-scrollbar {
+				display: none;
+			}
+		</style>
+		<!-- 正式发布的时候使用,开发期间不启用。↑ -->
+		<script>
+			document.addEventListener('DOMContentLoaded', function() {
+				document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
+			})
+		</script>
+		<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+	</head>
+	<body>
+		<!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 -->
+		<!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
+		<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
+		<noscript>
+			<strong>本站点必须要开启JavaScript才能运行</strong>
+		</noscript>
+		<div id="app"></div>
+		<!-- built files will be auto injected -->
+		<script>
+			/*BAIDU_STAT*/
+		</script>
+	</body>
+</html>

+ 7 - 0
uni.scss

@@ -0,0 +1,7 @@
+/**
+ * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量
+ * 使用的时候,请将下面的一行复制到您的uniapp项目根目录的uni.scss中即可
+ * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用 
+ */
+@import '@/uni_modules/uview-ui/theme.scss';
+

+ 21 - 0
uni_modules/uview-ui/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 www.uviewui.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 105 - 0
uni_modules/uview-ui/README.md

@@ -0,0 +1,105 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## 特性
+
+- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
+- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
+- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
+- 众多的常用页面和布局,让您专注逻辑,事半功倍
+- 详尽的文档支持,现代化的演示效果
+- 按需引入,精简打包体积
+
+
+## 安装
+
+```bash
+# npm方式安装
+npm i uview-ui
+```
+
+## 快速上手
+
+1. `main.js`引入uView库
+```js
+// main.js
+import uView from 'uview-ui';
+Vue.use(uView);
+```
+
+2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
+```css
+/* App.vue */
+<style lang="scss">
+@import "uview-ui/index.scss";
+</style>
+```
+
+3. `uni.scss`引入全局scss变量文件
+```css
+/* uni.scss */
+@import "uview-ui/theme.scss";
+```
+
+4. `pages.json`配置easycom规则(按需引入)
+
+```js
+// pages.json
+{
+	"easycom": {
+		// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
+		// npm安装方式
+		"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
+		// 下载安装方式
+		// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
+	},
+	// 此为本身已有的内容
+	"pages": [
+		// ......
+	]
+}
+```
+
+请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button>按钮</u-button>
+</template>
+```
+
+请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 链接
+
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changelog.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+## 捐赠uView的研发
+
+uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
+
+<img src="https://uviewui.com/common/alipay.png" width="220" ><img style="margin-left: 100px;" src="https://uviewui.com/common/wechat.png" width="220" >
+
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。

+ 35 - 0
uni_modules/uview-ui/changelog.md

@@ -0,0 +1,35 @@
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+
+

+ 74 - 0
uni_modules/uview-ui/components/u--form/u--form.vue

@@ -0,0 +1,74 @@
+<template>
+	<uvForm
+		ref="uForm"
+		:model="model"
+		:rules="rules"
+		:errorType="errorType"
+		:borderBottom="borderBottom"
+		:labelPosition="labelPosition"
+		:labelWidth="labelWidth"
+		:labelAlign="labelAlign"
+		:labelStyle="labelStyle"
+		:customStyle="customStyle"
+	>
+		<slot />
+	</uvForm>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+	 * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+	 */
+	import uvForm from '../u-form/u-form.vue';
+	import props from '../u-form/props.js'
+	export default {
+		// #ifdef MP-WEIXIN
+		name: 'u-form',
+		// #endif
+		// #ifndef MP-WEIXIN
+		name: 'u--form',
+		// #endif
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvForm
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			validate() {
+				/**
+				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+				 * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+				 */
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validate()
+			},
+			validateField(value, callback) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validateField(value, callback)
+			},
+			resetFields() {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.resetFields()
+			},
+			clearValidate(props) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.clearValidate(props)
+			},
+			setMpData() {
+				this.$refs.uForm.children = this.children
+			}
+		},
+	}
+</script>

+ 40 - 0
uni_modules/uview-ui/components/u--image/u--image.vue

@@ -0,0 +1,40 @@
+<template>
+	<uvImage 
+		:src="src"
+		:mode="mode"
+		:width="width"
+		:height="height"
+		:shape="shape"
+		:radius="radius"
+		:lazyLoad="lazyLoad"
+		:showMenuByLongpress="showMenuByLongpress"
+		:loadingIcon="loadingIcon"
+		:errorIcon="errorIcon"
+		:showLoading="showLoading"
+		:showError="showError"
+		:fade="fade"
+		:webp="webp"
+		:duration="duration"
+		:bgColor="bgColor"
+		:customStyle="customStyle"
+		@click="$emit('click')"
+		@error="$emit('error')"
+		@load="$emit('load')"
+	></uvImage>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
+	 * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
+	 */
+	import uvImage from '../u-image/u-image.vue';
+	import props from '../u-image/props.js';
+	export default {
+		name: 'u--image',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvImage
+		},
+	}
+</script>

+ 63 - 0
uni_modules/uview-ui/components/u--input/u--input.vue

@@ -0,0 +1,63 @@
+<template>
+	<uvInput 
+		:value="value"
+		:type="type"
+		:fixed="fixed"
+		:disabled="disabled"
+		:disabledColor="disabledColor"
+		:clearable="clearable"
+		:password="password"
+		:maxlength="maxlength"
+		:placeholder="placeholder"
+		:placeholderClass="placeholderClass"
+		:placeholderStyle="placeholderStyle"
+		:showWordLimit="showWordLimit"
+		:confirmType="confirmType"
+		:confirmHold="confirmHold"
+		:holdKeyboard="holdKeyboard"
+		:focus="focus"
+		:autoBlur="autoBlur"
+		:disableDefaultPadding="disableDefaultPadding"
+		:cursor="cursor"
+		:cursorSpacing="cursorSpacing"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:inputAlign="inputAlign"
+		:autosize="autosize"
+		:fontSize="fontSize"
+		:color="color"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:suffixIconStyle="suffixIconStyle"
+		:prefixIconStyle="prefixIconStyle"
+		:border="border"
+		:readonly="readonly"
+		:shape="shape"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		@focus="$emit('focus')"
+		@blur="$emit('blur')"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+		@change="e => $emit('change', e)"
+		@input="e => $emit('input', e)"
+		@clear="$emit('clear')"
+		@click="$emit('click')"
+	></uvInput>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+	 */
+	import uvInput from '../u-input/u-input.vue';
+	import props from '../u-input/props.js'
+	export default {
+		name: 'u--input',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvInput
+		},
+	}
+</script>

+ 46 - 0
uni_modules/uview-ui/components/u--text/u--text.vue

@@ -0,0 +1,46 @@
+<template>
+	<uvText
+		:type="type"
+		:show="show"
+		:text="text"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:mode="mode"
+		:href="href"
+		:format="format"
+		:call="call"
+		:encrypt="encrypt"
+		:openType="openType"
+		:bold="bold"
+		:block="block"
+		:lines="lines"
+		:color="color"
+		:size="size"
+		:iconStyle="iconStyle"
+		:precision="precision"
+		:decoration="decoration"
+		:margin="margin"
+		:lineHeight="lineHeight"
+		:align="align"
+		:wordWrap="wordWrap"
+		:customStyle="customStyle"
+		@click="$emit('click')"
+	></uvText>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+	 * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+	 */
+	import uvText from '../u-text/u-text.vue'
+	import props from '../u-text/props.js'
+	export default {
+		name: 'u--text',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvText
+		},
+	}
+</script>

+ 47 - 0
uni_modules/uview-ui/components/u--textarea/u--textarea.vue

@@ -0,0 +1,47 @@
+<template>
+	<uvTextarea
+		:value="value"
+		:placeholder="placeholder"
+		:height="height"
+		:confirmType="confirmType"
+		:disabled="disabled"
+		:count="count"
+		:focus="focus"
+		:autoHeight="autoHeight"
+		:fixed="fixed"
+		:cursorSpacing="cursorSpacing"
+		:cursor="cursor"
+		:showConfirmBar="showConfirmBar"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:disableDefaultPadding="disableDefaultPadding"
+		:holdKeyboard="holdKeyboard"
+		:maxlength="maxlength"
+		:border="border"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		@focus="$emit('focus')"
+		@blur="$emit('blur')"
+		@linechange="$emit('linechange')"
+		@confirm="$emit('confirm')"
+		@input="e => $emit('input', e)"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+	></uvTextarea>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+	 * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+	 */
+	import uvTextarea from '../u-textarea/u-textarea.vue';
+	import props from '../u-textarea/props.js'
+	export default {
+		name: 'u--textarea',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvTextarea
+		},
+	}
+</script>

+ 54 - 0
uni_modules/uview-ui/components/u-action-sheet/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 操作菜单是否展示 (默认false)
+        show: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.show
+        },
+        // 标题
+        title: {
+            type: String,
+            default: uni.$u.props.actionSheet.title
+        },
+        // 选项上方的描述信息
+        description: {
+            type: String,
+            default: uni.$u.props.actionSheet.description
+        },
+        // 数据
+        actions: {
+            type: Array,
+            default: uni.$u.props.actionSheet.actions
+        },
+        // 取消按钮的文字,不为空时显示按钮
+        cancelText: {
+            type: String,
+            default: uni.$u.props.actionSheet.cancelText
+        },
+        // 点击某个菜单项时是否关闭弹窗
+        closeOnClickAction: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickAction
+        },
+        // 处理底部安全区(默认true)
+        safeAreaInsetBottom: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.safeAreaInsetBottom
+        },
+        // 小程序的打开方式
+        openType: {
+            type: String,
+            default: uni.$u.props.actionSheet.openType
+        },
+        // 点击遮罩是否允许关闭 (默认true)
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickOverlay
+        },
+        // 是否显示圆角 (默认false)
+        round: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.round
+        }
+    }
+}

+ 275 - 0
uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue

@@ -0,0 +1,275 @@
+
+<template>
+	<u-popup
+	    :show="show"
+	    mode="bottom"
+	    @close="close"
+	    :closeOnClickOverlay="closeOnClickOverlay"
+	    :safeAreaInsetBottom="safeAreaInsetBottom"
+	    :round="round"
+	>
+		<view class="u-action-sheet">
+			<view
+			    class="u-action-sheet__header"
+			    v-if="title"
+			>
+				<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
+				<view
+				    class="u-action-sheet__header__icon-wrap"
+				    @tap.stop="close"
+				>
+					<u-icon
+					    name="close"
+					    size="17"
+					    color="#c8c9cc"
+					    bold
+					></u-icon>
+				</view>
+			</view>
+			<text
+			    class="u-action-sheet__description"
+				:style="[{
+					marginTop: `${title && description ? 0 : '18px'}`
+				}]"
+			    v-if="description"
+			>{{description}}</text>
+			<slot>
+				<u-line v-if="description"></u-line>
+				<view class="u-action-sheet__item-wrap">
+					<template v-for="(item, index) in actions">
+						<!-- #ifdef MP -->
+						<button
+						    :key="index"
+						    class="u-reset-button"
+						    :openType="item.openType"
+						    @getuserinfo="onGetUserInfo"
+						    @contact="onContact"
+						    @getphonenumber="onGetPhoneNumber"
+						    @error="onError"
+						    @launchapp="onLaunchApp"
+						    @opensetting="onOpenSetting"
+						    :lang="lang"
+						    :session-from="sessionFrom"
+						    :send-message-title="sendMessageTitle"
+						    :send-message-path="sendMessagePath"
+						    :send-message-img="sendMessageImg"
+						    :show-message-card="showMessageCard"
+						    :app-parameter="appParameter"
+						    @tap="selectHandler(index)"
+						    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+						>
+							<!-- #endif -->
+							<view
+							    class="u-action-sheet__item-wrap__item"
+							    @tap.stop="selectHandler(index)"
+							    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+							    :hover-stay-time="150"
+							>
+								<template v-if="!item.loading">
+									<text
+									    class="u-action-sheet__item-wrap__item__name"
+									    :style="[itemStyle(index)]"
+									>{{ item.name }}</text>
+									<text
+									    v-if="item.subname"
+									    class="u-action-sheet__item-wrap__item__subname"
+									>{{ item.subname }}</text>
+								</template>
+								<u-loading-icon
+								    v-else
+								    custom-class="van-action-sheet__loading"
+								    size="18"
+								    mode="circle"
+								/>
+							</view>
+							<!-- #ifdef MP -->
+						</button>
+						<!-- #endif -->
+						<u-line v-if="index !== actions.length - 1"></u-line>
+					</template>
+				</view>
+			</slot>
+			<u-gap
+			    bgColor="#eaeaec"
+			    height="6"
+			    v-if="cancelText"
+			></u-gap>
+			<view hover-class="u-action-sheet--hover">
+				<text
+				    @touchmove.stop.prevent
+				    :hover-stay-time="150"
+				    v-if="cancelText"
+				    class="u-action-sheet__cancel-text"
+				    @tap="close"
+				>{{cancelText}}</text>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	import openType from '../../libs/mixin/openType'
+	import button from '../../libs/mixin/button'
+	import props from './props.js';
+	/**
+	 * ActionSheet 操作菜单
+	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * 
+	 * @property {Boolean}			show				操作菜单是否展示 (默认 false )
+	 * @property {String}			title				操作菜单标题
+	 * @property {String}			description			选项上方的描述信息
+	 * @property {Array<Object>}	actions				按钮的文字数组,见官方文档示例
+	 * @property {String}			cancelText			取消按钮的提示文字,不为空时显示按钮
+	 * @property {Boolean}			closeOnClickAction	点击某个菜单项时是否关闭弹窗 (默认 true )
+	 * @property {Boolean}			safeAreaInsetBottom	处理底部安全区 (默认 true )
+	 * @property {String}			openType			小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+	 * @property {Boolean}			closeOnClickOverlay	点击遮罩是否允许关闭  (默认 true )
+	 * @property {Boolean}			round				是否显示圆角  (默认 false )
+	 * @property {String}			lang				指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+	 * @property {String}			sessionFrom			会话来源,openType="contact"时有效
+	 * @property {String}			sendMessageTitle	会话内消息卡片标题,openType="contact"时有效
+	 * @property {String}			sendMessagePath		会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+	 * @property {String}			sendMessageImg		会话内消息卡片图片,openType="contact"时有效
+	 * @property {Boolean}			showMessageCard		是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+	 * @property {String}			appParameter		打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+	 * 
+	 * @event {Function} select			点击ActionSheet列表项时触发 
+	 * @event {Function} close			点击取消按钮时触发
+	 * @event {Function} getuserinfo	用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+	 * @event {Function} contact		客服消息回调,openType="contact"时有效
+	 * @event {Function} getphonenumber	获取用户手机号回调,openType="getPhoneNumber"时有效
+	 * @event {Function} error			当使用开放能力时,发生错误的回调,openType="error"时有效
+	 * @event {Function} launchapp		打开 APP 成功的回调,openType="launchApp"时有效
+	 * @event {Function} opensetting	在打开授权设置页后回调,openType="openSetting"时有效
+	 * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
+	 */
+	export default {
+		name: "u-action-sheet",
+		// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
+		mixins: [openType, button, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			// 操作项目的样式
+			itemStyle() {
+				return (index) => {
+					let style = {};
+					if (this.actions[index].color) style.color = this.actions[index].color
+					if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
+					// 选项被禁用的样式
+					if (this.actions[index].disabled) style.color = '#c0c4cc'
+					return style;
+				}
+			},
+		},
+		methods: {
+			close() {
+				// 允许点击遮罩关闭时,才发出close事件
+				if(this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			selectHandler(index) {
+				const item = this.actions[index]
+				if (item && !item.disabled && !item.loading) {
+					this.$emit('select', item)
+					if (this.closeOnClickAction) {
+						this.$emit('close')
+					}
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+	$u-action-sheet-reset-button-width:100% !default;
+	$u-action-sheet-title-font-size: 16px !default;
+	$u-action-sheet-title-padding: 12px 30px !default;
+	$u-action-sheet-title-color: $u-main-color !default;
+	$u-action-sheet-header-icon-wrap-right:15px !default;
+	$u-action-sheet-header-icon-wrap-top:15px !default;
+	$u-action-sheet-description-font-size:13px !default;
+	$u-action-sheet-description-color:14px !default;
+	$u-action-sheet-description-margin: 18px 15px !default;
+	$u-action-sheet-item-wrap-item-padding:15px !default;
+	$u-action-sheet-item-wrap-name-font-size:16px !default;
+	$u-action-sheet-item-wrap-subname-font-size:13px !default;
+	$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+	$u-action-sheet-item-wrap-subname-margin-top:10px !default;
+	$u-action-sheet-cancel-text-font-size:16px !default;
+	$u-action-sheet-cancel-text-color:$u-content-color !default;
+	$u-action-sheet-cancel-text-font-size:15px !default;
+	$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
+
+	.u-reset-button {
+		width: $u-action-sheet-reset-button-width;
+	}
+
+	.u-action-sheet {
+		text-align: center;
+		&__header {
+			position: relative;
+			padding: $u-action-sheet-title-padding;
+			&__title {
+				font-size: $u-action-sheet-title-font-size;
+				color: $u-action-sheet-title-color;
+				font-weight: bold;
+				text-align: center;
+			}
+
+			&__icon-wrap {
+				position: absolute;
+				right: $u-action-sheet-header-icon-wrap-right;
+				top: $u-action-sheet-header-icon-wrap-top;
+			}
+		}
+
+		&__description {
+			font-size: $u-action-sheet-description-font-size;
+			color: $u-tips-color;
+			margin: $u-action-sheet-description-margin;
+			text-align: center;
+		}
+
+		&__item-wrap {
+
+			&__item {
+				padding: $u-action-sheet-item-wrap-item-padding;
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				flex-direction: column;
+
+				&__name {
+					font-size: $u-action-sheet-item-wrap-name-font-size;
+					color: $u-main-color;
+					text-align: center;
+				}
+
+				&__subname {
+					font-size: $u-action-sheet-item-wrap-subname-font-size;
+					color: $u-action-sheet-item-wrap-subname-color;
+					margin-top: $u-action-sheet-item-wrap-subname-margin-top;
+					text-align: center;
+				}
+			}
+		}
+
+		&__cancel-text {
+			font-size: $u-action-sheet-cancel-text-font-size;
+			color: $u-action-sheet-cancel-text-color;
+			text-align: center;
+			padding: $u-action-sheet-cancel-text-font-size;
+		}
+
+		&--hover {
+			background-color: $u-action-sheet-cancel-text-hover-background-color;
+		}
+	}
+</style>

+ 59 - 0
uni_modules/uview-ui/components/u-album/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 图片地址,Array<String>|Array<Object>形式
+        urls: {
+            type: Array,
+            default: uni.$u.props.album.urls
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.album.keyName
+        },
+        // 单图时,图片长边的长度
+        singleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.singleSize
+        },
+        // 多图时,图片边长
+        multipleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.multipleSize
+        },
+        // 多图时,图片水平和垂直之间的间隔
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.album.space
+        },
+        // 单图时,图片缩放裁剪的模式
+        singleMode: {
+            type: String,
+            default: uni.$u.props.album.singleMode
+        },
+        // 多图时,图片缩放裁剪的模式
+        multipleMode: {
+            type: String,
+            default: uni.$u.props.album.multipleMode
+        },
+        // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.maxCount
+        },
+        // 是否可以预览图片
+        previewFullImage: {
+            type: Boolean,
+            default: uni.$u.props.album.previewFullImage
+        },
+        // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+        rowCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.rowCount
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.album.showMore
+        }
+    }
+}

+ 236 - 0
uni_modules/uview-ui/components/u-album/u-album.vue

@@ -0,0 +1,236 @@
+<template>
+	<view class="u-album">
+		<view
+		    class="u-album__row"
+		    ref="u-album__row"
+		    v-for="(arr, index) in showUrls"
+			:forComputedUse="albumWidth"
+			:key="index"
+		>
+			<view
+			    class="u-album__row__wrapper"
+			    v-for="(item, index1) in arr"
+			    :key="index1"
+			    :style="[imageStyle(index + 1, index1 + 1)]"
+				@tap="onPreviewTap(getSrc(item))"
+			>
+				<image
+				    :src="getSrc(item)"
+				    :mode="urls.length === 1 ? (imageHeight > 0 ? singleMode : 'widthFix') : multipleMode"
+				    :style="[{
+						width: $u.addUnit(imageWidth),
+						height: $u.addUnit(imageHeight)
+					}]"
+				></image>
+				<view
+				    v-if="showMore && urls.length > rowCount * showUrls.length && index === showUrls.length - 1 && index1 === showUrls[showUrls.length - 1].length - 1"
+				    class="u-album__row__wrapper__text"
+				>
+					<u--text
+					    :text="`+${urls.length - maxCount}`" 
+					    color="#fff"
+					    :size="multipleSize * 0.3"
+						align="center"
+						customStyle="justify-content: center"
+					></u--text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	
+	/**
+	 * Album 相册
+	 * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+	 * @tutorial https://www.uviewui.com/components/album.html
+	 * 
+	 * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式
+	 * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址 
+	 * @property {String | Number} singleSize       单图时,图片长边的长度  (默认 180 )
+	 * @property {String | Number} multipleSize     多图时,图片边长 (默认 70 )
+	 * @property {String | Number} space            多图时,图片水平和垂直之间的间隔 (默认 6 )
+	 * @property {String}          singleMode       单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+	 * @property {String}          multipleMode     多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+	 * @property {String | Number} maxCount         取消按钮的提示文字 (默认 9 )
+	 * @property {Boolean}         previewFullImage 是否可以预览图片 (默认 true )
+	 * @property {String | Number} rowCount         每行展示图片数量,如设置,singleSize和multipleSize将会无效	(默认 3 )
+	 * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 (默认 true )
+	 * 
+	 * @event    {Function}        albumWidth       某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送  (回调参数 width )
+	 * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
+	 */
+	export default {
+		name: 'u-album',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				// 单图的宽度
+				singleWidth: 0,
+				// 单图的高度
+				singleHeight: 0,
+				// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+				singlePercent: 0.6,
+			}
+		},
+		watch: {
+			urls: {
+				immediate: true,
+				handler(newVal) {
+					if (newVal.length === 1) {
+						this.getImageRect()
+					}
+				}
+			}
+		},
+		computed: {
+			imageStyle() {
+				return (index1, index2) => {
+					const {
+						space,
+						rowCount,
+						multipleSize,
+						urls
+					} = this, {
+						addUnit,
+						addStyle
+					} = uni.$u,
+						rowLen = this.showUrls.length,
+						allLen = this.urls.length
+					const style = {
+						marginRight: addUnit(space),
+						marginBottom: addUnit(space)
+					}
+					// 如果为最后一行,则每个图片都无需下边框
+					if (index1 === rowLen) style.marginBottom = 0
+					// 每行的最右边一张和总长度的最后一张无需右边框
+					if (index2 === rowCount || index1 === rowLen && index2 === this.showUrls[index1 - 1].length) style.marginRight =
+						0
+					return style
+				}
+			},
+			// 将数组划分为二维数组
+			showUrls() {
+				const arr = []
+				this.urls.map((item, index) => {
+					// 限制最大展示数量
+					if(index + 1 <= this.maxCount) {
+						// 计算该元素为第几个素组内
+						const itemIndex = Math.floor(index / this.rowCount)
+						// 判断对应的索引是否存在
+						if (!arr[itemIndex]) { 
+							arr[itemIndex] = []
+						}
+						arr[itemIndex].push(item)
+					}
+				})
+				return arr
+			},
+			imageWidth() {
+				return this.urls.length === 1 ? this.singleWidth : this.multipleSize
+			},
+			imageHeight() {
+				return this.urls.length === 1 ? this.singleHeight : this.multipleSize
+			},
+			// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+			// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+			albumWidth() {
+				let width = 0
+				if(this.urls.length === 1) {
+					width = this.singleWidth
+				} else {
+					width = this.showUrls[0].length * this.multipleSize + this.space * (this.showUrls[0].length - 1)
+				}
+				this.$emit('albumWidth', width)
+				return width
+			}
+		},
+		methods: {
+			// 预览图片
+			onPreviewTap(url) {
+				const urls = this.urls.map(item => {
+					return this.getSrc(item)
+				})
+				uni.previewImage({
+					current: url,
+					urls
+				});
+			},
+			// 获取图片的路径
+			getSrc(item) {
+				return uni.$u.test.object(item) ? this.keyName && item[this.keyName] || item.src : item
+			},
+			// 单图时,获取图片的尺寸
+			// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+			// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+			getImageRect() {
+				const src = this.getSrc(this.urls[0])
+				uni.getImageInfo({
+					src,
+					success: (res) => {
+						// 判断图片横向还是竖向展示方式
+						const isHorizotal = res.width >= res.height
+						this.singleWidth = isHorizotal ? this.singleSize : res.width / res.height * this.singleSize
+						this.singleHeight = !isHorizotal ? this.singleSize : res.height / res.width * this.singleWidth
+					},
+					fail: () => {
+						this.getComponentWidth()
+					}
+				})
+			},
+			// 获取组件的宽度
+			async getComponentWidth() {
+				// 延时一定时间,以获取dom尺寸
+				await uni.$u.sleep(30)
+				// #ifndef APP-NVUE
+				this.$uGetRect('.u-album__row').then(size => {
+					this.singleWidth = size.width * this.singlePercent
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
+				const ref = this.$refs['u-album__row'][0]
+				ref && dom.getComponentRect(ref, (res) => {
+					this.singleWidth = res.size.width * this.singlePercent
+				})
+				// #endif
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-album {
+		@include flex(column);
+
+		&__row {
+			@include flex(row);
+			flex-wrap: wrap;
+			
+			&__wrapper {
+				position: relative;
+				
+				&__text {
+					position: absolute;
+					top: 0;
+					left: 0;
+					right: 0;
+					bottom: 0;
+					background-color: rgba(0, 0, 0, 0.3);
+					@include flex(row);
+					justify-content: center;
+					align-items: center;
+				}
+			}
+		}
+	}
+</style>

+ 44 - 0
uni_modules/uview-ui/components/u-alert/props.js

@@ -0,0 +1,44 @@
+export default {
+    props: {
+        // 显示文字
+        title: {
+            type: String,
+            default: uni.$u.props.alert.title
+        },
+        // 主题,success/warning/info/error
+        type: {
+            type: String,
+            default: uni.$u.props.alert.type
+        },
+        // 辅助性文字
+        description: {
+            type: String,
+            default: uni.$u.props.alert.description
+        },
+        // 是否可关闭
+        closable: {
+            type: Boolean,
+            default: uni.$u.props.alert.closable
+        },
+        // 是否显示图标
+        showIcon: {
+            type: Boolean,
+            default: uni.$u.props.alert.showIcon
+        },
+        // 浅或深色调,light-浅色,dark-深色
+        effect: {
+            type: String,
+            default: uni.$u.props.alert.effect
+        },
+        // 文字是否居中
+        center: {
+            type: Boolean,
+            default: uni.$u.props.alert.center
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.alert.fontSize
+        }
+    }
+}

+ 243 - 0
uni_modules/uview-ui/components/u-alert/u-alert.vue

@@ -0,0 +1,243 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :show="show"
+	>
+		<view
+		    class="u-alert"
+		    :class="[`u-alert--${type}--${effect}`]"
+		    @tap.stop="clickHandler"
+		    :style="[$u.addStyle(customStyle)]"
+		>
+			<view
+			    class="u-alert__icon"
+			    v-if="showIcon"
+			>
+				<u-icon
+				    :name="iconName"
+				    size="18"
+				    :color="iconColor"
+				></u-icon>
+			</view>
+			<view
+			    class="u-alert__content"
+			    :style="[{
+					paddingRight: closable ? '20px' : 0
+				}]"
+			>
+				<text
+				    class="u-alert__content__title"
+				    v-if="title"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ title }}</text>
+				<text
+				    class="u-alert__content__desc"
+					v-if="description"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ description }}</text>
+			</view>
+			<view
+			    class="u-alert__close"
+			    v-if="closable"
+			    @tap.stop="closeHandler"
+			>
+				<u-icon
+				    name="close"
+				    :color="iconColor"
+				    size="15"
+				></u-icon>
+			</view>
+		</view>
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Alert  警告提示
+	 * @description 警告提示,展现需要关注的信息。
+	 * @tutorial https://www.uviewui.com/components/alertTips.html
+	 * 
+	 * @property {String}			title       显示的文字 
+	 * @property {String}			type        使用预设的颜色  (默认 'warning' )
+	 * @property {String}			description 辅助性文字,颜色比title浅一点,字号也小一点,可选  
+	 * @property {Boolean}			closable    关闭按钮(默认为叉号icon图标)  (默认 false )
+	 * @property {Boolean}			showIcon    是否显示左边的辅助图标   ( 默认 false )
+	 * @property {String}			effect      多图时,图片缩放裁剪的模式  (默认 'light' )
+	 * @property {Boolean}			center		文字是否居中  (默认 false )
+	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event    {Function}        click       点击组件时触发
+	 * @example  <u-alert :title="title"  type = "warning" :closable="closable" :description = "description"></u-alert>
+	 */
+	export default {
+		name: 'u-alert',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				show: true
+			}
+		},
+		computed: {
+			iconColor() {
+				return this.effect === 'light' ? this.type : '#fff'
+			},
+			// 不同主题对应不同的图标
+			iconName() {
+				switch (this.type) {
+					case 'success':
+						return 'checkmark-circle-fill';
+						break;
+					case 'error':
+						return 'close-circle-fill';
+						break;
+					case 'warning':
+						return 'error-circle-fill';
+						break;
+					case 'info':
+						return 'info-circle-fill';
+						break;
+					case 'primary':
+						return 'more-circle-fill';
+						break;
+					default: 
+						return 'error-circle-fill';
+				}
+			}
+		},
+		methods: {
+			// 点击内容
+			clickHandler() {
+				this.$emit('click')
+			},
+			// 点击关闭按钮
+			closeHandler() {
+				this.show = false
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-alert {
+		position: relative;
+		background-color: $u-primary;
+		padding: 8px 10px;
+		@include flex(row);
+		align-items: center;
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
+
+		&--primary--dark {
+			background-color: $u-primary;
+		}
+
+		&--primary--light {
+			background-color: #ecf5ff;
+		}
+
+		&--error--dark {
+			background-color: $u-error;
+		}
+
+		&--error--light {
+			background-color: #FEF0F0;
+		}
+
+		&--success--dark {
+			background-color: $u-success;
+		}
+
+		&--success--light {
+			background-color: #f5fff0;
+		}
+
+		&--warning--dark {
+			background-color: $u-warning;
+		}
+
+		&--warning--light {
+			background-color: #FDF6EC;
+		}
+
+		&--info--dark {
+			background-color: $u-info;
+		}
+
+		&--info--light {
+			background-color: #f4f4f5;
+		}
+
+		&__icon {
+			margin-right: 5px;
+		}
+
+		&__content {
+			@include flex(column);
+			flex: 1;
+
+			&__title {
+				color: $u-main-color;
+				font-size: 14px;
+				font-weight: bold;
+				color: #fff;
+				margin-bottom: 2px;
+			}
+
+			&__desc {
+				color: $u-main-color;
+				font-size: 14px;
+				flex-wrap: wrap;
+				color: #fff;
+			}
+		}
+
+		&__title--dark,
+		&__desc--dark {
+			color: #FFFFFF;
+		}
+
+		&__text--primary--light,
+		&__text--primary--light {
+			color: $u-primary;
+		}
+
+		&__text--success--light,
+		&__text--success--light {
+			color: $u-success;
+		}
+
+		&__text--warning--light,
+		&__text--warning--light {
+			color: $u-warning;
+		}
+
+		&__text--error--light,
+		&__text--error--light {
+			color: $u-error;
+		}
+
+		&__text--info--light,
+		&__text--info--light {
+			color: $u-info;
+		}
+
+		&__close {
+			position: absolute;
+			top: 11px;
+			right: 10px;
+		}
+	}
+</style>

+ 46 - 0
uni_modules/uview-ui/components/u-avatar-group/props.js

@@ -0,0 +1,46 @@
+export default {
+    props: {
+        // 头像图片组
+        urls: {
+            type: Array,
+            default: uni.$u.props.avatarGroup.urls
+        },
+        // 最多展示的头像数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.maxCount
+        },
+        // 头像形状
+        shape: {
+            type: String,
+            default: uni.$u.props.avatarGroup.shape
+        },
+        // 图片裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatarGroup.mode
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.avatarGroup.showMore
+        },
+        // 头像大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.size
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.avatarGroup.keyName
+        },
+        gap: {
+            type: [String, Number],
+            validator(value) {
+                return value >= 0 && value <= 1
+            },
+            default: uni.$u.props.avatarGroup.gap
+        }
+    }
+}

+ 103 - 0
uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="u-avatar-group">
+		<view
+		    class="u-avatar-group__item"
+		    v-for="(item, index) in showUrl"
+		    :key="index"
+		    :style="{
+				marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
+			}"
+		>
+			<u-avatar
+			    :size="size"
+			    :shape="shape"
+			    :mode="mode"
+			    :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
+			></u-avatar>
+			<view
+			    class="u-avatar-group__item__show-more"
+			    v-if="showMore && index === showUrl.length - 1 && urls.length > maxCount"
+				@tap="clickHandler"
+			>
+				<u--text
+				    color="#ffffff"
+				    :size="size * 0.4"
+				    :text="`+${urls.length - showUrl.length}`"
+					align="center"
+					customStyle="justify-content: center"
+				></u--text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * AvatarGroup  头像组
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * 
+	 * @property {Array}           urls     头像图片组 (默认 [] )
+	 * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+	 * @property {String}          shape    头像形状( 'circle' (默认) | 'square' )
+	 * @property {String}          mode     图片裁剪模式(默认 'scaleToFill' )
+	 * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+	 * @property {String | Number} size      头像大小 (默认 40 )
+	 * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 
+	 * @property {String | Number} gap      头像之间的遮挡比例(0.4代表遮挡40%)  (默认 0.5 )
+	 * 
+	 * @event    {Function}        showMore 头像组更多点击
+	 * @example  <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
+	 */
+	export default {
+		name: 'u-avatar-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			showUrl() {
+				return this.urls.slice(0, this.maxCount)
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('showMore')
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-avatar-group {
+		@include flex;
+
+		&__item {
+			margin-left: -10px;
+			position: relative;
+
+			&--no-indent {
+				// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+				margin-left: 0;
+			}
+
+			&__show-more {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				background-color: rgba(0, 0, 0, 0.3);
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				border-radius: 100px;
+			}
+		}
+	}
+</style>

+ 78 - 0
uni_modules/uview-ui/components/u-avatar/props.js

@@ -0,0 +1,78 @@
+export default {
+    props: {
+        // 头像图片路径(不能为相对路径)
+        src: {
+            type: String,
+            default: uni.$u.props.avatar.src
+        },
+        // 头像形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.avatar.shape
+        },
+        // 头像尺寸
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.size
+        },
+        // 裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatar.mode
+        },
+        // 显示的文字
+        text: {
+            type: String,
+            default: uni.$u.props.avatar.text
+        },
+        // 背景色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.avatar.bgColor
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.avatar.color
+        },
+        // 文字大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.fontSize
+        },
+        // 显示的图标
+        icon: {
+            type: String,
+            default: uni.$u.props.avatar.icon
+        },
+        // 显示小程序头像,只对百度,微信,QQ小程序有效
+        mpAvatar: {
+            type: Boolean,
+            default: uni.$u.props.avatar.mpAvatar
+        },
+        // 是否使用随机背景色
+        randomBgColor: {
+            type: Boolean,
+            default: uni.$u.props.avatar.randomBgColor
+        },
+        // 加载失败的默认头像(组件有内置默认图片)
+        defaultUrl: {
+            type: String,
+            default: uni.$u.props.avatar.defaultUrl
+        },
+        // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+        colorIndex: {
+            type: [String, Number],
+            // 校验参数规则,索引在0-19之间
+            validator(n) {
+                return uni.$u.test.range(n, [0, 19]) || n === ''
+            },
+            default: uni.$u.props.avatar.colorIndex
+        },
+        // 组件标识符
+        name: {
+            type: String,
+            default: uni.$u.props.avatar.name
+        }
+    }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 53 - 0
uni_modules/uview-ui/components/u-avatar/u-avatar.vue


+ 54 - 0
uni_modules/uview-ui/components/u-back-top/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 返回顶部的形状,circle-圆形,square-方形
+        mode: {
+            type: String,
+            default: uni.$u.props.backtop.mode
+        },
+        // 自定义图标
+        icon: {
+            type: String,
+            default: uni.$u.props.backtop.icon
+        },
+        // 提示文字
+        text: {
+            type: String,
+            default: uni.$u.props.backtop.text
+        },
+        // 返回顶部滚动时间
+        duration: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.duration
+        },
+        // 滚动距离
+        scrollTop: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.scrollTop
+        },
+        // 距离顶部多少距离显示,单位px
+        top: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.top
+        },
+        // 返回顶部按钮到底部的距离,单位px
+        bottom: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.bottom
+        },
+        // 返回顶部按钮到右边的距离,单位px
+        right: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.right
+        },
+        // 层级
+        zIndex: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.zIndex
+        },
+        // 图标的样式,对象形式
+        iconStyle: {
+            type: Object,
+            default: uni.$u.props.backtop.iconStyle
+        }
+    }
+}

+ 137 - 0
uni_modules/uview-ui/components/u-back-top/u-back-top.vue

@@ -0,0 +1,137 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :customStyle="backTopStyle"
+	    :show="show"
+	>
+		<view
+		    class="u-back-top"
+			:style="contentStyle"
+		    v-if="!$slots.default && !$slots.$default"
+			@click="backToTop"
+		>
+			<u-icon
+			    :name="icon"
+			    :custom-style="iconStyle"
+			></u-icon>
+			<text
+			    v-if="text"
+			    class="u-back-top__text"
+			>{{text}}</text>
+		</view>
+		<slot v-else />
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	/**
+	 * backTop 返回顶部
+	 * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+	 * @tutorial https://uviewui.com/components/backTop.html
+	 * 
+	 * @property {String}			mode  		返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
+	 * @property {String} 			icon 		自定义图标 (默认 'arrow-upward' ) 见官方文档示例
+	 * @property {String} 			text 		提示文字 
+	 * @property {String | Number}  duration	返回顶部滚动时间 (默认 100)
+	 * @property {String | Number}  scrollTop	滚动距离 (默认 0 )
+	 * @property {String | Number}  top  		距离顶部多少距离显示,单位px (默认 400 )
+	 * @property {String | Number}  bottom  	返回顶部按钮到底部的距离,单位px (默认 100 )
+	 * @property {String | Number}  right  		返回顶部按钮到右边的距离,单位px (默认 20 )
+	 * @property {String | Number}  zIndex 		层级   (默认 9 )
+	 * @property {Object<Object>}  	iconStyle 	图标的样式,对象形式   (默认 {color: '#909399',fontSize: '19px'})
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * 
+	 * @example <u-back-top :scrollTop="scrollTop"></u-back-top>
+	 */
+	export default {
+		name: 'u-back-top',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			backTopStyle() {
+				// 动画组件样式
+				const style = {
+					bottom: uni.$u.addUnit(this.bottom),
+					right: uni.$u.addUnit(this.right),
+					width: '40px',
+					height: '40px',
+					position: 'fixed',
+					zIndex: 10,
+				}
+				return style
+			},
+			show() {
+				let top
+				// 如果是rpx,转为px
+				if (/rpx$/.test(this.top)) {
+					top = uni.rpx2px(parseInt(this.top))
+				} else {
+					// 如果px,通过parseInt获取其数值部分
+					top = parseInt(this.top)
+				}
+				return this.scrollTop > top
+			},
+			contentStyle() {
+				const style = {}
+				let radius = 0
+				// 是否圆形
+				if(this.mode === 'circle') {
+					radius = '100px'
+				} else {
+					radius = '4px'
+				}
+				// 为了兼容安卓nvue,只能这么分开写
+				style.borderTopLeftRadius = radius
+				style.borderTopRightRadius = radius
+				style.borderBottomLeftRadius = radius
+				style.borderBottomRightRadius = radius
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		},
+		methods: {
+			backToTop() {
+				// #ifdef APP-NVUE
+				if (!this.$parent.$refs['u-back-top']) {
+					uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
+				}
+				dom.scrollToElement(this.$parent.$refs['u-back-top'], {
+					offset: 0
+				})
+				// #endif
+				
+				// #ifndef APP-NVUE
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				});
+				// #endif
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import '../../libs/css/components.scss';
+     $u-back-top-flex:1 !default;
+     $u-back-top-height:100% !default;
+     $u-back-top-background-color:#E1E1E1 !default;
+     $u-back-top-tips-font-size:12px !default;
+	.u-back-top {
+		@include flex;
+		flex-direction: column;
+		align-items: center;
+		flex:$u-back-top-flex;
+		height: $u-back-top-height;
+		justify-content: center;
+		background-color: $u-back-top-background-color;
+
+		&__tips {
+			font-size:$u-back-top-tips-font-size;
+			transform: scale(0.8);
+		}
+	}
+</style>

+ 72 - 0
uni_modules/uview-ui/components/u-badge/props.js

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

+ 171 - 0
uni_modules/uview-ui/components/u-badge/u-badge.vue

@@ -0,0 +1,171 @@
+<template>
+	<text
+		v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+		:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
+		:style="[$u.addStyle(customStyle), badgeStyle]"
+		class="u-badge"
+	>{{ isDot ? '' :showValue }}</text>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * badge 徽标数
+	 * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+	 * @tutorial https://uviewui.com/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 <u-badge :type="type" :count="count"></u-badge>
+	 */
+	export default {
+		name: 'u-badge',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		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 = uni.$u.addUnit(top)
+						style.right = uni.$u.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)
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	$u-badge-primary: $u-primary !default;
+	$u-badge-error: $u-error !default;
+	$u-badge-success: $u-success !default;
+	$u-badge-info: $u-info !default;
+	$u-badge-warning: $u-warning !default;
+	$u-badge-dot-radius: 100px !default;
+	$u-badge-dot-size: 8px !default;
+	$u-badge-dot-right: 4px !default;
+	$u-badge-dot-top: 0 !default;
+	$u-badge-text-font-size: 11px !default;
+	$u-badge-text-right: 10px !default;
+	$u-badge-text-padding: 2px 5px !default;
+	$u-badge-text-align: center !default;
+	$u-badge-text-color: #FFFFFF !default;
+
+	.u-badge {
+		border-top-right-radius: $u-badge-dot-radius;
+		border-top-left-radius: $u-badge-dot-radius;
+		border-bottom-left-radius: $u-badge-dot-radius;
+		border-bottom-right-radius: $u-badge-dot-radius;
+		@include flex;
+		line-height: $u-badge-text-font-size;
+		text-align: $u-badge-text-align;
+		font-size: $u-badge-text-font-size;
+		color: $u-badge-text-color;
+
+		&--dot {
+			height: $u-badge-dot-size;
+			width: $u-badge-dot-size;
+		}
+		
+		&--inverted {
+			font-size: 13px;
+		}
+		
+		&--not-dot {
+			padding: $u-badge-text-padding;
+		}
+
+		&--horn {
+			border-bottom-left-radius: 0;
+		}
+
+		&--primary {
+			background-color: $u-badge-primary;
+		}
+		
+		&--primary--inverted {
+			color: $u-badge-primary;
+		}
+
+		&--error {
+			background-color: $u-badge-error;
+		}
+		
+		&--error--inverted {
+			color: $u-badge-error;
+		}
+
+		&--success {
+			background-color: $u-badge-success;
+		}
+		
+		&--success--inverted {
+			color: $u-badge-success;
+		}
+
+		&--info {
+			background-color: $u-badge-info;
+		}
+		
+		&--info--inverted {
+			color: $u-badge-info;
+		}
+
+		&--warning {
+			background-color: $u-badge-warning;
+		}
+		
+		&--warning--inverted {
+			color: $u-badge-warning;
+		}
+	}
+</style>

+ 46 - 0
uni_modules/uview-ui/components/u-button/nvue.scss

@@ -0,0 +1,46 @@
+$u-button-active-opacity:0.75 !default;
+$u-button-loading-text-margin-left:4px !default;
+$u-button-text-color: #FFFFFF !default;
+$u-button-text-plain-error-color:$u-error !default;
+$u-button-text-plain-warning-color:$u-warning !default;
+$u-button-text-plain-success-color:$u-success !default;
+$u-button-text-plain-info-color:$u-info !default;
+$u-button-text-plain-primary-color:$u-primary !default;
+.u-button {
+	&--active {
+		opacity: $u-button-active-opacity;
+	}
+	
+	&--active--plain {
+		background-color: rgb(217, 217, 217);
+	}
+	
+	&__loading-text {
+		margin-left:$u-button-loading-text-margin-left;
+	}
+	
+	&__text,
+	&__loading-text {
+		color:$u-button-text-color;
+	}
+	
+	&__text--plain--error {
+		color:$u-button-text-plain-error-color;
+	}
+	
+	&__text--plain--warning {
+		color:$u-button-text-plain-warning-color;
+	}
+	
+	&__text--plain--success{
+		color:$u-button-text-plain-success-color;
+	}
+	
+	&__text--plain--info {
+		color:$u-button-text-plain-info-color;
+	}
+	
+	&__text--plain--primary {
+		color:$u-button-text-plain-primary-color;
+	}
+}

+ 156 - 0
uni_modules/uview-ui/components/u-button/props.js

@@ -0,0 +1,156 @@
+/*
+ * @Author       : LQ
+ * @Description  :
+ * @version      : 1.0
+ * @Date         : 2021-08-16 10:04:04
+ * @LastAuthor   : LQ
+ * @lastTime     : 2021-08-16 10:04:24
+ * @FilePath     : /u-view2.0/uview-ui/components/u-button/props.js
+ */
+export default {
+    props: {
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.button.hairline
+        },
+        // 按钮的预置样式,info,primary,error,warning,success
+        type: {
+            type: String,
+            default: uni.$u.props.button.type
+        },
+        // 按钮尺寸,large,normal,small,mini
+        size: {
+            type: String,
+            default: uni.$u.props.button.size
+        },
+        // 按钮形状,circle(两边为半圆),square(带圆角)
+        shape: {
+            type: String,
+            default: uni.$u.props.button.shape
+        },
+        // 按钮是否镂空
+        plain: {
+            type: Boolean,
+            default: uni.$u.props.button.plain
+        },
+        // 是否禁止状态
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.button.disabled
+        },
+        // 是否加载中
+        loading: {
+            type: Boolean,
+            default: uni.$u.props.button.loading
+        },
+        // 加载中提示文字
+        loadingText: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingText
+        },
+        // 加载状态图标类型
+        loadingMode: {
+            type: String,
+            default: uni.$u.props.button.loadingMode
+        },
+        // 加载图标大小
+        loadingSize: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingSize
+        },
+        // 开放能力,具体请看uniapp稳定关于button组件部分说明
+        // https://uniapp.dcloud.io/component/button
+        openType: {
+            type: String,
+            default: uni.$u.props.button.openType
+        },
+        // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+        // 取值为submit(提交表单),reset(重置表单)
+        formType: {
+            type: String,
+            default: uni.$u.props.button.formType
+        },
+        // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+        // 只微信小程序、QQ小程序有效
+        appParameter: {
+            type: String,
+            default: uni.$u.props.button.appParameter
+        },
+        // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+        hoverStopPropagation: {
+            type: Boolean,
+            default: uni.$u.props.button.hoverStopPropagation
+        },
+        // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+        lang: {
+            type: String,
+            default: uni.$u.props.button.lang
+        },
+        // 会话来源,open-type="contact"时有效。只微信小程序有效
+        sessionFrom: {
+            type: String,
+            default: uni.$u.props.button.sessionFrom
+        },
+        // 会话内消息卡片标题,open-type="contact"时有效
+        // 默认当前标题,只微信小程序有效
+        sendMessageTitle: {
+            type: String,
+            default: uni.$u.props.button.sendMessageTitle
+        },
+        // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+        // 默认当前分享路径,只微信小程序有效
+        sendMessagePath: {
+            type: String,
+            default: uni.$u.props.button.sendMessagePath
+        },
+        // 会话内消息卡片图片,open-type="contact"时有效
+        // 默认当前页面截图,只微信小程序有效
+        sendMessageImg: {
+            type: String,
+            default: uni.$u.props.button.sendMessageImg
+        },
+        // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+        // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+        showMessageCard: {
+            type: Boolean,
+            default: uni.$u.props.button.showMessageCard
+        },
+        // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+        dataName: {
+            type: String,
+            default: uni.$u.props.button.dataName
+        },
+        // 节流,一定时间内只能触发一次
+        throttleTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.throttleTime
+        },
+        // 按住后多久出现点击态,单位毫秒
+        hoverStartTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStartTime
+        },
+        // 手指松开后点击态保留时间,单位毫秒
+        hoverStayTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStayTime
+        },
+        // 按钮文字,之所以通过props传入,是因为slot传入的话
+        // nvue中无法控制文字的样式
+        text: {
+            type: [String, Number],
+            default: uni.$u.props.button.text
+        },
+        // 按钮图标
+        icon: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮颜色,支持传入linear-gradient渐变色
+        color: {
+            type: String,
+            default: uni.$u.props.button.color
+        }
+    }
+}

+ 485 - 0
uni_modules/uview-ui/components/u-button/u-button.vue

@@ -0,0 +1,485 @@
+<template>
+    <!-- #ifndef APP-NVUE -->
+    <button
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        :form-type="formType"
+        :open-type="openType"
+        :app-parameter="appParameter"
+        :hover-stop-propagation="hoverStopPropagation"
+        :send-message-title="sendMessageTitle"
+        send-message-path="sendMessagePath"
+        :lang="lang"
+        :data-name="dataName"
+        :session-from="sessionFrom"
+        :send-message-img="sendMessageImg"
+        :show-message-card="showMessageCard"
+        @getphonenumber="getphonenumber"
+        @getuserinfo="getuserinfo"
+        @error="error"
+        @opensetting="opensetting"
+        @launchapp="launchapp"
+        :hover-class="!disabled && !loading ? 'u-button--active' : ''"
+        class="u-button u-reset-button"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+        @tap="clickHandler"
+        :class="bemClass"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="textSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[{ fontSize: textSize + 'px' }]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColor"
+                :size="textSize * 1.35"
+                :customStyle="{ marginRight: '2px' }"
+            ></u-icon>
+            <slot>
+                <text
+                    class="u-button__text"
+                    :style="[{ fontSize: textSize + 'px' }]"
+                    >{{ text }}</text
+                >
+            </slot>
+        </template>
+    </button>
+    <!-- #endif -->
+
+    <!-- #ifdef APP-NVUE -->
+    <view
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        class="u-button"
+        :hover-class="
+            !disabled && !loading && !color && (plain || type === 'info')
+                ? 'u-button--active--plain'
+                : !disabled && !loading && !plain
+                ? 'u-button--active'
+                : ''
+        "
+        @tap="clickHandler"
+        :class="bemClass"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="textSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[nvueTextStyle]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColor"
+                :size="textSize * 1.35"
+            ></u-icon>
+            <text
+                class="u-button__text"
+                :style="[
+                    {
+                        marginLeft: icon ? '2px' : 0,
+                    },
+                    nvueTextStyle,
+                ]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ text }}</text
+            >
+        </template>
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+import button from "../../libs/mixin/button.js";
+import openType from "../../libs/mixin/openType.js";
+import props from "./props.js";
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uviewui.com/components/button.html
+ *
+ * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
+ * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String}			size					按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String}			shape					按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean}			plain					按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean}			disabled				是否禁用 (默认 false)
+ * @property {Boolean}			loading					按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number}	loadingText				加载中提示文字
+ * @property {String}			loadingMode				加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number}	loadingSize				加载图标大小 (默认 15 )
+ * @property {String}			openType				开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String}			formType				用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String}			appParameter			打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean}			hoverStopPropagation	指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String}			lang					指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String}			sessionFrom				会话来源,openType="contact"时有效
+ * @property {String}			sendMessageTitle		会话内消息卡片标题,openType="contact"时有效
+ * @property {String}			sendMessagePath			会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String}			sendMessageImg			会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean}			showMessageCard			是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String}			dataName				额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number}	throttleTime			节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number}	hoverStartTime			按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number}	hoverStayTime			手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number}	text					按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String}			icon					按钮图标
+ * @property {String}			color					按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ *
+ * @event {Function}	click			非禁止并且非加载中,才能点击
+ * @event {Function}	getphonenumber	open-type="getPhoneNumber"时有效
+ * @event {Function}	getuserinfo		用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function}	error			当使用开放能力时,发生错误的回调
+ * @event {Function}	opensetting		在打开授权设置页并关闭后回调
+ * @event {Function}	launchapp		打开 APP 成功的回调
+ * @example <u-button>月落</u-button>
+ */
+export default {
+    name: "u-button",
+    // #ifdef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
+    // #endif
+    // #ifndef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    // #endif
+    data() {
+        return {};
+    },
+    computed: {
+        // 生成bem风格的类名
+        bemClass() {
+            // this.bem为一个computed变量,在mixin中
+            if (!this.color) {
+                return this.bem(
+                    "button",
+                    ["type", "shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            } else {
+                // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+                return this.bem(
+                    "button",
+                    ["shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            }
+        },
+        loadingColor() {
+            if (this.plain) {
+                // 如果有设置color值,则用color值,否则使用type主题颜色
+                return this.color
+                    ? this.color
+                    : this.$u.config.color[`u-${this.type}`];
+            }
+            if (this.type === "info") {
+                return "#c9c9c9";
+            }
+            return "rgb(200, 200, 200)";
+        },
+        iconColor() {
+            // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+            // u-icon的color能接受一个主题颜色的值
+            if (this.plain) {
+                return this.color ? this.color : this.type;
+            } else {
+                return "#ffffff";
+            }
+        },
+        baseColor() {
+            let style = {};
+            if (this.color) {
+                // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+                style.color = this.plain ? this.color : "white";
+                if (!this.plain) {
+                    // 非镂空,背景色使用自定义的颜色
+                    style["background-color"] = this.color;
+                }
+                if (this.color.indexOf("gradient") !== -1) {
+                    // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+                    // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+                    // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+                    style.borderTopWidth = 0;
+                    style.borderRightWidth = 0;
+                    style.borderBottomWidth = 0;
+                    style.borderLeftWidth = 0;
+                    if (!this.plain) {
+                        style.backgroundImage = this.color;
+                    }
+                } else {
+                    // 非渐变色,则设置边框相关的属性
+                    style.borderColor = this.color;
+                    style.borderWidth = "1px";
+                    style.borderStyle = "solid";
+                }
+            }
+            return style;
+        },
+        // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+        nvueTextStyle() {
+            let style = {};
+            // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+            if (this.type === "info") {
+                style.color = "#323233";
+            }
+            if (this.color) {
+                style.color = this.plain ? this.color : "white";
+            }
+            style.fontSize = this.textSize + "px";
+            return style;
+        },
+        // 字体大小
+        textSize() {
+            let fontSize = 14,
+                { size } = this;
+            if (size === "large") fontSize = 16;
+            if (size === "normal") fontSize = 14;
+            if (size === "small") fontSize = 12;
+            if (size === "mini") fontSize = 10;
+            return fontSize;
+        },
+    },
+    methods: {
+        clickHandler() {
+            // 非禁止并且非加载中,才能点击
+            if (!this.disabled && !this.loading) {
+                this.$emit("click");
+            }
+        },
+        // 下面为对接uniapp官方按钮开放能力事件回调的对接
+        getphonenumber(res) {
+            this.$emit("getphonenumber", res);
+        },
+        getuserinfo(res) {
+            this.$emit("getuserinfo", res);
+        },
+        error(res) {
+            this.$emit("error", res);
+        },
+        opensetting(res) {
+            this.$emit("opensetting", res);
+        },
+        launchapp(res) {
+            this.$emit("launchapp", res);
+        },
+    },
+};
+</script>
+
+<style lang="scss">
+@import "../../libs/css/components.scss";
+
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
+
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
+
+$u-button-u-button-height: 40px !default;
+$u-button-text-font-size: 15px !default;
+$u-button-loading-text-font-size: 15px !default;
+$u-button-loading-text-margin-left: 4px !default;
+$u-button-large-width: 100% !default;
+$u-button-large-height: 50px !default;
+$u-button-normal-padding: 0 12px !default;
+$u-button-large-padding: 0 15px !default;
+$u-button-normal-font-size: 14px !default;
+$u-button-small-min-width: 60px !default;
+$u-button-small-height: 30px !default;
+$u-button-small-padding: 0px 8px !default;
+$u-button-mini-padding: 0px 8px !default;
+$u-button-small-font-size: 12px !default;
+$u-button-mini-height: 22px !default;
+$u-button-mini-font-size: 10px !default;
+$u-button-mini-min-width: 50px !default;
+$u-button-disabled-opacity: 0.5 !default;
+$u-button-info-color: #323233 !default;
+$u-button-info-background-color: #fff !default;
+$u-button-info-border-color: #ebedf0 !default;
+$u-button-info-border-width: 1px !default;
+$u-button-info-border-style: solid !default;
+$u-button-success-color: #fff !default;
+$u-button-success-background-color: $u-success !default;
+$u-button-success-border-color: $u-button-success-background-color !default;
+$u-button-success-border-width: 1px !default;
+$u-button-success-border-style: solid !default;
+$u-button-primary-color: #fff !default;
+$u-button-primary-background-color: $u-primary !default;
+$u-button-primary-border-color: $u-button-primary-background-color !default;
+$u-button-primary-border-width: 1px !default;
+$u-button-primary-border-style: solid !default;
+$u-button-error-color: #fff !default;
+$u-button-error-background-color: $u-error !default;
+$u-button-error-border-color: $u-button-error-background-color !default;
+$u-button-error-border-width: 1px !default;
+$u-button-error-border-style: solid !default;
+$u-button-warning-color: #fff !default;
+$u-button-warning-background-color: $u-warning !default;
+$u-button-warning-border-color: $u-button-warning-background-color !default;
+$u-button-warning-border-width: 1px !default;
+$u-button-warning-border-style: solid !default;
+$u-button-block-width: 100% !default;
+$u-button-circle-border-top-right-radius: 100px !default;
+$u-button-circle-border-top-left-radius: 100px !default;
+$u-button-circle-border-bottom-left-radius: 100px !default;
+$u-button-circle-border-bottom-right-radius: 100px !default;
+$u-button-square-border-top-right-radius: 3px !default;
+$u-button-square-border-top-left-radius: 3px !default;
+$u-button-square-border-bottom-left-radius: 3px !default;
+$u-button-square-border-bottom-right-radius: 3px !default;
+$u-button-icon-min-width: 1em !default;
+$u-button-plain-background-color: #fff !default;
+$u-button-hairline-border-width: 0.5px !default;
+
+.u-button {
+    height: $u-button-u-button-height;
+    position: relative;
+    align-items: center;
+    justify-content: center;
+    @include flex;
+    /* #ifndef APP-NVUE */
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
+
+    &__text {
+        font-size: $u-button-text-font-size;
+    }
+
+    &__loading-text {
+        font-size: $u-button-loading-text-font-size;
+        margin-left: $u-button-loading-text-margin-left;
+    }
+
+    &--large {
+        /* #ifndef APP-NVUE */
+        width: $u-button-large-width;
+        /* #endif */
+        height: $u-button-large-height;
+        padding: $u-button-large-padding;
+    }
+
+    &--normal {
+        padding: $u-button-normal-padding;
+        font-size: $u-button-normal-font-size;
+    }
+
+    &--small {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-small-min-width;
+        /* #endif */
+        height: $u-button-small-height;
+        padding: $u-button-small-padding;
+        font-size: $u-button-small-font-size;
+    }
+
+    &--mini {
+        height: $u-button-mini-height;
+        font-size: $u-button-mini-font-size;
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-mini-min-width;
+        /* #endif */
+        padding: $u-button-mini-padding;
+    }
+
+    &--disabled {
+        opacity: $u-button-disabled-opacity;
+    }
+
+    &--info {
+        color: $u-button-info-color;
+        background-color: $u-button-info-background-color;
+        border-color: $u-button-info-border-color;
+        border-width: $u-button-info-border-width;
+        border-style: $u-button-info-border-style;
+    }
+
+    &--success {
+        color: $u-button-success-color;
+        background-color: $u-button-success-background-color;
+        border-color: $u-button-success-border-color;
+        border-width: $u-button-success-border-width;
+        border-style: $u-button-success-border-style;
+    }
+
+    &--primary {
+        color: $u-button-primary-color;
+        background-color: $u-button-primary-background-color;
+        border-color: $u-button-primary-border-color;
+        border-width: $u-button-primary-border-width;
+        border-style: $u-button-primary-border-style;
+    }
+
+    &--error {
+        color: $u-button-error-color;
+        background-color: $u-button-error-background-color;
+        border-color: $u-button-error-border-color;
+        border-width: $u-button-error-border-width;
+        border-style: $u-button-error-border-style;
+    }
+
+    &--warning {
+        color: $u-button-warning-color;
+        background-color: $u-button-warning-background-color;
+        border-color: $u-button-warning-border-color;
+        border-width: $u-button-warning-border-width;
+        border-style: $u-button-warning-border-style;
+    }
+
+    &--block {
+        @include flex;
+        width: $u-button-block-width;
+    }
+
+    &--circle {
+        border-top-right-radius: $u-button-circle-border-top-right-radius;
+        border-top-left-radius: $u-button-circle-border-top-left-radius;
+        border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
+        border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
+    }
+
+    &--square {
+        border-bottom-left-radius: $u-button-square-border-top-right-radius;
+        border-bottom-right-radius: $u-button-square-border-top-left-radius;
+        border-top-left-radius: $u-button-square-border-bottom-left-radius;
+        border-top-right-radius: $u-button-square-border-bottom-right-radius;
+    }
+
+    &__icon {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-icon-min-width;
+        line-height: inherit !important;
+        vertical-align: top;
+        /* #endif */
+    }
+
+    &--plain {
+        background-color: $u-button-plain-background-color;
+    }
+
+    &--hairline {
+        border-width: $u-button-hairline-border-width !important;
+    }
+}
+</style>

+ 73 - 0
uni_modules/uview-ui/components/u-button/vue.scss

@@ -0,0 +1,73 @@
+// nvue下hover-class无效
+$u-button-before-top:50% !default;
+$u-button-before-left:50% !default;
+$u-button-before-width:100% !default;
+$u-button-before-height:100% !default;
+$u-button-before-transform:translate(-50%, -50%) !default;
+$u-button-before-opacity:0 !default;
+$u-button-before-background-color:#000 !default;
+$u-button-before-border-color:#000 !default;
+$u-button-active-before-opacity:.15 !default;
+$u-button-icon-margin-left:4px !default;
+$u-button-plain-u-button-info-color:$u-info;
+$u-button-plain-u-button-success-color:$u-success;
+$u-button-plain-u-button-error-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-error;
+
+.u-button {
+	&:before {
+		position: absolute;
+		top:$u-button-before-top;
+		left:$u-button-before-left;
+		width:$u-button-before-width;
+		height:$u-button-before-height;
+		border: inherit;
+		border-radius: inherit;
+		transform:$u-button-before-transform;
+		opacity:$u-button-before-opacity;
+		content: " ";
+		background-color:$u-button-before-background-color;
+		border-color:$u-button-before-border-color;
+	}
+	
+	&--active {
+		&:before {
+			opacity: .15
+		}
+	}
+	
+	&__icon+&__text:not(:empty),
+	&__loading-text {
+		margin-left:$u-button-icon-margin-left;
+	}
+	
+	&--plain {
+		&.u-button--primary {
+			color: $u-primary;
+		}
+	}
+	
+	&--plain {
+		&.u-button--info {
+			color:$u-button-plain-u-button-info-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--success {
+			color:$u-button-plain-u-button-success-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--error {
+			color:$u-button-plain-u-button-error-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--warning {
+			color:$u-button-plain-u-button-warning-color;
+		}
+	}
+}

+ 99 - 0
uni_modules/uview-ui/components/u-calendar/header.vue

@@ -0,0 +1,99 @@
+<template>
+	<view class="u-calendar-header u-border-bottom">
+		<text
+			class="u-calendar-header__title"
+			v-if="showTitle"
+		>{{ title }}</text>
+		<text
+			class="u-calendar-header__subtitle"
+			v-if="showSubtitle"
+		>{{ subtitle }}</text>
+		<view class="u-calendar-header__weekdays">
+			<text class="u-calendar-header__weekdays__weekday">一</text>
+			<text class="u-calendar-header__weekdays__weekday">二</text>
+			<text class="u-calendar-header__weekdays__weekday">三</text>
+			<text class="u-calendar-header__weekdays__weekday">四</text>
+			<text class="u-calendar-header__weekdays__weekday">五</text>
+			<text class="u-calendar-header__weekdays__weekday">六</text>
+			<text class="u-calendar-header__weekdays__weekday">日</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-calendar-header',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 标题
+			title: {
+				type: String,
+				default: ''
+			},
+			// 副标题
+			subtitle: {
+				type: String,
+				default: ''
+			},
+			// 是否显示标题
+			showTitle: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示副标题
+			showSubtitle: {
+				type: Boolean,
+				default: true
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			name() {
+
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-header {
+		padding-bottom: 4px;
+
+		&__title {
+			font-size: 16px;
+			color: $u-main-color;
+			text-align: center;
+			height: 42px;
+			line-height: 42px;
+			font-weight: bold;
+		}
+
+		&__subtitle {
+			font-size: 14px;
+			color: $u-main-color;
+			height: 40px;
+			text-align: center;
+			line-height: 40px;
+			font-weight: bold;
+		}
+
+		&__weekdays {
+			@include flex;
+			justify-content: space-between;
+
+			&__weekday {
+				font-size: 13px;
+				color: $u-main-color;
+				line-height: 30px;
+				flex: 1;
+				text-align: center;
+			}
+		}
+	}
+</style>

+ 570 - 0
uni_modules/uview-ui/components/u-calendar/month.vue

@@ -0,0 +1,570 @@
+<template>
+	<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
+		<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
+			:ref="`u-calendar-month-${index}`" :id="`month-${item.month}`">
+			<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+			<view class="u-calendar-month__days">
+				<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
+					<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+				</view>
+				<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+					:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+					:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
+					<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+						<text class="u-calendar-month__days__day__select__info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
+							:style="[textStyle(item1)]">{{ item1.day }}</text>
+						<text v-if="getBottomInfo(index, index1, item1)"
+							class="u-calendar-month__days__day__select__buttom-info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
+							:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+						<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import dayjs from '../../libs/util/dayjs.js';
+	export default {
+		name: 'u-calendar-month',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 是否显示月份背景色
+			showMark: {
+				type: Boolean,
+				default: true
+			},
+			// 主题色,对底部按钮和选中日期有效
+			color: {
+				type: String,
+				default: '#3c9cff'
+			},
+			// 月份数据
+			months: {
+				type: Array,
+				default: () => []
+			},
+			// 日期选择类型
+			mode: {
+				type: String,
+				default: 'single'
+			},
+			// 日期行高
+			rowHeight: {
+				type: [String, Number],
+				default: 58
+			},
+			// mode=multiple时,最多可选多少个日期
+			maxCount: {
+				type: [String, Number],
+				default: Infinity
+			},
+			// mode=range时,第一个日期底部的提示文字
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时,最后一个日期底部的提示文字
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			// 默认选中的日期,mode为multiple或range是必须为数组格式
+			defaultDate: {
+				type: [Array, String, Date],
+				default: null
+			},
+			// 最小的可选日期
+			minDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 最大可选日期
+			maxDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 如果没有设置maxDate,则往后推多少个月
+			maxMonth: {
+				type: [String, Number],
+				default: 2
+			},
+			// 是否为只读状态,只读状态下禁止选择日期
+			readonly: {
+				type: Boolean,
+				default: uni.$u.props.calendar.readonly
+			},
+			// 日期区间最多可选天数,默认无限制,mode = range时有效
+			maxRange: {
+				type: [Number, String],
+				default: Infinity
+			},
+			// 范围选择超过最多可选天数时的提示文案,mode = range时有效
+			rangePrompt: {
+				type: String,
+				default: ''
+			},
+			// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+			showRangePrompt: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许日期范围的起止时间为同一天,mode = range时有效
+			allowSameDay: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 每个日期的宽度
+				width: 0,
+				// 当前选中的日期item
+				item: {},
+				selected: []
+			}
+		},
+		watch: {
+			selectedChange: {
+				immediate: true,
+				handler(n) {
+					this.setDefaultDate()
+				}
+			}
+		},
+		computed: {
+			// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+			selectedChange() {
+				return [this.minDate, this.maxDate, this.defaultDate]
+			},
+			dayStyle(index1, index2, item) {
+				return (index1, index2, item) => {
+					const style = {}
+					let week = item.week
+					// 不进行四舍五入的形式保留2位小数
+					const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+					// 得出每个日期的宽度
+					style.width = uni.$u.addUnit(dayWidth)
+					style.height = uni.$u.addUnit(this.rowHeight)
+					if (index2 === 0) {
+						// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+						week = (week === 0 ? 7 : week) - 1
+						style.marginLeft = uni.$u.addUnit(week * dayWidth)
+					}
+					if (this.mode === 'range') {
+						// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+						style.paddingLeft = 0
+						style.paddingRight = 0
+						style.paddingBottom = 0
+						style.paddingTop = 0
+					}
+					return style
+				}
+			},
+			daySelectStyle() {
+				return (index1, index2, item) => {
+					let date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.backgroundColor = this.color
+					}
+					if (this.mode === 'single') {
+						if (date === this.selected[0]) {
+							// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					} else if (this.mode === 'range') {
+						if (this.selected.length >= 2) {
+							const len = this.selected.length - 1
+							// 第一个日期设置左上角和左下角的圆角
+							if (this.dateSame(date, this.selected[0])) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							// 最后一个日期设置右上角和右下角的圆角
+							if (this.dateSame(date, this.selected[len])) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+							if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+									.selected[len]))) {
+								style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
+								// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+								style.opacity = 0.7
+							}
+						} else if (this.selected.length === 1) {
+							// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+							// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+						}
+					} else {
+						if (this.selected.some(item => this.dateSame(item, date))) {
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					}
+					return style
+				}
+			},
+			// 某个日期是否被选中
+			textStyle() {
+				return (item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 选中的日期,提示文字设置白色
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.color = '#ffffff'
+					}
+					if (this.mode === 'range') {
+						const len = this.selected.length - 1
+						// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+						if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+								.selected[len]))) {
+							style.color = this.color
+						}
+					}
+					return style
+				}
+			},
+			// 获取底部的提示文字
+			getBottomInfo() {
+				return (index1, index2, item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD")
+					const bottomInfo = item.bottomInfo
+					// 当为日期范围模式时,且选择的日期个数大于0时
+					if (this.mode === 'range' && this.selected.length > 0) {
+						if (this.selected.length === 1) {
+							// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+							if (this.dateSame(date, this.selected[0])) return this.startText
+							else return bottomInfo
+						} else {
+							const len = this.selected.length - 1
+							// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+							if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+								len === 1) {
+								// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+								return `${this.startText}/${this.endText}`
+							} else if (this.dateSame(date, this.selected[0])) {
+								return this.startText
+							} else if (this.dateSame(date, this.selected[len])) {
+								return this.endText
+							} else {
+								return bottomInfo
+							}
+						}
+					} else {
+						return bottomInfo
+					}
+				}
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				this.$nextTick(() => {
+					// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+					// 因为nvue下,$nextTick并不是100%可靠的
+					uni.$u.sleep(10).then(() => {
+						this.getWrapperWidth()
+						this.getMonthRect()
+					})
+				})
+			},
+			// 判断两个日期是否相等
+			dateSame(date1, date2) {
+				return dayjs(date1).isSame(dayjs(date2))
+			},
+			// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+			getWrapperWidth() {
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
+					this.width = res.size.width
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.$uGetRect('.u-calendar-month-wrapper').then(size => {
+					this.width = size.width
+				})
+				// #endif
+			},
+			getMonthRect() {
+				// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+				const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+					`u-calendar-month-${index}`))
+				// 一次性返回
+				Promise.all(promiseAllArr).then(
+					sizes => {
+						let height = 1
+						const topArr = []
+						for (let i = 0; i < this.months.length; i++) {
+							// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+							topArr[i] = height
+							height += sizes[i].height
+						}
+						// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+						this.$emit('updateMonthTop', topArr)
+					})
+			},
+			// 获取每个月份区域的尺寸
+			getMonthRectByPromise(el) {
+				// #ifndef APP-NVUE
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE 
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[el][0], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			},
+			// 点击某一个日期
+			clickHandler(index1, index2, item) {
+				if (this.readonly) {
+					return;
+				}
+				this.item = item
+				const date = dayjs(item.date).format("YYYY-MM-DD")
+				if (item.disabled) return
+				// 对上一次选择的日期数组进行深度克隆
+				let selected = uni.$u.deepClone(this.selected)
+				if (this.mode === 'single') {
+					// 单选情况下,让数组中的元素为当前点击的日期
+					selected = [date]
+				} else if (this.mode === 'multiple') {
+					if (selected.some(item => this.dateSame(item, date))) {
+						// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+						const itemIndex = selected.findIndex(item => item === date)
+						selected.splice(itemIndex, 1)
+					} else {
+						// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+						if (selected.length < this.maxCount) selected.push(date)
+					}
+				} else {
+					// 选择区间形式
+					if (selected.length === 0 || selected.length >= 2) {
+						// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+						selected = [date]
+					} else if (selected.length === 1) {
+						// 如果已经选择了开始日期
+						const existsDate = selected[0]
+						// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+						if (dayjs(date).isBefore(existsDate)) {
+							selected = [date]
+						} else if (dayjs(date).isAfter(existsDate)) {
+							// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+							if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+								if(this.rangePrompt) {
+									uni.$u.toast(this.rangePrompt)
+								} else {
+									uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
+								}
+								return 
+							}
+							// 如果当前日期大于已有日期,将当前的添加到数组尾部
+							selected.push(date)
+							const startDate = selected[0]
+							const endDate = selected[1]
+							const arr = []
+							let i = 0
+							do {
+								// 将开始和结束日期之间的日期添加到数组中
+								arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+								i++
+								// 累加的日期小于结束日期时,继续下一次的循环
+							} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+							// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+							arr.push(endDate)
+							selected = arr
+						} else {
+							// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+							if (selected[0] === date && !this.allowSameDay) return
+							selected.push(date)
+						}
+					}
+				}
+				this.setSelected(selected)
+			},
+			// 设置默认日期
+			setDefaultDate() {
+				if (!this.defaultDate) {
+					// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+					const selected = [dayjs().format("YYYY-MM-DD")]
+					return this.setSelected(selected, false)
+				}
+				let defaultDate = []
+				const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+				const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+				if (this.mode === 'single') {
+					// 单选模式,可以是字符串或数组,Date对象等
+					if (!uni.$u.test.array(this.defaultDate)) {
+						defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+					} else {
+						defaultDate = [this.defaultDate[0]]
+					}
+				} else {
+					// 如果为非数组,则不执行
+					if (!uni.$u.test.array(this.defaultDate)) return
+					defaultDate = this.defaultDate
+				}
+				// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+				defaultDate = defaultDate.filter(item => {
+					return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+						maxDate).add(1, 'day'))
+				})
+				this.setSelected(defaultDate, false)
+			},
+			setSelected(selected, event = true) {
+				this.selected = selected
+				event && this.$emit('monthSelected', this.selected)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-month-wrapper {
+		margin-top: 4px;
+	}
+
+	.u-calendar-month {
+
+		&__title {
+			font-size: 14px;
+			line-height: 42px;
+			height: 42px;
+			color: $u-main-color;
+			text-align: center;
+			font-weight: bold;
+		}
+
+		&__days {
+			position: relative;
+			@include flex;
+			flex-wrap: wrap;
+
+			&__month-mark-wrapper {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				@include flex;
+				justify-content: center;
+				align-items: center;
+
+				&__text {
+					font-size: 155px;
+					color: rgba(231, 232, 234, 0.83);
+				}
+			}
+
+			&__day {
+				@include flex;
+				padding: 2px;
+
+				&__select {
+					flex: 1;
+					@include flex;
+					align-items: center;
+					justify-content: center;
+					position: relative;
+
+					&__dot {
+						width: 7px;
+						height: 7px;
+						border-radius: 100px;
+						background-color: $u-error;
+						position: absolute;
+						top: 12px;
+						right: 7px;
+					}
+
+					&__buttom-info {
+						color: $u-content-color;
+						text-align: center;
+						position: absolute;
+						bottom: 5px;
+						font-size: 10px;
+						text-align: center;
+						left: 0;
+						right: 0;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&__info {
+						text-align: center;
+						font-size: 16px;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&--selected {
+						background-color: $u-primary;
+						@include flex;
+						justify-content: center;
+						align-items: center;
+						flex: 1;
+						border-radius: 3px;
+					}
+
+					&--range-selected {
+						opacity: 0.3;
+						border-radius: 0;
+					}
+
+					&--range-start-selected {
+						border-top-right-radius: 0;
+						border-bottom-right-radius: 0;
+					}
+
+					&--range-end-selected {
+						border-top-left-radius: 0;
+						border-bottom-left-radius: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 134 - 0
uni_modules/uview-ui/components/u-calendar/props.js

@@ -0,0 +1,134 @@
+export default {
+    props: {
+        // 日历顶部标题
+        title: {
+            type: String,
+            default: uni.$u.props.calendar.title
+        },
+        // 是否显示标题
+        showTitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showTitle
+        },
+        // 是否显示副标题
+        showSubtitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showSubtitle
+        },
+        // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+        mode: {
+            type: String,
+            default: uni.$u.props.calendar.mode
+        },
+        // mode=range时,第一个日期底部的提示文字
+        startText: {
+            type: String,
+            default: uni.$u.props.calendar.startText
+        },
+        // mode=range时,最后一个日期底部的提示文字
+        endText: {
+            type: String,
+            default: uni.$u.props.calendar.endText
+        },
+        // 自定义列表
+        customList: {
+            type: Array,
+            default: uni.$u.props.calendar.customList
+        },
+        // 主题色,对底部按钮和选中日期有效
+        color: {
+            type: String,
+            default: uni.$u.props.calendar.color
+        },
+        // 最小的可选日期
+        minDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.minDate
+        },
+        // 最大可选日期
+        maxDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxDate
+        },
+        // 默认选中的日期,mode为multiple或range是必须为数组格式
+        defaultDate: {
+            type: [Array, String, Date, null],
+            default: uni.$u.props.calendar.defaultDate
+        },
+        // mode=multiple时,最多可选多少个日期
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxCount
+        },
+        // 日期行高
+        rowHeight: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.rowHeight
+        },
+        // 日期格式化函数
+        formatter: {
+            type: [Function, null],
+            default: uni.$u.props.calendar.formatter
+        },
+        // 是否显示农历
+        showLunar: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showLunar
+        },
+        // 是否显示月份背景色
+        showMark: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showMark
+        },
+        // 确定按钮的文字
+        confirmText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmText
+        },
+        // 确认按钮处于禁用状态时的文字
+        confirmDisabledText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmDisabledText
+        },
+        // 是否显示日历弹窗
+        show: {
+            type: Boolean,
+            default: uni.$u.props.calendar.show
+        },
+        // 是否允许点击遮罩关闭日历
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.closeOnClickOverlay
+        },
+        // 是否为只读状态,只读状态下禁止选择日期
+        readonly: {
+            type: Boolean,
+            default: uni.$u.props.calendar.readonly
+        },
+        // 	是否展示确认按钮
+        showConfirm: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showConfirm
+        },
+        // 日期区间最多可选天数,默认无限制,mode = range时有效
+        maxRange: {
+            type: [Number, String],
+            default: uni.$u.props.calendar.maxRange
+        },
+        // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+        rangePrompt: {
+            type: String,
+            default: uni.$u.props.calendar.rangePrompt
+        },
+        // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+        showRangePrompt: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showRangePrompt
+        },
+        // 是否允许日期范围的起止时间为同一天,mode = range时有效
+        allowSameDay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.allowSameDay
+        }
+    }
+}

+ 288 - 0
uni_modules/uview-ui/components/u-calendar/u-calendar.vue

@@ -0,0 +1,288 @@
+<template>
+	<u-popup
+		:show="show"
+		mode="bottom"
+		closeable
+		@close="close"
+		round
+		:closeOnClickOverlay="closeOnClickOverlay"
+	>
+		<view class="u-calendar">
+			<uHeader
+				:title="title"
+				:subtitle="subtitle"
+				:showSubtitle="showSubtitle"
+				:showTitle="showTitle"
+			></uHeader>
+			<scroll-view
+				:style="{
+					height: $u.addUnit(listHeight)
+				}"
+				scroll-y
+				@scroll="onScroll"
+				:scrollIntoView="scrollIntoView"
+			>
+				<uMonth
+					:color="color"
+					:rowHeight="rowHeight"
+					:showMark="showMark"
+					:months="months"
+					:mode="mode"
+					:maxCount="maxCount"
+					:startText="startText"
+					:endText="endText"
+					:defaultDate="defaultDate"
+					:minDate="minDate"
+					:maxDate="maxDate"
+					:maxMonth="maxMonth"
+					:readonly="readonly"
+					:maxRange="maxRange"
+					:rangePrompt="rangePrompt"
+					:showRangePrompt="showRangePrompt"
+					:allowSameDay="allowSameDay"
+					ref="month"
+					@monthSelected="monthSelected"
+					@updateMonthTop="updateMonthTop"
+				></uMonth>
+			</scroll-view>
+			<slot name="footer" v-if="showConfirm">
+				<view class="u-calendar__confirm">
+					<u-button
+						shape="circle"
+						:text="buttonDisabled ? confirmDisabledText : confirmText"
+						:color="color"
+						@click="confirm"
+						:disabled="buttonDisabled"
+					></u-button>
+				</view>
+			</slot>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	import uHeader from './header.vue';
+	import uMonth from './month.vue';
+	import props from './props.js';
+	import util from './util.js';
+	import dayjs from '../../libs/util/dayjs.js';
+	import Calendar from '../../libs/util/calendar.js';
+	/**
+	 * Calendar 日历
+	 * @description  此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+	 * @tutorial https://www.uviewui.com/components/calendar.html
+	 * 
+	 * @property {String}				title				标题内容 (默认 日期选择 )
+	 * @property {Boolean}				showTitle			是否显示标题  (默认 true )
+	 * @property {Boolean}				showSubtitle		是否显示副标题	(默认 true )
+	 * @property {String}				mode				日期类型选择  single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+	 * @property {String}				startText			mode=range时,第一个日期底部的提示文字  (默认 '开始' )
+	 * @property {String}				endText				mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+	 * @property {Array}				customList			自定义列表 
+	 * @property {String}				color				主题色,对底部按钮和选中日期有效  (默认 ‘#3c9cff' )
+	 * @property {String | Number}		minDate				最小的可选日期	 (默认 0 )
+	 * @property {String | Number}		maxDate				最大可选日期  (默认 0 )
+	 * @property {Array | String| Date}	defaultDate			默认选中的日期,mode为multiple或range是必须为数组格式 
+	 * @property {String | Number}		maxCount			mode=multiple时,最多可选多少个日期  (默认 	Number.MAX_SAFE_INTEGER  )
+	 * @property {String | Number}		rowHeight			日期行高 (默认 56 )
+	 * @property {Function}				formatter			日期格式化函数
+	 * @property {Boolean}				showLunar			是否显示农历  (默认 false )
+	 * @property {Boolean}				showMark			是否显示月份背景色 (默认 true )
+	 * @property {String}				confirmText			确定按钮的文字 (默认 '确定' )
+	 * @property {String}				confirmDisabledText	确认按钮处于禁用状态时的文字 (默认 '确定' )
+	 * @property {Boolean}				show				是否显示日历弹窗 (默认 false )
+	 * @property {Boolean}				closeOnClickOverlay	是否允许点击遮罩关闭日历 (默认 false )
+	 * @property {Boolean}				readonly	        是否为只读状态,只读状态下禁止选择日期 (默认 false )
+	 * @property {String | Number}		maxRange	        日期区间最多可选天数,默认无限制,mode = range时有效
+	 * @property {String}				rangePrompt	        范围选择超过最多可选天数时的提示文案,mode = range时有效
+	 * @property {Boolean}				showRangePrompt	    范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+	 * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+	 * 
+	 * @event {Function()} confirm 		点击确定按钮时触发		选择日期相关的返回参数
+	 * @event {Function()} close 		日历关闭时触发			可定义页面关闭时的回调事件
+	 * @example <u-calendar  :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
+	</u-calendar>
+	 * */ 
+	export default {
+		name: 'u-calendar',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		components: {
+			uHeader,
+			uMonth
+		},
+		data() {
+			return {
+				// 需要显示的月份的数组
+				months: [],
+				// 在月份滚动区域中,当前视图中月份的index索引
+				monthIndex: 0,
+				// 月份滚动区域的高度
+				listHeight: 0,
+				// month组件中选择的日期数组
+				selected: [],
+				// 如果没有设置最大可选日期,默认为往后推3个月
+				maxMonth: 3,
+				scrollIntoView: '',
+				// 过滤处理方法
+				innerFormatter: value => value
+			}
+		},
+		watch: {
+			selectedChange: {
+				immediate: true,
+				handler(n) {
+					this.setMonth()
+				}
+			},
+			// 打开弹窗时,设置月份数据
+			show: {
+				immediate: true,
+				handler(n) {
+					this.setMonth()
+				}
+			},
+		},
+		computed: {
+			// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+			selectedChange() {
+				return [this.minDate, this.maxDate, this.defaultDate]
+			},
+			subtitle() {
+				// 初始化时,this.months为空数组,所以需要特别判断处理
+				if (this.months.length) {
+					return `${this.months[this.monthIndex].year}年${this.months[this.monthIndex].month}月`
+				} else {
+					return ''
+				}
+			},
+			buttonDisabled() {
+				// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+				if (this.mode === 'range') {
+					if (this.selected.length <= 1) {
+						return true
+					} else {
+						return false
+					}
+				} else {
+					return false
+				}
+			}
+		},
+		mounted() {
+			this.start = Date.now()
+			this.init()
+		},
+		methods: {
+			// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+			setFormatter(e) {
+				this.innerFormatter = e
+			},
+			// month组件内部选择日期后,通过事件通知给父组件
+			monthSelected(e) {
+				this.selected = e
+				if(!this.showConfirm) {
+					// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+					if (this.mode === 'multiple' || this.mode === 'single' || this.mode === 'range' && this.selected.length >= 2) {
+						this.$emit('confirm', this.selected)
+					}
+				}
+			},
+			init() {
+				// 滚动区域的高度
+				this.listHeight = this.rowHeight * 5 + 30
+				this.setMonth()
+			},
+			close() {
+				this.$emit('close')
+			},
+			// 点击确定按钮
+			confirm() {
+				if (!this.buttonDisabled) {
+					this.$emit('confirm', this.selected)
+				}
+			},
+			// 设置月份数据
+			setMonth() {
+				// 最小日期的毫秒数
+				const minDate = this.minDate || dayjs().valueOf()
+				// 如果没有指定最大日期,则往后推3个月
+				const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').valueOf()
+				// 最小与最大月份
+				let minMonth = dayjs(minDate).month() + 1
+				let maxMonth = dayjs(maxDate).month() + 1
+				// 如果maxMonth小于minMonth,则意味着maxMonth为下一年的月份,需要加上12,为的是计算出两个月份之间间隔着多少月份
+				maxMonth = minMonth > maxMonth ? maxMonth + 12 : maxMonth
+				// 最大最小月份之间的共有多少个月份
+				const months = Math.abs(minMonth - maxMonth)
+				// 先清空数组
+				this.months = []
+				for (let i = 0; i <= months; i++) {
+					this.months.push({
+						date: new Array(dayjs(minDate).add(i, 'month').daysInMonth()).fill(1).map((item,
+							index) => {
+							// 日期,取值1-31
+							let day = index + 1
+							// 星期,0-6,0为周日
+							const week = dayjs(minDate).add(i, "month").date(day).day()
+							const date = dayjs(minDate).add(i, "month").date(day).format("YYYY-MM-DD")
+							let bottomInfo = ''
+							if (this.showLunar) {
+								// 将日期转为农历格式
+								const lunar = Calendar.solar2lunar(dayjs(date).year(), dayjs(date)
+									.month() + 1, dayjs(date).date())
+								bottomInfo = lunar.IDayCn
+							}
+							let config = {
+								day,
+								week,
+								// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+								disabled: dayjs(date).isBefore(dayjs(minDate).format("YYYY-MM-DD")) ||
+									dayjs(date).isAfter(dayjs(maxDate).format("YYYY-MM-DD")),
+								// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+								date: new Date(date),
+								bottomInfo,
+								dot: false,
+								month: dayjs(minDate).add(i, "month").month() + 1
+							}
+							const formatter = this.formatter || this.innerFormatter
+							return formatter(config)
+						}),
+						// 当前所属的月份
+						month: dayjs(minDate).add(i, "month").month() + 1,
+						// 当前年份
+						year: dayjs(minDate).add(i, "month").year()
+					});
+				}
+			},
+			// scroll-view滚动监听
+			onScroll(event) {
+				// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+				const scrollTop = Math.max(0, event.detail.scrollTop)
+				// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+				for (let i = 0; i < this.months.length; i++) {
+					if (scrollTop >= (this.months[i].top || this.listHeight)) {
+						this.monthIndex = i
+					}
+				}
+			},
+			// 更新月份的top值
+			updateMonthTop(topArr = []) {
+				// 设置对应月份的top值,用于onScroll方法更新月份
+				topArr.map((item, index) => {
+					this.months[index].top = item
+				}) 
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-calendar {
+
+		&__confirm {
+			padding: 7px 18px;
+		}
+	}
+</style>

+ 85 - 0
uni_modules/uview-ui/components/u-calendar/util.js

@@ -0,0 +1,85 @@
+export default {
+    methods: {
+        // 设置月份数据
+        setMonth() {
+            // 月初是周几
+            const day = dayjs(this.date).date(1).day()
+            const start = day == 0 ? 6 : day - 1
+
+            // 本月天数
+            const days = dayjs(this.date).endOf('month').format('D')
+
+            // 上个月天数
+            const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
+
+            // 日期数据
+            const arr = []
+            // 清空表格
+            this.month = []
+
+            // 添加上月数据
+            arr.push(
+                ...new Array(start).fill(1).map((e, i) => {
+                    const day = prevDays - start + i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加本月数据
+            arr.push(
+                ...new Array(days - 0).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        date: dayjs(this.date).date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加下个月
+            arr.push(
+                ...new Array(42 - days - start).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 分割数组
+            for (let n = 0; n < arr.length; n += 7) {
+                this.month.push(
+                    arr.slice(n, n + 7).map((e, i) => {
+                        e.index = i + n
+
+                        // 自定义信息
+                        const custom = this.customList.find((c) => c.date == e.date)
+
+                        // 农历
+                        if (this.lunar) {
+                            const {
+                                IDayCn,
+                                IMonthCn
+                            } = this.getLunar(e.date)
+                            e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
+                        }
+
+                        return {
+                            ...e,
+                            ...custom
+                        }
+                    })
+                )
+            }
+        }
+    }
+}

+ 14 - 0
uni_modules/uview-ui/components/u-car-keyboard/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 是否打乱键盘按键的顺序
+        random: {
+            type: Boolean,
+            default: false
+        },
+        // 输入一个中文后,是否自动切换到英文
+        autoChange: {
+            type: Boolean,
+            default: false
+        }
+    }
+}

+ 311 - 0
uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue

@@ -0,0 +1,311 @@
+<template>
+	<view
+		class="u-keyboard"
+		@touchmove.stop.prevent="noop"
+	>
+		<view
+			v-for="(group, i) in abc ? engKeyBoardList : areaList"
+			:key="i"
+			class="u-keyboard__button"
+			:index="i"
+			:class="[i + 1 === 4 && 'u-keyboard__button--center']"
+		>
+			<view
+				v-if="i === 3"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__left"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+					@tap="changeCarInputMode"
+				>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>中</text>
+					<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>英</text>
+				</view>
+			</view>
+			<view
+				class="u-keyboard__button__inner-wrapper"
+				v-for="(item, j) in group"
+				:key="j"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__inner"
+					:hover-stay-time="200"
+					@tap="carInputClick(i, j)"
+					hover-class="u-hover-class"
+				>
+					<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
+				</view>
+			</view>
+			<view
+				v-if="i === 3"
+				@touchstart="backspaceClick"
+				@touchend="clearTimer"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__right"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+				>
+					<u-icon
+						size="28"
+						name="backspace"
+						color="#303133"
+					></u-icon>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * keyboard 键盘组件
+	 * @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+	 * @tutorial https://uviewui.com/components/keyboard.html
+	 * @property {Boolean} random 是否打乱键盘的顺序
+	 * @event {Function} change 点击键盘触发
+	 * @event {Function} backspace 点击退格键触发
+	 * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
+	 */
+	export default {
+		name: "u-keyboard",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+				abc: false
+			};
+		},
+		computed: {
+			areaList() {
+				let data = [
+					'京',
+					'沪',
+					'粤',
+					'津',
+					'冀',
+					'豫',
+					'云',
+					'辽',
+					'黑',
+					'湘',
+					'皖',
+					'鲁',
+					'苏',
+					'浙',
+					'赣',
+					'鄂',
+					'桂',
+					'甘',
+					'晋',
+					'陕',
+					'蒙',
+					'吉',
+					'闽',
+					'贵',
+					'渝',
+					'川',
+					'青',
+					'琼',
+					'宁',
+					'挂',
+					'藏',
+					'港',
+					'澳',
+					'新',
+					'使',
+					'学'
+				];
+				let tmp = [];
+				// 打乱顺序
+				if (this.random) data = this.$u.randomArray(data);
+				// 切割成二维数组
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			},
+			engKeyBoardList() {
+				let data = [
+					1,
+					2,
+					3,
+					4,
+					5,
+					6,
+					7,
+					8,
+					9,
+					0,
+					'Q',
+					'W',
+					'E',
+					'R',
+					'T',
+					'Y',
+					'U',
+					'I',
+					'O',
+					'P',
+					'A',
+					'S',
+					'D',
+					'F',
+					'G',
+					'H',
+					'J',
+					'K',
+					'L',
+					'Z',
+					'X',
+					'C',
+					'V',
+					'B',
+					'N',
+					'M'
+				];
+				let tmp = [];
+				if (this.random) data = this.$u.randomArray(data);
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			}
+		},
+		methods: {
+			// 点击键盘按钮
+			carInputClick(i, j) {
+				let value = '';
+				// 不同模式,获取不同数组的值
+				if (this.abc) value = this.engKeyBoardList[i][j];
+				else value = this.areaList[i][j];
+				// 如果允许自动切换,则将中文状态切换为英文
+				if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)
+				this.$emit('change', value);
+			},
+			// 修改汽车牌键盘的输入模式,中文|英文
+			changeCarInputMode() {
+				this.abc = !this.abc;
+			},
+			// 点击退格键
+			backspaceClick() {
+				this.$emit('backspace');
+				clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+				this.timer = null;
+				this.timer = setInterval(() => {
+					this.$emit('backspace');
+				}, 250);
+			},
+			clearTimer() {
+				clearInterval(this.timer);
+				this.timer = null;
+			},
+		}
+	};
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+	$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
+	$u-car-keyboard-padding:6px 0 6px !default;
+	$u-car-keyboard-button-inner-width:64rpx !default;
+	$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
+	$u-car-keyboard-button-height:80rpx !default;
+	$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
+	$u-car-keyboard-button-border-radius:4px !default;
+	$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
+	$u-car-keyboard-button-text-font-size:16px !default;
+	$u-car-keyboard-button-text-color:$u-main-color !default;
+	$u-car-keyboard-center-inner-margin: 0 4rpx !default;
+	$u-car-keyboard-special-button-width:134rpx !default;
+	$u-car-keyboard-lang-font-size:16px !default;
+	$u-car-keyboard-lang-color:$u-main-color !default;
+	$u-car-keyboard-active-color:$u-primary !default;
+	$u-car-keyboard-line-font-size:15px !default;
+	$u-car-keyboard-line-color:$u-main-color !default;
+	$u-car-keyboard-line-margin:0 1px !default;
+	$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
+
+	.u-keyboard {
+		@include flex(column);
+		justify-content: space-around;
+		background-color: $u-car-keyboard-background-color;
+		align-items: stretch;
+		padding: $u-car-keyboard-padding;
+
+		&__button {
+			@include flex;
+			justify-content: center;
+			flex: 1;
+			/* #ifndef APP-NVUE */
+			/* #endif */
+
+			&__inner-wrapper {
+				box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				margin: $u-car-keyboard-button-inner-margin;
+				border-radius: $u-car-keyboard-button-border-radius;
+
+				&__inner {
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					width: $u-car-keyboard-button-inner-width;
+					background-color: $u-car-keyboard-button-inner-background-color;
+					height: $u-car-keyboard-button-height;
+					border-radius: $u-car-keyboard-button-border-radius;
+
+					&__text {
+						font-size: $u-car-keyboard-button-text-font-size;
+						color: $u-car-keyboard-button-text-color;
+					}
+				}
+
+				&__left,
+				&__right {
+					border-radius: $u-car-keyboard-button-border-radius;
+					width: $u-car-keyboard-special-button-width;
+					height: $u-car-keyboard-button-height;
+					background-color: $u-car-keyboard-u-hover-class-background-color;
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				}
+
+				&__left {
+					&__line {
+						font-size: $u-car-keyboard-line-font-size;
+						color: $u-car-keyboard-line-color;
+						margin: $u-car-keyboard-line-margin;
+					}
+
+					&__lang {
+						font-size: $u-car-keyboard-lang-font-size;
+						color: $u-car-keyboard-lang-color;
+
+						&--active {
+							color: $u-car-keyboard-active-color;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.u-hover-class {
+		background-color: $u-car-keyboard-u-hover-class-background-color;
+	}
+</style>

+ 14 - 0
uni_modules/uview-ui/components/u-cell-group/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 分组标题
+        title: {
+            type: String,
+            default: uni.$u.props.cellGroup.title
+        },
+        // 是否显示外边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cellGroup.border
+        }
+    }
+}

+ 61 - 0
uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue

@@ -0,0 +1,61 @@
+<template>
+    <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
+        <view v-if="title" class="u-cell-group__title">
+            <slot name="title">
+				<text class="u-cell-group__title__text">{{ title }}</text>
+			</slot>
+        </view>
+        <view class="u-cell-group__wrapper">
+			<u-line v-if="border"></u-line>
+            <slot />
+        </view>
+    </view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cellGroup  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * 
+	 * @property {String}	title		分组标题
+	 * @property {Boolean}	border		是否显示外边框 (默认 true )
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 * 
+	 * @event {Function} click 	点击cell列表时触发
+	 * @example <u-cell-group title="设置喜好">
+	 */
+	export default {
+		name: 'u-cell-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+	
+	$u-cell-group-title-padding: 16px 16px 8px !default;
+	$u-cell-group-title-font-size: 15px !default;
+	$u-cell-group-title-line-height: 16px !default;
+	$u-cell-group-title-color: $u-main-color !default;
+
+    .u-cell-group {
+		flex: 1;
+		
+        &__title {
+            padding: $u-cell-group-title-padding;
+
+            &__text {
+                font-size: $u-cell-group-title-font-size;
+                line-height: $u-cell-group-title-line-height;
+                color: $u-cell-group-title-color;
+            }
+        }
+		
+		&__wrapper {
+			position: relative;
+		}
+    }
+</style>
+

+ 109 - 0
uni_modules/uview-ui/components/u-cell/props.js

@@ -0,0 +1,109 @@
+export default {
+    props: {
+        // 标题
+        title: {
+            type: [String, Number],
+            default: uni.$u.props.cell.title
+        },
+        // 标题下方的描述信息
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.cell.label
+        },
+        // 右侧的内容
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.cell.value
+        },
+        // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+        icon: {
+            type: String,
+            default: uni.$u.props.cell.icon
+        },
+        // 标题的宽度,单位任意,数值默认为px单位
+        titleWidth: {
+            type: [String, Number],
+            default: uni.$u.props.cell.titleWidth
+        },
+        // 是否禁用cell
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.cell.disabled
+        },
+        // 是否显示下边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cell.border
+        },
+        // 内容是否垂直居中(主要是针对右侧的value部分)
+        center: {
+            type: Boolean,
+            default: uni.$u.props.cell.center
+        },
+        // 点击后跳转的URL地址
+        url: {
+            type: String,
+            default: uni.$u.props.cell.url
+        },
+        // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
+        linkType: {
+            type: String,
+            default: uni.$u.props.cell.linkType
+        },
+        // 是否开启点击反馈(表现为点击时加上灰色背景)
+        clickable: {
+            type: Boolean,
+            default: uni.$u.props.cell.clickable
+        },
+        // 是否展示右侧箭头并开启点击反馈
+        isLink: {
+            type: Boolean,
+            default: uni.$u.props.cell.isLink
+        },
+        // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+        required: {
+            type: Boolean,
+            default: uni.$u.props.cell.required
+        },
+        // 右侧的图标箭头
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.cell.rightIcon
+        },
+        // 右侧箭头的方向,可选值为:left,up,down
+        arrowDirection: {
+            type: String,
+            default: uni.$u.props.cell.arrowDirection
+        },
+        // 左侧图标样式
+        iconStyle: {
+            type: Object,
+            default: () => {}
+        },
+        // 右侧箭头图标的样式
+        rightIconStyle: {
+            type: Object,
+            default: () => uni.$u.props.cell.rightIconStyle
+        },
+        // 标题的样式
+        titleStyle: {
+            type: Object,
+            default: () => uni.$u.props.cell.titleStyle
+        },
+        // 单位元的大小,可选值为large
+        size: {
+            type: String,
+            default: uni.$u.props.cell.size
+        },
+        // 点击cell是否阻止事件传播
+        stop: {
+            type: Boolean,
+            default: uni.$u.props.cell.stop
+        },
+        // 标识符,cell被点击时返回
+        name: {
+            type: [Number, String],
+            default: uni.$u.props.cell.name
+        }
+    }
+}

+ 224 - 0
uni_modules/uview-ui/components/u-cell/u-cell.vue

@@ -0,0 +1,224 @@
+<template>
+	<view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]"
+		:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
+		@tap="clickHandler">
+		<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
+			<view class="u-cell__body__content">
+				<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
+					<slot name="icon" v-if="$slots.icon">
+					</slot>
+					<u-icon v-else :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></u-icon>
+				</view>
+				<view class="u-cell__title">
+					<slot name="title">
+						<text v-if="title" class="u-cell__title-text"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
+					</slot>
+					<slot name="label">
+						<text class="u-cell__label" v-if="label"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
+					</slot>
+				</view>
+			</view>
+			<slot name="value">
+				<text class="u-cell__value"
+					:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
+					v-if="!$u.test.empty(value)">{{ value }}</text>
+			</slot>
+			<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
+				:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
+				<slot name="right-icon" v-if="$slots['right-icon']">
+				</slot>
+				<u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+					:size="size === 'large' ? 18 : 16"></u-icon>
+			</view>
+		</view>
+		<u-line v-if="border"></u-line>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cell  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * @property {String | Number}	title			标题
+	 * @property {String | Number}	label			标题下方的描述信息
+	 * @property {String | Number}	value			右侧的内容
+	 * @property {String}			icon			左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+	 * @property {String | Number}	titleWidth		标题的宽度,单位任意,数值默认为px单位
+	 * @property {Boolean}			disabled		是否禁用cell	
+	 * @property {Boolean}			border			是否显示下边框 (默认 true )
+	 * @property {Boolean}			center			内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+	 * @property {String}			url				点击后跳转的URL地址
+	 * @property {String}			linkType		链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+	 * @property {Boolean}			clickable		是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) 
+	 * @property {Boolean}			isLink			是否展示右侧箭头并开启点击反馈 (默认 false )
+	 * @property {Boolean}			required		是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+	 * @property {String}			rightIcon		右侧的图标箭头 (默认 'arrow-right')
+	 * @property {String}			arrowDirection	右侧箭头的方向,可选值为:left,up,down
+	 * @property {Object}			rightIconStyle	右侧箭头图标的样式
+	 * @property {Object}			titleStyle		标题的样式
+	 * @property {String}			size			单位元的大小,可选值为 large,normal,mini 
+	 * @property {Boolean}			stop			点击cell是否阻止事件传播 (默认 true )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}			click			点击cell列表时触发
+	 * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+	 */
+	export default {
+		name: 'u-cell',
+		data() {
+			return {
+
+			}
+		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		methods: {
+			// 点击cell
+			clickHandler(e) {
+				if (this.disabled) return
+				this.$emit('click', {
+					name: this.name
+				})
+				// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+				this.openPage()
+				// 是否阻止事件传播
+				this.stop && this.preventEvent(e)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	$u-cell-padding: 10px 15px !default;
+	$u-cell-font-size: 15px !default;
+	$u-cell-line-height: 24px !default;
+	$u-cell-color: $u-main-color !default;
+	$u-cell-icon-size: 16px !default;
+	$u-cell-title-font-size: 15px !default;
+	$u-cell-title-line-height: 22px !default;
+	$u-cell-title-color: $u-main-color !default;
+	$u-cell-label-font-size: 12px !default;
+	$u-cell-label-color: $u-tips-color !default;
+	$u-cell-label-line-height: 18px !default;
+	$u-cell-value-font-size: 14px !default;
+	$u-cell-value-color: $u-content-color !default;
+	$u-cell-clickable-color: $u-bg-color !default;
+	$u-cell-disabled-color: #c8c9cc !default;
+	$u-cell-padding-top-large: 13px !default;
+	$u-cell-padding-bottom-large: 13px !default;
+	$u-cell-value-font-size-large: 15px !default;
+	$u-cell-label-font-size-large: 14px !default;
+	$u-cell-title-font-size-large: 16px !default;
+	$u-cell-left-icon-wrap-margin-right: 4px !default;
+	$u-cell-right-icon-wrap-margin-left: 4px !default;
+	$u-cell-title-flex:1 !default;
+	$u-cell-label-margin-top:5px !default;
+
+
+	.u-cell {
+		&__body {
+			@include flex();
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			padding: $u-cell-padding;
+			font-size: $u-cell-font-size;
+			color: $u-cell-color;
+			// line-height: $u-cell-line-height;
+			align-items: center;
+
+			&__content {
+				@include flex(row);
+				align-items: center;
+				flex: 1;
+			}
+
+			&--large {
+				padding-top: $u-cell-padding-top-large;
+				padding-bottom: $u-cell-padding-bottom-large;
+			}
+		}
+
+		&__left-icon-wrap,
+		&__right-icon-wrap {
+			@include flex();
+			align-items: center;
+			// height: $u-cell-line-height;
+			font-size: $u-cell-icon-size;
+		}
+
+		&__left-icon-wrap {
+			margin-right: $u-cell-left-icon-wrap-margin-right;
+		}
+
+		&__right-icon-wrap {
+			margin-left: $u-cell-right-icon-wrap-margin-left;
+			transition: transform 0.3s;
+
+			&--up {
+				transform: rotate(-90deg);
+			}
+
+			&--down {
+				transform: rotate(90deg);
+			}
+		}
+
+		&__title {
+			flex: $u-cell-title-flex;
+
+			&-text {
+				font-size: $u-cell-title-font-size;
+				line-height: $u-cell-title-line-height;
+				color: $u-cell-title-color;
+
+				&--large {
+					font-size: $u-cell-title-font-size-large;
+				}
+			}
+
+		}
+
+		&__label {
+			margin-top: $u-cell-label-margin-top;
+			font-size: $u-cell-label-font-size;
+			color: $u-cell-label-color;
+			line-height: $u-cell-label-line-height;
+
+			&--large {
+				font-size: $u-cell-label-font-size-large;
+			}
+		}
+
+		&__value {
+			text-align: right;
+			font-size: $u-cell-value-font-size;
+			line-height: $u-cell-line-height;
+			color: $u-cell-value-color;
+
+			&--large {
+				font-size: $u-cell-value-font-size-large;
+			}
+		}
+
+		&--clickable {
+			background-color: $u-cell-clickable-color;
+		}
+
+		&--disabled {
+			color: $u-cell-disabled-color;
+			/* #ifndef APP-NVUE */
+			cursor: not-allowed;
+			/* #endif */
+		}
+
+		&--center {
+			align-items: center;
+		}
+	}
+</style>

+ 82 - 0
uni_modules/uview-ui/components/u-checkbox-group/props.js

@@ -0,0 +1,82 @@
+export default {
+    props: {
+        // 标识符
+        name: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.name
+        },
+        // 绑定的值
+        value: {
+            type: Array,
+            default: uni.$u.props.checkboxGroup.value
+        },
+        // 形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.shape
+        },
+        // 是否禁用全部checkbox
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.disabled
+        },
+
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.inactiveColor
+        },
+
+        // 整个组件的尺寸,默认px
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.size
+        },
+        // 布局方式,row-横向,column-纵向
+        placement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.placement
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.labelSize
+        },
+        // label的字体颜色
+        labelColor: {
+            type: [String],
+            default: uni.$u.props.checkboxGroup.labelColor
+        },
+        // 是否禁止点击文本操作
+        labelDisabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.labelDisabled
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.iconSize
+        },
+        // 勾选图标的对齐方式,left-左边,right-右边
+        iconPlacement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconPlacement
+        },
+        // 竖向配列时,是否显示下划线
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.borderBottom
+        }
+
+    }
+}

+ 103 - 0
uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view
+	    class="u-checkbox-group"
+	    :class="bemClass"
+	>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * checkboxGroup 复选框组
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @property {String}			name			标识符 
+	 * @property {Array}			value			绑定的值
+	 * @property {String}			shape			形状,circle-圆形,square-方形 (默认 'square' )
+	 * @property {Boolean}			disabled		是否禁用全部checkbox (默认 false )
+	 * @property {String}			activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
+	 * @property {String}			inactiveColor	未选中的颜色 (默认 '#c8c9cc' )
+	 * @property {String | Number}	size			整个组件的尺寸 单位px (默认 18 )
+	 * @property {String}			placement		布局方式,row-横向,column-纵向 (默认 'row' )
+	 * @property {String | Number}	labelSize		label的字体大小,px单位  (默认 14 )
+	 * @property {String}			labelColor		label的字体颜色 (默认 '#303133' )
+	 * @property {Boolean}			labelDisabled	是否禁止点击文本操作 (默认 false )
+	 * @property {String}			iconColor		图标颜色 (默认 '#ffffff' )
+	 * @property {String | Number}	iconSize		图标的大小,单位px (默认 12 )
+	 * @property {String}			iconPlacement	勾选图标的对齐方式,left-左边,right-右边  (默认 'left' )
+	 * @property {Boolean}			borderBottom	placement为row时,是否显示下边框 (默认 false )
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @event {Function}	input	修改通过v-model绑定的值时触发,回调为一个对象
+	 * @example <u-checkbox-group></u-checkbox-group>
+	 */
+	export default {
+		name: 'u-checkbox-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+			// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
+			// 拉取父组件新的变化后的参数
+			parentData() {
+				return [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+					this.iconSize, this.borderBottom, this.placement
+				]
+			},
+			bemClass() {
+				// this.bem为一个computed变量,在mixin中
+				return this.bem('checkbox-group', ['placement'])
+			},
+		},
+		watch: {
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(u-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.updateParentData) === 'function' && child.updateParentData()
+					})
+				}
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 将其他的checkbox设置为未选中的状态
+			unCheckedOther(childInstance) {
+				const values = []
+				this.children.map(child => {
+					// 将被选中的checkbox,放到数组中返回
+					if (child.isChecked) {
+						values.push(child.name)
+					}
+				})
+				// 发出事件
+				this.$emit('change', values)
+				// 修改通过v-model绑定的值
+				this.$emit('input', values)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/components.scss";
+
+	.u-checkbox-group {
+
+		&--row {
+			@include flex;
+		}
+
+		&--column {
+			@include flex(column);
+		}
+	}
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است