dever před 4 roky
rodič
revize
df2d853234
100 změnil soubory, kde provedl 11530 přidání a 0 odebrání
  1. 33 0
      App.vue
  2. 7 0
      lib/clipboard.min.js
  3. 57 0
      lib/clipboard.thorui.js
  4. 166 0
      lib/dever/config.js
  5. binární
      lib/dever/images/live_weixin.png
  6. 1176 0
      lib/dever/index.js
  7. 31 0
      lib/dever/pages/index/index.vue
  8. 31 0
      lib/dever/pages/update/index.vue
  9. 48 0
      lib/dever/pages/web_error.vue
  10. 28 0
      lib/dever/pages/web_view.vue
  11. binární
      lib/dever/static/share/live_weixin.png
  12. binární
      lib/dever/static/update/info-circle.png
  13. binární
      lib/dever/static/update/sync.png
  14. binární
      lib/dever/static/update/up-circle.png
  15. 2052 0
      lib/graceUI/animate.css
  16. 71 0
      lib/graceUI/components/graceActionSheet.vue
  17. 118 0
      lib/graceUI/components/graceAddressPicker.vue
  18. 59 0
      lib/graceUI/components/graceAnalysis.vue
  19. 24 0
      lib/graceUI/components/graceBorderRadius.vue
  20. 69 0
      lib/graceUI/components/graceBottomDialog.vue
  21. 69 0
      lib/graceUI/components/graceBoxBanner.vue
  22. 65 0
      lib/graceUI/components/graceCheckBtn.vue
  23. 122 0
      lib/graceUI/components/graceCheckList.vue
  24. 122 0
      lib/graceUI/components/graceCirProgress.vue
  25. 125 0
      lib/graceUI/components/graceCountDown.vue
  26. 46 0
      lib/graceUI/components/graceCoupons.vue
  27. 241 0
      lib/graceUI/components/graceDate.vue
  28. 157 0
      lib/graceUI/components/graceDateBetween.vue
  29. 191 0
      lib/graceUI/components/graceDateTime.vue
  30. 90 0
      lib/graceUI/components/graceDateTimeBetween.vue
  31. 153 0
      lib/graceUI/components/graceDateTimeBetweenBase.vue
  32. 106 0
      lib/graceUI/components/graceDialog.vue
  33. 68 0
      lib/graceUI/components/graceDrawer.vue
  34. 136 0
      lib/graceUI/components/graceEditor.vue
  35. 33 0
      lib/graceUI/components/graceEmpty.vue
  36. 12 0
      lib/graceUI/components/graceEmptyNew.vue
  37. 59 0
      lib/graceUI/components/graceFaceGroup.vue
  38. 44 0
      lib/graceUI/components/graceFixedMsg.vue
  39. 39 0
      lib/graceUI/components/graceFlex.vue
  40. 54 0
      lib/graceUI/components/graceFullLoading.vue
  41. 60 0
      lib/graceUI/components/graceHeader.vue
  42. 48 0
      lib/graceUI/components/graceHeaderAlert.vue
  43. 271 0
      lib/graceUI/components/graceIMInput.vue
  44. 200 0
      lib/graceUI/components/graceIMMsg.vue
  45. 36 0
      lib/graceUI/components/graceIconClasses.vue
  46. 740 0
      lib/graceUI/components/graceImageCrop.vue
  47. 28 0
      lib/graceUI/components/graceImmersedStatusbar.vue
  48. 43 0
      lib/graceUI/components/graceIphonexBottom.vue
  49. 46 0
      lib/graceUI/components/graceLink.vue
  50. 42 0
      lib/graceUI/components/graceLoading.vue
  51. 64 0
      lib/graceUI/components/graceNavBar.vue
  52. 66 0
      lib/graceUI/components/graceNavBar2.vue
  53. 71 0
      lib/graceUI/components/graceNubmerAnimate.vue
  54. 130 0
      lib/graceUI/components/graceNumberBox.vue
  55. 82 0
      lib/graceUI/components/graceNumberKeyboard.vue
  56. 61 0
      lib/graceUI/components/graceNvueActionSheet.vue
  57. 118 0
      lib/graceUI/components/graceNvueAddressPicker.vue
  58. 67 0
      lib/graceUI/components/graceNvueAnalysis.vue
  59. 24 0
      lib/graceUI/components/graceNvueBorderRadius.vue
  60. 44 0
      lib/graceUI/components/graceNvueBottomDialog.vue
  61. 71 0
      lib/graceUI/components/graceNvueBoxBanner.vue
  62. 58 0
      lib/graceUI/components/graceNvueCheckBtn.vue
  63. 119 0
      lib/graceUI/components/graceNvueCheckList.vue
  64. 126 0
      lib/graceUI/components/graceNvueCountDown.vue
  65. 51 0
      lib/graceUI/components/graceNvueCoupons.vue
  66. 244 0
      lib/graceUI/components/graceNvueDate.vue
  67. 157 0
      lib/graceUI/components/graceNvueDateBetween.vue
  68. 189 0
      lib/graceUI/components/graceNvueDateTime.vue
  69. 98 0
      lib/graceUI/components/graceNvueDialog.vue
  70. 70 0
      lib/graceUI/components/graceNvueDrawer.vue
  71. 129 0
      lib/graceUI/components/graceNvueEditor.vue
  72. 26 0
      lib/graceUI/components/graceNvueEmpty.vue
  73. 12 0
      lib/graceUI/components/graceNvueEmptyNew.vue
  74. 54 0
      lib/graceUI/components/graceNvueFaceGroup.vue
  75. 43 0
      lib/graceUI/components/graceNvueFixedMsg.vue
  76. 29 0
      lib/graceUI/components/graceNvueFlex.vue
  77. 76 0
      lib/graceUI/components/graceNvueFullLoading.vue
  78. 47 0
      lib/graceUI/components/graceNvueHeader.vue
  79. 47 0
      lib/graceUI/components/graceNvueHeaderAlert.vue
  80. 33 0
      lib/graceUI/components/graceNvueImmersedStatusbar.vue
  81. 37 0
      lib/graceUI/components/graceNvueLink.vue
  82. 55 0
      lib/graceUI/components/graceNvueLoading.vue
  83. 55 0
      lib/graceUI/components/graceNvueNavBar.vue
  84. 59 0
      lib/graceUI/components/graceNvueNavBar2.vue
  85. 71 0
      lib/graceUI/components/graceNvueNubmerAnimate.vue
  86. 130 0
      lib/graceUI/components/graceNvueNumberBox.vue
  87. 87 0
      lib/graceUI/components/graceNvueNumberKeyboard.vue
  88. 156 0
      lib/graceUI/components/graceNvuePage.vue
  89. 56 0
      lib/graceUI/components/graceNvuePopupMenu.vue
  90. 47 0
      lib/graceUI/components/graceNvueProgressButton.vue
  91. 154 0
      lib/graceUI/components/graceNvueReload.vue
  92. 58 0
      lib/graceUI/components/graceNvueScroll.vue
  93. 64 0
      lib/graceUI/components/graceNvueSearch.vue
  94. 47 0
      lib/graceUI/components/graceNvueSegmentedControl.vue
  95. 95 0
      lib/graceUI/components/graceNvueSelectImg.vue
  96. 216 0
      lib/graceUI/components/graceNvueSelectImgAndUpload.vue
  97. 76 0
      lib/graceUI/components/graceNvueSelectList.vue
  98. 96 0
      lib/graceUI/components/graceNvueSelectMenu.vue
  99. 127 0
      lib/graceUI/components/graceNvueSelectTags.vue
  100. 22 0
      lib/graceUI/components/graceNvueShade.vue

+ 33 - 0
App.vue

@@ -0,0 +1,33 @@
+<script>
+export default {
+	onLaunch: function() {
+		// #ifdef APP-PLUS
+		plus.screen.lockOrientation('portrait-primary'); //锁定屏幕
+		const dom = weex.requireModule('dom');
+		dom.addRule('fontFace', {
+			fontFamily: 'graceIconfont',
+			src: "url('https://at.alicdn.com/t/font_823462_1p2i18cul3t.ttf')"
+		});
+		// #endif
+	},
+	onShow: function() {},
+	onHide: function() {}
+};
+</script>
+<style>
+/* #ifndef APP-PLUS-NVUE */
+@import './lib/graceUI/graceUI.css';
+@import './lib/graceUI/graceIcons.css';
+/* #endif */
+/* #ifdef APP-PLUS-NVUE */
+@import './lib/graceUI/graceWeex.css';
+.grace-icons {
+	font-family: graceIconfont; 
+}
+/* #endif */
+page{background-color: #ECEEF7;}
+/* 重写showModal样式 */
+uni-modal .uni-modal__hd {padding:30rpx 0;}
+uni-modal .uni-modal__title {font-size: 28rpx;color: #666666;}
+uni-modal .uni-modal__bd{font-size: 30rpx;color: #222222;padding:0 0 30rpx 0}
+</style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 7 - 0
lib/clipboard.min.js


+ 57 - 0
lib/clipboard.thorui.js

@@ -0,0 +1,57 @@
+/**
+ * 复制文本 兼容H5
+ * 来自 ThorUI  www.thorui.cn | 文档地址: www.donarui.com
+ * @author echo.
+ * @version 1.0.0
+ **/
+// #ifdef H5
+import ClipboardJS from "@/components/clipboard.min.js"
+// #endif
+const thorui = {
+	getClipboardData: function(data, callback) {
+		// #ifdef APP-PLUS || MP
+		uni.setClipboardData({
+			data: data,
+			success(res) {
+				uni.getClipboardData({
+					success(res) {
+						("function" == typeof callback) && callback(true)
+						uni.showToast({title:'复制成功',icon:'none'})
+					},
+					fail(res) {
+						("function" == typeof callback) && callback(false)
+						uni.showToast({title:'复制失败',icon:'none'})
+					}
+				})
+			},
+			fail(res) {
+				("function" == typeof callback) && callback(false)
+			}
+		})
+		// #endif
+
+		// #ifdef H5
+		let event = window.event || {}
+		let clipboard = new ClipboardJS("", {
+			text: () => data
+		})
+		clipboard.on('success', (e) => {
+			("function" == typeof callback) && callback(true)
+			clipboard.off('success')
+			clipboard.off('error')
+			clipboard.destroy()
+		});
+		clipboard.on('error', (e) => {
+			("function" == typeof callback) && callback(false)
+			clipboard.off('success')
+			clipboard.off('error')
+			clipboard.destroy()
+		});
+		clipboard.onClick(event)
+		// #endif
+	}
+};
+
+module.exports = {
+	getClipboardData: thorui.getClipboardData
+};

+ 166 - 0
lib/dever/config.js

@@ -0,0 +1,166 @@
+//基本配置项
+const base = {
+	debug: false
+}
+
+//http request请求配置
+const request = {
+	//	服务器接口地址
+
+	host: "https://dev.youqianzhuan.vip/", // 服务器开发域名
+	host: "https://api.youqianzhuan.vip/", // 服务器正式域名
+	// host: "http://www.zhuan.com/", //本地测试
+	//host: "http://zhuan.5dev.cn/", //本地测试
+	
+	//	请求的参数	
+	data: {},
+	//	设置请求的 header,header 中不能设置 Referer。
+	header: {},
+	//	(需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT	
+	method: "POST",
+	//	json	如果设为json,会尝试对返回的数据做一次 JSON.parse	
+	dataType: "json",
+	//	text	设置响应的数据类型。合法值:text、arraybuffer	1.7.0
+	responseType: "text",
+	//	收到开发者服务成功返回的回调函数	
+	success() {},
+	//	接口调用失败的回调函数	
+	fail() {},
+	//	接口调用结束的回调函数(调用成功、失败都会执行)
+	complete() {},
+}
+
+//上传配置
+const upload = {
+	url: request.host + 'member/api.up_file/plupload',
+	name: 'file',
+}
+
+//后端服务列表
+const server = {
+	// ========== 大A ================
+	// 公用 - 获取1个会员基础数据
+	'getMemberSimple': 'member/api.member/getMember',
+	// 公用 - 获取1个会员完整信息
+	'getMember': 'member/api.member/get',
+	
+	// 注册-提交手机号
+	'checkPhone': 'member/api.register/checkPhone',
+	// 注册-发送验证码
+	'sendSms': 'member/api.register/sendsms',
+	// 注册-验证验证码
+	'checkCode': 'member/api.register/checkCode',
+	// 注册-提交密码并写入用户
+	'setReg': 'member/api.register/setReg',
+	// 用户登录
+	'login': 'member/api.login/index',
+	// 用户登录 - 修改密码 - 检查是否注册并发短信
+	'reCheckPhone': 'member/api.login/reCheckPhone',
+	// 用户登录 - 修改密码 - 检查验证码
+	'reCheckCode': 'member/api.login/reCheckCode',
+	// 用户登录 - 修改密码
+	'setPassword': 'member/api.login/setPassword',
+	
+	// 用户中心 - 提交编辑资料
+	'setMember': 'member/api.ucenter/upMember',
+	// 用户中心 - 实名提交
+	'setRealName': 'member/api.real_name/set',
+	// 用户中心 - 银行卡绑定提交
+	'setBindBank': 'member/api.bind_bank/set',
+	// 用户中心 - 共用发短信
+	'sendSms': 'member/api.ucenter/sendSms',
+	// 用户中心 - 提交修改密码
+	'rePassword': 'member/api.ucenter/rePassword',
+	// 钱包 - 获取钱包日志
+	'getWalletLog': 'member/api.wallet/getLog',
+	// 钱包 - 获取钱包数据
+	'getWallet': 'member/api.wallet/getInfo',
+	// 钱包 - 提现 - 获取银行
+	'getBank': 'member/api.bind_bank/get',
+	// 钱包 - 提现 - 提交提现数据
+	'setBank': 'member/api.wallet/set',
+	// 代销系统 - 提交申请
+	'setAgent': 'agent/api.agent/set',
+	// 代销系统 - 代缴列表
+	'agentList': 'agent/api.agent/agentList',
+	// 代销系统 - 处理代缴请求
+	'recharge': 'agent/api.agent/recharge',
+	// 代销系统 - 充值
+	'agentHandle': 'agent/api.agent/handle',
+	// 代销系统 - 充值日志
+	'getAentLog': 'agent/api.agent/getLog',
+	// 客服系统 - 提交工单
+	'setFeedback': 'help/api.feed_back/set',
+	// 客服系统 - 获取列表
+	'getList': 'help/api.feed_back/gets',
+	// 客服系统 - 获取1个详情
+	'getReply': 'help/api.feed_back/getReply',
+	// 客服系统 - 用户中心 - 查询是否有未读记录
+	'getRedDot': 'help/api.feed_back/getRedDot',
+	// 帮助中心 - 获取列表
+	'getHelp': 'help/api.help/gets',
+	// 帮助中心 - 获取单挑
+	'getOneHelp': 'help/api.help/get',
+	
+	// 卡 - 提交移动卡表单
+	'setKa': 'quan/api.card/set',
+	
+	
+	
+	// ========== rabin ================
+	// 首页
+	'home': 'quan/api.info/home',
+	// 查看补贴卡
+	'quanView': 'quan/api.info/get',
+	// 查看我邀请的人
+	'quanInvite': 'quan/api.info/getInvite',
+	// 购买卡
+	'quanBuy' : 'quan/api.info/buy',
+	// 支付信息
+	'buyInfo' : 'pay/api.act/run',
+	// 支付下单
+	'buyOrder' : 'pay/api.act/order',
+	// 支付下单成功的页面
+	'buyMsg' : 'pay/api.act/finish',
+	// 获取分享
+	'quanShare' : 'quan/api.info/share',
+	// 根据code获取用户信息
+	'codeUser' : 'invite/api.code/user',
+	// 获取提额信息
+	'tie' : 'quan/api.tie/home',
+	// 确定提额
+	'tieAct' : 'quan/api.tie/act',
+	// 补贴转到钱包
+	'tieWallet' : 'quan/api.tie/ed',
+	// 补贴转到钱包
+	'tieWalletAct' : 'quan/api.tie/wallet',
+	// 获取基本配置
+	'getConfig' : 'base/api.config/index',
+	// 奖池基本信息
+	'rankInfo' : 'ranking/api.day/info',
+	// 奖池排行
+	'rankList' : 'ranking/api.day/list',
+	// 奖池里我自己的排行
+	'rankMy' : 'ranking/api.day/my',
+	// 历史奖池
+	'rankHistory' : 'ranking/api.day/history',
+	// 内容详细信息
+	'contentView' : 'content/api.info/view',
+	// 内容列表信息
+	'contentList' : 'content/api.info/data',
+	//获取机器人信息
+	'imInfo' : 'im/api.info/check',
+	//获取机器人聊天信息
+	'imMsg' : 'im/api.info/msg',
+	//发送机器人信息
+	'imMsgSend' : 'im/api.info/send',
+	//佣金转到钱包
+	'imWalletAct' : 'im/api.info/wallet',
+}
+
+export default {
+	base,
+	request,
+	server,
+	upload
+}

binární
lib/dever/images/live_weixin.png


+ 1176 - 0
lib/dever/index.js

@@ -0,0 +1,1176 @@
+//封装好一系列常用的方法 dever
+import config from './config';
+
+//http网络请求
+var http = {
+	//token的key
+	token : 'token',
+	//获取token
+	getToken: function() {
+		return uni.getStorageSync('prev1_' + this.token);
+	},
+	
+	//设置token
+	setToken: function(value) {
+		return uni.setStorageSync('prev1_' + this.token, value);
+	},
+	
+	//设置http的option
+	option: function(options) {
+		if (!options) {
+			options = {};
+		}
+		options[this.token] = this.getToken();
+		return options;
+	},
+	
+	//request请求
+	request: function(vue, url, options, callback, err_callback) {
+		var self = this;
+		if (vue == 'post') {
+			config.request.method = 'POST';
+			vue = false;
+		} else {
+			config.request.method = 'GET';
+		}
+		if (typeof(url) == 'object') {
+			vue = url[1];
+			url = url[0];
+		}
+		if (typeof(vue) == 'object') {
+			vue.isDisabled = true;
+		}
+		
+		self.core(url, options).then((result, state) => {
+			if (typeof(vue) == 'object') {
+				if (!vue.fetch) {
+					vue.fetch = {}
+				}
+				vue.isDisabled = false;
+				vue.fetch = Object.assign({}, vue.fetch, result)
+			}
+			
+			if (callback) {
+				callback(result, state);
+			}
+		}).catch((result) => {
+			if (typeof(vue) == 'object') {
+				vue.isDisabled = false;
+			}
+			if (err_callback) {
+				err_callback(result);
+			} else {
+				if (result.code == '2') {
+					dever.location(dever.login);
+				} else {
+					dever.alert(result.info)
+				}
+			}
+		})
+	},
+	
+	core : function(url, options) {
+		var self = this;
+		options = this.option(options);
+
+		if (config.server[url]) {
+			config.request.url = config.server[url];
+		} else {
+			config.request.url = url;
+		}
+		var loading_method = config.request.method;
+		if (options && options.page && options.page > 0) {
+			loading_method = 'page';
+		}
+		config.request.url = config.request.host + config.request.url;
+		config.request.data = options;
+		return new Promise((resolve, reject) => {
+			config.request.complete = (response) => {
+				dever.debug(response);
+				if (response.statusCode === 200) {
+					if (response.data.code == '1') {
+						if (options.page && options.page > 0) {
+							page.state = true;
+							if (page.key && response.data.data[page.key].length > 0) {
+								if (options && options.concat && options.concat == -1) {
+									page.data = response.data.data[page.key].concat(page.data);
+								} else {
+									page.data = page.data.concat(response.data.data[page.key]);
+								}
+								response.data.data[page.key] = page.data;
+							} else if (response.data.data.length > 0) {
+								if (options && options.concat && options.concat == -1) {
+									response.data.data = response.data.data.reverse();
+									page.data = response.data.data.concat(page.data);
+								} else {
+									page.data = page.data.concat(response.data.data);
+								}
+								response.data.data = page.data;
+							} else {
+								page.state = false;
+								resolve(false);
+								/*
+								if (page.key) {
+									resolve(response.data.data[page.key], false);
+								} else {
+									resolve(response.data.data, false);
+								}
+								*/
+							   if (options && options.noloading) {
+							   	
+							   } else {
+							   	dever.hideLoading(loading_method, options, url);
+							   }
+								return;
+							}
+						}
+						resolve(response.data.data);
+					} else if (response.data.code == '-1') {
+						//退出登录
+						self.setToken('');
+						console.info(dever);
+						dever.location('index/index', 'go');
+						reject(response.data)
+					} else {
+						reject(response.data)
+					}
+					if (options && options.noloading) {
+						
+					} else {
+						dever.hideLoading(loading_method, options, url);
+					}
+				} else {
+					// 处理catch 请求,不在本页面之外处理,统一在这里处理
+					if (options && options.handle) {
+						reject(response)
+					} else {
+						try {
+							Promise.reject(response).catch(err => {
+								self.error(response.statusCode || response.errMsg);
+							});
+						} catch (e) {
+							dever.alert(e)
+						}
+					}
+					if (options && options.noloading) {
+						
+					} else {
+						dever.hideLoading(loading_method, options, url);
+					}
+				}
+			}
+			//uni.request(Object.assign({}, config, options));
+			dever.debug(config.request);
+			if (options.noloading) {
+				
+			} else {
+				dever.loading(loading_method, options, url);
+			}
+			
+			uni.request(config.request);
+		})
+	},
+	
+	error : function(err) {
+		//console.error("请求背拒绝" + err)
+		/*
+		dever.data('web_error', err);
+		var path = '/components/dever/pages/web_error';
+
+		uni.navigateTo({
+			url: path
+		})
+		return;
+		*/
+
+		switch (err) {
+			case 401:
+				// 错误码404的处理方式
+				console.error("请求背拒绝" + err)
+				break;
+			case 404:
+				// 错误码404的处理方式
+				console.error("没有找到页面"  + err)
+				break;
+			case 500:
+				// 错误码404的处理方式
+				console.error("500服务器错误"  + err)
+				break;
+			case 405:
+				console.error("错误的请求"  + err)
+				break;
+		}
+	},
+}
+
+var im = {
+	socket : false,
+	init : function() {
+		this.connect();
+		this.info();
+	},
+	
+	connect : function() {
+		uni.connectSocket({
+			url: 'text://me.5dev.cn:8282',
+			complete: (res)=> {
+				console.log(res);
+			}
+		});
+		uni.onSocketOpen(function (res) {
+		  console.log('WebSocket连接已打开!');
+		});
+		uni.onSocketError(function (res) {
+		  console.log('WebSocket连接打开失败,请检查!');
+		});
+		
+	},
+	
+	info : function() {
+		console.info(222);
+		uni.onSocketMessage(function (res) {
+			console.log('收到服务器内容:' + res.data);
+		});
+	}
+}
+
+var page = {
+	//分页控制
+	value : 1,
+	//分页数据
+	data : [],
+	//分页所属的key
+	key : '',
+	//分页状态 true可以分页,false不能分页
+	state : true,
+	
+	//通用的获取数据方法:瀑布流分页 (1,'level')
+	get : function(config, vue, url, options, callback, err_callback) {
+		options = http.option(options);
+		if (typeof(config) == 'object') {
+			var state = config[0];
+			this.key = config[1];
+		} else {
+			var state = config;
+			this.key = '';
+		}
+		if (state == 1) {
+			this.value = 1;
+			this.data = [];
+		} else {
+			this.value++;
+		}
+		
+		if (this.state == false && state != 1) {
+			return;
+		}
+		
+		options.page = this.value;
+		http.request(vue, url, options, callback, err_callback);
+	},
+}
+
+var upload = {
+	data : {},
+	handle : function(key, count, callback) {
+		if (!count) {
+			count = 1;
+		}
+		var type = 1;
+		if (count > 1) {
+			type = 2;
+		}
+		var self = this;
+		count = parseInt(count);
+		uni.chooseImage({
+			count: count,
+			sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album'], //从相册选择
+			success: res => {
+				uni.showLoading();
+				res.tempFilePaths.forEach(function(item, index) {
+					//请求上传接口
+					uni.uploadFile({
+						url: config.upload.url, //仅为示例,非真实的接口地址
+						filePath: item,
+						name: config.upload.name,
+						formData: { token: http.getToken() },
+						success: res => {
+							dever.debug(res);
+							var data = JSON.parse(res.data);
+							uni.hideLoading();
+							if (data.uploaded) {
+								var backUrl = data.url;
+								if (count > 1) {
+									if (!self.data[key]) {
+										self.data[key] = [];
+									}
+									self.data[key].push(backUrl);
+								} else {
+									self.data[key] = backUrl;
+								}
+								if (callback) {
+									callback(type, self.data[key]);
+								}
+							} else if (data.uploaded == false) {
+								uni.showToast({ title: data.error.message, icon: 'none' });
+							}
+						}
+					});
+				});
+			},
+			fail: () => {
+				uni.showToast({ title: '图片上传失败,请重试', icon: 'none' });
+				uni.hideLoading();
+			}
+		});
+	},
+	
+	upfile : function(e, key, count, callback, ext, title, style) {
+		if (!count) {
+			count = 1;
+		}
+		var type = 1;
+		if (count > 1) {
+			type = 2;
+		}
+		if (!title) {
+			title = '点击这里上传文件';
+		}
+		var self = this;
+		count = parseInt(count);
+		var a = document.createElement('button');
+		a.className = 'file';
+		if (style) {
+			a.style.cssText = style;
+		} else {
+			a.style.cssText = 'padding: 4px 10px;height: 20px;line-height: 20px;position: relative;cursor: pointer;color: #888;background: #fafafa;border: 1px solid #ddd;border-radius: 4px;overflow: hidden;display: inline-block;*display: inline;*zoom: 1;';
+		}
+		var style = 'position: absolute;opacity: 0;filter: alpha(opacity=0);cursor: pointer';
+		a.innerHTML = '<input style="'+style+'" type="file" name="upload_file" id="upload_file" />' + title;
+		e.$refs.input.$el.appendChild(a)
+		var input = document.getElementById('upload_file');
+		
+		input.onchange = (event) => {
+			uni.showLoading();
+			var reader = new FileReader();
+			var type = input.files[0].type;
+			var temp = type.split('/');
+			type = temp[1];
+			//console.info(input.files[0]);
+			if (ext && ext.indexOf(type) == -1) {
+				uni.showToast({ title: '您上传的文件类型错误,请上传带有' + ext + '这些后缀的文件', icon: 'none' });
+			} else {
+				reader.readAsDataURL(input.files[0]);
+				reader.onload = function(){
+					//读取完成后,数据保存在对象的result属性中
+					var pic = this.result;
+					var temp = pic.split('base64,');
+					pic = temp[1];
+					dever.debug(pic);
+					var url = "http://up.qiniu.com/putb64/-1";
+					var xhr = new XMLHttpRequest();
+					xhr.onreadystatechange=function(){
+						if (xhr.readyState==4){
+							dever.debug(xhr.responseText);
+							var data = JSON.parse(xhr.responseText);
+							uni.hideLoading();
+							if (data.uploaded) {
+								var backUrl = data.url;
+								if (count > 1) {
+									if (!self.data[key]) {
+										self.data[key] = [];
+									}
+									self.data[key].push(backUrl);
+								} else {
+									self.data[key] = backUrl;
+								}
+								if (callback) {
+									callback(type, self.data[key], input.files[0]);
+								}
+							} else if (data.uploaded == false) {
+								uni.showToast({ title: data.error.message, icon: 'none' });
+							}
+						}
+					}
+					xhr.open("POST", url, true); 
+					xhr.setRequestHeader("Content-Type", "application/octet-stream");
+					xhr.setRequestHeader("Authorization", "UpToken " + e.token);
+					xhr.send(pic);
+				}
+			}
+			
+		}
+	},
+	
+	qiniu : function(qn, config, key, count, callback) {
+		if (!count) {
+			count = 1;
+		}
+		var type = 1;
+		if (count > 1) {
+			type = 2;
+		}
+		var self = this;
+		count = parseInt(count);
+		uni.chooseImage({
+			count: count,
+			sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album'], //从相册选择
+			success: res => {
+				uni.showLoading();
+				res.tempFilePaths.forEach(function(item, index) {
+					//请求上传接口
+					qn.upload(
+						item,
+						res => {
+							dever.debug(res);
+							var data = res;
+							uni.hideLoading();
+							if (data.uploaded) {
+								var backUrl = data.url;
+								if (count > 1) {
+									if (!self.data[key]) {
+										self.data[key] = [];
+									}
+									self.data[key].push(backUrl);
+								} else {
+									self.data[key] = backUrl;
+								}
+								if (callback) {
+									callback(type, self.data[key]);
+								}
+							} else if (data.uploaded == false) {
+								uni.showToast({ title: data.error.message, icon: 'none' });
+							}
+						},
+						error => {
+							uni.showToast({ title: '图片上传失败,请重试', icon: 'none' });
+							uni.hideLoading();
+						},
+						{
+							region: 'ECN',
+							domain: config.upload,
+							//key: key,
+							uptoken: config.token
+						},
+						res => {
+								//上传进度
+							if(res.progress === 100){
+								//resolve(keys);
+							}
+						}
+					);
+				});
+			},
+			fail: () => {
+				uni.showToast({ title: '图片上传失败,请重试', icon: 'none' });
+				uni.hideLoading();
+			}
+		});
+	},
+	
+	uploadDel : function(key, index, callback) {
+		var self = this;
+		uni.showModal({
+			content: '确定要删除吗?',
+			cancelText: '取消',
+			confirmText: '确定',
+			success: res => {
+				if (res.confirm) {
+					// self.data[key].splice(index, 1);
+					if (callback) {
+						callback(res, index);
+					}
+				}
+			}
+		});
+	}
+}
+
+var dever = {
+	login : 'login/login?refer=-1',
+	web_view : '/dever/pages/web_view',
+	source : 'h5',
+	host : '',
+	doc : false,
+	config : {},
+	vue : false,
+	cur : false,
+	getLoadingState : {},
+	getLoadingCall : false,
+	postLoadingCall : false,
+	pageLoadingCall : false,
+	lastGetUrl : '',
+	showLoad : false,
+	btnText : '确定提交',
+	switchTab : [],
+	switchTabCall : {},
+	
+	//获取当前时间戳
+	curTime : function() {
+		var time = Date.parse(new Date())/1000;
+		return time;
+	},
+	//载入全局配置
+	init : function(url, vue, value, set) {
+		this.vue = vue;
+		vue.prototype.$config = value;
+		var self = this;
+		var config = self.data('dever_config');
+		var state = false;
+		if (config && !set) {
+			self.config = config;
+			vue.prototype.$config = self.config;
+			if (self.config.update_time) {
+				var time = this.curTime();
+				if (!self.config.update_time || time - self.config.update_time >= value.set_update) {
+					state = true;
+				}
+			} else {
+				state = false;
+			}
+			
+		} else {
+			state = true;
+		}
+
+		if (state) {
+			
+			if (url) {
+				self.post(url, {noloading:1}, function(t) {
+					self.config = t;
+					self.config.update_time = self.curTime();
+					self.data('dever_config', self.config);
+					vue.prototype.$config = self.config;
+				});
+			} else {
+				self.config = value;
+				vue.prototype.$config = self.config;
+				self.data('dever_config', self.config);
+			}
+		}
+		
+		return this;
+	},
+	//加载通用的方法
+	common : function(func) {
+		this.source = 'h5';
+		this.host = '';
+		//#ifdef H5
+		this.source = 'h5';
+		this.host = window.location.protocol + '//' + window.location.host + '/#';
+		//#endif
+		//#ifdef APP-PLUS
+		this.source = 'app';
+		//#endif
+		//#ifdef MP-WEIXIN
+		this.source = 'applet';
+		//#endif
+		
+		if (func) {
+			for (var i in func) {
+				this.vue.prototype[i] = func[i];
+			}
+		}
+	},
+	//设置提交的按钮
+	btnLoad : function(cur) {
+		if (!cur) {
+			cur = this.cur;
+		}
+		if (!cur) {
+			return;
+		}
+		cur.btn = this.btn(cur);
+	},
+	//设置提交的按钮
+	btnFinish : function() {
+		var cur = this.cur;
+		if (!cur) {
+			return;
+		}
+		cur.btn = {
+			disabled:false,
+			loading:false,
+			text:this.btnText,
+		};
+	},
+	//设置提交的按钮
+	btn : function(cur, text) {
+		this.cur = cur;
+		if (text) {
+			this.btnText = text;
+		}
+		return {
+			disabled:true,
+			loading:true,
+			text:'加载中...',
+		};
+	},
+	//设置loading
+	setGetLoading : function(yes, no) {
+		this.getLoadingCall = {'yes': yes, 'no' : no};
+	},
+	setPostLoading : function(yes, no) {
+		this.postLoadingCall = {'yes': yes, 'no' : no};
+	},
+	setPageLoading : function(yes, no) {
+		this.pageLoadingCall = {'yes': yes, 'no' : no};
+	},
+	//获取当前route
+	route : function() {
+		var page = this.getPage();
+		if (!page) {
+			return '';
+		}
+		var route = page.route;
+		return route;
+	},
+	//获取当前page
+	getPage : function() {
+		var pages = getCurrentPages();
+		if (pages.length > 0) {
+			return pages[pages.length - 1];
+		} else {
+			return false;
+		}
+	},
+	//loading
+	loading : function(method, options, url) {
+		var state = false;
+		if (method == 'POST') {
+			if (this.postLoadingCall && this.postLoadingCall.yes) {
+				state = true;
+				var callback = this.postLoadingCall.yes;
+				callback(this);
+			}
+		} else if (method == 'page') {
+			state = true;
+			if (options.page && options.page > 1) {
+				state = false;
+			}
+			if (this.pageLoadingCall && this.pageLoadingCall.yes) {
+				state = true;
+				var callback = this.pageLoadingCall.yes;
+				callback(this);
+			}
+		} else {
+			url = this.route();
+			if (this.lastGetUrl && this.lastGetUrl != url && this.getLoadingState[this.lastGetUrl]) {
+				this.getLoadingState[this.lastGetUrl] = false;
+			}
+			if (this.getLoadingState && this.getLoadingState[url]) {
+				state = false;
+			} else {
+				if (this.getLoadingCall && this.getLoadingCall.yes) {
+					state = true;
+					var callback = this.getLoadingCall.yes;
+					callback(this);
+				}
+			}
+		}
+		if (!state) {
+			uni.showLoading({title: '加载中', mask: true});
+			//uni.showNavigationBarLoading();
+			this.showLoad = true;
+		}
+		uni.stopPullDownRefresh();
+	},
+	//取消loading
+	hideLoading : function(method, options, url) {
+		var state = false;
+		if (method == 'POST' || method == 'post') {
+			if (this.postLoadingCall && this.postLoadingCall.no) {
+				state = true;
+				var callback = this.postLoadingCall.no;
+				callback(this);
+			}
+		} else if (method == 'page') {
+			state = true;
+			if (options.page && options.page > 1) {
+				state = false;
+			}
+			if (this.pageLoadingCall && this.pageLoadingCall.no) {
+				state = true;
+				var callback = this.pageLoadingCall.no;
+				callback(this);
+			}
+		} else {
+			url = this.route();
+			if (!this.lastGetUrl || (this.lastGetUrl && this.lastGetUrl != url)) {
+				this.lastGetUrl = url;
+			}
+			if (this.getLoadingState && this.getLoadingState[url]) {
+				state = false;
+			} else {
+				this.getLoadingState[url] = true;
+				if (this.getLoadingCall && this.getLoadingCall.no) {
+					state = true;
+					var callback = this.getLoadingCall.no;
+					callback(this);
+				}
+			}
+		}
+		
+		if (!state && this.showLoad) {
+			uni.hideLoading();
+			this.showLoad = false;
+			this.btnFinish();
+			//uni.hideNavigationBarLoading();
+		}
+	},
+	//data数据获取
+	dataset : function(e) {
+		if (e.currentTarget) {
+			var dataset = e.currentTarget.dataset;
+		} else {
+			var dataset = e.target.dataset;
+		}
+		
+		return dataset;
+	},
+	//view中的页面提示
+	viewAlert : function(e) {
+		if (e) {
+			var dataset = this.dataset(e);
+			var msg = dataset.msg;
+			var icon = dataset.icon;
+			var callback = dataset.callback;
+			
+			this.alert(msg, icon, callback);
+		} else {
+			this.alert('敬请期待');
+		}
+	},
+	//view中的页面跳转
+	viewLocation : function(e) {
+		var page = '';
+		var to = '';
+		var option = '';
+		if (e) {
+			var dataset = this.dataset(e);
+			page = dataset.page;
+			to = dataset.to;
+			option = []
+			
+			for (var i in dataset) {
+				if (i != 'page' && i != 'to') {
+					option.push(i + '=' + dataset[i]);
+				}
+			}
+			if (option) {
+				option = option.join('&');
+			}
+		}
+		
+		if (!page) {
+			uni.navigateBack({});
+		} else {
+			var go = '';
+			if (to) {
+				go = 'to';
+			}
+			if (option) {
+				page = page + '?' + option;
+			}
+			this.location(page, go);
+		}
+	},
+	//页面跳转
+	location : function(path, go) {
+		var self = this;
+		if (path.indexOf('http') > -1) {
+			this.debug('http:' + path);
+			this.data('web_view', path);
+			if (this.source == 'app' || this.source == 'applet') {
+				uni.navigateTo({
+					url: this.web_view
+				})
+			} else {
+				if (!go) {
+					go = '';
+				}
+				if (go && go == 'location') {
+					window.location.href = path;
+				} else {
+					var url = window.location.href;
+					if (go.indexOf('/') != -1) {
+						url = this.host + go
+					} else {
+						url = url + go;
+					}
+					url = window.btoa(url);
+					if (path.indexOf('?') == -1) {
+						path = path + '?';
+					} else {
+						path = path + '&';
+					}
+					window.location.href = path + 'refer=' + url;
+				}
+			}
+		} else {
+			this.debug('navigateTo:' + go + ':' + path);
+			if (this.switchTab.indexOf(path) != -1) {
+				uni.switchTab({
+					url: '/pages/' + path,
+					success: function(e) {
+						if (self.source != 'app' && self.switchTabCall[path]) {
+							var call = self.switchTabCall[path];
+							call(self, e);
+						}
+					}
+				})
+			} else if (go) {
+				uni.redirectTo({
+					url: '/pages/' + path
+				})
+			} else {
+				uni.navigateTo({
+					url: '/pages/' + path
+				})
+			}
+		}
+	},
+	
+	//提示信息
+	alert : function(info, icon, callback) {
+		if (!icon) {
+			icon = 'none'
+		}
+		if (info) {
+			uni.showToast({title: info, icon: icon});
+		}
+		this.debug('alert:' + info)
+		if (callback) {
+			this.debug('callback:' + callback)
+			setTimeout(function(){
+				callback();
+			}, 1000)
+		}
+	},
+	
+	//debug调试
+	debug : function(string) {
+		if (config.base.debug) {
+			this.log(string);
+		}
+	},
+	
+	//记录日志
+	log : function(string) {
+		console.log(string);
+	},
+	
+	//获取token
+	getToken: function() {
+		return http.getToken();
+	},
+	
+	//设置token
+	setToken: function(value) {
+		return http.setToken(value);
+	},
+	
+	//通用的提交数据的方法
+	post : function(url, options, callback, err_callback) {
+		http.request('post', url, options, callback, err_callback);
+	},
+	
+	//通用的获取数据方法
+	get : function(vue, url, options, callback, err_callback) {
+		http.request(vue, url, options, callback, err_callback);
+	},
+	
+	//通用的获取数据方法
+	page : function(config, vue, url, options, callback, err_callback) {
+		page.get(config, vue, url, options, callback, err_callback);
+	},
+
+	//上传
+	upload : function(id, count, callback) {
+		upload.handle(id, count, callback);
+	},
+	
+	//上传
+	qiniu : function(qn, self, id, count, callback) {
+		upload.qiniu(qn, self, id, count, callback);
+	},
+	
+	//上传文件
+	upfile : function(self, key, count, callback, ext, title, style) {
+		upload.upfile(self, key, count, callback, ext, title, style);
+	},
+	
+	//im聊天
+	im : function() {
+		im.init();
+	},
+	
+	//删除上传
+	uploadDel : function(id, index, callback) {
+		upload.uploadDel(id, index, callback);
+	},
+	
+	//验证登录
+	checkLogin : function() {
+		if (!this.getToken()) {
+			//this.hideLoading();
+			if (this.source == 'h5') {
+				this.location(this.login);
+			} else {
+				this.location(this.login, 'go');
+			}
+		}
+	},
+	
+	//数据存储
+	data : function(key, value) {
+		if (value) {
+			if (value == 'del') {
+				uni.removeStorageSync(key);
+				return true;
+			} else {
+				uni.setStorageSync(key, value);
+				return value;
+			}
+		} else {
+			return uni.getStorageSync(key);
+		}
+	},
+	
+	//插入html代码,初始化
+	initHtml : function(doc) {
+		this.doc = doc.$refs.initHtml.$el;
+	},
+	
+	//插入html代码
+	html : function(html) {
+		var self = this;
+		var div = document.createElement('div');
+		div.innerHTML = html;
+		this.doc.appendChild(div);
+		var scripts = div.querySelectorAll('script');
+		return Array.prototype.slice.apply(scripts).reduce((chain, script) => {
+			return chain.then(() => self.runScript(script));
+		}, Promise.resolve());
+	},
+	
+	//微信提醒
+	wxTip : function() {
+		var wx = this.is_weixin();
+		if (wx) {
+			var tip = '<div id="weixin-tip" style="position: fixed; left:0; top:0; background: rgba(0,0,0,0.8); filter:alpha(opacity=80); width: 100%; height:100%; z-index: 100;"  onclick="document.getElementById(\'weixin-tip\').remove()"><p style="text-align: center; margin-top: 10%; padding:0 5%;"><img src="static/dever/live_weixin.png" alt="微信打开" style="max-width: 100%; height: auto;"/></p></div>';
+			this.html(tip);
+			return true;
+		}
+		return false;
+	},
+	
+	is_weixin: function() {
+		if (this.source != 'h5') {
+			return false;
+		}
+		var ua = navigator.userAgent.toLowerCase();  
+		if(ua.match(/MicroMessenger/i)=="micromessenger") {  
+			return true;  
+		} else {  
+			return false;  
+		}
+	},
+	
+	//执行script代码
+	runScript : function(script) {
+		return new Promise((reslove, rejected) => {
+			const newScript = document.createElement('script');
+			newScript.innerHTML = script.innerHTML;
+			const src = script.getAttribute('src');
+			if (src) newScript.setAttribute('src', src);
+			// script 加载完成和错误处理
+			newScript.onload = () => reslove();
+			newScript.onerror = err => rejected();
+			document.head.appendChild(newScript);
+			document.head.removeChild(newScript);
+			if (!src) {
+				// 如果是 inline script 执行是同步的
+				reslove();
+			}
+	  })
+	},
+	
+	//app支付
+	appPayment : function(type, order, callback, errorCallback) {
+		uni.requestPayment({
+		    provider: type,
+		    orderInfo: order, //微信、支付宝订单数据
+		    success: function (res) {
+		        //console.log('success:' + JSON.stringify(res));
+				callback(res);
+		    },
+		    fail: function (err) {
+		        //console.log('fail:' + JSON.stringify(err));
+				errorCallback(err)
+		    }
+		});
+	},
+	
+	//跳转到refer
+	jump : function() {
+		var refer = this.data('login_refer');
+		var id = this.data('invite_id');
+		var type = this.data('invite_type');
+		if (id && refer != 'index/index') {
+			refer = refer + '?id=' + id + '&type=' + type;
+		}
+		this.data('login_refer', 'del');
+		this.data('invite_id', 'del');
+		this.data('invite_type', 'del');
+		if (!refer) {
+			refer = 'index/index';
+		}
+		this.location(refer, 'go');
+	},
+	//转星号
+	xing : function(s) {
+		return s.replace(s, function(sMatch){
+			return sMatch.replace(/./g,"*");
+		});
+	},
+	
+	//检查更新
+	checkUpdate : function() {
+		this.update();
+	},
+	update : function(path, call) {
+		var self = this;
+		var source = this.source;
+		if (source != 'app') {
+			return;
+		}
+		var type = plus.os.name.toLowerCase();
+		//var type = 'android';
+		//var config = this.config.version;
+		
+		self.post('isUpdate', {t:1}, function(res) {
+			if (type == 'android') {
+				res.downloadUrl = res.androidUrl;
+			} else {
+				res.downloadUrl = res.iosUrl;
+			}
+			self.data('dever_update_link', res.downloadUrl);
+			if (plus.runtime.versionCode < res.versionCode) {
+				//强制更新
+				var txt = '正在为您下载更新,下载完成将重启应用';
+				if (res.forceUpdate == true) {
+					self.down(res, res.downloadUrl, txt, type);
+				} else if(call) {
+					call(res, txt, type);
+				} else {
+					uni.showModal({
+					    title: '发现新版本',
+					    content: '有新版本可用 (版本号:' + res.versionName + '),请问您是否更新?',
+					    success: (t) => {
+					        if (t.confirm) {
+								if (path) {
+									self.location(path);
+								} else {
+									self.down(res, res.downloadUrl, txt, type);
+								}
+					        }
+					    }
+					})
+				}
+			}
+		});
+	},
+	uploadCall : function(config, type, packgePath) {
+		// 保存更新记录到stroage,下次启动app时安装更新
+		var self = this;
+		self.data('dever_update', 1);
+		self.data('dever_update_down', packgePath);
+		// 任务完成,关闭下载任务,开始安装
+		plus.runtime.install(packgePath, {force: true}, function() {
+			self.data('dever_update', 2);
+			self.data('dever_update_down', 'del');
+			uni.showModal({
+			  title: '提示',
+			  content: '应用将重启以完成更新',
+			  showCancel: false,
+			  complete: () => {
+				plus.runtime.restart();
+			  }
+			})
+		});
+	},
+	down : function(config, url, txt, type, call) {
+		if (!url) {
+			return false;
+		}
+		if (txt) {
+			this.alert(txt);
+		}
+		if (type != 'android' && !url.match(RegExp(/.wgt/))) {
+			plus.runtime.openURL(url);
+			return false;
+		}
+		var self = this;
+		var packgePath = self.data('dever_update_down');
+		if (packgePath) {
+			self.uploadCall(config, type, packgePath);
+			return false;
+		}
+		var downloadTask = uni.downloadFile({
+		    url: url,
+		    success: (res) => {
+		        if (res.statusCode === 200) {
+		            // 保存下载的安装包
+		            uni.saveFile({
+		                tempFilePath: res.tempFilePath,
+		                success: (res) => {
+		                    var packgePath = res.savedFilePath;
+							if (call) {
+								call();
+							}
+		                    self.uploadCall(config, type, packgePath);
+							downloadTask.abort();
+							downloadTask = null;
+		                }
+		            })
+		        }
+		    }
+		});
+		return downloadTask;
+	},
+	
+	//截取APP退出功能
+	quit : function(page, call) {
+		var self = this;
+		if (self.source == 'app') {
+			var main = plus.android.runtimeMainActivity();
+			//为了防止快速点按返回键导致程序退出重写quit方法改为隐藏至后台 
+			/*
+			plus.runtime.quit = function(){  
+				main.moveTaskToBack(false);  
+			};  
+			*/
+			//重写toast方法如果内容为 ‘再按一次退出应用’ 就隐藏应用,其他正常toast  
+			plus.nativeUI.toast = (function(str){  
+				if(str == '再按一次退出应用') {
+					if (call) {
+						call(main);
+					} else {
+						var webview = page.$mp.page.$getAppWebview();
+						var child = webview.children();
+						child[0].back();
+					}
+					
+					return false;  
+				}else{  
+					self.alert(str);
+				}  
+			});
+		}
+	}
+}
+
+module.exports = dever

+ 31 - 0
lib/dever/pages/index/index.vue

@@ -0,0 +1,31 @@
+<template>
+	<view>
+		<view class="status_bar"></view>
+		<web-view :webview-styles="webviewStyles" src="https://h5.youqianzhuan.vip/" ref="webview"></web-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			webviewStyles: {
+				progress: {
+					color: '#FF3333'
+				}
+			}
+		};
+	},
+	onLoad() {
+		var self = this;
+		this.Dever.update('update/index');
+		this.Dever.quit(this);
+	},
+	methods: {
+	}
+};
+</script>
+
+<style>
+	.status_bar { padding-top:var(--status-bar-height); }
+</style>

+ 31 - 0
lib/dever/pages/update/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <view>
+    <view class="status_bar"></view>
+    <web-view :webview-styles="webviewStyles" src="https://h5.youqianzhuan.vip/" ref="webview"></web-view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      webviewStyles: {
+        progress: {
+          color: '#FF3333'
+        }
+      }
+    };
+  },
+  onLoad() {
+    var self = this;
+    this.Dever.update('update/index');
+    this.Dever.quit(this);
+  },
+  methods: {
+  }
+};
+</script>
+
+<style>
+  .status_bar { padding-top:var(--status-bar-height); }
+</style>

+ 48 - 0
lib/dever/pages/web_error.vue

@@ -0,0 +1,48 @@
+<template>
+	<view>
+	        <view class="text-center text-bold text-xxl">{{msg}}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			code: 401,
+			msg: '请求被拒绝',
+		};
+	},
+	onLoad: function(option) {
+		var self = this;
+		self.code = this.Dever.data('web_error');
+		switch (self.code) {
+			case 401:
+				self.msg = '请求被拒绝';
+				console.error(self. msg + self.code)
+				break;
+			case 404:
+				self.msg = '没有找到页面';
+				console.error(self. msg + self.code)
+				break;
+			case 500:
+				self.msg = '服务器500错误';
+				console.error(self. msg + self.code)
+				break;
+			case 405:
+				self.msg = '请求错误';
+				console.error(self. msg + self.code)
+				break;
+		}
+	},
+	onShow: function() {},
+
+	methods: {
+		callback(e) {
+			
+		},
+	}
+};
+</script>
+
+<style>
+</style>

+ 28 - 0
lib/dever/pages/web_view.vue

@@ -0,0 +1,28 @@
+<template>
+	<view>
+	        <web-view :src="url" @message="callback"></web-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			url: '',
+		};
+	},
+	onLoad: function(option) {
+		this.url = this.Dever.data('web_view');
+	},
+	onShow: function() {},
+
+	methods: {
+		callback(e) {
+			
+		},
+	}
+};
+</script>
+
+<style>
+</style>

binární
lib/dever/static/share/live_weixin.png


binární
lib/dever/static/update/info-circle.png


binární
lib/dever/static/update/sync.png


binární
lib/dever/static/update/up-circle.png


+ 2052 - 0
lib/graceUI/animate.css

@@ -0,0 +1,2052 @@
+/*
+ * 整理自 animate.css 
+ * animate.css -http://daneden.me/animate
+ * Version - 3.7.0
+ * Licensed under the MIT license - http://opensource.org/licenses/MIT
+ * Copyright (c) 2018 Daniel Eden
+*/
+.grace-animate{animation:1s linear;}
+@keyframes bounce {
+	from,
+	20%,
+	53%,
+	80%,
+	to {
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		transform: translate3d(0, 0, 0);
+	}
+	40%,
+	43% {
+		animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+		transform: translate3d(0, -30px, 0);
+	}
+	70% {
+		animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+		transform: translate3d(0, -15px, 0);
+	}
+	90% {
+		transform: translate3d(0, -4px, 0);
+	}
+}
+
+.bounce {
+	animation-name: bounce;
+	transform-origin: center bottom;
+}
+
+@keyframes flash {
+	from,
+	50%,
+	to {
+		opacity: 1;
+	}
+
+	25%,
+	75% {
+		opacity: 0;
+	}
+}
+
+.flash {
+	-webkit-animation-name: flash;
+	animation-name: flash;
+}
+
+
+@keyframes pulse {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.05, 1.05, 1.05);
+		transform: scale3d(1.05, 1.05, 1.05);
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+}
+
+.pulse {
+	-webkit-animation-name: pulse;
+	animation-name: pulse;
+}
+
+@keyframes rubberBand {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+
+	30% {
+		-webkit-transform: scale3d(1.25, 0.75, 1);
+		transform: scale3d(1.25, 0.75, 1);
+	}
+
+	40% {
+		-webkit-transform: scale3d(0.75, 1.25, 1);
+		transform: scale3d(0.75, 1.25, 1);
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.15, 0.85, 1);
+		transform: scale3d(1.15, 0.85, 1);
+	}
+
+	65% {
+		-webkit-transform: scale3d(0.95, 1.05, 1);
+		transform: scale3d(0.95, 1.05, 1);
+	}
+
+	75% {
+		-webkit-transform: scale3d(1.05, 0.95, 1);
+		transform: scale3d(1.05, 0.95, 1);
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+}
+
+.rubberBand {
+	-webkit-animation-name: rubberBand;
+	animation-name: rubberBand;
+}
+
+
+@keyframes shake {
+	from,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	10%,
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0);
+	}
+
+	20%,
+	40%,
+	60%,
+	80% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0);
+	}
+}
+
+.shake {
+	-webkit-animation-name: shake;
+	animation-name: shake;
+}
+
+
+@keyframes headShake {
+	0% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0);
+	}
+
+	6.5% {
+		-webkit-transform: translateX(-6px) rotateY(-9deg);
+		transform: translateX(-6px) rotateY(-9deg);
+	}
+
+	18.5% {
+		-webkit-transform: translateX(5px) rotateY(7deg);
+		transform: translateX(5px) rotateY(7deg);
+	}
+
+	31.5% {
+		-webkit-transform: translateX(-3px) rotateY(-5deg);
+		transform: translateX(-3px) rotateY(-5deg);
+	}
+
+	43.5% {
+		-webkit-transform: translateX(2px) rotateY(3deg);
+		transform: translateX(2px) rotateY(3deg);
+	}
+
+	50% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0);
+	}
+}
+
+.headShake {
+	-webkit-animation-timing-function: ease-in-out;
+	animation-timing-function: ease-in-out;
+	-webkit-animation-name: headShake;
+	animation-name: headShake;
+}
+
+
+@keyframes swing {
+	20% {
+		-webkit-transform: rotate3d(0, 0, 1, 15deg);
+		transform: rotate3d(0, 0, 1, 15deg);
+	}
+
+	40% {
+		-webkit-transform: rotate3d(0, 0, 1, -10deg);
+		transform: rotate3d(0, 0, 1, -10deg);
+	}
+
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 5deg);
+		transform: rotate3d(0, 0, 1, 5deg);
+	}
+
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, -5deg);
+		transform: rotate3d(0, 0, 1, -5deg);
+	}
+
+	to {
+		-webkit-transform: rotate3d(0, 0, 1, 0deg);
+		transform: rotate3d(0, 0, 1, 0deg);
+	}
+}
+
+.swing {
+	-webkit-transform-origin: top center;
+	transform-origin: top center;
+	-webkit-animation-name: swing;
+	animation-name: swing;
+}
+
+
+@keyframes tada {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+
+	10%,
+	20% {
+		-webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
+	}
+
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
+	}
+
+	40%,
+	60%,
+	80% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+}
+
+.tada {
+	-webkit-animation-name: tada;
+	animation-name: tada;
+}
+
+
+@-webkit-keyframes wobble {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	15% {
+		-webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+		transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+	}
+
+	30% {
+		-webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+		transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+	}
+
+	45% {
+		-webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+		transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+	}
+
+	60% {
+		-webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+		transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+	}
+
+	75% {
+		-webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+		transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+@keyframes wobble {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	15% {
+		-webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+		transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+	}
+
+	30% {
+		-webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+		transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+	}
+
+	45% {
+		-webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+		transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+	}
+
+	60% {
+		-webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+		transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+	}
+
+	75% {
+		-webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+		transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.wobble {
+	-webkit-animation-name: wobble;
+	animation-name: wobble;
+}
+
+
+@keyframes jello {
+	from,
+	11.1%,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	22.2% {
+		-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
+		transform: skewX(-12.5deg) skewY(-12.5deg);
+	}
+
+	33.3% {
+		-webkit-transform: skewX(6.25deg) skewY(6.25deg);
+		transform: skewX(6.25deg) skewY(6.25deg);
+	}
+
+	44.4% {
+		-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
+		transform: skewX(-3.125deg) skewY(-3.125deg);
+	}
+
+	55.5% {
+		-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
+		transform: skewX(1.5625deg) skewY(1.5625deg);
+	}
+
+	66.6% {
+		-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
+		transform: skewX(-0.78125deg) skewY(-0.78125deg);
+	}
+
+	77.7% {
+		-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
+		transform: skewX(0.390625deg) skewY(0.390625deg);
+	}
+
+	88.8% {
+		-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
+		transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
+	}
+}
+
+.jello {
+	-webkit-animation-name: jello;
+	animation-name: jello;
+	-webkit-transform-origin: center;
+	transform-origin: center;
+}
+
+
+@keyframes heartBeat {
+	0% {
+		-webkit-transform: scale(1);
+		transform: scale(1);
+	}
+
+	14% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3);
+	}
+
+	28% {
+		-webkit-transform: scale(1);
+		transform: scale(1);
+	}
+
+	42% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3);
+	}
+
+	70% {
+		-webkit-transform: scale(1);
+		transform: scale(1);
+	}
+}
+
+.heartBeat {
+	-webkit-animation-name: heartBeat;
+	animation-name: heartBeat;
+	-webkit-animation-duration: 1.3s;
+	animation-duration: 1.3s;
+	-webkit-animation-timing-function: ease-in-out;
+	animation-timing-function: ease-in-out;
+}
+
+
+@keyframes bounceIn {
+	from,
+	20%,
+	40%,
+	60%,
+	80%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: scale3d(0.3, 0.3, 0.3);
+		transform: scale3d(0.3, 0.3, 0.3);
+	}
+
+	20% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1);
+	}
+
+	40% {
+		-webkit-transform: scale3d(0.9, 0.9, 0.9);
+		transform: scale3d(0.9, 0.9, 0.9);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.03, 1.03, 1.03);
+		transform: scale3d(1.03, 1.03, 1.03);
+	}
+
+	80% {
+		-webkit-transform: scale3d(0.97, 0.97, 0.97);
+		transform: scale3d(0.97, 0.97, 0.97);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1);
+	}
+}
+
+.bounceIn {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: bounceIn;
+	animation-name: bounceIn;
+}
+
+
+@keyframes bounceInDown {
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -3000px, 0);
+		transform: translate3d(0, -3000px, 0);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 25px, 0);
+		transform: translate3d(0, 25px, 0);
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0);
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, 5px, 0);
+		transform: translate3d(0, 5px, 0);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.bounceInDown {
+	-webkit-animation-name: bounceInDown;
+	animation-name: bounceInDown;
+}
+
+
+@keyframes bounceInLeft {
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(-3000px, 0, 0);
+		transform: translate3d(-3000px, 0, 0);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(25px, 0, 0);
+		transform: translate3d(25px, 0, 0);
+	}
+
+	75% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0);
+	}
+
+	90% {
+		-webkit-transform: translate3d(5px, 0, 0);
+		transform: translate3d(5px, 0, 0);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.bounceInLeft {
+	-webkit-animation-name: bounceInLeft;
+	animation-name: bounceInLeft;
+}
+
+
+@keyframes bounceInRight {
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(3000px, 0, 0);
+		transform: translate3d(3000px, 0, 0);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(-25px, 0, 0);
+		transform: translate3d(-25px, 0, 0);
+	}
+
+	75% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0);
+	}
+
+	90% {
+		-webkit-transform: translate3d(-5px, 0, 0);
+		transform: translate3d(-5px, 0, 0);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.bounceInRight {
+	-webkit-animation-name: bounceInRight;
+	animation-name: bounceInRight;
+}
+
+
+@keyframes bounceInUp {
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+		animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 3000px, 0);
+		transform: translate3d(0, 3000px, 0);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0);
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0);
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, -5px, 0);
+		transform: translate3d(0, -5px, 0);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.bounceInUp {
+	-webkit-animation-name: bounceInUp;
+	animation-name: bounceInUp;
+}
+
+
+@keyframes bounceOut {
+	20% {
+		-webkit-transform: scale3d(0.9, 0.9, 0.9);
+		transform: scale3d(0.9, 0.9, 0.9);
+	}
+
+	50%,
+	55% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(0.3, 0.3, 0.3);
+		transform: scale3d(0.3, 0.3, 0.3);
+	}
+}
+
+.bounceOut {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: bounceOut;
+	animation-name: bounceOut;
+}
+
+
+@keyframes bounceOutDown {
+	20% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0);
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0);
+	}
+}
+
+.bounceOutDown {
+	-webkit-animation-name: bounceOutDown;
+	animation-name: bounceOutDown;
+}
+
+
+@keyframes bounceOutLeft {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(20px, 0, 0);
+		transform: translate3d(20px, 0, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0);
+	}
+}
+
+.bounceOutLeft {
+	-webkit-animation-name: bounceOutLeft;
+	animation-name: bounceOutLeft;
+}
+
+
+@keyframes bounceOutRight {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(-20px, 0, 0);
+		transform: translate3d(-20px, 0, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0);
+	}
+}
+
+.bounceOutRight {
+	-webkit-animation-name: bounceOutRight;
+	animation-name: bounceOutRight;
+}
+
+
+@keyframes bounceOutUp {
+	20% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0);
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 20px, 0);
+		transform: translate3d(0, 20px, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0);
+	}
+}
+
+.bounceOutUp {
+	-webkit-animation-name: bounceOutUp;
+	animation-name: bounceOutUp;
+}
+
+
+@keyframes fadeIn {
+	from {
+		opacity: 0;
+	}
+
+	to {
+		opacity: 1;
+	}
+}
+
+.fadeIn {
+	-webkit-animation-name: fadeIn;
+	animation-name: fadeIn;
+}
+
+
+@keyframes fadeInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInDown {
+	-webkit-animation-name: fadeInDown;
+	animation-name: fadeInDown;
+}
+
+
+@keyframes fadeInDownBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInDownBig {
+	-webkit-animation-name: fadeInDownBig;
+	animation-name: fadeInDownBig;
+}
+
+@keyframes fadeInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInLeft {
+	-webkit-animation-name: fadeInLeft;
+	animation-name: fadeInLeft;
+}
+
+
+@keyframes fadeInLeftBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInLeftBig {
+	-webkit-animation-name: fadeInLeftBig;
+	animation-name: fadeInLeftBig;
+}
+
+
+@keyframes fadeInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInRight {
+	-webkit-animation-name: fadeInRight;
+	animation-name: fadeInRight;
+}
+
+
+@keyframes fadeInRightBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInRightBig {
+	-webkit-animation-name: fadeInRightBig;
+	animation-name: fadeInRightBig;
+}
+
+@keyframes fadeInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInUp {
+	-webkit-animation-name: fadeInUp;
+	animation-name: fadeInUp;
+}
+
+
+@keyframes fadeInUpBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.fadeInUpBig {
+	-webkit-animation-name: fadeInUpBig;
+	animation-name: fadeInUpBig;
+}
+
+@keyframes fadeOut {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+	}
+}
+
+.fadeOut {
+	-webkit-animation-name: fadeOut;
+	animation-name: fadeOut;
+}
+
+@keyframes fadeOutDown {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+	}
+}
+
+.fadeOutDown {
+	-webkit-animation-name: fadeOutDown;
+	animation-name: fadeOutDown;
+}
+
+
+@keyframes fadeOutDownBig {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0);
+	}
+}
+
+.fadeOutDownBig {
+	-webkit-animation-name: fadeOutDownBig;
+	animation-name: fadeOutDownBig;
+}
+
+@keyframes fadeOutLeft {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+	}
+}
+
+.fadeOutLeft {
+	-webkit-animation-name: fadeOutLeft;
+	animation-name: fadeOutLeft;
+}
+
+
+@keyframes fadeOutLeftBig {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0);
+	}
+}
+
+.fadeOutLeftBig {
+	-webkit-animation-name: fadeOutLeftBig;
+	animation-name: fadeOutLeftBig;
+}
+
+@keyframes fadeOutRight {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+	}
+}
+
+.fadeOutRight {
+	-webkit-animation-name: fadeOutRight;
+	animation-name: fadeOutRight;
+}
+
+@keyframes fadeOutRightBig {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0);
+	}
+}
+
+.fadeOutRightBig {
+	-webkit-animation-name: fadeOutRightBig;
+	animation-name: fadeOutRightBig;
+}
+
+@keyframes fadeOutUp {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+	}
+}
+
+.fadeOutUp {
+	-webkit-animation-name: fadeOutUp;
+	animation-name: fadeOutUp;
+}
+
+@keyframes fadeOutUpBig {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0);
+	}
+}
+
+.fadeOutUpBig {
+	-webkit-animation-name: fadeOutUpBig;
+	animation-name: fadeOutUpBig;
+}
+
+
+@keyframes flip {
+	from {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0)
+			rotate3d(0, 1, 0, -360deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out;
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px)
+			rotate3d(0, 1, 0, -190deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px)
+			rotate3d(0, 1, 0, -190deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out;
+	}
+
+	50% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px)
+			rotate3d(0, 1, 0, -170deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px)
+			rotate3d(0, 1, 0, -170deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0)
+			rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0)
+			rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+	}
+
+	to {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0)
+			rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+	}
+}
+
+.flip {
+	-webkit-backface-visibility: visible;
+	backface-visibility: visible;
+	-webkit-animation-name: flip;
+	animation-name: flip;
+}
+
+
+@keyframes flipInX {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0;
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		opacity: 1;
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px);
+	}
+}
+
+.flipInX {
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipInX;
+	animation-name: flipInX;
+}
+
+
+@keyframes flipInY {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0;
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		opacity: 1;
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px);
+	}
+}
+
+.flipInY {
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipInY;
+	animation-name: flipInY;
+}
+
+
+@keyframes flipOutX {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px);
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		opacity: 0;
+	}
+}
+
+.flipOutX {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: flipOutX;
+	animation-name: flipOutX;
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+}
+
+
+@keyframes flipOutY {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px);
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		opacity: 0;
+	}
+}
+
+.flipOutY {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipOutY;
+	animation-name: flipOutY;
+}
+
+@keyframes lightSpeedIn {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(-30deg);
+		transform: translate3d(100%, 0, 0) skewX(-30deg);
+		opacity: 0;
+	}
+
+	60% {
+		-webkit-transform: skewX(20deg);
+		transform: skewX(20deg);
+		opacity: 1;
+	}
+
+	80% {
+		-webkit-transform: skewX(-5deg);
+		transform: skewX(-5deg);
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.lightSpeedIn {
+	-webkit-animation-name: lightSpeedIn;
+	animation-name: lightSpeedIn;
+	-webkit-animation-timing-function: ease-out;
+	animation-timing-function: ease-out;
+}
+
+@keyframes lightSpeedOut {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(30deg);
+		transform: translate3d(100%, 0, 0) skewX(30deg);
+		opacity: 0;
+	}
+}
+
+.lightSpeedOut {
+	-webkit-animation-name: lightSpeedOut;
+	animation-name: lightSpeedOut;
+	-webkit-animation-timing-function: ease-in;
+	animation-timing-function: ease-in;
+}
+
+
+@keyframes rotateIn {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, -200deg);
+		transform: rotate3d(0, 0, 1, -200deg);
+		opacity: 0;
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+	}
+}
+
+.rotateIn {
+	-webkit-animation-name: rotateIn;
+	animation-name: rotateIn;
+}
+
+
+@keyframes rotateInDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0;
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+	}
+}
+
+.rotateInDownLeft {
+	-webkit-animation-name: rotateInDownLeft;
+	animation-name: rotateInDownLeft;
+}
+
+@keyframes rotateInDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0;
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+	}
+}
+
+.rotateInDownRight {
+	-webkit-animation-name: rotateInDownRight;
+	animation-name: rotateInDownRight;
+}
+
+
+@keyframes rotateInUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0;
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+	}
+}
+
+.rotateInUpLeft {
+	-webkit-animation-name: rotateInUpLeft;
+	animation-name: rotateInUpLeft;
+}
+
+@keyframes rotateInUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -90deg);
+		transform: rotate3d(0, 0, 1, -90deg);
+		opacity: 0;
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+	}
+}
+
+.rotateInUpRight {
+	-webkit-animation-name: rotateInUpRight;
+	animation-name: rotateInUpRight;
+}
+
+@keyframes rotateOut {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, 200deg);
+		transform: rotate3d(0, 0, 1, 200deg);
+		opacity: 0;
+	}
+}
+
+.rotateOut {
+	-webkit-animation-name: rotateOut;
+	animation-name: rotateOut;
+}
+
+@keyframes rotateOutDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0;
+	}
+}
+
+.rotateOutDownLeft {
+	-webkit-animation-name: rotateOutDownLeft;
+	animation-name: rotateOutDownLeft;
+}
+
+@keyframes rotateOutDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0;
+	}
+}
+
+.rotateOutDownRight {
+	-webkit-animation-name: rotateOutDownRight;
+	animation-name: rotateOutDownRight;
+}
+
+@keyframes rotateOutUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0;
+	}
+}
+
+.rotateOutUpLeft {
+	-webkit-animation-name: rotateOutUpLeft;
+	animation-name: rotateOutUpLeft;
+}
+
+@keyframes rotateOutUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 90deg);
+		transform: rotate3d(0, 0, 1, 90deg);
+		opacity: 0;
+	}
+}
+
+.rotateOutUpRight {
+	-webkit-animation-name: rotateOutUpRight;
+	animation-name: rotateOutUpRight;
+}
+
+
+@keyframes hinge {
+	0% {
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out;
+	}
+
+	20%,
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 80deg);
+		transform: rotate3d(0, 0, 1, 80deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out;
+	}
+
+	40%,
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, 60deg);
+		transform: rotate3d(0, 0, 1, 60deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out;
+		opacity: 1;
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 700px, 0);
+		transform: translate3d(0, 700px, 0);
+		opacity: 0;
+	}
+}
+
+.hinge {
+	-webkit-animation-duration: 2s;
+	animation-duration: 2s;
+	-webkit-animation-name: hinge;
+	animation-name: hinge;
+}
+
+@keyframes jackInTheBox {
+	from {
+		opacity: 0;
+		-webkit-transform: scale(0.1) rotate(30deg);
+		transform: scale(0.1) rotate(30deg);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+	}
+
+	50% {
+		-webkit-transform: rotate(-10deg);
+		transform: rotate(-10deg);
+	}
+
+	70% {
+		-webkit-transform: rotate(3deg);
+		transform: rotate(3deg);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale(1);
+		transform: scale(1);
+	}
+}
+
+.jackInTheBox {
+	-webkit-animation-name: jackInTheBox;
+	animation-name: jackInTheBox;
+}
+
+@keyframes rollIn {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
+		transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.rollIn {
+	-webkit-animation-name: rollIn;
+	animation-name: rollIn;
+}
+
+@keyframes rollOut {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
+		transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
+	}
+}
+
+.rollOut {
+	-webkit-animation-name: rollOut;
+	animation-name: rollOut;
+}
+
+@keyframes zoomIn {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(0.3, 0.3, 0.3);
+		transform: scale3d(0.3, 0.3, 0.3);
+	}
+
+	50% {
+		opacity: 1;
+	}
+}
+
+.zoomIn {
+	-webkit-animation-name: zoomIn;
+	animation-name: zoomIn;
+}
+
+
+@keyframes zoomInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomInDown {
+	-webkit-animation-name: zoomInDown;
+	animation-name: zoomInDown;
+}
+
+@keyframes zoomInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomInLeft {
+	-webkit-animation-name: zoomInLeft;
+	animation-name: zoomInLeft;
+}
+
+@keyframes zoomInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomInRight {
+	-webkit-animation-name: zoomInRight;
+	animation-name: zoomInRight;
+}
+
+@keyframes zoomInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomInUp {
+	-webkit-animation-name: zoomInUp;
+	animation-name: zoomInUp;
+}
+
+@keyframes zoomOut {
+	from {
+		opacity: 1;
+	}
+
+	50% {
+		opacity: 0;
+		-webkit-transform: scale3d(0.3, 0.3, 0.3);
+		transform: scale3d(0.3, 0.3, 0.3);
+	}
+
+	to {
+		opacity: 0;
+	}
+}
+
+.zoomOut {
+	-webkit-animation-name: zoomOut;
+	animation-name: zoomOut;
+}
+
+@keyframes zoomOutDown {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomOutDown {
+	-webkit-animation-name: zoomOutDown;
+	animation-name: zoomOutDown;
+}
+
+@keyframes zoomOutLeft {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(0.1) translate3d(-2000px, 0, 0);
+		transform: scale(0.1) translate3d(-2000px, 0, 0);
+		-webkit-transform-origin: left center;
+		transform-origin: left center;
+	}
+}
+
+.zoomOutLeft {
+	-webkit-animation-name: zoomOutLeft;
+	animation-name: zoomOutLeft;
+}
+
+@keyframes zoomOutRight {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(0.1) translate3d(2000px, 0, 0);
+		transform: scale(0.1) translate3d(2000px, 0, 0);
+		-webkit-transform-origin: right center;
+		transform-origin: right center;
+	}
+}
+
+.zoomOutRight {
+	-webkit-animation-name: zoomOutRight;
+	animation-name: zoomOutRight;
+}
+
+@keyframes zoomOutUp {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+		transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+		animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
+		transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+		animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+	}
+}
+
+.zoomOutUp {
+	-webkit-animation-name: zoomOutUp;
+	animation-name: zoomOutUp;
+}
+
+@keyframes slideInDown {
+	from {
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+		visibility: visible;
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.slideInDown {
+	-webkit-animation-name: slideInDown;
+	animation-name: slideInDown;
+}
+
+@keyframes slideInLeft {
+	from {
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+		visibility: visible;
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.slideInLeft {
+	-webkit-animation-name: slideInLeft;
+	animation-name: slideInLeft;
+}
+
+@keyframes slideInRight {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+		visibility: visible;
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.slideInRight {
+	-webkit-animation-name: slideInRight;
+	animation-name: slideInRight;
+}
+
+@keyframes slideInUp {
+	from {
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+		visibility: visible;
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+}
+
+.slideInUp {
+	-webkit-animation-name: slideInUp;
+	animation-name: slideInUp;
+}
+
+@keyframes slideOutDown {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+	}
+}
+
+.slideOutDown {
+	-webkit-animation-name: slideOutDown;
+	animation-name: slideOutDown;
+}
+
+@keyframes slideOutLeft {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+	}
+}
+
+.slideOutLeft {
+	-webkit-animation-name: slideOutLeft;
+	animation-name: slideOutLeft;
+}
+
+@keyframes slideOutRight {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+	}
+}
+
+.slideOutRight {
+	-webkit-animation-name: slideOutRight;
+	animation-name: slideOutRight;
+}
+
+@keyframes slideOutUp {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+	}
+}
+
+.slideOutUp {
+	-webkit-animation-name: slideOutUp;
+	animation-name: slideOutUp;
+}

+ 71 - 0
lib/graceUI/components/graceActionSheet.vue

@@ -0,0 +1,71 @@
+<template>
+	<view class="graceActionSheet" @tap.stop="closeByShade" @touchmove.stop="stopFun" 
+	:style="{backgroundColor:background, zIndex:zIndex, width:realShow ? '750rpx' : '0rpx'}">
+		<view :class="['graceActionSheetBody', isIpx ? 'grace-ipx-bottom' : '']" 
+		:style="{width:realShow ? width : '0rpx',left:left,borderTopLeftRadius:borderRadius,borderTopRightRadius:borderRadius, zIndex:zIndex+1}" 
+		@tap.stop="stopFun">
+			<view class="graceActionSheetTitle" :style="{color:titleColor}">{{title}}</view>
+			<view class="graceActionSheetList" v-for="(item, index) in items" :key="index" @tap.stop="selected" :data-index="index" 
+			:style="{color:listColor, lineHeight:listLineHeight, fontSize:listFontSize}">{{item}}</view>
+			<view class="graceActionSheetList" :style="{border:'none', color:cancelBtnColor}" @tap.stop="cancel">{{cancelBtnName}}</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		width:{type:String,default:'720rpx'},
+		left:{type:String,default:'15rpx'},
+		background:{type : String,default : 'rgba(0, 0, 0, 0.3)'},
+		borderRadius : {type : String,default : '10rpx'},
+		zIndex:{type : Number,default : 99},
+		title:{type:String,default:''},
+		titleColor:{type:String, default:'#323232'},
+		items:{type:Array, default:function(){return [];}},
+		listColor:{type:String, default:'#3688FF'},
+		listLineHeight:{type:String, default:'100rpx'},
+		listFontSize:{type:String, default:'30rpx'},
+		cancelBtnName:{type:String,default:'取消'},
+		cancelBtnColor:{type:String, default:'#999999'}
+	},
+	data() {
+		return {
+			realShow : false,
+			isIpx:false
+		}
+	},
+	created : function(){
+		// #ifndef APP-PLUS
+		try {
+		    var res = uni.getSystemInfoSync();
+			res.model = res.model.replace(' ', '');
+			res.model = res.model.toLowerCase();
+			if(res.model.indexOf('iphonex') != -1 || res.model.indexOf('iphone11') != -1){
+				this.isIpx = true;
+			}
+		} catch (e){return null;}
+		// #endif
+	},
+	methods:{
+		cancel:function(){
+			this.realShow = false;
+			this.$emit('cancel')
+		},
+		closeByShade: function(){this.cancel();},
+		stopFun : function(){},
+		selected:function (e) {
+			this.$emit('selected', e.currentTarget.dataset.index);
+			this.close();
+		},
+		close:function(){this.realShow = false;},
+		show:function(){this.realShow = true;}
+	}
+}
+</script>
+<style scoped>
+.graceActionSheet{position:fixed; width:100%; height:100%; left:0; top:0;}
+.graceActionSheetBody{background:#FFFFFF; position:absolute; bottom:0; left:0; overflow:hidden;}
+.graceActionSheetTitle{text-align:center; font-size:26rpx; line-height:120rpx; height:120rpx; font-weight:bold; border-bottom:1px solid #F8F8F8;}
+.grace-ipx-bottom{padding-bottom:50rpx !important;}
+.graceActionSheetList{line-height:120rpx; font-weight:600; overflow:hidden; text-align:center; margin:0 30rpx; border-bottom:1px solid #F8F8F8;}
+</style>

+ 118 - 0
lib/graceUI/components/graceAddressPicker.vue

@@ -0,0 +1,118 @@
+<template>
+	<view class="gap" v-if="show" @touchmove.stop="" @tap.stop="" :style="{backgroundColor:background}">
+		<view class="gap-body">
+			<view class="gap-header grace-space-between">
+				<text class="gap-header-btn" :style="{color:cancelTColor}" @tap="close">{{cancelText}}</text>
+				<text class="gap-header-btn" :style="{textAlign:'right', color:confirmColor}" @tap="confirm">{{confirmText}}</text>
+			</view>
+			<picker-view :indicator-style="indicatorStyle" class="gap-main" :value="defaultVal" @change="change">
+				<picker-view-column v-if="level >= 1">
+					<text class="gap-item" v-for="(item, index) in province" :key="index">{{item.label}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="level >= 2">
+					<text class="gap-item" v-for="(item, index) in city[defaultVal[0]]" :key="index">{{item.label}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="level >= 3">
+					<text class="gap-item" v-for="(item, index) in area[defaultVal[0]][defaultVal[1]]" :key="index">{{item.label}}</text>
+				</picker-view-column>
+			</picker-view>
+		</view>
+	</view>
+</template>
+<script>
+import provinceData from '../data/city-data/province.js';
+import cityData from '../data/city-data/city.js';
+import areaData from '../data/city-data/area.js';
+export default {
+	name: "graceAddressPicker",
+	props: {
+		show : { type : Boolean, default : true },
+		background:{ type : String, default : 'rgba(0, 0, 0, 0.5)' },
+		cancelText : { type : String, default : '取消' },
+		cancelTColor : { type : String, default : '#888888' },
+		confirmText : { type : String, default : '确定' },
+		confirmColor : { type : String, default : '#3688FF' },
+		value : { type : Array , default () { return ['', '', ''] }},
+		level : {type : Number, default : 3}
+	},
+	data() {
+		return {
+			indicatorStyle : 'height:35px',
+			province       : provinceData,
+			city           : cityData,
+			area           : areaData,
+			defaultVal     : [0,0,0]
+		}
+	},
+	watch:{
+		value:function(nv, ov){this.setDefault();},
+		defaultVal : function(nv, ov){
+			if(ov[0] != nv[0]){
+				this.defaultVal.splice(1,1,0);
+				this.defaultVal.splice(2,1,0);
+			}else if(ov[1] != nv[1]){
+				this.defaultVal.splice(2,1,0);
+			}
+		}
+	},
+	created() {
+		this.setDefault();
+	},
+	methods: {
+		setDefault:function(){
+			if(this.value[0] == ''){return ;}
+			setTimeout(()=>{
+				this.defaultVal.splice(0, 1, this.arrayIndexOf(this.province, this.value[0]));
+				if(this.value[1] == ''){return ;}
+				setTimeout(()=>{
+					this.defaultVal.splice(1,1, this.arrayIndexOf(this.city[this.defaultVal[0]], this.value[1]));
+					if(this.value[2] == ''){return ;}
+					setTimeout(()=>{
+						this.defaultVal.splice(2,1, this.arrayIndexOf(this.area[this.defaultVal[0]][this.defaultVal[1]], this.value[2]));
+					}, 500);
+				}, 500);
+			}, 500);
+			
+		},
+		arrayIndexOf : function(arr, needFind){
+			var index = 0;
+			for(let i = 0; i < arr.length; i++){if(arr[i].label == needFind){index = i; return i;}}
+			return index;
+		},
+		change : function (e) {
+			var res = e.detail.value;
+			if(!res[0]){res[0] = 0;}
+			if(!res[1]){res[1] = 0;}
+			if(!res[2]){res[2] = 0;}
+			this.defaultVal = res;
+		},
+		confirm:function () {
+			var codes = [
+				provinceData[this.defaultVal[0]].value,
+				cityData[this.defaultVal[0]][this.defaultVal[1]].value,
+				areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].value ? areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].value : 0
+			];
+			var names = [
+				provinceData[this.defaultVal[0]].label,
+				cityData[this.defaultVal[0]][this.defaultVal[1]].label,
+				areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].label ? areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].label : ''
+			];
+			codes = codes.splice(0, this.level);
+			names = names.splice(0, this.level);
+			var res = {codes : codes, names : names, indexs : this.defaultVal};
+			this.$emit('confirm', res);
+		},
+		close : function () {
+			this.$emit('close');
+		}
+	}
+}
+</script>
+<style scoped>
+.gap{position:fixed; width:100%; height:100%; top:0; left:0; bottom:0; z-index:998;}
+.gap-body{background-color:#FFFFFF; position:absolute; z-index:999; bottom:0; left:0; width:100%;}
+.gap-header{padding:25rpx;}
+.gap-header-btn{width:200rpx; line-height:38rpx; height:38rpx; display:block; font-size:28rpx;}
+.gap-main{width:100%; height:280px;}
+.gap-item{display:block; width:100%; height:35px; font-size:12px; line-height:35px; overflow:hidden; text-align:center;}
+</style>

+ 59 - 0
lib/graceUI/components/graceAnalysis.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="grace-editor-show">
+		<view v-for="(item, index) in article" :key="index" class="grace-editor-show-item">
+			<text class="grace-editor-show-txt" v-if="item.type == 'txt'">{{item.content}}</text>
+			<view class="grace-flex-center" v-else-if="item.type == 'center'">
+				<text class="grace-editor-show-center">{{item.content}}</text>
+			</view>
+			<image class="grace-editor-show-image" :src="item.content" 
+			v-else-if="item.type == 'img'" :data-url="item.content" @tap="showImgs" mode="widthFix"></image>
+			<text class="grace-editor-show-quote" v-else-if="item.type == 'quote' || item.type == 'pre'">{{item.content}}</text> 
+			<text class="grace-editor-show-strong" v-else-if="item.type == 'strong'">{{item.content}}</text>
+			<view v-else-if="item.type == 'link'" class="grace-editor-show-link">
+				<graceLink :url="item.content" :title="item.content"></graceLink>
+			</view>
+			<text class="grace-editor-show-spline" v-else-if="item.type == 'spline'">● ● ●</text>
+		</view>
+	</view>
+</template>
+<script>
+import graceLink from "../../graceUI/components/graceLink.vue";
+export default{
+	props:{
+		article:{
+			type : Array,
+			default : function(){return new Array();}
+		}
+	},
+	components:{
+		graceLink
+	},
+	methods: {
+		showImgs:function(e){
+			var currentUrl = e.currentTarget.dataset.url;
+			var imgs       = [];
+			var items      = this.article;
+			for(let i = 0; i < items.length; i++){
+				if(items[i].type ==  'img'){
+					imgs.push(items[i].content);
+				}
+			}
+			uni.previewImage({
+				urls:imgs,
+				current:currentUrl
+			})
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-editor-show{}
+.grace-editor-show-item{line-height:1.8em; letter-spacing:1px; font-size:0;}
+.grace-editor-show-txt{line-height:52rpx; font-size:26rpx; color:#333333;}
+.grace-editor-show-image{width:100%; margin:10rpx 0;}
+.grace-editor-show-quote{margin:10rpx 0; width:92%; color:#333333; background:#F8F8F8; line-height:36rpx; font-size:26rpx; padding:20rpx 4%; display:block;}
+.grace-editor-show-strong{font-weight:bold; width:100%; font-size:30rpx; line-height:45rpx; padding:10px 0; color:#3D3D3D; display:block;}
+.grace-editor-show-spline{padding:10px 0; display:block; text-align:center; line-height:50rpx; color:#8788A3; font-size:20rpx; opacity:0.3;}
+.grace-editor-show-center{text-align:center; font-size:28rpx; color:#333333; line-height:60rpx; padding:15rpx; border-bottom:1px solid #C1C1C1; font-weight:bold;}
+.grace-editor-show-link{line-height:32rpx; padding:10px 0; color:#007AFF;}
+</style>

+ 24 - 0
lib/graceUI/components/graceBorderRadius.vue

@@ -0,0 +1,24 @@
+<template>
+	<view class="grace-border-radius" 
+	:style="{
+		background:background, 
+		borderTopLeftRadius:radius[0],
+		borderTopRightRadius:radius[1],
+		borderBottomRightRadius:radius[2],
+		borderBottomLeftRadius:radius[3]}">
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		radius : {type:Array, default:function(){
+			return['10px', '10px', '10px', '10px']
+		}},
+		background:{type:String, default:""}
+	}
+}
+</script>
+<style>
+.grace-border-radius{width:100%;}
+</style>

+ 69 - 0
lib/graceUI/components/graceBottomDialog.vue

@@ -0,0 +1,69 @@
+<template>
+	<view class="grace-btdialog-shade" v-if="realShow" @tap.stop="closeDialog" @touchmove.stop="stopFun" 
+	:style="{backgroundColor:background, zIndex:zIndex}">
+		<view :class="['dialog', 'gdIn', isIpx ? 'grace-ipx-bottom' : '', isOut ? 'gdOut' : '']" 
+		:style="{width:width,left:left,borderTopLeftRadius:borderRadius,borderTopRightRadius:borderRadius}" 
+		@tap.stop="stopFun">
+			<view><slot name="btns"></slot></view>
+			<view @tap.stop="stopFun"><slot name="content"></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		width:{type:String,default:'750rpx'},
+		left:{type:String,default:'0rpx'},
+		show : {type : Boolean,default : false},
+		background:{type : String,default : 'rgba(0, 0, 0, 0.5)'},
+		borderRadius : {type : String,default : '0rpx'},
+		zIndex:{type : Number,default : 99}
+	},
+	data() {
+		return {
+			realShow:false,
+			isIpx:false,
+			isOut : false
+		}
+	},
+	watch:{
+		show : function (vn, vo) {
+			if(vn){
+				this.realShow = vn;
+			}else{
+				this.isOut = true;
+				setTimeout(()=>{this.realShow = false;}, 120);
+				setTimeout(()=>{this.isOut = false;}, 150);
+			}
+		}
+	},
+	created : function(){
+		this.realShow = this.show;
+		// #ifndef APP-PLUS
+		try {
+		    var res = uni.getSystemInfoSync();
+			res.model = res.model.replace(' ', '');
+			res.model = res.model.toLowerCase();
+			if(res.model.indexOf('iphonex') != -1 || res.model.indexOf('iphone11') != -1){
+				this.isIpx = true;
+			}
+		} catch (e){return null;}
+		// #endif
+	},
+	methods:{
+		closeDialog : function(){
+			this.$emit('closeDialog');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style scoped>
+@keyframes gdIn{ from {bottom:-1000px; } 100% { bottom: 0px; }}
+.gdIn {animation:gdIn 200ms ease-in forwards;}
+@keyframes gdOut{ from {bottom:0px;} 100% { bottom:-1000px; }}
+.gdOut {animation:gdOut 200ms ease-out forwards;}
+.grace-btdialog-shade{position:fixed; width:100%; height:100%; left:0; top:0; z-index:9; background:rgba(0, 0, 0, 0.5);}
+.grace-btdialog-shade .dialog{width:100%; height:auto; background:#FFFFFF; position:absolute; bottom:0; left:0;}
+.grace-ipx-bottom{padding-bottom:50rpx !important;}
+</style>

+ 69 - 0
lib/graceUI/components/graceBoxBanner.vue

@@ -0,0 +1,69 @@
+<template>
+	<view class="grace-box-banner" :style="{background:background, paddingTop:padding, paddingBottom:padding, borderRadius:borderRadius}">
+	    <view class="grace-box-items" v-for="(item, index) in items" :key="index" @tap.stop="taped(index)"
+		:style="{borderRight : index+1 >= items.length ? '0rpx' : border[0]+' '+border[1]+' '+border[2]}">
+	        <view class="grace-box-banner-line" :style="{lineHeight:lineHeight}">
+				<text class="grace-box-banner-line-text1" :style="{color:color[0],fontSize:fontSize[0]}">{{item[0]}}</text>
+				<text class="grace-box-banner-line-text2" :style="{color:color[1],fontSize:fontSize[1]}">{{item[1]}}</text></view>
+	        <view class="grace-box-banner-line">
+				<text :style="{color:color[2],fontSize:fontSize[2]}">{{item[2]}}</text>
+			</view>
+	    </view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		items:{
+			type : Array,
+			default : function () {
+				return []
+			}
+		},
+		color:{
+			type : Array,
+			default : function () {
+				return ['#333333', '#999999', '#999999']
+			}
+		},
+		fontSize:{
+			type : Array,
+			default : function () {
+				return ['36rpx', '24rpx', '24rpx']
+			}
+		},
+		background : {
+			type : String,
+			default : ''
+		},
+		padding:{
+			type : String,
+			default : '25rpx'
+		},
+		border:{
+			type:Array,
+			default:function () {return ["none","",""];}
+		},
+		borderRadius:{
+			type : String,
+			default : '10rpx'
+		},
+		lineHeight:{
+			type : String,
+			default : '1.5em'
+		}
+	},
+	methods:{
+		taped:function (index) {
+			this.$emit('taped', index);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-box-banner{padding:26rpx 0; display:flex; flex-wrap:nowrap; overflow:hidden;}
+.grace-box-items{width:100%;}
+.grace-box-banner-line{overflow:hidden; display:flex; width:100%; justify-content:center; align-items:center; line-height:1.5em; padding-top:8rpx;}
+.grace-box-banner-line-text1{}
+.grace-box-banner-line-text2{margin-left:3px;}
+</style>

+ 65 - 0
lib/graceUI/components/graceCheckBtn.vue

@@ -0,0 +1,65 @@
+<template>
+	<view class="grace-nowrap grace-flex-vcenter" :style="{width:width}" @tap.stop="changeStatus">
+		<view 
+		:class="['grace-check-btn grace-icons', status ? 'grace-check-checked' : '']" 
+		:style="{fontSize:size+'rpx', lineHeight:size+'rpx', color : status ? checkedColor : color}"></view>
+		<view class="grace-check-lable"><slot></slot></view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		width:{
+			type:String,
+			default:'100%'
+		},
+		size : {
+			type : Number,
+			default : 38
+		},
+		color : {
+			type : String,
+			default : '#EEEEEE'
+		},
+		checked : {
+			type : Boolean,
+			default : false
+		},
+		checkedColor : {
+			type : String,
+			default : '#FF0036'
+		},
+		parameter : {
+			type : Array,
+			default : function () {
+				return []
+			}
+		}
+	},
+	data() {
+		return {
+			status : false
+		}
+	},
+	watch: {
+		checked : function (val, old) {
+			this.status = val;
+		}
+	},
+	created : function(){
+		this.status = this.checked;
+	},
+	methods:{
+		changeStatus : function(){
+			this.status = !this.status;
+			this.$emit('change', [this.status, this.parameter]);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-check-btn{color:#999999; flex-shrink:0;}
+.grace-check-btn:after{content:"\e762";}
+.grace-check-checked:after{content:"\e7f8";}
+.grace-check-lable{color:#555555; margin-left:20rpx; font-size:26rpx; width:700rpx;}
+</style>

+ 122 - 0
lib/graceUI/components/graceCheckList.vue

@@ -0,0 +1,122 @@
+<template>
+	<view class="check-list" :style="{height:height+'px'}">
+		<scroll-view scroll-y="true" :style="{height:height+'px'}">
+			<view v-for="(item, index) in dataIn" :key="index" class="check-list-item" :data-index="index" @tap.stop="choose">
+				<view class="check-list-icon grace-icons" v-if="item.checked && batch" :style="{color:checkColor}">&#xe7f8;</view>
+				<view class="check-list-icon grace-icons" v-if="!item.checked && batch" style="color:#D2D2D2;">&#xe762;</view>
+				<view class="check-list-body" :class="[isBorder && index < dataIn.length - 1 ? 'grace-border-b':'']">
+					<image :src="item.img" class="check-list-img" v-if="item.img" mode="widthFix" :style="{width:imgSize[0], height:imgSize[1],borderRadius:imgBordeRadius}"></image>
+					<view class="check-list-content">
+						<view class="check-list-content-title" :style="{fontSize:fontSize, color:itemColor}">{{item.title}}</view>
+						<view class="check-list-content-desc" :style="{color:smallTextColor, fontSize:smallTextSize}">{{item.desc}}</view>
+					</view>
+				</view>
+			</view>
+			<view style="height:120rpx; width:100%;" v-if="batch"></view>
+		</scroll-view>
+		<view class="check-list-footer" v-if="height > 0 && batch">
+			<view class="check-list-icon" @tap.stop="selectAll">
+				<text class="grace-icons" v-if="sedAll" :style="{color:checkColor}">&#xe7f8;</text>
+				<text class="grace-icons" v-if="!sedAll">&#xe762;</text>
+			</view>
+			<view class="check-list-footer-text" @tap.stop="selectAll">全选 {{sedNumbers > 0 ? sedNumbers +'/'+ dataIn.length : ''}}</view>
+			<view class="check-list-footer-btns"><slot></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		height:{type:Number, default:0},
+		lists : {type:Array, default:function(){return [];}},
+		checkColor:{type:String, default:"#3688FF"},
+		imgSize:{type:Array, default:function(){return ['68rpx','68rpx'];}},
+		isBorder:{type:Boolean, default:true},
+		fontSize:{type: String, default: "28rpx"},
+		itemColor:{type:String, default:"#323232"},
+		imgBordeRadius:{type: String, default: "8rpx"},
+		smallTextColor:{type:String, default:"#999999"},
+		smallTextSize:{type:String, default:"24rpx"},
+		batch:{type:Boolean, default:false}
+	},
+	data() {
+		return {
+			dataIn: [],
+			sedAll:false,
+			sedNumbers:0
+		}
+	},
+	created:function(){
+		this.dataIn = this.lists;
+	},
+	watch:{
+		lists:function(val){
+			this.dataIn = val;
+		},
+		dataIn:function(val){
+			var sedNumber = 0;
+			for(let i = 0; i < this.dataIn.length; i++){
+				if(this.dataIn[i].checked){
+					sedNumber++;
+				}
+			}
+			this.sedNumbers = sedNumber;
+			if(sedNumber >= val.length){this.sedAll = true;}else{this.sedAll = false;}
+		}
+	},
+	methods:{
+		choose:function(e){
+			if(!this.batch){return ;}
+			var index = e.currentTarget.dataset.index;
+			if(this.dataIn[index].checked){
+				this.dataIn[index].checked = false;
+				this.dataIn.splice(index, 1, this.dataIn[index]);
+			}else{
+				this.dataIn[index].checked = true;
+				this.dataIn.splice(index, 1, this.dataIn[index]);
+			}
+			this.dataIn = this.dataIn;
+			var sedArr = [];
+			for(let i = 0; i < this.dataIn.length; i++){
+				if(this.dataIn[i].checked){
+					sedArr.push(i);
+				}
+			}
+			this.$emit('change', sedArr);
+		},
+		selectAll : function(){
+			if(this.sedAll){
+				for(let i = 0; i < this.dataIn.length; i++){
+					this.dataIn[i].checked = false;
+					this.dataIn.splice(i, 1, this.dataIn[i]);
+				}
+				this.sedAll = false;
+				this.$emit('change', []);
+			}else{
+				var sedArr = [];
+				for(let i = 0; i < this.dataIn.length; i++){
+					this.dataIn[i].checked = true;
+					this.dataIn.splice(i, 1, this.dataIn[i]);
+					sedArr.push(i);
+				}
+				this.sedAll = true;
+				this.$emit('change', sedArr);
+			}			
+		}
+	}
+}
+</script>
+<style>
+.check-list{position:relative;}
+.check-list-item{display:flex; flex-direction:row; flex-wrap:nowrap; align-items:center; font-size:0; background-color:#FFFFFF; padding:0 25rpx;}
+.check-list-icon{width:60rpx; line-height:60rpx; text-align:center; flex-shrink:0; font-size:36rpx; color:#D2D2D2; font-weight:700; margin-right:15rpx;}
+.check-list-img{flex-shrink:0; border-radius:10rpx; margin-right:28rpx;}
+.check-list-body{width:700rpx; display:flex; flex-wrap:nowrap; flex-direction:row; align-items:center;}
+.check-list-content{width:200rpx; flex:1; overflow:hidden; padding:22rpx 0;}
+.check-list-content-title{line-height:44rpx; font-size:28rpx;}
+.check-list-content-desc{line-height:36rpx; font-size:22rpx;}
+.grace-border-b{border-bottom:1px solid #F4F5F6;}
+.check-list-footer{background:#FFFFFF; position:absolute; z-index:1; left:0; bottom:0; padding:20rpx 25rpx; width:100%; box-sizing:border-box; display:flex; flex-direction:row; flex-wrap:nowrap; align-items:center; flex-shrink:0;}
+.check-list-footer-text{font-size:28rpx; color:#323232; flex-shrink:0;}
+.check-list-footer-btns{width:700rpx; margin-left:28rpx;}
+</style>

+ 122 - 0
lib/graceUI/components/graceCirProgress.vue

@@ -0,0 +1,122 @@
+<template name="graceCirProgress">
+	<view :style="{width:width+'px', height:width+'px'}">
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceCirProgress",
+	props: {
+		canvasId:{
+			type:String, 
+			default:'',
+		},
+		width : {
+			type : Number,
+			default : 200
+		},
+		lineWidth: {
+			type: Number,
+			default: 10
+		},
+		bgColor:{
+			type : String,
+			default : "#F1F1F1"
+		},
+		progressColor:{
+			type: String,
+			default: "#00C777"
+		},
+		fontColor: {
+			type: String,
+			default: "#00C777"
+		},
+		value : {
+			type : Number,
+			default : 0
+		},
+		fontSize : {
+			type: Number,
+			default: 30
+		},
+		speed : {
+			type : Number,
+			default : 20
+		}
+	},
+	data() {
+		return {
+			ctx : null,
+			sets : {},
+			oldVal : 0,
+			canvas : {}
+		}
+	},
+	created:function(){
+		var _self		 = this; 
+		var sets		 = {};
+		sets.center		 = this.width / 2; // 圆心
+		sets.lineWidth   = this.lineWidth; // 外层圆环宽
+		sets.r = (this.width / 2) - (sets.lineWidth / 2); // 背景半径 = 外层
+		sets.lineWidth2  = sets.lineWidth - 2; // 内层圆环宽
+		this.sets        = sets;
+		// 创建画布
+		this.ctx =  wx.createCanvasContext(this.canvasId);
+		this.drawAnimate(this.value);
+	},
+	watch:{
+		value(val, val2){
+			this.drawAnimate(val);
+		}
+	},
+	methods: {
+		drawAnimate: function (num){
+			var _self = this;
+			var step = 0;
+			if (_self.oldVal > num) {
+				for (var i = _self.oldVal; i >= num; i--) {
+					(function(i){setTimeout(function(){_self.draw(i);}, step);})(i);
+					step += _self.speed;
+				}
+			} else {
+				for (var i = _self.oldVal; i <= num; i++) {
+					(function(i){setTimeout(function(){_self.draw(i);}, step);})(i);
+					step += _self.speed;
+				}
+			}
+			_self.oldVal = num;
+		},
+		draw : function (num){
+			if (num < 0) { num = 0;}
+			if(num > 100){num = 100;}
+			// 背景圆
+			this.ctx.setLineWidth(this.sets.lineWidth);
+			this.ctx.setStrokeStyle(this.bgColor);
+			this.ctx.setLineCap('round');
+			this.ctx.beginPath();
+			this.ctx.arc(this.sets.center, this.sets.center, this.sets.r, 0, 2 * Math.PI, false);
+			this.ctx.stroke();
+
+			// 进度圆
+			this.ctx.setLineWidth(this.sets.lineWidth2);
+			this.ctx.setStrokeStyle(this.progressColor);
+			this.ctx.setLineCap('round')
+			this.ctx.beginPath();//开始一个新的路径
+			num = (num / 100);
+			this.ctx.arc(this.sets.center, this.sets.center, this.sets.r, 0 * Math.PI, num * 2 * Math.PI, false);
+			this.ctx.stroke();
+
+			// 文字
+			this.ctx.setFillStyle(this.fontColor);
+			this.ctx.setFontSize(this.fontSize);
+			this.ctx.setTextAlign("center");
+			this.ctx.setTextBaseline('middle');
+			this.ctx.fillText(Math.round(num * 100) + "%", this.sets.center, this.sets.center);
+			
+			this.ctx.draw();
+		}
+	}
+}
+</script>
+<style scoped>
+</style>

+ 125 - 0
lib/graceUI/components/graceCountDown.vue

@@ -0,0 +1,125 @@
+<template name="graceCountDown">
+	<view class="grace-countdown" v-if="show">
+		<view class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, background:bgrColor}" v-if="d > 0">{{d}}</view>
+		<view class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}" v-if="d > 0">{{splitorText[0]}}</view>
+		<view class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, background:bgrColor}" v-if="(h != '00' || zeroShow)">{{h}}</view>
+		<view class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}" v-if="(h != '00' || zeroShow)">{{splitorText[1]}}</view>
+		<view class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, background:bgrColor}">{{i}}</view>
+		<view class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}">{{splitorText[2]}}</view>
+		<view class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, background:bgrColor}">{{s}}</view>
+		<view class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}">{{splitorText[3]}}</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceCountDown",
+	props: {
+		bgrColor: {
+			type: String,
+			default: "#FFFFFF"
+		},
+		borderColor:{
+			type:String,
+			default : "#000000"
+		},
+		fontColor: {
+			type: String,
+			default: "#000000"
+		},
+		fontSize: {
+			type: String,
+			default: "22rpx"
+		},
+		splitorColor: {
+			type: String,
+			default: "#000000"
+		},
+		timer:{
+			type:String,
+			default:""
+		},
+		width : {
+			type:String,
+			default:"40rpx"
+		},
+		splitorText : {
+			type : Array,
+			default : function () {
+				return [':', ':', ':', '']
+			}
+		},
+		show:{type:Boolean, default:true},
+		zeroShow:{type:Boolean, default:true}
+	},
+	data() {
+		return {
+			d        : 0,
+			h        : "",
+			i        : "",
+			s        : "",
+			leftTime : 0,
+			outTimer : null,
+			timerIn  : '',
+			leftTimeNum : 0
+		}
+	},
+	created:function(){
+		this.timerIn = this.timer;
+		this.runbase();
+	},
+	methods: {
+		runbase : function(){
+			var reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;
+			var res = this.timerIn.match(reg);
+			if (res == null){this.outTimer = setTimeout(() => { this.runbase(); }, 1000); return false; }
+			var year = parseInt(res[1]);
+			if (year < 1000) { return false; }
+			var month = parseInt(res[2]);
+			var day = parseInt(res[3]);
+			var h = parseInt(res[4]);
+			if (h < 0 || h > 24) { return false; }
+			var i = parseInt(res[5]);
+			if (i < 0 || i > 60) { return false; }
+			var s = parseInt(res[6]);
+			if (s < 0 || s > 60) { return false; }
+			var leftTime = new Date(year, month - 1, day, h, i, s);
+			this.leftTime = leftTime;
+			clearTimeout(this.outTimer);
+			this.countDown();
+		},
+		countDown: function (){
+			var leftTime = this.leftTime - new Date();
+			this.leftTimeNum = leftTime;
+			if (leftTime > 0) {
+				var day     = parseInt(leftTime / (1000 * 60 * 60 * 24));
+				var hours   = parseInt((leftTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+				var minutes = parseInt((leftTime % (1000 * 60 * 60)) / (1000 * 60));
+				var seconds = parseInt((leftTime % (1000 * 60)) / 1000);
+				if (hours   < 10) { hours = '0' + hours;}
+				if (minutes < 10) { minutes = '0' + minutes; }
+				if (seconds < 10) { seconds = '0' + seconds; }
+				this.h = hours; this.i = minutes; this.s = seconds; this.d = day;
+				this.outTimer = setTimeout(()=>{this.countDown();}, 1000);
+			}else{
+				clearTimeout(this.outTimer);
+				this.h = '00'; this.i = '00'; this.s = '00'; this.d = 0;
+				this.$emit('endDo');
+			}
+		},
+		reSetTimer : function(timer){
+			clearTimeout(this.outTimer);
+			this.timerIn = timer;
+			this.runbase();
+		},
+		getTimeRemaining : function(){
+			if(this.leftTimeNum < 0){return 0;}
+			return parseInt(this.leftTimeNum / 1000);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-countdown{display:flex; flex-wrap:nowrap; justify-content:center;}
+.grace-countdown-splitor{justify-content:center; padding:0 5rpx;}
+.grace-countdown-numbers{border-radius:8rpx; margin:0 5rpx; text-align:center; border:1px solid #000000; font-size:22rpx;}
+</style>

+ 46 - 0
lib/graceUI/components/graceCoupons.vue

@@ -0,0 +1,46 @@
+<template>
+	<view class="grace-coupons" :style="{background:coupon.color}">
+		<view class="left" :style="{background:coupon.ltBg, height:coupon.height}">
+			<view class="number">
+				<view class="show" :style="{color:coupon.color}"><text>{{coupon.unit}}</text>{{coupon.number}}</view>
+				<view class="txt" :style="{color:coupon.color}">{{coupon.txt}}</view>
+			</view>
+			<view class="body">
+				<view class="title">{{coupon.title}}</view>
+				<view class="desc">{{coupon.desc}}</view>
+			</view>
+			<view class="status" v-if="coupon.drawed" :style="{background:coupon.color}">{{coupon.drawed}}</view>
+		</view>
+		<view class="right" :style="{height:coupon.height}">{{coupon.btn}}</view>
+		<view class="sawtooth" :style="{height:coupon.height}">
+			<view class="circular" v-for="(item, index) in '1234567890'" :key="index"></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		coupon : {
+			type : Object,
+			default :  function  () {
+				return {}
+			}
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-coupons{display:flex; flex-wrap:nowrap; border-top-left-radius:10rpx; border-bottom-left-radius:10rpx;}
+.grace-coupons .left{width:100%; height:150rpx; padding-right:10rpx; display:flex; justify-content:space-between; align-items:center; border-radius:8rpx; position:relative;}
+.grace-coupons .left .number{width:32%; display:flex; flex-wrap:wrap; border-right:1px dashed #D2D2D2;}
+.grace-coupons .left .number .show{font-size:50rpx; font-weight:700; line-height:66rpx; text-align:center; width:100%;}
+.grace-coupons .left .number text{color:#828282; font-weight:400; font-size:50rpx;}
+.grace-coupons .left .number .txt{line-height:26rpx; font-size:22rpx; text-align:center; width:100%;}
+.grace-coupons .left .body{width:62%; margin-left:4%; display:flex; flex-wrap:wrap; position:relative;}
+.grace-coupons .left .status{display:inline-block; color:#FFFFFF; padding:0 10px; height:36rpx; line-height:36rpx; font-size:20rpx; position:absolute; z-index:1; right:6px; top:8px; border-radius:36rpx;}
+.grace-coupons .left .body .title{width:100%; font-size:30rpx; line-height:46rpx;}
+.grace-coupons .left .body .desc{color:#999999; margin-top:3px; font-size:24rpx; line-height:36rpx;}
+.grace-coupons .right{width:150rpx; height:150rpx; flex-shrink:0; font-size:30rpx; display:flex; align-items:center; justify-content:center; color:#FFF;}
+.grace-coupons .sawtooth{width:8px; height:150rpx; overflow:hidden; position:relative; flex-shrink:0;}
+.grace-coupons .sawtooth .circular{width:10px; margin:5px; height:10px; border-radius:10px; background:#F9F9F9;}
+</style>

+ 241 - 0
lib/graceUI/components/graceDate.vue

@@ -0,0 +1,241 @@
+<template name="graceDate">
+	<view class="grace-date" v-if="show" :style="{top:top, zIndex:zIndex}" @tap.stop="" @touchmove.stop="">
+		<view class="grace-date-header">
+			<text class="grace-date-header-btn grace-icons" @click="prevYear">&#xe600;&#xe600;</text>
+			<text class="grace-date-header-btn grace-icons" @click="prevMonth">&#xe600;</text>
+			<text class="grace-date-header-date grace-icons">{{cYear}} 年 {{cMonth}} 月</text>
+			<text class="grace-date-header-btn grace-icons" @click="nextMonth">&#xe601;</text>
+			<text class="grace-date-header-btn grace-icons" @click="nextYear">&#xe601;&#xe601;</text>
+		</view>
+		<view class="grace-date-week">
+			<text class="grace-date-weeks" v-for="(item, index) in weeks" :key="index">{{item}}</text>
+		</view>
+		<view class="grace-date-days">
+			<view :class="['grace-date-ditems', currentDayIn == cYear+'-'+cMonthStr+'-'+ item.date ? 'grace-d-current' : '']" 
+			:style="{background: currentDayIn == cYear+'-'+cMonthStr+'-'+ item.date ? activeBgColor : bgColor, borderRadius:borderRadius}" 
+			@click="chooseDate(cYear+'-'+cMonthStr+'-'+item.date, item.date)" v-for="(item, index) in days" :key="index">
+				<text class="grace-date-day" :class="[currentDayIn == (cYear+'-'+cMonthStr+'-'+item.date) ? 'grace-d-current-txt' : '']">{{item.date}}</text>
+				<text class="grace-date-nl" v-if="isLunar" :class="[currentDayIn == (cYear+'-'+cMonthStr+'-'+item.date) ? 'grace-d-current-txt' : '']">{{item.nl}}</text>
+			</view>
+		</view>
+		<view class="grace-nowrap grace-flex-center" style="margin-top:50rpx;" v-if="isTime">
+			<picker class="grace-date-time" mode="time" @change="timechange" :value="currentTimeIn">
+				<text>时间 : {{currentTimeIn}}</text>
+			</picker>
+		</view>
+		<view class="grace-date-btns" v-if="isTime">
+			<text class="grace-date-btns-text" @click="close" style="color:#888888;">关闭</text>
+			<text class="grace-date-btns-text" @click="submit">确定</text>
+		</view>
+	</view>
+</template>
+<script>
+var CalendarData = new Array(100);
+var madd = new Array(12);
+var tgString = "甲乙丙丁戊己庚辛壬癸";
+var dzString = "子丑寅卯辰巳午未申酉戌亥";
+var numString = "一二三四五六七八九十";
+var monString = "正二三四五六七八九十冬腊";
+var weekString = "日一二三四五六";
+var sx = "鼠牛虎兔龙蛇马羊猴鸡狗猪";
+var cYear, cMonth, cDay, TheDate;
+CalendarData = new Array(0xA4B, 0x5164B, 0x6A5, 0x6D4, 0x415B5, 0x2B6, 0x957, 0x2092F, 0x497, 0x60C96, 0xD4A, 0xEA5, 0x50DA9, 0x5AD, 0x2B6, 0x3126E, 0x92E, 0x7192D, 0xC95, 0xD4A, 0x61B4A, 0xB55, 0x56A, 0x4155B, 0x25D, 0x92D, 0x2192B, 0xA95, 0x71695, 0x6CA, 0xB55, 0x50AB5, 0x4DA, 0xA5B, 0x30A57, 0x52B, 0x8152A, 0xE95, 0x6AA, 0x615AA, 0xAB5, 0x4B6, 0x414AE, 0xA57, 0x526, 0x31D26, 0xD95, 0x70B55, 0x56A, 0x96D, 0x5095D, 0x4AD, 0xA4D, 0x41A4D, 0xD25, 0x81AA5, 0xB54, 0xB6A, 0x612DA, 0x95B, 0x49B, 0x41497, 0xA4B, 0xA164B, 0x6A5, 0x6D4, 0x615B4, 0xAB6, 0x957, 0x5092F, 0x497, 0x64B, 0x30D4A, 0xEA5, 0x80D65, 0x5AC, 0xAB6, 0x5126D, 0x92E, 0xC96, 0x41A95, 0xD4A, 0xDA5, 0x20B55, 0x56A, 0x7155B, 0x25D, 0x92D, 0x5192B, 0xA95, 0xB4A, 0x416AA, 0xAD5, 0x90AB5, 0x4BA, 0xA5B, 0x60A57, 0x52B, 0xA93, 0x40E95);
+madd[0] = 0;
+madd[1] = 31;
+madd[2] = 59;
+madd[3] = 90;
+madd[4] = 120;
+madd[5] = 151;
+madd[6] = 181;
+madd[7] = 212;
+madd[8] = 243;
+madd[9] = 273;
+madd[10] = 304;
+madd[11] = 334;
+function GetBit(m, n){return (m >> n) & 1;}
+//农历转换
+function e2c() {
+	TheDate = (arguments.length != 3) ? new Date() : new Date(arguments[0], arguments[1], arguments[2]);
+	var total, m, n, k;
+	var isEnd = false;
+	var tmp = TheDate.getYear();
+	if (tmp < 1900) {tmp += 1900;}
+	total = (tmp - 1921) * 365 + Math.floor((tmp - 1921) / 4) + madd[TheDate.getMonth()] + TheDate.getDate() - 38;
+	if (TheDate.getYear() % 4 == 0 && TheDate.getMonth() > 1) {total++;}
+	for (m = 0; ; m++) {
+		k = (CalendarData[m] < 0xfff) ? 11 : 12;
+		for (n = k; n >= 0; n--) {
+			if (total <= 29 + GetBit(CalendarData[m], n)) {isEnd = true; break;}
+			total = total - 29 - GetBit(CalendarData[m], n);
+		}
+		if (isEnd) break;
+	}
+	cYear = 1921 + m;
+	cMonth = k - n + 1;
+	cDay = total;
+	if (k == 12) {
+		if (cMonth == Math.floor(CalendarData[m] / 0x10000) + 1) {cMonth = 1 - cMonth;}
+		if (cMonth > Math.floor(CalendarData[m] / 0x10000) + 1) {cMonth--;}
+	}
+}
+function GetcDateString() {
+	var tmp = "";
+	tmp += (cDay < 11) ? "初" : ((cDay < 20) ? "十" : ((cDay < 30) ? "廿" : "三十"));
+	if (cDay % 10 != 0 || cDay == 10) {tmp += numString.charAt((cDay - 1) % 10);}
+	return tmp;
+}
+function GetLunarDay(solarYear, solarMonth, solarDay) {
+	if (solarYear < 1921) {return "";}
+	solarMonth = (parseInt(solarMonth) > 0) ? (solarMonth - 1) : 11;
+	e2c(solarYear, solarMonth, solarDay);
+	return GetcDateString();
+}
+export default {
+	name: "graceCountd",
+	props: {
+		show        : {type:Boolean, default:false },
+		currentDate : {type:String, default:"" },
+		isTime      : {type:Boolean, default:true },
+		top : {
+			type : String,
+			// #ifdef H5
+			default : '44px'
+			// #endif
+			// #ifndef H5
+			default : '0'
+			// #endif
+		},
+		zIndex        : {type:String, default:"1"},
+		bgColor       : {type:String, default:"#F6F7F8"},
+		activeBgColor : {type:String, default:"#3688FF"},
+		borderRadius  : {type:String, default:"6rpx"},
+		isLunar       : {type:Boolean, default:true }
+	},
+	data(){
+		return {
+			weeks         : ['一', '二', '三', '四', '五', '六', '日'],
+			cYear         : 2016,
+			cMonth        : 6,
+			cMonthStr     : "06",
+			cDay          : "01",
+			days          : '',
+			currentDayIn : '',
+			currentTimeIn: ''
+		}
+	},
+	methods: {
+		timechange : function(e){
+			this.currentTimeIn = e.detail.value;
+		},
+		getDaysInOneMonth : function (){
+			var d = new Date(this.cYear, this.cMonth, 0);
+			return d.getDate();
+		},
+		getDay : function (){
+			var d = new Date(this.cYear, this.cMonth - 1, 0);
+			return d.getDay();
+		},
+		prevYear : function(){
+			this.cYear = this.cYear - 1;
+			this.changeMonth();
+		},
+		prevMonth : function(){
+			this.cMonth =  this.cMonth - 1;
+			if (this.cMonth < 1) { this.cMonth = 12; this.cYear = this.cYear - 1; }
+			this.cMonthStr = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+			this.changeMonth();
+		},
+		nextMonth : function(){
+			this.cMonth = this.cMonth + 1;
+			if (this.cMonth > 12){this.cMonth = 1; this.cYear = this.cYear + 1;}
+			this.cMonthStr = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+			this.changeMonth();
+		},
+		nextYear : function(){
+			this.cYear = this.cYear + 1;
+			this.changeMonth();
+		},
+		changeMonth:function(){
+			var daysList  = [];
+			var days      = this.getDaysInOneMonth();
+			var startWeek = this.getDay();
+			var forSteps  = 0;
+			for (var i = (0 - startWeek); i < days; i++){
+				if(i >= 0){
+					daysList[forSteps] = {date : i >= 9 ? i + 1 : '0' + (i+1), nl : ''};
+					daysList[forSteps].nl = GetLunarDay(this.cYear, this.cMonth, i + 1);
+				}else{
+					daysList[forSteps] = '';
+				}
+				forSteps++;
+			}
+			this.days    = daysList;
+		},
+		chooseDate: function (sedDate, isday) {
+			if(!isday){return ;}
+			this.currentDayIn = sedDate;
+			if(this.isTime){return ;}
+			this.$emit('changeDate', sedDate);
+		},
+		submit : function(){
+			if(this.isTime){
+				this.$emit('changeDate', this.currentDayIn+' '+this.currentTimeIn);
+			}else{
+				this.$emit('changeDate', this.currentDayIn);
+			}
+		},
+		close : function(){
+			this.$emit("closeDate");
+		},
+		//初始化时间
+		initTime : function(){
+			if(this.currentDate == ''){
+				var dateObj        = new Date();
+				this.cYear         = dateObj.getFullYear();
+				this.cMonth        = dateObj.getMonth() + 1;
+				this.cMonthStr     = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+				this.cDay          = dateObj.getDate();
+				this.cDay          = this.cDay < 10 ? '0' + this.cDay : this.cDay;
+				this.currentDayIn  = this.cYear + '-' + this.cMonthStr + '-' + this.cDay;
+				this.currentTimeIn = '00:00';
+				this.changeMonth();
+			}else{
+				var dates          = this.currentDate.split(' ');
+				if (!dates[1]) { dates[1] = '';}
+				var dayArr         = dates[0].split('-');
+				this.cYear         = Number(dayArr[0]);
+				this.cMonth        = dayArr[1];
+				this.cDay          = dayArr[2];
+				var reg            = new RegExp('^0[0-9]+$');
+				if(reg.test(this.cMonth)){this.cMonth = this.cMonth.substr(1,1);}
+				this.cMonth        = Number(this.cMonth);
+				this.cMonthStr     = this.cMonth < 10 ? '0'+this.cMonth : this.cMonth;
+				this.currentDayIn  = dates[0];
+				this.currentTimeIn = dates[1];
+				this.changeMonth();
+			}
+		}
+	},
+	created:function(){this.initTime();},
+	watch:{
+		currentDate  : function(){this.initTime();}
+	}
+}
+</script>
+<style scoped>
+.grace-date{position:fixed; z-index:1; left:0; top:0; bottom:0; width:730rpx; padding:0 10rpx; display:flex; flex-direction:column; align-items:center; background:#FFFFFF;}
+.grace-date-header{display:flex; justify-content:center; flex-direction:row; text-align:center; margin-top:20rpx;}
+.grace-date-header-btn{font-size:36rpx; line-height:88rpx; padding:0 10rpx; color:#888888;}
+.grace-date-header-date{line-height:88rpx; font-size:36rpx; margin:0 20rpx;}
+.grace-date-week{text-align:center; width:702rpx; display:flex; flex-wrap:nowrap; flex-direction:row;}
+.grace-date-weeks{display:block; width:100rpx; height:80rpx; text-align:center; font-size:32rpx; line-height:80rpx; color:#666666;}
+.grace-date-days{width:702rpx; display:flex; flex-direction:row; flex-wrap:wrap;}
+.grace-date-ditems{width:96rpx; height:96rpx; display:flex; flex-direction:column; align-items:center; justify-content:center; margin:2rpx; background-color:#F6F7F8; border-radius:5rpx;}
+.grace-d-current{background-color:#3688FF;}
+.grace-d-current-txt{color:#FFFFFF !important;}
+.grace-date-day{display:block; width:100%; height:38rpx; line-height:38rpx; text-align:center; font-size:32rpx;}
+.grace-date-nl{display:block; width:100%; height:26rpx; line-height:26rpx; color:#888888; font-size:20rpx; text-align:center;}
+.grace-date-btns{display:flex; flex-wrap:nowrap; flex-direction:row; justify-content:space-between; position:absolute; z-index:1; left:0; bottom:20px; width:100%;}
+.grace-date-btns-text{display:block; color:#3688FF; line-height:100rpx; font-size:30rpx; text-align:center; width:300rpx;}
+.grace-date-time{font-size:30rpx; line-height:100rpx; color:#666666; border-top:1px solid #F6F6F6; border-bottom:1px solid #F6F6F6; padding:0 20rpx;}
+</style>

+ 157 - 0
lib/graceUI/components/graceDateBetween.vue

@@ -0,0 +1,157 @@
+<template>
+	<view>
+		<view class="gui-date-bt-block" :style="{background:weekBg}">
+			<text class="gui-date-bt-item gui-date-bt-week">一</text>
+			<text class="gui-date-bt-item gui-date-bt-week">二</text>
+			<text class="gui-date-bt-item gui-date-bt-week">三</text>
+			<text class="gui-date-bt-item gui-date-bt-week">四</text>
+			<text class="gui-date-bt-item gui-date-bt-week">五</text>
+			<text class="gui-date-bt-item gui-date-bt-week">六</text>
+			<text class="gui-date-bt-item gui-date-bt-week">日</text>
+		</view>
+		<view v-for="(days, daysIndex) in daysData" :key="daysIndex">
+			<view class="gui-date-bt-month-w">
+				<view class="gui-date-bt-month" :style="{backgroundColor:startBg}">{{days[0][0]}}{{unit[0]}}{{days[0][1]}}{{unit[1]}}</view>
+			</view>
+			<view class="gui-date-bt-block">
+				<text class="gui-date-bt-item gui-date-bt-days" v-for="(item, index) in days[1]" :key="index" 
+				:style="{background:item[1] ? sedBg : itemBg, color:item[1]?sedColor:color}" 
+				:data-dt="item[0] == '' ? '' : days[0][0]+''+days[0][1]+''+item[0]" @tap.stop="selectDay">{{item[0]}}<text class="gui-date-bt-start" v-if="sedDays[0] == days[0][0]+''+days[0][1]+''+item[0] || sedDays[1] == days[0][0]+''+days[0][1]+''+item[0]" 
+				:style="{backgroundColor:sedDays[1] == days[0][0]+''+days[0][1]+''+item[0] ? endBg : startBg}">{{item[0]}}</text></text>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+/* 组件百分百原创 拒绝任何代码抄袭及思路抄袭 */
+export default{
+	props:{
+		weekBg:{type:String, default:'#F8F8F8'},
+		unit:{type:Array,default:function(){return [' 年 ',' 月'];}},
+		sedBg:{type:String, default:'rgba(54,136,255,0.88)'},
+		itemBg:{type:String, default:'#FFFFFF'},
+		color:{type:String, default:'#323232'},
+		sedColor:{type:String, default:'#FFFFFF'},
+		startBg:{type:String, default:'rgba(54,136,255,1)'},
+		endBg:{type:String,default:'rgba(54,136,255,1)'},
+		monthNumber:{type:Number,default:2}
+	},
+	data() {
+		return {
+			startYear:'1900',
+			startMonth:"01",
+			endYear:'1900',
+			endMonth:"01",
+			nextdays:[],
+			sedDays:[0,0],
+			btDays :[],
+			nextbtDays:[],
+			daysData : []
+		}
+	},
+	created:function(){
+		this.setMonth(this.currentMonth());
+	},
+	methods:{
+		setBetween:function(days){
+			if(days[1] == 0){days[1] = days[0];}
+			days[0] = Number(days[0]);
+			days[1] = Number(days[1]);
+			this.sedDays = days;
+			var countNumber = 0;
+			var daysNew = [];
+			this.daysData.forEach((itm)=>{
+				var year  = itm[0][0];
+				var month = itm[0][1];
+				var daysIn = [];
+				itm[1].forEach((item)=>{
+					var cDay = year+''+month+''+item[0];
+					cDay = Number(cDay);
+					if(cDay >= days[0] && cDay <= days[1]){
+						item[1] = true; 
+						countNumber++;
+					}else{
+						item[1] = false;
+					}
+					daysIn.push(item);
+				});
+				daysNew.push([[year, month], daysIn]);
+			});
+			this.daysData = daysNew;
+			this.$emit('selectDate', [days, countNumber]);
+		},
+		setMonth:function(month){
+			var reg = /^([0-9]{4}).*([0-9]{2}).*$/;
+			var res = month.match(reg);
+			if(res == null){month = this.currentMonth(); res = month.match(reg);}
+			this.setMonthBase(res);
+		},
+		setMonthBase : function(res){
+			var daysData = [];
+			if(res[2].substr(0,1) == '0'){res[2] = res[2].substr(1);}
+			res[1] = Number(res[1]);
+			res[2] = Number(res[2]);
+			for(let i = 0; i < this.monthNumber; i++){
+				var year  = res[1];
+				var month = res[2] + i;
+				if(month > 12){month = 1; year += 1;}
+				if(month < 10){month = '0'+month;}
+				daysData[i] = [];
+				daysData[i].push([year, month]);
+				var days = this.getDays(year,month);
+				var daysList  = [];
+				for (let ii = (0 - days[1]); ii < days[0]; ii++){
+					if(ii >= 0){
+						daysList.push([ii >= 9 ? ii + 1 : '0' + (ii+1), false]);
+					}else{
+						daysList.push(['',false]);
+					}
+				}
+				daysData[i].push(daysList);
+			}
+			this.daysData = daysData;
+		},
+		currentMonth : function () {
+			var date = new Date();
+			var y = date.getFullYear();
+			var m = date.getMonth() + 1;
+			m = m < 10 ? ('0' + m) : m;
+			return y + '年' + m + '月';
+		},
+		getDays : function(year,month){
+			var d    = new Date(year, month, 0);
+			var days = d.getDate();
+			var d2   = new Date(year, month - 1, 0);
+			var startWeek = d2.getDay();
+			return [days, startWeek];
+		},
+		selectDay:function(e){
+			var day = e.currentTarget.dataset.dt;
+			if(day == ''){return ;}
+			day = Number(day);
+			if(this.sedDays[0] == 0){ this.sedDays[0] = day; }
+			else if(this.sedDays[1] == 0 || this.sedDays[0] == this.sedDays[1]){
+				if(day > this.sedDays[0]){
+					this.sedDays[1] = day;
+				}else if(day < this.sedDays[0]){
+					this.sedDays[1] = this.sedDays[0];
+					this.sedDays[0] = day;
+				}
+			}else{
+				this.sedDays[0] = day;
+				this.sedDays[1] = 0;
+			}
+			this.setBetween(this.sedDays);
+		}
+	}
+}
+</script>
+<style>
+.gui-date-bt-block{width:700rpx; display:flex; flex-direction:row; flex-wrap:wrap;}
+.gui-date-bt-item{width:100rpx; display:block; text-align:center; position:relative;}
+.gui-date-bt-week{line-height:66rpx; height:66rpx; font-size:28rpx;}
+.gui-date-bt-month-w{display:flex; flex-direction:row; flex-wrap:nowrap; margin:15px 0;}
+.gui-date-bt-month{line-height:50rpx; text-align:center; color:#FFFFFF; width:258rpx; border-radius:30rpx; font-size:26rpx;}
+.gui-date-bt-days{line-height:100rpx; height:100rpx; font-size:28rpx;}
+.gui-date-bt-start{display:block; position:absolute; width:100rpx; height:100rpx; z-index:1; left:0; top:0;}
+</style>

+ 191 - 0
lib/graceUI/components/graceDateTime.vue

@@ -0,0 +1,191 @@
+<template>
+	<view>
+		<view @tap.stop="open"><slot></slot></view>
+		<view class="graceDateTime" v-if="show" @touchmove.stop="" @tap.self="close" :style="{backgroundColor:background}"></view>
+		<view class="graceDateTime-body" :class="[show?'gdIn':'gdOut']" @touchmove.stop="" :style="{paddingBottom:paddingBottom}">
+			<view class="graceDateTime-header grace-space-between" v-if="isHeaderBar">
+				<text class="graceDateTime-header-btn" :style="{color:cancelTColor}" @tap="close">{{cancelText}}</text>
+				<text class="graceDateTime-header-btn" :style="{textAlign:'right', color:confirmColor}" @tap="confirm">{{confirmText}}</text>
+			</view>
+			<picker-view :indicator-style="indicatorStyle" class="graceDateTime-main" :value="defaultVal" 
+			@change="change" :style="{height:height}">
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[0]" :key="index">{{item}}{{units[0]}}</text>
+				</picker-view-column>
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[1]" :key="index">{{item}}{{units[1]}}</text>
+				</picker-view-column>
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[2]" :key="index">{{item}}{{units[2]}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="isTime">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[3]" :key="index">{{item}}{{units[3]}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="isTime">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[4]" :key="index">{{item}}{{units[4]}}</text>
+				</picker-view-column>
+				<picker-view-column  v-if="isTime && isSecond">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[5]" :key="index">{{item}}{{units[5]}}</text>
+				</picker-view-column>
+			</picker-view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		background:{ type : String, default : 'rgba(0, 0, 0, 0.5)' },
+		cancelText : { type : String, default : '取消' },
+		cancelTColor : { type : String, default : '#888888' },
+		confirmText : { type : String, default : '确定' },
+		confirmColor : { type : String, default : '#3688FF' },
+		value : { type : String , default:''},
+		isTime : {type : Boolean, default : true},
+		isSecond : {type : Boolean, default : true},
+		startYear : {type : Number, default : 1980},
+		endYear : {type : Number, default : 2050},
+		units : {type : Array , default:function(){return new Array('年','月','日','时','分','秒')}},
+		height:{ type : String, default : '300rpx' },
+		isHeaderBar : {type : Boolean, default : true},
+		paddingBottom:{type : String , default:'0rpx'}
+	},
+	data() {
+		return {
+			show:false,
+			indicatorStyle : 'height:35px',
+			defaultVal     : [0,0,0,0,0,0],
+			sDate:[[],[],[],[],[],[]]
+		}
+	},
+	created() {
+		this.init();
+	},
+	methods: {
+		now : function () {
+			var date = new Date();
+			var y = date.getFullYear();
+			var m = date.getMonth() + 1;
+			m = m < 10 ? ('0' + m) : m;
+			var d = date.getDate();
+			d = d < 10 ? ('0' + d) : d;
+			var h = date.getHours();
+			h = h < 10 ? ('0' + h) : h;
+			var minute = date.getMinutes();
+			var second = date.getSeconds();
+			minute = minute < 10 ? ('0' + minute) : minute;
+			second = second < 10 ? ('0' + second) : second;
+			return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;
+		},
+		arrayIndexOf : function(arr, needFind){
+			var index = -1;
+			for(let i = 0; i < arr.length; i++){if(arr[i] == needFind){index = i; return i;}}
+			return index;
+		},
+		setValue : function (val) {
+			if(val == ''){val = this.now();}
+			var reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;
+			var res = val.match(reg);
+			if(res == null){
+				reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
+				res = val.match(reg);
+				if(res == null){
+					this.setValue(this.now());
+					return ;
+				}
+				res[4] = '00';
+				res[5] = '00';
+				res[6] = '00';
+			}
+			this.setDefaults([res[1],res[2],res[3],res[4],res[5],res[6]]);
+		},
+		setDefaults : function (res) {
+			for(let i = 0; i < res.length; i++){
+				var index = this.arrayIndexOf(this.sDate[i], res[i]);
+				if(index == -1){index = 0;}
+				this.defaultVal.splice(i, 1, index);
+			}
+			this.changeBase(this.defaultVal);
+		},
+		// 初始化组件
+		init:function(){
+			if(this.endYear < this.startYear){this.endYear = this.startYear + 10;}
+			var years     = new Array();
+			for(let i = this.startYear; i <= this.endYear; i++){years.push(i);}
+			var months     = new Array();
+			for(let i = 1; i <= 12; i++){if(i < 10){months.push('0'+i);}else{months.push(i);}}
+			var days     = new Array();
+			for(let i = 1; i <= 31; i++){if(i < 10){days.push('0'+i);}else{days.push(i);}}
+			var hours     =  new Array();
+			for(let i = 0; i < 24; i++){if(i < 10){hours.push('0'+i);}else{hours.push(i);}}
+			var minutes  =  new Array();
+			var seconds  =  new Array();
+			for(let i = 0; i < 60; i++){
+				if(i < 10){minutes.push('0'+i); seconds.push('0'+i);}else{minutes.push(i); seconds.push(i);}
+			}
+			this.sDate = [years, months, days, hours, minutes, seconds];
+			this.$nextTick(()=>{setTimeout(()=>{ this.setValue(this.value);},800);});
+		},
+		change : function (res) {
+			this.changeBase(res.detail.value);
+		},
+		changeBase:function(res){
+			var date = new Date(this.sDate[0][res[0]], this.sDate[1][res[1]], 0);
+			var days = date.getDate();
+			var daysOut = new Array();
+			for(let i = 1; i <= days; i++){
+				if(i < 10){daysOut.push('0'+i);}else{daysOut.push(i);}
+			}
+			this.sDate.splice(2, 1, daysOut);
+			if(res[2] + 1 > days){res[2] = days - 1;}
+			this.defaultVal = res;
+			if(this.isTime){
+				var resdata = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var resdata = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]])
+			}
+			this.$emit('change', resdata);
+		},
+		confirm:function () {
+			if(this.isTime){
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]])
+			}
+			this.$emit('confirm', res);
+			this.show = false;
+		},
+		open : function () {
+			this.show = true;
+		},
+		close : function () {
+			this.show = false;
+		}
+	}
+}
+</script>
+<style scoped>
+.graceDateTime{position:fixed; width:100%; height:100%; top:0; left:0; bottom:0; z-index:998;}
+.graceDateTime-body{background-color:#FFFFFF; position:fixed; z-index:999; bottom:-1000px; left:0; width:100%;}
+.graceDateTime-header{padding:25rpx;}
+.graceDateTime-header-btn{width:200rpx; line-height:38rpx; height:38rpx; display:block; font-size:28rpx;}
+.graceDateTime-main{width:100%;}
+.graceDateTime-item{display:block; width:100%; height:35px; font-size:28rpx; line-height:35px; overflow:hidden; text-align:center;}
+@keyframes gdIn{ from {bottom:-1000px; } 100% { bottom: 0px; }}
+.gdIn {animation:gdIn 200ms ease-in forwards;}
+@keyframes gdOut{ from {bottom:0px;} 100% { bottom:-1000px; }}
+.gdOut {animation:gdOut 200ms ease-out forwards;}
+</style>

+ 90 - 0
lib/graceUI/components/graceDateTimeBetween.vue

@@ -0,0 +1,90 @@
+<template>
+	<view>
+		<view @tap.stop="open"><slot></slot></view>
+		<view class="graceDateTimeBT" v-if="show" @touchmove.stop="" @tap.self="close" :style="{backgroundColor:background}"></view>
+		<view class="graceDateTimeBT-body" :class="[show?'gdIn':'gdOut']" @touchmove.stop="" :style="{paddingBottom:paddingBottom}">
+			<view class="graceDateTimeBT-header grace-space-between">
+				<text class="graceDateTime-header-btn" 
+				:style="{color:cancelTColor}" @tap="close">{{cancelText}}</text>
+				<text class="graceDateTime-header-btn" 
+				:style="{textAlign:'right', color:confirmColor}" @tap="confirm">{{confirmText}}</text>
+			</view>
+			<view>
+				<text class="graceDateTimeBT-text">{{titles[0]}}</text>
+			</view>
+			<view>
+				<graceDateTimeBetweenBase  :value="startValue" :isTime="isTime" @change="chang1" 
+				:isSecond="isSecond" :startYear="startYear" :endYear="endYear" :units="units"></graceDateTimeBetweenBase>
+			</view>
+			<view>
+				<text class="graceDateTimeBT-text">{{titles[1]}}</text>
+			</view>
+			<view>
+				<graceDateTimeBetweenBase :value="endValue" :isTime="isTime" @change="chang2" 
+				:isSecond="isSecond" :startYear="startYear" :endYear="endYear" :units="units"></graceDateTimeBetweenBase>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+import graceDateTimeBetweenBase from "./graceDateTimeBetweenBase.vue";
+export default {
+	components:{
+		graceDateTimeBetweenBase
+	},
+	props: {
+		background:{ type : String, default : 'rgba(0, 0, 0, 0.5)' },
+		cancelText : { type : String, default : '取消' },
+		cancelTColor : { type : String, default : '#888888' },
+		confirmText : { type : String, default : '确定' },
+		confirmColor : { type : String, default : '#3688FF' },
+		startValue : { type : String , default:''},
+		endValue : { type : String , default:''},
+		isTime : {type : Boolean, default : true},
+		isSecond : {type : Boolean, default : true},
+		startYear : {type : Number, default : 1980},
+		endYear : {type : Number, default : 2050},
+		units : {type : Array , default:function(){return new Array('年','月','日','时','分','秒')}},
+		titles : {type : Array , default:function(){return new Array('请选择开始时间','请选择结束时间')}},
+		paddingBottom:{type : String , default:'0rpx'}
+	},
+	data() {
+		return {
+			show:false,
+			indicatorStyle : 'height:35px',
+			defaultVal     : [0,0,0,0,0,0],
+			sDate:[[],[],[],[],[],[]],
+			recDate:[[],[]]
+		}
+	},
+	methods:{
+		open : function () {
+			this.show = true;
+		},
+		close : function () {
+			this.show = false;
+		},
+		confirm : function(){
+			this.show = false;
+			this.$emit('confirm', this.recDate);
+		},
+		chang1 : function(res){
+			this.recDate[0] = res;
+		},
+		chang2 : function(res){
+			this.recDate[1] = res;
+		}
+	}
+}
+</script>
+<style>
+.graceDateTimeBT{position:fixed; width:100%; height:100%; top:0; left:0; bottom:0; z-index:998;}
+.graceDateTimeBT-body{background-color:#FFFFFF; position:fixed; z-index:999; bottom:-1000px; left:0; width:100%;}
+.graceDateTimeBT-header{padding:25rpx;}
+.graceDateTimeBT-header-btn{width:200rpx; line-height:38rpx; height:38rpx; display:block; font-size:28rpx;}
+.graceDateTimeBT-text{display:block; box-sizing:border-box; padding:15rpx; background-color:#FFFFFF; width:100%; line-height:60rpx; height:100rpx; color:#666666; font-size:28rpx;}
+@keyframes gdIn{ from {bottom:-1000px; } 100% { bottom: 0px; }}
+.gdIn {animation:gdIn 200ms ease-in forwards;}
+@keyframes gdOut{ from {bottom:0px;} 100% { bottom:-1000px; }}
+.gdOut {animation:gdOut 200ms ease-out forwards;}
+</style>

+ 153 - 0
lib/graceUI/components/graceDateTimeBetweenBase.vue

@@ -0,0 +1,153 @@
+<template>
+	<picker-view :indicator-style="indicatorStyle" class="graceDateTime-main" :value="defaultVal" @change="change">
+		<picker-view-column>
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[0]" :key="index">{{item}}{{units[0]}}</text>
+		</picker-view-column>
+		<picker-view-column>
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[1]" :key="index">{{item}}{{units[1]}}</text>
+		</picker-view-column>
+		<picker-view-column>
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[2]" :key="index">{{item}}{{units[2]}}</text>
+		</picker-view-column>
+		<picker-view-column v-if="isTime">
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[3]" :key="index">{{item}}{{units[3]}}</text>
+		</picker-view-column>
+		<picker-view-column v-if="isTime">
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[4]" :key="index">{{item}}{{units[4]}}</text>
+		</picker-view-column>
+		<picker-view-column  v-if="isTime && isSecond">
+			<text class="graceDateTime-item" v-for="(item, index) in sDate[5]" :key="index">{{item}}{{units[5]}}</text>
+		</picker-view-column>
+	</picker-view>
+</template>
+<script>
+export default {
+	props: {
+		value : { type : String , default:''},
+		isTime : {type : Boolean, default : true},
+		isSecond : {type : Boolean, default : true},
+		startYear : {type : Number, default : 1980},
+		endYear : {type : Number, default : 2050},
+		units : {type : Array , default:function(){return new Array('年','月','日','时','分','秒')}},
+		height:{ type : String, default : '300rpx' }
+	},
+	data() {
+		return {
+			indicatorStyle : 'height:35px',
+			defaultVal     : [0,0,0,0,0,0],
+			sDate:[[],[],[],[],[],[]]
+		}
+	},
+	created() {this.init();},
+	methods: {
+		now : function () {
+			var date = new Date();
+			var y = date.getFullYear();
+			var m = date.getMonth() + 1;
+			m = m < 10 ? ('0' + m) : m;
+			var d = date.getDate();
+			d = d < 10 ? ('0' + d) : d;
+			var h = date.getHours();
+			h = h < 10 ? ('0' + h) : h;
+			var minute = date.getMinutes();
+			var second = date.getSeconds();
+			minute = minute < 10 ? ('0' + minute) : minute;
+			second = second < 10 ? ('0' + second) : second;
+			return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;
+		},
+		arrayIndexOf : function(arr, needFind){
+			var index = -1;
+			for(let i = 0; i < arr.length; i++){if(arr[i] == needFind){index = i; return i;}}
+			return index;
+		},
+		setValue : function (val) {
+			if(val == ''){val = this.now();}
+			var reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;
+			var res = val.match(reg);
+			if(res == null){
+				reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
+				res = val.match(reg);
+				if(res == null){
+					this.setValue(this.now());
+					return ;
+				}
+				res[4] = '00';
+				res[5] = '00';
+				res[6] = '00';
+			}
+			this.setDefaults([res[1],res[2],res[3],res[4],res[5],res[6]]);
+		},
+		setDefaults : function (res) {
+			for(let i = 0; i < res.length; i++){
+				var index = this.arrayIndexOf(this.sDate[i], res[i]);
+				if(index == -1){index = 0;}
+				this.defaultVal.splice(i, 1, index);
+			}
+			this.changeBase(this.defaultVal);
+		},
+		// 初始化组件
+		init:function(){
+			if(this.endYear < this.startYear){this.endYear = this.startYear + 10;}
+			var years     = new Array();
+			for(let i = this.startYear; i <= this.endYear; i++){years.push(i);}
+			var months     = new Array();
+			for(let i = 1; i <= 12; i++){if(i < 10){months.push('0'+i);}else{months.push(i);}}
+			var days     = new Array();
+			for(let i = 1; i <= 31; i++){if(i < 10){days.push('0'+i);}else{days.push(i);}}
+			var hours     =  new Array();
+			for(let i = 0; i < 24; i++){if(i < 10){hours.push('0'+i);}else{hours.push(i);}}
+			var minutes  =  new Array();
+			var seconds  =  new Array();
+			for(let i = 0; i < 60; i++){
+				if(i < 10){minutes.push('0'+i); seconds.push('0'+i);}else{minutes.push(i); seconds.push(i);}
+			}
+			this.sDate = [years, months, days, hours, minutes, seconds];
+			this.$nextTick(()=>{setTimeout(()=>{ this.setValue(this.value);},800);});
+		},
+		change : function (res) {
+			this.changeBase(res.detail.value);
+		},
+		changeBase:function(res){
+			var date = new Date(this.sDate[0][res[0]], this.sDate[1][res[1]], 0);
+			var days = date.getDate();
+			var daysOut = new Array();
+			for(let i = 1; i <= days; i++){if(i < 10){daysOut.push('0'+i);}else{daysOut.push(i);}}
+			if(res[2] + 1 > days){res[2] = days - 1;}
+			this.sDate.splice(2, 1, daysOut);
+			this.defaultVal = res;
+			if(this.isTime){
+				var resdata = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var resdata = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]])
+			}
+			this.$emit('change', resdata);
+		},
+		confirm:function () {
+			if(this.isTime){
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]])
+			}
+			this.$emit('confirm', res);
+		}
+	}
+}
+</script>
+<style scoped>
+.graceDateTime-main{width:100%; height:300rpx;}
+.graceDateTime-item{display:block; width:100%; height:35px; font-size:28rpx; line-height:35px; overflow:hidden; text-align:center;}
+</style>

+ 106 - 0
lib/graceUI/components/graceDialog.vue

@@ -0,0 +1,106 @@
+<template>
+	<view class="grace-dialog-shade" v-if="show" @tap.stop="closeDialogByShade" @touchmove.stop="stopFun" :style="{backgroundColor:background, zIndex:zIndex}">
+		<view class="grace-dialog gdFadeIn" @tap.stop="stopFun" :style="{width:width, borderRadius:borderRadius}">
+			<view class="grace-dialog-title" v-if="isTitle" 
+			:style="{fontSize:titleSize, color:titleColor, fontWeight:titleWeight?'bold':'', background:titleBg, lineHeight:titleHeight}">{{title}}</view>
+			<view class="grace-dialog-content" @tap.stop="stopFun"><slot name="content"></slot></view>
+			<view class="grace-dialog-close-btn" :style="{color:closeBtnColor, zIndex:zIndex+1}" v-if="isCloseBtn" @tap.stop="closeDialog"></view>
+			<view v-if="isBtns"><slot name="btns"></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceDialog",
+	props: {
+		show : {
+			type : Boolean,
+			default : false
+		},
+		width : {
+			type : String,
+			default : '580rpx'
+		},
+		isCloseBtn: {
+			type : Boolean,
+			default : true
+		},
+		closeBtnColor : {
+			type : String,
+			default : '#FF0036'
+		},
+		isTitle : {
+			type : Boolean,
+			default : true
+		},
+		title : {
+			type : String,
+			default : ''
+		},
+		titleBg : {
+			type : String,
+			default : ''
+		},
+		titleHeight : {
+			type : String,
+			default : '100rpx'
+		},
+		titleWeight : {
+			type : Boolean,
+			default : true
+		},
+		titleSize : {
+			type : String,
+			default : '28rpx'
+		},
+		titleColor : {
+			type : String,
+			default : '#333333'
+		},
+		isBtns : {
+			type : Boolean,
+			default : true
+		},
+		background:{
+			type : String,
+			default : 'rgba(0, 0, 0, 0.5)'
+		},
+		borderRadius : {
+			type : String,
+			default : '6rpx'
+		},
+		zIndex : {
+			type : Number,
+			default : 999
+		},
+		canCloseByShade:{
+			type : Boolean,
+			default : true
+		}
+	},
+	data() {
+		return {
+			
+		}
+	},
+	methods:{
+		closeDialogByShade:function(){
+			if(this.canCloseByShade){this.closeDialog();}
+		},
+		closeDialog : function(){
+			this.$emit('closeDialog');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style scoped>
+@keyframes gdFadeIn{ from { opacity: 0; } 100% { opacity: 1; }}
+.gdFadeIn {animation:gdFadeIn 200ms linear;}
+.grace-dialog-shade{position:fixed; width:100%; height:100%; left:0; top:0; bottom:0; z-index:9991; display:flex; justify-content:center; align-items:center;}
+.grace-dialog{width:580rpx; background:#FFFFFF; position:relative;}
+.grace-dialog-title{line-height:100rpx; font-size:30rpx; text-align:center;}
+.grace-dialog-content{}
+.grace-dialog-close-btn{position:absolute; z-index:9993; right:0px; top:0px; font-size:30rpx; width:80rpx; height:80rpx; line-height:80rpx; text-align:center; font-family:"grace-iconfont";}
+.grace-dialog-close-btn:before{content:"\e632";}
+</style>

+ 68 - 0
lib/graceUI/components/graceDrawer.vue

@@ -0,0 +1,68 @@
+<template>
+	<view class="grace-drawer-shade" v-if="show" @tap.stop="closeDrawer" @touchmove.stop="" :style="{background:background,zIndex:zIndex, top:top+'px'}">
+		<view  :class="['grace-drawer-shade-nav', direction == 'left' ? 'gdSlideLeft' : 'gdSlideRight']" :style="{width:width,padding:padding, zIndex:zIndex+1, background:slotBg}" @tap.stop="stopFun">
+			<slot name="links"></slot>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceDrawer",
+	props: {
+		show : {
+			type : Boolean,
+			default : false
+		},
+		direction : {
+			type : String,
+			default : 'left'
+		},
+		width : {
+			type : String,
+			default : '60%'
+		},
+		background:{
+			type : String,
+			default : 'rgba(0, 0, 0, 0.5)'
+		},
+		slotBg:{
+			type : String,
+			default : 'rgba(255, 255, 255, 1)'
+		},
+		padding :{
+			type : String,
+			default : '30rpx'
+		},
+		zIndex : {
+			type : Number,
+			default : 1
+		},
+		// #ifdef H5
+		top:{
+			type : Number,
+			default : 44
+		},
+		// #endif
+		// #ifndef H5
+		top:{
+			type : Number,
+			default : 0
+		},
+		// #endif
+	},
+	methods:{
+		closeDrawer : function(){
+			this.$emit('closeDrawer');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style>
+@keyframes gdSlideLeft{ from {left:-300px; } 100% { left:0px; }}
+.gdSlideLeft {animation:gdSlideLeft 200ms linear; left:0;}
+@keyframes gdSlideRight{ from {right:-300px; } 100% { right:0px; }}
+.gdSlideRight {animation:gdSlideRight 200ms linear; right:0;}
+.grace-drawer-shade{position:fixed; width:100%; height:100%; top:0; left:0; z-index:1; background:rgba(0, 0, 0, 0.5);}
+.grace-drawer-shade-nav{height:100%; background:#FFFFFF; position:absolute; top:0; z-index:2; box-sizing:border-box;}
+</style>

+ 136 - 0
lib/graceUI/components/graceEditor.vue

@@ -0,0 +1,136 @@
+<template>
+<view class="grace-editor">
+	<textarea class="grace-editor-title" v-model="article.title" placeholder="# 请输入标题" auto-height />
+	<!-- 内容区域 -->
+	<view v-for="(item, index) in article.contents" :key="index" class="grace-editor-items">
+	<!-- 普通文本 -->
+	<textarea class="grace-editor-txt" maxlength="-1" :data-index="index" v-if="item.type == 'txt'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入文本内容" auto-height />
+	<!-- 居中标题 -->
+	<input type="text" class="grace-editor-center" maxlength="-1" :data-index="index" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 输入居中题目" v-else-if="item.type == 'center'" />
+	<!-- 图片 -->
+	<view class="grace-editor-img-wrap" v-else-if="item.type == 'img'">
+		<image :src="item.content" class="grace-editor-img" :data-index="index" mode="widthFix" @tap="deleteItem"></image>
+		<view class="grace-editor-img-error" v-if="item.error">
+			<view class="grace-flex-center">
+				<text class="grace-editor-img-error-text grace-red" style="font-size:32rpx;">图片上传失败!</text>
+			</view>
+			<view class="grace-flex-center grace-margin-top">
+				<text class="grace-editor-img-error-text grace-green">点击发布按钮重新上传</text>
+				<text class="grace-editor-img-error-text">或</text>
+				<text class="grace-editor-img-error-text grace-red" :data-index="index" @tap.stop="deleteItem">删除图片</text>
+			</view>
+		</view>
+	</view>
+	<!-- 引用 -->
+	<textarea class="grace-editor-quote" maxlength="-1" :data-index="index" v-else-if="item.type == 'quote'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入引用内容" auto-height />
+	<!-- 加粗 -->
+	<textarea class="grace-editor-strong" :data-index="index" maxlength="-1" v-else-if="item.type == 'strong'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入加粗内容" auto-height />
+	<!-- 链接 -->
+	<input type="text" class="grace-editor-link" :data-index="index" v-else-if="item.type == 'link'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 输入连接地址" />
+	<!-- 分割 -->
+	<text class="grace-editor-spline" :data-index="index" @tap="deleteItem" v-else-if="item.type == 'spline'">● ● ●</text>
+	</view>
+	<!-- 选项类型选择 -->
+	<view class="grace-space-between grace-editor-menus" :style="{paddingBottom:ipxHeight}">
+		<view class="grace-editor-icons grace-icons icon-txt" data-type="txt" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-center" data-type="center" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-img" data-type="img" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-quote" data-type="quote" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-strong" data-type="strong" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-link" data-type="link" @tap="graceEditorAddItem"></view>
+		<view class="grace-editor-icons grace-icons icon-sp-line" data-type="spline" @tap="graceEditorAddItem"></view>
+	</view>
+	<!-- 底部占位 -->
+	<view style="height:150rpx; width:100%"></view>
+</view>
+</template>
+<script>
+var systemInfo = require('../jsTools/systemInfo.js');
+export default{
+	data() {
+		return {
+			article : {title:'', contents:[]},
+			ipxHeight : 0
+		}
+	},
+	created:function(){
+		var system = systemInfo.info();
+		// #ifdef H5
+		this.ipxHeight = system.iPhoneXBottomHeightRpx + 'rpx';
+		// #endif
+	},
+	methods:{
+		graceEditorAddItem : function(e){
+			var type = e.currentTarget.dataset.type;
+			if(type == 'img'){
+				uni.chooseImage({
+					success:(e)=>{
+						var imgs = e.tempFilePaths;
+						for(let i = 0; i < imgs.length; i++){
+							this.article.contents.push({type:type,content:imgs[i]});
+						}
+						this.returnArt();
+					}
+				});
+			}else{
+				this.article.contents.push({type:type,content:''});
+				this.returnArt();
+			}
+		},
+		graceEditorInput : function(e){
+			var index = e.currentTarget.dataset.index;
+			var val   = e.detail.value;
+			if(val == ''){
+				this.article.contents.splice(index, 1);
+			}else{
+				this.article.contents[index].content = val;
+			}
+			this.returnArt();
+		},
+		deleteItem : function(e){
+			var index = e.currentTarget.dataset.index;
+			uni.showModal({
+				title:"提示",
+				content:"确定要删除项目吗?",
+				success:(e)=>{
+					if(e.confirm){this.article.contents.splice(index, 1); this.returnArt();}
+				}
+			})
+		},
+		returnArt : function(){
+			this.$emit('change', this.article);
+		},
+		setError : function(index){
+			this.article.contents[index].error = true;
+			this.article.contents.splice(index, 1, this.article.contents[index]);
+		},
+		setDefault : function (article) {
+			this.article = article;
+			this.returnArt();
+		}
+	}
+}
+</script>
+<style>
+/* 编辑器 */
+.grace-editor{padding:10rpx 25rpx; width:700rpx;}
+.grace-editor-title{padding:25rpx 0; border-bottom:1px solid #F1F2F3; width:100%; font-size:36rpx; font-weight:bold; line-height:50rpx; background:none; color:#555555;}
+.grace-editor-menus{width:700rpx; position:fixed; z-index:1; left:0; background-color:#F8F8F8; bottom:0; border-top:1px solid #F1F2F3; padding:0 25rpx;}
+.grace-editor-icons{width:700rpx; height:88rpx; color:#666666; line-height:88rpx; text-align:center; font-size:38rpx !important;}
+.grace-editor-items{margin-top:10rpx; display:flex; flex-direction:row; justify-content:center;}
+.grace-editor-txt{background:none; width:700rpx; border:none; font-size:26rpx; line-height:36rpx; margin:15rpx 0;}
+.grace-editor-center{background:none; width:300rpx; text-align:center; border:none; font-size:28rpx; color:#333333; line-height:50rpx; padding:10rpx; border-bottom:1px solid #F5F6F8; font-weight:bold; min-height:50rpx;}
+.grace-editor-img-wrap{width:700rpx; height:300rpx; overflow:hidden;  margin:10rpx 0; position:relative; font-size:0;}
+.grace-editor-img{width:700rpx;}
+.grace-editor-img-error{position:absolute; width:700rpx; height:300rpx; left:0; top:0; background-color:rgba(0,0,0,0.5); display:flex; flex-direction:column; align-items:center; justify-content:center;}
+.grace-editor-img-error-text{font-size:28rpx; margin:0 15rpx; color:#FFFFFF;}
+.grace-editor-quote{border:none; margin:8px 0; border-radius:8rpx; width:660rpx; color:#333333; background:#F8F8F8; line-height:38rpx; padding:15rpx 20rpx; font-size:26rpx;}
+.grace-editor-strong{width:700rpx; background:none; font-weight:bold; border:none; font-size:26rpx; line-height:36rpx; margin:15rpx 0;}
+.grace-editor-link{background:none; width:700rpx; border:none; font-size:26rpx; line-height:36rpx; margin:15rpx 0; color:#007AFF;}
+.grace-editor-spline{width:700rpx; line-height:60rpx; text-align:center; color:#8788A3; font-size:28rpx; opacity:0.6;}
+</style>

+ 33 - 0
lib/graceUI/components/graceEmpty.vue

@@ -0,0 +1,33 @@
+<template>
+	<view class="grace-empty">
+		<view :class="['grace-empty-icon', 'grace-empty-icon'+iconType]" :style="{fontSize:iconSize+'rpx', lineHeight:(iconSize+10)+'rpx', color:iconColor}"></view>
+		<view class="grace-empty-text" v-if="text != ''" :style="{fontSize:textSize+'rpx', lineHeight:(textSize+10)+'rpx', color:textColor}">{{text}}</view>
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		iconType  : { type: Number,  default : 1},
+		iconSize  : { type: Number,  default : 80},
+		iconColor : { type: String,  default : "#999999"},
+		textSize  : { type: Number,  default : 26},
+		textColor : { type: String,  default : "#999999"},
+		text      : { type: String,  default :''}
+	},
+}
+</script>
+<style scoped>
+.grace-empty{width:100%;}
+.grace-empty-icon{text-align:center;}
+.grace-empty-text{padding:20rpx 0; text-align:center;}
+@font-face{
+	font-family:"graceEmptyFont"; 
+	src:url('https://at.alicdn.com/t/font_1348648_qbg88v58i.ttf') format('truetype');
+}
+.grace-empty-icon1:before{font-family: "graceEmptyFont" !important; content:"\e736";}
+.grace-empty-icon2:before{font-family: "graceEmptyFont" !important; content:"\e600";}
+.grace-empty-icon3:before{font-family: "graceEmptyFont" !important; content:"\e704";}
+.grace-empty-icon4:before{font-family: "graceEmptyFont" !important; content:"\e66e";}
+.grace-empty-icon5:before{font-family: "graceEmptyFont" !important; content:"\e644";}
+</style>

+ 12 - 0
lib/graceUI/components/graceEmptyNew.vue

@@ -0,0 +1,12 @@
+<template>
+	<view class="grace-empty">
+		<slot name="img"></slot>
+		<slot name="text"></slot>
+		<slot name="other"></slot>
+	</view>
+</template>
+<script>
+</script>
+<style scoped>
+.grace-empty{display:flex; flex-direction:column; justify-content:center; align-items:center;}
+</style>

+ 59 - 0
lib/graceUI/components/graceFaceGroup.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="grace-face-group" :style="{height:(size + borderWidth * 2 ) +'rpx'}">
+		<view class="grace-face-items" v-if="isAddBtn" 
+		:style="{'z-index':2, width:size+'rpx', height:size+'rpx', border:borderWidth + 'rpx solid ' + borderColor}" 
+		@tap.stop="addBtnClick">
+			<slot></slot>
+		</view>
+		<view class="grace-face-items" 
+		v-for="(item, index) in items" :key="index" 
+		:style="{'z-index':1 - index, 
+			left:isAddBtn ? (space * (index+1)) + 'rpx' : (space * index) + 'rpx', 
+			border:borderWidth + 'rpx solid ' + borderColor, 
+			width:size+'rpx', height:size+'rpx'}">
+			<image :src="item" mode="widthFix"></image>
+		</view>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		items : {
+			type : Array,
+			default : function () {
+				return new Array()
+			}
+		},
+		size : {
+			type : Number,
+			default : 80
+		},
+		space : {
+			type : Number,
+			default : 60
+		},
+		borderWidth : {
+			type : Number,
+			default : 4
+		},
+		borderColor : {
+			type : String,
+			default : '#F5F5F5'
+		},
+		isAddBtn : {
+			type : Boolean,
+			default : false
+		}
+	},
+	methods:{
+		addBtnClick : function () {
+			this.$emit('addBtnClicked');
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-face-group{position:relative;}
+.grace-face-items{width:100rpx; height:100rpx; position:absolute; overflow:hidden; border-radius:100%;}
+.grace-face-items image{width:100%;}
+</style>

+ 44 - 0
lib/graceUI/components/graceFixedMsg.vue

@@ -0,0 +1,44 @@
+<template>
+	<view class="grace-fixed-msg" :style="{width:width+'rpx', bottom:bottom+'rpx'}" v-if="show" @tap.stop="tapme">
+		<text class="grace-fixed-msg-icon grace-icons msg-icon" :style="{color:color}"></text>
+		<text class="grace-fixed-msg-text" :style="{color:color}">{{msg}}</text>
+		<text class="grace-fixed-msg-icon grace-icons icon-arrow-right" :style="{color:color}"></text>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		msg : {
+			type : String,
+			default : ""
+		},
+		color : {
+			type : String,
+			default : "#008CFF"
+		},
+		width : {
+			type : Number,
+			default : 300
+		},
+		bottom : {
+			type : Number,
+			default : 80
+		},
+		show : {
+			type : Boolean,
+			default : true
+		}
+	},
+	methods:{
+		tapme:function(){
+			this.$emit('tapme');
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-fixed-msg{width:300rpx; display:flex; flex-direction:row; flex-wrap:nowrap; padding:18rpx 20rpx; border-radius:50rpx; background:#FFFFFF; box-shadow:0px 0px 16rpx #D5D6D8; position:fixed; bottom:60rpx; left: 50%; transform: translateX(-50%); z-index:9999;}
+.grace-fixed-msg-icon{width:36rpx; height:36rpx; line-height:36rpx; font-size:30rpx; color:#3688FF; flex-shrink:0; opacity:0.6; text-align:center;}
+.msg-icon:before{content: "\e666";}
+.grace-fixed-msg-text{width:500rpx; line-height:36rpx; font-size:28rpx; color:#008CFF; height:36rpx; text-align:center; overflow:hidden;}
+</style>

+ 39 - 0
lib/graceUI/components/graceFlex.vue

@@ -0,0 +1,39 @@
+<template>
+	<view :class="['gui-flex', classesToUse]"><slot></slot></view>
+</template>
+<script>
+export default{
+	props:{
+		classes : {
+			type : Array,
+			default : function(){
+				return new Array()
+			}
+		}
+	},
+	data() {
+		return {classesToUse: ''}
+	},
+	watch:{
+		classes : function(val){this.classesToUse = this.classes.join(' ');}
+	},
+	created:function(){
+		this.classesToUse = this.classes.join(' ');
+	}
+}
+</script>
+<style scoped>
+.gui-flex{display:flex;}
+.row{flex-direction:row;}
+.column{flex-direction:column;}
+.wrap{flex-direction:row; flex-wrap:wrap;}
+.nowrap{flex-direction:row; flex-wrap:nowrap;}
+.space-between{flex-direction:row; justify-content:space-between;}
+.left{justify-content:flex-start;}
+.right{justify-content:flex-end;}
+.xcenter{justify-content:center;}
+.xreverse{flex-direction:row-reverse;}
+.top{align-items:flex-start;}
+.ycenter{align-items:center;}
+.bottom{align-items:flex-end;}
+</style>

+ 54 - 0
lib/graceUI/components/graceFullLoading.vue

@@ -0,0 +1,54 @@
+<template name="graceFullLoading">
+	<view class="grace-full-loading" v-if="graceFullLoading" :style="{background:background}"  @tap.stop="stopFun" @touchmove.stop="stopFun">
+		<view class="content">
+			<view class="image"><image :src="logoUrl" mode="widthFix" :style="{width:size, height:size}"></image></view>
+			<view class="text" :style="{fontSize:fontSize, color:textColor}">{{text}}</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceFullLoading",
+	props: {
+		graceFullLoading: {
+			type    : Boolean,
+			default : false
+		},
+		logoUrl : {
+			type    : String,
+			default : ""
+		},
+		text : {
+			type    : String,
+			default : "Loading ..."
+		},
+		size : {
+			type    : String,
+			default : "138rpx"
+		},
+		fontSize : {
+			type    : String,
+			default : "22rpx"
+		},
+		textColor : {
+			type    : String,
+			default : "#999999"
+		},
+		background : {
+			type    : String,
+			default : 'rgba(255,255,255,1)'
+		}
+	},
+	methods:{
+		stopFun : function(){return false;}
+	}
+}
+</script>
+<style scoped>
+@keyframes grace-fade{0%{opacity:0.6;} 25%{opacity:1;} 50%{opacity:0.6;} 75%{opacity:1;} 100%{opacity:0.6;}}
+.grace-full-loading{width:100%; height:100%; background:#FFFFFF; position:fixed; z-index:9999; left:0; top:0; bottom:0;}
+.grace-full-loading .content{width:300rpx; height:auto; position:absolute; z-index:100; left:50%; top:50%; transform:translate(-50%, -50%); animation:grace-fade 3500ms infinite linear; opacity:0.6; padding:10rpx;}
+.grace-full-loading .image{text-align:center; font-size:0;}
+.grace-full-loading .image image{width:138rpx; height:138rpx; border-radius:138rpx;}
+.grace-full-loading .text{line-height:1.5em; font-size:22rpx; margin-top:6px; text-align:center;}
+</style>

+ 60 - 0
lib/graceUI/components/graceHeader.vue

@@ -0,0 +1,60 @@
+<template>
+	<view>
+		<view class="grace-header" 
+		:style="{background:background, height:height+'rpx', 'padding-top':top+'px', zIndex:zIndex}">
+			<view class="grace-header-main"><slot></slot></view>
+			<view :style="{width:BoundingWidth, height:'10px'}"></view>
+		</view>
+		<!-- 占位 view -->
+		<view :style="{height:viewHeight+'px'}" v-if="isSeize"></view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		background:{
+			type : String,
+			default : "#F8F8F8"
+		},
+		height:{
+			type : Number,
+			default : 90
+		},
+		haveStatusBar:{
+			type : Boolean,
+			default : true
+		},
+		zIndex : {
+			type:Number,
+			default : 99
+		},
+		isSeize: {
+		  type: Boolean,
+		  default: true
+		}
+	},
+	data(){
+		return{
+			top : 0,
+			viewHeight : 0,
+			BoundingWidth :'0px'
+		}
+	},
+	created:function(){
+		// #ifndef MP-ALIPAY
+		var res  = uni.getSystemInfoSync();
+		this.top = res.statusBarHeight;
+		this.viewHeight = this.top + uni.upx2px(this.height);
+		// #ifdef MP-WEIXIN
+		// 小程序胶囊按钮
+		var res  = uni.getMenuButtonBoundingClientRect();
+		this.BoundingWidth = (res.width + 20) + 'px';
+		// #endif
+		// #endif
+	}
+}
+</script>
+<style scoped>
+.grace-header{width:100%; position:fixed; left:0; top:0; z-index:99; height:44px; padding-top:20px; display:flex; flex-direction:row; flex-wrap:nowrap;}
+.grace-header-main{width:300rpx; flex:auto;}
+</style>

+ 48 - 0
lib/graceUI/components/graceHeaderAlert.vue

@@ -0,0 +1,48 @@
+<template name="graceHeaderAlert">
+	<view :class="[show ? 'ganimate-show grace-alert' : 'grace-alert']" :style="{background:background, top : topReal + 'px'}" v-if="showIn">
+		<slot></slot>
+	</view>
+</template>
+<script>
+	export default {
+		name: "graceHeaderAlert",
+		props: {
+			show    : {
+				type : Boolean,
+				default : false
+			},
+			background : {
+				type : String,
+				default : '#F1F2F3'
+			},
+			top:{
+				type : Number,
+				default: 5
+			}
+		},
+		data() {
+			return {
+				showIn : false,
+				topReal : 0
+			}
+		},
+		created:function(){
+			this.topReal = this.top;
+			// #ifdef H5
+			this.topReal += 46;
+			// #endif
+		},
+		watch: {
+			show : function(n , o){
+				this.showIn = true;
+				setTimeout(function(){this.showIn = false}.bind(this), 2000);
+			}
+		}
+	}
+</script>
+<style scoped>
+@keyframes g-show{from {opacity:0; transform:scale3d(0.1, 0.1, 0.1);} to {opacity:1;}}
+.ganimate-show{animation:g-show 200ms linear;}
+.grace-alert{display:flex; width:580rpx; padding:25rpx; border-radius:10rpx; flex-wrap:nowrap; justify-content:center; align-items:center; background:#FFF; position:fixed; z-index:99999; left:60rpx; top:50px;}
+.grace-alert::before{content:""; display:block; background:inherit; filter:blur(6rpx); position:absolute; width:100%; height:100%; top:8rpx; left:8rpx; z-index:-1; opacity:0.38; transform-origin:0 0; border-radius:inherit; transform:scale(1, 1);}
+</style>

+ 271 - 0
lib/graceUI/components/graceIMInput.vue

@@ -0,0 +1,271 @@
+<template name="graceIMMsg">
+	<view>
+		<view class="grace-im-footer" :style="{paddingBottom:paddingB}">
+			<view class="grace-im-menus graceIMFonts icon-voice" 
+				v-if="voiceBtnShow" 
+				@tap="showRec"></view>
+			<view class="grace-im-menus graceIMFonts icon-photograph" @tap="chooseImg"></view>
+			<view class="grace-im-input">
+				<input type="text" v-model="inputMsg" @confirm="sendTextMsg" />
+			</view>
+			<view class="grace-items" style="padding:0 8px; margin-right:6px;" @tap="sendTextMsg">发送</view>
+		</view>
+		<!-- 语音输入 -->
+		<view class="grace-im-record" v-if="recShow">
+			<view class="grace-im-record-txt">
+				{{recTxt}}<text v-if="recing">已录音 : {{recLength}} s</text>
+			</view>
+			<view class="grace-im-record-btn"  @tap="rec" :class="[recing ? 'grace-im-recording' : '']"></view>
+			<view class="grace-im-send-voice" v-if="tmpVoice != ''">
+				<text @tap="playVoice">{{playTxt}}</text>| <text @tap="sendVoiceMsg">发送语音</text>
+			</view>
+			<view class="grace-im-record-close graceIMFonts icon-close" @tap="closeRec" v-if="!recing"></view>
+		</view>
+	</view>
+</template>
+<script>
+var bgAudioMannager = {};
+export default {
+	name: "graceIMMsg",
+	props: {
+		user : {
+			type : Object,
+			default : function(){return {};}
+		},
+		token : {
+			type : String,
+			default : ""
+		},
+		config :{
+			type : Object,
+			default : function(){return {};}
+		}
+	},
+	data() {
+		return {
+			paddingB        : '0',
+			uploading       : false,
+			recShow         : false,
+			recTxt          : "请点击绿色按钮开始录音",
+			inputMsg        : "",
+			recorderManager : null,
+			recing          : false,
+			recLength       : 1,
+			recTimer        : null,
+			tmpVoice        : '',
+			voiceLen        : 0,
+			voiceBtnShow    : false,
+			// 播放相关
+			player          : null,
+			playTxt         : "试听语音"
+		}
+	},
+	created : function(){
+		var _self = this;
+		// #ifndef H5
+		this.voiceBtnShow = true;
+		this.recorderManager = uni.getRecorderManager();
+		this.recorderManager.onStop((res) => {
+			_self.tmpVoice     = res.tempFilePath;
+			this.recing = false;
+			this.recTxt   =  "... 已录音 "+this.recLength+"s, 点击绿色按钮重新录音 ...";
+			clearInterval(this.recTimer);
+		});
+		this.recorderManager.onError(function () {
+			uni.showToast({ title: '录音失败', icon: 'none' });
+			_self.recing = false;
+			_self.recTxt   = "请点击绿色按钮开始录音",
+			clearInterval(_self.recTimer);
+		});
+		// #endif
+		// #ifdef MP
+		try {
+		    var res = uni.getSystemInfoSync();
+			res.model = res.model.replace(' ', '');
+			res.model = res.model.toLowerCase();
+			if(res.model.indexOf('iphonex') != -1 || res.model.indexOf('iphone11') != -1){
+				this.paddingB = uni.upx2px(50)+'px';
+			}
+		} catch (e){return null;}
+		// #endif
+	},
+	methods:{
+		// 录音
+		rec : function(){
+			if (this.recing){
+				this.recorderManager.stop();
+				this.recing = false;
+				this.recTxt   =  "... 已录音 "+this.recLength+"s, 点击绿色按钮重新录音 ...";
+				clearInterval(this.recTimer);
+			} else {
+				this.recorderManager.start({duration:60000, format:'mp3' });
+				this.recing     = true;
+				this.recTxt     =  "... 正在录音 ...";
+				this.recLength  = 1;
+				this.recTimer   = setInterval(function(){this.recLength++;}.bind(this), 1000);
+			}
+		},
+		// 播放录音
+		playVoice : function(){
+			var _self = this;
+			if(this.playTxt == '正在播放'){
+				this.playTxt = '试听语音';
+				bgAudioMannager.stop();
+				return ;
+			}
+			bgAudioMannager = uni.getBackgroundAudioManager();
+			this.playTxt      = '正在播放';
+			bgAudioMannager.src   = this.tmpVoice;
+			bgAudioMannager.onEnded(function(){
+				_self.playTxt = "试听语音";
+			});
+			bgAudioMannager.onStop(function(){
+				_self.playTxt = "试听语音";
+			});
+			
+		},
+		// 发送录音
+		sendVoiceMsg : function(){
+			var _self = this;
+			if (_self.tmpVoice == '') {
+				uni.showToast({ title: "请先录制一段语音", icon: "none" });
+				return;
+			}
+			// 关闭界面
+			this.recShow = false;
+			uni.showLoading({ title:"正在发送" });
+			const uploadTask = uni.uploadFile({
+				url      : _self.config.serverUrl+'?c=uploadFile&token='+_self.token,
+				filePath : _self.tmpVoice,
+				name     : 'gChatUploadFile',
+				success   : function (uploadFileRes) {
+					var res = JSON.parse(uploadFileRes.data);
+					if(res.status == 'ok'){
+						var msg         = _self.user;
+						msg.type        = 'msg';
+						msg.contentType = 'voice';
+						msg.content     = res.msg;
+						msg.length      = _self.recLength;
+						uni.sendSocketMessage({
+							data : JSON.stringify(msg),
+							complete:function(){
+								setTimeout(function(){
+									_self.uploading = false;
+									uni.hideLoading();
+									_self.tmpVoice = '';
+								}, 500);
+							},
+							fail:function(e){
+								_self.uploading = false;
+								console.log('消息发送失败');
+							}
+						});
+					}else{
+						setTimeout(function(){uni.hideLoading();}, 1000);
+					}
+					
+				},
+				fail:function(e){
+					_self.uploading = false;
+					console.log(e);
+				}
+			});
+		},
+		// 展示录音界面
+		showRec : function(){this.recShow = true;},
+		// 关闭录音界面
+		closeRec: function (){this.recShow = false;},
+		// 发送文本消息
+		sendTextMsg: function () {
+			if (this.inputMsg < 1) {return false;}
+			var msg         = this.user;
+			msg.type        = 'msg';
+			msg.contentType = 'txt';
+			msg.content     = this.inputMsg;
+			msg.token       = this.token;
+			console.log(msg,33);
+			uni.sendSocketMessage({
+				data : JSON.stringify(msg),
+				fail:function(e){console.log('消息发送失败 ...');}
+			});
+			this.inputMsg = '';
+		},
+		// 选择图片
+		chooseImg : function(){
+			if(this.uploading){ return false;}
+			var _self = this;
+			uni.showLoading({title:"选择图片"});
+			this.uploading = true;
+			uni.chooseImage({
+				count: 1,
+				sizeType: ['compressed'],
+				sourceType: ['album', 'camera'],
+				success : function(res){
+					uni.showLoading({title:"图片发送中"});
+					const tempFilePaths = res.tempFilePaths;
+					const uploadTask = uni.uploadFile({
+						url      : _self.config.serverUrl+'?c=uploadFile&token='+_self.token,
+						filePath : tempFilePaths[0],
+						name     : 'gChatUploadFile',
+						success   : function (uploadFileRes) {
+							var res = JSON.parse(uploadFileRes.data);
+							if(res.status == 'ok'){
+								var msg         = _self.user;
+								msg.type        = 'msg';
+								msg.contentType = 'img';
+								msg.content     = res.msg;
+								uni.sendSocketMessage({
+									data : JSON.stringify(msg),
+									complete:function(){
+										setTimeout(function(){
+											_self.uploading = false;
+											uni.hideLoading();
+										}, 500);
+									},
+									fail:function(e){
+										_self.uploading = false;
+										console.log('消息发送失败');
+									}
+								});
+							}else{
+								setTimeout(function(){uni.hideLoading();}, 500);
+							}
+							
+						},
+						fail:function(e){
+							_self.uploading = false;
+							console.log(e);
+						}
+					});
+				},
+				fail:function(){uni.hideLoading();}
+			});
+		}
+	}
+}
+</script>
+<style scoped>
+@font-face {font-family: "graceIMFonts"; src:url('data:font/truetype;charset=utf-8;base64,d09GMgABAAAAAASoAAsAAAAACpAAAARbAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDMgqILIZ4ATYCJAMUCwwABCAFhG0HSxsHCcgOJQUABgUAAMBk8EB6fW8yk/3Z7Cd4BLPXQW6vA9B99wc4f67lvzL5jC8R2YVkAS+XAsN4QPB1pPqF+wfHUj8K/8Hyvu1BXjGyLDq25lGBDagb1ySRuNkEfcvRGr5iXEVcDQGYRBOJS2OXykbwRoFqQYpmTZc48HZYUBEkgaF3BBypENvQMbT12hNgq/b24g35wgANXYLX6jelSITc52EvKqRlDzJKWljFuYDbcSCBTAAFYkGgZxZuhipT05hBv+dOLYCJgYb2POx50vPyFxW2DZaiQzqT/8MDDYlCgHCAGk+KrY0reB4m8aDB8yQeJDwv50HBiwrZtGh0ur0JhAGiEhCdYIisoERHonui2lcmOM2zJHp7m6a+suXpoNZng9tfDG17vkyohugi+3dbns4wdwWyFuTaGyL2DH1o+v/RweMTg2Y6LqngIEk2qXNSr6zPX9naOrilZVBb25D29qFN+/Nh99riT7Z3vdsad6xlyKkr1tWNZ09eGz6kkbRWS7iliav7hBVp1hm64Q4O3rMhcFCzzBa4ywoa3MgWtHtjyNAmVUhYR3kdd6O7DasHDfpeN2jwb7vbQFjoV6Fa7o6VK9v0qChdVrZdRURhRbwW3rqlUHWLS5mMreMeM3nRSDO2TbYUy16uGosg002pTpZwphre5b7iZKa3S1VPqnNboCS+PE4Hmd9c9SJ2Lk+1zG3JjnAsPmH0XBpvDWJl+WX186e6zIWKSG6z0np0qthYRZSdTEQULL34fb9uJZ66RKiru0eP4K4lSXcWecR/xPf20/WbFi0Bz/8qJrcUz0VLHq9jd7SqyEjVyoWKSFZYZV4TL99QQihCuiGEghgP3sLyqYFyCAlv/7hDnEsCyWXFCOnjZkAVINnlhrLtTEJFJEusvRjRsGVZpxpGRH+psDBFlC1MRCRsj0k9bjZvPtcjYrOpiXamCVUROssu7hLWO2VNhdl5+yxFRU/Z8/pcBwNGkpu2yNAKAPaY/CNXAvak/CmXAfaM/CYXH8o/w6M7d+aODar8ZrgUAB2fJ5zRauxBhbTgeNSSWHrgShOOqa8crbp6d0qSt09n10Gh0T6LTOBwDfjzrxh7EEUmBAZBFmh4EAUSg0SkwmaCjotCcGBQAyYZlBzvIgCKG0I5gXTGAAS+nIKGN9dB4ssnUmH/QCeY/+DAV0gwGSICzugiWUw3GxZgjmER8V1IESyTCDvz1YvrsdSmc9Qu18jNmDqVIIrLznUnS7CJaRH7ONuleMYIItQyUDFcD+u6hWxqqVhg0TJjdnpMzpSgJ4oWLAN8aYCC4WBgRBA8XSAUBCxMiLIww9fz+vVgJNrQ4aARXT/8zWAoTpTVI+KIJjYBskRjJuralEon7UjEw2iEgCC3QgsDRDF1YXQes0DYwZupYAQY0cgZIjbpxNDJSFJNdPU6Yx5LT5ueeAZdaEIKJXThwGPGdM60RcRz0WJbUY1TF8sb8IrH8sVdigkAAA==') format('truetype');}
+.graceIMFonts {font-family:"graceIMFonts" !important; font-size:15px; font-style:normal; color:#5E5E5E;}
+.icon-voice:before{content: "\e63a";}
+.icon-photograph:before {content:"\e619";}
+.icon-close:before {content:"\e625";}
+.icon-kbd{content:"\e73b";}
+
+.grace-im-footer{display:flex; background:#FFFFFF; width:100%; position:fixed; left:0; bottom:0; height:50px; flex-wrap:nowrap; overflow:hidden; box-shadow:1px 1px 6px #888;}
+.grace-im-footer .grace-items{width:auto; line-height:50px; flex-shrink:0;}
+.grace-im-menus{width:44px; height:50px; flex-shrink:0; line-height:50px; text-align:center; font-size:28px;}
+.grace-im-input{width:100%; margin:5px; padding:0 8px; background:#F4F5F6; border-radius:5px; height:40px; flex-wrap:nowrap; margin-left:10px;}
+.grace-im-input input{width:100%; background:#F4F5F6; height:20px; margin:8px 0; line-height:20px; border-radius:5px;}
+
+.grace-im-record{width:100%; position:fixed; left:0; bottom:0; background:#FFF; padding:30px 0; padding-bottom:50px; z-index:11; box-shadow:1px 1px 6px #888;}
+.grace-im-record-close{width:50px; height:50px; position:absolute; top:0px; right:0px; z-index:100; text-align:center; line-height:50px; color:#ccc; font-size:20px;}
+.grace-im-record-txt{text-align:center; line-height:30px; padding-bottom:10px; color:#ccc;}
+.grace-im-record-btn{width:60px; height:60px; margin:0 auto; border:5px solid #F1F2F3; border-radius:100%; background:#00B26A;}
+.grace-im-recording{background:#FF0000; animation:fade linear 2s infinite;}
+@keyframes fade{from{opacity:0.1;} 50%{opacity:1;} to{opacity:0.1;}}
+.grace-im-record-txt text{color:#00B26A; padding:0 12px;}
+
+.grace-im-send-voice{margin-top:12px; color:#00BA62; text-align:center;}
+.grace-im-send-voice text{margin:0 15px; color:#00BA62;}
+</style>

+ 200 - 0
lib/graceUI/components/graceIMMsg.vue

@@ -0,0 +1,200 @@
+<template name="graceIMMsg">
+	<view class='grace-im'>
+		<block v-for="(item, index) in msgs" :key="index">
+			<block v-if="item.group == group">
+				<!-- 文本消息 -->
+				<view class="grace-im-msg" 
+				:class="[userid == item.uindex ? 'grace-im-msg-right' : 'grace-im-msg-left']" 
+				v-if="item.contentType == 'txt'">
+					<view class="face"><image :src="item.uface" mode="widthFix"></image></view>
+					<view class="grace-im-content-in">
+						<view class="grace-im-name">{{item.uname}}</view>
+						<view><view class="content">{{item.content}}</view></view>
+						<view class="timer"><text>{{item.date}}</text></view>
+					</view>
+				</view>
+				<!-- 图片消息 -->
+				<view class="grace-im-msg" 
+				:class="[userid == item.uindex ? 'grace-im-msg-right' : 'grace-im-msg-left']" 
+				v-if="item.contentType == 'img'">
+					<view class="face"><image :src="item.uface" mode="widthFix"></image></view>
+					<view class="grace-im-content-in">
+						<view class="grace-im-name">{{item.uname}}</view>
+						<view>
+							<view class="content img">
+								<image :src="item.content" :data-img="item.content" @tap="showImgs" mode="widthFix"></image>
+							</view>
+						</view>
+						<view class="timer"><text>{{item.date}}</text></view>
+					</view>
+				</view>
+				<!-- 语音消息 -->
+				<view class="grace-im-msg" 
+				:class="[userid == item.uindex ? 'grace-im-msg-right' : 'grace-im-msg-left']" 
+				v-if="item.contentType == 'voice'">
+					<view class="face"><image :src="item.uface" mode="widthFix"></image></view>
+					<view class="grace-im-content-in">
+						<view class="grace-im-name">{{item.uname}}</view>
+						<view>
+							<view :class="['content', 'grace-im-voice-msg', index == playIndex ? 'grace-im-playing' : '']" 
+								:data-voice="item.content" 
+								:data-index='index' @tap='playVoice' :style="{'min-width':'50px', 'width':(item.length*3)+'px'}">
+								<text class="graceIMFont icon-audio"></text>{{item.length}}"
+							</view>
+						</view>
+						<view class="timer"><text>{{item.date}}</text></view>
+					</view>
+				</view>
+				<!-- 系统消息 -->
+				<view class="grace-im-msg" v-if="item.ctype == 'system'">
+					<view class="grace-im-system-msg">{{item.msg}}</view>
+				</view>
+				<view></view>
+			</block>
+		</block>
+		<view style="height:100px;"></view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceIMMsg",
+	props: {
+		msgs : {
+			type : Array,
+			default : function(){
+				return []
+			},
+		},
+		userid: {
+			type: String,
+			default:'',
+		},
+		scrollTop : {
+			type: Number,
+			default: 99999,
+		},
+		group :{
+			type : String,
+			default : ""
+		}
+	},
+	data() {
+		return {
+			player : null,
+			playIndex : -1
+		}
+	},
+	created: function(){
+		var _self = this;
+		// #ifndef H5
+		this.player = uni.getBackgroundAudioManager();
+		this.player.onPlay(() => {});
+		this.player.onEnded(function(){
+			var cIndex     = _self.playIndex;
+			if(cIndex < 0){return false;}
+			_self.playIndex = -1;
+			for (var i = cIndex + 1; i < _self.msgs.length; i++) {
+				if (_self.msgs[i].contentType == 'voice') {
+					_self.playNow(_self.msgs[i].content, i);
+					break;
+					return;
+				}
+			}
+		});
+		// #endif
+  },
+  methods: {
+		// 播放语音
+		playVoice: function (e) {
+			// #ifdef H5
+			uni.showToast({title:"请使用 APP 或 小程序聆听语音", icon:"none"});
+			return ;
+			// #endif
+			var voicelUrl = e.currentTarget.dataset.voice;
+			var index = e.currentTarget.dataset.index;
+			if (this.playIndex == -1){return this.playNow(voicelUrl, index);}
+			var _self = this;
+			if (this.playIndex == index) {
+				wx.getBackgroundAudioPlayerState({
+					success(res) {
+						const status = res.status
+						switch (status) {
+							case 0:
+							_self.player.play();
+							break;
+							case 1:
+							_self.player.pause();
+							break;
+							default:
+							_self.player.play();
+						}
+					}
+				});
+			} else {
+				this.player.stop();
+				this.playNow(voicelUrl, index);
+			}
+		},
+		// 语音播放基础函数
+		playNow: function (voicelUrl, index){
+			this.playIndex = index;
+			this.player.src = voicelUrl;
+			this.player.title = "graceUI";
+			return true;
+		},
+		// 图片预览
+		showImgs : function(e){
+			var imgs        = [];
+			var imgsCurrent = e.currentTarget.dataset.img;
+			for (var i = 0; i < this.msgs.length; i++) {
+				if (this.msgs[i].contentType == 'img') {
+					imgs.push(this.msgs[i].content);
+				}
+			}
+			wx.previewImage({urls : imgs, current : imgsCurrent});
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-im{padding:0 10px;}
+.grace-im-system-msg{background:#C1C1C1; width:auto; color:#FFF; font-size:12px; line-height:1.5em; padding:5px 10px; margin:0 auto; display:inline-block; border-radius:3px;}
+.grace-im-msg{margin:15px 0; flex-wrap:nowrap; display:flex;}
+.grace-im-msg .face{width:44px; height:44px; overflow:hidden; flex-shrink:0;}
+.grace-im-msg .face image{width:44px; height:44px; border-radius:4px;}
+.grace-im-name{line-height:15px; height:15px; font-size:11px; color:#3688FF; text-indent:10px;}
+.grace-im-content-in{margin:0 5px; width:100%; overflow:hidden;}
+.grace-im-content-in > view{width:100%; overflow:hidden;}
+.grace-im-content-in .content{padding:8px 15px; margin-top:6px; background:#FFF; line-height:1.5em; border-radius:3px; word-break:break-all; display:inline-block; position:relative;}
+.grace-im-content-in .content::after{content:""; position:absolute; height:10px; width:10px; background:#FFFFFF; top:12px; transform: translate(0,-50%) rotate(45deg); z-index:2;}
+.grace-im-content-in .timer{margin-top:5px; line-height:15px;}
+.grace-im-content-in .timer text{font-size:10px; margin-left:10px; color:#8788A3;}
+.grace-im-msg-left .content{margin-left:10px;}
+.grace-im-msg-left .content::after{left:-4px;}
+
+.grace-im-msg-right .content{margin-right:10px;}
+.grace-im-msg-right .content::after{right:-4px; background:#00B26A;}
+.grace-im-msg-right{flex-direction:row-reverse;}
+.grace-im-msg-right .grace-im-name{display:none;}
+.grace-im-msg-right .content{float:right; background:#00B26A; color:#FFFFFF; margin-top:0;}
+.grace-im-msg-right .timer{text-align:right;}
+.grace-im-msg-right .timer text{text-align:right; margin-right:10px;}
+
+.grace-im-content-in .img{padding:0 !important; font-size:0; background:#FFF;}
+.grace-im-content-in .img::after{width:0 !important; height:0 !important;}
+.grace-im-content-in .img image{width:400upx; border-radius:5px;}
+
+.grace-im-voice-msg{font-size:10px; line-height:22px !important;}
+.grace-im-voice-msg text{font-size:20px;}
+.grace-im-playing{background:#007AFF !important; color:#FFF;}
+.grace-im-playing text{color:#FFF;}
+.grace-im-playing::after{background:#007AFF !important;}
+
+
+@font-face {
+	font-family: "graceIMFont"; 
+	src:url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAARIAAsAAAAABpAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY+dknyY21hcAAAAYAAAABKAAABcOkou6pnbHlmAAABzAAAAJgAAACcsQjN8WhlYWQAAAJkAAAALwAAADYTkbRlaGhlYQAAApQAAAAcAAAAJAfeA4NobXR4AAACsAAAAAgAAAAICAAAAGxvY2EAAAK4AAAABgAAAAYATgAAbWF4cAAAAsAAAAAgAAAAIAEQADxuYW1lAAAC4AAAAUUAAAJtPlT+fXBvc3QAAAQoAAAAHgAAAC/RhmVweJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGBxeMLxgYG7438AQw9zA0AAUZgTJAQDdeQvneJztkLENgDAQA8/KQ4EYgSo1s1Cxf80WycfAFrF0lnz66oEFKMmZBOhGjFxpZV/Y7IPqmxj+obW/c4cbrczs7uNb/tUL6i3WCXEAAHicY2BhYPj/mVmKmZeBj0GaQZ1Bn4GBkY+RXdyO0URNH0jLMTIbKBmLmwmAOGIC7CKm4mYmYLaIOoShxHSL0dNNJyA4OIBZl8XN8xsTk25VFbMuc+ppXX9/Fh1muzpFReaQcFbGhuQtnroBpx+fBCpkdt/8b7FOA6N3HbMOS86/l2ysrLrBt8KAHOfljMqxyszBwQAGoSFheJxjYGRgYADih0telMfz23xl4GZhAIEblhYmCPr/ZxYGZl4gl4OBCSQKAC7VCZ8AeJxjYGRgYG7438AQw8IAAkCSkQEVMAEARwgCawQAAAAEAAAAAAAAAABOAAAAAQAAAAIAMAAEAAAAAAACAAAACgAKAAAA/wAAAAAAAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgImRiZGZgTWxNCUzn4EBAAzjAi0AAA=='); /* iOS 4.1- */
+}
+.graceIMFont {font-family:"graceIMFont" !important; font-size:16px;}
+.icon-audio:before {content:"\e800";}
+</style>

+ 36 - 0
lib/graceUI/components/graceIconClasses.vue

@@ -0,0 +1,36 @@
+<template>
+	<view class="graceIconClasses">
+		<navigator :url="item.path" :open-type="item.openType" class="item" :style="{background:item.bgColor}" v-for="(item, index) in classes" :key="index">
+			<image :src="item.icon" mode="widthFix"></image>
+			<view class="content">
+				<view>
+					<view class="image"><image :src="item.icon" mode="widthFix"></image></view>
+					<view class="txt">
+						{{item.txt}}<text>{{item.smallTxt}}</text>
+					</view>
+				</view>
+			</view>
+		</navigator>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		classes:{
+			type : Array,
+			default : function(){return [];}
+		}
+	}
+}
+</script>
+<style>
+.graceIconClasses{display:flex; flex-direction:row; flex-wrap:wrap; justify-content:space-between;}
+.graceIconClasses > .item{width:31.3%; height:168rpx; display:flex; border-radius:5px; background:#6DCD70; color:#FFF; position:relative; margin:10rpx 0; overflow:hidden;}
+.graceIconClasses > .item > image{width:40%; position:absolute; z-index:1; opacity:0.15; height:auto; right:3px; bottom:3px;}
+.graceIconClasses > .item > .content{width:100%; height:168rpx; display:flex; flex-wrap:wrap; align-items:center !important;}
+.graceIconClasses > .item > .content view{width:100%;}
+.graceIconClasses > .item > .content .txt{font-size:28rpx !important; color:#FFFFFF; text-indent:20rpx; line-height:40rpx; margin-top:5px;}
+.graceIconClasses > .item > .content .txt text{font-size:20rpx; margin-left:5rpx; opacity:0.8;}
+.graceIconClasses > .item > .content .image{font-size:0;}
+.graceIconClasses > .item > .content .image image{width:30%; height:auto; margin-left:20rpx;}
+</style>

+ 740 - 0
lib/graceUI/components/graceImageCrop.vue

@@ -0,0 +1,740 @@
+<template>
+    <view class="vue-cropper" ref="cropper" :style="{top:`${containerTop}px`}" v-show="show">
+        <view class="cropper-box">
+            <view class="cropper-box-canvas" @touchstart.stop.prevent="imgTouchStart" @touchmove.stop.prevent="imgMoveing" @touchend.stop.prevent="imgMoveEnd" :style="{
+			'width': imageWidth + 'px',
+			'height': imageHeight + 'px',
+			'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ x / scale + 'px,' + y / scale + 'px,' + '0)'
+			+ 'rotateZ('+ rotate * 90 +'deg)'}">
+                <image :src="src" alt="cropper-img" ref="cropperImg" mode="scaleToFill" class="uni-image"></image>
+            </view>
+        </view>
+        <view class="cropper-drag-box cropper-modal cropper-move pointer-events"></view>
+        <view class="cropper-crop-box" :class="{'pointer-events': cropFixed}" :style="{'width': cropW + 'px','height': cropH + 'px','transform': 'translate3d('+ cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'}">
+            <view class="cropper-view-box">
+                <image :style="{'width': imageWidth + 'px','height': imageHeight + 'px','transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ (x - cropOffsertX) / scale  + 'px,' + (y - cropOffsertY) / scale + 'px,' + '0)' + 'rotateZ('+ rotate * 90 +'deg)'}" mode="scaleToFill" :src="src" alt="cropper-img"></image>
+            </view>
+            <view v-if="!cropFixed" class="cropper-face cropper-move" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="cropMoveing"></view>
+            <view class="crop-line line-w"></view>
+            <view class="crop-line line-a"></view>
+            <view class="crop-line line-s"></view>
+            <view class="crop-line line-d"></view>
+        </view>
+		<canvas  id="myCanvas" canvas-id="myCanvas" class="cropper-canvas" :style="{ 'width': cropW + 'px','height': cropH + 'px' }"></canvas>
+        <view class="btn-group">
+            <view class="btn-item reset-btn" v-show="showResetBtn" @tap="init"></view>
+            <view class="btn-item rotate-btn" v-show="showRotateBtn" @tap="rotateHandler"></view>
+        </view>
+
+        <view class="uni-info__ft" :style="{paddingBottom:iPhoneXBottomHeightRpx+'rpx', background:'#FFFFFF'}">
+            <view class="uni-modal__btn uni-modal__btn_default" style="color: rgb(0, 0, 0);" @tap="cancel">取消</view>
+            <view class="uni-modal__btn uni-modal__btn_primary" style="color: rgb(0, 122, 255);" @tap="confirm">确定</view>
+        </view>
+    </view>
+</template>
+<script>
+export default {
+	name: 'image-cropper',
+	props: {
+		cropWidth: {
+			type: Number,
+			default: 200,
+		},
+		cropHeight: {
+			type: Number,
+			default: 200
+		},
+		cropFixed: {
+			type: Boolean,
+			default: false,
+		},
+		src: {
+			type: String,
+		},
+		showResetBtn: {
+			type: Boolean,
+			default: true,
+		},
+		showRotateBtn: {
+			type: Boolean,
+			default: true,
+		}
+	},
+	data() {
+		const sysInfo = uni.getSystemInfoSync();
+		const pixelRatio = sysInfo.pixelRatio;
+		return {
+			show: false,
+			scale: 1,
+			rotate: 0,
+			cropW: 0,
+			cropH: 0,
+			cropOldW: 0,
+			cropOldH: 0,
+			sysInfo: sysInfo,
+			pixelRatio: pixelRatio,
+			imageRealWidth: 0,
+			imageRealHeight: 0,
+			cropOffsertX: 0,
+			cropOffsertY: 0,
+			startX: 0,
+			startY: 0,
+			// 裁剪框与边界间距
+			border: 5,
+			x: 0,
+			y: 0,
+			startL: 0,
+			oldScale: 1,
+			iPhoneXBottomHeightRpx:0
+		}
+	},
+	created:function(){
+		const sysInfo = uni.getSystemInfoSync();
+		const pixelRatio = sysInfo.pixelRatio;
+		var iPhoneXBottom = 0;
+		sysInfo.model = sysInfo.model.replace(' ', '');
+		sysInfo.model = sysInfo.model.toLowerCase();
+		if(sysInfo.model.indexOf('iphonex') != -1 || sysInfo.model.indexOf('iphone11') != -1){
+			this.iPhoneXBottomHeightRpx = 50;
+		}else{
+			this.iPhoneXBottomHeightRpx = 0;
+		}
+	},
+	watch: {
+		src(val) {if(val.length > 0) { this.init();}},
+		show(val) {if(!val){}}
+	},
+	computed: {
+		containerTop() {
+			let top = 0
+			// #ifdef H5
+			top = 44
+			// #endif
+			return top;
+		},
+		// 容器高度
+		containerHeight() {
+			return this.windowHeight - 48;
+		},
+		// 屏幕宽度
+		windowWidth() {
+			return this.sysInfo.windowWidth;
+		},
+		windowHeight() {
+			return this.sysInfo.windowHeight;
+		},
+		// 图片宽高比
+		imageRatio() {
+			if (this.imageRealHeight > 0) {
+				return this.imageRealWidth / this.imageRealHeight;;
+			}
+			return 0;
+		},
+		// 等比缩放后的宽度
+		imageWidth() {
+			if (this.imageRatio >= 1) {
+				return this.windowWidth;
+			}
+			var imageWidth = this.windowWidth * this.imageRatio;
+			if(imageWidth < this.cropWidth){return this.cropWidth;}
+			return this.windowWidth
+		},
+		// 等比缩放后的高度
+		imageHeight() {
+			return this.windowWidth / this.imageRatio
+		},
+	},
+	methods: {
+		rotateHandler() {
+			if(this.rotate == 3) {
+				this.rotate = 0;
+			}else {
+				++this.rotate
+			}
+		},
+		init() {
+			this.rotate = 0;
+			this.scale = 1;
+			this.cropW = this.cropWidth
+			this.cropH = this.cropHeight
+			uni.showLoading({title: '图片加载中...',})
+			this.loadImage(this.src).then((e) => {
+				uni.hideLoading()
+			}).catch((e) => {
+				uni.hideLoading()
+				uni.showModal({
+					title: '标题',
+					content: '图片加载失败'
+				})
+			})
+		},
+		loadImage(src) {
+			const _this = this
+			return new Promise((resolve, reject) => {
+				uni.getImageInfo({
+					src: src,
+					success: (res) => {
+					
+						_this.imageRealWidth = res.width
+						_this.imageRealHeight = res.height
+
+						_this.cropOffsertX = _this.windowWidth / 2 - _this.cropW / 2
+						_this.cropOffsertY = _this.windowHeight / 2 - _this.cropH / 2
+						_this.show = true
+
+						_this.$nextTick(() => {
+							_this.x = _this.windowWidth / 2 - _this.imageWidth / 2
+							_this.y = _this.containerHeight / 2 - _this.imageHeight / 2
+						});
+						resolve(res)
+					},
+					fail: (e) => {
+						_this.show = false
+						reject(e)
+					}
+				})
+			}).catch((e) => {});
+		},
+		cancel() {
+			this.show = false
+			this.$emit('cancel')
+		},
+		confirm(event) {
+			uni.showLoading({
+				title: '裁剪中...',
+			})
+			const _this = this
+			const ctx = uni.createCanvasContext('myCanvas', _this);
+			const pixelRatio = _this.pixelRatio;
+			const imgage = _this.src;
+			const imgW = _this.imageWidth * _this.scale;
+			const imgH = _this.imageHeight * _this.scale
+			const rotate = _this.rotate;
+			let dx = _this.cropOffsertX - _this.x - (_this.imageWidth - imgW) / 2;
+			let dy = _this.cropOffsertY - _this.y - (_this.imageHeight - imgH) / 2;
+			ctx.setFillStyle('white');
+			ctx.fillRect(0, 0, imgW, imgH);
+			ctx.save();
+			ctx.rotate((rotate * 90 * Math.PI) / 180);
+			switch (rotate) {
+				case 1:
+					dx += (imgH-imgW) / 2
+					dy -= (imgH-imgW) / 2
+					ctx.drawImage(imgage, -dy, dx, imgW, -imgH);
+					break;
+				case 2:
+					ctx.drawImage(imgage, dx, dy, -imgW, -imgH);
+					break;
+				case 3:
+					dx += (imgH-imgW) / 2
+					dy -= (imgH-imgW) / 2
+					ctx.drawImage(imgage, dy, -dx, -imgW, imgH);
+					break;
+				default:
+				 ctx.drawImage(imgage, -dx, -dy, imgW, imgH);
+					   //ctx.drawImage(imgage, 2, 2, 375,375);
+					break;
+			}
+			ctx.restore()
+				// #ifdef MP-ALIPAY
+				ctx.draw(true, () => {
+					ctx.toTempFilePath({
+						destWidth: _this.cropW * pixelRatio,
+						destHeight: _this.cropH * pixelRatio,
+						success: (res) => {
+							uni.hideLoading()
+								event.detail.tempFilePath =res.apFilePath
+								_this.show = false
+								_this.$emit('confirm', event)
+							},
+							fail: (e) => {
+								uni.hideLoading()
+								uni.showModal({
+									title: '提示',
+									content: '裁剪失败'
+								})
+							}
+					}, _this);
+				});
+			// #endif
+			// #ifndef MP-ALIPAY
+			ctx.draw(false, () => {
+				uni.canvasToTempFilePath({
+					canvasId: 'myCanvas',
+					destWidth: _this.cropW * pixelRatio,
+					destHeight: _this.cropH * pixelRatio,
+					success: (res) => {
+						uni.hideLoading()
+						event.detail.tempFilePath = res.tempFilePath;
+						_this.show = false
+						_this.$emit('confirm', event)
+					},
+					fail: (e) => {
+						uni.hideLoading()
+						uni.showModal({
+							title: '提示',
+							content: '裁剪失败'
+						})
+					}
+				}, _this);
+			})
+			// #endif
+		},
+		imgTouchStart(e) {
+			if(e.touches.length == 2) {
+				this.oldScale = this.scale
+				this.scaling = true
+				const x = e.touches[0].pageX - e.touches[1].pageX
+				const y = e.touches[0].pageY - e.touches[1].pageY
+				const hypotenuse = Math.sqrt(
+					Math.pow(x, 2) +
+					Math.pow(y, 2)
+				)
+				this.startL = Math.max(x, y, hypotenuse)
+				uni.showModal({content: this.startL});
+			} else {
+				this.startX = e.touches[0].pageX - this.x
+				this.startY = e.touches[0].pageY - this.y
+			}
+		},
+		imgMoveing(e) {
+			if(this.scaling) {
+				let scale = this.oldScale;
+				const x = e.touches[0].pageX - e.touches[1].pageX
+				const y = e.touches[0].pageY - e.touches[1].pageY
+				const hypotenuse = Math.sqrt(
+					Math.pow(x, 2) +
+					Math.pow(y, 2)
+				)
+				const newL = Math.max(x, y, hypotenuse)
+				const cha = newL - this.startL;
+				// 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
+				// 1px - 0.2
+				let coe = 1;
+				coe =
+					coe / this.imageWidth > coe / this.imageHeight
+						? coe / this.imageHeight
+						: coe / this.imageWidth;
+				coe = coe > 0.1 ? 0.1 : coe;
+				const num = coe * cha;
+				if (cha > 0) {
+					scale += Math.abs(num);
+				} else if (cha < 0) {
+					scale > Math.abs(num) ? (scale -= Math.abs(num)) : scale;
+				}
+				this.scale = scale;
+			} else {
+				const moveX = e.touches[0].pageX - this.startX
+				const moveY = e.touches[0].pageY - this.startY
+				this.x = moveX
+				this.y = moveY
+			}
+		},
+		imgMoveEnd() {
+			setTimeout(() => {
+				this.scaling = false
+			}, 100)
+		},
+		touchStart(e) {
+			this.startX = e.touches[0].pageX - this.cropOffsertX;
+			this.startY = e.touches[0].pageY - this.cropOffsertY;
+
+			this.cropOldW = this.cropW
+			this.cropOldH = this.cropH
+		},
+		cropMoveing(e) {
+			const moveX = this._cropX(e.touches[0].pageX - this.startX)
+			const moveY = this._cropY(e.touches[0].pageY - this.startY)
+
+			this.cropOffsertX = moveX
+			this.cropOffsertY = moveY
+		},
+		dragMove(e, type) {
+			if(this.cropFixed) {
+				return false
+			}
+			const moveX = e.touches[0].pageX - this.startX
+			const moveY = e.touches[0].pageY - this.startY
+			switch (type) {
+				case 'left-top':
+					this._cropMoveLeft(moveX)
+					this._cropMoveTop(moveY)
+					break;
+				case 'middle-top':
+					this._cropMoveTop(moveY)
+					break;
+				case 'right-top':
+					this._cropMoveTop(moveY)
+					this._cropMoveRight(moveX)
+					break;
+				case 'middle-right':
+					this._cropMoveRight(moveX)
+					break;
+				case 'right-bottom':
+					this._cropMoveRight(moveX)
+					this._cropMoveBottom(moveY)
+					break;
+				case 'middle-bottom':
+					this._cropMoveBottom(moveY)
+					break;
+				case 'left-bottom':
+					this._cropMoveBottom(moveY)
+					this._cropMoveLeft(moveX)
+					break;
+				case 'middle-left':
+					this._cropMoveLeft(moveX)
+					break;
+				default:
+					break;
+			}
+		},
+		_cropMoveTop(y) {
+			const topY = this._cropY(y)
+			this.cropH += this.cropOffsertY - topY
+			this.cropOffsertY = topY
+		},
+		_cropMoveRight(x) {
+			if(this.cropOldW + x >= this.windowWidth - this.border) {
+				return false;
+			}
+			this.cropW = this.cropOldW + (x  - this.cropOffsertX)
+		},
+		_cropMoveBottom(y) {
+			if(this.cropOldH + y >= this.windowHeight - this.containerTop - this.border) {
+				return false;
+			}
+			this.cropH = this.cropOldH + (y  - this.cropOffsertY)
+		},
+		_cropMoveLeft(x) {
+			const leftX = this._cropY(x)
+			this.cropW += this.cropOffsertX - leftX
+			this.cropOffsertX = leftX
+		},
+		_cropX(x) {
+			if(x <= this.border) {
+				return this.border
+			}
+			if(x + this.cropW >= this.windowWidth - this.border) {
+				return this.windowWidth - this.cropW - this.border
+			}
+			return x
+		},
+		_cropY(y) {
+			if(y <= this.border) {
+				return this.border
+			}
+			if(y + this.cropH >= this.windowHeight - this.containerTop - this.border) {
+				return this.windowHeight - this.cropH - this.containerTop - this.border
+			}
+			return y
+		}
+	}
+}
+</script>
+<style scoped lang="css">
+@font-face {
+	font-family: "iconfont";
+	src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAR4AAsAAAAACKgAAAQsAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqEfIRGATYCJAMMCwgABCAFhG0HShugB8gOJUHBwAAAAAFEBNmwzd4dtatSmmpFoVAEhUThEAYkCozFKDCqCVO6RfH/89v869awDnTR1qrSANFt4GG4SNxreBn91fmV9f3+53J613ieHba+N1zmGM8PA7oXTaCAxpjei8IoLWFsGLu4jPME6vWJJdovqmgAO4U2LRBnep0K7GJmpYQWanXVOWuLuAFrtenK4haAa/f38QnKsCOpyrRFh6eFWsh5KXnfYcn958BGQNKfE8wmMmaAQpzkuo9Z+ukZluoltVV5abUipL5i/ysArlhWVut/eCRBVNPUjYg6oUo7JTHFoaYDSvdacnKTq9GAB4AY5y2dtL3qpFh1DENdnJC6Hq+xYb7pyRMDMzc/fYoJjY8flwO3m98rMucF+IZHj6Cagw5UeKpxyFbt2rHGY/8jpa7CYMvLfcIesLjY3bdqhaf+nqgQs2qT/+rjCH/VfA0VFGuAC3iE8NEr/Vau8vZsXiUy7+V3c3tQQXMAuNjDCC89KDIHH0OFhnUi81GEPwyc7wZUaN7DnUf4g+ZLQsMKYV/94NjK7R7TEM4niTY1oJ5zEU62aNVaasUub08YLUEam5EnT6a61/I17dNk+vTu9jpJjXhsTFwjqTtpCBxBIIgS6iQnc/Zod1YGKp0rAwsD8kkyP6AwcK0hcAwkiQmBhWvxPZWKDu86aUH2nLEdi9rGX1eXq5P6A1SrnAucMVMdZH/GKi/jyfCqJyucfK3mXpVujXOPfFf5LC4Dvx0X/943JyOq4HuCTZ8KiIPPAb6ro8akpT6ufiq39BQrNlk5mp8pO0JlJLk8f5QalRjoP60IMx0N8n7wGhSD3n6/F1zlcTVz/cR+Ev0lkLSTd7UiPbD/wCxGRMA2Krwro2O0bTQtImbwhjAJc0S3N4ROx15/PH60IzaIOjCbEelqkDOfETNxb/FMixnWNzeJp2KPQw9A5d76jGUOQOUvH7RE/o2RfkNatd3OGf9q0QKbnq8WB7qy+hVqJRjJn1BQgP/iErks0yy5iGJTrOayW7C/z0IoZH0qNH+7N+31XXc7G2p1hZDU6IWs1ghaqDNQpcEKVKu1BfWmFW9u0IFhKUodpswCEFodgqTZHWStbqOF+hqqdPsG1VrDEuodhfueDcZCj+QzuIrFtZh6BNNraIowbCzi1dbhOlOfionKXHoTzgzoY5hCKk/minEKZ/pYMDCoU7IsgREM3Y8Vgcvwvj4aMzK0AdewUpJljWkyGZH3IKmG7gfEHgZOhYXTwqiNwOhp0CiE3ZiFpL5fB6dj0keFKcGV+JvgGAP0vWMUpOQ10GI1VQt3LoMHDNJRYrEIPInAoPXDFEEnrk9P0zDG/FEGOA2WFNkiaZRGhuoRddXS8bX917cL6mn9c6TIUXSekybKHKQfJXFq2KSiRklLYU8dNKWDIX0cAA==') format('woff2');
+}
+.vue-cropper {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	z-index: 998;
+	box-sizing: border-box;
+	user-select: none;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	direction: ltr;
+	touch-action: none;
+	text-align: left;
+	background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
+}
+.cropper-canvas {
+	position: absolute;
+	top: -9999px;
+	left:-9999px;
+	z-index: -998;
+}
+.vue-cropper .uni-info__ft {
+	position: absolute;
+	line-height: 48px;
+	font-size: 18px;
+	display: -webkit-box;
+	display: -webkit-flex;
+	display: flex;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	z-index: 998;
+}
+.btn-group {
+	position: absolute;
+	right: 30px;
+	bottom: 98px;
+	z-index: 998;
+}
+.btn-item {
+	position: relative;
+	width:40px;
+	height:40px;
+	background:#fff;
+	border-radius: 20px;
+	display: inline-block;
+	margin-left: 10px;
+	text-align:center; line-height:40px;
+}
+.btn-item:active {background: #ccc;}
+.rotate-btn {
+	font-family: "iconfont" !important;
+	font-size:30px;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+}
+.rotate-btn:before {
+	content: "\e65c";
+	margin-left: -2px;
+}
+
+.reset-btn {
+	font-family: "iconfont" !important;
+	font-size:30px;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+}
+.reset-btn:before {
+	content: "\e648";
+	margin-left: -2px;
+}
+
+.vue-cropper .uni-info__ft:after {
+	content: " ";
+	position: absolute;
+	left: 0;
+	top: 0;
+	right: 0;
+	height: 1px;
+	border-top: 1px solid #d5d5d6;
+	color: #d5d5d6;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	z-index: 998;
+}
+
+.vue-cropper .uni-modal__btn {
+	display: block;
+	-webkit-box-flex: 1;
+	-webkit-flex: 1;
+	flex: 1;
+	color: #3cc51f;
+	text-decoration: none;
+	-webkit-tap-highlight-color: rgba(0,0,0,0);
+	position: relative;
+	text-align: center;
+	background-color: #fff;
+	z-index: 998;
+}
+
+.vue-cropper .uni-modal__btn:first-child:after { display:  none }
+.vue-cropper .uni-modal__btn:after {
+	content: " ";
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 1px;
+	bottom: 0;
+	border-left: 1px solid #d5d5d6;
+	color: #d5d5d6;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	-webkit-transform: scaleX(.5);
+	transform: scaleX(.5);
+	z-index: 998;
+}
+
+.vue-cropper .uni-modal__btn:active {
+	background-color: #eee;
+}
+
+.cropper-box,
+.cropper-box-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-face {
+	position: absolute;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	user-select: none;
+	z-index: 998;
+}
+
+.uni-image {
+	width: 100%;
+	height: 100%;
+}
+
+.cropper-box-canvas image {
+	position: relative;
+	text-align: left;
+	user-select: none;
+	transform: none;
+	max-width: none;
+	max-height: none;
+	z-index: 998;
+}
+
+.cropper-box {
+	overflow: hidden;
+}
+
+.cropper-move {
+	cursor: move;
+}
+
+.cropper-crop {
+	cursor: crosshair;
+}
+
+.cropper-modal {
+	background: rgba(0, 0, 0, 0.5);
+}
+
+.pointer-events {
+	pointer-events:none;
+}
+
+.cropper-crop-box {
+	/*border: 2px solid #39f;*/
+}
+
+.cropper-view-box {
+	display: block;
+	overflow: hidden;
+	width: 100%;
+	height: 100%;
+	outline: 1px solid #39f;
+	outline-color: rgba(51, 153, 255, 0.75);
+	user-select: none;
+}
+
+.cropper-view-box image {
+	user-select: none;
+	text-align: left;
+	max-width: none;
+	max-height: none;
+}
+
+.cropper-face {
+	top: 0;
+	left: 0;
+	background-color: #fff;
+	opacity: 0.1;
+}
+
+.crop-line {
+	position: absolute;
+	display: block;
+	width: 100%;
+	height: 100%;
+	opacity: 0.1;
+	z-index: 998;
+}
+
+.line-w {
+	top: -3px;
+	left: 0;
+	height: 5px;
+	cursor: n-resize;
+}
+
+.line-a {
+	top: 0;
+	left: -3px;
+	width: 5px;
+	cursor: w-resize;
+}
+
+.line-s {
+	bottom: -3px;
+	left: 0;
+	height: 5px;
+	cursor: s-resize;
+}
+
+.line-d {
+	top: 0;
+	right: -3px;
+	width: 5px;
+	cursor: e-resize;
+}
+
+.crop-point {
+	position: absolute;
+	width: 8px;
+	height: 8px;
+	opacity: 0.75;
+	background-color: #39f;
+	border-radius: 100%;
+	z-index: 998;
+}
+
+.point-lt {
+	top: -4px;
+	left: -4px;
+	cursor: nw-resize;
+}
+
+.point-mt {
+	top: -5px;
+	left: 50%;
+	margin-left: -3px;
+	cursor: n-resize;
+}
+
+.point-rt {
+	top: -4px;
+	right: -4px;
+	cursor: ne-resize;
+}
+
+.point-ml {
+	top: 50%;
+	left: -4px;
+	margin-top: -3px;
+	cursor: w-resize;
+}
+
+.point-mr {
+	top: 50%;
+	right: -4px;
+	margin-top: -3px;
+	cursor: e-resize;
+}
+
+.point-lb {
+	bottom: -5px;
+	left: -4px;
+	cursor: sw-resize;
+}
+
+.point-mb {
+	bottom: -5px;
+	left: 50%;
+	margin-left: -3px;
+	cursor: s-resize;
+}
+
+.point-rb {
+	bottom: -5px;
+	right: -4px;
+	cursor: se-resize;
+}
+</style>

+ 28 - 0
lib/graceUI/components/graceImmersedStatusbar.vue

@@ -0,0 +1,28 @@
+<template>
+	<view>
+		<view class="graceImmersedStatusbar" :style="{background:bgColor, height:height}"></view>
+		<view :style="{height:height, width:'100%'}"></view>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		bgColor : {
+			type : String,
+			default : "#FFF"
+		}
+	},
+	data(){
+		return{
+			height : 0	
+		}
+	},
+	created:function(){
+		var res = uni.getSystemInfoSync();
+		this.height = res.statusBarHeight  + 'px';
+	}
+}
+</script>
+<style scoped>
+.graceImmersedStatusbar{height:20px; position:fixed; z-index:1; top:0; left:0; width:100%;}
+</style>

+ 43 - 0
lib/graceUI/components/graceIphonexBottom.vue

@@ -0,0 +1,43 @@
+<template>
+	<view>
+		<view :class="['grace-iphonex-bottom', isFixed ? 'grace-iphonex-bottom-fixed' : '']" :style="{height:height, background:bgcolor}"></view>
+		<view :style="{width:'100%', height:height}" v-if="isFixed"></view>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		bgcolor : {
+			type : String,
+			default : '#FFFFFF'
+		},
+		isFixed : {
+			type : Boolean,
+			default : true
+		}
+	},
+	data() {
+		return {
+			height:'0rpx'
+		}
+	},
+	created : function () {
+		var _self = this;
+		// #ifdef MP
+		uni.getSystemInfo({
+			success: function(res) {
+				var model = res.model;
+				model = model.replace(' ', '');
+				if (model.search('iPhoneX') != -1){
+				   _self.height = '50rpx';
+				}
+			}
+		})
+		// #endif
+	} 
+}
+</script>
+<style scoped>
+.grace-iphonex-bottom{width:100%; height:0rpx; flex:auto;}
+.grace-iphonex-bottom-fixed{position:fixed; z-index:999999; left:0; bottom:0;}
+</style>

+ 46 - 0
lib/graceUI/components/graceLink.vue

@@ -0,0 +1,46 @@
+<template>
+	<!-- #ifdef APP-PLUS -->
+	<text class="link" :data-url="url" @tap="openUrlForApp" :style="{color:color, lineHeight:lineHeight, fontSize:fontSize}">{{title}}</text>
+	<!-- #endif -->
+	<!-- #ifdef H5 -->
+	<a :href="url" class="link" target="_blank" :style="{color:color, lineHeight:lineHeight, fontSize:fontSize}">{{title}}</a>
+	<!-- #endif -->
+	<!-- #ifdef MP -->
+	<text class="link" :style="{color:color, lineHeight:lineHeight, fontSize:fontSize}">{{url}}</text>
+	<!-- #endif -->
+</template>
+<script>
+export default {
+	props: {
+		url:{
+			type : String,
+			default : ""
+		},
+		title : {
+			type : String,
+			default : ""
+		},
+		color:{
+			type : String,
+			default : "#3688FF"
+		},
+		fontSize : {
+			type : String,
+			default : "28rpx"
+		},
+		lineHeight : {
+			type : String,
+			default : "50rpx"
+		}
+	},
+	methods:{
+		openUrlForApp : function(e){
+			var link = e.currentTarget.dataset.url;
+			plus.runtime.openURL(link);
+		}
+	}
+}
+</script>
+<style scoped>
+.link{text-decoration:none;}
+</style>

+ 42 - 0
lib/graceUI/components/graceLoading.vue

@@ -0,0 +1,42 @@
+<template name="graceLoading">
+	<view class="grace-loading grace-ellipsis" v-if="loadingType < 4">
+		<view class="grace-loading-icon" v-if="loadingType === 1" :style="{color:iconColor}"></view>
+		<text :style="{color:textColor}">{{loadingText[loadingType]}}</text>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		loadingType: {
+			type : Number,
+			default: 0
+		},
+		loadingText : {
+			type  : Array,
+			default : function () {
+				// 0 上拉加载更多
+				// 1 加载中
+				// 2 加载全部
+				// 3 空文本
+				// 4 不展示组件
+				return ["上拉加载更多", "正在努力加载", "已经加载全部数据",'',''];
+			}
+		},
+		iconColor : {
+			type : String,
+			default : "#888888"
+		},
+		textColor : {
+			type : String,
+			default : "#888888"
+		}
+	}
+}
+</script>
+<style scoped>
+@keyframes grace-rotate360{0%{transform:rotate(0deg);} 50%{transform:rotate(180deg);} 100%{transform:rotate(360deg);}}
+.grace-loading{display:flex; width:100%; justify-content:center; padding:16rpx 0; padding-bottom:36rpx; line-height:40rpx; color:#888;}
+.grace-loading text{margin-left:12rpx;}
+.grace-loading-icon{width:40rpx; height:40rpx; justify-content:center; line-height:40rpx; font-size:30rpx; text-align:center; font-family:"grace-iconfont"; animation:grace-rotate360 1200ms infinite linear;}
+.grace-loading-icon:before {content:"\e9db";}
+</style>

+ 64 - 0
lib/graceUI/components/graceNavBar.vue

@@ -0,0 +1,64 @@
+<template>
+	<scroll-view 
+	:class="['grace-nav-bar', isCenter ? 'grace-nav-center' : '']" :scroll-x="true" :show-scrollbar="false" 
+	:scroll-into-view="'tab-'+currentIndex+autoLeft">
+		<view class="nav-item" :id="'tab-'+index" 
+		:style="{width:size < 1 ? 'auto' : size+'rpx', marginRight:margin+'rpx', padding:'0rpx '+padding}" 
+		v-for="(item, index) in items" :key="index" @click="navchang" :data-index="index">
+			<view :class="['nav-item-title', currentIndex == index ? 'nav-active' : '']"
+			:style="{color:currentIndex == index ? activeColor : color, textAlign : textAlign, lineHeight:lineHeight, fontSize:currentIndex == index ? activeFontSize : fontSize, fontWeight:currentIndex == index ? activeFontWeight : ''}">{{item}}</view>
+			<view class="nav-active-line-wrap" :style="{justifyContent:activeDirection}">
+				<view class="nav-active-line" :class="[currentIndex == index && animatie ?'grace-nav-scale':'']" 
+				:style="{background:activeLineBg, width:activeLineWidth, height:activeLineHeight, borderRadius:activeLineRadius}" 
+				v-if="currentIndex == index"></view>
+			</view>
+		</view>
+	</scroll-view>
+</template>
+<script>
+export default {
+	props:{
+		isCenter : {type : Boolean, default : false},
+		currentIndex : {type : Number,default : 0},
+		size : {type : Number, default : 120},
+		fontSize : {type : String,default : '28rpx'},
+		activeFontSize : {type : String, default : '28rpx'},
+		items : {type : Array,default : function () {return []}},
+		activeLineBg : {type : String,default : "linear-gradient(to right, #66BFFF,#3388FF)"},
+		color : {type : String, default : "#333333"},
+		activeColor:{type : String,	default : "#333333"},
+		activeLineHeight : {type : String, default : '6rpx'},
+		activeLineWidth : {type : String, default : "36rpx"},
+		activeLineRadius : {type : String, default : "0rpx"},
+		activeDirection : {type : String, default : ""},
+		activeFontWeight: {type : Number, default : 700},
+		margin : {type : Number, default : 0},
+		textAlign : {type : String, default : ''},
+		lineHeight :{type : String, default : '50rpx'},
+		padding : {type : String, default : '0rpx'},
+		animatie:{type:Boolean, default:true},
+		autoLeft: {type : String, default : ''}
+	},
+	methods:{
+		navchang : function (e){
+			this.$emit('change', Number(e.currentTarget.dataset.index))
+		}
+	}
+}
+</script>
+<style scoped>
+/* #ifndef MP-ALIPAY */
+.grace-nav-bar{width:100%; display:flex; white-space:nowrap;}
+/* #endif */
+/* #ifdef MP-ALIPAY */
+.grace-nav-bar{width:100%; white-space:nowrap;}
+/* #endif */
+.nav-item{width:100rpx; display:inline-flex; flex-direction:column;}
+.nav-item-title{width:100%; color:#333333;}
+.nav-active{}
+.nav-active-line-wrap{display:flex;}
+.nav-active-line{margin-top:5rpx;}
+.grace-nav-center{justify-content:center; text-align:center;}
+@keyframes grace-nav-scale{0%{transform: scale(0.1);} 100%{transform: scale(1);}}
+.grace-nav-scale{animation:grace-nav-scale 300ms forwards;}
+</style>

+ 66 - 0
lib/graceUI/components/graceNavBar2.vue

@@ -0,0 +1,66 @@
+<template>
+	<scroll-view
+	:class="['grace-nav-bar2', isCenter ? 'grace-nav2-center' : '']" :scroll-x="true" :show-scrollbar="false" 
+	:scroll-into-view="'tab-'+currentIndex+autoLeft">
+		<view class="nav2-item" :id="'tab-'+index" :style="{width:size+'rpx', height:(lineHeight+lineHeightSamll+6)+'rpx'}" 
+		v-for="(item, index) in items" :key="index" @click="navchang" :data-index="index">
+			<view class="nav2-nav-body">
+				<text :class="['nav2-item-title', currentIndex == index ? 'nav-active' : '']"
+				:style="{
+					color:currentIndex == index ? activeColor : color, 
+					lineHeight:lineHeight+'rpx', fontSize:currentIndex == index ? activeFontSize : fontSize, 
+					background:bgColor,
+					fontWeight:currentIndex == index ? 'bold' : ''}">{{item.title}}</text>
+				<text :class="['nav2-item-desc', currentIndex == index ? 'nav-active' : '']"
+				:style="{color:currentIndex == index ? activeColor : descColor, lineHeight:lineHeightSamll+'rpx', fontSize:fontSizeSmall, background:bgColor}">{{item.desc}}</text>
+			</view>
+			<view class="nav2-active-line-in" :class="currentIndex == index ? 'grace-nav-fadein' : ''">
+				<view class="nav2-active-line" :style="{borderColor:currentIndex == index ? activeColor : bgColor}"></view>
+			</view>
+		</view>
+	</scroll-view>
+</template>
+<script>
+export default {
+	props:{
+		isCenter : {type : Boolean, default : false},
+		currentIndex : {type : Number,default : 0},
+		size : {type : Number, default : 138},
+		fontSize : {type : String,default : '28rpx'},
+		activeFontSize: {type : String,default : '30rpx'},
+		lineHeight :{type : Number, default : 52},
+		fontSizeSmall: {type : String,default : '22rpx'},
+		lineHeightSamll :{type : Number, default : 28},
+		items : {type : Array,default : function () {return []}},
+		color : {type : String, default : "#333333"},
+		descColor : {type : String, default : "#999999"},
+		activeColor:{type : String,	default : "#3688FF"},
+		bgColor:{type : String, default : '#FFFFFF'},
+		autoLeft: {type : String, default : ''}
+	},
+	methods:{
+		navchang : function (e){
+			this.$emit('change', Number(e.currentTarget.dataset.index))
+		}
+	}
+}
+</script>
+<style scoped>
+/* #ifndef MP-ALIPAY */
+.grace-nav-bar2{width:100%; display:flex; white-space:nowrap;}
+/* #endif */
+/* #ifdef MP-ALIPAY */
+.grace-nav-bar2{width:100%; white-space:nowrap;}
+/* #endif */
+.nav2-item{width:100rpx; display:inline-flex; flex-direction:column; padding-bottom:16rpx; position:relative;}
+.nav2-item-title{width:100%; color:#333333; display:block; text-align:center;}
+.nav2-item-desc{width:100%; color:#666666; display:block; text-align:center; padding-bottom:6rpx;}
+.nav2-active{font-weight:bold;}
+.nav2-active-line-wrap{display:flex;}
+.grace-nav2-center{justify-content:center; text-align:center;}
+@keyframes grace-nav-fadein{0%{transform: scale(0,0);} 100%{transform: scale(1,1);}}
+.grace-nav-fadein{animation:grace-nav-fadein 300ms forwards;}
+.nav2-active-line-in{position:absolute; left:0; bottom:2rpx; z-index:1; display:flex; flex-direction:row; justify-content:center; width:100%;}
+.nav2-active-line{width:20rpx; height:20rpx; border-radius:50rpx; border:6rpx solid #FF0000;}
+.nav2-nav-body{position:absolute; z-index:2; left:0; top:0; width:100%;}
+</style>

+ 71 - 0
lib/graceUI/components/graceNubmerAnimate.vue

@@ -0,0 +1,71 @@
+<template>
+	<text :style="{fontSize:fontSize, fontWeight:fontWeight, color:color, lineHeight:lineHeight}">{{numAnimate}}</text>
+</template>
+<script>
+export default{
+	props:{
+		num : {
+			type : Number,
+			default : 0
+		},
+		stepNumber : {
+			type : Number,
+			default : 50
+		},
+		timer : {
+			type : Number,
+			default : 800
+		},
+		keepInt : {
+			type : Boolean,
+			default : false
+		},
+		fontSize:{
+			type : String,
+			default : '28rpx'
+		},
+		color:{
+			type : String,
+			default : '#333333'
+		},
+		lineHeight:{
+			type : String,
+			default : '50rpx'
+		},
+		fontWeight:{
+			type : String,
+			default : '400'
+		}
+	},
+	data() {
+		return {
+			numAnimate : 0,
+			intervalId : null
+		}
+	},
+	created:function(){
+		let timer = this.timer / this.stepNumber;
+		let step  = Math.floor((this.num / this.stepNumber) * 100) / 100;
+		this.intervalId = setInterval(() => {
+			// 正值 
+			if(this.num >= 0){
+				if(this.numAnimate >= this.num){
+					this.numAnimate = this.num;
+					clearInterval(this.intervalId);
+					return;
+				}
+			}else{
+				if(this.numAnimate <= this.num){
+					this.numAnimate = this.num;
+					clearInterval(this.intervalId);
+					return;
+				}
+			}
+			let  res = this.numAnimate + step;
+			this.numAnimate = this.keepInt ? parseInt(res) : Math.floor(res * 100) / 100;
+		}, timer);
+	}
+}
+</script>
+<style scoped>
+</style>

+ 130 - 0
lib/graceUI/components/graceNumberBox.vue

@@ -0,0 +1,130 @@
+<template>
+	<view class="grace-number-box" :style="{width:width}">
+		<view class="grace-number-box-doBtn" @tap.stop="reduce" 
+		:style="{width:btnSize, height:btnSize, fontSize:btnFontSize, lineHeight:btnSize, color:btnColr}">-</view>
+		<input class="grace-number-box-input" type="number" v-model="inputNumber" :disabled="disabled" 
+		:style="{background:inputBG, height:inputHeight, lineHeight:inputHeight, fontSize:inputFontSize, color:inputColor, padding:inputPadding, borderRadius:inputBorderRadius}"></input>
+		<view class="grace-number-box-doBtn" @tap.stop="add" 
+		:style="{width:btnSize, height:btnSize, fontSize:btnFontSize, lineHeight:btnSize, color:btnColr}">+</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceNumberBox",
+	props: {
+		disabled :{
+			type : Boolean,
+			default : false
+		},
+		value : {
+			type : Number,
+			default : 0 
+		},
+		index : {
+			type : Number,
+			default : 0
+		},
+		maxNum:{
+			type: Number,
+			default: 999999
+		},
+		minNum: {
+			type: Number,
+			default: 0
+		},
+		datas:{
+			type: String,
+			default: ''
+		},
+		btnSize : {
+			type : String,
+			default : '60rpx'
+		},
+		btnFontSize : {
+			type : String,
+			default : '36rpx'
+		},
+		btnColr : {
+			type : String,
+			default : '#666666'
+		},
+		inputHeight : {
+			type : String,
+			default : '30rpx'
+		},
+		inputFontSize : {
+			type : String,
+			default : '26rpx'
+		},
+		inputColor : {
+			type : String,
+			default : '#333333'
+		},
+		inputBG : {
+			type : String,
+			default : '#F6F7F8'
+		},
+		inputPadding : {
+			type : String,
+			default : '10rpx'
+		},
+		inputBorderRadius : {
+			type : String,
+			default : '8rpx'
+		},
+		width:{
+			type : String,
+			default : '200rpx'
+		},
+		step : {
+			type : Number,
+			default : 1
+		}
+	},
+	data() {
+		return {
+			inputNumber: 0,
+			callbackNumber : 0
+		}
+	},
+	created:function(){
+		this.inputNumber = Number(this.value);
+		this.callbackNumber++;
+	},
+	watch:{
+		value :function(val, vo){this.inputNumber = Number(val);},
+		inputNumber :function(val, vo){
+			val = Number(val);
+			if(isNaN(val)){ setTimeout(()=>{this.inputNumber = Number(vo);}, 200);  return; }
+			if(val > this.maxNum){
+				this.$emit('change', [this.maxNum, this.index, this.datas]);
+				setTimeout(()=>{this.inputNumber = this.maxNum;}, 200);
+				return ;
+			}
+			if(val < this.minNum){
+				this.$emit('change', [this.minNum, this.index, this.datas]);
+				setTimeout(()=>{this.inputNumber = this.minNum;}, 200);
+				return ;
+			}
+			if(val != vo && this.callbackNumber > 0){this.$emit('change', [val, this.index, this.datas]);}
+		}
+	},
+	methods: {
+		add : function(){
+			var newVal = Number(this.inputNumber) + Number(this.step);
+			if(newVal > this.maxNum){return ;}
+			this.inputNumber = Number(newVal);
+		},
+		reduce: function () {
+			var newVal = Number(this.inputNumber) - Number(this.step);
+			if(newVal < this.minNum){return ;}
+			this.inputNumber = newVal;
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-number-box{display:flex; overflow:hidden; flex-wrap:nowrap; align-items:center;}
+.grace-number-box-doBtn{text-align:center; color:#666666; flex-shrink:0;}
+.grace-number-box-input{text-align:center; width:700rpx;}
+</style>

+ 82 - 0
lib/graceUI/components/graceNumberKeyboard.vue

@@ -0,0 +1,82 @@
+<template>
+	<view>
+		<view class="grace-mask" @tap.stop="done" @touchmove.stop="" v-if="show"></view>
+		<view class="grace-keyboard" v-if="show">
+			<view class="grace-keyboard-res" v-if="showInputRes" 
+			:style="{color:resultColor, fontSize:resultSize}">{{resVal}}</view>
+			<view class="grace-keyboard-body">
+				<view class="grace-keyboard-left">
+					<view class="grace-keyboard-keys" 
+					v-for="(item, index) in [1,2,3,4,5,6,7,8,9]" :key="index" 
+					:data-keynumber="item" hover-class="keydown" hover-stay-time="100" 
+					@tap.stop="inputNow">{{item}}</view>
+					<view class="grace-keyboard-keys" 
+					:style="{width:isPoint ? '259rpx':'538rpx'}" hover-class="keydown" hover-stay-time="100" 
+					data-keynumber="0" @tap.stop="inputNow">0</view>
+					<view v-if="isPoint" class="grace-keyboard-keys" 
+					style="width:259rpx;" hover-class="keydown" hover-stay-time="100" 
+					data-keynumber="." @tap.stop="inputNow">.</view>
+				</view>
+				<view class="grace-keyboard-right">
+					<view class="grace-keyboard-keys grace-keyboard-delete grace-icons" 
+					hover-class="keydown" hover-stay-time="100" @tap.stop="deleteNow"></view>
+					<view class="grace-keyboard-keys grace-keyboard-done" @tap.stop="done" 
+					:style="{background:confirmBtnColor}">{{doneBtnName}}</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		show : { type : Boolean, default : false },
+		doneBtnName   : { type : String, default : "完成" },
+		isPoint       : { type : Boolean, default : true },
+		showInputRes  : {type : Boolean, default : true},
+		value         : {type:String, default:''},
+		confirmBtnColor:{type:String, default:'#3688FF'},
+		resultColor   : {type:String, default:'#323232'},
+		resultSize    : {type:String, default:'32rpx'}
+	},
+	data() {
+		return {
+			resVal: ''
+		}
+	},
+	created:function(){
+		this.resVal = this.value;
+	},
+	methods: {
+		inputNow : function (e){
+			var k = e.currentTarget.dataset.keynumber;
+			this.resVal += k+'';
+			this.$emit('keyboardInput', k);
+		},
+		deleteNow : function (e){
+			this.resVal = this.resVal.substring(0, this.resVal.length - 1);
+			this.$emit('keyboardDelete');
+		},
+		done : function(e){
+			console.log('ok');
+			this.$emit('keyboardDone');
+		},
+		setVal : function (val) {
+			this.resVal = val;
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-mask{background:rgba(255, 255, 255, 0); position:fixed; width:100%; height:100%; left:0; top:0; z-index:1;}
+.grace-keyboard{background:#F4F5F6; position:fixed; width:100%; height:auto; left:0; bottom:0; z-index:3;}
+.grace-keyboard-body{display:flex; flex-direction:row; justify-content:space-between; padding:10px 0;}
+.grace-keyboard-left{width:560rpx; display:flex; flex-direction:row; flex-wrap:wrap; justify-content:space-between;}
+.grace-keyboard-right{width:188rpx; display:flex; flex-direction:column; justify-content:space-between;}
+.grace-keyboard-keys{width:166rpx; height:100rpx; margin:10rpx; background:#FFFFFF; text-align:center; line-height:100rpx; border-radius:8rpx; font-weight:700; font-size:50rpx;}
+.grace-keyboard-done{height:340rpx !important; line-height:340rpx !important; font-size:36rpx !important; font-weight:400 !important; color:#FFFFFF !important;}
+.grace-keyboard-delete{font-family:"grace-iconfont";}
+.grace-keyboard-delete:before{content:"\e623";}
+.keydown{background:#3688FF !important; color:#FFFFFF !important;}
+.grace-keyboard-res{line-height:60rpx; text-align:center; font-size:32rpx; font-weight:bold; padding-top:20rpx;}
+</style>

+ 61 - 0
lib/graceUI/components/graceNvueActionSheet.vue

@@ -0,0 +1,61 @@
+<template>
+	<view class="">
+	<view class="graceActionSheet" @tap.stop="closeByShade" @touchmove.stop="stopFun" 
+	:style="{backgroundColor:background}" v-if="realShow"></view>
+	<view :class="['graceActionSheetBody', isIpx ? 'grace-ipx-bottom' : '']"
+	:style="{width:realShow ? width : '0rpx',left:left,borderTopLeftRadius:borderRadius,borderTopRightRadius:borderRadius}" 
+	@tap.stop="stopFun" v-if="realShow">
+		<text class="graceActionSheetTitle border-bottom" :style="{color:titleColor}">{{title}}</text>
+		<text class="graceActionSheetList border-bottom" v-for="(item, index) in items" :key="index" @tap.stop="selected" :data-index="index" 
+		:style="{color:listColor, lineHeight:listLineHeight, fontSize:listFontSize}">{{item}}</text>
+		<text class="graceActionSheetList" :style="{border:'none', color:cancelBtnColor}" @tap.stop="cancel">{{cancelBtnName}}</text>
+	</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		width:{type:String,default:'720rpx'},
+		left:{type:String,default:'15rpx'},
+		background:{type : String,default : 'rgba(0, 0, 0, 0.3)'},
+		borderRadius : {type : String,default : '10rpx'},
+		zIndex:{type : Number,default : 99},
+		title:{type:String,default:''},
+		titleColor:{type:String, default:'#323232'},
+		items:{type:Array, default:function(){return [];}},
+		listColor:{type:String, default:'#3688FF'},
+		listLineHeight:{type:String, default:'100rpx'},
+		listFontSize:{type:String, default:'30rpx'},
+		cancelBtnName:{type:String,default:'取消'},
+		cancelBtnColor:{type:String, default:'#999999'}
+	},
+	data() {
+		return {
+			realShow : false,
+			isIpx:false
+		}
+	},
+	methods:{
+		cancel:function(){
+			this.realShow = false;
+			this.$emit('cancel')
+		},
+		closeByShade: function(){this.cancel();},
+		stopFun : function(){},
+		selected:function (e) {
+			this.$emit('selected', e.currentTarget.dataset.index);
+			this.close();
+		},
+		close:function(){this.realShow = false;},
+		show:function(){this.realShow = true;}
+	}
+}
+</script>
+<style scoped>
+.graceActionSheet{position:fixed; width:750rpx; left:0; top:0; bottom:0;}
+.graceActionSheetBody{background-color:#FFFFFF; position:fixed; bottom:0; left:0; overflow:hidden;}
+.graceActionSheetTitle{text-align:center; font-size:26rpx; line-height:120rpx; height:120rpx; margin:0 30rpx; font-weight:bold;}
+.grace-ipx-bottom{padding-bottom:50rpx !important;}
+.graceActionSheetList{line-height:120rpx; font-size:26rpx; font-weight:600; overflow:hidden; text-align:center; margin:0 30rpx;}
+.border-bottom{border-bottom-width:1px; border-style:solid; border-color:#F8F8F8;}
+</style>

+ 118 - 0
lib/graceUI/components/graceNvueAddressPicker.vue

@@ -0,0 +1,118 @@
+<template>
+	<view class="gap" v-if="show" @touchmove.stop="" @tap.stop="" :style="{backgroundColor:background}">
+		<view class="gap-body">
+			<view class="gap-header grace-space-between">
+				<text class="gap-header-btn" :style="{color:cancelTColor}" @tap="close">{{cancelText}}</text>
+				<text class="gap-header-btn" :style="{textAlign:'right', color:confirmColor}" @tap="confirm">{{confirmText}}</text>
+			</view>
+			<picker-view :indicator-style="indicatorStyle" class="gap-main" :value="defaultVal" @change="change">
+				<picker-view-column v-if="level >= 1">
+					<text class="gap-item" v-for="(item, index) in province" :key="index">{{item.label}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="level >= 2">
+					<text class="gap-item" v-for="(item, index) in city[defaultVal[0]]" :key="index">{{item.label}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="level >= 3">
+					<text class="gap-item" v-for="(item, index) in area[defaultVal[0]][defaultVal[1]]" :key="index">{{item.label}}</text>
+				</picker-view-column>
+			</picker-view>
+		</view>
+	</view>
+</template>
+<script>
+import provinceData from '../data/city-data/province.js';
+import cityData from '../data/city-data/city.js';
+import areaData from '../data/city-data/area.js';
+export default {
+	name: "graceAddressPicker",
+	props: {
+		show : { type : Boolean, default : true },
+		background:{ type : String, default : 'rgba(0, 0, 0, 0.5)' },
+		cancelText : { type : String, default : '取消' },
+		cancelTColor : { type : String, default : '#888888' },
+		confirmText : { type : String, default : '确定' },
+		confirmColor : { type : String, default : '#3688FF' },
+		value : { type : Array , default () { return ['', '', ''] }},
+		level : {type : Number, default : 3}
+	},
+	data() {
+		return {
+			indicatorStyle : 'height:35px',
+			province       : provinceData,
+			city           : cityData,
+			area           : areaData,
+			defaultVal     : [0,0,0]
+		}
+	},
+	created() {
+		this.setDefault();
+	},
+	watch:{
+		value:function(nv, ov){this.setDefault();},
+		defaultVal : function(nv, ov){
+			if(ov[0] != nv[0]){
+				this.defaultVal.splice(1,1,0);
+				this.defaultVal.splice(2,1,0);
+			}else if(ov[1] != nv[1]){
+				this.defaultVal.splice(2,1,0);
+			}
+		}
+	},
+	methods: {
+		setDefault:function(){
+			if(this.value[0] == ''){return ;}
+			setTimeout(()=>{
+				this.defaultVal.splice(0, 1, this.arrayIndexOf(this.province, this.value[0]));
+				if(this.value[1] == ''){return ;}
+				setTimeout(()=>{
+					this.defaultVal.splice(1,1, this.arrayIndexOf(this.city[this.defaultVal[0]], this.value[1]));
+					if(this.value[2] == ''){return ;}
+					setTimeout(()=>{
+						this.defaultVal.splice(2,1, this.arrayIndexOf(this.area[this.defaultVal[0]][this.defaultVal[1]], this.value[2]));
+					}, 500);
+				}, 500);
+			}, 500);
+			
+		},
+		arrayIndexOf : function(arr, needFind){
+			var index = 0;
+			for(let i = 0; i < arr.length; i++){if(arr[i].label == needFind){index = i; return i;}}
+			return index;
+		},
+		change : function (e) {
+			var res = e.detail.value;
+			if(!res[0]){res[0] = 0;}
+			if(!res[1]){res[1] = 0;}
+			if(!res[2]){res[2] = 0;}
+			this.defaultVal = res;
+		},
+		confirm:function () {
+			var codes = [
+				provinceData[this.defaultVal[0]].value,
+				cityData[this.defaultVal[0]][this.defaultVal[1]].value,
+				areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].value ? areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].value : 0
+			];
+			var names = [
+				provinceData[this.defaultVal[0]].label,
+				cityData[this.defaultVal[0]][this.defaultVal[1]].label,
+				areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].label ? areaData[this.defaultVal[0]][this.defaultVal[1]][this.defaultVal[2]].label : ''
+			];
+			codes = codes.splice(0, this.level);
+			names = names.splice(0, this.level);
+			var res = {codes : codes, names : names, indexs : this.defaultVal};
+			this.$emit('confirm', res);
+		},
+		close : function () {
+			this.$emit('close');
+		}
+	}
+}
+</script>
+<style scoped>
+.gap{position:fixed; width:750rpx; flex:1; top:0; left:0; bottom:0;}
+.gap-body{background-color:#FFFFFF; position:absolute; bottom:0; left:0; width:750rpx;}
+.gap-header{padding:25rpx;}
+.gap-header-btn{width:200rpx; line-height:38rpx; height:38rpx; font-size:28rpx;}
+.gap-main{width:750rpx; height:280px;}
+.gap-item{height:35px; font-size:12px; line-height:35px; overflow:hidden; text-align:center;}
+</style>

+ 67 - 0
lib/graceUI/components/graceNvueAnalysis.vue

@@ -0,0 +1,67 @@
+<template>
+	<view class="grace-editor-show">
+		<view v-for="(item, index) in article" :key="index" class="grace-editor-show-item">
+			<text class="grace-editor-show-txt" v-if="item.type == 'txt'">{{item.content}}</text>
+			<view class="grace-flex-center" v-else-if="item.type == 'center'">
+				<text class="grace-editor-show-center">{{item.content}}</text>
+			</view>
+			<image class="grace-editor-show-image" :src="item.content" :data-index="index" @load="imgLoad" 
+			:style="{width:'700rpx', height:item.height?item.height:''}" 
+			v-else-if="item.type == 'img'" :data-url="item.content" @tap="showImgs" mode="widthFix"></image>
+			<text class="grace-editor-show-quote" v-else-if="item.type == 'quote' || item.type == 'pre'">{{item.content}}</text> 
+			<text class="grace-editor-show-strong" v-else-if="item.type == 'strong'">{{item.content}}</text>
+			<view class="grace-editor-show-link" v-else-if="item.type == 'link'">
+				<graceLink :url="item.content" :title="item.content"></graceLink>
+			</view>
+			<text class="grace-editor-show-spline" v-else-if="item.type == 'spline'">● ● ●</text>
+		</view>
+	</view>
+</template>
+<script>
+import graceLink from "../../graceUI/weexComponents/graceLink.nvue";
+export default{
+	props:{
+		article:{
+			type : Array,
+			default : function(){return new Array();}
+		}
+	},
+	components:{
+		graceLink
+	},
+	methods: {
+		imgLoad:function (e) {
+			var index  = e.currentTarget.dataset.index;
+			var width  = e.detail.width;
+			var height = e.detail.height;
+			this.article[index].height = (700 * (height / width)) + 'rpx';
+			this.article.splice(index, 1, this.article[index]);
+		},
+		showImgs:function(e){
+			var currentUrl = e.currentTarget.dataset.url;
+			var imgs       = [];
+			var items      = this.article;
+			for(let i = 0; i < items.length; i++){
+				if(items[i].type ==  'img'){
+					imgs.push(items[i].content);
+				}
+			}
+			uni.previewImage({
+				urls:imgs,
+				current:currentUrl
+			})
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-editor-show{}
+.grace-editor-show-item{font-size:0;}
+.grace-editor-show-txt{line-height:52rpx; font-size:26rpx; color:#333333;}
+.grace-editor-show-image{margin:10rpx 0; width:700rpx;}
+.grace-editor-show-quote{margin:10rpx 0; color:#333333; background-color:#F8F8F8; line-height:36rpx; font-size:26rpx; padding:20rpx;}
+.grace-editor-show-strong{font-weight:bold; font-size:30rpx; line-height:45rpx; padding:10px 0; color:#3D3D3D;}
+.grace-editor-show-spline{text-align:center; color:#8788A3; line-height:60rpx; font-size:20rpx; opacity:0.3;}
+.grace-editor-show-center{text-align:center; font-size:28rpx; color:#333333; line-height:60rpx; padding:15rpx; border-bottom-width:1rpx; border-bottom-style:solid; border-bottom-color:#C1C1C1; font-weight:900;}
+.grace-editor-show-link{line-height:60rpx; color:#007AFF;}
+</style>

+ 24 - 0
lib/graceUI/components/graceNvueBorderRadius.vue

@@ -0,0 +1,24 @@
+<template>
+	<view class="grace-border-radius" 
+	:style="{
+		backgroundImage:background, 
+		borderTopLeftRadius:radius[0],
+		borderTopRightRadius:radius[1],
+		borderBottomRightRadius:radius[2],
+		borderBottomLeftRadius:radius[3]}">
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		radius : {type:Array, default:function(){
+			return['10px', '10px', '10px', '10px']
+		}},
+		background:{type:String, default:""}
+	}
+}
+</script>
+<style>
+.grace-border-radius{}
+</style>

+ 44 - 0
lib/graceUI/components/graceNvueBottomDialog.vue

@@ -0,0 +1,44 @@
+<template>
+	<view class="grace-btdialog-shade" v-if="show" @tap.stop="closeDialog" @touchmove.stop="stopFun" :style="{backgroundColor:background}">
+		<view class="grace-btdialog-shade-dialog" @tap.stop="stopFun" 
+		:style="{borderTopLeftRadius:borderRadius,borderTopRightRadius:borderRadius,width:width,left:left}">
+			<view class="title"><slot name="btns"></slot></view>
+			<view class="content" @tap.stop="stopFun"><slot name="content"></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		width:{type:String,default:'750rpx'},
+		left:{type:String,default:'0rpx'},
+		show : {
+			type : Boolean,
+			default : false
+		},
+		background:{
+			type : String,
+			default : 'rgba(0, 0, 0, 0.5)'
+		},
+		borderRadius : {
+			type : String,
+			default : '0rpx'
+		}
+	},
+	data() {
+		return {}
+	},
+	methods:{
+		closeDialog : function(){
+			this.$emit('closeDialog');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style scoped>
+.grace-btdialog-shade{position:fixed; width:750rpx; left:0; top:0; bottom:0; background-color:rgba(0, 0, 0, 0.5);}
+.grace-btdialog-shade-dialog{width:750rpx; background-color:#FFFFFF; position:absolute; bottom:0; left:0;}
+.grace-btdialog-shade-title{flex-direction:row; flex-wrap:nowrap; justify-content:space-between;}
+.grace-btdialog-shade-content{}
+</style>

+ 71 - 0
lib/graceUI/components/graceNvueBoxBanner.vue

@@ -0,0 +1,71 @@
+<template>
+	<view class="grace-box-banner" :style="{backgroundColor:background, paddingTop:padding, paddingBottom:padding, borderRadius:borderRadius}">
+	    <view class="grace-box-items" v-for="(item, index) in items" :key="index" @tap.stop="taped(index)"
+		:style="{
+			borderRightStyle : index+1 >= items.length ? '0rpx' : border[0],
+			borderRightWidth : index+1 >= items.length ? '0rpx' : border[2],
+			borderRightColor : index+1 >= items.length ? '0rpx' : border[1],
+		}">
+			<view class="grace-flex-center grace-box-line1">
+				<text :style="{color:color[0],fontSize:fontSize[0], lineHeight:lineHeight}">{{item[0]}}</text>
+				<text :style="{color:color[1],fontSize:fontSize[1], lineHeight:lineHeight}" style="margin-left:3px;">{{item[1]}}</text>
+			</view>
+	        <text class="grace-box-line2" :style="{color:color[2],fontSize:fontSize[2]}">{{item[2]}}</text>
+	    </view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		items:{
+			type : Array,
+			default : function () {
+				return []
+			}
+		},
+		color:{
+			type : Array,
+			default : function () {
+				return ['#333333', '#999999', '#999999']
+			}
+		},
+		fontSize:{
+			type : Array,
+			default : function () {
+				return ['36rpx', '24rpx', '24rpx']
+			}
+		},
+		background : {
+			type : String,
+			default : ''
+		},
+		padding:{
+			type : String,
+			default : '25rpx'
+		},
+		border:{
+			type:Array,
+			default:function () {return ["none","",""];}
+		},
+		borderRadius : {
+			type:String,
+			default:'10rpx'
+		},
+		lineHeight:{
+			type : String,
+			default : '50rpx'
+		}
+	},
+	methods:{
+		taped:function (index) {
+			this.$emit('taped', index);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-box-banner{flex-wrap:nowrap; flex-direction:row; overflow:hidden;}
+.grace-box-items{justify-content:center; width:700rpx; flex:1; text-align:center;}
+.grace-box-line1{align-items:center; padding-top:2px;}
+.grace-box-line2{margin-top:5px; text-align:center; padding-bottom:2px;}
+</style>

+ 58 - 0
lib/graceUI/components/graceNvueCheckBtn.vue

@@ -0,0 +1,58 @@
+<template>
+	<view class="grace-nowrap grace-flex-vcenter" @tap.stop="changeStatus">
+		<text v-if="status" class="grace-check-btn grace-icons" :style="{fontSize:size+'rpx', lineHeight:size+'rpx', color : checkedColor}">&#xe7f8;</text>
+		<text v-else class="grace-check-btn grace-icons" :style="{fontSize:size+'rpx', lineHeight:size+'rpx', color : color}">&#xe762;</text>
+		<view class="grace-check-lable"><slot></slot></view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		size : {
+			type : Number,
+			default : 38
+		},
+		color : {
+			type : String,
+			default : '#EEEEEE'
+		},
+		checked : {
+			type : Boolean,
+			default : false
+		},
+		checkedColor : {
+			type : String,
+			default : '#FF0036'
+		},
+		parameter : {
+			type : Array,
+			default : function () {
+				return []
+			}
+		}
+	},
+	data() {
+		return {
+			status : false
+		}
+	},
+	watch: {
+		checked : function (val, old) {
+			this.status = val;
+		}
+	},
+	created : function(){
+		this.status = this.checked;
+	},
+	methods:{
+		changeStatus : function(){
+			this.status = !this.status;
+			this.$emit('change', [this.status, this.parameter]);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-check-btn{color:#999999;}
+.grace-check-lable{color:#555555; margin-left:20rpx; font-size:26rpx; width:700rpx; flex:1;}
+</style>

+ 119 - 0
lib/graceUI/components/graceNvueCheckList.vue

@@ -0,0 +1,119 @@
+<template>
+	<view class="check-list" :style="{height:height+'px'}">
+		<scroll-view scroll-y="true" :style="{height:height+'px'}">
+			<view v-for="(item, index) in dataIn" :key="index" class="check-list-item" :data-index="index" @tap.stop="choose">
+				<text class="check-list-icon grace-icons" v-if="item.checked && batch" :style="{color:checkColor}">&#xe7f8;</text>
+				<text class="check-list-icon grace-icons" v-if="!item.checked && batch" style="color:#D2D2D2;">&#xe762;</text>
+				<view class="check-list-body" :class="[isBorder && index < dataIn.length - 1 ? 'grace-border-b':'']">
+					<image :src="item.img" class="check-list-img" v-if="item.img" mode="widthFix" :style="{width:imgSize[0], height:imgSize[1],borderRadius:imgBordeRadius}"></image>
+					<view class="check-list-content">
+						<text class="check-list-content-title" :style="{fontSize:fontSize, color:itemColor}">{{item.title}}</text>
+						<text class="check-list-content-desc" :style="{color:smallTextColor, fontSize:smallTextSize}">{{item.desc}}</text>
+					</view>
+				</view>
+			</view>
+			<view style="height:120rpx;" v-if="batch"></view>
+		</scroll-view>
+		<view class="check-list-footer" v-if="height > 0 && batch">
+			<text class="grace-icons check-list-icon" v-if="sedAll" :style="{color:checkColor}" @tap.stop="selectAll">&#xe7f8;</text>
+			<text class="grace-icons check-list-icon " v-if="!sedAll" @tap.stop="selectAll">&#xe762;</text>
+			<text class="check-list-footer-text" @tap.stop="selectAll">全选 {{sedNumbers > 0 ? sedNumbers +'/'+ dataIn.length : ''}}</text>
+			<view class="check-list-footer-btns"><slot></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		height:{type:Number, default:0},
+		lists : {type:Array, default:function(){return [];}},
+		checkColor:{type:String, default:"#3688FF"},
+		imgSize:{type:Array, default:function(){return ['68rpx','68rpx'];}},
+		isBorder:{type:Boolean, default:true},
+		fontSize:{type: String, default: "28rpx"},
+		itemColor:{type:String, default:"#323232"},
+		imgBordeRadius:{type: String, default: "8rpx"},
+		smallTextColor:{type:String, default:"#999999"},
+		smallTextSize:{type:String, default:"24rpx"},
+		batch:{type:Boolean, default:false}
+	},
+	data() {
+		return {
+			dataIn: [],
+			sedAll:false,
+			sedNumbers:0
+		}
+	},
+	created:function(){
+		this.dataIn = this.lists;
+	},
+	watch:{
+		lists:function(val){
+			this.dataIn = val;
+		},
+		dataIn:function(val){
+			var sedNumber = 0;
+			for(let i = 0; i < this.dataIn.length; i++){
+				if(this.dataIn[i].checked){
+					sedNumber++;
+				}
+			}
+			this.sedNumbers = sedNumber;
+			if(sedNumber >= val.length){this.sedAll = true;}else{this.sedAll = false;}
+		}
+	},
+	methods:{
+		choose:function(e){
+			var index = e.currentTarget.dataset.index;
+			if(this.dataIn[index].checked){
+				this.dataIn[index].checked = false;
+				this.dataIn.splice(index, 1, this.dataIn[index]);
+			}else{
+				this.dataIn[index].checked = true;
+				this.dataIn.splice(index, 1, this.dataIn[index]);
+			}
+			this.dataIn = this.dataIn;
+			var sedArr = [];
+			for(let i = 0; i < this.dataIn.length; i++){
+				if(this.dataIn[i].checked){
+					sedArr.push(i);
+				}
+			}
+			this.$emit('change', sedArr);
+		},
+		selectAll : function(){
+			if(this.sedAll){
+				for(let i = 0; i < this.dataIn.length; i++){
+					this.dataIn[i].checked = false;
+					this.dataIn.splice(i, 1, this.dataIn[i]);
+				}
+				this.sedAll = false;
+				this.$emit('change', []);
+			}else{
+				var sedArr = [];
+				for(let i = 0; i < this.dataIn.length; i++){
+					this.dataIn[i].checked = true;
+					this.dataIn.splice(i, 1, this.dataIn[i]);
+					sedArr.push(i);
+				}
+				this.sedAll = true;
+				this.$emit('change', sedArr);
+			}			
+		}
+	}
+}
+</script>
+<style>
+.check-list{position:relative;}
+.check-list-item{flex-direction:row; flex-wrap:nowrap; align-items:center; font-size:0; background-color:#FFFFFF; padding:0 25rpx;}
+.check-list-icon{width:60rpx; line-height:50rpx; text-align:center; font-size:36rpx; color:#D2D2D2; font-weight:700; margin-right:15rpx;}
+.check-list-img{border-radius:10rpx; margin-right:28rpx;}
+.check-list-body{width:300rpx; flex:1; flex-wrap:nowrap; flex-direction:row; align-items:center;}
+.check-list-content{width:200rpx; flex:1; overflow:hidden; padding:22rpx 0;}
+.check-list-content-title{line-height:44rpx; font-size:28rpx;}
+.check-list-content-desc{line-height:36rpx; font-size:22rpx;}
+.grace-border-b{border-color:#F4F5F6;}
+.check-list-footer{background-color:#FFFFFF; position:absolute; left:0; bottom:0; width:750rpx; padding-top:10rpx; padding-bottom:10rpx; padding-left:25rpx; padding-right:25rpx; flex-direction:row; flex-wrap:nowrap; align-items:center;}
+.check-list-footer-text{font-size:28rpx; color:#323232; line-height:36rpx;}
+.check-list-footer-btns{width:200rpx; flex:1; margin-left:28rpx;}
+</style>

+ 126 - 0
lib/graceUI/components/graceNvueCountDown.vue

@@ -0,0 +1,126 @@
+<template name="graceCountDown">
+	<view class="grace-countdown" v-if="show">
+		<text class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, backgroundColor:bgrColor}" v-if="d > 0">{{d}}</text>
+		<text class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}" v-if="d > 0">{{splitorText[0]}}</text>
+		<text class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, backgroundColor:bgrColor}" v-if="(h != '00' || zeroShow)">{{h}}</text>
+		<text class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}" v-if="(h != '00' || zeroShow)">{{splitorText[1]}}</text>
+		<text class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, backgroundColor:bgrColor}">{{i}}</text>
+		<text class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}">{{splitorText[2]}}</text>
+		<text class="grace-countdown-numbers" :style="{borderColor:borderColor, width:width, height:width, fontSize:fontSize, lineHeight:width, color:fontColor, backgroundColor:bgrColor}">{{s}}</text>
+		<text class="grace-countdown-splitor" :style="{color:splitorColor, lineHeight:width, fontSize:fontSize}">{{splitorText[3]}}</text>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceCountDown",
+	props: {
+		bgrColor: {
+			type: String,
+			default: "#FFFFFF"
+		},
+		borderColor:{
+			type:String,
+			default : "#000000"
+		},
+		fontColor: {
+			type: String,
+			default: "#000000"
+		},
+		fontSize: {
+			type: String,
+			default: "22rpx"
+		},
+		splitorColor: {
+			type: String,
+			default: "#000000"
+		},
+		timer:{
+			type:String,
+			default:""
+		},
+		width : {
+			type:String,
+			default:"40rpx"
+		},
+		splitorText : {
+			type : Array,
+			default : function () {
+				return [':', ':', ':', '']
+			}
+		},
+		show:{type:Boolean, default:true},
+		zeroShow:{type:Boolean, default:true}
+	},
+	data() {
+		return {
+			d        : 0,
+			h        : "",
+			i        : "",
+			s        : "",
+			leftTime : 0,
+			outTimer : null,
+			leftTimeNum : 0
+		}
+	},
+	created:function(){
+		this.runbase();
+	},
+	updated:function(){
+		this.runbase();
+	},
+	methods: {
+		runbase : function(){
+			var reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;
+			var res = this.timer.match(reg);
+			if (res == null){  return false; }
+			var year = parseInt(res[1]);
+			if (year < 1000) { return false; }
+			var month = parseInt(res[2]);
+			var day = parseInt(res[3]);
+			var h = parseInt(res[4]);
+			if (h < 0 || h > 24) { return false; }
+			var i = parseInt(res[5]);
+			if (i < 0 || i > 60) { return false; }
+			var s = parseInt(res[6]);
+			if (s < 0 || s > 60) { return false; }
+			var leftTime = new Date(year, month - 1, day, h, i, s);
+			this.leftTime = leftTime;
+			clearTimeout(this.outTimer);
+			this.countDown();
+		},
+		countDown: function (){
+			var leftTime = this.leftTime - new Date();
+			this.leftTimeNum = leftTime;
+			if (leftTime > 0) {
+				var day     =  parseInt(leftTime / (1000 * 60 * 60 * 24));
+				var hours   = parseInt((leftTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+				var minutes = parseInt((leftTime % (1000 * 60 * 60)) / (1000 * 60));
+				var seconds = parseInt((leftTime % (1000 * 60)) / 1000);
+				if (hours   < 10) { hours = '0' + hours;}
+				if (minutes < 10) { minutes = '0' + minutes; }
+				if (seconds < 10) { seconds = '0' + seconds; }
+				this.h = hours; this.i = minutes; this.s = seconds; this.d = day;
+				this.outTimer = setTimeout(()=>{this.countDown();}, 1000);
+			}else{
+				clearTimeout(this.outTimer);
+				this.h = '00'; this.i = '00'; this.s = '00'; this.d = 0;
+				this.$emit('endDo');
+			}
+		},
+		reSetTimer : function(timer){
+			clearTimeout(this.outTimer);
+			this.timer = timer;
+			this.runbase();
+		},
+		getTimeRemaining : function(){
+			if(this.leftTimeNum < 0){return 0;}
+			return parseInt(this.leftTimeNum / 1000);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-countdown{flex-wrap:nowrap; justify-content:center; flex-direction:row;}
+.grace-countdown-splitor{justify-content:center; padding:0 5rpx;}
+.grace-countdown-numbers{border-radius:8rpx; margin:0 5rpx; text-align:center; border-width:1px; border-style:solid; border-color:#000000; font-size:22rpx;}
+</style>

+ 51 - 0
lib/graceUI/components/graceNvueCoupons.vue

@@ -0,0 +1,51 @@
+<template>
+	<view class="grace-coupons" :style="{backgroundColor:coupon.color}">
+		<view class="grace-coupons-left" :style="{backgroundColor:coupon.ltBg, height:coupon.height}">
+			<view class="grace-coupons-number">
+				<view class="grace-coupons-number-show">
+					<text class="grace-coupons-unit" :style="{color:coupon.color}">{{coupon.unit}}</text>
+					<text class="grace-coupons-text" :style="{color:coupon.color}">{{coupon.number}}</text>
+				</view>
+				<text class="grace-coupons-left-txt" :style="{color:coupon.color}">{{coupon.txt}}</text>
+			</view>
+			<view class="grace-coupons-body">
+				<text class="grace-coupons-title">{{coupon.title}}</text>
+				<text class="grace-coupons-desc">{{coupon.desc}}</text>
+			</view>
+			<text class="grace-coupons-status" v-if="coupon.drawed" :style="{backgroundColor:coupon.color}">{{coupon.drawed}}</text>
+		</view>
+		<text class="grace-coupons-right" :style="{height:coupon.height, lineHeight:coupon.height}">{{coupon.btn}}</text>
+		<view class="grace-coupons-sawtooth" :style="{height:coupon.height}">
+			<view class="grace-coupons-circular" v-for="(item, index) in '1234567890'" :key="index"></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		coupon : {
+			type : Object,
+			default :  function  () {
+				return {}
+			}
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-coupons{flex-direction:row; flex-wrap:nowrap; border-top-left-radius:10rpx; border-bottom-left-radius:10rpx; margin-top:20rpx;}
+.grace-coupons-left{width:700rpx; height:150rpx; flex:1; padding-right:10rpx; flex-direction:row; justify-content:space-between; align-items:center; border-radius:8rpx; position:relative;}
+.grace-coupons-number{width:200rpx; border-right-width:1px; border-right-style:dashed; border-right-color:#D2D2D2;}
+.grace-coupons-number-show{flex-direction:row; flex-wrap:nowrap; align-items:flex-end; justify-content:center;}
+.grace-coupons-text{color:#828282; font-size:50rpx;}
+.grace-coupons-unit{font-size:32rpx;}
+.grace-coupons-left-txt{font-size:26rpx; text-align:center;}
+.grace-coupons-body{width:700rpx; margin-left:30rpx; flex:1;}
+.grace-coupons-title{font-size:30rpx; line-height:46rpx;}
+.grace-coupons-desc{color:#999999; margin-top:3px; font-size:24rpx; line-height:36rpx;}
+.grace-coupons-status{width:180rpx; padding:0 10px; height:36rpx; line-height:36rpx; font-size:20rpx; position:absolute; right:6px; top:5px; border-radius:36rpx; text-align:center; color:#FFFFFF;}
+.grace-coupons-right{width:150rpx; height:150rpx; font-size:30rpx; color:#FFFFFF; flex-direction:row; text-align:center;}
+.grace-coupons-sawtooth{width:8px; height:150rpx; overflow:hidden;}
+.grace-coupons-circular{width:10px; margin:2px 5px; height:10px; border-radius:10px; background-color:#F9F9F9;}
+
+</style>

+ 244 - 0
lib/graceUI/components/graceNvueDate.vue

@@ -0,0 +1,244 @@
+<template name="graceDate">
+	<view class="grace-date" v-if="show" :style="{top:top}" @tap.stop="" @touchmove.stop="">
+		<view class="grace-date-header">
+			<text class="grace-date-header-btn grace-icons" @click="prevYear">&#xe600;&#xe600;</text>
+			<text class="grace-date-header-btn grace-icons" @click="prevMonth">&#xe600;</text>
+			<text class="grace-date-header-date grace-icons">{{cYear}} 年 {{cMonth}} 月</text>
+			<text class="grace-date-header-btn grace-icons" @click="nextMonth">&#xe601;</text>
+			<text class="grace-date-header-btn grace-icons" @click="nextYear">&#xe601;&#xe601;</text>
+		</view>
+		<view class="grace-date-week">
+			<text class="grace-date-weeks" v-for="(item, index) in weeks" :key="index">{{item}}</text>
+		</view>
+		<view class="grace-date-days">
+			<view :class="['grace-date-ditems', currentDayIn == cYear+'-'+cMonthStr+'-'+ item.date ? 'grace-d-current' : '']" 
+			:style="{backgroundColor: currentDayIn == cYear+'-'+cMonthStr+'-'+ item.date ? activeBgColor : bgColor, borderRadius:borderRadius}" 
+			@click="chooseDate(cYear+'-'+cMonthStr+'-'+item.date, item.date)" v-for="(item, index) in days" :key="index">
+				<text class="grace-date-day" :class="[currentDayIn == (cYear+'-'+cMonthStr+'-'+item.date) ? 'grace-d-current-txt' : '']">{{item.date}}</text>
+				<text class="grace-date-nl" v-if="isLunar" :class="[currentDayIn == (cYear+'-'+cMonthStr+'-'+item.date) ? 'grace-d-current-txt' : '']">{{item.nl}}</text>
+			</view>
+		</view>
+		<view class="grace-nowrap grace-flex-center" style="margin-top:50rpx;" v-if="isTime">
+			<picker mode="time" @change="timechange" :value="currentTimeIn">
+				<text class="grace-date-time">时间 : {{currentTimeIn}}</text>
+			</picker>
+		</view>
+		<view class="grace-date-btns" v-if="isTime">
+			<text class="grace-date-btns-text" @click="close" style="color:#888888;">关闭</text>
+			<text class="grace-date-btns-text" @click="submit">确定</text>
+		</view>
+	</view>
+</template>
+<script>
+var CalendarData = new Array(100);
+var madd = new Array(12);
+var tgString = "甲乙丙丁戊己庚辛壬癸";
+var dzString = "子丑寅卯辰巳午未申酉戌亥";
+var numString = "一二三四五六七八九十";
+var monString = "正二三四五六七八九十冬腊";
+var weekString = "日一二三四五六";
+var sx = "鼠牛虎兔龙蛇马羊猴鸡狗猪";
+var cYear, cMonth, cDay, TheDate;
+CalendarData = new Array(0xA4B, 0x5164B, 0x6A5, 0x6D4, 0x415B5, 0x2B6, 0x957, 0x2092F, 0x497, 0x60C96, 0xD4A, 0xEA5, 0x50DA9, 0x5AD, 0x2B6, 0x3126E, 0x92E, 0x7192D, 0xC95, 0xD4A, 0x61B4A, 0xB55, 0x56A, 0x4155B, 0x25D, 0x92D, 0x2192B, 0xA95, 0x71695, 0x6CA, 0xB55, 0x50AB5, 0x4DA, 0xA5B, 0x30A57, 0x52B, 0x8152A, 0xE95, 0x6AA, 0x615AA, 0xAB5, 0x4B6, 0x414AE, 0xA57, 0x526, 0x31D26, 0xD95, 0x70B55, 0x56A, 0x96D, 0x5095D, 0x4AD, 0xA4D, 0x41A4D, 0xD25, 0x81AA5, 0xB54, 0xB6A, 0x612DA, 0x95B, 0x49B, 0x41497, 0xA4B, 0xA164B, 0x6A5, 0x6D4, 0x615B4, 0xAB6, 0x957, 0x5092F, 0x497, 0x64B, 0x30D4A, 0xEA5, 0x80D65, 0x5AC, 0xAB6, 0x5126D, 0x92E, 0xC96, 0x41A95, 0xD4A, 0xDA5, 0x20B55, 0x56A, 0x7155B, 0x25D, 0x92D, 0x5192B, 0xA95, 0xB4A, 0x416AA, 0xAD5, 0x90AB5, 0x4BA, 0xA5B, 0x60A57, 0x52B, 0xA93, 0x40E95);
+madd[0] = 0;
+madd[1] = 31;
+madd[2] = 59;
+madd[3] = 90;
+madd[4] = 120;
+madd[5] = 151;
+madd[6] = 181;
+madd[7] = 212;
+madd[8] = 243;
+madd[9] = 273;
+madd[10] = 304;
+madd[11] = 334;
+function GetBit(m, n){return (m >> n) & 1;}
+//农历转换
+function e2c() {
+	TheDate = (arguments.length != 3) ? new Date() : new Date(arguments[0], arguments[1], arguments[2]);
+	var total, m, n, k;
+	var isEnd = false;
+	var tmp = TheDate.getYear();
+	if (tmp < 1900) {tmp += 1900;}
+	total = (tmp - 1921) * 365 + Math.floor((tmp - 1921) / 4) + madd[TheDate.getMonth()] + TheDate.getDate() - 38;
+	if (TheDate.getYear() % 4 == 0 && TheDate.getMonth() > 1) {total++;}
+	for (m = 0; ; m++) {
+		k = (CalendarData[m] < 0xfff) ? 11 : 12;
+		for (n = k; n >= 0; n--) {
+			if (total <= 29 + GetBit(CalendarData[m], n)) {isEnd = true; break;}
+			total = total - 29 - GetBit(CalendarData[m], n);
+		}
+		if (isEnd) break;
+	}
+	cYear = 1921 + m;
+	cMonth = k - n + 1;
+	cDay = total;
+	if (k == 12) {
+		if (cMonth == Math.floor(CalendarData[m] / 0x10000) + 1) {cMonth = 1 - cMonth;}
+		if (cMonth > Math.floor(CalendarData[m] / 0x10000) + 1) {cMonth--;}
+	}
+}
+function GetcDateString() {
+	var tmp = "";
+	tmp += (cDay < 11) ? "初" : ((cDay < 20) ? "十" : ((cDay < 30) ? "廿" : "三十"));
+	if (cDay % 10 != 0 || cDay == 10) {tmp += numString.charAt((cDay - 1) % 10);}
+	return tmp;
+}
+function GetLunarDay(solarYear, solarMonth, solarDay) {
+	if (solarYear < 1921) {return "";}
+	solarMonth = (parseInt(solarMonth) > 0) ? (solarMonth - 1) : 11;
+	e2c(solarYear, solarMonth, solarDay);
+	return GetcDateString();
+}
+export default {
+	name: "graceCountd",
+	props: {
+		show : {
+			type : Boolean,
+			default : false
+		},
+		currentDate : {
+			type : String,
+			default : ""
+		},
+		isTime : {
+			type : Boolean,
+			default :true
+		},
+		top : {
+			type : String,
+			default : '0'
+		},
+		bgColor       : {type:String, default:"#F6F7F8"},
+		activeBgColor : {type:String, default:"#3688FF"},
+		borderRadius  : {type:String, default:"6rpx"},
+		isLunar       : {type:Boolean, default:true }
+	},
+	data(){
+		return {
+			weeks         : ['一', '二', '三', '四', '五', '六', '日'],
+			cYear         : 2016,
+			cMonth        : 6,
+			cMonthStr     : "06",
+			cDay          : "01",
+			days          : '',
+			currentDayIn : '',
+			currentTimeIn: ''
+		}
+	},
+	methods: {
+		timechange : function(e){
+			this.currentTimeIn = e.detail.value;
+		},
+		getDaysInOneMonth : function (){
+			var d = new Date(this.cYear, this.cMonth, 0);
+			return d.getDate();
+		},
+		getDay : function (){
+			var d = new Date(this.cYear, this.cMonth - 1, 0);
+			return d.getDay();
+		},
+		prevYear : function(){
+			this.cYear = this.cYear - 1;
+			this.changeMonth();
+		},
+		prevMonth : function(){
+			this.cMonth =  this.cMonth - 1;
+			if (this.cMonth < 1) { this.cMonth = 12; this.cYear = this.cYear - 1; }
+			this.cMonthStr = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+			this.changeMonth();
+		},
+		nextMonth : function(){
+			this.cMonth = this.cMonth + 1;
+			if (this.cMonth > 12){this.cMonth = 1; this.cYear = this.cYear + 1;}
+			this.cMonthStr = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+			this.changeMonth();
+		},
+		nextYear : function(){
+			this.cYear = this.cYear + 1;
+			this.changeMonth();
+		},
+		changeMonth:function(){
+			var daysList  = [];
+			var days      = this.getDaysInOneMonth();
+			var startWeek = this.getDay();
+			var forSteps  = 0;
+			for (var i = (0 - startWeek); i < days; i++){
+				if(i >= 0){
+					daysList[forSteps] = {date : i >= 9 ? i + 1 : '0' + (i+1), nl : ''};
+					daysList[forSteps].nl = GetLunarDay(this.cYear, this.cMonth, i + 1);
+				}else{
+					daysList[forSteps] = '';
+				}
+				forSteps++;
+			}
+			this.days    = daysList;
+		},
+		chooseDate: function (sedDate, isday) {
+			if(!isday){return ;}
+			this.currentDayIn = sedDate;
+			if(this.isTime){return ;}
+			this.$emit('changeDate', sedDate);
+		},
+		submit : function(){
+			if(this.isTime){
+				this.$emit('changeDate', this.currentDayIn+' '+this.currentTimeIn);
+			}else{
+				this.$emit('changeDate', this.currentDayIn);
+			}
+		},
+		close : function(){
+			this.$emit("closeDate");
+		},
+		//初始化时间
+		initTime : function(){
+			if(this.currentDate == ''){
+				var dateObj        = new Date();
+				this.cYear         = dateObj.getFullYear();
+				this.cMonth        = dateObj.getMonth() + 1;
+				this.cMonthStr     = this.cMonth < 10 ? '0' + this.cMonth : this.cMonth;
+				this.cDay          = dateObj.getDate();
+				this.cDay          = this.cDay < 10 ? '0' + this.cDay : this.cDay;
+				this.currentDayIn  = this.cYear + '-' + this.cMonthStr + '-' + this.cDay;
+				this.currentTimeIn = '00:00';
+				this.changeMonth();
+			}else{
+				var dates          = this.currentDate.split(' ');
+				if (!dates[1]) { dates[1] = '';}
+				var dayArr         = dates[0].split('-');
+				this.cYear         = Number(dayArr[0]);
+				this.cMonth        = dayArr[1];
+				this.cDay          = dayArr[2];
+				var reg            = new RegExp('^0[0-9]+$');
+				if(reg.test(this.cMonth)){this.cMonth = this.cMonth.substr(1,1);}
+				this.cMonth        = Number(this.cMonth);
+				this.cMonthStr     = this.cMonth < 10 ? '0'+this.cMonth : this.cMonth;
+				this.currentDayIn  = dates[0];
+				this.currentTimeIn = dates[1];
+				this.changeMonth();
+			}
+		}
+	},
+	created:function(){this.initTime();},
+	watch:{
+		currentDate  : function(){this.initTime();}
+	}
+}
+</script>
+<style scoped>
+.grace-date{position:fixed; left:0; top:0; bottom:0; width:750rpx; flex:1; padding:10rpx; align-items:center; background-color:#FFFFFF;}
+.grace-date-header{justify-content:center; flex-direction:row; text-align:center; margin-top:20rpx;}
+.grace-date-header-btn{font-size:36rpx; line-height:88rpx; padding:0 10rpx; color:#888888;}
+.grace-date-header-date{line-height:88rpx; font-size:36rpx; margin:0 20rpx;}
+.grace-date-week{text-align:center; width:702rpx; flex-wrap:nowrap; flex-direction:row;}
+.grace-date-weeks{width:100rpx; color:#666666; height:80rpx; text-align:center; font-size:32rpx; line-height:80rpx;}
+.grace-date-days{width:700rpx; flex-direction:row; flex-wrap:wrap;}
+.grace-date-ditems{width:96rpx; height:96rpx; margin:2rpx; background-color:#F6F7F8; border-radius:5rpx; flex-direction:column; align-items:center; justify-content:center;}
+.grace-d-current{background-color:#3688FF;}
+.grace-d-current-txt{color:#FFFFFF;}
+.grace-date-day{height:38rpx; line-height:38rpx; text-align:center; font-size:32rpx;}
+.grace-date-nl{height:26rpx; line-height:26rpx; color:#888888; font-size:20rpx; text-align:center;}
+.grace-date-btns{text-align:center; margin-top:10rpx; flex-direction:row; justify-content:space-between; position:absolute; left:0; bottom:20px; width:730rpx;}
+.grace-date-btns-text{color:#3688FF; line-height:100rpx; font-size:30rpx; text-align:center; width:300rpx;}
+.grace-date-time{font-size:30rpx; line-height:100rpx; margin-top:30rpx; color:#666666; border-bottom-style:solid; border-bottom-width:1px; border-bottom-color:#F1F2F3; border-top-style:solid; border-top-width:1px; border-top-color:#F1F2F3;}
+</style>

+ 157 - 0
lib/graceUI/components/graceNvueDateBetween.vue

@@ -0,0 +1,157 @@
+<template>
+	<view>
+		<view class="gui-date-bt-block" :style="{background:weekBg}">
+			<text class="gui-date-bt-item gui-date-bt-week">一</text>
+			<text class="gui-date-bt-item gui-date-bt-week">二</text>
+			<text class="gui-date-bt-item gui-date-bt-week">三</text>
+			<text class="gui-date-bt-item gui-date-bt-week">四</text>
+			<text class="gui-date-bt-item gui-date-bt-week">五</text>
+			<text class="gui-date-bt-item gui-date-bt-week">六</text>
+			<text class="gui-date-bt-item gui-date-bt-week">日</text>
+		</view>
+		<view v-for="(days, daysIndex) in daysData" :key="daysIndex">
+			<view class="gui-date-bt-month-w">
+				<text class="gui-date-bt-month" :style="{backgroundColor:startBg}">{{days[0][0]}}{{unit[0]}}{{days[0][1]}}{{unit[1]}}</text>
+			</view>
+			<view class="gui-date-bt-block">
+				<text class="gui-date-bt-item gui-date-bt-days" v-for="(item, index) in days[1]" :key="index" 
+				:style="{backgroundColor:item[1] ? sedBg : itemBg, color:item[1]?sedColor:color}" 
+				:data-dt="item[0] == '' ? '' : days[0][0]+''+days[0][1]+''+item[0]" @tap.stop="selectDay">{{item[0]}}<text class="gui-date-bt-start" v-if="sedDays[0] == days[0][0]+''+days[0][1]+''+item[0] || sedDays[1] == days[0][0]+''+days[0][1]+''+item[0]" 
+				:style="{backgroundColor:sedDays[1] == days[0][0]+''+days[0][1]+''+item[0] ? endBg : startBg, color:sedColor}">{{item[0]}}</text></text>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+/* 组件百分百原创 拒绝任何代码抄袭及思路抄袭 */
+export default{
+	props:{
+		weekBg:{type:String, default:'#F8F8F8'},
+		unit:{type:Array,default:function(){return [' 年 ',' 月'];}},
+		sedBg:{type:String, default:'rgba(54,136,255,0.88)'},
+		itemBg:{type:String, default:'#FFFFFF'},
+		color:{type:String, default:'#323232'},
+		sedColor:{type:String, default:'#FFFFFF'},
+		startBg:{type:String, default:'rgba(54,136,255,1)'},
+		endBg:{type:String,default:'rgba(54,136,255,1)'},
+		monthNumber:{type:Number,default:2}
+	},
+	data() {
+		return {
+			startYear:'1900',
+			startMonth:"01",
+			endYear:'1900',
+			endMonth:"01",
+			nextdays:[],
+			sedDays:[0,0],
+			btDays :[],
+			nextbtDays:[],
+			daysData : []
+		}
+	},
+	created:function(){
+		this.setMonth(this.currentMonth());
+	},
+	methods:{
+		setBetween:function(days){
+			if(days[1] == 0){days[1] = days[0];}
+			days[0] = Number(days[0]);
+			days[1] = Number(days[1]);
+			this.sedDays = days;
+			var countNumber = 0;
+			var daysNew = [];
+			this.daysData.forEach((itm)=>{
+				var year  = itm[0][0];
+				var month = itm[0][1];
+				var daysIn = [];
+				itm[1].forEach((item)=>{
+					var cDay = year+''+month+''+item[0];
+					cDay = Number(cDay);
+					if(cDay >= days[0] && cDay <= days[1]){
+						item[1] = true; 
+						countNumber++;
+					}else{
+						item[1] = false;
+					}
+					daysIn.push(item);
+				});
+				daysNew.push([[year, month], daysIn]);
+			});
+			this.daysData = daysNew;
+			this.$emit('selectDate', [days, countNumber]);
+		},
+		setMonth:function(month){
+			var reg = /^([0-9]{4}).*([0-9]{2}).*$/;
+			var res = month.match(reg);
+			if(res == null){month = this.currentMonth(); res = month.match(reg);}
+			this.setMonthBase(res);
+		},
+		setMonthBase : function(res){
+			var daysData = [];
+			if(res[2].substr(0,1) == '0'){res[2] = res[2].substr(1);}
+			res[1] = Number(res[1]);
+			res[2] = Number(res[2]);
+			for(let i = 0; i < this.monthNumber; i++){
+				var year  = res[1];
+				var month = res[2] + i;
+				if(month > 12){month = 1; year += 1;}
+				if(month < 10){month = '0'+month;}
+				daysData[i] = [];
+				daysData[i].push([year, month]);
+				var days = this.getDays(year,month);
+				var daysList  = [];
+				for (let ii = (0 - days[1]); ii < days[0]; ii++){
+					if(ii >= 0){
+						daysList.push([ii >= 9 ? ii + 1 : '0' + (ii+1), false]);
+					}else{
+						daysList.push(['',false]);
+					}
+				}
+				daysData[i].push(daysList);
+			}
+			this.daysData = daysData;
+		},
+		currentMonth : function () {
+			var date = new Date();
+			var y = date.getFullYear();
+			var m = date.getMonth() + 1;
+			m = m < 10 ? ('0' + m) : m;
+			return y + '年' + m + '月';
+		},
+		getDays : function(year,month){
+			var d    = new Date(year, month, 0);
+			var days = d.getDate();
+			var d2   = new Date(year, month - 1, 0);
+			var startWeek = d2.getDay();
+			return [days, startWeek];
+		},
+		selectDay:function(e){
+			var day = e.currentTarget.dataset.dt;
+			if(day == ''){return ;}
+			day = Number(day);
+			if(this.sedDays[0] == 0){ this.sedDays[0] = day; }
+			else if(this.sedDays[1] == 0 || this.sedDays[0] == this.sedDays[1]){
+				if(day > this.sedDays[0]){
+					this.sedDays[1] = day;
+				}else if(day < this.sedDays[0]){
+					this.sedDays[1] = this.sedDays[0];
+					this.sedDays[0] = day;
+				}
+			}else{
+				this.sedDays[0] = day;
+				this.sedDays[1] = 0;
+			}
+			this.setBetween(this.sedDays);
+		}
+	}
+}
+</script>
+<style>
+.gui-date-bt-block{width:700rpx; flex-direction:row; flex-wrap:wrap;}
+.gui-date-bt-item{width:100rpx; text-align:center; position:relative;}
+.gui-date-bt-week{line-height:66rpx; height:66rpx; font-size:28rpx;}
+.gui-date-bt-month-w{flex-direction:row; flex-wrap:nowrap; margin-top:15px; margin-bottom:15px;}
+.gui-date-bt-month{line-height:50rpx; text-align:center; color:#FFFFFF; width:258rpx; border-radius:30rpx; font-size:26rpx;}
+.gui-date-bt-days{line-height:100rpx; height:100rpx; font-size:28rpx;}
+.gui-date-bt-start{position:absolute; font-size:28rpx; width:100rpx; height:100rpx; text-align:center; line-height:100rpx; z-index:1; left:0; top:0;}
+</style>

+ 189 - 0
lib/graceUI/components/graceNvueDateTime.vue

@@ -0,0 +1,189 @@
+<template>
+	<view>
+		<view @tap.stop="open"><slot></slot></view>
+		<view class="graceDateTime" @touchmove.stop="" @tap.self="close" :style="{backgroundColor:background, width:show ? '750rpx' : '0px'}"></view>
+		<view class="graceDateTime-body" :style="{bottom : show ? '0px' : '-1000px', paddingBottom:paddingBottom}">
+			<view class="graceDateTime-header grace-space-between" v-if="isHeaderBar">
+				<text class="graceDateTime-header-btn" :style="{color:cancelTColor}" @tap="close">{{cancelText}}</text>
+				<text class="graceDateTime-header-btn" :style="{textAlign:'right', color:confirmColor}" @tap="confirm">{{confirmText}}</text>
+			</view>
+			<picker-view :indicator-style="indicatorStyle" class="graceDateTime-main" 
+			:value="defaultVal" @change="change" :style="{height:height}">
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[0]" :key="index">{{item}}{{units[0]}}</text>
+				</picker-view-column>
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[1]" :key="index">{{item}}{{units[1]}}</text>
+				</picker-view-column>
+				<picker-view-column>
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[2]" :key="index">{{item}}{{units[2]}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="isTime">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[3]" :key="index">{{item}}{{units[3]}}</text>
+				</picker-view-column>
+				<picker-view-column v-if="isTime">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[4]" :key="index">{{item}}{{units[4]}}</text>
+				</picker-view-column>
+				<picker-view-column  v-if="isTime && isSecond">
+					<text class="graceDateTime-item" v-for="(item, index) in sDate[5]" :key="index">{{item}}{{units[5]}}</text>
+				</picker-view-column>
+			</picker-view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		background:{ type : String, default : 'rgba(0, 0, 0, 0.5)' },
+		cancelText : { type : String, default : '取消' },
+		cancelTColor : { type : String, default : '#888888' },
+		confirmText : { type : String, default : '确定' },
+		confirmColor : { type : String, default : '#3688FF' },
+		value : { type : String , default:''},
+		isTime : {type : Boolean, default : true},
+		isSecond : {type : Boolean, default : true},
+		startYear : {type : Number, default : 1980},
+		endYear : {type : Number, default : 2050},
+		units : {type : Array , default:function(){return new Array('年','月','日','时','分','秒')}},
+		height:{ type : String, default : '300rpx' },
+		isHeaderBar : {type : Boolean, default : true},
+		paddingBottom :{ type : String, default : '0rpx' }
+	},
+	data() {
+		return {
+			show:false,
+			indicatorStyle : 'height:35px',
+			defaultVal     : [0,0,0,0,0,0],
+			sDate:[[],[],[],[],[],[]],
+			timer:null
+		}
+	},
+	created() {
+		this.init();
+	},
+	methods: {
+		now : function () {
+			var date = new Date();
+			var y = date.getFullYear();
+			var m = date.getMonth() + 1;
+			m = m < 10 ? ('0' + m) : m;
+			var d = date.getDate();
+			d = d < 10 ? ('0' + d) : d;
+			var h = date.getHours();
+			h = h < 10 ? ('0' + h) : h;
+			var minute = date.getMinutes();
+			var second = date.getSeconds();
+			minute = minute < 10 ? ('0' + minute) : minute;
+			second = second < 10 ? ('0' + second) : second;
+			return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;
+		},
+		arrayIndexOf : function(arr, needFind){
+			var index = -1;
+			for(let i = 0; i < arr.length; i++){if(arr[i] == needFind){index = i; return i;}}
+			return index;
+		},
+		setValue : function (val) {
+			if(val == ''){val = this.now();}
+			var reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/;
+			var res = val.match(reg);
+			if(res == null){
+				reg = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
+				res = val.match(reg);
+				if(res == null){
+					this.setValue(this.now());
+					return ;
+				}
+				res[4] = '00';
+				res[5] = '00';
+				res[6] = '00';
+			}
+			this.setDefaults([res[1],res[2],res[3],res[4],res[5],res[6]]);
+		},
+		setDefaults : function (res) {
+			for(let i = 0; i < res.length; i++){
+				var index = this.arrayIndexOf(this.sDate[i], res[i]);
+				if(index == -1){index = 0;}
+				this.defaultVal.splice(i, 1, index);
+			}
+			this.changeBase(this.defaultVal);
+		},
+		// 初始化组件
+		init:function(){
+			if(this.endYear < this.startYear){this.endYear = this.startYear + 10;}
+			var years     = new Array();
+			for(let i = this.startYear; i <= this.endYear; i++){years.push(i);}
+			var months     = new Array();
+			for(let i = 1; i <= 12; i++){if(i < 10){months.push('0'+i);}else{months.push(i);}}
+			var days     = new Array();
+			for(let i = 1; i <= 31; i++){if(i < 10){days.push('0'+i);}else{days.push(i);}}
+			var hours     =  new Array();
+			for(let i = 0; i < 24; i++){if(i < 10){hours.push('0'+i);}else{hours.push(i);}}
+			var minutes  =  new Array();
+			var seconds  =  new Array();
+			for(let i = 0; i < 60; i++){
+				if(i < 10){minutes.push('0'+i); seconds.push('0'+i);}else{minutes.push(i); seconds.push(i);}
+			}
+			this.sDate = [years, months, days, hours, minutes, seconds];
+			this.$nextTick(()=>{setTimeout(()=>{ this.setValue(this.value);},300);});
+		},
+		change : function (res) {
+			if(this.timer != null){clearTimeout(this.timer);}
+			this.timer = setTimeout(()=>{this.changeBase(res.detail.value);},500);
+		},
+		changeBase:function(res){
+			var date = new Date(this.sDate[0][res[0]], this.sDate[1][res[1]], 0);
+			var days = date.getDate();
+			var daysOut = new Array();
+			for(let i = 1; i <= days; i++){if(i < 10){daysOut.push('0'+i);}else{daysOut.push(i);}}
+			this.sDate.splice(2, 1, daysOut);
+			if(res[2] + 1 > days){res[2] = days - 1;}
+			this.defaultVal = res;
+			if(this.isTime){
+				var resdata = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var resdata = new Array(
+					this.sDate[0][this.defaultVal[0]],
+					this.sDate[1][this.defaultVal[1]],
+					this.sDate[2][this.defaultVal[2]]
+				)
+			}
+			this.$emit('change', resdata);
+		},
+		confirm:function () {
+			if(this.isTime){
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]],
+				this.sDate[3][this.defaultVal[3]],
+				this.sDate[4][this.defaultVal[4]],
+				this.sDate[5][this.defaultVal[5]]);
+			}else{
+				var res = new Array(this.sDate[0][this.defaultVal[0]],
+				this.sDate[1][this.defaultVal[1]],
+				this.sDate[2][this.defaultVal[2]])
+			}
+			this.$emit('confirm', res);
+			this.show = false;
+		},
+		open : function () {
+			this.show = true;
+		},
+		close : function () {
+			this.show = false;
+		}
+	}
+}
+</script>
+<style scoped>
+.graceDateTime{position:fixed; top:0; left:0; bottom:0; width:0rpx; flex:1;}
+.graceDateTime-body{background-color:#FFFFFF; position:fixed; bottom:-1000px; left:0; width:750rpx;}
+.graceDateTime-header{padding:25rpx;}
+.graceDateTime-header-btn{width:200rpx; line-height:38rpx; height:38rpx; font-size:28rpx;}
+.graceDateTime-main{width:750rpx;}
+.graceDateTime-item{height:35px; font-size:28rpx; line-height:35px; overflow:hidden; text-align:center;}
+</style>

+ 98 - 0
lib/graceUI/components/graceNvueDialog.vue

@@ -0,0 +1,98 @@
+<template>
+	<view class="grace-dialog-shade" v-if="show" @tap.stop="closeDialogByShade" @touchmove.stop="stopFun" :style="{backgroundColor:background}">
+		<view class="grace-dialog gdFadeIn" @tap.stop="stopFun" :style="{width:width, borderRadius:borderRadius}">
+			<text class="grace-dialog-title" v-if="isTitle" 
+			:style="{fontSize:titleSize, color:titleColor,fontWeight:titleWeight?'900':'',backgroundColor:titleBg, lineHeight:titleHeight}">{{title}}</text>
+			<view @tap.stop="stopFun"><slot name="content"></slot></view>
+			<text class="grace-dialog-close-btn grace-icons" :style="{color:closeBtnColor}" v-if="isCloseBtn" @tap.stop="closeDialog">&#xe632;</text>
+			<view v-if="isBtns"><slot name="btns"></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceDialog",
+	props: {
+		show : {
+			type : Boolean,
+			default : false
+		},
+		width : {
+			type : String,
+			default : '580rpx'
+		},
+		isCloseBtn: {
+			type : Boolean,
+			default : true
+		},
+		closeBtnColor : {
+			type : String,
+			default : '#FF0036'
+		},
+		isTitle : {
+			type : Boolean,
+			default : true
+		},
+		title : {
+			type : String,
+			default : ''
+		},
+		titleWeight : {
+			type : Boolean,
+			default : true
+		},
+		titleSize : {
+			type : String,
+			default : '28rpx'
+		},
+		titleColor : {
+			type : String,
+			default : '#333333'
+		},
+		isBtns : {
+			type : Boolean,
+			default : true
+		},
+		background:{
+			type : String,
+			default : 'rgba(0, 0, 0, 0.5)'
+		},
+		borderRadius : {
+			type : String,
+			default : '6rpx'
+		},
+		titleBg : {
+			type : String,
+			default : ''
+		},
+		titleHeight : {
+			type : String,
+			default : '100rpx'
+		},
+		canCloseByShade:{
+			type : Boolean,
+			default : true
+		}
+	},
+	data() {
+		return {
+			
+		}
+	},
+	methods:{
+		closeDialogByShade:function(){
+			if(this.canCloseByShade){this.closeDialog();}
+		},
+		closeDialog : function(){
+			this.$emit('closeDialog');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style scoped>
+.grace-dialog-shade{position:fixed; width:750rpx; flex:1; left:0; top:0; bottom:0; justify-content:center; align-items:center;}
+.grace-dialog{width:580rpx; background-color:#FFFFFF;}
+.grace-dialog-title{line-height:100rpx; font-size:30rpx; text-align:center;}
+.grace-dialog-close-btn{position:absolute; right:0px; top:0px; font-size:30rpx; width:80rpx; height:80rpx; line-height:80rpx; text-align:center;}
+</style>

+ 70 - 0
lib/graceUI/components/graceNvueDrawer.vue

@@ -0,0 +1,70 @@
+<template>
+	<view>
+		<view class="grace-drawer-shade" v-if="show" @click.stop="closeDrawer" @touchmove.stop="" :style="{backgroundColor:background}"></view>
+		<view ref="graceDrawerMenu" v-if="show" class="grace-drawer-nav" @tap.stop="stopFun" 
+		:style="{width:width+'rpx', left : direction == 'left' ? (width*-1)+'rpx' : 'none', top : top+'px', 
+			right : direction == 'right' ? (width*-1)+'rpx' : 'none', 
+			padding:padding, backgroundColor:slotBg}">
+			<view class="grace-drawer-main"><slot name="links"></slot></view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceDrawer",
+	props: {
+		width : {
+			type : Number,
+			default : 500
+		},
+		show : {
+			type : Boolean,
+			default : true
+		},
+		direction : {
+			type : String,
+			default : 'left'
+		},
+		background:{
+			type : String,
+			default : 'rgba(0, 0, 0, 0.5)'
+		},
+		slotBg:{
+			type : String,
+			default : 'rgba(255, 255, 255, 1)'
+		},
+		padding : {
+			type : String,
+			default : '30rpx'
+		},
+		top:{type:Number, value:0}
+	},
+	updated:function(){
+		if(this.show){
+			setTimeout(()=>{
+				var moveX = this.direction == 'left' ? uni.upx2px(this.width) + 'px' : (uni.upx2px(this.width) * -1) + 'px';
+				var animation = weex.requireModule('animation');
+				animation.transition(this.$refs.graceDrawerMenu, {
+					styles: {transform:'translate('+moveX+', 0)'},
+					duration:200, 
+					timingFunction: 'linear',
+					needLayout:false,
+					delay: 0 
+					}, function (){}
+				);
+			}, 100);
+		}
+	},
+	methods:{
+		closeDrawer : function(){
+			this.$emit('closeDrawer');
+		},
+		stopFun : function(){}
+	}
+}
+</script>
+<style scoped>
+.grace-drawer-shade{position:fixed; width:750rpx; bottom:0; top:0; left:0; background-color:rgba(0, 0, 0, 0.5);}
+.grace-drawer-nav{background-color:#FFFFFF; position:fixed; bottom:0; top:0;}
+.grace-drawer-main{}
+</style>

+ 129 - 0
lib/graceUI/components/graceNvueEditor.vue

@@ -0,0 +1,129 @@
+<template>
+<view class="grace-editor">
+	<textarea class="grace-editor-title" v-model="article.title" placeholder="# 请输入标题" auto-height />
+	<!-- 内容区域 -->
+	<view v-for="(item, index) in article.contents" :key="index" class="grace-editor-items">
+	<!-- 普通文本 -->
+	<textarea class="grace-editor-txt" maxlength="-1" :data-index="index" v-if="item.type == 'txt'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入文本内容" auto-height />
+	<!-- 居中标题 -->
+	<input type="text" class="grace-editor-center" maxlength="-1" :data-index="index" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 输入居中题目" v-else-if="item.type == 'center'" />
+	<!-- 图片 -->
+	<view class="grace-editor-img-wrap" v-else-if="item.type == 'img'">
+		<image :src="item.content" class="grace-editor-img" :data-index="index" mode="widthFix" @tap="deleteItem"></image>
+		<view class="grace-editor-img-error" v-if="item.error">
+			<view class="grace-flex-center">
+				<text class="grace-editor-img-error-text grace-red" style="font-size:32rpx;">图片上传失败!</text>
+			</view>
+			<view class="grace-flex-center grace-margin-top">
+				<text class="grace-editor-img-error-text grace-green">点击发布按钮重新上传</text>
+				<text class="grace-editor-img-error-text">或</text>
+				<text class="grace-editor-img-error-text grace-red" :data-index="index" @tap.stop="deleteItem">删除图片</text>
+			</view>
+		</view>
+	</view>
+	<!-- 引用 -->
+	<textarea class="grace-editor-quote" maxlength="-1" :data-index="index" v-else-if="item.type == 'quote'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入引用内容" auto-height />
+	<!-- 加粗 -->
+	<textarea class="grace-editor-strong" :data-index="index" maxlength="-1" v-else-if="item.type == 'strong'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 请在此处输入加粗内容" auto-height />
+	<!-- 链接 -->
+	<input type="text" class="grace-editor-link" :data-index="index" v-else-if="item.type == 'link'" 
+	@input="graceEditorInput" :value="item.content" placeholder="# 输入连接地址" />
+	<!-- 分割 -->
+	<text class="grace-editor-spline" :data-index="index" @tap="deleteItem" v-else-if="item.type == 'spline'">● ● ●</text>
+	</view>
+	<!-- 选项类型选择 -->
+	<view class="grace-space-between grace-editor-menus" :style="{paddingBottom:ipxHeight}">
+		<text class="grace-editor-icons grace-icons" data-type="txt" @tap="graceEditorAddItem">&#xe9e4;</text>
+		<text class="grace-editor-icons grace-icons" data-type="center" @tap="graceEditorAddItem">&#xe621;</text>
+		<text class="grace-editor-icons grace-icons" data-type="img" @tap="graceEditorAddItem">&#xe63d;</text>
+		<text class="grace-editor-icons grace-icons" data-type="quote" @tap="graceEditorAddItem">&#xe620;</text>
+		<text class="grace-editor-icons grace-icons" data-type="strong" @tap="graceEditorAddItem">&#xe640;</text>
+		<text class="grace-editor-icons grace-icons" data-type="link" @tap="graceEditorAddItem">&#xe61e;</text>
+		<text class="grace-editor-icons grace-icons" data-type="spline" @tap="graceEditorAddItem">&#xe61b;</text>
+	</view>
+	<!-- 底部占位 -->
+	<view style="height:200rpx; width:100%"></view>
+</view>
+</template>
+<script>
+export default{
+	data() {
+		return {
+			article : {title:'', contents:[]},
+			ipxHeight : 0
+		}
+	},
+	methods:{
+		graceEditorAddItem : function(e){
+			var type = e.currentTarget.dataset.type;
+			if(type == 'img'){
+				uni.chooseImage({
+					success:(e)=>{
+						var imgs = e.tempFilePaths;
+						for(let i = 0; i < imgs.length; i++){
+							this.article.contents.push({type:type,content:imgs[i]});
+						}
+						this.returnArt();
+					}
+				});
+			}else{
+				this.article.contents.push({type:type,content:''});
+				this.returnArt();
+			}
+		},
+		graceEditorInput : function(e){
+			var index = e.currentTarget.dataset.index;
+			var val   = e.detail.value;
+			if(val == ''){
+				this.article.contents.splice(index, 1);
+			}else{
+				this.article.contents[index].content = val;
+			}
+			this.returnArt();
+		},
+		deleteItem : function(e){
+			var index = e.currentTarget.dataset.index;
+			uni.showModal({
+				title:"提示",
+				content:"确定要删除项目吗?",
+				success:(e)=>{
+					if(e.confirm){this.article.contents.splice(index, 1); this.returnArt();}
+				}
+			})
+		},
+		returnArt : function(){
+			this.$emit('change', this.article);
+		},
+		setError : function(index){
+			this.article.contents[index].error = true;
+			this.article.contents.splice(index, 1, this.article.contents[index]);
+		},
+		setDefault : function (article) {
+			this.article = article;
+			this.returnArt();
+		}
+	}
+}
+</script>
+<style>
+/* 编辑器 */
+.grace-editor{padding:10rpx 25rpx; width:750rpx;}
+.grace-editor-title{padding:25rpx 0; border-bottom-width:1px; border-bottom-style:solid; border-color:#F1F2F3; width:700rpx; font-size:36rpx; font-weight:bold; line-height:50rpx; color:#555555;}
+.grace-editor-menus{width:750rpx; position:fixed; left:0; background-color:#F8F8F8; bottom:0; border-top-width:1px; border-top-style:solid; border-color:#F1F2F3; padding:0 25rpx;}
+.grace-editor-icons{width:700rpx; flex:1; height:88rpx; color:#666666; line-height:88rpx; text-align:center; font-size:38rpx !important;}
+.grace-editor-items{margin-top:10rpx; flex-direction:row; justify-content:center;}
+.grace-editor-txt{width:700rpx; font-size:26rpx; margin-top:15rpx; margin-bottom:15rpx;}
+.grace-editor-center{width:300rpx; text-align:center; font-size:28rpx; color:#333333; line-height:60rpx; padding:10rpx; border-bottom-width:1px; border-bottom-style:solid; border-color:#F1F2F3; font-weight:bold;}
+.grace-editor-img-wrap{width:700rpx; height:300rpx; overflow:hidden;  margin:10rpx 0; position:relative; font-size:0;}
+.grace-editor-img{width:700rpx; height:300rpx;}
+.grace-editor-img-error{position:absolute; width:700rpx; height:300rpx; left:0; top:0; background-color:rgba(0,0,0,0.5); flex-direction:column; align-items:center; justify-content:center;}
+.grace-editor-img-error-text{font-size:28rpx; margin:0 15rpx; color:#FFFFFF;}
+.grace-editor-quote{border-radius:8rpx; width:700rpx; color:#333333; background-color:#F8F8F8; margin-top:15rpx; margin-bottom:15rpx; font-size:26rpx;}
+.grace-editor-strong{width:700rpx; font-weight:bold; font-size:26rpx; margin-top:15rpx; margin-bottom:15rpx;}
+.grace-editor-link{width:700rpx; font-size:26rpx; margin-top:15rpx; margin-bottom:15rpx; color:#007AFF;}
+.grace-editor-spline{width:700rpx; line-height:60rpx; text-align:center; color:#8788A3; font-size:28rpx; opacity:0.6;}
+</style>

+ 26 - 0
lib/graceUI/components/graceNvueEmpty.vue

@@ -0,0 +1,26 @@
+<template>
+	<view class="grace-empty">
+		<text class="grace-empty-icon grace-icons" v-if="iconType == 1" :style="{fontSize:iconSize+'rpx', lineHeight:(iconSize+10)+'rpx', color:iconColor}">&#xe704;</text>
+		<text class="grace-empty-icon grace-icons" v-if="iconType == 2" :style="{fontSize:iconSize+'rpx', lineHeight:(iconSize+10)+'rpx', color:iconColor}">&#xe736;</text>
+		<text class="grace-empty-icon grace-icons" v-if="iconType == 3" :style="{fontSize:iconSize+'rpx', lineHeight:(iconSize+10)+'rpx', color:iconColor}">&#xe644;</text>
+		<text class="grace-empty-text" v-if="text != ''" :style="{fontSize:textSize+'rpx', lineHeight:(textSize+10)+'rpx', color:textColor}">{{text}}</text>
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		iconType  : { type: Number,  default : 1},
+		iconSize  : { type: Number,  default : 80},
+		iconColor : { type: String,  default : "#999999"},
+		textSize  : { type: Number,  default : 26},
+		textColor : { type: String,  default : "#999999"},
+		text      : { type: String,  default :''}
+	}
+}
+</script>
+<style scoped>
+.grace-empty{}
+.grace-empty-icon{text-align:center;}
+.grace-empty-text{padding:20rpx 0; text-align:center;}
+</style>

+ 12 - 0
lib/graceUI/components/graceNvueEmptyNew.vue

@@ -0,0 +1,12 @@
+<template>
+	<view class="grace-empty">
+		<slot name="img"></slot>
+		<slot name="text"></slot>
+		<slot name="other"></slot>
+	</view>
+</template>
+<script>
+</script>
+<style scoped>
+.grace-empty{flex-direction:column; justify-content:center; align-items:center;}
+</style>

+ 54 - 0
lib/graceUI/components/graceNvueFaceGroup.vue

@@ -0,0 +1,54 @@
+<template>
+	<view class="grace-face-group" :style="{height:(size + borderWidth * 2 ) +'rpx'}">
+		<view class="grace-face-items" v-if="isAddBtn" 
+		:style="{width:size+'rpx', height:size+'rpx'}" 
+		@tap.stop="addBtnClick">
+			<slot></slot>
+		</view>
+		<image :src="item" class="grace-face-items" v-for="(item, index) in items" :key="index"  :style="{
+			left:isAddBtn ? (space * (index+1)) + 'rpx' : (space * index) + 'rpx', 
+			borderWidth:borderWidth + 'rpx', borderColor:borderColor, borderRadius:size+'rpx', width:size+'rpx', height:size+'rpx'}">
+		</image>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		items : {
+			type : Array,
+			default : function () {
+				return new Array()
+			}
+		},
+		size : {
+			type : Number,
+			default : 80
+		},
+		space : {
+			type : Number,
+			default : 60
+		},
+		borderWidth : {
+			type : Number,
+			default : 4
+		},
+		borderColor : {
+			type : String,
+			default : '#F5F5F5'
+		},
+		isAddBtn : {
+			type : Boolean,
+			default : false
+		}
+	},
+	methods:{
+		addBtnClick : function () {
+			this.$emit('addBtnClicked');
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-face-group{}
+.grace-face-items{width:100rpx; height:100rpx; position:absolute; overflow:hidden;  border-radius:100rpx; font-size:0; borderStyle:solid;}
+</style>

+ 43 - 0
lib/graceUI/components/graceNvueFixedMsg.vue

@@ -0,0 +1,43 @@
+<template>
+	<view class="grace-fixed-msg" :style="{width:width+'rpx', bottom:bottom+'rpx', left:(( 750 - width ) / 2) + 'rpx'}" v-if="show" @tap.stop="tapme">
+		<text class="grace-fixed-msg-icon grace-icons" :style="{color:color}">&#xe666;</text>
+		<text class="grace-fixed-msg-text" :style="{color:color}">{{msg}}</text>
+		<text class="grace-fixed-msg-icon grace-icons" :style="{color:color}">&#xe601;</text>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		msg : {
+			type : String,
+			default : ""
+		},
+		color : {
+			type : String,
+			default : "#008CFF"
+		},
+		width : {
+			type : Number,
+			default : 300
+		},
+		bottom : {
+			type : Number,
+			default : 80
+		},
+		show : {
+			type : Boolean,
+			default : true
+		}
+	},
+	methods:{
+		tapme : function(){
+			this.$emit('tapme');
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-fixed-msg{width:300rpx; flex-direction:row; flex-wrap:nowrap; align-items:center; padding:0rpx 20rpx; height:70rpx; border-radius:50rpx; background-color:#F6F7F8; position:fixed; bottom:60rpx;}
+.grace-fixed-msg-icon{width:40rpx; height:40rpx; line-height:40rpx; font-size:30rpx; color:#3688FF; opacity:0.6; text-align:center;}
+.grace-fixed-msg-text{width:500rpx; line-height:70rpx; font-size:28rpx; color:#008CFF; height:70rpx; text-align:center; flex: 1;}
+</style>

+ 29 - 0
lib/graceUI/components/graceNvueFlex.vue

@@ -0,0 +1,29 @@
+<template>
+	<view :class="classes"><slot></slot></view>
+</template>
+<script>
+export default{
+	props:{
+		classes : {
+			type : Array,
+			default : function(){
+				return new Array()
+			}
+		}
+	}
+}
+</script>
+<style scoped>
+.row{flex-direction:row;}
+.column{flex-direction:column;}
+.wrap{flex-direction:row; flex-wrap:wrap;}
+.xreverse{flex-direction:row-reverse;}
+.nowrap{flex-direction:row; flex-wrap:nowrap;}
+.space-between{flex-direction:row; justify-content:space-between;}
+.left{justify-content:flex-start;}
+.right{justify-content:flex-end;}
+.xcenter{justify-content:center;}
+.top{align-items:flex-start;}
+.ycenter{align-items:center;}
+.bottom{align-items:flex-end;}
+</style>

+ 76 - 0
lib/graceUI/components/graceNvueFullLoading.vue

@@ -0,0 +1,76 @@
+<template>
+	<view class="grace-full-loading" v-if="graceFullLoading" :style="{backgroundColor:background}" @tap.stop="" @touchmove.stop="">
+		<image class="grace-full-loading-image" ref="loadingLogos" :src="logoUrl" mode="widthFix" :style="{width:size, height:size}"></image>
+		<text class="grace-full-loading-text" :style="{color:textColor, fontSize:fontSize}">{{text}}</text>
+	</view>
+</template>
+<script>
+var animation = weex.requireModule('animation');
+export default {
+	name: "graceFullLoading",
+	props: {
+		graceFullLoading: {
+			type    : Boolean,
+			default : false
+		},
+		logoUrl : {
+			type    : String,
+			default : ""
+		},
+		text : {
+			type    : String,
+			default : "Loading ..."
+		},
+		fontSize : {
+			type    : String,
+			default : "22rpx"
+		},
+		textColor : {
+			type    : String,
+			default : "#999999"
+		},
+		size : {
+			type    : String,
+			default : "138rpx"
+		},
+		background : {
+			type    : String,
+			default : 'rgba(255,255,255,1)'
+		}
+	},
+	updated : function(){
+		setTimeout(()=>{
+			var loadingLogos = this.$refs.loadingLogos;
+			this.animation1(loadingLogos);
+		}, 200);
+	},
+	methods: {
+		animation1 : function(loadingLogos){
+			var _self = this;
+			animation.transition(loadingLogos, {
+				styles: { opacity:0.3},
+				duration: 1000, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0 
+			}, function (){_self.animation2(loadingLogos);});
+		},
+		animation2 : function(loadingLogos){
+			var _self = this;
+			animation.transition(loadingLogos, {
+				styles: { opacity: 1},
+				duration: 1000, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0 
+				}, function (){_self.animation1(loadingLogos);}
+			);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-full-loading{width:750rpx; justify-content:center; align-items:center; background-color:rgba(255,255,255,0.9); position:fixed; left:0; top:0; bottom:0;}
+.grace-full-loading-image{width:150rpx; height:150rpx; border-radius:150rpx;}
+.grace-full-loading-text{line-height:50rpx; font-size:22rpx; margin-top:10rpx; text-align:center;}
+</style>

+ 47 - 0
lib/graceUI/components/graceNvueHeader.vue

@@ -0,0 +1,47 @@
+<template>
+	<view>
+		<view class="grace-header" :style="{backgroundImage:background, height:viewHeight+'px', 'padding-top':top+'px'}">
+			<slot></slot>
+		</view>
+		<!-- 占位 view -->
+		<view :style="{height:viewHeight+'px'}" v-if="isSeize"></view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		background:{
+			type : String,
+			default : "linear-gradient(to right, #3688FF, #3688FF)"
+		},
+		height:{
+			type : Number,
+			default : 90
+		},
+		haveStatusBar:{
+			type : Boolean,
+			default : true
+		},
+		isSeize: {
+		  type: Boolean,
+		  default: true
+		}
+	},
+	data(){
+		return{
+			top : 0,
+			BoundingWidth :'0px',
+			viewHeight : 80
+		}
+	},
+	created:function(){
+		var res         = uni.getSystemInfoSync();
+		this.top        = res.statusBarHeight;
+		this.viewHeight = uni.upx2px(this.height) + this.top;
+	}
+}
+</script>
+<style scoped>
+.grace-header{width:750rpx; position:fixed; left:0; top:0; height:44px; flex-direction:row; flex-wrap:nowrap;}
+.grace-header-main{width:750rpx; flex:1;}
+</style>

+ 47 - 0
lib/graceUI/components/graceNvueHeaderAlert.vue

@@ -0,0 +1,47 @@
+<template>
+	<view class="grace-alert" v-if="showIn">
+		<view class="grace-alert-body" :style="{'background-color':background}"  ref="gracealert">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+<script>
+var animation = weex.requireModule('animation');
+export default {
+	name: "graceHeaderAlert",
+	props: {
+		show    : {
+			type : Boolean,
+			default : false
+		},
+		background : {
+			type : String,
+			default : '#F1F2F3'
+		}
+	},
+	data() {
+		return {
+			showIn : false
+		}
+	},
+	watch: {
+		show : function(n , o){
+			this.showIn = true;
+			setTimeout(() => {
+				animation.transition(this.$refs.gracealert, {
+					styles         : {opacity:1},
+					duration       : 50, 
+					timingFunction : 'linear',
+					needLayout     : false,
+					delay          : 0 
+				});
+			}, 100);
+			setTimeout(function(){this.showIn = false;}.bind(this), 3000);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-alert{width:750rpx; flex-direction:row; flex-wrap:nowrap; justify-content:center; align-items:center;  position:fixed; left:0rpx; top:20rpx;}
+.grace-alert-body{width:600rpx; padding:20rpx 25rpx; border-radius:10rpx; background-color:#FFFFFF; flex-direction:row; flex-wrap:nowrap; justify-content:center; align-items:center; opacity:0.1;}
+</style>

+ 33 - 0
lib/graceUI/components/graceNvueImmersedStatusbar.vue

@@ -0,0 +1,33 @@
+<template>
+	<view>
+		<view class="graceImmersedStatusbar" :class="[isFixed ? 'graceImmersedStatusbar-fixed' : '']" :style="{height:height, 'background-image':bgColor}"></view>
+		<view :style="{height:height, width:'750rpx'}"></view>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		bgColor : {
+			type : String,
+			default : "none"
+		},
+		isFixed : {
+			type : Boolean,
+			default : false
+		}
+	},
+	data(){
+		return{
+			height : '0px'
+		}
+	},
+	created:function(){
+		var res = uni.getSystemInfoSync();
+		this.height = res.statusBarHeight  + 'px';
+	}
+}
+</script>
+<style scoped>
+.graceImmersedStatusbar{height:20px; width:750rpx;}
+.graceImmersedStatusbar-fixed{position: fixed; left:0; top:0;}
+</style>

+ 37 - 0
lib/graceUI/components/graceNvueLink.vue

@@ -0,0 +1,37 @@
+<template>
+	<text class="link" @tap="openUrlForApp(url)" :style="{color:color, lineHeight:lineHeight, fontSize:fontSize}">{{title}}</text>
+</template>
+<script>
+export default {
+	props: {
+		url:{
+			type : String,
+			default : ""
+		},
+		title : {
+			type : String,
+			default : ""
+		},
+		color:{
+			type : String,
+			default : "#3688FF"
+		},
+		fontSize : {
+			type : String,
+			default : "28rpx"
+		},
+		lineHeight : {
+			type : String,
+			default : "50rpx"
+		}
+	},
+	methods:{
+		openUrlForApp : function(url){
+			plus.runtime.openURL(url);
+		}
+	}
+}
+</script>
+<style scoped>
+.link{color:#3688FF;}
+</style>

+ 55 - 0
lib/graceUI/components/graceNvueLoading.vue

@@ -0,0 +1,55 @@
+<template>
+	<view class="grace-loading grace-ellipsis"  v-if="loadingType < 4">
+		<text class="grace-loading-icon grace-icons" ref="loadingIcon" v-if="loadingType === 1" :style="{color:iconColor}">&#xe9db;</text>
+		<text class="grace-loading-text" :style="{color:textColor}">{{loadingText[loadingType]}}</text>
+	</view>
+</template>
+<script>
+const animation = weex.requireModule('animation');
+export default {
+	props: {
+		loadingType: {
+			type : Number,
+			default: 0
+		},
+		loadingText : {
+			type  : Array,
+			default : function () {
+				return ["上拉加载更多", "正在努力加载", "已经加载全部数据",'',''];
+			}
+		},
+		iconColor : {
+			type : String,
+			default : "#888888"
+		},
+		textColor : {
+			type : String,
+			default : "#888888"
+		}
+	},
+	watch:{
+		loadingType : function (nval, oval) {
+			if(nval == 1){
+				setTimeout(()=>{this.rotate360();}, 300);
+			}
+		}
+	},
+	methods: {
+		rotate360 : function(){
+			var el = this.$refs.loadingIcon;
+			animation.transition(el, {
+				styles: {transform: 'rotate(7200deg)'},
+				duration: 20000,
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0
+			});
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-loading{flex-direction:row; flex-wrap:nowrap; justify-content:center; padding:16rpx 0; padding-bottom:36rpx; align-items:center;}
+.grace-loading-text{margin-left:12rpx; font-size:26rpx;}
+.grace-loading-icon{width:40rpx; height:40rpx; justify-content:center; line-height:40rpx; font-size:30rpx; text-align:center;}
+</style>

+ 55 - 0
lib/graceUI/components/graceNvueNavBar.vue

@@ -0,0 +1,55 @@
+<template>
+	<scroll-view
+	:class="['grace-nav-bar', isCenter ? 'grace-nav-center' : '']" :scroll-x="true" 
+	:scroll-into-view="'tab-'+currentIndex" :show-scrollbar="false">
+		<view class="nav-item" :id="'tab-'+index" 
+		:style="{width:size+'rpx', marginRight:margin+'rpx', padding:'0rpx '+padding}" 
+		v-for="(item, index) in items" :key="index" @click="navchang" :data-index="index">
+			<text :class="['nav-item-title', currentIndex == index ? 'nav-active' : '']"
+			:style="{color:currentIndex == index ? activeColor:color, width:size+'rpx', lineHeight:lineHeight, fontSize:currentIndex == index ? activeFontSize : fontSize, textAlign:textAlign, fontWeight:currentIndex == index ? activeFontWeight : ''}">{{item}}</text>
+			<view class="nav-active-line-wrap" :style="{justifyContent:activeDirection}">
+				<view class="nav-active-line" 
+				:style="{backgroundImage:activeLineBg, width:activeLineWidth, height:activeLineHeight, borderRadius:activeLineRadius}" 
+				v-if="currentIndex == index"></view>
+			</view>
+		</view>
+	</scroll-view>
+</template>
+<script>
+export default {
+	props:{
+		isCenter : {type : Boolean, default : false},
+		currentIndex : {type : Number,default : 0},
+		size : {type : Number, default : 120},
+		fontSize : {type : String,default : '28rpx'},
+		activeFontSize : {type : String, default : '28rpx'},
+		items : {type : Array,default : function () {return []}},
+		activeLineBg : {type : String, default : "linear-gradient(to right, #66BFFF,#3388FF)"},
+		color : {type : String, default : "#333333"},
+		activeColor:{type : String,	default : "#333333"},
+		activeLineHeight : {type : String, default : '6rpx'},
+		activeLineWidth : {type : String, default : "36rpx"},
+		activeDirection : {type : String, default : ""},
+		activeFontWeight: {type : Number, default : 700},
+		margin : {type : Number, default : 0},
+		textAlign : {type : String, default : 'left'},
+		lineHeight :{type : String, default : '50rpx'},
+		padding : {type : String, default : '0rpx'},
+		activeLineRadius : {type : String, default : "0rpx"}
+	},
+	methods:{
+		navchang : function (e){
+			this.$emit('change', Number(e.currentTarget.dataset.index))
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-nav-bar{flex-direction:row;}
+.nav-item{flex-direction:column;}
+.nav-item-title{width:100%; color:#333333;}
+.nav-active{}
+.nav-active-line-wrap{flex-direction:row;}
+.nav-active-line{margin-top:5rpx;}
+.grace-nav-center{justify-content:center; text-align:center;}
+</style>

+ 59 - 0
lib/graceUI/components/graceNvueNavBar2.vue

@@ -0,0 +1,59 @@
+<template>
+	<scroll-view
+	:class="['grace-nav-bar2', isCenter ? 'grace-nav2-center' : '']" :scroll-x="true" :show-scrollbar="false" 
+	:scroll-into-view="'tab-'+currentIndex">
+		<view class="nav2-item" :id="'tab-'+index" :style="{width:size+'rpx', height:(lineHeight+lineHeightSamll+18)+'rpx'}" 
+		v-for="(item, index) in items" :key="index" @click="navchang" :data-index="index">
+		<view class="nav2-active-line-in" :style="{width:size+'rpx'}">
+			<view class="nav2-active-line" :style="{borderColor:currentIndex == index ? activeColor : bgColor}"></view>
+		</view>
+			<view class="nav2-nav-body">
+				<text :class="['nav2-item-title', currentIndex == index ? 'nav-active' : '']"
+				:style="{
+					color:currentIndex == index ? activeColor : color, width:size+'rpx', 
+					lineHeight:lineHeight+'rpx', fontSize:currentIndex == index ? activeFontSize : fontSize, 
+					backgroundColor:bgColor,
+					fontWeight:currentIndex == index ? 'bold' : ''}">{{item.title}}</text>
+				<text :class="['nav2-item-desc', currentIndex == index ? 'nav-active' : '']"
+				:style="{color:currentIndex == index ? activeColor : descColor, lineHeight:lineHeightSamll+'rpx', 
+				fontSize:fontSizeSmall, backgroundColor:bgColor, width:size+'rpx'}">{{item.desc}}</text>
+			</view>
+		</view>
+	</scroll-view>
+</template>
+<script>
+export default {
+	props:{
+		isCenter : {type : Boolean, default : false},
+		currentIndex : {type : Number,default : 0},
+		size : {type : Number, default : 138},
+		fontSize : {type : String,default : '28rpx'},
+		activeFontSize: {type : String,default : '30rpx'},
+		lineHeight :{type : Number, default : 52},
+		fontSizeSmall: {type : String,default : '22rpx'},
+		lineHeightSamll :{type : Number, default : 28},
+		items : {type : Array,default : function () {return []}},
+		color : {type : String, default : "#333333"},
+		descColor : {type : String, default : "#999999"},
+		activeColor:{type : String,	default : "#3688FF"},
+		bgColor:{type : String, default : '#FFFFFF'}
+	},
+	methods:{
+		navchang : function (e){
+			this.$emit('change', Number(e.currentTarget.dataset.index))
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-nav-bar2{flex-direction:row;}
+.nav2-item{width:100rpx; flex-direction:column; padding-bottom:16rpx;}
+.nav2-item-title{color:#333333; text-align:center;}
+.nav2-item-desc{color:#666666; text-align:center; padding-bottom:6rpx;}
+.nav2-active{font-weight:bold;}
+.nav2-active-line-wrap{}
+.grace-nav2-center{justify-content:center; text-align:center;}
+.nav2-active-line-in{position:absolute; left:0; bottom:2rpx; flex-direction:row; justify-content:center;}
+.nav2-active-line{width:20rpx; height:20rpx; border-radius:50rpx; border-width:6rpx; border-style:solid; border-color:#FF0000;}
+.nav2-nav-body{position:absolute; left:0; top:0;}
+</style>

+ 71 - 0
lib/graceUI/components/graceNvueNubmerAnimate.vue

@@ -0,0 +1,71 @@
+<template>
+	<text :style="{fontSize:fontSize, fontWeight:fontWeight, color:color, lineHeight:lineHeight}">{{numAnimate}}</text>
+</template>
+<script>
+export default{
+	props:{
+		num : {
+			type : Number,
+			default : 0
+		},
+		stepNumber : {
+			type : Number,
+			default : 50
+		},
+		timer : {
+			type : Number,
+			default : 800
+		},
+		keepInt : {
+			type : Boolean,
+			default : false
+		},
+		fontSize:{
+			type : String,
+			default : '28rpx'
+		},
+		color:{
+			type : String,
+			default : '#333333'
+		},
+		lineHeight:{
+			type : String,
+			default : '50rpx'
+		},
+		fontWeight:{
+			type : String,
+			default : '400'
+		}
+	},
+	data() {
+		return {
+			numAnimate : 0,
+			intervalId : null
+		}
+	},
+	created:function(){
+		let timer = this.timer / this.stepNumber;
+		let step  = Math.floor((this.num / this.stepNumber) * 100) / 100;
+		this.intervalId = setInterval(() => {
+			// 正值 
+			if(this.num >= 0){
+				if(this.numAnimate >= this.num){
+					this.numAnimate = this.num;
+					clearInterval(this.intervalId);
+					return;
+				}
+			}else{
+				if(this.numAnimate <= this.num){
+					this.numAnimate = this.num;
+					clearInterval(this.intervalId);
+					return;
+				}
+			}
+			let  res = this.numAnimate + step;
+			this.numAnimate = this.keepInt ? parseInt(res) : Math.floor(res * 100) / 100;
+		}, timer);
+	}
+}
+</script>
+<style scoped>
+</style>

+ 130 - 0
lib/graceUI/components/graceNvueNumberBox.vue

@@ -0,0 +1,130 @@
+<template>
+	<view class="grace-number-box" :style="{width:width}">
+		<text class="grace-number-box-doBtn" @tap.stop="reduce" 
+		:style="{width:btnSize, height:btnSize, fontSize:btnFontSize, lineHeight:btnSize, color:btnColr}">-</text>
+		<input class="grace-number-box-input" type="number" v-model="inputNumber" :disabled="disabled" 
+		:style="{backgroundColor:inputBG, height:inputHeight, lineHeight:inputHeight, fontSize:inputFontSize, color:inputColor, padding:inputPadding, borderRadius:inputBorderRadius}"></input>
+		<text class="grace-number-box-doBtn" @tap.stop="add" 
+		:style="{width:btnSize, height:btnSize, fontSize:btnFontSize, lineHeight:btnSize, color:btnColr}">+</text>
+	</view>
+</template>
+<script>
+export default {
+	name: "graceNumberBox",
+	props: {
+		disabled :{
+			type : Boolean,
+			default : false
+		},
+		value : {
+			type : Number,
+			default : 0 
+		},
+		index : {
+			type : Number,
+			default : 0
+		},
+		maxNum:{
+			type: Number,
+			default: 999999
+		},
+		minNum: {
+			type: Number,
+			default: 0
+		},
+		datas:{
+			type: String,
+			default: ''
+		},
+		btnSize : {
+			type : String,
+			default : '60rpx'
+		},
+		btnFontSize : {
+			type : String,
+			default : '36rpx'
+		},
+		btnColr : {
+			type : String,
+			default : '#666666'
+		},
+		inputHeight : {
+			type : String,
+			default : '56rpx'
+		},
+		inputFontSize : {
+			type : String,
+			default : '26rpx'
+		},
+		inputColor : {
+			type : String,
+			default : '#333333'
+		},
+		inputBG : {
+			type : String,
+			default : '#F6F7F8'
+		},
+		inputPadding : {
+			type : String,
+			default : '10rpx'
+		},
+		inputBorderRadius : {
+			type : String,
+			default : '8rpx'
+		},
+		width:{
+			type : String,
+			default : '200rpx'
+		},
+		step : {
+			type : Number,
+			default : 1
+		}
+	},
+	data() {
+		return {
+			inputNumber: 0,
+			callbackNumber:0
+		}
+	},
+	created:function(){
+		this.inputNumber = Number(this.value);
+		this.callbackNumber++;
+	},
+	watch:{
+		value :function(val, vo){this.inputNumber = Number(val);},
+		inputNumber :function(val, vo){
+			val = Number(val);
+			if(isNaN(val)){ setTimeout(()=>{this.inputNumber = Number(vo);}, 200);  return; }
+			if(val > this.maxNum){
+				this.$emit('change', [this.maxNum, this.index, this.datas]);
+				setTimeout(()=>{this.inputNumber = this.maxNum;}, 200);
+				return ;
+			}
+			if(val < this.minNum){
+				this.$emit('change', [this.minNum, this.index, this.datas]);
+				setTimeout(()=>{this.inputNumber = this.minNum;}, 200);
+				return ;
+			}
+			if(val != vo && this.callbackNumber > 0){this.$emit('change', [val, this.index, this.datas]);}
+		}
+	},
+	methods: {
+		add : function(){
+			var newVal = Number(this.inputNumber) + Number(this.step);
+			if(newVal > this.maxNum){return ;}
+			this.inputNumber = Number(newVal);
+		},
+		reduce: function () {
+			var newVal = Number(this.inputNumber) - Number(this.step);
+			if(newVal < this.minNum){return ;}
+			this.inputNumber = newVal;
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-number-box{overflow:hidden; flex-direction:row; flex-wrap:nowrap; align-items:center;}
+.grace-number-box-doBtn{text-align:center; color:#666666;}
+.grace-number-box-input{text-align:center; width:700rpx; flex:1;}
+</style>

+ 87 - 0
lib/graceUI/components/graceNvueNumberKeyboard.vue

@@ -0,0 +1,87 @@
+<template>
+	<view>
+		<view class="grace-mask" @tap.stop="done" @touchmove.stop="" v-if="show"></view>
+		<view class="grace-keyboard" v-if="show">
+			<text class="grace-keyboard-res" v-if="showInputRes" 
+			:style="{color:resultColor, fontSize:resultSize}">{{resVal}}</text>
+			<view class="grace-keyboard-body">
+				<view class="grace-keyboard-left">
+					<text v-for="(item, index) in [1,2,3,4,5,6,7,8,9]" :key="index" 
+					class="grace-keyboard-keys" :class="[tapIndex == item ? 'keydown' : '']" :data-keynumber="item" @tap.stop="inputNow">{{item}}</text>
+					<text class="grace-keyboard-keys" :class="[tapIndex == 0 ? 'keydown' : '']" 
+					:style="{width: isPoint ? '259rpx':'538rpx'}" data-keynumber="0" @tap.stop="inputNow">0</text>
+					<text v-if="isPoint" class="grace-keyboard-keys" style="width:259rpx;" 
+					:class="[tapIndex == '.' ? 'keydown' : '']" data-keynumber="." @tap.stop="inputNow">.</text>
+				</view>
+				<view class="grace-keyboard-right">
+					<text class="grace-keyboard-keys grace-icons" 
+					:class="[tapIndex == -3 ? 'keydown' : '']" :data-keynumber="-3" @tap.stop="deleteNow">&#xe623;</text>
+					<text class="grace-keyboard-keys grace-keyboard-done" 
+					:style="{backgroundColor:confirmBtnColor}" @tap.stop="done">{{doneBtnName}}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		show         : { type : Boolean, default : false },
+		doneBtnName  : { type : String, default : "完成" },
+		isPoint      : { type : Boolean, default : true },
+		showInputRes : {type : Boolean, default : true},
+		value        : {type:String, default:''},
+		confirmBtnColor : {type:String, default:'#3688FF'},
+		resultColor  : {type:String, default:'#323232'},
+		resultSize   : {type:String, default:'32rpx'}
+	},
+	data() {
+		return {
+			resVal: '',
+			tapIndex : -1
+		}
+	},
+	created:function(){
+		this.resVal = this.value;
+	},
+	methods: {
+		inputNow : function (e){
+			var k = e.currentTarget.dataset.keynumber;
+			this.resVal += k+'';
+			this.tapIndex = k;
+			this.$emit('keyboardInput', k);
+			this.removeClass();
+		},
+		deleteNow : function (e){
+			var k = e.currentTarget.dataset.keynumber;
+			this.tapIndex = k;
+			this.resVal = this.resVal.substring(0, this.resVal.length - 1);
+			this.$emit('keyboardDelete');
+			this.removeClass();
+		},
+		done : function(e){
+			var k = e.currentTarget.dataset.keynumber;
+			this.tapIndex = k;
+			this.$emit('keyboardDone');
+			this.removeClass();
+		},
+		setVal : function (val) {
+			this.resVal = val;
+		},
+		removeClass : function () {
+			setTimeout(()=>{this.tapIndex = -1;}, 200);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-mask{background-color:rgba(255, 255, 255, 0); position:fixed; width:750rpx; flex:1; left:0; top:0; bottom:0;}
+.grace-keyboard{background-color:#F4F5F6; position:fixed; width:750rpx; left:0; bottom:0;}
+.grace-keyboard-body{flex-direction:row; justify-content:space-between; padding:10px 0;}
+.grace-keyboard-left{width:560rpx; flex-direction:row; flex-wrap:wrap; justify-content:space-between;}
+.grace-keyboard-right{width:188rpx; flex-direction:column; justify-content:space-between;}
+.grace-keyboard-keys{width:166rpx; height:100rpx; margin:10rpx; background-color:#FFFFFF; text-align:center; line-height:100rpx; border-radius:8rpx; font-weight:700; font-size:50rpx;}
+.grace-keyboard-done{height:340rpx; line-height:340rpx; font-size:36rpx; font-weight:400; color:#FFFFFF;}
+.grace-keyboard-res{line-height:60rpx; text-align:center; font-size:32rpx; font-weight:bold; padding-top:20rpx;}
+.keydown{background-color:#3688FF; color:#FFFFFF;}
+</style>

+ 156 - 0
lib/graceUI/components/graceNvuePage.vue

@@ -0,0 +1,156 @@
+<template>
+	<view class="gui-sbody" :style="{flex:flexVal}">
+		<!-- 占位 view -->
+		<view :style="{width:'750rpx', height:(headerHeight+statusBarHeight)+ 'px'}" v-if="customHeader"></view>
+		<!-- 页面主体 -->
+		<view class="gui-page-body" :style="{flex:flexVal}"><slot name="gBody"></slot></view>
+		<!-- 头部导航 -->
+		<view v-if="customHeader">
+			<view class="gui-page-header" 
+			:style="{backgroundColor:headerBG, borderBottomWidth:borderWidth, borderBottomColor:borderColor}">
+				<!-- 沉浸式状态栏 -->
+				<view class="gui-page-status-bar" :style="{height:statusBarHeight+'px', backgroundColor:statusBarBG}"></view>
+				<!-- 头部核心 -->
+				<view class="gui-page-header-nav" ref="gracePageHeader">
+					<view class="gui-header-main gui-flex-auto">
+						<slot name="gHeader"></slot>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 页面底部 -->
+		<view class="gui-page-footer" :style="{backgroundColor:footerBg}">
+			<slot name="gFooter"></slot>
+		</view>
+		<!-- 右下角悬浮按钮 -->
+		<view class="gui-page-rb-area" :style="{right:rbRight+'rpx', bottom:rbBottom+'rpx', width:rbWidth+'rpx'}"><slot name="gRTArea"></slot></view>
+		<!-- 全屏 loading -->
+		<view class="grace-page-loading" @tap.stop="" @touchmove.stop="" :style="{backgroundColor:loadingBG}" v-if="isLoading">
+			<view class="grace-page-loading-point">
+				<view class="grace-page-loading-points" ref="loadingPoints1" :style="{background:loadingPointBg}"></view>
+				<view class="grace-page-loading-points" ref="loadingPoints2" :style="{background:loadingPointBg}"></view>
+				<view class="grace-page-loading-points" ref="loadingPoints3" :style="{background:loadingPointBg}"></view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+var animation = weex.requireModule('animation');
+const dom = weex.requireModule('dom');
+export default{
+	props:{
+		customHeader : { type : Boolean, default : true },
+		headerHeight : { type : Number, default : 44 },
+		headerIndex  : { type : Number, default : 999 },
+		headerBG     : { type : String, default : '#FFFFFF'},
+		statusBarBG  : { type : String, default : '#FFFFFF'},
+		footerIndex  : { type : Number, default : 999},
+		rbWidth      : { type : Number, default : 100},
+		rbBottom     : { type : Number, default : 100 },
+		rbRight      : { type : Number, default : 20 },
+		footerBg     : { type : String, default : '' },
+		flexVal      : { type : String, default : ''},
+		borderWidth  : { type : String,  default : '0' },
+		borderColor  : { type : String,  default : '#D1D1D1' },
+		loadingBG    : { type : String,  default : 'rgba(255,255,255, 0.1)'},
+		isLoading    :  { type : Boolean, default : false },
+		loadingPointBg : {type : String,  default : '#3688FF'}
+	},
+	data() {
+		return {
+			statusBarHeight : 44,
+			iphoneXButtomHeight:0,
+			BoundingWidth : '0px',
+			animateCount : 0
+		}
+	},
+	watch:{
+		isLoading:function(val){
+			this.animateCount = 0;
+			if(val){ setTimeout(() => { this.ldAnimation(); }, 800);}
+		}
+	},
+	methods:{
+		ldAnimation : function(){
+			if(this.animateCount >= 10){return ;}
+			this.animateCount++;
+			var animations1 = this.$refs.loadingPoints1;
+			var animations2 = this.$refs.loadingPoints2;
+			var animations3 = this.$refs.loadingPoints3;
+			animation.transition(animations1, {
+				styles: { opacity:0.3, transform: 'scale(1)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0 
+			});
+			animation.transition(animations2, {
+				styles: { opacity:0.3, transform: 'scale(1)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 100 
+			});
+			animation.transition(animations3, {
+				styles: { opacity:0.3, transform: 'scale(1)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 200
+			},() => {this.ldAnimation2();});
+		},
+		ldAnimation2 : function(){
+			var animations1 = this.$refs.loadingPoints1;
+			var animations2 = this.$refs.loadingPoints2;
+			var animations3 = this.$refs.loadingPoints3;
+			animation.transition(animations1, {
+				styles: { opacity:1, transform: 'scale(1.3)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0 
+			});
+			animation.transition(animations2, {
+				styles: { opacity:1, transform: 'scale(1.3)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 100 
+			});
+			animation.transition(animations3, {
+				styles: { opacity:1, transform: 'scale(1.3)'},
+				duration: 500, 
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 200
+			},() => {this.ldAnimation();});
+		},
+		getHeaderHeight:function(){
+			return this.headerHeight + thi.statusBarHeight;
+		}
+	},
+	created:function(){
+		if(this.isLoading){this.ldAnimation();}
+		try {
+		    var system = uni.getSystemInfoSync();
+			this.statusBarHeight = system.statusBarHeight;
+		} catch (e){
+		    return null;
+		}
+	}
+}
+</script>
+<style scoped>
+.gui-sbody{width:750rpx;}
+.gui-page-header{width:750rpx; left:0; top:0; border-style:solid; position: fixed;}
+.gui-page-status-bar{width:750rpx; height:0;}
+.gui-page-header-nav{width:750rpx; flex-direction:row; flex-wrap:nowrap;}
+.gui-header-main{width:750rpx;}
+.gui-page-body{width:750rpx;}
+.gui-page-footer{width:750rpx; position:fixed; left:0; bottom:0; }
+.gui-page-rb-area{width:100rpx; position:fixed; right:20rpx; bottom:100rpx;}
+.grace-pf{position:fixed;}
+.grace-page-loading{width:750rpx; position:fixed; left:0; top:0; bottom:0; flex:1; flex-direction:column; justify-content:center; align-items:center;}
+.grace-page-loading-point{flex-direction:row; flex-wrap:nowrap; justify-content:center;}
+.grace-page-loading-points{width:20rpx; height:20rpx; background-color:#3688FF; border-radius:50rpx; margin:10rpx; opacity:0.5;}
+</style>

+ 56 - 0
lib/graceUI/components/graceNvuePopupMenu.vue

@@ -0,0 +1,56 @@
+<template name="gracePopupMenu">
+	<view>
+		<view 
+		class="grace-popup-mask" v-if="show" @tap.stop="hideMenu" @touchmove.stop="" 
+		:style="{backgroundColor:background}">
+			<view class="grace-popup-menu" v-if="show"
+			:style="{top:top+'px', right:right, backgroundColor:bgColor, width:menuWidth, borderRadius:borderRadius}">
+				<slot></slot>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: "gracePopupMenu",
+		props: {
+			show:{
+				type : Boolean,
+				default : false
+			},
+			top:{
+				type : Number,
+				default : 0
+			},
+			bgColor:{
+				type : String,
+				default :'#FFFFFF'
+			},
+			menuWidth :{
+				type : String,
+				default : '258rpx'
+			},
+			background : {
+				type : String,
+				default : 'rgba(0,0,0, 0.3)'
+			},
+			right:{
+				type : String,
+				default:'0rpx'
+			},
+			borderRadius:{
+				type : String,
+				default:'0rpx'
+			}
+		},
+		methods: {
+			hideMenu : function() {
+				this.$emit('hideMenu');
+			}
+		},
+	}
+</script>
+<style scoped>
+.grace-popup-menu{background-color:#FFFFFF; width:258rpx; padding:10rpx; right:0px; top:0px; position:absolute;}
+.grace-popup-mask{background-color:rgba(0,0,0, 0.3); width:750rpx; position:fixed; left:0; top:0; bottom:0;}
+</style>

+ 47 - 0
lib/graceUI/components/graceNvueProgressButton.vue

@@ -0,0 +1,47 @@
+<template>
+	<view class="graceProgressButton" :style="{height:height+'rpx', borderRadius:borderRadius, backgroundColor:background}">
+		<text 
+		:style="{fontSize:fontSize+'rpx', color:color, lineHeight:height+'rpx', textAlign:'center'}">{{val > 0 && val < 100 ? progressText + val + '%' : buttonText}}</text>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		height : {
+			type : Number,
+			default : 80
+		},
+		fontSize : {
+			type : Number,
+			default : 30
+		},
+		val : {
+			type : Number,
+			default : 0
+		},
+		buttonText : {
+			type : String,
+			default : ""
+		},
+		color : {
+			type : String,
+			default : "#FFFFFF"
+		},
+		background:{
+			type : String,
+			default : "#5CC72F"
+		},
+		borderRadius : {
+			type : String,
+			default : "6rpx"
+		},
+		progressText : {
+			type : String,
+			default : "已下载 : "
+		}
+	}
+}
+</script>
+<style scoped>
+.graceProgressButton{border-radius:0rpx; text-align:center;}
+</style>

+ 154 - 0
lib/graceUI/components/graceNvueReload.vue

@@ -0,0 +1,154 @@
+<template>
+	<view>
+		<view class="grace-reload" ref="guiReload" :style="{backgroundColor:reloadBgColor[reloadStatus], width:width, marginLeft:marginLeft}">
+			<text class="grace-reload-icon grace-icons" v-if="reloadStatus == 0" :style="{color:reloadColor[reloadStatus]}">&#xe633;</text>
+			<view ref="loadingIcon" v-if="reloadStatus == 1"><text class="grace-reload-icon grace-icons" :style="{color:reloadColor[reloadStatus]}">&#xe9db;</text></view>
+			<text class="grace-reload-icon grace-icons" v-if="reloadStatus == 2" :style="{color:reloadColor[reloadStatus]}">&#xe7f8;</text>
+			<text class="grace-reload-text" :style="{color:reloadColor[reloadStatus]}">{{reloadTxt[reloadStatus]}}</text>
+		</view>
+		<!-- view class="grace-reload-shade" @tap.stop="" @touchstart.stop="" @touchmove.stop="" v-if="reloadStatus != 5"></view -->
+	</view>
+</template>
+<script>
+var animation = null;
+export default{
+	props:{
+		reloadTxt    : {
+			type : Array,
+			default : function(){
+				return ['松开手指开始刷新', '数据刷新中', '数据已经刷新']
+			}
+		},
+		reloadBgColor : {
+			type : Array,
+			default : function(){
+				return ['', '', '#63D2BC']
+			}
+		},
+		reloadColor : {
+			type : Array,
+			default : function(){
+				return ['#999999', '#63D2BC', '#FFFFFF']
+			}
+		},
+		width : {
+			type : String,
+			default:'750rpx'
+		},
+		marginLeft : {
+			type : String,
+			default:'0rpx'
+		}
+	},
+	data() {
+		return {
+			reloadStatus : 5,
+			height : 0,
+			startY : 0,
+			startTime : 0,
+			el:null
+		}
+	},
+	watch:{
+		reloadStatus : function (nval, oval) {
+			if(nval == 1){
+				setTimeout(()=>{this.rotate360();}, 300);
+			}else if(nval == 0){
+				var el = this.$refs.guiReload;
+				animation = weex.requireModule('animation');
+				animation.transition(el, {
+					styles: {height:'50px', opacity:1},
+					duration: 200,
+					timingFunction: 'ease-in',
+					needLayout:true,
+					delay: 0
+				});
+			}else if(nval == 2){
+				var el = this.$refs.guiReload;
+				animation = weex.requireModule('animation');
+				animation.transition(el, {
+					styles: {height:'0', opacity:0},
+					duration: 200,
+					timingFunction: 'ease-in',
+					needLayout:true,
+					delay: 500
+				});
+			}
+		}
+	},
+	methods:{
+		/*
+		刷新状态 : 
+		5 无状态可以 进行下拉
+		0 继续下拉刷新
+		1 刷新中
+		2 刷新结束
+		3 不确认状态但下拉动作已经在执行
+		*/
+		touchstart:function (e) {
+			// 如果滚动条位置不在最顶部禁止刷新
+			if(e.scrollTop > 0){return ;}
+			if(this.reloadStatus != 5){return ;}
+			// 开始准备下拉
+			this.reloadStatus = 3;
+			this.startY = e.moveY;
+			this.startTime = new Date().getTime();
+		},
+		touchmove:function (e) {
+			if(this.reloadStatus == 3){
+				// 检查时间是否够长
+				var timer = new Date().getTime() - this.startTime;
+				if(timer < 200){return ;}
+				var moveY = e.moveY - this.startY;
+				if(moveY > 100){
+					this.reloadStatus = 0;
+					this.height       = 100;
+				}
+			}
+		},
+		touchend:function (e) {
+			switch(this.reloadStatus){
+				case 0 :
+					this.reloadStatus = 1; this.$emit('reload');
+				break;
+				case 1 :
+				break;
+				case 2 :
+				break;
+				case 3 :
+					this.reloadStatus = 5;
+				break;
+			}
+		},
+		endReload : function(){
+			this.reloadStatus = 2;
+			setTimeout(()=>{
+				this.height = 0;
+				this.reloadStatus = 5;
+			},1000)
+		},
+		rotate360 : function(){
+			this.el = this.$refs.loadingIcon;
+			animation = weex.requireModule('animation');
+			animation.transition(this.el, {
+				styles: {transform: 'rotate(7200deg)'},
+				duration: 20000,
+				timingFunction: 'linear',
+				needLayout:false,
+				delay: 0
+				}
+			);
+		},
+		stopReload : function () {
+			this.height = 0;
+			this.reloadStatus = 5;
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-reload{width:750rpx; height:0px; overflow:hidden; flex-direction:row; justify-content:center; align-items:center; opacity:0;}
+.grace-reload-icon{width:50rpx; height:50rpx; text-align:center; line-height:50rpx; font-size:44rpx;}
+.grace-reload-text{margin-left:16rpx; line-height:50rpx; font-size:26rpx;}
+.grace-reload-shade{position:fixed; width:750rpx; flex:1; left:0; top:0; bottom:0; background-color:rgba(255, 255, 255, 0); justify-content:center; align-items:center;}
+</style>

+ 58 - 0
lib/graceUI/components/graceNvueScroll.vue

@@ -0,0 +1,58 @@
+<template>
+	<view class="grace-scroll-body">
+		<text class="grace-scroll-msg" 
+		:style="{marginLeft:ml+'px', fontSize:fontSize, fontWeight:fontWeight, color:color}" ref="mainText">{{msg}}</text>
+	</view>
+</template>
+<script>
+const dom = weex.requireModule('dom');
+export default {
+	props:{
+		msg : {
+			type : String,
+			default : ""
+		},
+		fontSize : {
+			type : String,
+			default : "26rpx"
+		},
+		color : {
+			type : String,
+			default : "#333333"
+		},
+		fontWeight : {
+			type : String,
+			default : ""
+		}
+	},
+	data() {
+		return {
+			ml: 0
+		}
+	},
+	created:function(){
+		setTimeout(()=>{
+			var el = this.$refs.mainText;
+			dom.getComponentRect(el, (res) => {
+				this.animate(res.size.width);
+			});
+		}, 500);
+	},
+	methods:{
+		animate : function (w) {
+			if(this.ml <= w*-1){
+				this.ml = w;
+			}else{
+				this.ml = this.ml - 1;
+			}
+			setTimeout(()=>{
+				this.animate(w);
+			}, 10);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-scroll-body{line-height:60rpx; height:60rpx; overflow:hidden;}
+.grace-scroll-msg{line-height:60rpx; height:60rpx; white-space:nowrap; font-size:26rpx;}
+</style>

+ 64 - 0
lib/graceUI/components/graceNvueSearch.vue

@@ -0,0 +1,64 @@
+<template>
+	<view class="gui-search" :style="{height:height, backgroundColor:background,borderRadius:borderRadius}">
+		<text class="gui-search-icon grace-icons" 
+		:style="{color:iconColor, fontSize:iconFontSize, lineHeight:height, width:iconWidth}">&#xe604;</text>
+		<input type="text" class="gui-search-input" :placeholder="placeholder" v-model="inputVal" 
+		:style="{height:inputHeight, lineHeight:inputHeight, fontSize:inputFontSize, backgroundColor:background, color:inputColor}" 
+		@input="inputting" @confirm="confirm" confirm-type="search" v-if="!disabled" />
+		<text class="gui-search-input" :style="{height:inputHeight, lineHeight:inputHeight, fontSize:inputFontSize, backgroundColor:background, color:iconColor}" @tap.stop="tapme" v-if="disabled">{{placeholder}}</text>
+		<text class="gui-search-icon grace-icons" v-if="inputVal.length > 0" @tap.stop="clearKwd" 
+		:style="{color:iconColor, fontSize:iconFontSize, lineHeight:height, width:iconWidth}">&#xe632;</text>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		height:{type:String, default:'66rpx'},
+		background:{type:String, default:'#FFFFFF'},
+		fontSize:{type:String, default:'28rpx'},
+		iconWidth:{type:String, default:'60rpx'},
+		iconColor:{type:String, default:'#A5A7B2'},
+		iconFontSize:{type:String, default:'30rpx'},
+		inputHeight:{type:String, default:'30rpx'},
+		inputFontSize:{type:String, default:'26rpx'},
+		placeholder:{type:String, default:'关键字'},
+		kwd:{type:String, default:''},
+		borderRadius:{type:String, default:'66rpx'},
+		disabled:{type:Boolean, default:false},
+		inputColor:{type:String, default:'#323232'}
+	},
+	data() {
+		return {
+			inputVal : ''
+		}
+	},
+	created: function (){
+		this.inputVal = this.kwd;
+	},
+	watch:{
+		kwd : function(val){
+			this.inputVal = val;
+		}
+	},
+	methods:{
+		clearKwd : function () {
+			this.inputVal = '';
+			this.$emit('clear', '');
+		},
+		inputting : function(e){
+			this.$emit('inputting', e.detail.value);
+		},
+		confirm : function (e) {
+			this.$emit('confirm', e.detail.value);
+		},
+		tapme : function () {
+			this.$emit('tapme')
+		}
+	}
+}
+</script>
+<style scoped>
+.gui-search{border-radius:66rpx; padding:0 10rpx; flex-direction:row; flex-wrap:nowrap; align-items:center; overflow:hidden; flex:1;}
+.gui-search-icon{text-align:center;}
+.gui-search-input{width:600rpx; margin:0 10rpx; flex:1; border-width:0; padding:0;}
+</style>

+ 47 - 0
lib/graceUI/components/graceNvueSegmentedControl.vue

@@ -0,0 +1,47 @@
+<template>
+	<view class="gui-segmented-Control" :style="{backgroundColor:bgColor}">
+		<text class="gui-segmented-Control-item" v-for="(item, index) in items" :key="index" 
+		:style="{height:height, lineHeight:height, color:current == index ? '#FFFFFF' : color, backgroundColor:current == index ? color : bgColor, fontSize:fontSize}" @tap.stop="changeSC" :data-index="index">{{item}}</text>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		items : {
+			type : Array,
+			default : function () {
+				return new Array()
+			}
+		},
+		height : {
+			type : String,
+			default : '60rpx'
+		},
+		color : {
+			type : String,
+			default : '#3688FF'
+		},
+		fontSize : {
+			type : String,
+			default : '26rpx'
+		},
+		current : {
+			type : Number,
+			default : 0
+		},
+		bgColor : {
+			type : String,
+			default : '#F8F8F8'
+		}
+	},
+	methods:{
+		changeSC:function (e) {
+			this.$emit('change', Number(e.currentTarget.dataset.index));
+		}
+	}
+}
+</script>
+<style scoped>
+.gui-segmented-Control{flex-direction:row; flex-wrap:nowrap; background-color:#F5F6F8; border-radius:10rpx; padding:6rpx; width:750rpx; flex:1;}
+.gui-segmented-Control-item{width:700rpx; text-align:center; flex:1; border-radius:8rpx;}
+</style>

+ 95 - 0
lib/graceUI/components/graceNvueSelectImg.vue

@@ -0,0 +1,95 @@
+<template>
+	<view class="grace-add-list">
+		<view class="grace-add-list-items" v-for="(item, index) in imgLists" :key="index">
+			<image :src="item" :data-imgurl="item" @tap="showImgs" class="grace-add-list-img" :mode="imgMode"></image>
+			<text class="grace-add-list-remove grace-icons" @tap="removeImg" :id="'grace-items-img-'+index" :style="{color:closeBtnColor}">&#xe632;</text>
+		</view>
+		<view class="grace-add-list-items grace-add-list-btn" @tap="addImg" v-if="imgLists.length < maxFileNumber">
+			<text class="grace-add-list-btn-icon">+</text>
+			<text class="grace-add-list-btn-text">{{btnName}}</text>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		maxFileNumber : {
+			type : Number,
+			default : 9
+		},
+		btnName : {
+			type : String,
+			default : "添加照片"
+		},
+		items : {
+			type : Array,
+			default : function () {
+				return [];
+			}
+		},
+		closeBtnColor : {
+			type : String,
+			default : "#666666"
+		},
+		imgMode:{
+			type:String,
+			default:'widthFix'
+		}
+	},
+	data() {
+		return {
+			imgLists : []
+		}
+	},
+	created:function () {
+		this.imgLists = this.items;
+	},
+	watch:{
+		imgLists : function(newVal, oldVal){
+			this.$emit('change', newVal);
+		}
+	},
+    methods:{
+        addImg : function(){
+            var num = this.maxFileNumber - this.imgLists.length;
+            if(num < 1){return false;}
+            uni.showLoading({title:""});
+            uni.chooseImage({
+                count: num,
+                sizeType: ['compressed'],
+                success:(res) => {
+                    this.imgLists = this.imgLists.concat(res.tempFilePaths);
+                    uni.hideLoading();
+                },
+				fail:function(){
+					uni.hideLoading();
+				}
+            });
+        },
+        removeImg : function(e){
+            var index = e.currentTarget.id.replace('grace-items-img-', '');
+            this.imgLists.splice(index, 1);
+        },
+        showImgs : function(e){
+            var currentImg = e.currentTarget.dataset.imgurl;
+            uni.previewImage({
+              urls: this.imgLists,
+              current : currentImg
+            })
+        },
+		setItems : function(items){
+			this.imgLists = items;
+		}
+    }
+}
+</script>
+<style scoped>
+.grace-add-list{flex-wrap:wrap; flex-direction:row;}
+.grace-add-list-btn{flex-direction:column; align-items:center; justify-content:center;}
+.grace-add-list-btn-text{font-size:26rpx; line-height:36rpx; text-align:center; color:#999999;}
+.grace-add-list-btn-icon{font-size:70rpx; height:80rpx; line-height:80rpx; color:#999999;}
+.grace-add-list-items{width:222rpx; height:222rpx; overflow:hidden; margin-bottom:12rpx; margin-right:11rpx; background-color:#F6F7F8; font-size:0; border-radius:10rpx;}
+.grace-add-list-image{width:222rpx; height:222rpx;}
+.grace-add-list-remove{width:50rpx; height:50rpx; line-height:50rpx; text-align:center; font-size:40rpx; position:absolute; right:0; bottom:0; color:#888888;}
+.grace-add-list-img{width:222rpx; height:222rpx;}
+</style>

+ 216 - 0
lib/graceUI/components/graceNvueSelectImgAndUpload.vue

@@ -0,0 +1,216 @@
+<template>
+	<view class="grace-add-list">
+		<view class="grace-add-list-items" v-for="(item, index) in imgLists" :key="index">
+			<image 
+			:src="item.url" :data-imgurl="item.url" @tap="showImgs" @load="imgLoad" :data-index="index" class="grace-add-list-img" :mode="imgMode" 
+			:style="{width:'222rpx', height:item.height}"></image>
+			<text class="grace-add-list-remove grace-icons" :style="{color:closeBtnColor}" @tap.stop="removeImg" :id="'grace-items-img-'+index">&#xe632;</text>
+			<view class="upload-progress">
+			   <progress :percent="item.progress" :stroke-width="progressSize" :activeColor="progressColor" :backgroundColor="progressBGColor" />	
+			</view>
+			<view class="grace-add-list-reup" v-if="item.error">
+				<text class="grace-add-list-reup-icon grace-icons" @tap.stop="retry" :data-index="index">&#xe635;</text>
+				<text class="grace-add-list-reup-text">失败重试</text>
+			</view>
+		</view>
+		<view class="grace-add-list-items grace-add-list-btn" @tap="addImg" v-if="imgLists.length < maxFileNumber">
+			<text class="grace-add-list-btn-icon">+</text>
+			<text class="grace-add-list-btn-text">{{btnName}}</text>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		maxFileNumber : {
+			type : Number,
+			default : 9
+		},
+		btnName : {
+			type : String,
+			default : "添加照片"
+		},
+		items : {
+			type : Array,
+			default : function () {
+				return [];
+			}
+		},
+		closeBtnColor : {
+			type : String,
+			default : "#666666"
+		},
+		uploadServerUrl : {
+			type : String,
+			default : ''
+		},
+		progressSize :{
+			type:Number,
+			default:1
+		},
+		progressColor :{
+			type:String,
+			default:'#27BD81'
+		},
+		progressBGColor :{
+			type:String,
+			default:'#F8F8F8'
+		},
+		fileName : {type:String, default:'img'},
+		formData : {type:Object, default:function(){return {};}},
+		imgMode:{ type:String, default:'widthFix'},
+		header:{type:Object, default:function(){return {};}}
+	},
+	data() {
+		return {
+			imgLists : [],
+			updatting : false
+		}
+	},
+	watch:{
+		imgLists : function(newVal, oldVal){
+			if(!this.updatting){this.$emit('change', newVal);}
+		}
+	},
+    methods:{
+		clearAllImgs : function(){
+			this.imgLists = [];
+		},
+        addImg : function(){
+            var num = this.maxFileNumber - this.imgLists.length;
+            if(num < 1){return false;}
+            uni.showLoading({title:""});
+            uni.chooseImage({
+                count: num,
+                sizeType: ['compressed'],
+                success:(res) => {
+					for(let i = 0; i < res.tempFilePaths.length; i++){
+						this.imgLists.push({url : res.tempFilePaths[i], progress : 0, error : false});
+					}
+                    uni.hideLoading();
+                },
+				complete:function(){
+					uni.hideLoading();
+				},
+				fail:function(){
+					uni.hideLoading();
+				}
+            });
+        },
+        removeImg : function(e){
+            var index = e.currentTarget.id.replace('grace-items-img-', '');
+            this.imgLists.splice(index, 1);
+        },
+        showImgs : function(e){
+            var currentImg = e.currentTarget.dataset.imgurl;
+			var imgs = [];
+			for(let i = 0; i < this.imgLists.length; i++){
+				imgs.push(this.imgLists[i].url);
+			}
+            uni.previewImage({
+              urls: imgs,
+              current : currentImg
+            })
+        },
+		upload : function(index){
+			if(this.updatting){return ;}
+			this.updatting = true;
+			if(!index){index = 0;}
+			uni.showLoading({title:"图片上传中" });
+			this.uploadBase(index);
+		},
+		retry : function (e) {
+			var index = e.currentTarget.dataset.index;
+			this.upload(index);
+		},
+		uploadBase : function (index) {
+			// 全部上传完成
+			if(index > (this.imgLists.length - 1)){
+				uni.hideLoading();
+				this.updatting = false;
+				this.$emit('uploaded', this.imgLists);
+				return ;
+			}
+			// 验证后端
+			if(this.uploadServerUrl == ''){
+				uni.showToast({title:"请设置上传服务器地址", icon:"none"});
+				return ;
+			}
+			// 检查是否是默认值
+			if(this.imgLists[index].progress >= 1){
+				this.uploadBase(index+1);
+				return ;
+			}
+			this.imgLists[index].error = false;
+			// 创建上传对象
+			const task = uni.uploadFile({
+				url      : this.uploadServerUrl,
+				filePath : this.imgLists[index].url,
+				name     : this.fileName,
+				formData : this.formData,
+				header   : this.header,
+				success  : (uploadRes) => {
+					uploadRes = JSON.parse(uploadRes.data);
+					if(uploadRes.status != 'ok'){
+						uni.showToast({title:"上传失败 : "+uploadRes.data, icon:"none"});
+						this.error(index);
+					}else{
+						//上传图片成功
+						this.imgLists[index].progress = 100;
+						this.imgLists[index].url      = uploadRes.data;
+						this.imgLists[index].result   = uploadRes;
+						this.uploadBase(index+1);
+					}
+				},
+				fail    : (e) => {
+					uni.showToast({title:"上传失败,请点击图片重试", icon:"none"});
+					this.error(index);
+					
+				}
+			});
+			task.onProgressUpdate((res) => {
+				if(res.progress > 0){
+					this.imgLists[index].progress = res.progress;
+					this.imgLists.splice(index, 1, this.imgLists[index]);
+				}
+			});
+		},
+		// 上传错误
+		error : function(index){
+			this.updatting = false;
+			setTimeout(()=>{
+				this.imgLists[index].progress = 0;
+				this.imgLists[index].error    = true;
+				this.$emit('uploaderror');
+			}, 500);
+		},
+		// 设置默认值
+		setItems : function(items){
+			for(let i = 0; i < items.length; i++){
+				this.imgLists.push({url : items[i], progress : 100, error : false});
+			}
+		},
+		imgLoad : function (e) {
+			var height = e.detail.height;
+			var width  = e.detail.width;
+			var index  = e.currentTarget.dataset.index;
+			this.imgLists[index].height = (height/width*uni.upx2px(222)) + 'px';
+			this.imgLists.splice(index, 1, this.imgLists[index]);
+		}
+    }
+}
+</script>
+<style scoped>
+.grace-add-list{flex-wrap:wrap; flex-direction:row;}
+.grace-add-list-btn{flex-direction:column; align-items:center; justify-content:center;}
+.grace-add-list-btn-text{font-size:26rpx; line-height:36rpx; text-align:center; color:#999999;}
+.grace-add-list-btn-icon{font-size:80rpx; height:80rpx; line-height:80rpx; margin-bottom:20rpx; color:#999999;}
+.grace-add-list-items{width:222rpx; height:222rpx; overflow:hidden; margin-bottom:10rpx; margin-right:11rpx; background-color:#F6F7F8; font-size:0; border-radius:10rpx;}
+.grace-add-list-image{width:222rpx;}
+.grace-add-list-remove{width:50rpx; height:50rpx; line-height:50rpx; text-align:center; font-size:40rpx; position:absolute; right:10rpx; top:10rpx; color:#888888;}
+.upload-progress{position:absolute; left:22rpx; bottom:10rpx; width:180rpx;}
+.grace-add-list-reup{position:absolute; left:0; top:60rpx; width:222rpx; height:160rpx; flex-direction:column;}
+.grace-add-list-reup-icon{text-align:center; color:#FFFFFF; font-size:80rpx; line-height:80rpx;}
+.grace-add-list-reup-text{text-align:center; color:#FFFFFF; font-size:20rpx; line-height:30rpx;}
+.grace-add-list-img{width:222rpx;}
+</style>

+ 76 - 0
lib/graceUI/components/graceNvueSelectList.vue

@@ -0,0 +1,76 @@
+<template>
+	<view class="select-list">
+		<view v-for="(item, index) in dataIn" :key="index" class="select-list-item" :data-index="index" @tap.stop="choose">
+			<image :src="item.img" class="select-list-img" v-if="item.img" mode="widthFix" :style="{width:imgSize[0], height:imgSize[1]}"></image>
+			<view class="select-list-body" :class="[isBorder?'grace-border-b':'']">
+				<text class="select-list-content" :style="{fontSize:fontSize, lineHeight:lineHeight, color:itemColor}">{{item.title}}</text>
+				<text class="select-list-icon grace-icons" v-if="item.checked" :style="{color:item.checked ? checkColor : '#A5A7B2'}">&#xe60f;</text>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		lists : {type:Array, default:function(){return [];}},
+		checkColor:{type:String, default:"#3688FF"},
+		type : { type: String, default: "radio"},
+		imgSize:{type:Array, default:function(){return ['68rpx','68rpx'];}},
+		fontSize:{type: String, default: "28rpx"},
+		isBorder:{type:Boolean, default:true},
+		lineHeight:{type: String, default: "50rpx"},
+		itemColor:{type:String, default:"#323232"}
+	},
+	data() {
+		return {
+			dataIn: []
+		}
+	},
+	created:function(){
+		this.dataIn = this.lists;
+	},
+	watch:{
+		lists:function(val){
+			this.dataIn = val;
+		}
+	},
+	methods:{
+		choose:function(e){
+			var index = e.currentTarget.dataset.index;
+			if(this.type == 'radio'){
+				if(this.dataIn[index].checked){
+					this.dataIn[index].checked = false;
+					this.$emit('change', -1);
+				}else{
+					for(let i = 0; i < this.dataIn.length; i++){
+						this.dataIn[i].checked = false;
+					}
+					this.dataIn[index].checked = true;
+					this.$emit('change', index);
+				}
+			}else{
+				if(this.dataIn[index].checked){
+					this.dataIn[index].checked = false;
+				}else{
+					this.dataIn[index].checked = true;
+				}
+				var sedArr = [];
+				for(let i = 0; i < this.dataIn.length; i++){
+					if(this.dataIn[i].checked){
+						sedArr.push(i);
+					}
+				}
+				this.$emit('change', sedArr);
+			}
+		}
+	}
+}
+</script>
+<style>
+.select-list{}
+.select-list-item{flex-direction:row; flex-wrap:nowrap; align-items:center; font-size:0;}
+.select-list-icon{width:60rpx; line-height:60rpx; text-align:center; font-size:36rpx; color:#A5A7B2; font-weight:700; margin-left:28rpx;}
+.select-list-img{border-radius:60rpx; margin-right:28rpx;}
+.select-list-body{width:300rpx; flex:1; flex-wrap:nowrap; flex-direction:row; align-items:center;}
+.select-list-content{width:200rpx; flex:1; overflow:hidden; margin-top:25rpx; margin-bottom:25rpx;}
+</style>

+ 96 - 0
lib/graceUI/components/graceNvueSelectMenu.vue

@@ -0,0 +1,96 @@
+<template>
+	<view class="grace-select-menu-wrap">
+		<view class="grace-select-menu-title" @click.stop="showMenu" id="menumain">
+			<text class="grace-select-menu-title-txt" :style="{fontSize:fontSize}">{{items[currentIndex]}}</text>
+			<text class="grace-select-menu-title-icon grace-icons" v-if="!show">&#xe603;</text>
+			<text class="grace-select-menu-title-icon grace-icons" v-if="show">&#xe654;</text>
+		</view>
+		<view class="grace-select-menu" @click.stop="close" @touchmove.stop="" v-if="show">
+			<scroll-view class="grace-select-menus" scroll-y :style="{padding:padding, height:height+'rpx'}">
+				<view class="grace-select-item" v-for="(item, index) in items" :key="index" :data-index="index" @click.stop="select">
+					<text class="grace-selected-icon grace-icons" :style="{color : index == currentIndex ? activeColor : color}" v-if="index == currentIndex">&#xe7f8;</text>
+					<text class="grace-selected-text" :style="{color : index == currentIndex ? activeColor : color, fontSize:fontSize}">{{item}}</text>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	props:{
+		items : {
+			type : Array,
+			default : function () {
+				return []
+			}
+		},
+		show : {
+			type : Boolean,
+			default : false
+		},
+		height : {
+			type : Number,
+			default : 300
+		},
+		color : {
+			type : String,
+			default : "#333333"
+		},
+		activeColor : {
+			type : String,
+			default : "#3688FF"
+		},
+		selectIndex : {
+			type : Number,
+			default : 0
+		},
+		fontSize : {
+			type : String,
+			default : '26rpx'
+		},
+		padding:{
+			type : String,
+			default : "20rpx"
+		}
+	},
+	data() {
+		return {
+			currentIndex : 0,
+			top : 0
+		}
+	},
+	created : function () {
+		this.currentIndex = this.selectIndex;
+	},
+	watch:{
+		selectIndex : function () {
+			this.currentIndex = this.selectIndex;
+		}
+	},
+	methods:{
+		showMenu:function () {
+			this.$emit('showMenu');
+		},
+		close : function(){
+			this.$emit('close');
+		},
+		select : function(e){
+			var index = Number(e.currentTarget.dataset.index);
+			this.currentIndex = index;
+			this.$emit('select', index);
+			this.close();
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-select-menu-wrap{}
+.grace-select-menu-title{flex-direction:row; justify-content:center; flex-wrap:nowrap; align-items:center;}
+.grace-select-menu-title-txt{font-size:28rpx; line-height:92rpx;}
+.grace-select-menu-title-icon{margin-left:10rpx; font-size:22rpx; line-height:92rpx;}
+.grace-select-menu{position:fixed; width:750rpx; left:0; top:0; bottom:0; background-color:rgba(0,0,0,0.2); flex-direction:column; align-items:flex-end;}
+.grace-select-menus{width:750rpx; position:absolute; left:0; bottom:0; background-color:#FFFFFF;}
+.grace-select-item{padding:10rpx; background-color:#FFFFFF; font-size:28rpx; color:#333333; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:#F8F8F8; flex-direction:row; flex-wrap:nowrap; align-items:center;}
+.grace-selected-icon{font-size:28rpx; line-height:80rpx; margin-right:20rpx;}
+.grace-selected-text{font-size:28rpx; line-height:80rpx;}
+</style>

+ 127 - 0
lib/graceUI/components/graceNvueSelectTags.vue

@@ -0,0 +1,127 @@
+<template>
+	<view class="grace-select-tags">
+		<text class="grace-select-tag" 
+		:style="{
+			width : itemWidth, 'background-color' : item.checked ? selectedColor : bgColor, 
+			fontSize:fontSize, borderRadius:borderRadius, height:height, lineHeight:height, 
+			color : item.checked ? fontActiveColor : fontColor
+		}" 
+		@tap.stop="graceSelectChange(index)" v-for="(item, index) in tagsData" :key="index">{{item.name}}</text>
+	</view>
+</template>
+<script>
+export default {
+	props: {
+		type : { type: String, default: "radio"},
+		items : { type: Array, default : function(){return []}},
+		itemWidth : {type: String, default:"220rpx"},
+		selectedColor:{type: String, default:"#3688FF"},
+		fontSize : { type: String, default: "26rpx"},
+		bgColor :  { type: String, default: "#F6F7F8"},
+		height:{type: String, default:"68rpx"},
+		borderRadius : {type: String, default:"10rpx"},
+		fontColor:{type: String, default:"#323232"},
+		fontActiveColor:{type: String, default:"#FFFFFF"}
+	},
+	created : function(){
+		this.tagsData = this.items;
+	},
+	created : function(){
+		this.tagsData = this.items == null ? [] : this.items;
+		this.initChange();
+	},
+	data() {
+		return {
+			tagsData : []
+		}
+	},
+	watch:{
+		items:function(val){
+			this.tagsData = val;
+			this.initChange();
+		}
+	},
+	methods:{
+		initChange : function () {
+			if(this.type == 'radio'){
+				var selectIndex = -1;
+				var selectVal   = '';
+				for (var i = 0; i < this.tagsData.length; i++){
+					if(this.tagsData[i].checked){
+						selectIndex = i;
+						selectVal   = this.tagsData[i].value;
+					}
+				}
+				this.$emit("change", selectVal, this.datas, selectIndex);
+			}else{
+				var sedRes = [], indexs = [];
+				for (var i = 0; i < this.tagsData.length; i++){
+					if(this.tagsData[i].checked){
+						sedRes.push(this.tagsData[i].value);
+						indexs.push(i);
+					}
+				}
+				this.$emit("change", sedRes, this.datas, indexs);
+			}
+		},
+		graceSelectChange : function(index){
+			if(this.type == 'radio'){
+				for (var i = 0; i < this.tagsData.length; i++){
+					this.tagsData[i].checked = false;
+					this.tagsData.splice(i,1,this.tagsData[i]);
+				}
+				this.tagsData[index].checked = true;
+				this.tagsData.splice(index,1,this.tagsData[index]);
+				this.$emit("change", this.tagsData[index].value, this.datas, index);
+			}else{
+				if(this.tagsData[index].checked){
+					this.tagsData[index].checked = false;
+				}else{
+					this.tagsData[index].checked = true;
+				}
+				var sedRes = [], indexs = [];
+				for (var i = 0; i < this.tagsData.length; i++){
+					if(this.tagsData[i].checked){
+						sedRes.push(this.tagsData[i].value);
+						indexs.push(i);
+					}
+				}
+				this.$emit("change", sedRes, this.datas, indexs);
+			}
+		},
+		setItems : function(items){
+			this.tagsData = items;
+			this.initChange();
+		},
+		clearSelected:function(){
+			var newData = [];
+			for(let i = 0; i < this.tagsData.length; i++){
+				this.tagsData[i].checked = false;
+				newData.push(this.tagsData[i]);
+			}
+			this.tagsData = newData;
+			if(this.type == 'radio'){
+				this.$emit("change", '', this.datas, -1);
+			}else{
+				this.$emit("change", [], this.datas, []);
+			}
+		},
+		selectAll : function () {
+			if(this.type == 'radio'){return ;}
+			var newData = [],reDatas = [], reIndex = [];
+			for(let i = 0; i < this.tagsData.length; i++){
+				this.tagsData[i].checked = true;
+				newData.push(this.tagsData[i]);
+				reDatas.push(this.tagsData[i].value);
+				reIndex.push(i);
+			}
+			this.tagsData = newData;
+			this.$emit("change", reDatas, this.datas, reIndex);
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-select-tags{padding:0; flex-direction:row; flex-wrap:wrap;}
+.grace-select-tag{height:68rpx; line-height:68rpx; text-align:center; margin:10rpx; padding-left:15rpx; padding-right:15rpx; font-size:26rpx; border-radius:8rpx;}
+</style>

+ 22 - 0
lib/graceUI/components/graceNvueShade.vue

@@ -0,0 +1,22 @@
+<template>
+	<view class="grace-shade" :style="{backgroundColor:background}" v-if="show" @tap.stop="closeShade" @touchmove.stop="">
+		<slot></slot>
+	</view>
+</template>
+<script>
+export default{
+	props:{
+		background : {type:String, default:"rgba(0, 0, 0, 0.1)"},
+		zIndex : {type:Number, default:1},
+		show : {type:Boolean, default:true}
+	},
+	methods:{
+		closeShade : function(){
+			this.$emit('closeShade');
+		}
+	}
+}
+</script>
+<style scoped>
+.grace-shade{position:fixed; width:750rpx; left:0; top:0; bottom:0; z-index:1; justify-content:center; align-items:center;}
+</style>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů